diff --git a/README.md b/README.md index e69de29..cfed43b 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,6 @@ += dev-expr +Simple program for testing purpose on the go-pkg/expr module, + +== First build +Currently, _dev-expr_ is all contained in the main.go source file, but the version infos that are imported from the version.go file, not included in this repository +Use the build.bash script to create the version.go file and build the program, at least for the first time. diff --git a/build.bash b/build.bash new file mode 100755 index 0000000..4c8d87d --- /dev/null +++ b/build.bash @@ -0,0 +1,173 @@ +#!/usr/bin/env bash +# Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). +# All rights reserved. + + +RESOURCE_FILE=".build.rc" +BUILD_REGISTER=".build_register" + +PROGRAM_NAME="" +PROGRAM_VERSION="" + +function usage() { + prog=$(basename "${0}") + msgln "USAGE:" + #msgln " ${prog} /..." + msgln " ${prog} /..." + msgln " ${prog} --dev Build the local exec" + msgln " ${prog} --init Create the resource file in the current directory" + msgln + if [ -r "${RESOURCE_FILE}" ]; then + msgln "Resource file '${RESOURCE_FILE}' content:" + cat >&2 ${RESOURCE_FILE} + else + msgln "Resource file '${RESOURCE_FILE}' not found" + fi +} + +function msgln() { + echo >&2 "${1}" +} + +function exitUsage() { + echo >&2 "${1}" + usage + exit 1 +} + +function exitMsg() { + echo >&2 "${1}" + exit 1 +} + +# CMDLINE: help +if [ "${1}" == "help,," ] || [ "${1,,}" == "--help" ] || [ "${1,,}" == "-h" ]; then + usage + exit +fi + +# CMDLINE: init +if [ "${1,,}" == "--init" ]; then + cat >"${RESOURCE_FILE}" <"${BUILD_REGISTER}" "${PROGRAM_VERSION} ${count}" + echo ${count} +} + +function build() { + local target=${1} ext cmd + IFS=/ read os cpu <<<"${p}" + #msgln "OS=${os}; CPU=${cpu}" + ext="" + if [ "${os}" == 'windows' ]; then + ext=".exe" + fi + cmd="GOOS='${os}' GOARCH='${cpu}' go build -o '${PROGRAM_NAME}_v${PROGRAM_VERSION}_${os}_${cpu}${ext}'" + eval "${cmd}" +} + +function buildLocal() { + local ext cmd + ext="" + if [[ "${OSTYPE}" =~ win.* ]]; then + ext=".exe" + fi + cmd="go build -o '${PROGRAM_NAME}${ext}'" + eval "${cmd}" +} + +function exprPath() { + local path + path=$(grep -m1 -F "go-pkg/expr" go.work) + echo ${path} +} + +function gitTag() { + local expr_path + expr_path=$(exprPath) + tag=$(git -C "${expr_path}" tag -l --sort=-version:refname "v*"|head -1) + echo ${tag} +} + +function createVersionSource() { + local tag + tag=$(gitTag) + cat >version.go <&2 "${1}" +} + +function exitMsg() { + echo >&2 "${1}" + exit 1 +} + +function readBuildCount() { + local reg ver count + if [ -r "${BUILD_REGISTER}" ]; then + reg=$(<"${BUILD_REGISTER}") + else + reg="${PROGRAM_VERSION} 0" + fi + read ver count <<<"${reg}" + if [ "${ver}" != "${PROGRAM_VERSION}" ]; then + count=0 + fi + echo ${count} +} + +if [ -r "${GITEA_PASSWORD_FILE}" ]; then + if ! PASSWORD=$(<"${GITEA_PASSWORD_FILE}"); then + exitMsg "Can're password file '${GITEA_PASSWORD_FILE}'" + fi +else + exitMsg "Password file '${GITEA_PASSWORD_FILE}' not found" +fi +if [ -z "${PASSWORD}" ]; then + exitMsg "Empty password. Please, check file '${GITEA_PASSWORD_FILE}'" +fi + +if [ -r "${RESOURCE_FILE}" ]; then + source "${RESOURCE_FILE}" +else + exitMsg "resource file '${RESOURCE_FILE}' not found" +fi + +if [ -r "${BUILD_REGISTER}" ]; then + BUILD_TAG=$(<"${BUILD_REGISTER}") +else + exitMsg "build register file '${BUILD_REGISTER}' not found" +fi +url="${GITEA_HOST}/${GITEA_BASE_PATH}/${GITEA_OWNER}/${GITEA_PKG_TYPE}/${PROGRAM_NAME}/${PROGRAM_VERSION}/files" +#echo "URL: ${url}" +#echo $(curl --user "${GITEA_USER}:${PASSWORD}" -X GET ${url}|jq '.[]."name"') + +declare -a files=( +$(curl --no-progress-meter --user "${GITEA_USER}:${PASSWORD}" -X GET ${url}|jq '.[]."name"') +) + +for name in ${files[@]}; do + filename=${name:1:${#name}-2} + name_terminal=${filename##*_} + filever=${name_terminal%%.*} + + if [ "${BUILD_TAG}" != "${PROGRAM_VERSION} ${filever}" ]; then + msgln "Deleting ${name}" + curl --no-progress-meter --user "${GITEA_USER}:${PASSWORD}" -X DELETE ${GITEA_HOST}/api/packages/${GITEA_OWNER}/${GITEA_PKG_TYPE}/${PROGRAM_NAME}/${PROGRAM_VERSION}/${filename} +# else +# echo "most recent version" + fi +done + +#curl --user "${GITEA_USER}:${PASSWORD}" -X GET https://git.portale-stac.it/api/v1/packages/go-pkg/generic/dev-expr/1.7.0/files diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..ec42a38 --- /dev/null +++ b/go.mod @@ -0,0 +1,14 @@ +module dev-expr + +go 1.21.7 + +require ( + git.portale-stac.it/go-pkg/expr v0.17.0 + git.portale-stac.it/go-pkg/utils v0.2.0 + github.com/ergochat/readline v0.1.1 +) + +require ( + golang.org/x/sys v0.21.0 // indirect + golang.org/x/text v0.16.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..6e86da4 --- /dev/null +++ b/go.sum @@ -0,0 +1,22 @@ +git.portale-stac.it/go-pkg/expr v0.1.0 h1:7xGEuUhdh6RRFaRbRnLVqVJBmHJWHfqjDBm2K0fIW2s= +git.portale-stac.it/go-pkg/expr v0.1.0/go.mod h1:kUFEQkUMCJ1IiUKkL0P5/vznaAIzFI26Xf5P0rTXqR0= +git.portale-stac.it/go-pkg/expr v0.2.0 h1:AAaVsV0uaC4EikKU91VuubIpbIN7wuya7t4avyFgg+0= +git.portale-stac.it/go-pkg/expr v0.2.0/go.mod h1:DZqqZ3A9h4qEOs7yMvG4VZq7B/xhFsYqC3IKd3M2VKc= +git.portale-stac.it/go-pkg/expr v0.17.0 h1:4ANGwJfwJO3AmnKka4Cf1AO9/ckGLMj8RIWeoDFKawQ= +git.portale-stac.it/go-pkg/expr v0.17.0/go.mod h1:DZqqZ3A9h4qEOs7yMvG4VZq7B/xhFsYqC3IKd3M2VKc= +git.portale-stac.it/go-pkg/utils v0.2.0 h1:2l4IVUhElzjaIUJlahPG2DZTGb9x7OXuFTO4z1K6LmY= +git.portale-stac.it/go-pkg/utils v0.2.0/go.mod h1:PebQ45Qbe89aMTd3wcbcx1bkpNRW4/frNLnpuyZYovU= +github.com/ergochat/readline v0.1.0 h1:KEIiAnyH9qGZB4K8oq5mgDcExlEKwmZDcyyocgJiABc= +github.com/ergochat/readline v0.1.0/go.mod h1:o3ux9QLHLm77bq7hDB21UTm6HlV2++IPDMfIfKDuOgY= +github.com/ergochat/readline v0.1.1 h1:C8Uuo3ybB23GWOt0uxmHbGzKM9owmtXary6Clrj84s0= +github.com/ergochat/readline v0.1.1/go.mod h1:o3ux9QLHLm77bq7hDB21UTm6HlV2++IPDMfIfKDuOgY= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= diff --git a/main.go b/main.go new file mode 100644 index 0000000..774188c --- /dev/null +++ b/main.go @@ -0,0 +1,613 @@ +// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). +// All rights reserved. + +// main.go +package main + +import ( + "bufio" + "fmt" + "io" + "os" + "strings" + + "git.portale-stac.it/go-pkg/expr" + "git.portale-stac.it/go-pkg/utils" + + // https://pkg.go.dev/github.com/ergochat/readline#section-readme + "github.com/ergochat/readline" +) + +const ( + intro = PROGNAME + ` -- Expressions calculator ` + VERSION + ` + Based on the Expr package ` + EXPR_VERSION + ` + Type help to get the list of available commands + See also https://git.portale-stac.it/go-pkg/expr/src/branch/main/README.adoc +` + mainPrompt = ">>> " + contPrompt = "... " + + historyFile = "~/.expr_history" +) + +type commandFunction func(ctx expr.ExprContext, args []string) (err error) + +type command struct { + name string + description string + code commandFunction +} + +func (cmd *command) exec(ctx expr.ExprContext, args []string) (err error) { + return cmd.code(ctx, args) +} + +type commandHandler struct { + commands map[string]*command + // ctx expr.ExprContext +} + +func NewCommandHandler() *commandHandler { + return &commandHandler{ + commands: make(map[string]*command), + } +} + +// func (h *commandHandler) setContext(ctx expr.ExprContext) { +// h.ctx = ctx +// } + +func (h *commandHandler) add(name, description string, f commandFunction) { + h.commands[name] = &command{name: name, description: description, code: f} +} + +func (h *commandHandler) get(cmdLine string) (cmd *command, args []string) { + if len(cmdLine) > 0 { + tokens := strings.Split(cmdLine, " ") + name := tokens[0] + args = make([]string, 0, len(tokens)-1) + if cmd = h.commands[name]; cmd != nil && len(tokens) > 1 { + for _, tk := range tokens[1:] { + if tk != "" { + args = append(args, tk) + } + } + } + } + return +} + +// ------ + +type Options struct { + printTree bool + printPrefix bool + forceInteractive bool + builtin []any + expressions []io.Reader + formOpt expr.FmtOpt + baseVerb string + base int + output bool + rcCount int +} + +func NewOptions() *Options { + return &Options{ + expressions: make([]io.Reader, 0), + builtin: make([]any, 0), + formOpt: expr.Base10, + baseVerb: "%d", + base: 10, + output: true, + rcCount: 0, + } +} + +func errOptValueRequired(opt string) error { + return fmt.Errorf("option %q requires a value", opt) +} + +func about() string { + return PROGNAME + " -- " + VERSION + "; Expr package " + EXPR_VERSION +} + +func (opt *Options) loadRc() { + var rcPath string + var fh *os.File + var err error + if rcPath, err = utils.ExpandPath("~/.dev-expr.rc"); err != nil { + return + } + if fh, err = os.Open(rcPath); err == nil { + opt.expressions = append(opt.expressions, fh) + opt.rcCount++ + } +} + +func (opt *Options) parseArgs() (err error) { + for i := 1; i < len(os.Args) && err == nil; i++ { + arg := os.Args[i] + switch arg { + case "-i": + opt.forceInteractive = true + case "-t": + opt.printTree = true + case "-p": + opt.printPrefix = true + case "-e": + if i+1 < len(os.Args) { + i++ + spec := os.Args[i] + if strings.HasPrefix(spec, "@") { + var f *os.File + if f, err = os.Open(spec[1:]); err == nil { + opt.expressions = append(opt.expressions, f) + } else { + return + } + } else { + opt.expressions = append(opt.expressions, strings.NewReader(spec)) + } + } else { + err = errOptValueRequired(arg) + } + case "-b": + if i+1 < len(os.Args) { + i++ + specs := strings.Split(os.Args[i], ",") + if len(specs) == 1 { + opt.builtin = append(opt.builtin, specs[0]) + } else { + opt.builtin = append(opt.builtin, specs) + } + } else { + err = errOptValueRequired(arg) + } + case "-m", "--modules": + expr.IterateBuiltinModules(func(name, description string, _ bool) bool { + fmt.Printf("%20q: %s\n", name, description) + return true + }) + os.Exit(0) + case "--noout": + opt.output = false + case "-h", "--help", "help": + cmdHandler.help() + os.Exit(0) + case "-v", "--version", "version", "about": + fmt.Println(about()) + os.Exit(0) + default: + err = fmt.Errorf("invalid option nr %d %q", i+1, arg) + } + } + return +} + +// ------ +var cmdHandler *commandHandler + +func (h *commandHandler) help() { + fmt.Fprintln(os.Stderr, `--- REPL commands:`) + for _, cmd := range h.commands { + fmt.Fprintf(os.Stderr, "%12s -- %s\n", cmd.name, cmd.description) + } + fmt.Fprint(os.Stderr, ` +--- Command line options: + -b Import builtin modules. + can be a list of module names or a glob-pattern. + Use the special value 'all' or the pattern '*' to import all modules. + -e Evaluate instead of standard-input + -i Force REPL operation when all -e occurences have been processed + -h, --help, help Show this help menu + -m, --modules List all builtin modules + --noout Disable printing of expression results + -p Print prefix form + -t Print tree form + -v, --version Show program version +`) + +} + +func importBuiltins(opt *Options) (err error) { + for _, spec := range opt.builtin { + if moduleSpec, ok := spec.(string); ok { + if moduleSpec == "all" { + moduleSpec = "*" + } + _, err = expr.ImportInContextByGlobPattern(moduleSpec) + } else if moduleSpec, ok := spec.([]string); ok { + notFoundList := make([]string, 0) + for _, name := range moduleSpec { + if !expr.ImportInContext(name) { + notFoundList = append(notFoundList, name) + } + } + if len(notFoundList) > 0 { + err = fmt.Errorf("not found modules: %s", strings.Join(notFoundList, ",")) + } + } + } + return +} + +func initReadlineConfig(cfg *readline.Config) { + if histfile, err := utils.ExpandPath(historyFile); err == nil { + cfg.HistoryFile = histfile + } + cfg.Undo = true + cfg.DisableAutoSaveHistory = true + +} + +func goInteractiveReadline(opt *Options, ctx expr.ExprContext, r io.Reader) { + var sb strings.Builder + var cfg readline.Config + initReadlineConfig(&cfg) + rl, err := readline.NewFromConfig(&cfg) + if err != nil { + goInteractive(opt, ctx, r) + return + } + defer rl.Close() + + fmt.Print(intro) + rl.SetPrompt(mainPrompt) + for line, err := rl.ReadLine(); err == nil; line, err = rl.ReadLine() { + if len(line) > 0 && line[len(line)-1] == '\\' { + sb.WriteString(line[0 : len(line)-1]) + rl.SetPrompt(contPrompt) + continue + } + rl.SetPrompt(mainPrompt) + sb.WriteString(line) + source := strings.TrimSpace(sb.String()) + if source != "" && !strings.HasPrefix(source, "//") { + if cmd, args := cmdHandler.get(source); cmd != nil { + rl.SaveToHistory(source) + if err = cmd.exec(ctx, args); err != nil { + break + } + } else { + rl.SaveToHistory(source) + r := strings.NewReader(source) + compute(opt, ctx, r, true) + + } + } + sb.Reset() + } + fmt.Println() +} + +func goInteractive(opt *Options, ctx expr.ExprContext, r io.Reader) { + var sb strings.Builder + fmt.Print(intro) + fmt.Print(mainPrompt) + reader := bufio.NewReaderSize(r, 1024) + for line, err := reader.ReadString('\n'); err == nil && line != "exit\n"; line, err = reader.ReadString('\n') { + if strings.HasSuffix(line, "\\\n") { + sb.WriteString(line[0 : len(line)-2]) + fmt.Print(contPrompt) + continue + } + sb.WriteString(line) + source := strings.TrimSpace(sb.String()) + // fmt.Printf("source=%q\n", source) + if source != "" && !strings.HasPrefix(source, "//") { + if cmd, args := cmdHandler.get(source); cmd != nil { + if err = cmd.exec(ctx, args); err != nil { + break + } + } else { + r := strings.NewReader(source) + compute(opt, ctx, r, true) + } + } + sb.Reset() + fmt.Print(mainPrompt) + } + fmt.Println() +} + +func goBatch(opt *Options, ctx expr.ExprContext, r io.Reader) { + var sb strings.Builder + reader := bufio.NewReaderSize(r, 1024) + for line, err := reader.ReadString('\n'); err == nil && line != "exit\n"; line, err = reader.ReadString('\n') { + if strings.HasSuffix(line, "\\\n") { + sb.WriteString(line[0 : len(line)-2]) + continue + } + sb.WriteString(line) + source := strings.TrimSpace(sb.String()) + // fmt.Printf("source=%q\n", source) + if source != "" && !strings.HasPrefix(source, "//") { + if cmd, args := cmdHandler.get(source); cmd != nil { + if err = cmd.exec(ctx, args); err != nil { + break + } + } else { + r := strings.NewReader(source) + compute(opt, ctx, r, false) + } + } + sb.Reset() + } +} + +func compute(opt *Options, ctx expr.ExprContext, r io.Reader, outputEnabled bool) { + scanner := expr.NewScanner(r, expr.DefaultTranslations()) + parser := expr.NewParser() + + if ast, err := parser.Parse(scanner); err == nil { + if opt.printPrefix { + fmt.Println(ast) + } + if opt.printTree { + r := expr.NewExprReticle(ast) + fmt.Println(r.String()) + } + + outputEnabled = outputEnabled && opt.output + if result, err := ast.Eval(ctx); err == nil { + if outputEnabled { + printResult(opt, result) + } + } else { + fmt.Fprintln(os.Stderr, "Eval Error:", err) + } + } else { + fmt.Fprintln(os.Stderr, "Parse Error:", err) + } +} + +func printResult(opt *Options, result any) { + if f, ok := result.(expr.Formatter); ok { + fmt.Println(f.ToString(opt.formOpt)) + } else if expr.IsInteger(result) { + fmt.Printf(opt.baseVerb, result) + fmt.Println() + } else if expr.IsString(result) { + fmt.Printf("\"%s\"\n", result) + } else { + fmt.Println(result) + } +} + +func isReaderTerminal(r io.Reader) bool { + if fh, ok := r.(*os.File); ok { + return utils.StreamIsTerminal(fh) + } + return false +} + +func registerLocalFunctions(ctx expr.ExprContext) { + aboutFunc := func(ctx expr.ExprContext, name string, args []any) (result any, err error) { + result = about() + return + } + + ctrlListFunc := func(ctx expr.ExprContext, name string, args []any) (result any, err error) { + vars := ctx.EnumVars(func(name string) bool { + return len(name) > 0 && name[0] == '_' + }) + result = expr.ListFromStrings(vars) + return + } + + ctrlFunc := func(ctx expr.ExprContext, name string, args []any) (result any, err error) { + varName, _ := args[0].(string) + if len(args) == 1 { + result = expr.GlobalCtrlGet(varName) + } else { + result = expr.GlobalCtrlSet(varName, args[1]) + } + return + } + + envSetFunc := func(ctx expr.ExprContext, name string, args []any) (result any, err error) { + var varName, value string + var ok bool + if varName, ok = args[0].(string); !ok { + err = expr.ErrExpectedGot(name, expr.TypeString, args[0]) + return + } + if value, ok = args[1].(string); !ok { + err = expr.ErrExpectedGot(name, expr.TypeString, args[1]) + return + } + if err = os.Setenv(varName, value); err == nil { + result = value + } + return + } + + envGetFunc := func(ctx expr.ExprContext, name string, args []any) (result any, err error) { + var varName string + var ok bool + if varName, ok = args[0].(string); !ok { + err = expr.ErrExpectedGot(name, expr.TypeString, args[0]) + return + } + + if result, ok = os.LookupEnv(varName); !ok { + err = fmt.Errorf("environment variable %q does not exist", varName) + } + return + } + + ctx.RegisterFunc("about", expr.NewGolangFunctor(aboutFunc), expr.TypeString, []expr.ExprFuncParam{}) + ctx.RegisterFunc("ctrlList", expr.NewGolangFunctor(ctrlListFunc), expr.TypeListOfStrings, []expr.ExprFuncParam{}) + ctx.RegisterFunc("ctrl", expr.NewGolangFunctor(ctrlFunc), expr.TypeAny, []expr.ExprFuncParam{ + expr.NewFuncParam("prop"), + expr.NewFuncParamFlag(expr.ParamValue, expr.PfOptional), + }) + ctx.RegisterFunc("envSet", expr.NewGolangFunctor(envSetFunc), expr.TypeString, []expr.ExprFuncParam{ + expr.NewFuncParam(expr.ParamName), + expr.NewFuncParam(expr.ParamValue), + }) + ctx.RegisterFunc("envGet", expr.NewGolangFunctor(envGetFunc), expr.TypeString, []expr.ExprFuncParam{ + expr.NewFuncParam(expr.ParamName), + }) + +} + +var opt *Options + +func main() { + opt = NewOptions() + opt.loadRc() + if err := opt.parseArgs(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + + if err := importBuiltins(opt); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + + ctx := expr.NewSimpleStore() + registerLocalFunctions(ctx) + + if len(opt.expressions) == opt.rcCount || opt.forceInteractive { + opt.expressions = append(opt.expressions, os.Stdin) + } + + for _, input := range opt.expressions { + if isReaderTerminal(input) { + goInteractiveReadline(opt, ctx, input) + } else { + goBatch(opt, ctx, input) + if f, ok := input.(*os.File); ok { + f.Close() + } + } + } + if opt.output { + printResult(opt, ctx.GetLast()) + } +} + +// -------- + +func cmdExit(ctx expr.ExprContext, args []string) (err error) { + return io.EOF +} + +func cmdHelp(ctx expr.ExprContext, args []string) (err error) { + cmdHandler.help() + return +} + +func cmdMultiLine(ctx expr.ExprContext, args []string) (err error) { + if opt.formOpt&expr.MultiLine == 0 { + opt.formOpt |= expr.MultiLine + } else { + opt.formOpt &= ^expr.MultiLine + } + return +} + +func cmdTty(ctx expr.ExprContext, args []string) (err error) { + if opt.formOpt&expr.TTY == 0 { + opt.formOpt |= expr.TTY + } else { + opt.formOpt &= ^expr.TTY + } + return +} + +func cmdSource(ctx expr.ExprContext, args []string) (err error) { + var fh *os.File + for _, arg := range args { + length := len(arg) + if length == 0 { + continue + } + if length >= 2 { + if (arg[0] == '"' && arg[length-1] == '"') || arg[0] == '\'' && arg[length-1] == '\'' { + arg = arg[1 : length-1] + } + } + if fh, err = os.Open(arg); err == nil { + goBatch(opt, ctx, fh) + fh.Close() + } else { + break + } + } + return +} + +func cmdModules(ctx expr.ExprContext, args []string) (err error) { + expr.IterateBuiltinModules(func(name, description string, imported bool) bool { + var check rune = ' ' + if imported { + check = '*' + } + fmt.Printf("%c %20q: %s\n", check, name, description) + return true + }) + return +} + +func cmdBase(ctx expr.ExprContext, args []string) (err error) { + if len(args) == 0 { + fmt.Println(opt.base) + } else if args[0] == "2" { + opt.baseVerb = "0b%b" + opt.base = 2 + } else if args[0] == "8" { + opt.baseVerb = "0o%o" + opt.base = 8 + } else if args[0] == "10" { + opt.baseVerb = "%d" + opt.base = 10 + } else if args[0] == "16" { + opt.baseVerb = "0x%x" + opt.base = 16 + } else { + err = fmt.Errorf("invalid number base %s", args[0]) + } + return +} + +func cmdOutput(ctx expr.ExprContext, args []string) (err error) { + var outputArg string + if len(args) == 0 { + outputArg = "status" + } else { + outputArg = strings.ToLower(args[0]) + } + switch outputArg { + case "on": + opt.output = true + case "off": + opt.output = false + case "status": + if opt.output { + fmt.Println("on") + } else { + fmt.Println("off") + } + default: + err = fmt.Errorf("output: unknown option %q", outputArg) + } + return +} + +//------------------ + +func init() { + cmdHandler = NewCommandHandler() + cmdHandler.add("base", "Set the integer output base: 2, 8, 10, or 16", cmdBase) + cmdHandler.add("exit", "Exit the program", cmdExit) + cmdHandler.add("help", "Show command list", cmdHelp) + cmdHandler.add("ml", "Enable/Disable multi-line output", cmdMultiLine) + cmdHandler.add("mods", "List builtin modules", cmdModules) + cmdHandler.add("output", "Enable/Disable printing expression results. Options 'on', 'off', 'status'", cmdOutput) + cmdHandler.add("source", "Load a file as input", cmdSource) + cmdHandler.add("tty", "Enable/Disable ansi output", cmdTty) +} diff --git a/publish.bash b/publish.bash new file mode 100755 index 0000000..7949e58 --- /dev/null +++ b/publish.bash @@ -0,0 +1,79 @@ +#!/usr/bin/env bash +# Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). +# All rights reserved. + +RESOURCE_FILE=".build.rc" +BUILD_REGISTER=".build_register" +PASSWORD= +GITEA_USER="camoroso" +GITEA_PASSWORD_FILE="${HOME}/.gitea_password" +GITEA_OWNER="go-pkg" +GITEA_HOST="https://git.portale-stac.it" +GITEA_BASE_PATH="api/packages" +GITEA_PKG_TYPE="generic" + +function exitMsg() { + echo >&2 "${1}" + exit 1 +} + +function readBuildCount() { + local reg ver count + if [ -r "${BUILD_REGISTER}" ]; then + reg=$(<"${BUILD_REGISTER}") + else + reg="${PROGRAM_VERSION} 0" + fi + read ver count <<<"${reg}" + if [ "${ver}" != "${PROGRAM_VERSION}" ]; then + count=0 + fi + echo ${count} +} + +if [ -r "${GITEA_PASSWORD_FILE}" ]; then + if ! PASSWORD=$(<"${GITEA_PASSWORD_FILE}"); then + exitMsg "Can're password file '${GITEA_PASSWORD_FILE}'" + fi +else + exitMsg "Password file '${GITEA_PASSWORD_FILE}' not found" +fi +if [ -z "${PASSWORD}" ]; then + exitMsg "Empty password. Please, check file '${GITEA_PASSWORD_FILE}'" +fi + +if ! ./build.bash; then + exitMsg "Build program failed" +fi + +if ! source "${RESOURCE_FILE}"; then + exitMsg "Loading resource file failed" +fi + +if ! exeList=$(echo 2>/dev/null ${PROGRAM_NAME}_v${PROGRAM_VERSION}_*); then + exitMsg "No executable found" +fi + +buildCount=$(readBuildCount) +fileCount=0 +for exe in ${exeList}; do + if [ "${exe/tar.gz/}" != "${exe}" ]; then + continue + fi + ((fileCount++)) + dir="${exe}_${buildCount}" + dist="${dir}.tar.gz" + rm -f "${dist}" + printf "%2d: %-30s --> %s\n" "${fileCount}" "${exe}" "${dist}" + mkdir "${dir}" + cp "${exe}" "${dir}/${PROGRAM_NAME}" + tar czf "${dist}" "${dir}" + rm -fR "${dir}" + + url="${GITEA_HOST}/${GITEA_BASE_PATH}/${GITEA_OWNER}/${GITEA_PKG_TYPE}/${PROGRAM_NAME}/${PROGRAM_VERSION}/${dist}" +# echo "${url}" + curl --user "${USER}:${PASSWORD}" --upload-file "${dist}" "${url}" + + rm -f "${dist}" +done + diff --git a/version.txt b/version.txt new file mode 100644 index 0000000..81c871d --- /dev/null +++ b/version.txt @@ -0,0 +1 @@ +1.10.0