mirror of
https://codeberg.org/scip/tablizer.git
synced 2025-12-16 12:10:57 +01:00
get rid of lisp interpreter, -R and -F are enough, fixes #30
This commit is contained in:
46
README.md
46
README.md
@@ -8,6 +8,49 @@ Tablizer can be used to re-format tabular output of other
|
|||||||
programs. While you could do this using standard unix tools, in some
|
programs. While you could do this using standard unix tools, in some
|
||||||
cases it's a hard job.
|
cases it's a hard job.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
```default
|
||||||
|
Usage:
|
||||||
|
tablizer [regex] [file, ...] [flags]
|
||||||
|
|
||||||
|
Operational Flags:
|
||||||
|
-c, --columns string Only show the speficied columns (separated by ,)
|
||||||
|
-v, --invert-match select non-matching rows
|
||||||
|
-n, --no-numbering Disable header numbering
|
||||||
|
-N, --no-color Disable pattern highlighting
|
||||||
|
-H, --no-headers Disable headers display
|
||||||
|
-s, --separator string Custom field separator
|
||||||
|
-k, --sort-by int Sort by column (default: 1)
|
||||||
|
-z, --fuzzy Use fuzzy search [experimental]
|
||||||
|
-F, --filter field=reg Filter given field with regex, can be used multiple times
|
||||||
|
-T, --transpose-columns string Transpose the speficied columns (separated by ,)
|
||||||
|
-R, --regex-transposer /from/to/ Apply /search/replace/ regexp to fields given in -T
|
||||||
|
|
||||||
|
Output Flags (mutually exclusive):
|
||||||
|
-X, --extended Enable extended output
|
||||||
|
-M, --markdown Enable markdown table output
|
||||||
|
-O, --orgtbl Enable org-mode table output
|
||||||
|
-S, --shell Enable shell evaluable output
|
||||||
|
-Y, --yaml Enable yaml output
|
||||||
|
-C, --csv Enable CSV output
|
||||||
|
-A, --ascii Default output mode, ascii tabular
|
||||||
|
-L, --hightlight-lines Use alternating background colors for tables
|
||||||
|
|
||||||
|
Sort Mode Flags (mutually exclusive):
|
||||||
|
-a, --sort-age sort according to age (duration) string
|
||||||
|
-D, --sort-desc Sort in descending order (default: ascending)
|
||||||
|
-i, --sort-numeric sort according to string numerical value
|
||||||
|
-t, --sort-time sort according to time string
|
||||||
|
|
||||||
|
Other Flags:
|
||||||
|
--completion <shell> Generate the autocompletion script for <shell>
|
||||||
|
-f, --config <file> Configuration file (default: ~/.config/tablizer/config)
|
||||||
|
-d, --debug Enable debugging
|
||||||
|
-h, --help help for tablizer
|
||||||
|
-m, --man Display manual page
|
||||||
|
-V, --version Print program version
|
||||||
|
```
|
||||||
|
|
||||||
Let's take this output:
|
Let's take this output:
|
||||||
```
|
```
|
||||||
% kubectl get pods -o wide
|
% kubectl get pods -o wide
|
||||||
@@ -99,9 +142,6 @@ a dash. If you need to work with `/` characters, you can also use any
|
|||||||
other separator, for instance: `-R '| |-|'`.
|
other separator, for instance: `-R '| |-|'`.
|
||||||
|
|
||||||
|
|
||||||
Last but not least tablizer has support for plugins written in
|
|
||||||
lisp. This feature is expermental yet. Take a look into the manpage
|
|
||||||
for details.
|
|
||||||
|
|
||||||
## Demo
|
## Demo
|
||||||
|
|
||||||
|
|||||||
10
TODO.md
10
TODO.md
@@ -6,13 +6,3 @@
|
|||||||
|
|
||||||
- add --no-headers option
|
- add --no-headers option
|
||||||
|
|
||||||
### Lisp Plugin Infrastructure using zygo
|
|
||||||
|
|
||||||
Hooks:
|
|
||||||
|
|
||||||
| Filter | Purpose | Args | Return |
|
|
||||||
|-----------|-------------------------------------------------------------|---------------------|--------|
|
|
||||||
| filter | include or exclude lines | row as hash | bool |
|
|
||||||
| process | do calculations with data, store results in global lisp env | whole dataset | nil |
|
|
||||||
| transpose | modify a cell | headername and cell | cell |
|
|
||||||
| append | add one or more rows to the dataset (use this to add stats) | nil | rows |
|
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/glycerine/zygomys/zygo"
|
|
||||||
"github.com/gookit/color"
|
"github.com/gookit/color"
|
||||||
"github.com/hashicorp/hcl/v2/hclsimple"
|
"github.com/hashicorp/hcl/v2/hclsimple"
|
||||||
)
|
)
|
||||||
@@ -32,7 +31,6 @@ const DefaultSeparator string = `(\s\s+|\t)`
|
|||||||
const Version string = "v1.3.0"
|
const Version string = "v1.3.0"
|
||||||
const MAXPARTS = 2
|
const MAXPARTS = 2
|
||||||
|
|
||||||
var DefaultLoadPath = os.Getenv("HOME") + "/.config/tablizer/lisp"
|
|
||||||
var DefaultConfigfile = os.Getenv("HOME") + "/.config/tablizer/config"
|
var DefaultConfigfile = os.Getenv("HOME") + "/.config/tablizer/config"
|
||||||
|
|
||||||
var VERSION string // maintained by -x
|
var VERSION string // maintained by -x
|
||||||
@@ -89,13 +87,6 @@ type Config struct {
|
|||||||
|
|
||||||
NoColor bool
|
NoColor bool
|
||||||
|
|
||||||
// special case: we use the config struct to transport the lisp
|
|
||||||
// env trough the program
|
|
||||||
Lisp *zygo.Zlisp
|
|
||||||
|
|
||||||
// a path containing lisp scripts to be loaded on startup
|
|
||||||
LispLoadPath string
|
|
||||||
|
|
||||||
// config file, optional
|
// config file, optional
|
||||||
Configfile string
|
Configfile string
|
||||||
|
|
||||||
@@ -138,9 +129,6 @@ type Sortmode struct {
|
|||||||
Age bool
|
Age bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// valid lisp hooks
|
|
||||||
var ValidHooks []string
|
|
||||||
|
|
||||||
// default color schemes
|
// default color schemes
|
||||||
func (conf *Config) Colors() map[color.Level]map[string]color.Color {
|
func (conf *Config) Colors() map[color.Level]map[string]color.Color {
|
||||||
colors := map[color.Level]map[string]color.Color{
|
colors := map[color.Level]map[string]color.Color{
|
||||||
@@ -342,8 +330,6 @@ func (conf *Config) ApplyDefaults() {
|
|||||||
if conf.OutputMode == Yaml || conf.OutputMode == CSV {
|
if conf.OutputMode == Yaml || conf.OutputMode == CSV {
|
||||||
conf.NoNumbering = true
|
conf.NoNumbering = true
|
||||||
}
|
}
|
||||||
|
|
||||||
ValidHooks = []string{"filter", "process", "transpose", "append"}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conf *Config) PreparePattern(pattern string) error {
|
func (conf *Config) PreparePattern(pattern string) error {
|
||||||
|
|||||||
@@ -117,9 +117,6 @@ func Execute() {
|
|||||||
conf.DetermineColormode()
|
conf.DetermineColormode()
|
||||||
conf.ApplyDefaults()
|
conf.ApplyDefaults()
|
||||||
|
|
||||||
// setup lisp env, load plugins etc
|
|
||||||
wrapE(lib.SetupLisp(&conf))
|
|
||||||
|
|
||||||
// actual execution starts here
|
// actual execution starts here
|
||||||
wrapE(lib.ProcessFiles(&conf, args))
|
wrapE(lib.ProcessFiles(&conf, args))
|
||||||
},
|
},
|
||||||
@@ -187,10 +184,6 @@ func Execute() {
|
|||||||
rootCmd.MarkFlagsMutuallyExclusive("extended", "markdown", "orgtbl",
|
rootCmd.MarkFlagsMutuallyExclusive("extended", "markdown", "orgtbl",
|
||||||
"shell", "yaml", "csv")
|
"shell", "yaml", "csv")
|
||||||
|
|
||||||
// lisp options
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&conf.LispLoadPath, "load-path", "l", cfg.DefaultLoadPath,
|
|
||||||
"Load path for lisp plugins (expects *.zy files)")
|
|
||||||
|
|
||||||
// config file
|
// config file
|
||||||
rootCmd.PersistentFlags().StringVarP(&conf.Configfile, "config", "f", cfg.DefaultConfigfile,
|
rootCmd.PersistentFlags().StringVarP(&conf.Configfile, "config", "f", cfg.DefaultConfigfile,
|
||||||
"config file (default: ~/.config/tablizer/config)")
|
"config file (default: ~/.config/tablizer/config)")
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ SYNOPSIS
|
|||||||
Other Flags:
|
Other Flags:
|
||||||
--completion <shell> Generate the autocompletion script for <shell>
|
--completion <shell> Generate the autocompletion script for <shell>
|
||||||
-f, --config <file> Configuration file (default: ~/.config/tablizer/config)
|
-f, --config <file> Configuration file (default: ~/.config/tablizer/config)
|
||||||
-l, --load-path <path> Load path for lisp plugins (expects *.zy files)
|
|
||||||
-d, --debug Enable debugging
|
-d, --debug Enable debugging
|
||||||
-h, --help help for tablizer
|
-h, --help help for tablizer
|
||||||
-m, --man Display manual page
|
-m, --man Display manual page
|
||||||
@@ -344,60 +343,6 @@ CONFIGURATION AND COLORS
|
|||||||
Colorization can be turned off completely either by setting the
|
Colorization can be turned off completely either by setting the
|
||||||
parameter "-N" or the environment variable NO_COLOR to a true value.
|
parameter "-N" or the environment variable NO_COLOR to a true value.
|
||||||
|
|
||||||
LISP PLUGINS [experimental]
|
|
||||||
Tablizer supports plugins written in zygomys lisp. You can supply a
|
|
||||||
directory to the "-l" parameter containing *.zy files or a single .zy
|
|
||||||
file containing lisp code.
|
|
||||||
|
|
||||||
You can put as much code as you want into the file, but you need to add
|
|
||||||
one lips function to a hook at the end.
|
|
||||||
|
|
||||||
The following hooks are available:
|
|
||||||
|
|
||||||
filter
|
|
||||||
The filter hook works one a whole line of the input. Your hook
|
|
||||||
function is expected to return true or false. If you return true,
|
|
||||||
the line will be included in the output, otherwise not.
|
|
||||||
|
|
||||||
Multiple filter hook functions are supported.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
/*
|
|
||||||
Simple filter hook function. Splits the argument by whitespace,
|
|
||||||
fetches the 2nd element, converts it to an int and returns true
|
|
||||||
if it s larger than 5, false otherwise.
|
|
||||||
*/
|
|
||||||
(defn uselarge [line]
|
|
||||||
(cond (> (atoi (second (resplit line ` +`))) 5) true false))
|
|
||||||
|
|
||||||
/* Register the filter hook */
|
|
||||||
(addhook %filter %uselarge)
|
|
||||||
|
|
||||||
process
|
|
||||||
The process hook function gets a table containing the parsed input
|
|
||||||
data (see "lib/common.go:type Tabdata struct". It is expected to
|
|
||||||
return a pair containing a bool to denote if the table has been
|
|
||||||
modified, and the [modified] table. The resulting table may have
|
|
||||||
less rows than the original and cells may have changed content but
|
|
||||||
the number of columns must persist.
|
|
||||||
|
|
||||||
transpose
|
|
||||||
not yet implemented.
|
|
||||||
|
|
||||||
append
|
|
||||||
not yet implemented.
|
|
||||||
|
|
||||||
Beside the existing language features, the following additional lisp
|
|
||||||
functions are provided by tablizer:
|
|
||||||
|
|
||||||
(resplit [string, regex]) => list
|
|
||||||
(atoi [string]) => int
|
|
||||||
(matchre [string, regex]) => bool
|
|
||||||
|
|
||||||
The standard language is described here:
|
|
||||||
<https://github.com/glycerine/zygomys/wiki/Language>.
|
|
||||||
|
|
||||||
BUGS
|
BUGS
|
||||||
In order to report a bug, unexpected behavior, feature requests or to
|
In order to report a bug, unexpected behavior, feature requests or to
|
||||||
submit a patch, please open an issue on github:
|
submit a patch, please open an issue on github:
|
||||||
@@ -472,7 +417,6 @@ Sort Mode Flags (mutually exclusive):
|
|||||||
Other Flags:
|
Other Flags:
|
||||||
--completion <shell> Generate the autocompletion script for <shell>
|
--completion <shell> Generate the autocompletion script for <shell>
|
||||||
-f, --config <file> Configuration file (default: ~/.config/tablizer/config)
|
-f, --config <file> Configuration file (default: ~/.config/tablizer/config)
|
||||||
-l, --load-path <path> Load path for lisp plugins (expects *.zy files)
|
|
||||||
-d, --debug Enable debugging
|
-d, --debug Enable debugging
|
||||||
-h, --help help for tablizer
|
-h, --help help for tablizer
|
||||||
-m, --man Display manual page
|
-m, --man Display manual page
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ package lib
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -144,18 +143,6 @@ func FilterByPattern(conf cfg.Config, input io.Reader) (io.Reader, error) {
|
|||||||
// so we ignore all lines, which DO match.
|
// so we ignore all lines, which DO match.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply user defined lisp filters, if any
|
|
||||||
accept, err := RunFilterHooks(conf, line)
|
|
||||||
if err != nil {
|
|
||||||
return input, fmt.Errorf("failed to apply filter hook: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !accept {
|
|
||||||
// IF there are filter hook[s] and IF one of them
|
|
||||||
// returns false on the current line, reject it
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lines = append(lines, line)
|
lines = append(lines, line)
|
||||||
|
|||||||
319
lib/lisp.go
319
lib/lisp.go
@@ -1,319 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright © 2023 Thomas von Dein
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package lib
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/glycerine/zygomys/zygo"
|
|
||||||
"github.com/tlinden/tablizer/cfg"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
needs to be global because we can't feed an cfg object to AddHook()
|
|
||||||
which is being called from user lisp code
|
|
||||||
*/
|
|
||||||
var Hooks map[string][]*zygo.SexpSymbol
|
|
||||||
|
|
||||||
/*
|
|
||||||
AddHook() (called addhook from lisp code) can be used by the user to
|
|
||||||
add a function to one of the available hooks provided by tablizer.
|
|
||||||
*/
|
|
||||||
func AddHook(env *zygo.Zlisp, name string, args []zygo.Sexp) (zygo.Sexp, error) {
|
|
||||||
var hookname string
|
|
||||||
|
|
||||||
if len(args) < 2 {
|
|
||||||
return zygo.SexpNull, errors.New("argument of %add-hook should be: %hook-name %your-function")
|
|
||||||
}
|
|
||||||
|
|
||||||
switch sexptype := args[0].(type) {
|
|
||||||
case *zygo.SexpSymbol:
|
|
||||||
if !HookExists(sexptype.Name()) {
|
|
||||||
return zygo.SexpNull, errors.New("Unknown hook " + sexptype.Name())
|
|
||||||
}
|
|
||||||
|
|
||||||
hookname = sexptype.Name()
|
|
||||||
|
|
||||||
default:
|
|
||||||
return zygo.SexpNull, errors.New("hook name must be a symbol ")
|
|
||||||
}
|
|
||||||
|
|
||||||
switch sexptype := args[1].(type) {
|
|
||||||
case *zygo.SexpSymbol:
|
|
||||||
_, exists := Hooks[hookname]
|
|
||||||
if !exists {
|
|
||||||
Hooks[hookname] = []*zygo.SexpSymbol{sexptype}
|
|
||||||
} else {
|
|
||||||
Hooks[hookname] = append(Hooks[hookname], sexptype)
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
return zygo.SexpNull, errors.New("hook function must be a symbol ")
|
|
||||||
}
|
|
||||||
|
|
||||||
return zygo.SexpNull, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Check if a hook exists
|
|
||||||
*/
|
|
||||||
func HookExists(key string) bool {
|
|
||||||
for _, hook := range cfg.ValidHooks {
|
|
||||||
if hook == key {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Basic sanity checks and load lisp file
|
|
||||||
*/
|
|
||||||
func LoadAndEvalFile(env *zygo.Zlisp, path string) error {
|
|
||||||
if strings.HasSuffix(path, `.zy`) {
|
|
||||||
code, err := os.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to read lisp file %s: %w", path, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: check what res (_ here) could be and mean
|
|
||||||
_, err = env.EvalString(string(code))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(env.GetStackTrace(err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Setup lisp interpreter environment
|
|
||||||
*/
|
|
||||||
func SetupLisp(conf *cfg.Config) error {
|
|
||||||
// iterate over load-path and evaluate all *.zy files there, if any
|
|
||||||
// we ignore if load-path does not exist, which is the default anyway
|
|
||||||
path, err := os.Stat(conf.LispLoadPath)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
// ignore non-existent files
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("failed to stat path: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// init global hooks
|
|
||||||
Hooks = make(map[string][]*zygo.SexpSymbol)
|
|
||||||
|
|
||||||
// init sandbox
|
|
||||||
env := zygo.NewZlispSandbox()
|
|
||||||
env.AddFunction("addhook", AddHook)
|
|
||||||
|
|
||||||
if !path.IsDir() {
|
|
||||||
// load single lisp file
|
|
||||||
err = LoadAndEvalFile(env, conf.LispLoadPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// load all lisp file in load dir
|
|
||||||
dir, err := os.ReadDir(conf.LispLoadPath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to read lisp dir %s: %w",
|
|
||||||
conf.LispLoadPath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, entry := range dir {
|
|
||||||
if !entry.IsDir() {
|
|
||||||
err := LoadAndEvalFile(env, conf.LispLoadPath+"/"+entry.Name())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RegisterLib(env)
|
|
||||||
|
|
||||||
conf.Lisp = env
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Execute every user lisp function registered as filter hook.
|
|
||||||
|
|
||||||
Each function is given the current line as argument and is expected to
|
|
||||||
return a boolean. True indicates to keep the line, false to skip
|
|
||||||
it.
|
|
||||||
|
|
||||||
If there are multiple such functions registered, then the first one
|
|
||||||
returning false wins, that is if each function returns true the line
|
|
||||||
will be kept, if at least one of them returns false, it will be
|
|
||||||
skipped.
|
|
||||||
*/
|
|
||||||
func RunFilterHooks(conf cfg.Config, line string) (bool, error) {
|
|
||||||
for _, hook := range Hooks["filter"] {
|
|
||||||
var result bool
|
|
||||||
|
|
||||||
conf.Lisp.Clear()
|
|
||||||
|
|
||||||
res, err := conf.Lisp.EvalString(fmt.Sprintf("(%s `%s`)", hook.Name(), line))
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("failed to evaluate hook loader: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch sexptype := res.(type) {
|
|
||||||
case *zygo.SexpBool:
|
|
||||||
result = sexptype.Val
|
|
||||||
default:
|
|
||||||
return false, fmt.Errorf("filter hook shall return bool")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !result {
|
|
||||||
// the first hook which returns false leads to complete false
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if no hook returned false, we succeed and accept the given line
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
These hooks get the data (Tabdata) readily processed by tablizer as
|
|
||||||
argument. They are expected to return a SexpPair containing a boolean
|
|
||||||
denoting if the data has been modified and the actual modified
|
|
||||||
data. Columns must be the same, rows may differ. Cells may also have
|
|
||||||
been modified.
|
|
||||||
|
|
||||||
Replaces the internal data structure Tabdata with the user supplied
|
|
||||||
version.
|
|
||||||
|
|
||||||
Only one process hook function is supported.
|
|
||||||
|
|
||||||
The somewhat complicated code is being caused by the fact, that we
|
|
||||||
need to convert our internal structure to a lisp variable and vice
|
|
||||||
versa afterwards.
|
|
||||||
*/
|
|
||||||
func RunProcessHooks(conf cfg.Config, data *Tabdata) (*Tabdata, bool, error) {
|
|
||||||
var userdata Tabdata
|
|
||||||
|
|
||||||
lisplist := []zygo.Sexp{}
|
|
||||||
|
|
||||||
if len(Hooks["process"]) == 0 {
|
|
||||||
return data, false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(Hooks["process"]) > 1 {
|
|
||||||
fmt.Println("Warning: only one process hook is allowed!")
|
|
||||||
}
|
|
||||||
|
|
||||||
// there are hook[s] installed, convert the go data structure 'data to lisp
|
|
||||||
for _, row := range data.entries {
|
|
||||||
var entry zygo.SexpHash
|
|
||||||
|
|
||||||
for idx, cell := range row {
|
|
||||||
err := entry.HashSet(&zygo.SexpStr{S: data.headers[idx]}, &zygo.SexpStr{S: cell})
|
|
||||||
if err != nil {
|
|
||||||
return data, false, fmt.Errorf("failed to convert to lisp data: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lisplist = append(lisplist, &entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
// we need to add it to the env so that the function can use the struct directly
|
|
||||||
conf.Lisp.AddGlobal("data", &zygo.SexpArray{Val: lisplist, Env: conf.Lisp})
|
|
||||||
|
|
||||||
// execute the actual hook
|
|
||||||
hook := Hooks["process"][0]
|
|
||||||
|
|
||||||
conf.Lisp.Clear()
|
|
||||||
|
|
||||||
var result bool
|
|
||||||
|
|
||||||
res, err := conf.Lisp.EvalString(fmt.Sprintf("(%s data)", hook.Name()))
|
|
||||||
if err != nil {
|
|
||||||
return data, false, fmt.Errorf("failed to eval lisp loader: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// we expect (bool, array(hash)) as return from the function
|
|
||||||
switch sexptype := res.(type) {
|
|
||||||
case *zygo.SexpPair:
|
|
||||||
switch th := sexptype.Head.(type) {
|
|
||||||
case *zygo.SexpBool:
|
|
||||||
result = th.Val
|
|
||||||
default:
|
|
||||||
return data, false, errors.New("expect (bool, array(hash)) as return value")
|
|
||||||
}
|
|
||||||
|
|
||||||
switch sexptailtype := sexptype.Tail.(type) {
|
|
||||||
case *zygo.SexpArray:
|
|
||||||
lisplist = sexptailtype.Val
|
|
||||||
default:
|
|
||||||
return data, false, errors.New("expect (bool, array(hash)) as return value ")
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return data, false, errors.New("process hook shall return array of hashes ")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !result {
|
|
||||||
// no further processing required
|
|
||||||
return data, result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// finally convert lispdata back to Tabdata
|
|
||||||
for _, item := range lisplist {
|
|
||||||
row := []string{}
|
|
||||||
|
|
||||||
switch hash := item.(type) {
|
|
||||||
case *zygo.SexpHash:
|
|
||||||
for _, header := range data.headers {
|
|
||||||
entry, err := hash.HashGetDefault(
|
|
||||||
conf.Lisp,
|
|
||||||
&zygo.SexpStr{S: header},
|
|
||||||
&zygo.SexpStr{S: ""})
|
|
||||||
if err != nil {
|
|
||||||
return data, false, fmt.Errorf("failed to get lisp hash entry: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch sexptype := entry.(type) {
|
|
||||||
case *zygo.SexpStr:
|
|
||||||
row = append(row, sexptype.S)
|
|
||||||
default:
|
|
||||||
return data, false, errors.New("hash values should be string ")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return data, false, errors.New("returned array should contain hashes ")
|
|
||||||
}
|
|
||||||
|
|
||||||
userdata.entries = append(userdata.entries, row)
|
|
||||||
}
|
|
||||||
|
|
||||||
userdata.headers = data.headers
|
|
||||||
|
|
||||||
return &userdata, result, nil
|
|
||||||
}
|
|
||||||
110
lib/lisplib.go
110
lib/lisplib.go
@@ -1,110 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright © 2023 Thomas von Dein
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package lib
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/glycerine/zygomys/zygo"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Splice2SexpList(list []string) zygo.Sexp {
|
|
||||||
slist := []zygo.Sexp{}
|
|
||||||
|
|
||||||
for _, item := range list {
|
|
||||||
slist = append(slist, &zygo.SexpStr{S: item})
|
|
||||||
}
|
|
||||||
|
|
||||||
return zygo.MakeList(slist)
|
|
||||||
}
|
|
||||||
|
|
||||||
func StringReSplit(env *zygo.Zlisp, name string, args []zygo.Sexp) (zygo.Sexp, error) {
|
|
||||||
if len(args) < 2 {
|
|
||||||
return zygo.SexpNull, errors.New("expecting 2 arguments: <string>, <regex>")
|
|
||||||
}
|
|
||||||
|
|
||||||
var separator, input string
|
|
||||||
|
|
||||||
switch t := args[0].(type) {
|
|
||||||
case *zygo.SexpStr:
|
|
||||||
input = t.S
|
|
||||||
default:
|
|
||||||
return zygo.SexpNull, errors.New("first argument must be a string")
|
|
||||||
}
|
|
||||||
|
|
||||||
switch t := args[1].(type) {
|
|
||||||
case *zygo.SexpStr:
|
|
||||||
separator = t.S
|
|
||||||
default:
|
|
||||||
return zygo.SexpNull, errors.New("second argument must be a string")
|
|
||||||
}
|
|
||||||
|
|
||||||
sep := regexp.MustCompile(separator)
|
|
||||||
|
|
||||||
return Splice2SexpList(sep.Split(input, -1)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func String2Int(env *zygo.Zlisp, name string, args []zygo.Sexp) (zygo.Sexp, error) {
|
|
||||||
var number int
|
|
||||||
|
|
||||||
switch t := args[0].(type) {
|
|
||||||
case *zygo.SexpStr:
|
|
||||||
num, err := strconv.Atoi(t.S)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return zygo.SexpNull, fmt.Errorf("failed to convert string to number: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
number = num
|
|
||||||
|
|
||||||
default:
|
|
||||||
return zygo.SexpNull, errors.New("argument must be a string")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &zygo.SexpInt{Val: int64(number)}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func RegMatch(env *zygo.Zlisp, name string, args []zygo.Sexp) (zygo.Sexp, error) {
|
|
||||||
if len(args) != 2 {
|
|
||||||
return zygo.SexpNull, fmt.Errorf("argument must be <regexp>, <string>")
|
|
||||||
}
|
|
||||||
|
|
||||||
arguments := []string{}
|
|
||||||
|
|
||||||
for _, arg := range args {
|
|
||||||
switch t := arg.(type) {
|
|
||||||
case *zygo.SexpStr:
|
|
||||||
arguments = append(arguments, t.S)
|
|
||||||
default:
|
|
||||||
return zygo.SexpNull, errors.New("argument must be a string")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
reg := regexp.MustCompile(arguments[0])
|
|
||||||
|
|
||||||
return &zygo.SexpBool{Val: reg.MatchString(arguments[1])}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func RegisterLib(env *zygo.Zlisp) {
|
|
||||||
env.AddFunction("resplit", StringReSplit)
|
|
||||||
env.AddFunction("atoi", String2Int)
|
|
||||||
env.AddFunction("matchre", RegMatch)
|
|
||||||
}
|
|
||||||
@@ -145,18 +145,6 @@ func parseTabular(conf cfg.Config, input io.Reader) (Tabdata, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply user defined lisp filters, if any
|
|
||||||
accept, err := RunFilterHooks(conf, line)
|
|
||||||
if err != nil {
|
|
||||||
return data, fmt.Errorf("failed to apply filter hook: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !accept {
|
|
||||||
// IF there are filter hook[s] and IF one of them
|
|
||||||
// returns false on the current line, reject it
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
idx := 0 // we cannot use the header index, because we could exclude columns
|
idx := 0 // we cannot use the header index, because we could exclude columns
|
||||||
values := []string{}
|
values := []string{}
|
||||||
for _, part := range parts {
|
for _, part := range parts {
|
||||||
@@ -214,17 +202,6 @@ func PostProcess(conf cfg.Config, data *Tabdata) (*Tabdata, bool, error) {
|
|||||||
modified = true
|
modified = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply user defined lisp process hooks, if any
|
|
||||||
userdata, changed, err := RunProcessHooks(conf, data)
|
|
||||||
if err != nil {
|
|
||||||
return data, false, fmt.Errorf("failed to apply filter hook: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if changed {
|
|
||||||
data = userdata
|
|
||||||
modified = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if conf.Debug {
|
if conf.Debug {
|
||||||
repr.Print(data)
|
repr.Print(data)
|
||||||
}
|
}
|
||||||
|
|||||||
58
tablizer.1
58
tablizer.1
@@ -178,7 +178,6 @@ tablizer \- Manipulate tabular output of other programs
|
|||||||
\& Other Flags:
|
\& Other Flags:
|
||||||
\& \-\-completion <shell> Generate the autocompletion script for <shell>
|
\& \-\-completion <shell> Generate the autocompletion script for <shell>
|
||||||
\& \-f, \-\-config <file> Configuration file (default: ~/.config/tablizer/config)
|
\& \-f, \-\-config <file> Configuration file (default: ~/.config/tablizer/config)
|
||||||
\& \-l, \-\-load\-path <path> Load path for lisp plugins (expects *.zy files)
|
|
||||||
\& \-d, \-\-debug Enable debugging
|
\& \-d, \-\-debug Enable debugging
|
||||||
\& \-h, \-\-help help for tablizer
|
\& \-h, \-\-help help for tablizer
|
||||||
\& \-m, \-\-man Display manual page
|
\& \-m, \-\-man Display manual page
|
||||||
@@ -541,63 +540,6 @@ the \f(CW\*(C`\-L\*(C'\fR parameter).
|
|||||||
.PP
|
.PP
|
||||||
Colorization can be turned off completely either by setting the
|
Colorization can be turned off completely either by setting the
|
||||||
parameter \f(CW\*(C`\-N\*(C'\fR or the environment variable \fB\s-1NO_COLOR\s0\fR to a true value.
|
parameter \f(CW\*(C`\-N\*(C'\fR or the environment variable \fB\s-1NO_COLOR\s0\fR to a true value.
|
||||||
.SH "LISP PLUGINS [experimental]"
|
|
||||||
.IX Header "LISP PLUGINS [experimental]"
|
|
||||||
Tablizer supports plugins written in zygomys lisp. You can supply a
|
|
||||||
directory to the \f(CW\*(C`\-l\*(C'\fR parameter containing \fB*.zy\fR files or a single
|
|
||||||
\&.zy file containing lisp code.
|
|
||||||
.PP
|
|
||||||
You can put as much code as you want into the file, but you need to
|
|
||||||
add one lips function to a hook at the end.
|
|
||||||
.PP
|
|
||||||
The following hooks are available:
|
|
||||||
.IP "\fBfilter\fR" 4
|
|
||||||
.IX Item "filter"
|
|
||||||
The filter hook works one a whole line of the input. Your hook
|
|
||||||
function is expected to return true or false. If you return true, the
|
|
||||||
line will be included in the output, otherwise not.
|
|
||||||
.Sp
|
|
||||||
Multiple filter hook functions are supported.
|
|
||||||
.Sp
|
|
||||||
Example:
|
|
||||||
.Sp
|
|
||||||
.Vb 7
|
|
||||||
\& /*
|
|
||||||
\& Simple filter hook function. Splits the argument by whitespace,
|
|
||||||
\& fetches the 2nd element, converts it to an int and returns true
|
|
||||||
\& if it s larger than 5, false otherwise.
|
|
||||||
\& */
|
|
||||||
\& (defn uselarge [line]
|
|
||||||
\& (cond (> (atoi (second (resplit line \` +\`))) 5) true false))
|
|
||||||
\&
|
|
||||||
\& /* Register the filter hook */
|
|
||||||
\& (addhook %filter %uselarge)
|
|
||||||
.Ve
|
|
||||||
.IP "\fBprocess\fR" 4
|
|
||||||
.IX Item "process"
|
|
||||||
The process hook function gets a table containing the parsed input
|
|
||||||
data (see \f(CW\*(C`lib/common.go:type Tabdata struct\*(C'\fR. It is expected to
|
|
||||||
return a pair containing a bool to denote if the table has been
|
|
||||||
modified, and the [modified] table. The resulting table may have less
|
|
||||||
rows than the original and cells may have changed content but the
|
|
||||||
number of columns must persist.
|
|
||||||
.IP "\fBtranspose\fR" 4
|
|
||||||
.IX Item "transpose"
|
|
||||||
not yet implemented.
|
|
||||||
.IP "\fBappend\fR" 4
|
|
||||||
.IX Item "append"
|
|
||||||
not yet implemented.
|
|
||||||
.PP
|
|
||||||
Beside the existing language features, the following additional lisp
|
|
||||||
functions are provided by tablizer:
|
|
||||||
.PP
|
|
||||||
.Vb 3
|
|
||||||
\& (resplit [string, regex]) => list
|
|
||||||
\& (atoi [string]) => int
|
|
||||||
\& (matchre [string, regex]) => bool
|
|
||||||
.Ve
|
|
||||||
.PP
|
|
||||||
The standard language is described here: <https://github.com/glycerine/zygomys/wiki/Language>.
|
|
||||||
.SH "BUGS"
|
.SH "BUGS"
|
||||||
.IX Header "BUGS"
|
.IX Header "BUGS"
|
||||||
In order to report a bug, unexpected behavior, feature requests
|
In order to report a bug, unexpected behavior, feature requests
|
||||||
|
|||||||
61
tablizer.pod
61
tablizer.pod
@@ -39,7 +39,6 @@ tablizer - Manipulate tabular output of other programs
|
|||||||
Other Flags:
|
Other Flags:
|
||||||
--completion <shell> Generate the autocompletion script for <shell>
|
--completion <shell> Generate the autocompletion script for <shell>
|
||||||
-f, --config <file> Configuration file (default: ~/.config/tablizer/config)
|
-f, --config <file> Configuration file (default: ~/.config/tablizer/config)
|
||||||
-l, --load-path <path> Load path for lisp plugins (expects *.zy files)
|
|
||||||
-d, --debug Enable debugging
|
-d, --debug Enable debugging
|
||||||
-h, --help help for tablizer
|
-h, --help help for tablizer
|
||||||
-m, --man Display manual page
|
-m, --man Display manual page
|
||||||
@@ -384,67 +383,7 @@ the C<-L> parameter).
|
|||||||
Colorization can be turned off completely either by setting the
|
Colorization can be turned off completely either by setting the
|
||||||
parameter C<-N> or the environment variable B<NO_COLOR> to a true value.
|
parameter C<-N> or the environment variable B<NO_COLOR> to a true value.
|
||||||
|
|
||||||
=head1 LISP PLUGINS [experimental]
|
|
||||||
|
|
||||||
Tablizer supports plugins written in zygomys lisp. You can supply a
|
|
||||||
directory to the C<-l> parameter containing B<*.zy> files or a single
|
|
||||||
.zy file containing lisp code.
|
|
||||||
|
|
||||||
You can put as much code as you want into the file, but you need to
|
|
||||||
add one lips function to a hook at the end.
|
|
||||||
|
|
||||||
The following hooks are available:
|
|
||||||
|
|
||||||
=over
|
|
||||||
|
|
||||||
=item B<filter>
|
|
||||||
|
|
||||||
The filter hook works one a whole line of the input. Your hook
|
|
||||||
function is expected to return true or false. If you return true, the
|
|
||||||
line will be included in the output, otherwise not.
|
|
||||||
|
|
||||||
Multiple filter hook functions are supported.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
/*
|
|
||||||
Simple filter hook function. Splits the argument by whitespace,
|
|
||||||
fetches the 2nd element, converts it to an int and returns true
|
|
||||||
if it s larger than 5, false otherwise.
|
|
||||||
*/
|
|
||||||
(defn uselarge [line]
|
|
||||||
(cond (> (atoi (second (resplit line ` +`))) 5) true false))
|
|
||||||
|
|
||||||
/* Register the filter hook */
|
|
||||||
(addhook %filter %uselarge)
|
|
||||||
|
|
||||||
=item B<process>
|
|
||||||
|
|
||||||
The process hook function gets a table containing the parsed input
|
|
||||||
data (see C<lib/common.go:type Tabdata struct>. It is expected to
|
|
||||||
return a pair containing a bool to denote if the table has been
|
|
||||||
modified, and the [modified] table. The resulting table may have less
|
|
||||||
rows than the original and cells may have changed content but the
|
|
||||||
number of columns must persist.
|
|
||||||
|
|
||||||
=item B<transpose>
|
|
||||||
|
|
||||||
not yet implemented.
|
|
||||||
|
|
||||||
=item B<append>
|
|
||||||
|
|
||||||
not yet implemented.
|
|
||||||
|
|
||||||
=back
|
|
||||||
|
|
||||||
Beside the existing language features, the following additional lisp
|
|
||||||
functions are provided by tablizer:
|
|
||||||
|
|
||||||
(resplit [string, regex]) => list
|
|
||||||
(atoi [string]) => int
|
|
||||||
(matchre [string, regex]) => bool
|
|
||||||
|
|
||||||
The standard language is described here: L<https://github.com/glycerine/zygomys/wiki/Language>.
|
|
||||||
|
|
||||||
=head1 BUGS
|
=head1 BUGS
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user