master > master: code go - entwicklung für interaktiven Modus gestartet

This commit is contained in:
RD 2021-11-03 11:04:59 +01:00
parent 43f5447ed7
commit 893db52d90
11 changed files with 421 additions and 42 deletions

View File

@ -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()
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}
}
}
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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

View File

@ -26,6 +26,11 @@ func DisplayStartOfCase(index int, descr *string) {
}
}
func DisplayStartOfCaseBlank() {
DisplayBar(80)
return
}
func DisplayEndOfCase() {
DisplayBar(80)
return

View File

@ -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
}
/* ---------------------------------------------------------------- *

View File

@ -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"`

View File

@ -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()
}