Merge analyze tui with trufflehog tui (#3735)

* Move pkg/analyzer/tui to pkg/tui/analyzer

* [WIP] Wire up arguments into the TUI package

* WIP - moving analyze inside tui

Co-authored-by: mcastorina <[email protected]>

* [WIP] Analyze page

Co-authored-by: hxnyk <[email protected]>

* Add SetSize to KeyTypePage

* Use pageHistory stack for proper history traversal

Co-authored-by: hxnyk <[email protected]>

* Ongoing work - separate analyze pages

Co-authored-by: mcastorina <[email protected]>

* Set size on list

Co-authored-by: mcastorina <[email protected]>

* Pass analyzer as message

Co-authored-by: mcastorina <[email protected]>

* fix args for analyze

Co-authored-by: mcastorina <[email protected]>

* Actually run the analysis

Co-authored-by: hxnyk <[email protected]>

* Remove analyze_old

Co-authored-by: hxnyk <[email protected]>

* remove old comments

Co-authored-by: mcastorina <[email protected]>

* Remove unused analyzeKeyType

---------

Co-authored-by: Miccah Castorina <[email protected]>
This commit is contained in:
Hon
2025-02-07 12:09:22 -08:00
committed by GitHub
co-authored by mcastorina
parent ad8fd36a62
commit 03ca8aaa08
6 changed files with 228 additions and 267 deletions
+51 -41
View File
@@ -269,8 +269,8 @@ func init() {
// Support -h for help
cli.HelpFlag.Short('h')
if len(os.Args) <= 1 && isatty.IsTerminal(os.Stdout.Fd()) {
args := tui.Run()
if isatty.IsTerminal(os.Stdout.Fd()) && (len(os.Args) <= 1 || os.Args[1] == analyzeCmd.FullCommand()) {
args := tui.Run(os.Args[1:])
if len(args) == 0 {
os.Exit(0)
}
@@ -520,46 +520,56 @@ func run(state overseer.State) {
return
}
topLevelSubCommand, _, _ := strings.Cut(cmd, " ")
switch topLevelSubCommand {
case analyzeCmd.FullCommand():
analyzer.Run(cmd)
default:
metrics, err := runSingleScan(ctx, cmd, engConf)
if err != nil {
logFatal(err, "error running scan")
}
verificationCacheMetrics := struct {
Hits int32
Misses int32
HitsWasted int32
AttemptsSaved int32
VerificationTimeSpentMS int64
}{
Hits: verificationCacheMetrics.ResultCacheHits.Load(),
Misses: verificationCacheMetrics.ResultCacheMisses.Load(),
HitsWasted: verificationCacheMetrics.ResultCacheHitsWasted.Load(),
AttemptsSaved: verificationCacheMetrics.CredentialVerificationsSaved.Load(),
VerificationTimeSpentMS: verificationCacheMetrics.FromDataVerifyTimeSpentMS.Load(),
}
// Print results.
logger.Info("finished scanning",
"chunks", metrics.ChunksScanned,
"bytes", metrics.BytesScanned,
"verified_secrets", metrics.VerifiedSecretsFound,
"unverified_secrets", metrics.UnverifiedSecretsFound,
"scan_duration", metrics.ScanDuration.String(),
"trufflehog_version", version.BuildVersion,
"verification_caching", verificationCacheMetrics,
)
if metrics.hasFoundResults && *fail {
logger.V(2).Info("exiting with code 183 because results were found")
os.Exit(183)
}
metrics, err := runSingleScan(ctx, cmd, engConf)
if err != nil {
logFatal(err, "error running scan")
}
verificationCacheMetricsSnapshot := struct {
Hits int32
Misses int32
HitsWasted int32
AttemptsSaved int32
VerificationTimeSpentMS int64
}{
Hits: verificationCacheMetrics.ResultCacheHits.Load(),
Misses: verificationCacheMetrics.ResultCacheMisses.Load(),
HitsWasted: verificationCacheMetrics.ResultCacheHitsWasted.Load(),
AttemptsSaved: verificationCacheMetrics.CredentialVerificationsSaved.Load(),
VerificationTimeSpentMS: verificationCacheMetrics.FromDataVerifyTimeSpentMS.Load(),
}
// Print results.
logger.Info("finished scanning",
"chunks", metrics.ChunksScanned,
"bytes", metrics.BytesScanned,
"verified_secrets", metrics.VerifiedSecretsFound,
"unverified_secrets", metrics.UnverifiedSecretsFound,
"scan_duration", metrics.ScanDuration.String(),
"trufflehog_version", version.BuildVersion,
"verification_caching", verificationCacheMetricsSnapshot,
)
if metrics.hasFoundResults && *fail {
logger.V(2).Info("exiting with code 183 because results were found")
os.Exit(183)
}
// Print results.
logger.Info("finished scanning",
"chunks", metrics.ChunksScanned,
"bytes", metrics.BytesScanned,
"verified_secrets", metrics.VerifiedSecretsFound,
"unverified_secrets", metrics.UnverifiedSecretsFound,
"scan_duration", metrics.ScanDuration.String(),
"trufflehog_version", version.BuildVersion,
)
if metrics.hasFoundResults && *fail {
logger.V(2).Info("exiting with code 183 because results were found")
os.Exit(183)
}
}
func compareScans(ctx context.Context, cmd string, cfg engine.Config) error {
+8 -29
View File
@@ -1,11 +1,9 @@
package analyzer
import (
"fmt"
"strings"
"github.com/alecthomas/kingpin/v2"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/airbrake"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/asana"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/bitbucket"
@@ -28,37 +26,18 @@ import (
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/stripe"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/twilio"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/tui"
)
var (
// TODO: Add list of supported key types.
analyzeKeyType *string
)
func Command(app *kingpin.Application) *kingpin.CmdClause {
cli := app.Command("analyze", "Analyze API keys for fine-grained permissions information.")
keyTypeHelp := fmt.Sprintf(
"Type of key to analyze. Omit to interactively choose. Available key types: %s",
strings.Join(analyzers.AvailableAnalyzers(), ", "),
)
// Lowercase the available analyzers.
availableAnalyzers := make([]string, len(analyzers.AvailableAnalyzers()))
for i, a := range analyzers.AvailableAnalyzers() {
availableAnalyzers[i] = strings.ToLower(a)
}
analyzeKeyType = cli.Arg("key-type", keyTypeHelp).Enum(availableAnalyzers...)
return cli
type SecretInfo struct {
Parts map[string]string
Cfg *config.Config
}
func Run(cmd string) {
keyType, secretInfo, err := tui.Run(*analyzeKeyType)
if err != nil {
// TODO: Log error.
return
}
func Command(app *kingpin.Application) *kingpin.CmdClause {
return app.Command("analyze", "Analyze API keys for fine-grained permissions information.")
}
func Run(keyType string, secretInfo SecretInfo) {
if secretInfo.Cfg == nil {
secretInfo.Cfg = &config.Config{}
}
-122
View File
@@ -1,122 +0,0 @@
package tui
import (
"errors"
"fmt"
"strings"
tea "github.com/charmbracelet/bubbletea"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/keymap"
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/styles"
)
// TUI is the main TUI model.
type TUI struct {
keyType string
secretInfo *SecretInfo
common *common.Common
model tea.Model
}
type SecretInfo struct {
Parts map[string]string
Cfg *config.Config
}
var AbortError error = errors.New("command aborted")
func Run(keyType string) (string, *SecretInfo, error) {
// If a keyType is provided, make sure it's in the list of AvailableAnalyzers.
if keyType != "" {
var found bool
for _, a := range analyzers.AvailableAnalyzers() {
if strings.EqualFold(a, keyType) {
keyType = a
found = true
break
}
}
if !found {
return "", nil, fmt.Errorf("Unrecognized command %q", keyType)
}
}
t := &TUI{
keyType: keyType,
common: &common.Common{
KeyMap: keymap.DefaultKeyMap(),
},
}
if _, err := tea.NewProgram(t).Run(); err != nil {
return "", nil, err
}
if t.secretInfo == nil {
return "", nil, AbortError
}
return t.keyType, t.secretInfo, nil
}
func (ui *TUI) Init() tea.Cmd {
return nil
}
func (ui *TUI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if msg, ok := msg.(tea.WindowSizeMsg); ok {
ui.SetSize(msg)
}
// Always be able to force quit.
if msg, ok := msg.(tea.KeyMsg); ok && msg.Type.String() == "ctrl+c" {
return ui, tea.Quit
}
switch m := msg.(type) {
case SetKeyTypeMsg:
ui.keyType = string(m)
case SecretInfo:
ui.secretInfo = &m
return ui, tea.Quit
}
if ui.model == nil {
return ui, nil
}
var cmd tea.Cmd
ui.model, cmd = ui.model.Update(msg)
return ui, cmd
}
func (ui *TUI) View() string {
if ui.model == nil {
return "Loading..."
}
return ui.model.View()
}
func (ui *TUI) SetSize(msg tea.WindowSizeMsg) {
h, v := styles.AppStyle.GetFrameSize()
h, v = msg.Width-h, msg.Height-v
ui.common.SetSize(h, v)
if ui.model != nil {
return
}
// Set the model only after we have size information.
// TODO: Responsive pages.
if ui.keyType == "" {
ui.model = NewKeyTypePage(ui.common)
} else {
ui.model = NewFormPage(ui.common, ui.keyType)
}
}
type SetKeyTypeMsg string
func SetKeyTypeCmd(keyType string) tea.Cmd {
return func() tea.Msg {
return SetKeyTypeMsg(keyType)
}
}
@@ -1,26 +1,38 @@
package tui
package analyze_form
import (
"fmt"
"slices"
"strings"
"github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
"github.com/charmbracelet/lipgloss"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/components/textinputs"
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/styles"
)
type FormPage struct {
Common *common.Common
var (
titleStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("#FFFDF5")).
Background(lipgloss.Color(styles.Colors["bronze"])).
Padding(0, 1)
)
type AnalyzeForm struct {
common.Common
KeyType string
form textinputs.Model
}
func NewFormPage(c *common.Common, keyType string) FormPage {
type Submission struct {
AnalyzerType string
AnalyzerInfo analyzer.SecretInfo
}
func New(c common.Common, keyType string) *AnalyzeForm {
var inputs []textinputs.InputConfig
switch strings.ToLower(keyType) {
case "twilio":
@@ -65,25 +77,31 @@ func NewFormPage(c *common.Common, keyType string) FormPage {
SetHeader(titleStyle.Render(fmt.Sprintf("Configuring %s analyzer", keyType))).
SetFooter("⚠️ Running TruffleHog Analyze will send a lot of requests ⚠️\n\n🚧 Please confirm you have permission to run TruffleHog Analyze against this secret 🚧").
SetSubmitMsg("Run TruffleHog Analyze")
return FormPage{
return &AnalyzeForm{
Common: c,
KeyType: keyType,
form: form,
}
}
func (FormPage) Init() tea.Cmd {
func (AnalyzeForm) Init() tea.Cmd {
return nil
}
func (ui FormPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// TODO: Check form focus.
if msg, ok := msg.(tea.KeyMsg); ok {
type SetAnalyzerMsg string
func (ui *AnalyzeForm) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case SetAnalyzerMsg:
ui = New(ui.Common, string(msg))
return ui, nil
case tea.KeyMsg:
switch {
case key.Matches(msg, ui.Common.KeyMap.Back):
return ui.PrevPage()
return nil, tea.Quit
}
}
if _, ok := msg.(textinputs.SelectNextMsg); ok {
values := make(map[string]string)
for k, v := range ui.form.GetInputs() {
@@ -96,27 +114,29 @@ func (ui FormPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
LogFile: logFile,
LoggingEnabled: logFile != "",
}
return SecretInfo{Cfg: &cfg, Parts: values}
return Submission{
AnalyzerType: ui.KeyType,
AnalyzerInfo: analyzer.SecretInfo{Cfg: &cfg, Parts: values},
}
}
return nil, secretInfoCmd
return ui, secretInfoCmd
}
form, cmd := ui.form.Update(msg)
ui.form = form.(textinputs.Model)
return ui, cmd
}
func (ui FormPage) View() string {
func (ui *AnalyzeForm) View() string {
return styles.AppStyle.Render(ui.form.View())
}
func (ui FormPage) PrevPage() (tea.Model, tea.Cmd) {
page := NewKeyTypePage(ui.Common)
// Select what was previously selected.
index, ok := slices.BinarySearch(analyzers.AvailableAnalyzers(), ui.KeyType)
if !ok {
// Should be impossible.
index = 0
}
page.list.Select(index)
return page, nil
func (m *AnalyzeForm) ShortHelp() []key.Binding {
// TODO: actually return something
return nil
}
func (m *AnalyzeForm) FullHelp() [][]key.Binding {
// TODO: actually return something
return nil
}
@@ -1,4 +1,4 @@
package tui
package analyze_keys
import (
"github.com/charmbracelet/bubbles/key"
@@ -7,6 +7,7 @@ import (
"github.com/charmbracelet/lipgloss"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/components/selector"
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/styles"
)
@@ -23,17 +24,16 @@ var (
Padding(0, 1)
)
type KeyTypePage struct {
Common *common.Common
type AnalyzeKeyPage struct {
common.Common
list list.Model
}
func (ui KeyTypePage) Init() tea.Cmd {
func (ui *AnalyzeKeyPage) Init() tea.Cmd {
return nil
}
func NewKeyTypePage(c *common.Common) KeyTypePage {
func New(c common.Common) *AnalyzeKeyPage {
items := make([]list.Item, len(analyzers.AvailableAnalyzers()))
for i, analyzerType := range analyzers.AvailableAnalyzers() {
items[i] = KeyTypeItem(analyzerType)
@@ -47,39 +47,43 @@ func NewKeyTypePage(c *common.Common) KeyTypePage {
list.Title = "Select an analyzer type"
list.SetShowStatusBar(false)
list.Styles.Title = titleStyle
return KeyTypePage{
return &AnalyzeKeyPage{
Common: c,
list: list,
}
}
func (ui KeyTypePage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
func (ui *AnalyzeKeyPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if !ui.list.SettingFilter() {
switch msg := msg.(type) {
case tea.WindowSizeMsg:
h, v := styles.AppStyle.GetFrameSize()
ui.list.SetSize(msg.Width-h, msg.Height-v)
case tea.KeyMsg:
switch {
case key.Matches(msg, ui.Common.KeyMap.Back):
return nil, tea.Quit
case key.Matches(msg, ui.Common.KeyMap.Select):
chosen := string(ui.list.SelectedItem().(KeyTypeItem))
return NewFormPage(ui.Common, chosen), SetKeyTypeCmd(chosen)
chosenAnalyzer := ui.list.SelectedItem().(KeyTypeItem)
return ui, func() tea.Msg {
return selector.SelectMsg{IdentifiableItem: chosenAnalyzer}
}
}
}
var cmd tea.Cmd
ui.list, cmd = ui.list.Update(msg)
return ui, cmd
}
var cmd tea.Cmd
ui.list, cmd = ui.list.Update(msg)
return ui, cmd
return ui, func() tea.Msg { return nil }
}
func (ui KeyTypePage) View() string {
func (ui AnalyzeKeyPage) View() string {
return styles.AppStyle.Render(ui.list.View())
}
func (ui KeyTypePage) NextPage(keyType string) (tea.Model, tea.Cmd) {
return NewFormPage(ui.Common, keyType), SetKeyTypeCmd(keyType)
}
type KeyTypeItem string
func (i KeyTypeItem) ID() string { return string(i) }
@@ -87,10 +91,12 @@ func (i KeyTypeItem) Title() string { return string(i) }
func (i KeyTypeItem) Description() string { return "" }
func (i KeyTypeItem) FilterValue() string { return string(i) }
func init() {
// Preload HasDarkBackground call. For some reason, if we don't do
// this, the TUI can take a noticeably long time to start. We should
// investigate further, but this is a good-enough bandaid for now.
// See: https://github.com/charmbracelet/lipgloss/issues/73
_ = lipgloss.HasDarkBackground()
func (m AnalyzeKeyPage) ShortHelp() []key.Binding {
// TODO: actually return something
return nil
}
func (m AnalyzeKeyPage) FullHelp() [][]key.Binding {
// TODO: actually return something
return nil
}
+93 -25
View File
@@ -8,9 +8,13 @@ import (
"github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea"
zone "github.com/lrstanley/bubblezone"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/components/selector"
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/keymap"
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/pages/analyze_form"
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/pages/analyze_keys"
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/pages/contact_enterprise"
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/pages/source_configure"
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/pages/source_select"
@@ -27,6 +31,8 @@ const (
sourceConfigurePage
viewOSSProjectPage
contactEnterprisePage
analyzeKeysPage
analyzeFormPage
)
type sessionState int
@@ -39,20 +45,31 @@ const (
// TUI is the main TUI model.
type TUI struct {
common common.Common
pages []common.Component
activePage page
state sessionState
args []string
common common.Common
pages []common.Component
pageHistory []page
state sessionState
args []string
// Analyzer specific values that are only set if running an analysis.
analyzerType string
analyzerInfo analyzer.SecretInfo
}
// New returns a new TUI model.
func New(c common.Common) *TUI {
func New(c common.Common, args []string) *TUI {
ui := &TUI{
common: c,
pages: make([]common.Component, 5),
activePage: wizardIntroPage,
state: startState,
common: c,
pages: make([]common.Component, 7),
pageHistory: []page{wizardIntroPage},
state: startState,
args: args,
}
switch {
case len(args) == 0:
return ui
case len(args) == 1 && args[0] == "analyze":
ui.pageHistory = []page{wizardIntroPage, analyzeKeysPage}
}
return ui
}
@@ -69,11 +86,28 @@ func (ui *TUI) SetSize(width, height int) {
// Init implements tea.Model.
func (ui *TUI) Init() tea.Cmd {
ui.pages[wizardIntroPage] = wizard_intro.New(ui.common)
ui.pages[sourceSelectPage] = source_select.New(ui.common)
ui.pages[sourceConfigurePage] = source_configure.New(ui.common)
ui.pages[viewOSSProjectPage] = view_oss.New(ui.common)
ui.pages[contactEnterprisePage] = contact_enterprise.New(ui.common)
ui.pages[analyzeKeysPage] = analyze_keys.New(ui.common)
if len(ui.args) > 1 && ui.args[0] == "analyze" {
analyzerArg := strings.ToLower(ui.args[1])
ui.pages[analyzeFormPage] = analyze_form.New(ui.common, analyzerArg)
ui.setActivePage(analyzeKeysPage)
for _, analyzer := range analyzers.AvailableAnalyzers() {
if strings.ToLower(analyzer) == analyzerArg {
ui.setActivePage(analyzeFormPage)
}
}
} else {
ui.pages[analyzeFormPage] = analyze_form.New(ui.common, "this is a bug")
}
ui.SetSize(ui.common.Width, ui.common.Height)
cmds := make([]tea.Cmd, 0)
cmds = append(cmds,
@@ -82,6 +116,8 @@ func (ui *TUI) Init() tea.Cmd {
ui.pages[sourceConfigurePage].Init(),
ui.pages[viewOSSProjectPage].Init(),
ui.pages[contactEnterprisePage].Init(),
ui.pages[analyzeKeysPage].Init(),
ui.pages[analyzeFormPage].Init(),
)
ui.state = loadedState
ui.SetSize(ui.common.Width, ui.common.Height)
@@ -106,12 +142,12 @@ func (ui *TUI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case tea.KeyMsg:
switch {
case key.Matches(msg, ui.common.KeyMap.Help):
case key.Matches(msg, ui.common.KeyMap.CmdQuit) && ui.activePage != sourceConfigurePage:
case key.Matches(msg, ui.common.KeyMap.CmdQuit) && ui.activePage() != sourceConfigurePage:
return ui, tea.Quit
case key.Matches(msg, ui.common.KeyMap.Quit):
return ui, tea.Quit
case ui.activePage > 0 && key.Matches(msg, ui.common.KeyMap.Back):
ui.activePage -= 1
case ui.activePage() > 0 && key.Matches(msg, ui.common.KeyMap.Back):
_ = ui.popHistory()
return ui, nil
}
}
@@ -120,37 +156,45 @@ func (ui *TUI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case selector.SelectMsg:
switch item := msg.IdentifiableItem.(type) {
case wizard_intro.Item:
switch item {
case wizard_intro.Quit:
cmds = append(cmds, tea.Quit)
case wizard_intro.ViewOSSProject:
ui.activePage = viewOSSProjectPage
ui.setActivePage(viewOSSProjectPage)
case wizard_intro.ViewHelpDocs:
ui.args = []string{"--help"}
return ui, tea.Quit
case wizard_intro.EnterpriseInquire:
ui.activePage = contactEnterprisePage
ui.setActivePage(contactEnterprisePage)
case wizard_intro.ScanSourceWithWizard:
ui.activePage = sourceSelectPage
ui.setActivePage(sourceSelectPage)
case wizard_intro.AnalyzeSecret:
ui.args = []string{"analyze"}
return ui, tea.Quit
ui.setActivePage(analyzeKeysPage)
}
case source_select.SourceItem:
ui.activePage = sourceConfigurePage
ui.setActivePage(sourceConfigurePage)
cmds = append(cmds, func() tea.Msg {
return source_configure.SetSourceMsg{Source: item.ID()}
})
case analyze_keys.KeyTypeItem:
ui.setActivePage(analyzeFormPage)
cmds = append(cmds, func() tea.Msg {
return analyze_form.SetAnalyzerMsg(item.ID())
})
}
case source_configure.SetArgsMsg:
ui.args = strings.Split(string(msg), " ")[1:]
return ui, tea.Quit
case analyze_form.Submission:
ui.analyzerType = msg.AnalyzerType
ui.analyzerInfo = msg.AnalyzerInfo
return ui, tea.Quit
}
if ui.state == loadedState {
m, cmd := ui.pages[ui.activePage].Update(msg)
ui.pages[ui.activePage] = m.(common.Component)
m, cmd := ui.pages[ui.activePage()].Update(msg)
ui.pages[ui.activePage()] = m.(common.Component)
if cmd != nil {
cmds = append(cmds, cmd)
}
@@ -168,7 +212,7 @@ func (ui *TUI) View() string {
case startState:
view = "Loading..."
case loadedState:
view = ui.pages[ui.activePage].View()
view = ui.pages[ui.activePage()].View()
default:
view = "Unknown state :/ this is a bug!"
}
@@ -177,7 +221,7 @@ func (ui *TUI) View() string {
)
}
func Run() []string {
func Run(args []string) []string {
c := common.Common{
Copy: nil,
Styles: styles.DefaultStyles(),
@@ -186,12 +230,36 @@ func Run() []string {
Height: 0,
Zone: zone.New(),
}
m := New(c)
m := New(c, args)
p := tea.NewProgram(m)
// TODO: Print normal help message.
if _, err := p.Run(); err != nil {
fmt.Printf("Alas, there's been an error: %v", err)
os.Exit(1)
}
if m.analyzerType != "" {
analyzer.Run(m.analyzerType, m.analyzerInfo)
os.Exit(0)
}
return m.args
}
func (ui *TUI) activePage() page {
if len(ui.pageHistory) == 0 {
return wizardIntroPage
}
return ui.pageHistory[len(ui.pageHistory)-1]
}
func (ui *TUI) popHistory() page {
if len(ui.pageHistory) == 0 {
return wizardIntroPage
}
p := ui.activePage()
ui.pageHistory = ui.pageHistory[:len(ui.pageHistory)-1]
return p
}
func (ui *TUI) setActivePage(p page) {
ui.pageHistory = append(ui.pageHistory, p)
}