diff --git a/cmd.go b/cmd.go index 1884c448..6c6dee23 100644 --- a/cmd.go +++ b/cmd.go @@ -136,174 +136,205 @@ func init() { } } -func parser() (op string, options []string, packages []string, changedConfig bool, err error) { - if len(os.Args) < 2 { - err = fmt.Errorf("no operation specified") - return - } - changedConfig = false - op = "yogurt" - - for _, arg := range os.Args[1:] { - if len(arg) < 2 { - continue - } - if arg[0] == '-' && arg[1] != '-' { - switch arg { - case "-V": - arg = "--version" - case "-h": - arg = "--help" - default: - op = arg - continue - } - } - - if strings.HasPrefix(arg, "--") { - changedConfig = true - switch arg { - case "--afterclean": - config.CleanAfter = true - case "--noafterclean": - config.CleanAfter = false - case "--printconfig": - fmt.Printf("%#v", config) - os.Exit(0) - case "--gendb": - err = createDevelDB() - if err != nil { - fmt.Println(err) - } - err = saveVCSInfo() - if err != nil { - fmt.Println(err) - } - os.Exit(0) - case "--devel": - config.Devel = true - case "--nodevel": - config.Devel = false - case "--timeupdate": - config.TimeUpdate = true - case "--notimeupdate": - config.TimeUpdate = false - case "--topdown": - config.SortMode = TopDown - case "--complete": - config.Shell = "sh" - _ = complete() - os.Exit(0) - case "--fcomplete": - config.Shell = fishShell - _ = complete() - os.Exit(0) - case "--help": - usage() - os.Exit(0) - case "--version": - fmt.Printf("yay v%s\n", version) - os.Exit(0) - case "--noconfirm": - config.NoConfirm = true - fallthrough - default: - options = append(options, arg) - } - continue - } - packages = append(packages, arg) - } - return -} - func main() { - op, options, pkgs, changedConfig, err := parser() + var err error + var changedConfig bool + + parser := makeArgParser(); + err = parser.parseCommandLine(); + if err != nil { fmt.Println(err) os.Exit(1) } + + if parser.existsArg("-") { + err = parser.parseStdin(); - switch op { - case "-Cd": - err = cleanDependencies(pkgs) - case "-G": - for _, pkg := range pkgs { - err = getPkgbuild(pkg) - if err != nil { - fmt.Println(pkg+":", err) - } - } - case "-Qstats": - err = localStatistics() - case "-Ss", "-Ssq", "-Sqs": - if op == "-Ss" { - config.SearchMode = Detailed - } else { - config.SearchMode = Minimal - } - - if pkgs != nil { - err = syncSearch(pkgs) - } - case "-S": - err = install(pkgs, options) - case "-Sy": - err = passToPacman("-Sy", nil, nil) if err != nil { - break + fmt.Println(err) + os.Exit(1) } - err = install(pkgs, options) - case "-Syu", "-Suy", "-Su": - if strings.Contains(op, "y") { - err = passToPacman("-Sy", nil, nil) - if err != nil { - break - } - } - err = upgradePkgs(options) - case "-Si": - err = syncInfo(pkgs, options) - case "yogurt": - config.SearchMode = NumberMenu - - if pkgs != nil { - err = numberMenu(pkgs, options) - } - default: - if op[0] == 'R' { - removeVCSPackage(pkgs) - } - err = passToPacman(op, pkgs, options) } - - var erra error + + fmt.Println(parser) + + changedConfig, err = handleCmd(parser) + + if err != nil { + fmt.Println(err) + } + if updated { - erra = saveVCSInfo() - if erra != nil { + err = saveVCSInfo() + + if err != nil { fmt.Println(err) } } if changedConfig { - erra = config.saveConfig() - if erra != nil { + err = config.saveConfig() + + if err != nil { fmt.Println(err) } } - erra = alpmHandle.Release() - if erra != nil { - fmt.Println(err) - } - + err = alpmHandle.Release() if err != nil { fmt.Println(err) - os.Exit(1) } } +func handleCmd(parser *argParser) (changedConfig bool, err error) { + var _changedConfig bool + + for option, _ := range parser.options { + _changedConfig, err = handleConfig(option) + + if err != nil { + return + } + + if _changedConfig { + changedConfig = true + } + } + + switch parser.op { + case "V", "version": + handleVersion() + case "D", "database": + //passToPacman() + case "F", "files": + //passToPacman() + case "Q", "query": + //passToPacman() + case "R", "remove": + // + case "S", "sync": + err = handleSync(parser) + case "T", "deptest": + //passToPacman() + case "U", "upgrade": + //passToPacman() + case "Y", "--yay": + err = handleYogurt(parser) + default: + //this means we allowed an op but not implement it + //if this happens it an error in the code and not the usage + err = fmt.Errorf("unhandled operation") + } + + return +} + +//this function should only set config options +//but currently still uses the switch left over from old code +//eventuall this should be refactored out futher +//my current plan is to have yay specific operations in its own operator +//e.g. yay -Y --gendb +//e.g yay -Yg +func handleConfig(option string) (changedConfig bool, err error) { + switch option { + case "afterclean": + config.CleanAfter = true + case "noafterclean": + config.CleanAfter = false + case "printconfig": + fmt.Printf("%#v", config) + os.Exit(0) + case "gendb": + err = createDevelDB() + if err != nil { + fmt.Println(err) + } + err = saveVCSInfo() + if err != nil { + fmt.Println(err) + } + os.Exit(0) + case "devel": + config.Devel = true + case "nodevel": + config.Devel = false + case "timeupdate": + config.TimeUpdate = true + case "notimeupdate": + config.TimeUpdate = false + case "topdown": + config.SortMode = TopDown + case "complete": + config.Shell = "sh" + complete() + os.Exit(0) + case "fcomplete": + config.Shell = fishShell + complete() + os.Exit(0) + case "help": + usage() + os.Exit(0) + case "version": + fmt.Printf("yay v%s\n", version) + os.Exit(0) + case "noconfirm": + config.NoConfirm = true + default: + return + } + + changedConfig = true + return +} + +func handleVersion() { + usage() +} + +func handleYogurt(parser *argParser) (err error) { + _, options, targets := parser.formatArgs() + + config.SearchMode = NumberMenu + err = numberMenu(targets, options) + + return +} + +func handleSync(parser *argParser) (err error) { + op, options, targets := parser.formatArgs() + + fmt.Println("op", op) + fmt.Println("options", options) + fmt.Println("targets", targets) + + if parser.existsArg("y") { + err = passToPacman("-Sy", nil, nil) + if err != nil { + return + } + } + + if parser.existsArg("s") { + if parser.existsArg("i") { + config.SearchMode = Detailed + } else { + config.SortMode = Minimal + } + + err = syncSearch(targets) + } + + if len(targets) > 0 { + err = install(targets, options) + } + + return +} + + // NumberMenu presents a CLI for selecting packages to install. func numberMenu(pkgS []string, flags []string) (err error) { var num int diff --git a/parser.go b/parser.go new file mode 100644 index 00000000..4cf8ab58 --- /dev/null +++ b/parser.go @@ -0,0 +1,297 @@ +package main + +import ( + "os" + "fmt" + "strings" + "io" +) + +type argParser struct { + op string + options map[string]string + doubles map[string]struct{} //tracks args passed twice such as -yy and -dd + targets map[string]struct{} +} + +func makeArgParser() *argParser { + return &argParser { + "", + make(map[string]string), + make(map[string]struct{}), + make(map[string]struct{}), + } +} + +func (praser *argParser) delArg(option string) { + delete(praser.options, option) + delete(praser.doubles, option) +} + +func (praser *argParser) addOP(op string) (err error) { + if praser.op != "" { + err = fmt.Errorf("only one operation may be used at a time") + return + } + + praser.op = op + return +} + +func (praser *argParser) addParam(option string, arg string) (err error) { + if isOp(option) { + err = praser.addOP(option) + return + } + + if praser.existsArg(option) { + praser.doubles[option] = struct{}{} + } else { + praser.options[option] = arg + } + + return +} + +func (praser *argParser) addArg(option string) (err error) { + err = praser.addParam(option, "") + return +} + +func (praser *argParser) existsArg(option string) (ok bool) { + _, ok = praser.options[option] + return ok +} + +func (praser *argParser) getArg(option string) (arg string, double bool, exists bool) { + arg, exists = praser.options[option] + _, double = praser.doubles[option] + return +} + +func (praser *argParser) addTarget(target string) { + praser.targets[target] = struct{}{} +} + +func (praser *argParser) delTarget(target string) { + delete(praser.targets, target) +} + +func (parser *argParser) existsDouble(option string) bool { + _, ok := parser.doubles[option] + return ok +} + +func (parser *argParser) formatArgs() (op string, options []string, targets []string) { + op = formatArg(parser.op) + + for option, arg := range parser.options { + option = formatArg(option) + options = append(options, option) + + if arg != "" { + options = append(options, arg) + } + + if parser.existsDouble(option) { + options = append(options, option) + } + } + + for target := range parser.targets { + targets = append(targets, target) + } + + return + +} + +func formatArg(arg string) string { + if len(arg) > 1 { + arg = "--" + arg + } else { + arg = "-" + arg + } + + return arg +} + +func isOp(op string) bool { + switch op { + case "V", "version": + return true + case "D", "database": + return true + case "F", "files": + return true + case "Q", "query": + return true + case "R", "remove": + return true + case "S", "sync": + return true + case "T", "deptest": + return true + case "U", "upgrade": + return true + case "Y", "yay": + return true + default: + return false + } +} + +func hasParam(arg string) bool { + switch arg { + case "dbpath", "b": + return true + case "root", "r": + return true + case "sysroot": + return true + case "config": + return true + case "ignore": + return true + case "assume-installed": + return true + case "overwrite": + return true + case "ask": + return true + case "cachedir": + return true + case "hookdir": + return true + case "logfile": + return true + case "ignoregroup": + return true + case "arch": + return true + case "print-format": + return true + case "gpgdir": + return true + case "color": + return true + default: + return false + } +} + +//parses short hand options such as: +//-Syu -b/some/path - +func (parser *argParser) parseShortOption(arg string, param string) (usedNext bool, err error) { + if arg == "-" { + err = parser.addArg("-") + return + } + + arg = arg[1:] + + for k, _char := range arg { + char := string(_char) + + if hasParam(char) { + if k < len(arg) - 2 { + err = parser.addParam(char, arg[k+2:]) + } else { + usedNext = true + err = parser.addParam(char, param) + } + + break + } else { + err = parser.addArg(char) + + if err != nil { + return + } + } + } + + return +} + +//parses full length options such as: +//--sync --refresh --sysupgrade --dbpath /some/path -- +func (parser *argParser) parseLongOption(arg string, param string) (usedNext bool, err error){ + if arg == "--" { + err = parser.addArg(arg) + return + } + + arg = arg[2:] + + if hasParam(arg) { + err = parser.addParam(arg, param) + usedNext = true + } else { + err = parser.addArg(arg) + } + + return +} + +func (parser *argParser) parseStdin() (err error) { + for true { + var target string + _, err = fmt.Scan(&target) + + if err != nil { + if err == io.EOF { + err = nil + } + + return + } + + parser.addTarget(target) + } + + return +} + +func (parser *argParser)parseCommandLine() (err error) { + args := os.Args[1:] + usedNext := false + + if len(args) < 1 { + err = fmt.Errorf("no operation specified (use -h for help)") + return + } + + for k, arg := range args { + var nextArg string + + if usedNext { + usedNext = false + continue + } + + if k + 1 < len(args) { + nextArg = args[k + 1] + } + + if parser.existsArg("--") { + parser.addTarget(arg) + } else if strings.HasPrefix(arg, "--") { + usedNext, err = parser.parseLongOption(arg, nextArg) + } else if strings.HasPrefix(arg, "-") { + usedNext, err = parser.parseShortOption(arg, nextArg) + } else { + parser.addTarget(arg) + } + + if err != nil { + return + } + } + + if parser.op == "" { + parser.op = "Y" + } + + return +} \ No newline at end of file