Compare commits
5 Commits
38316cec0b
...
all-in-one
| Author | SHA1 | Date | |
|---|---|---|---|
| 6a6c897268 | |||
| 9eb3e2d072 | |||
| b86ce6fe62 | |||
| 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
|
||||||
+19
-16
@@ -1,6 +1,7 @@
|
|||||||
= Expr
|
= Expr
|
||||||
Expressions calculator
|
Expressions calculator
|
||||||
:authors: Celestino Amoroso
|
:authors: Celestino Amoroso
|
||||||
|
:email: celestino.amoroso@gmail.com
|
||||||
:docinfo: shared
|
:docinfo: shared
|
||||||
:encoding: utf-8
|
:encoding: utf-8
|
||||||
:toc: right
|
:toc: right
|
||||||
@@ -16,10 +17,11 @@ Expressions calculator
|
|||||||
:sectlinks:
|
:sectlinks:
|
||||||
:sectanchors:
|
:sectanchors:
|
||||||
:source-highlighter: rouge
|
:source-highlighter: rouge
|
||||||
// :rouge-style: ThankfulEyes
|
// :rouge-style: gruvbox
|
||||||
:rouge-style: gruvbox
|
:rouge-style: manni
|
||||||
// :rouge-style: colorful
|
:stylesdir: /home/share/s3-howto/styles
|
||||||
//:rouge-style: monokay
|
:stylesheet: adoc-colony.css
|
||||||
|
|
||||||
// Workaround to manage double-column in back-tick quotes
|
// Workaround to manage double-column in back-tick quotes
|
||||||
:2c: ::
|
:2c: ::
|
||||||
// Workaround to manage double-plus in back-tick quotes
|
// Workaround to manage double-plus in back-tick quotes
|
||||||
@@ -74,7 +76,7 @@ Imported functions are registered in the _global context_. When an expression fi
|
|||||||
===== Inspecting contexts
|
===== Inspecting contexts
|
||||||
_Expr_ provides the operator [blue]_$$_ that returns the current context. This can be used to inspect the content of the context, for example to check the value of a variable or to see which functions are currently linked to the context. This operator is primarily intended for debugging purposes.
|
_Expr_ provides the operator [blue]_$$_ that returns the current context. This can be used to inspect the content of the context, for example to check the value of a variable or to see which functions are currently linked to the context. This operator is primarily intended for debugging purposes.
|
||||||
|
|
||||||
An interactive tool could like `dev-expr` (see <<_dev-expr_test_tool>>) can be used to inspect contexts interactively.
|
An interactive tool could like `ecli` (see <<_ecli_test_tool>>) can be used to inspect contexts interactively.
|
||||||
|
|
||||||
|
|
||||||
.Example: inspecting contexts
|
.Example: inspecting contexts
|
||||||
@@ -123,24 +125,25 @@ In order to inspect the global context issue the [blue]`$$ global` operation.
|
|||||||
[green]`]`
|
[green]`]`
|
||||||
////
|
////
|
||||||
|
|
||||||
=== `dev-expr` test tool
|
[[sec_ecli]]
|
||||||
Before we begin to describe the syntax of _Expr_, it is worth introducing _dev-expr_ because it will be used to show many examples of expressions.
|
=== `ecli` Expression Calculator Interactive Tool
|
||||||
|
Before we begin to describe the syntax of _Expr_, it is worth introducing _ecli_, former _dev-expr_, because it will be used to show many examples of expressions.
|
||||||
|
|
||||||
`dev-expr` is a simple program that can be used to evaluate expressions interactively. As its name suggests, it was created for testing purpose. In fact, in additon to the automatic verification test suite based on the Go test framework, `dev-expr` provided an important aid for quickly testing of new features during their development.
|
`ecli` is a simple program that can be used to evaluate expressions interactively. As its name suggests, it was created for testing purpose. In fact, in additon to the automatic verification test suite based on the Go test framework, `ecli` provided an important aid for quickly testing of new features during their development.
|
||||||
|
|
||||||
`dev-expr` can work as a _REPL_, _**R**ead-**E**xecute-**P**rint-**L**oop_, or it can process expression acquired from files or standard input.
|
`ecli` can work as a _REPL_, _**R**ead-**E**xecute-**P**rint-**L**oop_, or it can process expression acquired from files or standard input.
|
||||||
|
|
||||||
The program can be downloaded from https://git.portale-stac.it/go-pkg/-/packages/generic/dev-expr/[dev-expr].
|
The program can be downloaded from https://git.portale-stac.it/go-pkg/-/packages/generic/ecli/[ecli].
|
||||||
|
|
||||||
Here are some examples of execution.
|
Here are some examples of execution.
|
||||||
|
|
||||||
.Run `dev-expr` in REPL mode and ask for help
|
.Run `ecli` in REPL mode and ask for help
|
||||||
[source,shell]
|
[source,shell]
|
||||||
----
|
----
|
||||||
# Type 'exit' or Ctrl+D to quit the program.
|
# Type 'exit' or Ctrl+D to quit the program.
|
||||||
|
|
||||||
[user]$ ./dev-expr
|
[user]$ ./ecli
|
||||||
dev-expr -- Expressions calculator v1.12.0(build 1),2024/09/14 (celestino.amoroso@portale-stac.it)
|
ecli -- Expressions calculator v1.12.0(build 1),2024/09/14 (celestino.amoroso@portale-stac.it)
|
||||||
Based on the Expr package v0.26.0
|
Based on the Expr package v0.26.0
|
||||||
Type help to get the list of available commands
|
Type help to get the list of available commands
|
||||||
See also https://git.portale-stac.it/go-pkg/expr/src/branch/main/README.adoc
|
See also https://git.portale-stac.it/go-pkg/expr/src/branch/main/README.adoc
|
||||||
@@ -176,8 +179,8 @@ dev-expr -- Expressions calculator v1.12.0(build 1),2024/09/14 (celestino.amoros
|
|||||||
.REPL examples
|
.REPL examples
|
||||||
[source,shell]
|
[source,shell]
|
||||||
----
|
----
|
||||||
[user]$ ./dev-expr
|
[user]$ ./ecli
|
||||||
dev-expr -- Expressions calculator v1.10.0(build 14),2024/06/17 (celestino.amoroso@portale-stac.it)
|
ecli -- Expressions calculator v1.10.0(build 14),2024/06/17 (celestino.amoroso@portale-stac.it)
|
||||||
Based on the Expr package v0.19.0
|
Based on the Expr package v0.19.0
|
||||||
Type help to get the list of available commands
|
Type help to get the list of available commands
|
||||||
See also https://git.portale-stac.it/go-pkg/expr/src/branch/main/README.adoc
|
See also https://git.portale-stac.it/go-pkg/expr/src/branch/main/README.adoc
|
||||||
@@ -476,7 +479,7 @@ Currently, boolean operations are evaluated using _short cut evaluation_. This m
|
|||||||
<1> This multi-expression returns _1_ because in the first expression the left value of [blue]`or` is _true_ and, as a conseguence, its right value is not computed. Therefore the _a_ variable only receives the integer _1_.
|
<1> This multi-expression returns _1_ because in the first expression the left value of [blue]`or` is _true_ and, as a conseguence, its right value is not computed. Therefore the _a_ variable only receives the integer _1_.
|
||||||
|
|
||||||
|
|
||||||
TIP: `dev-expr` provides the _ctrl()_ function that allows to change this behaviour.
|
TIP: `ecli` provides the _ctrl()_ function that allows to change this behaviour.
|
||||||
====
|
====
|
||||||
|
|
||||||
=== Lists
|
=== Lists
|
||||||
|
|||||||
@@ -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