diff --git a/Makefile b/Makefile index 2d1e390..4a8f0ca 100644 --- a/Makefile +++ b/Makefile @@ -25,20 +25,20 @@ UID = root GID = 0 HAVE_POD := $(shell pod2text -h 2>/dev/null) -all: $(tool).1 $(tool).go buildlocal +all: $(tool).1 cmd/$(tool).go buildlocal %.1: %.pod ifdef HAVE_POD pod2man -c "User Commands" -r 1 -s 1 $*.pod > $*.1 endif -%.go: %.pod +cmd/%.go: %.pod ifdef HAVE_POD - echo "package main" > $*.go - echo >> $*.go - echo "var manpage = \`" >> $*.go - pod2text $*.pod >> $*.go - echo "\`" >> $*.go + echo "package main" > cmd/$*.go + echo >> cmd/$*.go + echo "var manpage = \`" >> cmd/$*.go + pod2text cmd/$*.pod >> cmd/$*.go + echo "\`" >> cmd/$*.go endif buildlocal: diff --git a/calc.go b/cmd/calc.go similarity index 99% rename from calc.go rename to cmd/calc.go index bd1a205..e5ab3b7 100644 --- a/calc.go +++ b/cmd/calc.go @@ -15,7 +15,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -package main +package cmd import ( "errors" diff --git a/calc_test.go b/cmd/calc_test.go similarity index 99% rename from calc_test.go rename to cmd/calc_test.go index 56401c8..10f079d 100644 --- a/calc_test.go +++ b/cmd/calc_test.go @@ -15,7 +15,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -package main +package cmd import ( "fmt" @@ -327,7 +327,7 @@ func TestCalcLua(t *testing.T) { LuaInterpreter = lua.NewState(lua.Options{SkipOpenLibs: true}) defer LuaInterpreter.Close() - luarunner := NewInterpreter("example.lua", false) + luarunner := NewInterpreter("../example.lua", false) luarunner.InitLua() calc.SetInt(luarunner) diff --git a/command.go b/cmd/command.go similarity index 99% rename from command.go rename to cmd/command.go index e33d59d..dbf2bc6 100644 --- a/command.go +++ b/cmd/command.go @@ -15,7 +15,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -package main +package cmd import ( "bufio" diff --git a/funcs.go b/cmd/funcs.go similarity index 99% rename from funcs.go rename to cmd/funcs.go index 09d5e96..1856e13 100644 --- a/funcs.go +++ b/cmd/funcs.go @@ -15,7 +15,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -package main +package cmd import ( "errors" diff --git a/interpreter.go b/cmd/interpreter.go similarity index 99% rename from interpreter.go rename to cmd/interpreter.go index 8fb3bb3..6f64150 100644 --- a/interpreter.go +++ b/cmd/interpreter.go @@ -15,7 +15,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -package main +package cmd import ( "errors" diff --git a/pager.go b/cmd/pager.go similarity index 99% rename from pager.go rename to cmd/pager.go index e9e1ed0..5185d23 100644 --- a/pager.go +++ b/cmd/pager.go @@ -1,4 +1,4 @@ -package main +package cmd // pager setup using bubbletea // file shamlelessly copied from: diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..c85ea15 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,195 @@ +/* +Copyright © 2023-2024 Thomas von Dein + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +package cmd + +import ( + "fmt" + "log" + "os" + "strings" + + "github.com/chzyer/readline" + flag "github.com/spf13/pflag" + lua "github.com/yuin/gopher-lua" +) + +const VERSION string = "2.1.6" + +const Usage string = `This is rpn, a reverse polish notation calculator cli. + +Usage: rpn [-bdvh] [] + +Options: + -b, --batchmode enable batch mode + -d, --debug enable debug mode + -s, --stack show last 5 items of the stack (off by default) + -i --intermediate print intermediate results + -m, --manual show manual + -c, --config load containing LUA code + -p, --precision floating point number precision (default 2) + -v, --version show version + -h, --help show help + +When is given, batch mode ist automatically enabled. Use +this only when working with stdin. E.g.: echo "2 3 4 5" | rpn + + +Copyright (c) 2023-2025 T.v.Dein` + +func Main() int { + calc := NewCalc() + + showversion := false + showhelp := false + showmanual := false + enabledebug := false + configfile := "" + + flag.BoolVarP(&calc.batch, "batchmode", "b", false, "batch mode") + flag.BoolVarP(&calc.showstack, "show-stack", "s", false, "show stack") + flag.BoolVarP(&calc.intermediate, "showin-termediate", "i", false, + "show intermediate results") + flag.BoolVarP(&enabledebug, "debug", "d", false, "debug mode") + flag.BoolVarP(&showversion, "version", "v", false, "show version") + flag.BoolVarP(&showhelp, "help", "h", false, "show usage") + flag.BoolVarP(&showmanual, "manual", "m", false, "show manual") + flag.StringVarP(&configfile, "config", "c", + os.Getenv("HOME")+"/.rpn.lua", "config file (lua format)") + flag.IntVarP(&calc.precision, "precision", "p", Precision, "floating point precision") + + flag.Parse() + + if showversion { + fmt.Printf("This is rpn version %s\n", VERSION) + + return 0 + } + + if showhelp { + fmt.Println(Usage) + + return 0 + } + + if enabledebug { + calc.ToggleDebug() + } + + if showmanual { + man() + + return 0 + } + + // the lua state object is global, instantiate it early + LuaInterpreter = lua.NewState(lua.Options{SkipOpenLibs: true}) + defer LuaInterpreter.Close() + + // our config file is interpreted as lua code, only functions can + // be defined, init() will be called by InitLua(). + if _, err := os.Stat(configfile); err == nil { + luarunner := NewInterpreter(configfile, enabledebug) + luarunner.InitLua() + calc.SetInt(luarunner) + + if calc.debug { + fmt.Println("loaded config") + } + } else if calc.debug { + fmt.Println(err) + } + + if len(flag.Args()) > 1 { + // commandline calc operation, no readline etc needed + // called like rpn 2 2 + + calc.stdin = true + if err := calc.Eval(strings.Join(flag.Args(), " ")); err != nil { + fmt.Println(err) + + return 1 + } + + return 0 + } + + // interactive mode, need readline + reader, err := readline.NewEx(&readline.Config{ + Prompt: calc.Prompt(), + HistoryFile: os.Getenv("HOME") + "/.rpn-history", + HistoryLimit: 500, + AutoComplete: calc.completer, + InterruptPrompt: "^C", + EOFPrompt: "exit", + HistorySearchFold: true, + }) + + if err != nil { + panic(err) + } + defer func() { + if err := reader.Close(); err != nil { + log.Fatal(err) + } + }() + + reader.CaptureExitSignal() + + if inputIsStdin() { + // commands are coming on stdin, however we will still enter + // the same loop since readline just reads fine from stdin + calc.ToggleStdin() + } + + for { + // primary program repl + line, err := reader.Readline() + if err != nil { + break + } + + err = calc.Eval(line) + if err != nil { + fmt.Println(err) + } + + reader.SetPrompt(calc.Prompt()) + } + + if len(flag.Args()) > 0 { + // called like this: + // echo 1 2 3 4 | rpn + + // batch mode enabled automatically + calc.batch = true + if err = calc.Eval(flag.Args()[0]); err != nil { + fmt.Println(err) + + return 1 + } + } + + return 0 +} + +func inputIsStdin() bool { + stat, _ := os.Stdin.Stat() + + return (stat.Mode() & os.ModeCharDevice) == 0 +} + +func man() { + Pager("rpn manual page", manpage) +} diff --git a/rpn.go b/cmd/rpn.go similarity index 99% rename from rpn.go rename to cmd/rpn.go index 667134c..d0cb434 100644 --- a/rpn.go +++ b/cmd/rpn.go @@ -1,4 +1,4 @@ -package main +package cmd var manpage = ` NAME diff --git a/stack.go b/cmd/stack.go similarity index 99% rename from stack.go rename to cmd/stack.go index 35baf16..83ec620 100644 --- a/stack.go +++ b/cmd/stack.go @@ -15,7 +15,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -package main +package cmd import ( "container/list" diff --git a/stack_test.go b/cmd/stack_test.go similarity index 99% rename from stack_test.go rename to cmd/stack_test.go index b6ea807..f31e418 100644 --- a/stack_test.go +++ b/cmd/stack_test.go @@ -15,7 +15,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -package main +package cmd import ( "testing" diff --git a/util.go b/cmd/util.go similarity index 99% rename from util.go rename to cmd/util.go index 43134d5..600598a 100644 --- a/util.go +++ b/cmd/util.go @@ -15,7 +15,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -package main +package cmd import ( "fmt" diff --git a/util_test.go b/cmd/util_test.go similarity index 98% rename from util_test.go rename to cmd/util_test.go index 7c37735..ad0c3f2 100644 --- a/util_test.go +++ b/cmd/util_test.go @@ -15,7 +15,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -package main +package cmd import ( "testing" diff --git a/main.go b/main.go index ac8285f..72c7ae7 100644 --- a/main.go +++ b/main.go @@ -18,182 +18,10 @@ along with this program. If not, see . package main import ( - "fmt" - "log" "os" - "strings" - - "github.com/chzyer/readline" - flag "github.com/spf13/pflag" - lua "github.com/yuin/gopher-lua" + "rpn/cmd" ) -const VERSION string = "2.1.5" - -const Usage string = `This is rpn, a reverse polish notation calculator cli. - -Usage: rpn [-bdvh] [] - -Options: - -b, --batchmode enable batch mode - -d, --debug enable debug mode - -s, --stack show last 5 items of the stack (off by default) - -i --intermediate print intermediate results - -m, --manual show manual - -c, --config load containing LUA code - -p, --precision floating point number precision (default 2) - -v, --version show version - -h, --help show help - -When is given, batch mode ist automatically enabled. Use -this only when working with stdin. E.g.: echo "2 3 4 5" | rpn + - -Copyright (c) 2023-2025 T.v.Dein` - func main() { - os.Exit(Main()) -} - -func Main() int { - calc := NewCalc() - - showversion := false - showhelp := false - showmanual := false - enabledebug := false - configfile := "" - - flag.BoolVarP(&calc.batch, "batchmode", "b", false, "batch mode") - flag.BoolVarP(&calc.showstack, "show-stack", "s", false, "show stack") - flag.BoolVarP(&calc.intermediate, "showin-termediate", "i", false, - "show intermediate results") - flag.BoolVarP(&enabledebug, "debug", "d", false, "debug mode") - flag.BoolVarP(&showversion, "version", "v", false, "show version") - flag.BoolVarP(&showhelp, "help", "h", false, "show usage") - flag.BoolVarP(&showmanual, "manual", "m", false, "show manual") - flag.StringVarP(&configfile, "config", "c", - os.Getenv("HOME")+"/.rpn.lua", "config file (lua format)") - flag.IntVarP(&calc.precision, "precision", "p", Precision, "floating point precision") - - flag.Parse() - - if showversion { - fmt.Printf("This is rpn version %s\n", VERSION) - - return 0 - } - - if showhelp { - fmt.Println(Usage) - - return 0 - } - - if enabledebug { - calc.ToggleDebug() - } - - if showmanual { - man() - - return 0 - } - - // the lua state object is global, instantiate it early - LuaInterpreter = lua.NewState(lua.Options{SkipOpenLibs: true}) - defer LuaInterpreter.Close() - - // our config file is interpreted as lua code, only functions can - // be defined, init() will be called by InitLua(). - if _, err := os.Stat(configfile); err == nil { - luarunner := NewInterpreter(configfile, enabledebug) - luarunner.InitLua() - calc.SetInt(luarunner) - - if calc.debug { - fmt.Println("loaded config") - } - } else if calc.debug { - fmt.Println(err) - } - - if len(flag.Args()) > 1 { - // commandline calc operation, no readline etc needed - // called like rpn 2 2 + - calc.stdin = true - if err := calc.Eval(strings.Join(flag.Args(), " ")); err != nil { - fmt.Println(err) - - return 1 - } - - return 0 - } - - // interactive mode, need readline - reader, err := readline.NewEx(&readline.Config{ - Prompt: calc.Prompt(), - HistoryFile: os.Getenv("HOME") + "/.rpn-history", - HistoryLimit: 500, - AutoComplete: calc.completer, - InterruptPrompt: "^C", - EOFPrompt: "exit", - HistorySearchFold: true, - }) - - if err != nil { - panic(err) - } - defer func() { - if err := reader.Close(); err != nil { - log.Fatal(err) - } - }() - - reader.CaptureExitSignal() - - if inputIsStdin() { - // commands are coming on stdin, however we will still enter - // the same loop since readline just reads fine from stdin - calc.ToggleStdin() - } - - for { - // primary program repl - line, err := reader.Readline() - if err != nil { - break - } - - err = calc.Eval(line) - if err != nil { - fmt.Println(err) - } - - reader.SetPrompt(calc.Prompt()) - } - - if len(flag.Args()) > 0 { - // called like this: - // echo 1 2 3 4 | rpn + - // batch mode enabled automatically - calc.batch = true - if err = calc.Eval(flag.Args()[0]); err != nil { - fmt.Println(err) - - return 1 - } - } - - return 0 -} - -func inputIsStdin() bool { - stat, _ := os.Stdin.Stat() - - return (stat.Mode() & os.ModeCharDevice) == 0 -} - -func man() { - Pager("rpn manual page", manpage) + os.Exit(cmd.Main()) }