Skip to content

Commit

Permalink
Add Lua backend for scriptable output formats.
Browse files Browse the repository at this point in the history
  • Loading branch information
billbirchatcoles committed Jun 10, 2020
1 parent f2dc22a commit 5b88fe6
Show file tree
Hide file tree
Showing 7 changed files with 341 additions and 31 deletions.
112 changes: 111 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Program to convert lines of text into a beautiful tree structure.

<img src="frangipanni.jpg" alt="A Tree" width="200" align="right">

The program reads each line on the standard input in turn. It breaks the line into tokens, then adds the sequence of tokens into a tree structure which is printed as indented lines or JSON formats.
The program reads each line on the standard input in turn. It breaks the line into tokens, then adds the sequence of tokens into a tree structure which is printed as indented lines or JSON formats. Alternatively the tree can be passed to a gopher-Lua script which you can write for any format.

Options control where the line is broken into tokens, and output considerations.

Expand Down Expand Up @@ -106,6 +106,8 @@ cat <input> | frangipanni [options]
Number of spaces to indent per level. (default 4)
-level int
Analyse down to this level (positive integer). (default 2147483647)
-lua string
Lua Script to run
-no-fold
Don't fold into one line.
-order string
Expand Down Expand Up @@ -353,10 +355,118 @@ Which results in an indented bullet list:
> - main.conf
> - fish/completions/task.fish
## Lua Examples

### JSON (again)

First, we are going tell frangipanni to output via a Lua program called 'json.lua', and we will format the json with the 'jp' program.

```
$ <test/fixtures/simplechars.txt frangipanni -lua json.lua | jp @
```

The Lua script uses the `github.com/layeh/gopher-json` module which is imported in the Lua. The data
is made available in the variable `frangipanni` which has a table for each node, with fields

* depth - in the tree starting from 0
* lineNumber - the token was first detected
* numMatched - the number of times the token was seen
* sep - separation characters preceding the token
* text - the token itself
* children - a table containing the child nodes

```Lua
local json = require("json")

print(json.encode(frangipanni))
```

The output shows that all the fields of the parsed nodes are passed to Lua in a Table.
The root node is empty except for it's children. The Lua script is therafore able to use
the fields intelligently.

```json
{
"depth": 0,
"lineNumber": -1,
"numMatched": 1,
"sep": "",
"text": ""
"children": {
"1.2": {
"children": [],
"depth": 1,
"lineNumber": 8,
"numMatched": 1,
"sep": "",
"text": "1.2"
},
"A": {
"children": [],
"depth": 1,
"lineNumber": 1,
"numMatched": 1,
"sep": "",
"text": "A"
},
```

### Markdown

```Lua
function indent(n)
for i=1, n do
io.write(" ")
end
end

function markdown(node)
indent(node.depth)
io.write("* ")
print(node.text)
for k, v in pairs(node.children) do
markdown(v)
end
end

markdown(frangipanni)
```

The output can look like this:

```
*
* A
* C
* 2
* D
* x.a
* 2
* 1
* Z
* 1.2
```


### XML

The xml.lua script provided in the release outputs very basic XML format which might suit simple inputs.

```XML
<root count="1" sep="">
<C count="2" sep="">
<2 count="1" sep="."/>
<D count="1" sep="."/>
</C>
<x.a count="3" sep="">
<1 count="1" sep="."/>
<2 count="1" sep="."/>
</x.a>
<Z count="1" sep=""/>
<1.2 count="1" sep=""/>
<A count="1" sep=""/>
</root>
```



