Compare commits
4 Commits
main
...
all-in-one
| Author | SHA1 | Date | |
|---|---|---|---|
| 6a6c897268 | |||
| 9eb3e2d072 | |||
| cd8f331a32 | |||
| 7ed5a806f9 |
@@ -0,0 +1,14 @@
|
|||||||
|
# Builder resource file
|
||||||
|
# Created on gio 21 mag 2026, 15:35:18, CEST
|
||||||
|
|
||||||
|
# Program name
|
||||||
|
PROGRAM_NAME="ecli"
|
||||||
|
|
||||||
|
# Program version
|
||||||
|
PROGRAM_VERSION="$(<version.txt)"
|
||||||
|
|
||||||
|
# Preset platform
|
||||||
|
platform=("linux/amd64" "darwin/arm64")
|
||||||
|
|
||||||
|
#--- end of file ---
|
||||||
|
|
||||||
Executable
+199
@@ -0,0 +1,199 @@
|
|||||||
|
#!/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} <program-name> <program-version> <os>/<platform>..."
|
||||||
|
msgln " ${prog} <os>/<platform>..."
|
||||||
|
msgln " ${prog} --local 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}" <<eot
|
||||||
|
# Builder resource file
|
||||||
|
# Created on $(date)
|
||||||
|
|
||||||
|
# Program name
|
||||||
|
PROGRAM_NAME="name"
|
||||||
|
|
||||||
|
# Program version
|
||||||
|
PROGRAM_VERSION="version"
|
||||||
|
|
||||||
|
# Preset platform
|
||||||
|
platform=("linux/amd64" "darwin/arm64")
|
||||||
|
|
||||||
|
#--- end of file ---
|
||||||
|
|
||||||
|
eot
|
||||||
|
msgln "Resource file '${RESOURCE_FILE}' create. Edit it to set valid values."
|
||||||
|
#${EDITOR-vi} "${RESOURCE_FILE}"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -r "${RESOURCE_FILE}" ]; then
|
||||||
|
if ! source "${RESOURCE_FILE}"; then
|
||||||
|
exitMsg "Can't load build resource file '${RESOURCE_FILE}'"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${PROGRAM_NAME}" ]; then
|
||||||
|
exitUsage "Missing program name"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${PROGRAM_VERSION}" ]; then
|
||||||
|
exitUsage "Missing program version"
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
function getBuildCount() {
|
||||||
|
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
|
||||||
|
count=$((count+1))
|
||||||
|
echo >"${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 gitTag() {
|
||||||
|
local gopath gopkg mod
|
||||||
|
local tag
|
||||||
|
|
||||||
|
if ! tag=$(git tag -l --sort=-version:refname "v[0-9]*.[0-9]*.[0-9]*"|head -1) || [ -z "${tag}" ]; then
|
||||||
|
gopath=$(go env GOPATH)
|
||||||
|
gopkg="${gopath}/pkg/mod/git.portale-stac.it/go-pkg"
|
||||||
|
if cd "${gopkg}" 2>/dev/null; then
|
||||||
|
mod=$(ls -1v |grep expr@|tail -1)
|
||||||
|
tag=${mod##*@}
|
||||||
|
cd - >/dev/null
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo ${tag}
|
||||||
|
}
|
||||||
|
|
||||||
|
function gitTagDate() {
|
||||||
|
local tag_name=${1}
|
||||||
|
local tag_date
|
||||||
|
|
||||||
|
if ! tag_date=$(git show --no-patch --format=%ci "${tag_name}") || [ -z "${tag_date}" ]; then
|
||||||
|
tag_date="n/a"
|
||||||
|
fi
|
||||||
|
echo ${tag_date}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createVersionSource() {
|
||||||
|
local tag tag_date
|
||||||
|
tag=$(gitTag)
|
||||||
|
if [ -z "${tag}" ]; then
|
||||||
|
tag="n/a"
|
||||||
|
else
|
||||||
|
tag_date=$(gitTagDate "${tag}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat >version.go <<eot
|
||||||
|
// Copyright (c) 2024-$(date +%Y) Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// version.go
|
||||||
|
package main
|
||||||
|
|
||||||
|
const (
|
||||||
|
PROGNAME = "${PROGRAM_NAME}"
|
||||||
|
VERSION = "v${PROGRAM_VERSION}(build $(getBuildCount)),$(date +"%Y/%m/%d") (celestino.amoroso@portale-stac.it)"
|
||||||
|
EXPR_VERSION = "${tag}"
|
||||||
|
EXPR_DATE = "${tag_date}"
|
||||||
|
)
|
||||||
|
eot
|
||||||
|
}
|
||||||
|
|
||||||
|
## -- TEST -- ##
|
||||||
|
|
||||||
|
# echo "Tag: $(gitTag)"
|
||||||
|
# echo "Tag Date: $(gitTagDate $(gitTag))"
|
||||||
|
# exit 0
|
||||||
|
|
||||||
|
## -- MAIN -- ##
|
||||||
|
createVersionSource
|
||||||
|
|
||||||
|
if [ "${1}" == "--local" ]; then
|
||||||
|
buildLocal
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ${#} -gt 0 ]; then
|
||||||
|
for p; do
|
||||||
|
build "${p}"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
for p in ${platform[@]}; do
|
||||||
|
build "${p}"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
Executable
+84
@@ -0,0 +1,84 @@
|
|||||||
|
#!/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/v1/packages"
|
||||||
|
GITEA_PKG_TYPE="generic"
|
||||||
|
|
||||||
|
|
||||||
|
function msgln() {
|
||||||
|
echo >&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/ecli/1.7.0/files
|
||||||
@@ -0,0 +1,236 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// commands.go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.portale-stac.it/go-pkg/expr"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
"git.portale-stac.it/go-pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type commandFunction func(opt *Options, ctx kern.ExprContext, args []string) (err error)
|
||||||
|
|
||||||
|
type command struct {
|
||||||
|
name string
|
||||||
|
description string
|
||||||
|
code commandFunction
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd *command) exec(opt *Options, ctx kern.ExprContext, args []string) (err error) {
|
||||||
|
return cmd.code(opt, ctx, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
type commandHandler struct {
|
||||||
|
cmdIndex []string
|
||||||
|
commands map[string]*command
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCommandHandler() *commandHandler {
|
||||||
|
return &commandHandler{
|
||||||
|
cmdIndex: make([]string, 0, 20),
|
||||||
|
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.cmdIndex = append(h.cmdIndex, name)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------
|
||||||
|
var cmdHandler *commandHandler
|
||||||
|
|
||||||
|
func (h *commandHandler) help() {
|
||||||
|
fmt.Fprintln(os.Stderr, `--- REPL commands:`)
|
||||||
|
for _, name := range h.cmdIndex {
|
||||||
|
cmd := h.commands[name]
|
||||||
|
fmt.Fprintf(os.Stderr, "%12s -- %s\n", cmd.name, cmd.description)
|
||||||
|
}
|
||||||
|
fmt.Fprint(os.Stderr, `
|
||||||
|
--- Command line options:
|
||||||
|
-b <builtin> Import builtin modules.
|
||||||
|
<builtin> can be a list of module names or a glob-pattern.
|
||||||
|
Use the special value 'all' or the pattern '*' to import all modules.
|
||||||
|
-B, --list-builtins List all builtin module names
|
||||||
|
-e <expression> Evaluate <expression> 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 cmdExit(opt *Options, ctx kern.ExprContext, args []string) (err error) {
|
||||||
|
return io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdHelp(opt *Options, ctx kern.ExprContext, args []string) (err error) {
|
||||||
|
cmdHandler.help()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdMultiLine(opt *Options, ctx kern.ExprContext, args []string) (err error) {
|
||||||
|
if opt.formOpt&kern.MultiLine == 0 {
|
||||||
|
opt.formOpt |= kern.MultiLine
|
||||||
|
} else {
|
||||||
|
opt.formOpt &= ^kern.MultiLine
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdTty(opt *Options, ctx kern.ExprContext, args []string) (err error) {
|
||||||
|
if opt.formOpt&kern.TTY == 0 {
|
||||||
|
opt.formOpt |= kern.TTY
|
||||||
|
} else {
|
||||||
|
opt.formOpt &= ^kern.TTY
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func execFile(opt *Options, ctx kern.ExprContext, fileName string) (err error) {
|
||||||
|
var fh *os.File
|
||||||
|
if fh, err = os.Open(fileName); err == nil {
|
||||||
|
goBatch(opt, ctx, fh, false)
|
||||||
|
fh.Close()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdSource(opt *Options, ctx kern.ExprContext, args []string) (err error) {
|
||||||
|
var target string
|
||||||
|
for _, arg := range args {
|
||||||
|
if len(arg) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// TODO migliorare questa parte: eventualmente valutare un'espressione
|
||||||
|
if target, err = checkStringLiteral(arg); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if target, err = utils.ExpandPath(target); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if isPattern(target) {
|
||||||
|
var fileNames []string
|
||||||
|
if fileNames, err = matchPathPattern(target); err == nil {
|
||||||
|
for _, fileName := range fileNames {
|
||||||
|
if err = execFile(opt, ctx, fileName); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = execFile(opt, ctx, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdModules(opt *Options, ctx kern.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(opt *Options, ctx kern.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(opt *Options, ctx kern.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 setupCommands() {
|
||||||
|
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)
|
||||||
|
}
|
||||||
@@ -0,0 +1,314 @@
|
|||||||
|
= Ecli
|
||||||
|
Expression Calculator Interactive Tool
|
||||||
|
:authors: Celestino Amoroso
|
||||||
|
:email: celestino.amoroso@gmail.com
|
||||||
|
:docinfo: shared
|
||||||
|
:encoding: utf-8
|
||||||
|
:toc: right
|
||||||
|
:toclevels: 4
|
||||||
|
:icons: font
|
||||||
|
:icon-set: fi
|
||||||
|
:numbered:
|
||||||
|
:data-uri:
|
||||||
|
:docinfo1:
|
||||||
|
:sectlinks:
|
||||||
|
:sectanchors:
|
||||||
|
:source-highlighter: rouge
|
||||||
|
:rouge-style: manni
|
||||||
|
:stylesdir: /home/share/s3-howto/styles
|
||||||
|
:stylesheet: adoc-colony.css
|
||||||
|
|
||||||
|
// Workaround to manage double-column in back-tick quotes
|
||||||
|
:2c: ::
|
||||||
|
// Workaround to manage double-plus in back-tick quotes
|
||||||
|
:plusplus: ++
|
||||||
|
// Workaround to manage asterisk in back-tick quotes
|
||||||
|
:star: *
|
||||||
|
|
||||||
|
#Generated by Copilot#
|
||||||
|
|
||||||
|
== Overview
|
||||||
|
|
||||||
|
`ecli` (Expression Calculator Interactive Tool) is an interactive REPL (Read-Eval-Print Loop) application for evaluating expressions using the Expr package. It provides a powerful command-line interface for expression evaluation with support for multiple builtin modules, file operations, and interactive scripting.
|
||||||
|
|
||||||
|
The tool combines the expression evaluation capabilities of the Expr package with an interactive shell environment, making it ideal for:
|
||||||
|
|
||||||
|
- Interactive expression testing and prototyping
|
||||||
|
- Batch expression evaluation from scripts
|
||||||
|
- Data processing and transformation
|
||||||
|
- Mathematical computations with fractions and complex operators
|
||||||
|
|
||||||
|
== Getting Started
|
||||||
|
|
||||||
|
=== Installation
|
||||||
|
|
||||||
|
To build and install `ecli`:
|
||||||
|
|
||||||
|
[source,bash]
|
||||||
|
----
|
||||||
|
cd cmd/ecli
|
||||||
|
./build.bash
|
||||||
|
----
|
||||||
|
|
||||||
|
The compiled binary will be available as `ecli` in the current directory.
|
||||||
|
|
||||||
|
=== Basic Usage
|
||||||
|
|
||||||
|
Start the interactive REPL:
|
||||||
|
|
||||||
|
[source,bash]
|
||||||
|
----
|
||||||
|
./ecli
|
||||||
|
----
|
||||||
|
|
||||||
|
You'll see the prompt `>>> ` where you can enter expressions to evaluate.
|
||||||
|
|
||||||
|
=== Command Line Options
|
||||||
|
|
||||||
|
[cols="1,4", options="header"]
|
||||||
|
|===
|
||||||
|
| Option | Description
|
||||||
|
|
||||||
|
| `-e <expression>` | Evaluate an expression directly without entering REPL mode
|
||||||
|
| `-i` | Force REPL operation after processing all `-e` options
|
||||||
|
| `-b <builtin>` | Import builtin modules (comma-separated list, glob patterns, or 'all')
|
||||||
|
| `-B, --list-builtins` | List all available builtin module names
|
||||||
|
| `-m, --modules` | List all builtin modules
|
||||||
|
| `-p` | Print expressions in prefix form
|
||||||
|
| `-t` | Print expressions in tree form
|
||||||
|
| `--noout` | Disable printing of expression results
|
||||||
|
| `-h, --help` | Show help message
|
||||||
|
| `-v, --version` | Show program version
|
||||||
|
|===
|
||||||
|
|
||||||
|
== Interactive Commands
|
||||||
|
|
||||||
|
Within the REPL, you can use the following commands:
|
||||||
|
|
||||||
|
[cols="2,5", options="header"]
|
||||||
|
|===
|
||||||
|
| Command | Description
|
||||||
|
|
||||||
|
| `help` | Display available commands and command-line options
|
||||||
|
| `exit` | Exit the REPL
|
||||||
|
| `multiline` | Toggle multi-line input mode for complex expressions
|
||||||
|
| `tty` | Toggle TTY mode
|
||||||
|
| `source <file>` | Execute expressions from a file
|
||||||
|
|===
|
||||||
|
|
||||||
|
== Features
|
||||||
|
|
||||||
|
=== Expression Evaluation
|
||||||
|
|
||||||
|
`ecli` supports the full expression language provided by the Expr package, including:
|
||||||
|
|
||||||
|
- **Arithmetic Operations**: Addition, subtraction, multiplication, division, modulo
|
||||||
|
- **Bitwise Operations**: AND, OR, XOR, NOT, shift operations
|
||||||
|
- **Boolean Logic**: AND, OR, NOT operations
|
||||||
|
- **Relational Operators**: Comparison and equality operators
|
||||||
|
- **String Operations**: Concatenation and string manipulation
|
||||||
|
- **Iterators**: Range, list, and custom iterators
|
||||||
|
- **Functions**: Builtin and user-defined functions
|
||||||
|
- **Collections**: Lists, dictionaries, and linked lists
|
||||||
|
- **Fractions**: Support for fractional arithmetic
|
||||||
|
|
||||||
|
=== Builtin Modules
|
||||||
|
|
||||||
|
`ecli` provides access to various builtin modules through the `-b` option:
|
||||||
|
|
||||||
|
[cols="1,4", options="header"]
|
||||||
|
|===
|
||||||
|
| Module | Functionality
|
||||||
|
|
||||||
|
| `base` | Core expression evaluation functions
|
||||||
|
| `fmt` | String formatting and output functions
|
||||||
|
| `string` | String manipulation functions
|
||||||
|
| `math-arith` | Mathematical and arithmetic operations
|
||||||
|
| `iterator` | Iterator-related functions
|
||||||
|
| `os-file` | File I/O operations
|
||||||
|
| `import` | Module import functionality
|
||||||
|
|===
|
||||||
|
|
||||||
|
Use `-B` or `--list-builtins` to see all available modules:
|
||||||
|
|
||||||
|
[source,bash]
|
||||||
|
----
|
||||||
|
./ecli --list-builtins
|
||||||
|
----
|
||||||
|
|
||||||
|
== Examples
|
||||||
|
|
||||||
|
=== Basic Arithmetic
|
||||||
|
|
||||||
|
[source,bash]
|
||||||
|
----
|
||||||
|
>>> 2 + 3 * 4
|
||||||
|
14
|
||||||
|
>>> (2 + 3) * 4
|
||||||
|
20
|
||||||
|
----
|
||||||
|
|
||||||
|
=== String Operations
|
||||||
|
|
||||||
|
[source,bash]
|
||||||
|
----
|
||||||
|
>>> "hello" + " " + "world"
|
||||||
|
hello world
|
||||||
|
----
|
||||||
|
|
||||||
|
=== Using Iterators
|
||||||
|
|
||||||
|
[source,bash]
|
||||||
|
----
|
||||||
|
>>> [1, 2, 3, 4, 5] | map(. * 2)
|
||||||
|
[2, 4, 6, 8, 10]
|
||||||
|
----
|
||||||
|
|
||||||
|
=== Evaluating from Command Line
|
||||||
|
|
||||||
|
[source,bash]
|
||||||
|
----
|
||||||
|
./ecli -e "2 + 2"
|
||||||
|
4
|
||||||
|
----
|
||||||
|
|
||||||
|
=== Loading Builtin Modules
|
||||||
|
|
||||||
|
[source,bash]
|
||||||
|
----
|
||||||
|
./ecli -b "math-arith,string"
|
||||||
|
----
|
||||||
|
|
||||||
|
=== Loading Expressions from Files
|
||||||
|
|
||||||
|
Inside the REPL:
|
||||||
|
|
||||||
|
[source]
|
||||||
|
----
|
||||||
|
>>> source "expressions.expr"
|
||||||
|
----
|
||||||
|
|
||||||
|
Or from command line:
|
||||||
|
|
||||||
|
[source,bash]
|
||||||
|
----
|
||||||
|
./ecli -e '@include "expressions.expr"'
|
||||||
|
----
|
||||||
|
|
||||||
|
== Configuration
|
||||||
|
|
||||||
|
=== Resource Files
|
||||||
|
|
||||||
|
`ecli` supports startup resource files:
|
||||||
|
|
||||||
|
- `.ecli.rc` - Main configuration file
|
||||||
|
- `.ecli.rc.d/` - Directory for modular configuration files
|
||||||
|
|
||||||
|
These files are automatically loaded at startup if they exist in the current directory or home directory.
|
||||||
|
|
||||||
|
== Building from Source
|
||||||
|
|
||||||
|
=== Prerequisites
|
||||||
|
|
||||||
|
- Go 1.18 or later
|
||||||
|
- Make or bash shell
|
||||||
|
|
||||||
|
=== Build Steps
|
||||||
|
|
||||||
|
[source,bash]
|
||||||
|
----
|
||||||
|
cd cmd/ecli
|
||||||
|
./build.bash
|
||||||
|
----
|
||||||
|
|
||||||
|
=== Build Artifacts
|
||||||
|
|
||||||
|
The build process generates:
|
||||||
|
|
||||||
|
- `ecli` - The main executable
|
||||||
|
- `version.txt` - Version information
|
||||||
|
- Platform-specific binaries (e.g., `ecli_v1.17.0_linux_amd64`, `ecli_v1.17.0_darwin_arm64`)
|
||||||
|
|
||||||
|
== Advanced Usage
|
||||||
|
|
||||||
|
=== Multi-line Input
|
||||||
|
|
||||||
|
For complex expressions, toggle multi-line mode:
|
||||||
|
|
||||||
|
[source]
|
||||||
|
----
|
||||||
|
>>> multiline
|
||||||
|
>>> result = [1, 2, 3, 4, 5]
|
||||||
|
... | filter(. > 2)
|
||||||
|
... | map(. * 2)
|
||||||
|
>>> result
|
||||||
|
[6, 8, 10]
|
||||||
|
----
|
||||||
|
|
||||||
|
=== Script Execution
|
||||||
|
|
||||||
|
Create a file `calculations.expr`:
|
||||||
|
|
||||||
|
[source]
|
||||||
|
----
|
||||||
|
x = 10
|
||||||
|
y = 20
|
||||||
|
result = x + y * 2
|
||||||
|
----
|
||||||
|
|
||||||
|
Execute it:
|
||||||
|
|
||||||
|
[source,bash]
|
||||||
|
----
|
||||||
|
./ecli -e '@source "calculations.expr"' -e 'result'
|
||||||
|
----
|
||||||
|
|
||||||
|
=== Chaining Operations
|
||||||
|
|
||||||
|
[source]
|
||||||
|
----
|
||||||
|
>>> data = [{"name": "alice", "age": 30}, {"name": "bob", "age": 25}]
|
||||||
|
>>> data | map(.name)
|
||||||
|
[alice, bob]
|
||||||
|
----
|
||||||
|
|
||||||
|
== Troubleshooting
|
||||||
|
|
||||||
|
=== Expression Parsing Errors
|
||||||
|
|
||||||
|
If you encounter parsing errors, check:
|
||||||
|
|
||||||
|
- Bracket matching and quotation marks
|
||||||
|
- Operator precedence
|
||||||
|
- Variable and function names
|
||||||
|
|
||||||
|
=== Module Loading Issues
|
||||||
|
|
||||||
|
Verify available modules:
|
||||||
|
|
||||||
|
[source,bash]
|
||||||
|
----
|
||||||
|
./ecli --list-builtins
|
||||||
|
----
|
||||||
|
|
||||||
|
=== File Not Found Errors
|
||||||
|
|
||||||
|
Ensure file paths are:
|
||||||
|
|
||||||
|
- Properly quoted in expressions
|
||||||
|
- Relative to the current working directory or absolute paths
|
||||||
|
- Readable by the current user
|
||||||
|
|
||||||
|
== Related Documentation
|
||||||
|
|
||||||
|
- link:../../../README.adoc[Expr Package Documentation]
|
||||||
|
- Expr Expression Language Syntax
|
||||||
|
- Builtin Modules Reference
|
||||||
|
|
||||||
|
== Version History
|
||||||
|
|
||||||
|
For version information and changes, see the link:version.txt[version file].
|
||||||
|
|
||||||
|
== License
|
||||||
|
|
||||||
|
Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com). All rights reserved.
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
module ecli
|
||||||
|
|
||||||
|
go 1.24.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
git.portale-stac.it/go-pkg/expr v0.33.0
|
||||||
|
git.portale-stac.it/go-pkg/utils v0.3.0
|
||||||
|
github.com/ergochat/readline v0.1.3
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 // indirect
|
||||||
|
golang.org/x/sys v0.38.0 // indirect
|
||||||
|
golang.org/x/text v0.31.0 // indirect
|
||||||
|
)
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
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/expr v0.32.0 h1:ikXqHjJslIGkD79G1/51xe+c25TFi2CslJ6nu8mOuJY=
|
||||||
|
git.portale-stac.it/go-pkg/expr v0.32.0/go.mod h1:R2TYIahLtD8YDgNEHtgHCQdoEUZ7yCQsMHyJXhJijmw=
|
||||||
|
git.portale-stac.it/go-pkg/expr v0.33.0 h1:GJ7PPgA1689GSC/cUWGYm08jn7qMmkp0FMQf/As5sCw=
|
||||||
|
git.portale-stac.it/go-pkg/expr v0.33.0/go.mod h1:R2TYIahLtD8YDgNEHtgHCQdoEUZ7yCQsMHyJXhJijmw=
|
||||||
|
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=
|
||||||
|
git.portale-stac.it/go-pkg/utils v0.3.0 h1:kCJ3+XcekV7in/SieJjiswdtJKMBS0RTJMlG2fW5mK0=
|
||||||
|
git.portale-stac.it/go-pkg/utils v0.3.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=
|
||||||
|
github.com/ergochat/readline v0.1.3 h1:/DytGTmwdUJcLAe3k3VJgowh5vNnsdifYT6uVaf4pSo=
|
||||||
|
github.com/ergochat/readline v0.1.3/go.mod h1:o3ux9QLHLm77bq7hDB21UTm6HlV2++IPDMfIfKDuOgY=
|
||||||
|
golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 h1:zfMcR1Cs4KNuomFFgGefv5N0czO2XZpUbxGUy8i8ug0=
|
||||||
|
golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0=
|
||||||
|
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/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||||
|
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
|
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=
|
||||||
|
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||||
|
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
//go:build graph
|
||||||
|
|
||||||
|
// graph.go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.portale-stac.it/go-pkg/expr"
|
||||||
|
)
|
||||||
|
|
||||||
|
func printGraph() {
|
||||||
|
r := expr.NewExprReticle(ast)
|
||||||
|
fmt.Println(r.String())
|
||||||
|
}
|
||||||
@@ -0,0 +1,415 @@
|
|||||||
|
// 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/expr/kern"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/scan"
|
||||||
|
"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 + ` (` + EXPR_DATE + `)
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ------
|
||||||
|
|
||||||
|
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 importBuiltins(ctx kern.ExprContext, opt *Options) (err error) {
|
||||||
|
for _, spec := range opt.builtin {
|
||||||
|
if moduleSpec, ok := spec.(string); ok {
|
||||||
|
if moduleSpec == "all" {
|
||||||
|
moduleSpec = "*"
|
||||||
|
}
|
||||||
|
_, err = expr.ImportInContextByGlobPattern(ctx, moduleSpec)
|
||||||
|
} else if moduleSpec, ok := spec.([]string); ok {
|
||||||
|
notFoundList := make([]string, 0)
|
||||||
|
for _, name := range moduleSpec {
|
||||||
|
if !expr.ImportInContext(ctx, 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 kern.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 continuation(&sb, line) {
|
||||||
|
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(opt, ctx, args); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
fmt.Fprintln(os.Stderr, "Eval Error:", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rl.SaveToHistory(source)
|
||||||
|
r := strings.NewReader(source)
|
||||||
|
compute(opt, ctx, r, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.Reset()
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
func goInteractive(opt *Options, ctx kern.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 continuation(&sb, line) {
|
||||||
|
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(opt, ctx, args); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
r := strings.NewReader(source)
|
||||||
|
compute(opt, ctx, r, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.Reset()
|
||||||
|
fmt.Print(mainPrompt)
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
func continuation(sb *strings.Builder, line string) (cont bool) {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if strings.HasSuffix(line, "\\") {
|
||||||
|
sb.WriteString(line[0 : len(line)-1])
|
||||||
|
cont = true
|
||||||
|
} else if strings.HasSuffix(line, ";") {
|
||||||
|
sb.WriteString(line)
|
||||||
|
cont = true
|
||||||
|
} else if len(line) > 0 {
|
||||||
|
if scan.StringEndsWithOperator(line) {
|
||||||
|
sb.WriteString(line)
|
||||||
|
cont = true
|
||||||
|
} else {
|
||||||
|
fullInput := sb.String() + line
|
||||||
|
if strings.Count(fullInput, "(") > strings.Count(fullInput, ")") ||
|
||||||
|
strings.Count(fullInput, "[") > strings.Count(fullInput, "]") ||
|
||||||
|
strings.Count(fullInput, "{") > strings.Count(fullInput, "}") {
|
||||||
|
sb.WriteString(line)
|
||||||
|
cont = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func goBatch(opt *Options, ctx kern.ExprContext, r io.Reader, outputEnabled bool) {
|
||||||
|
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 continuation(&sb, line) {
|
||||||
|
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(opt, ctx, args); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "Eval Error:", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
r := strings.NewReader(source)
|
||||||
|
compute(opt, ctx, r, outputEnabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.Reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func compute(opt *Options, ctx kern.ExprContext, r io.Reader, outputEnabled bool) {
|
||||||
|
scanner := scan.NewScanner(r, scan.DefaultTranslations())
|
||||||
|
parser := expr.NewParser()
|
||||||
|
|
||||||
|
if ast, err := parser.Parse(scanner); err == nil {
|
||||||
|
if opt.printPrefix {
|
||||||
|
fmt.Println(ast)
|
||||||
|
}
|
||||||
|
if opt.printTree {
|
||||||
|
printGraph()
|
||||||
|
}
|
||||||
|
|
||||||
|
if result, err := ast.Eval(ctx); err == nil {
|
||||||
|
if outputEnabled && opt.output {
|
||||||
|
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.(kern.Formatter); ok {
|
||||||
|
fmt.Println(f.ToString(opt.formOpt))
|
||||||
|
} else if kern.IsInteger(result) {
|
||||||
|
fmt.Printf(opt.baseVerb, result)
|
||||||
|
fmt.Println()
|
||||||
|
} else if kern.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 kern.ExprContext) {
|
||||||
|
const (
|
||||||
|
devParamProp = "prop"
|
||||||
|
devParamDigits = "digits"
|
||||||
|
)
|
||||||
|
|
||||||
|
aboutFunc := func(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
result = about()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctrlListFunc := func(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
vars := ctx.EnumVars(func(name string) bool {
|
||||||
|
return len(name) > 0 && name[0] == '_'
|
||||||
|
})
|
||||||
|
result = kern.ListFromStrings(vars)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctrlFunc := func(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
varName, _ := args[devParamProp].(string)
|
||||||
|
if len(args) == 1 {
|
||||||
|
result = expr.GlobalCtrlGet(ctx, varName)
|
||||||
|
} else {
|
||||||
|
result = expr.GlobalCtrlSet(ctx, varName, args[kern.ParamValue])
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
envSetFunc := func(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
var varName, value string
|
||||||
|
var ok bool
|
||||||
|
if varName, ok = args[kern.ParamName].(string); !ok {
|
||||||
|
err = kern.ErrExpectedGot(name, kern.TypeString, args[kern.ParamName])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if value, ok = args[kern.ParamValue].(string); !ok {
|
||||||
|
err = kern.ErrExpectedGot(name, kern.TypeString, args[kern.ParamValue])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = os.Setenv(varName, value); err == nil {
|
||||||
|
result = value
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
envGetFunc := func(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
var varName string
|
||||||
|
var ok bool
|
||||||
|
if varName, ok = args[kern.ParamName].(string); !ok {
|
||||||
|
err = kern.ErrExpectedGot(name, kern.TypeString, args[kern.ParamName])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if result, ok = os.LookupEnv(varName); !ok {
|
||||||
|
err = fmt.Errorf("environment variable %q does not exist", varName)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
envListFunc := func(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
env := os.Environ()
|
||||||
|
vars := make([]string, 0, len(env))
|
||||||
|
for _, e := range env {
|
||||||
|
name, _, _ := strings.Cut(e, "=")
|
||||||
|
vars = append(vars, name)
|
||||||
|
}
|
||||||
|
result = kern.ListFromStrings(vars)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
binFunc := func(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
var value, digits int64
|
||||||
|
var ok bool
|
||||||
|
var sb strings.Builder
|
||||||
|
|
||||||
|
if value, ok = args[kern.ParamValue].(int64); !ok {
|
||||||
|
err = kern.ErrExpectedGot(name, kern.TypeInt, args[kern.ParamValue])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if digits, ok = args[devParamDigits].(int64); !ok {
|
||||||
|
err = kern.ErrExpectedGot(name, kern.TypeInt, args[devParamDigits])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if digits != 64 && digits != 32 && digits != 16 && digits != 8 {
|
||||||
|
err = fmt.Errorf("%s param allows 8, 16, 32, or 64 values only", devParamDigits)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mask := uint64(0)
|
||||||
|
for i := 0; i < int(digits); i++ {
|
||||||
|
mask |= (1 << i)
|
||||||
|
}
|
||||||
|
maskedValue := uint64(value) & mask
|
||||||
|
// if maskedValue != uint64(value) {
|
||||||
|
// err = fmt.Errorf("%s param (%d) is not compatible with the value (%d) of %s param", expr.ParamValue, value, digits, devParamDigits)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
for i := int(digits) - 1; i >= 0; i-- {
|
||||||
|
if maskedValue&(1<<i) == 0 {
|
||||||
|
sb.WriteByte('0')
|
||||||
|
} else {
|
||||||
|
sb.WriteByte('1')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result = sb.String()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.RegisterFunc("about", kern.NewGolangFunctor(aboutFunc), kern.TypeString, []kern.ExprFuncParam{})
|
||||||
|
ctx.RegisterFunc("ctrlList", kern.NewGolangFunctor(ctrlListFunc), kern.TypeListOfStrings, []kern.ExprFuncParam{})
|
||||||
|
ctx.RegisterFunc("ctrl", kern.NewGolangFunctor(ctrlFunc), kern.TypeAny, []kern.ExprFuncParam{
|
||||||
|
kern.NewFuncParam(devParamProp),
|
||||||
|
kern.NewFuncParamFlag(kern.ParamValue, kern.PfOptional),
|
||||||
|
})
|
||||||
|
ctx.RegisterFunc("envSet", kern.NewGolangFunctor(envSetFunc), kern.TypeString, []kern.ExprFuncParam{
|
||||||
|
kern.NewFuncParam(kern.ParamName),
|
||||||
|
kern.NewFuncParam(kern.ParamValue),
|
||||||
|
})
|
||||||
|
ctx.RegisterFunc("envGet", kern.NewGolangFunctor(envGetFunc), kern.TypeString, []kern.ExprFuncParam{
|
||||||
|
kern.NewFuncParam(kern.ParamName),
|
||||||
|
})
|
||||||
|
ctx.RegisterFunc("envList", kern.NewGolangFunctor(envListFunc), kern.TypeListOfStrings, []kern.ExprFuncParam{})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("bin", kern.NewGolangFunctor(binFunc), kern.TypeInt, []kern.ExprFuncParam{
|
||||||
|
kern.NewFuncParam(kern.ParamValue),
|
||||||
|
kern.NewFuncParamFlagDef(devParamDigits, kern.PfOptional|kern.PfDefault, int64(8)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
setupCommands()
|
||||||
|
opt := NewOptions()
|
||||||
|
opt.loadRc()
|
||||||
|
if err := opt.parseArgs(); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := expr.NewSimpleStore()
|
||||||
|
|
||||||
|
if err := importBuiltins(ctx, opt); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
_, enableOutput := input.(*strings.Reader)
|
||||||
|
goBatch(opt, ctx, input, enableOutput)
|
||||||
|
if f, ok := input.(*os.File); ok {
|
||||||
|
f.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: why did I added these lines?
|
||||||
|
// if opt.output {
|
||||||
|
// printResult(opt, ctx.GetLast())
|
||||||
|
// }
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// match.go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func matchFilePattern(dirName string, pattern string, join bool) (fileList []string, err error) {
|
||||||
|
var entries []os.DirEntry
|
||||||
|
if entries, err = os.ReadDir(dirName); err == nil {
|
||||||
|
for _, entry := range entries {
|
||||||
|
if entry.Type().IsRegular() {
|
||||||
|
var match bool
|
||||||
|
if match, err =filepath.Match(pattern, entry.Name()); err != nil {
|
||||||
|
fileList = nil
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if match {
|
||||||
|
if fileList == nil {
|
||||||
|
fileList = make([]string, 0, 1)
|
||||||
|
}
|
||||||
|
if join {
|
||||||
|
fileList = append(fileList, path.Join(dirName, entry.Name()))
|
||||||
|
} else {
|
||||||
|
fileList = append(fileList, entry.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchPathPattern(pathPattern string) (fileList []string, err error) {
|
||||||
|
dirName := path.Dir(pathPattern)
|
||||||
|
pattern := path.Base(pathPattern)
|
||||||
|
return matchFilePattern(dirName, pattern, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func isPattern(name string) bool{
|
||||||
|
return strings.ContainsAny(name, "*?[]")
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// main.go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path"
|
||||||
|
"slices"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIsPattern(t *testing.T) {
|
||||||
|
target := "non-pattern"
|
||||||
|
if isPattern(target) {
|
||||||
|
t.Errorf("%q recognized as a pattern", target)
|
||||||
|
}
|
||||||
|
|
||||||
|
target = "pattern/*.expr"
|
||||||
|
if !isPattern(target) {
|
||||||
|
t.Errorf("%q not recognized as a pattern", target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMatchFilePattern(t *testing.T) {
|
||||||
|
target := "./go.*sum"
|
||||||
|
dirName := path.Dir(target)
|
||||||
|
pattern := path.Base(target)
|
||||||
|
if matchedFiles, err := matchFilePattern(dirName, pattern, true); err == nil {
|
||||||
|
if slices.Compare(matchedFiles, []string{"go.sum", "go.work.sum"}) != 0 {
|
||||||
|
t.Errorf("Matched file list is not correct: %v", matchedFiles)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Errorf("Got error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// options.go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.portale-stac.it/go-pkg/expr"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
"git.portale-stac.it/go-pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
printTree bool
|
||||||
|
printPrefix bool
|
||||||
|
forceInteractive bool
|
||||||
|
builtin []any
|
||||||
|
expressions []io.Reader
|
||||||
|
formOpt kern.FmtOpt
|
||||||
|
baseVerb string
|
||||||
|
base int
|
||||||
|
output bool
|
||||||
|
rcCount int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOptions() *Options {
|
||||||
|
return &Options{
|
||||||
|
expressions: make([]io.Reader, 0),
|
||||||
|
builtin: make([]any, 0),
|
||||||
|
formOpt: kern.Base10,
|
||||||
|
baseVerb: "%d",
|
||||||
|
base: 10,
|
||||||
|
output: true,
|
||||||
|
rcCount: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *Options) loadRc() {
|
||||||
|
var rcPath string
|
||||||
|
var fh *os.File
|
||||||
|
var err error
|
||||||
|
|
||||||
|
rcList := []string{
|
||||||
|
".ecli.rc",
|
||||||
|
"~/.ecli.rc",
|
||||||
|
"~/.config/expr/ecli.rc",
|
||||||
|
"~/.dev-expr.rc", // OBSOLETE, to be removed in future releases
|
||||||
|
}
|
||||||
|
for _, rcFile := range rcList {
|
||||||
|
if rcPath, err = utils.ExpandPath(rcFile); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if fh, err = os.Open(rcPath); err == nil {
|
||||||
|
opt.expressions = append(opt.expressions, fh) // rc should be the first source to be read
|
||||||
|
opt.rcCount++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func listBuiltins() {
|
||||||
|
expr.IterateBuiltinModules(func(name, description string, imported bool) bool {
|
||||||
|
if imported {
|
||||||
|
name = "*" + name
|
||||||
|
}
|
||||||
|
fmt.Printf("%20q: %s\n", name, description)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
if len(spec) > 0 && spec[len(spec)-1] != '\n' {
|
||||||
|
spec += "\n"
|
||||||
|
}
|
||||||
|
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 "-B", "--list-builtins":
|
||||||
|
listBuiltins()
|
||||||
|
os.Exit(0)
|
||||||
|
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
|
||||||
|
}
|
||||||
Executable
+79
@@ -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
|
||||||
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// util-string.go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func checkStringLiteral(literal string) (value string, err error) {
|
||||||
|
length := len(literal)
|
||||||
|
if length >= 2 {
|
||||||
|
if (literal[0] == '"' && literal[length-1] == '"') || literal[0] == '\'' && literal[length-1] == '\'' {
|
||||||
|
value = literal[1 : length-1]
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("unquoted or partially quoted string literal: `%s`", literal)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("invalid string literal: `%s`", literal)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
1.17.0
|
||||||
@@ -1,4 +1,2 @@
|
|||||||
github.com/yqylovy/goimportdot v0.0.0-20170519021755-eb181a7eeabe h1:bWYrKmmfv37uNgXTdwkLSKYiYPJ1yfWmjBnvtMyAYzk=
|
|
||||||
github.com/yqylovy/goimportdot v0.0.0-20170519021755-eb181a7eeabe/go.mod h1:alTKUpAJ/zbp17qvZwcFNwzufrb5DljMDY4mgJlIHao=
|
|
||||||
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0=
|
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0=
|
||||||
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
|
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
|
||||||
|
|||||||
+7
-6
@@ -89,16 +89,17 @@ func searchAmongPath(filename string, dirList []string) (filePath string) {
|
|||||||
if dir, err = util.ExpandPath(dir); err != nil {
|
if dir, err = util.ExpandPath(dir); err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if fullPath := path.Join(dir, filename); isFile(fullPath) {
|
fullPath := path.Join(dir, filename)
|
||||||
|
if isFile(fullPath) {
|
||||||
filePath = fullPath
|
filePath = fullPath
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
subdir := strings.TrimSuffix(filename, suffix)
|
// subdir := strings.TrimSuffix(filename, suffix)
|
||||||
if fullPath := path.Join(dir, subdir, filename); isFile(fullPath) {
|
// if fullPath := path.Join(dir, subdir, filename); isFile(fullPath) {
|
||||||
filePath = fullPath
|
// filePath = fullPath
|
||||||
break
|
// break
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -33,9 +33,9 @@ func pluginExists(name string) (exists bool) {
|
|||||||
func makePluginName(name string) (decorated string) {
|
func makePluginName(name string) (decorated string) {
|
||||||
var template string
|
var template string
|
||||||
if execName, err := os.Executable(); err != nil || !strings.Contains(execName, "debug") {
|
if execName, err := os.Executable(); err != nil || !strings.Contains(execName, "debug") {
|
||||||
template = "expr-%s-plugin%s"
|
template = "expr-%s%s"
|
||||||
} else {
|
} else {
|
||||||
template = "expr-%s-plugin%s.debug"
|
template = "expr-%s%s.debug"
|
||||||
}
|
}
|
||||||
decorated = fmt.Sprintf(template, name, SHAREDLIBRARY_EXTENSION)
|
decorated = fmt.Sprintf(template, name, SHAREDLIBRARY_EXTENSION)
|
||||||
return
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user