diff --git a/code/golang/internal/core/logging/logging.go b/code/golang/internal/core/logging/logging.go index f34b9ef..030b606 100644 --- a/code/golang/internal/core/logging/logging.go +++ b/code/golang/internal/core/logging/logging.go @@ -5,10 +5,15 @@ package logging * ---------------------------------------------------------------- */ import ( + "bufio" "fmt" + "io" "os" + "github.com/buger/goterm" + "ads/internal/core/utils" + "ads/pkg/re" ) /* ---------------------------------------------------------------- * @@ -59,10 +64,10 @@ func SetTagAll(mode bool) { } /* ---------------------------------------------------------------- * - * METHOD logging + * METHODS logging * ---------------------------------------------------------------- */ -func logGeneric(tag string, lines ...interface{}) { +func logGeneric(pipe *os.File, tag string, lines ...interface{}) { if !force && quietmode { return } @@ -74,7 +79,7 @@ func logGeneric(tag string, lines ...interface{}) { if !ansimode { _line = utils.StripAnsi(_line) } - fmt.Println(_line) + fmt.Fprintln(pipe, _line) if !tagAll { tag = "" } @@ -83,12 +88,12 @@ func logGeneric(tag string, lines ...interface{}) { func LogPlain(lines ...interface{}) { SetTagAll(false) - logGeneric("", lines...) + logGeneric(os.Stdout, "", lines...) } func LogInfo(lines ...interface{}) { SetTagAll(true) - logGeneric("[\033[94;1mINFO\033[0m]", lines...) + logGeneric(os.Stdout, "[\033[94;1mINFO\033[0m]", lines...) } func LogDebug(lines ...interface{}) { @@ -96,21 +101,59 @@ func LogDebug(lines ...interface{}) { return } SetTagAll(true) - logGeneric("[\033[96;1mDEBUG\033[0m]", lines...) + logGeneric(os.Stdout, "[\033[96;1mDEBUG\033[0m]", lines...) } func LogWarn(lines ...interface{}) { SetTagAll(false) - logGeneric("[\033[93;1mWARNING\033[0m]", lines...) + logGeneric(os.Stdout, "[\033[93;1mWARNING\033[0m]", lines...) } func LogError(lines ...interface{}) { SetTagAll(false) - logGeneric("[\033[91;1mERROR\033[0m]", lines...) + logGeneric(os.Stderr, "[\033[91;1mERROR\033[0m]", lines...) } func LogFatal(lines ...interface{}) { SetTagAll(false) - logGeneric("[\033[91;1mFATAL\033[0m]", lines...) + logGeneric(os.Stderr, "[\033[91;1mFATAL\033[0m]", lines...) os.Exit(1) } + +/* ---------------------------------------------------------------- * + * METHOD prompt + * ---------------------------------------------------------------- */ + +// Zeigt Prompt an und liest Usereingabe aus, erkennt auch ob Meta+D geklickt wurde +func Prompt(lines ...interface{}) (string, bool, error) { + pipe := os.Stdout + if len(lines) > 0 { + logGeneric(pipe, "", lines...) + logGeneric(pipe, "", "") + } + fmt.Fprint(pipe, "$ ") + reader := bufio.NewReader(os.Stdin) + line, err := reader.ReadString('\n') + line = re.Sub(`\s+$`, "", line) + if err != nil && err == io.EOF { + fmt.Fprintln(pipe, "") + return line, true, err + } + return line, false, err +} + +func PromptAnyKeyToContinue() bool { + pipe := os.Stdout + fmt.Fprint(pipe, "\033[2;3mEingabetaste (Enter) zum Fortsetzen drücken:\033[0m ") + _, exit, _ := Prompt() + return exit +} + +/* ---------------------------------------------------------------- * + * METHODS terminal + * ---------------------------------------------------------------- */ + +func ClearScreen() { + goterm.Clear() + goterm.Flush() +} diff --git a/code/golang/internal/endpoints/endpoints_it.go b/code/golang/internal/endpoints/endpoints_it.go new file mode 100644 index 0000000..9c3f95a --- /dev/null +++ b/code/golang/internal/endpoints/endpoints_it.go @@ -0,0 +1,108 @@ +package endpoints + +/* ---------------------------------------------------------------- * + * IMPORTS + * ---------------------------------------------------------------- */ + +import ( + "ads/internal/core/logging" + "ads/internal/menus" + "ads/internal/setup" + "reflect" + + // algorithm_search_binary "ads/internal/algorithms/search/binary" + // algorithm_search_interpol "ads/internal/algorithms/search/interpol" + // algorithm_search_ith_element "ads/internal/algorithms/search/ith_element" + // algorithm_search_jump "ads/internal/algorithms/search/jump" + // algorithm_search_poison "ads/internal/algorithms/search/poison" + algorithm_search_sequential "ads/internal/algorithms/search/sequential" + // algorithm_sum_maxsubsum "ads/internal/algorithms/sum/maxsubsum" +) + +/* ---------------------------------------------------------------- * + * ENDPOINT run interactive modus + * ---------------------------------------------------------------- */ + +// Startet App im interaktiven Modus +func RunInteractive() error { + logging.LogPlain(setup.Logo()) + _, err := menuMain.ShowMenu() + logging.LogInfo("Programm terminiert.") + return err +} + +/* ---------------------------------------------------------------- * + * MENUS + * ---------------------------------------------------------------- */ + +var menuMain = menus.Menu{ + IsRoot: true, + Path: []string{"Hauptmenü"}, + PromptMessages: []string{ + "Option wählen", + }, + Options: []menus.MenuOption{ + {Label: "Version des Programms anzeigen", Action: actionShowVersion}, + {Label: "Suchalgorithmen", Submenu: &menuSearchAlgorithms}, + {Label: "Summenalgorithmen", Submenu: &menuSumAlgorithms}, + }, + DefaultOption: -1, +} + +var menuSearchAlgorithms = menus.Menu{ + IsRoot: false, + Path: []string{"Hauptmenü", "Suchalgorithmus"}, + PromptMessages: []string{ + "Algorithmus wählen", + }, + Options: []menus.MenuOption{ + {Label: "Binärsuchalgorithmus"}, + {Label: "Interpolationsalgorithmus"}, + {Label: "Suche i. kleinstes Element"}, + {Label: "Suche i. kleinstes Element", SubLabel: "D & C"}, + {Label: "Sprungsuche", SubLabel: "linear"}, + {Label: "Sprungsuche", SubLabel: "exponentiell"}, + {Label: "Giftsuche"}, + {Label: "Giftsuche", SubLabel: "optimiert"}, + {Label: "Sequentiellsuchalgorithmus", Action: actionAlgorithmSearchSequential}, + }, + DefaultOption: -1, +} + +var menuSumAlgorithms = menus.Menu{ + IsRoot: false, + Path: []string{"Hauptmenü", "Suchalgorithmus"}, + PromptMessages: []string{ + "Algorithmus wählen", + }, + Options: []menus.MenuOption{ + {Label: "Maximale Teilsumme", SubLabel: "brute force"}, + {Label: "Maximale Teilsumme", SubLabel: "D & C"}, + }, + DefaultOption: -1, +} + +/* ---------------------------------------------------------------- * + * ACTIONS - basic + * ---------------------------------------------------------------- */ + +func actionShowVersion() error { + logging.LogInfo("Version des Programms") + Version() + return nil +} + +/* ---------------------------------------------------------------- * + * ACTIONS - Algorithmen + * ---------------------------------------------------------------- */ + +// TODO +func actionAlgorithmSearchSequential() error { + input_L := []int{45, 67} + input_x := 6 + menus.PromptValue("L", "Liste von Integers", "z.B. \033[1m5 73 42\033[0m", reflect.TypeOf([]int{})) + setup.DisplayStartOfCaseBlank() + algorithm_search_sequential.FancySequentialSearch(input_L, input_x) + setup.DisplayEndOfCase() + return nil +} diff --git a/code/golang/internal/endpoints/endpoints_run.go b/code/golang/internal/endpoints/endpoints_run.go index b8ed601..b121501 100644 --- a/code/golang/internal/endpoints/endpoints_run.go +++ b/code/golang/internal/endpoints/endpoints_run.go @@ -20,11 +20,11 @@ import ( ) /* ---------------------------------------------------------------- * - * ENDPOINT run + * ENDPOINT run non-interactive modus * ---------------------------------------------------------------- */ // Liest Config Datei ein und führt Algorithmen auf Fälle durch -func Run(path string) error { +func RunNoninteractive(path string) error { var err error var err_case error @@ -38,7 +38,7 @@ func Run(path string) error { } // Fälle extrahieren - cases := []types.CaseConfig{} + cases := []types.UserConfigCase{} if config.Parts != nil && config.Parts.Cases != nil { cases = *config.Parts.Cases } @@ -46,7 +46,7 @@ func Run(path string) error { err_case = nil problem := cases[i] setup.DisplayStartOfCase(i, problem.Description) - inputs := types.InputsConfig{} + inputs := types.UserConfigInputs{} if problem.Inputs != nil { inputs = *problem.Inputs } diff --git a/code/golang/internal/menus/menus.go b/code/golang/internal/menus/menus.go new file mode 100644 index 0000000..f94ed85 --- /dev/null +++ b/code/golang/internal/menus/menus.go @@ -0,0 +1,102 @@ +package menus + +/* ---------------------------------------------------------------- * + * IMPORTS + * ---------------------------------------------------------------- */ + +import ( + "fmt" + "strconv" + "strings" + + "ads/internal/core/logging" +) + +/* ---------------------------------------------------------------- * + * METHOD show menu + * ---------------------------------------------------------------- */ + +func (menu Menu) ShowMenu() (bool, error) { + var ( + choice string + index int + meta bool + quit bool + ) + var promptMessages []string + var options [][2]string + var defaultOption string + var breaks []int + + // Headline einfügen + promptMessages = make([]string, len(menu.PromptMessages)) + copy(promptMessages, menu.PromptMessages) + head := fmt.Sprintf("\033[2m%s\033[0m", strings.Join(menu.Path, " > ")) + promptMessages = append([]string{head}, promptMessages...) + + // Zurück-Option einfügen + defaultOption = fmt.Sprintf("%v", menu.DefaultOption) + options = []([2]string){} + for i, opt := range menu.Options { + key := fmt.Sprintf("%v", i+1) + subLabel := opt.SubLabel + label := opt.Label + if !(subLabel == "") { + label = fmt.Sprintf("%v (\033[2m%v\033[0m)", opt.Label, subLabel) + } + options = append(options, [2]string{key, label}) + } + breaks = []int{len(menu.Options) - 1} + if !menu.IsRoot { + options = append(options, [2]string{"z", "Zurück zum vorherigen Menü"}) + } + options = append(options, [2]string{"q", "Programm schließen"}) + + // User Response immer abfragen und abarbeiten, bis quit/return. + performClearScreen := !menu.IsRoot + for { + if performClearScreen { + logging.ClearScreen() + } + performClearScreen = true + + choice, meta = PromptListOfOptions(promptMessages, options, breaks, defaultOption) + + // Falls quit wählt, -> quit: + if (menu.IsRoot && meta) || choice == "q" { + return true, nil + } + + // Falls User back wählt, -> return: + if (!menu.IsRoot && meta) || choice == "z" { + return false, nil + } + + // sonst führe die assoziierte Methode aus + index64, _ := strconv.ParseInt(choice, 10, 64) + index = int(index64) - 1 + if 0 <= index && index < len(menu.Options) { + opt := menu.Options[index] + // Entweder Untermenü öffnen oder Action ausführen + if opt.Submenu != nil { + quit, _ = opt.Submenu.ShowMenu() + if quit { + return true, nil + } + } else if opt.Action != nil { + opt.Action() + quit := logging.PromptAnyKeyToContinue() + // Falls während der Action der User Meta+D klickt, -> quit: + if quit { + return true, nil + } + } else { + logging.LogWarn("Option noch nicht implementiert.") + quit := logging.PromptAnyKeyToContinue() + if quit { + return true, nil + } + } + } + } +} diff --git a/code/golang/internal/menus/menus_prompt.go b/code/golang/internal/menus/menus_prompt.go new file mode 100644 index 0000000..16188e7 --- /dev/null +++ b/code/golang/internal/menus/menus_prompt.go @@ -0,0 +1,91 @@ +package menus + +/* ---------------------------------------------------------------- * + * IMPORTS + * ---------------------------------------------------------------- */ + +import ( + "fmt" + "reflect" + + "ads/internal/core/logging" + "ads/internal/core/utils" +) + +/* ---------------------------------------------------------------- * + * METHOD prompt options + * ---------------------------------------------------------------- */ + +// Zeigt Prompt mit Liste von Optionen an +func PromptListOfOptions(messages []string, options [][2]string, breaks []int, defaultChoice string) (string, bool) { + var n = len(options) + var ( + choice string + cancel bool + err error + ) + var lines []interface{} + var optionsMap = map[string]string{} + + // Erzeuge Messages + lines = []interface{}{} + for _, message := range messages { + lines = append(lines, message) + } + lines = append(lines, "") + for i, obj := range options { + key := obj[0] + label := obj[1] + optionsMap[key] = label + if key == defaultChoice { + lines = append(lines, fmt.Sprintf("\033[91m*\033[0m\033[93;1m%v)\033[0m %v", key, label)) + } else { + lines = append(lines, fmt.Sprintf(" \033[93;1m%v)\033[0m %v", key, label)) + } + if i < n-1 && utils.ArrayContains(breaks, i) { + lines = append(lines, " \033[93m--------\033[0m") + } + i++ + } + if !(defaultChoice == "" || defaultChoice == "-1") { + lines = append(lines, "") + lines = append(lines, "\033[91;2m*\033[0m\033[2m = Default\033[0m") + } + + // Wiederhole Prompt, bis gültige Eingabe erreicht + for { + choice, cancel, err = logging.Prompt(lines...) + if cancel { + return "", true + } + if err != nil { + logging.ClearScreen() + logging.LogError(err) + continue + } + if choice == "" { + choice = defaultChoice + break + } + if _, ok := optionsMap[choice]; !ok { + logging.ClearScreen() + logging.LogError("Ungültige eingabe") + continue + } + break + } + return choice, false +} + +/* ---------------------------------------------------------------- * + * METHOD prompt values + * ---------------------------------------------------------------- */ + +func PromptValue(varname string, typename string, example string, t reflect.Type) (bool, error) { + _, cancel, err := logging.Prompt( + fmt.Sprintf("Bitte den Wert von \033[1m%s\033[0m als \033[1m%s\033[0m eingeben.", varname, typename), + example, + ) + // TODO: input parsen + return cancel, err +} diff --git a/code/golang/internal/menus/menus_type.go b/code/golang/internal/menus/menus_type.go new file mode 100644 index 0000000..0077605 --- /dev/null +++ b/code/golang/internal/menus/menus_type.go @@ -0,0 +1,26 @@ +package menus + +/* ---------------------------------------------------------------- * + * IMPORTS + * ---------------------------------------------------------------- */ + +// + +/* ---------------------------------------------------------------- * + * TYPES + * ---------------------------------------------------------------- */ + +type MenuOption struct { + Label string + SubLabel string + Submenu *Menu + Action func() error // NOTE: in go, this is automatically a pointer type +} + +type Menu struct { + IsRoot bool + Path []string + PromptMessages []string + Options []MenuOption + DefaultOption int +} diff --git a/code/golang/internal/setup/cli/cli.go b/code/golang/internal/setup/cli/cli.go index 5851c03..56b847b 100644 --- a/code/golang/internal/setup/cli/cli.go +++ b/code/golang/internal/setup/cli/cli.go @@ -60,14 +60,15 @@ func ParseCli(args []string) (*types.CliArguments, error) { var err error Parser = argparse.NewParser("cli parser", "Liest Optionen + Flags von Kommandozeile.") arguments := types.CliArguments{ - Help: Parser.NewCommand("help", ""), - Version: Parser.NewCommand("version", "Ruft Endpunkt auf, der die Version anzeigt."), - Run: Parser.NewCommand("run", "Ruft Endpunkt auf, der die Algorithmen laufen lässt."), - Quiet: Parser.Flag("q", "quiet", &optionsQuiet), - Debug: Parser.Flag("", "debug", &optionsDebug), - Checks: Parser.String("", "checks", &optionsChecks), - Colour: Parser.String("", "colour", &optionsColour), - ConfigFile: Parser.String("", "config", &optionsConfigFile), + ModeHelp: Parser.NewCommand("help", ""), + ModeVersion: Parser.NewCommand("version", "Ruft Endpunkt auf, der die Version anzeigt."), + ModeRun: Parser.NewCommand("run", "Ruft Endpunkt auf, der die Algorithmen laufen lässt."), + ModeInteractive: Parser.NewCommand("it", "Startet die App im interaktiven Modus."), + Quiet: Parser.Flag("q", "quiet", &optionsQuiet), + Debug: Parser.Flag("", "debug", &optionsDebug), + Checks: Parser.String("", "checks", &optionsChecks), + Colour: Parser.String("", "colour", &optionsColour), + ConfigFile: Parser.String("", "config", &optionsConfigFile), } err = Parser.Parse(args) return &arguments, err diff --git a/code/golang/internal/setup/setup_display.go b/code/golang/internal/setup/setup_display.go index d9c74be..c788d20 100644 --- a/code/golang/internal/setup/setup_display.go +++ b/code/golang/internal/setup/setup_display.go @@ -26,6 +26,11 @@ func DisplayStartOfCase(index int, descr *string) { } } +func DisplayStartOfCaseBlank() { + DisplayBar(80) + return +} + func DisplayEndOfCase() { DisplayBar(80) return diff --git a/code/golang/internal/types/types_cli.go b/code/golang/internal/types/types_cli.go index 595244e..4705d77 100644 --- a/code/golang/internal/types/types_cli.go +++ b/code/golang/internal/types/types_cli.go @@ -15,14 +15,15 @@ import ( * ---------------------------------------------------------------- */ type CliArguments struct { - Help *argparse.Command - Version *argparse.Command - Run *argparse.Command - Quiet *bool - Debug *bool - Checks *string - Colour *string - ConfigFile *string + ModeHelp *argparse.Command + ModeVersion *argparse.Command + ModeRun *argparse.Command + ModeInteractive *argparse.Command + Quiet *bool + Debug *bool + Checks *string + Colour *string + ConfigFile *string } /* ---------------------------------------------------------------- * diff --git a/code/golang/internal/types/types_userconfig.go b/code/golang/internal/types/types_userconfig.go index 9a04e70..a894455 100644 --- a/code/golang/internal/types/types_userconfig.go +++ b/code/golang/internal/types/types_userconfig.go @@ -11,26 +11,26 @@ package types * ---------------------------------------------------------------- */ type UserConfig struct { - Info *InfoConfig `yaml:"info"` - Parts *PartsConfig `yaml:"parts"` + Info *UserConfigInfo `yaml:"info"` + Parts *UserConfigParts `yaml:"parts"` } -type InfoConfig struct { +type UserConfigInfo struct { Title *string `yaml:"title"` Description *string `yaml:"description"` } -type PartsConfig struct { - Cases *([]CaseConfig) `yaml:"cases"` +type UserConfigParts struct { + Cases *([]UserConfigCase) `yaml:"cases"` } -type CaseConfig struct { - Command *string `yaml:"command"` - Description *string `yaml:"description"` - Inputs *InputsConfig `yaml:"inputs"` +type UserConfigCase struct { + Command *string `yaml:"command"` + Description *string `yaml:"description"` + Inputs *UserConfigInputs `yaml:"inputs"` } -type InputsConfig struct { +type UserConfigInputs struct { List *[]int `yaml:"L"` SearchRank *int `yaml:"i"` SearchValue *int `yaml:"x"` diff --git a/code/golang/main.go b/code/golang/main.go index b33ebe0..d724dff 100644 --- a/code/golang/main.go +++ b/code/golang/main.go @@ -63,10 +63,12 @@ func main() { } if err == nil { - if arguments.Version.Happened() { + if arguments.ModeVersion.Happened() { endpoints.Version() - } else if arguments.Run.Happened() { - err = endpoints.Run(arguments.GetConfigFile()) + } else if arguments.ModeRun.Happened() { + err = endpoints.RunNoninteractive(arguments.GetConfigFile()) + } else if arguments.ModeInteractive.Happened() { + err = endpoints.RunInteractive() } else { // } else if arguments.Help.Happened() { endpoints.Help() }