Expand Down
66 changes: 36 additions & 30 deletions frangipanni.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import (
"io"
"log"
"os"
// "runtime"
// "runtime/pprof"
"encoding/json"
luajson "github.com/layeh/gopher-json"
"github.com/yuin/gopher-lua"
"math"
"sort"
"strconv"
Expand Down Expand Up @@ -309,8 +309,32 @@ func fakeCounts(n *node) {

}

//var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to `file`")
//var memprofile = flag.String("memprofile", "", "write memory profile to `file`")
func makeLuaTableFromNode(L *lua.LState, n *node) *lua.LTable {
tb := L.CreateTable(6,6)

tb.RawSet(lua.LString("lineNumber"), lua.LNumber(n.lineNumber))
tb.RawSet(lua.LString("text"), lua.LString(n.text))
tb.RawSet(lua.LString("sep"), lua.LString(n.sep))
tb.RawSet(lua.LString("numMatched"), lua.LNumber(n.numMatched))
tb.RawSet(lua.LString("depth"), lua.LNumber(n.depth))
ch := L.CreateTable(len(n.children), len(n.children))
tb.RawSet(lua.LString("children"), ch)
for k, v := range n.children {
ch.RawSet(lua.LString(k), makeLuaTableFromNode(L, v))
}
return tb
}

func luaRun(out io.Writer, root *node) {
L := lua.NewState()
luajson.Preload(L)
L.SetGlobal("frangipanni", makeLuaTableFromNode(L, root))
defer L.Close()
if err := L.DoFile(luaFile); err != nil {
panic(err)
}
}


// Nasty Globals for options ;-)
var printSeparators bool
Expand All @@ -324,6 +348,7 @@ var printCounts bool
var printDepth int
var indentWidth int
var indentString string
var luaFile string

func main() {

Expand All @@ -342,6 +367,7 @@ func main() {
flag.IntVar(&printDepth, "depth", math.MaxInt32, "Maximum tree depth to print.")
flag.IntVar(&indentWidth, "indent", 4, "Number of spaces to indent per level.")
flag.StringVar(&indentString, "spacer", " ", "Characters to indent lines with.")
flag.StringVar(&luaFile, "lua", "", "Lua Script to run")

flag.Parse()
if maxLevel < 0 {
Expand All @@ -355,19 +381,6 @@ func main() {
}
printSeparators = printSeparators || splitOnCharacters

/* if *cpuprofile != "" {
f, err := os.Create(*cpuprofile)
if err != nil {
log.Fatal("could not create CPU profile: ", err)
}
defer f.Close() // error handling omitted for example
if err := pprof.StartCPUProfile(f); err != nil {
log.Fatal("could not start CPU profile: ", err)
}
defer pprof.StopCPUProfile()
}
*/

file := os.Stdin
defer file.Close()

Expand Down Expand Up @@ -433,6 +446,12 @@ func main() {
if !noFold {
froot = fold(&root)
}

if luaFile != "" {
luaRun(stdoutBuffered, &root)
os.Exit(0)
}

switch format {
case "indent":
fprintTree(stdoutBuffered, froot)
Expand All @@ -447,17 +466,4 @@ func main() {
default:
log.Fatalf("Error: unknown format option '%v'", format)
}

/* if *memprofile != "" {
f, err := os.Create(*memprofile)
if err != nil {
log.Fatal("could not create memory profile: ", err)
}
defer f.Close() // error handling omitted for example
runtime.GC() // get up-to-date statistics
if err := pprof.WriteHeapProfile(f); err != nil {
log.Fatal("could not write memory profile: ", err)
}
}
*/
}
6 changes: 6 additions & 0 deletions json.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
--
-- Output in JSON format
--
local json = require("json")

print(json.encode(frangipanni))
30 changes: 30 additions & 0 deletions markdown.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
-- Beginning of Markdown unnumbered indented bullet list
-- Print each line preceded with indents and an '*'
-- Example:
-- *
-- * x.a
-- * 1
-- * 2
-- * Z
-- * 1.2
-- * A
-- * C
-- * D
-- * 2

function indent(n)
for i=1, n do
io.write(" ")
end
end

function markdown(node)
indent(node.depth)
io.write("* ")
print(node.text)
for k, v in pairs(node.children) do
markdown(v)
end
end

markdown(frangipanni)
7 changes: 7 additions & 0 deletions test/confidence.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,12 @@ do
head -50 "$tf" | ../frangipanni $sw -format json -order alpha >> "$tempfile"
done
done
# Lua
echo "fixtures/simplechars.txt--- -lua json.lua ----------------------------------------------------------------------------------------------------" >> "$tempfile"
<fixtures/simplechars.txt ../frangipanni -lua ../json.lua | jp '@' >> "$tempfile"

echo "fixtures/simplechars.txt--- -lua xml.lua ----------------------------------------------------------------------------------------------------" >> "$tempfile"
<fixtures/simplechars.txt ../frangipanni -lua ../xml.lua >> "$tempfile"

set -x
diff "$tempfile" $scriptdir/fixtures.log || kdiff3 "$tempfile" $scriptdir/fixtures.log
Loading

0 comments on commit 5b88fe6

Please sign in to comment.