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
|
||||
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:
|
||||
```
|
||||
% 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 '| |-|'`.
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
10
TODO.md
10
TODO.md
@@ -6,13 +6,3 @@
|
||||
|
||||
- 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"
|
||||
"strings"
|
||||
|
||||
"github.com/glycerine/zygomys/zygo"
|
||||
"github.com/gookit/color"
|
||||
"github.com/hashicorp/hcl/v2/hclsimple"
|
||||
)
|
||||
@@ -32,7 +31,6 @@ const DefaultSeparator string = `(\s\s+|\t)`
|
||||
const Version string = "v1.3.0"
|
||||
const MAXPARTS = 2
|
||||
|
||||
var DefaultLoadPath = os.Getenv("HOME") + "/.config/tablizer/lisp"
|
||||
var DefaultConfigfile = os.Getenv("HOME") + "/.config/tablizer/config"
|
||||
|
||||
var VERSION string // maintained by -x
|
||||
@@ -89,13 +87,6 @@ type Config struct {
|
||||
|
||||
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
|
||||
Configfile string
|
||||
|
||||
@@ -138,9 +129,6 @@ type Sortmode struct {
|
||||
Age bool
|
||||
}
|
||||
|
||||
// valid lisp hooks
|
||||
var ValidHooks []string
|
||||
|
||||
// default color schemes
|
||||
func (conf *Config) 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 {
|
||||
conf.NoNumbering = true
|
||||
}
|
||||
|
||||
ValidHooks = []string{"filter", "process", "transpose", "append"}
|
||||
}
|
||||
|
||||
func (conf *Config) PreparePattern(pattern string) error {
|
||||
|
||||
@@ -117,9 +117,6 @@ func Execute() {
|
||||
conf.DetermineColormode()
|
||||
conf.ApplyDefaults()
|
||||
|
||||
// setup lisp env, load plugins etc
|
||||
wrapE(lib.SetupLisp(&conf))
|
||||
|
||||
// actual execution starts here
|
||||
wrapE(lib.ProcessFiles(&conf, args))
|
||||
},
|
||||
@@ -187,10 +184,6 @@ func Execute() {
|
||||
rootCmd.MarkFlagsMutuallyExclusive("extended", "markdown", "orgtbl",
|
||||
"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
|
||||
rootCmd.PersistentFlags().StringVarP(&conf.Configfile, "config", "f", cfg.DefaultConfigfile,
|
||||
"config file (default: ~/.config/tablizer/config)")
|
||||
|
||||
@@ -40,7 +40,6 @@ SYNOPSIS
|
||||
Other Flags:
|
||||
--completion <shell> Generate the autocompletion script for <shell>
|
||||
-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
|
||||
-h, --help help for tablizer
|
||||
-m, --man Display manual page
|
||||
@@ -344,60 +343,6 @@ CONFIGURATION AND COLORS
|
||||
Colorization can be turned off completely either by setting the
|
||||
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
|
||||
In order to report a bug, unexpected behavior, feature requests or to
|
||||
submit a patch, please open an issue on github:
|
||||
@@ -472,7 +417,6 @@ Sort Mode Flags (mutually exclusive):
|
||||
Other Flags:
|
||||
--completion <shell> Generate the autocompletion script for <shell>
|
||||
-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
|
||||
-h, --help help for tablizer
|
||||
-m, --man Display manual page
|
||||
|
||||
@@ -19,7 +19,6 @@ package lib
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
@@ -144,18 +143,6 @@ func FilterByPattern(conf cfg.Config, input io.Reader) (io.Reader, error) {
|
||||
// so we ignore all lines, which DO match.
|
||||
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)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
values := []string{}
|
||||
for _, part := range parts {
|
||||
@@ -214,17 +202,6 @@ func PostProcess(conf cfg.Config, data *Tabdata) (*Tabdata, bool, error) {
|
||||
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 {
|
||||
repr.Print(data)
|
||||
}
|
||||
|
||||
58
tablizer.1
58
tablizer.1
@@ -178,7 +178,6 @@ tablizer \- Manipulate tabular output of other programs
|
||||
\& Other Flags:
|
||||
\& \-\-completion <shell> Generate the autocompletion script for <shell>
|
||||
\& \-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
|
||||
\& \-h, \-\-help help for tablizer
|
||||
\& \-m, \-\-man Display manual page
|
||||
@@ -541,63 +540,6 @@ the \f(CW\*(C`\-L\*(C'\fR parameter).
|
||||
.PP
|
||||
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.
|
||||
.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"
|
||||
.IX Header "BUGS"
|
||||
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:
|
||||
--completion <shell> Generate the autocompletion script for <shell>
|
||||
-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
|
||||
-h, --help help for tablizer
|
||||
-m, --man Display manual page
|
||||
@@ -384,67 +383,7 @@ the C<-L> parameter).
|
||||
Colorization can be turned off completely either by setting the
|
||||
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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user