{"id":13491923,"url":"https://github.com/birchb1024/frangipanni","last_synced_at":"2025-05-16T13:05:54.882Z","repository":{"id":48663044,"uuid":"260141985","full_name":"birchb1024/frangipanni","owner":"birchb1024","description":"Program to convert lines of text into a tree structure.","archived":false,"fork":false,"pushed_at":"2023-06-15T02:22:18.000Z","size":1051,"stargazers_count":1199,"open_issues_count":8,"forks_count":30,"subscribers_count":11,"default_branch":"master","last_synced_at":"2025-05-07T04:37:57.257Z","etag":null,"topics":["go","golang","text-processing","tree-structure"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/birchb1024.png","metadata":{"files":{"readme":"README.html","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2020-04-30T07:25:00.000Z","updated_at":"2025-05-05T21:02:02.000Z","dependencies_parsed_at":"2023-10-20T18:26:59.966Z","dependency_job_id":null,"html_url":"https://github.com/birchb1024/frangipanni","commit_stats":{"total_commits":67,"total_committers":5,"mean_commits":13.4,"dds":0.5223880597014925,"last_synced_commit":"e607c6c8f74c182933f95afd58aab671fd6ce6cc"},"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/birchb1024%2Ffrangipanni","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/birchb1024%2Ffrangipanni/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/birchb1024%2Ffrangipanni/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/birchb1024%2Ffrangipanni/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/birchb1024","download_url":"https://codeload.github.com/birchb1024/frangipanni/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254535827,"owners_count":22087399,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["go","golang","text-processing","tree-structure"],"created_at":"2024-07-31T19:01:01.553Z","updated_at":"2025-05-16T13:05:54.820Z","avatar_url":"https://github.com/birchb1024.png","language":"Go","funding_links":[],"categories":["Go","Tools","Repositories"],"sub_categories":["Go"],"readme":"\u003c?xml version=\"1.0\" encoding=\"utf-8\"?\u003e\n\u003c!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\"\u003e\n\u003chtml xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"en\" xml:lang=\"en\"\u003e\n\u003chead\u003e\n\u003c!-- 2021-12-05 Sun 14:51 --\u003e\n\u003cmeta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\" /\u003e\n\u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\" /\u003e\n\u003ctitle\u003eFrangipanni\u003c/title\u003e\n\u003cmeta name=\"generator\" content=\"Org mode\" /\u003e\n\u003cmeta name=\"author\" content=\"Peter Birch\" /\u003e\n\u003cstyle type=\"text/css\"\u003e\n \u003c!--/*--\u003e\u003c![CDATA[/*\u003e\u003c!--*/\n  .title  { text-align: center;\n             margin-bottom: .2em; }\n  .subtitle { text-align: center;\n              font-size: medium;\n              font-weight: bold;\n              margin-top:0; }\n  .todo   { font-family: monospace; color: red; }\n  .done   { font-family: monospace; color: green; }\n  .priority { font-family: monospace; color: orange; }\n  .tag    { background-color: #eee; font-family: monospace;\n            padding: 2px; font-size: 80%; font-weight: normal; }\n  .timestamp { color: #bebebe; }\n  .timestamp-kwd { color: #5f9ea0; }\n  .org-right  { margin-left: auto; margin-right: 0px;  text-align: right; }\n  .org-left   { margin-left: 0px;  margin-right: auto; text-align: left; }\n  .org-center { margin-left: auto; margin-right: auto; text-align: center; }\n  .underline { text-decoration: underline; }\n  #postamble p, #preamble p { font-size: 90%; margin: .2em; }\n  p.verse { margin-left: 3%; }\n  pre {\n    border: 1px solid #ccc;\n    box-shadow: 3px 3px 3px #eee;\n    padding: 8pt;\n    font-family: monospace;\n    overflow: auto;\n    margin: 1.2em;\n  }\n  pre.src {\n    position: relative;\n    overflow: visible;\n    padding-top: 1.2em;\n  }\n  pre.src:before {\n    display: none;\n    position: absolute;\n    background-color: white;\n    top: -10px;\n    right: 10px;\n    padding: 3px;\n    border: 1px solid black;\n  }\n  pre.src:hover:before { display: inline;}\n  /* Languages per Org manual */\n  pre.src-asymptote:before { content: 'Asymptote'; }\n  pre.src-awk:before { content: 'Awk'; }\n  pre.src-C:before { content: 'C'; }\n  /* pre.src-C++ doesn't work in CSS */\n  pre.src-clojure:before { content: 'Clojure'; }\n  pre.src-css:before { content: 'CSS'; }\n  pre.src-D:before { content: 'D'; }\n  pre.src-ditaa:before { content: 'ditaa'; }\n  pre.src-dot:before { content: 'Graphviz'; }\n  pre.src-calc:before { content: 'Emacs Calc'; }\n  pre.src-emacs-lisp:before { content: 'Emacs Lisp'; }\n  pre.src-fortran:before { content: 'Fortran'; }\n  pre.src-gnuplot:before { content: 'gnuplot'; }\n  pre.src-haskell:before { content: 'Haskell'; }\n  pre.src-hledger:before { content: 'hledger'; }\n  pre.src-java:before { content: 'Java'; }\n  pre.src-js:before { content: 'Javascript'; }\n  pre.src-latex:before { content: 'LaTeX'; }\n  pre.src-ledger:before { content: 'Ledger'; }\n  pre.src-lisp:before { content: 'Lisp'; }\n  pre.src-lilypond:before { content: 'Lilypond'; }\n  pre.src-lua:before { content: 'Lua'; }\n  pre.src-matlab:before { content: 'MATLAB'; }\n  pre.src-mscgen:before { content: 'Mscgen'; }\n  pre.src-ocaml:before { content: 'Objective Caml'; }\n  pre.src-octave:before { content: 'Octave'; }\n  pre.src-org:before { content: 'Org mode'; }\n  pre.src-oz:before { content: 'OZ'; }\n  pre.src-plantuml:before { content: 'Plantuml'; }\n  pre.src-processing:before { content: 'Processing.js'; }\n  pre.src-python:before { content: 'Python'; }\n  pre.src-R:before { content: 'R'; }\n  pre.src-ruby:before { content: 'Ruby'; }\n  pre.src-sass:before { content: 'Sass'; }\n  pre.src-scheme:before { content: 'Scheme'; }\n  pre.src-screen:before { content: 'Gnu Screen'; }\n  pre.src-sed:before { content: 'Sed'; }\n  pre.src-sh:before { content: 'shell'; }\n  pre.src-sql:before { content: 'SQL'; }\n  pre.src-sqlite:before { content: 'SQLite'; }\n  /* additional languages in org.el's org-babel-load-languages alist */\n  pre.src-forth:before { content: 'Forth'; }\n  pre.src-io:before { content: 'IO'; }\n  pre.src-J:before { content: 'J'; }\n  pre.src-makefile:before { content: 'Makefile'; }\n  pre.src-maxima:before { content: 'Maxima'; }\n  pre.src-perl:before { content: 'Perl'; }\n  pre.src-picolisp:before { content: 'Pico Lisp'; }\n  pre.src-scala:before { content: 'Scala'; }\n  pre.src-shell:before { content: 'Shell Script'; }\n  pre.src-ebnf2ps:before { content: 'ebfn2ps'; }\n  /* additional language identifiers per \"defun org-babel-execute\"\n       in ob-*.el */\n  pre.src-cpp:before  { content: 'C++'; }\n  pre.src-abc:before  { content: 'ABC'; }\n  pre.src-coq:before  { content: 'Coq'; }\n  pre.src-groovy:before  { content: 'Groovy'; }\n  /* additional language identifiers from org-babel-shell-names in\n     ob-shell.el: ob-shell is the only babel language using a lambda to put\n     the execution function name together. */\n  pre.src-bash:before  { content: 'bash'; }\n  pre.src-csh:before  { content: 'csh'; }\n  pre.src-ash:before  { content: 'ash'; }\n  pre.src-dash:before  { content: 'dash'; }\n  pre.src-ksh:before  { content: 'ksh'; }\n  pre.src-mksh:before  { content: 'mksh'; }\n  pre.src-posh:before  { content: 'posh'; }\n  /* Additional Emacs modes also supported by the LaTeX listings package */\n  pre.src-ada:before { content: 'Ada'; }\n  pre.src-asm:before { content: 'Assembler'; }\n  pre.src-caml:before { content: 'Caml'; }\n  pre.src-delphi:before { content: 'Delphi'; }\n  pre.src-html:before { content: 'HTML'; }\n  pre.src-idl:before { content: 'IDL'; }\n  pre.src-mercury:before { content: 'Mercury'; }\n  pre.src-metapost:before { content: 'MetaPost'; }\n  pre.src-modula-2:before { content: 'Modula-2'; }\n  pre.src-pascal:before { content: 'Pascal'; }\n  pre.src-ps:before { content: 'PostScript'; }\n  pre.src-prolog:before { content: 'Prolog'; }\n  pre.src-simula:before { content: 'Simula'; }\n  pre.src-tcl:before { content: 'tcl'; }\n  pre.src-tex:before { content: 'TeX'; }\n  pre.src-plain-tex:before { content: 'Plain TeX'; }\n  pre.src-verilog:before { content: 'Verilog'; }\n  pre.src-vhdl:before { content: 'VHDL'; }\n  pre.src-xml:before { content: 'XML'; }\n  pre.src-nxml:before { content: 'XML'; }\n  /* add a generic configuration mode; LaTeX export needs an additional\n     (add-to-list 'org-latex-listings-langs '(conf \" \")) in .emacs */\n  pre.src-conf:before { content: 'Configuration File'; }\n\n  table { border-collapse:collapse; }\n  caption.t-above { caption-side: top; }\n  caption.t-bottom { caption-side: bottom; }\n  td, th { vertical-align:top;  }\n  th.org-right  { text-align: center;  }\n  th.org-left   { text-align: center;   }\n  th.org-center { text-align: center; }\n  td.org-right  { text-align: right;  }\n  td.org-left   { text-align: left;   }\n  td.org-center { text-align: center; }\n  dt { font-weight: bold; }\n  .footpara { display: inline; }\n  .footdef  { margin-bottom: 1em; }\n  .figure { padding: 1em; }\n  .figure p { text-align: center; }\n  .equation-container {\n    display: table;\n    text-align: center;\n    width: 100%;\n  }\n  .equation {\n    vertical-align: middle;\n  }\n  .equation-label {\n    display: table-cell;\n    text-align: right;\n    vertical-align: middle;\n  }\n  .inlinetask {\n    padding: 10px;\n    border: 2px solid gray;\n    margin: 10px;\n    background: #ffffcc;\n  }\n  #org-div-home-and-up\n   { text-align: right; font-size: 70%; white-space: nowrap; }\n  textarea { overflow-x: auto; }\n  .linenr { font-size: smaller }\n  .code-highlighted { background-color: #ffff00; }\n  .org-info-js_info-navigation { border-style: none; }\n  #org-info-js_console-label\n    { font-size: 10px; font-weight: bold; white-space: nowrap; }\n  .org-info-js_search-highlight\n    { background-color: #ffff00; color: #000000; font-weight: bold; }\n  .org-svg { width: 90%; }\n  /*]]\u003e*/--\u003e\n\u003c/style\u003e\n\u003cscript type=\"text/javascript\"\u003e\n/*\n@licstart  The following is the entire license notice for the\nJavaScript code in this tag.\n\nCopyright (C) 2012-2020 Free Software Foundation, Inc.\n\nThe JavaScript code in this tag is free software: you can\nredistribute it and/or modify it under the terms of the GNU\nGeneral Public License (GNU GPL) as published by the Free Software\nFoundation, either version 3 of the License, or (at your option)\nany later version.  The code is distributed WITHOUT ANY WARRANTY;\nwithout even the implied warranty of MERCHANTABILITY or FITNESS\nFOR A PARTICULAR PURPOSE.  See the GNU GPL for more details.\n\nAs additional permission under GNU GPL version 3 section 7, you\nmay distribute non-source (e.g., minimized or compacted) forms of\nthat code without the copy of the GNU GPL normally required by\nsection 4, provided you include this license notice and a URL\nthrough which recipients can access the Corresponding Source.\n\n\n@licend  The above is the entire license notice\nfor the JavaScript code in this tag.\n*/\n\u003c!--/*--\u003e\u003c![CDATA[/*\u003e\u003c!--*/\n function CodeHighlightOn(elem, id)\n {\n   var target = document.getElementById(id);\n   if(null != target) {\n     elem.cacheClassElem = elem.className;\n     elem.cacheClassTarget = target.className;\n     target.className = \"code-highlighted\";\n     elem.className   = \"code-highlighted\";\n   }\n }\n function CodeHighlightOff(elem, id)\n {\n   var target = document.getElementById(id);\n   if(elem.cacheClassElem)\n     elem.className = elem.cacheClassElem;\n   if(elem.cacheClassTarget)\n     target.className = elem.cacheClassTarget;\n }\n/*]]\u003e*///--\u003e\n\u003c/script\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n\u003cdiv id=\"content\"\u003e\n\u003ch1 class=\"title\"\u003eFrangipanni\u003c/h1\u003e\n\u003cdiv id=\"table-of-contents\"\u003e\n\u003ch2\u003eTable of Contents\u003c/h2\u003e\n\u003cdiv id=\"text-table-of-contents\"\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"#frangipanni\"\u003e1. frangipanni\u003c/a\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"#basic-operation\"\u003e1.1. Basic Operation\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#usage\"\u003e2. Usage\u003c/a\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"#options\"\u003e2.1. Options\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#examples\"\u003e3. Examples\u003c/a\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"#log-files\"\u003e3.1. Log files\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#data-from-environment-variables\"\u003e3.2. Data from environment variables\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#split-the-path\"\u003e3.3. Split the PATH\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#query-a-csv-triplestore---json\"\u003e3.4. Query a CSV triplestore -\u0026gt; JSON\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#security-analysis-of-sudo-use-in-auth-log-file\"\u003e3.5. Security Analysis of sudo use in Auth Log File\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#output-for-spreadsheets\"\u003e3.6. Output for Spreadsheets\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#output-for-markdown\"\u003e3.7. Output for Markdown\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#lua-examples\"\u003e3.8. Lua Examples\u003c/a\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"#json-again\"\u003e3.8.1. JSON (again)\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#markdown\"\u003e3.8.2. Markdown\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#xml\"\u003e3.8.3. XML\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\u003cdiv id=\"outline-container-org57a14c5\" class=\"outline-2\"\u003e\n\u003ch2 id=\"frangipanni\"\u003e\u003cspan class=\"section-number-2\"\u003e1\u003c/span\u003e frangipanni\u003c/h2\u003e\n\u003cdiv class=\"outline-text-2\" id=\"text-frangipanni\"\u003e\n\u003cp\u003e\nProgram to convert lines of text into beautiful tree structures.\n\u003c/p\u003e\n\n\u003cp width=\"200px\"\u003e\n\u003cimg src=\"./frangipanni.jpg\" alt=\"frangipanni.jpg\" width=\"200px\" /\u003e\nPlumeria sanalsp\n\u003c/p\u003e\n\n\u003cp\u003e\nThe program reads each line on the standard input in turn. It breaks\neach line into tokens, then adds the sequence of tokens into a tree\nstructure. Lines with the same leading tokens are placed in the same\nbranch of the tree. The tree is printed as indented lines or JSON\nformat. Alternatively the tree can be passed to a user-provided Lua\nscript which can produce any output format.\n\u003c/p\u003e\n\n\u003cp\u003e\nOptions control where the line is broken into tokens, and how it is\nanalysed and output.\n\u003c/p\u003e\n\u003c/div\u003e\n\n\u003cdiv id=\"outline-container-org5965f35\" class=\"outline-3\"\u003e\n\u003ch3 id=\"basic-operation\"\u003e\u003cspan class=\"section-number-3\"\u003e1.1\u003c/span\u003e Basic Operation\u003c/h3\u003e\n\u003cdiv class=\"outline-text-3\" id=\"text-basic-operation\"\u003e\n\u003cp\u003e\nHere is a simple example. Given this command\n\u003ccode\u003esudo find /etc -maxdepth 3 | tail -9\u003c/code\u003e,\n\u003c/p\u003e\n\n\u003cp\u003e\nWe get this data:\n\u003c/p\u003e\n\n\u003cpre class=\"example\"\u003e\n/etc/bluetooth/rfcomm.conf.dpkg-remove\n/etc/bluetooth/serial.conf.dpkg-remove\n/etc/bluetooth/input.conf\n/etc/bluetooth/audio.conf.dpkg-remove\n/etc/bluetooth/network.conf\n/etc/bluetooth/main.conf\n/etc/fish\n/etc/fish/completions\n/etc/fish/completions/task.fish\n\u003c/pre\u003e\n\n\u003cp\u003e\nWhen we pipe this into the \u003ccode\u003efrangipanni\u003c/code\u003e program :\n\u003c/p\u003e\n\n\u003cpre class=\"example\"\u003e\nsudo find /etc -maxdepth 3 | tail -9 | frangipanni\n\u003c/pre\u003e\n\n\u003cp\u003e\nwe see this output:\n\u003c/p\u003e\n\n\u003cpre class=\"example\"\u003e\netc\n    bluetooth\n        rfcomm.conf.dpkg-remove\n        serial.conf.dpkg-remove\n        input.conf\n        audio.conf.dpkg-remove\n        network.conf\n        main.conf\n    fish/completions/task.fish\n\u003c/pre\u003e\n\n\u003cp\u003e\nBy default, it reads each line and splits them into tokens when it finds\na non-alphanumeric character.\n\u003c/p\u003e\n\n\u003cp\u003e\nIn this next example we're processing a list of files produced by \u003ccode\u003efind\u003c/code\u003e\nso we only want to break on directories. So we can specify \u003ccode\u003e-breaks /\u003c/code\u003e.\n\u003c/p\u003e\n\n\u003cp\u003e\nThe default behaviour is to \u003ci\u003efold\u003c/i\u003e tree branches with no sub-branches\ninto a single line of output. e.g. =fish/completions/task.fish= We turn\noff folding by specifying the \u003ccode\u003e-no-fold\u003c/code\u003e option. With the refined\ncommand\n\u003c/p\u003e\n\n\u003cpre class=\"example\"\u003e\nfrangipanni -breaks / -no-fold\n\u003c/pre\u003e\n\n\u003cp\u003e\nWe see this output\n\u003c/p\u003e\n\n\u003cpre class=\"example\"\u003e\netc\n    bluetooth\n        rfcomm.conf.dpkg-remove\n        serial.conf.dpkg-remove\n        input.conf\n        audio.conf.dpkg-remove\n        network.conf\n        main.conf\n    fish\n        completions\n            task.fish\n\u003c/pre\u003e\n\n\u003cp\u003e\nHaving restructured the data into a tree format we can output in other\nformats. We can ask for JSON by adding the \u003ccode\u003e-format json\u003c/code\u003e option. We get\nthis output:\n\u003c/p\u003e\n\n\u003cpre class=\"example\"\u003e\n{\"etc\" : \n    {\"bluetooth\" : \n        [\"rfcomm.conf.dpkg-remove\",\n        \"serial.conf.dpkg-remove\",\n        \"input.conf\",\n        \"audio.conf.dpkg-remove\",\n        \"network.conf\",\n        \"main.conf\"],\n    \"fish\" : \n        {\"completions\" : \"task.fish\"}}}\n\u003c/pre\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\n\u003cdiv id=\"outline-container-orge7f7693\" class=\"outline-2\"\u003e\n\u003ch2 id=\"usage\"\u003e\u003cspan class=\"section-number-2\"\u003e2\u003c/span\u003e Usage\u003c/h2\u003e\n\u003cdiv class=\"outline-text-2\" id=\"text-usage\"\u003e\n\u003cp\u003e\nThe command is a simple filter taking standard input, and output on\nstdout.\n\u003c/p\u003e\n\n\u003cdiv class=\"org-src-container\"\u003e\n\u003cpre class=\"src src-sh\"\u003ecat \u0026lt;input\u0026gt; | frangipanni [options]\n\u003c/pre\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\n\u003cdiv id=\"outline-container-org70c9866\" class=\"outline-3\"\u003e\n\u003ch3 id=\"options\"\u003e\u003cspan class=\"section-number-3\"\u003e2.1\u003c/span\u003e Options\u003c/h3\u003e\n\u003cdiv class=\"outline-text-3\" id=\"text-options\"\u003e\n\u003cpre class=\"example\"\u003e\n-breaks string\n      Characters to slice lines with.\n-chars\n      Slice line after every character.\n-counts\n      Print number of matches at the end of the line.\n-depth int\n      Maximum tree depth to print. (default 2147483647)\n-format string\n      Format of output: indent|json (default \"indent\")\n-indent int\n      Number of spaces to indent per level. (default 4)\n-level int\n      Analyse down to this level (positive integer). (default 2147483647)\n-lua string\n      Lua Script to run\n-no-fold\n      Don't fold into one line.\n-order string\n      Sort order input|alpha. Sort the childs either in input order or via character ordering (default \"input\")\n-separators\n      Print leading separators.\n-skip int\n      Number of leading fields to skip.\n-spacer string\n      Characters to indent lines with. (default \" \")\n\u003c/pre\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\n\u003cdiv id=\"outline-container-org0d59cbf\" class=\"outline-2\"\u003e\n\u003ch2 id=\"examples\"\u003e\u003cspan class=\"section-number-2\"\u003e3\u003c/span\u003e Examples\u003c/h2\u003e\n\u003cdiv class=\"outline-text-2\" id=\"text-examples\"\u003e\n\u003c/div\u003e\n\n\u003cdiv id=\"outline-container-orgdcebcf4\" class=\"outline-3\"\u003e\n\u003ch3 id=\"log-files\"\u003e\u003cspan class=\"section-number-3\"\u003e3.1\u003c/span\u003e Log files\u003c/h3\u003e\n\u003cdiv class=\"outline-text-3\" id=\"text-log-files\"\u003e\n\u003cp\u003e\nGiven input from a log file:\n\u003c/p\u003e\n\n\u003cpre class=\"example\"\u003e\nMay 10 03:17:06 localhost systemd: Removed slice User Slice of root.\nMay 10 03:17:06 localhost systemd: Stopping User Slice of root.\nMay 10 04:00:00 localhost systemd: Starting Docker Cleanup...\nMay 10 04:00:00 localhost systemd: Started Docker Cleanup.\nMay 10 04:00:00 localhost dockerd-current: time=\"2020-05-10T04:00:00.629849861+10:00\" level=debug msg=\"Calling GET /_ping\"\nMay 10 04:00:00 localhost dockerd-current: time=\"2020-05-10T04:00:00.629948000+10:00\" level=debug msg=\"Unable to determine container for /\"\nMay 10 04:00:00 localhost dockerd-current: time=\"2020-05-10T04:00:00.630103455+10:00\" level=debug msg=\"{Action=_ping, LoginUID=12345678, PID=21075}\"\nMay 10 04:00:00 localhost dockerd-current: time=\"2020-05-10T04:00:00.630684502+10:00\" level=debug msg=\"Calling GET /v1.26/containers/json?all=1\u0026amp;filters=%7B%22status%22%3A%7B%22dead%22%3Atrue%7D%7D\"\nMay 10 04:00:00 localhost dockerd-current: time=\"2020-05-10T04:00:00.630704513+10:00\" level=debug msg=\"Unable to determine container for containers\"\nMay 10 04:00:00 localhost dockerd-current: time=\"2020-05-10T04:00:00.630735545+10:00\" level=debug msg=\"{Action=json, LoginUID=12345678, PID=21075}\"\n\u003c/pre\u003e\n\n\u003cp\u003e\ndefault output is:\n\u003c/p\u003e\n\n\u003cpre class=\"example\"\u003e\nMay 10\n 03:17:06 localhost systemd\n  : Removed slice User Slice of root\n  : Stopping User Slice of root\n 04:00:00 localhost\n   dockerd-current: time=\"2020-05-10T04:00:00\n    .629849861+10:00\" level=debug msg=\"Calling GET /_ping\n    .629948000+10:00\" level=debug msg=\"Unable to determine container for\n    .630103455+10:00\" level=debug msg=\"{Action=_ping, LoginUID=12345678, PID=21075\n    .630684502+10:00\" level=debug msg=\"Calling GET /v1.26/containers/json?all=1\u0026amp;filters=%7B%22status%22%3A%7B%22dead%22%3Atrue%7D%7D\n    .630704513+10:00\" level=debug msg=\"Unable to determine container for containers\n    .630735545+10:00\" level=debug msg=\"{Action=json, LoginUID=12345678, PID=21075\n   systemd\n    : Started Docker Cleanup\n    : Starting Docker Cleanup\n\u003c/pre\u003e\n\n\u003cp\u003e\nwith the \u003ccode\u003e-skip 5\u003c/code\u003e option we can ignore the date and time at the\nbeginning of each line. The output is\n\u003c/p\u003e\n\n\u003cpre class=\"example\"\u003e\nlocalhost\n    systemd\n        Removed slice User Slice of root\n        Stopping User Slice of root\n        Starting Docker Cleanup\n        Started Docker Cleanup\n    dockerd-current: time=\"2020-05-10T04:00:00\n        629849861+10:00\" level=debug msg=\"Calling GET /_ping\n        629948000+10:00\" level=debug msg=\"Unable to determine container for\n        630103455+10:00\" level=debug msg=\"{Action=_ping, LoginUID=12345678, PID=21075\n        630684502+10:00\" level=debug msg=\"Calling GET /v1.26/containers/json?all=1\u0026amp;filters=%7B%22status%22%3A%7B%22dead%22%3Atrue%7D%7D\n        630704513+10:00\" level=debug msg=\"Unable to determine container for containers\n        630735545+10:00\" level=debug msg=\"{Action=json, LoginUID=12345678, PID=21075\n\u003c/pre\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\n\u003cdiv id=\"outline-container-org3fdca2b\" class=\"outline-3\"\u003e\n\u003ch3 id=\"data-from-environment-variables\"\u003e\u003cspan class=\"section-number-3\"\u003e3.2\u003c/span\u003e Data from environment variables\u003c/h3\u003e\n\u003cdiv class=\"outline-text-3\" id=\"text-data-from-environment-variables\"\u003e\n\u003cp\u003e\nGive this input, from \u003ccode\u003eenv | egrep '^XDG'\u003c/code\u003e :\n\u003c/p\u003e\n\n\u003cpre class=\"example\"\u003e\nXDG_VTNR=2\nXDG_SESSION_ID=5\nXDG_SESSION_TYPE=x11\nXDG_DATA_DIRS=/usr/share:/usr/share:/usr/local/share\nXDG_SESSION_DESKTOP=plasma\nXDG_CURRENT_DESKTOP=KDE\nXDG_SEAT=seat0\nXDG_RUNTIME_DIR=/run/user/1000\nXDG_SESSION_COOKIE=fe37f2ef4-158904.727668-469753\n\u003c/pre\u003e\n\n\u003cp\u003e\nAnd run with\n\u003c/p\u003e\n\n\u003cpre class=\"example\"\u003e\n$ env | egrep '^XDG' | ./frangipanni -breaks '=_' -no-fold -format json\n\u003c/pre\u003e\n\n\u003cp\u003e\nwe get\n\u003c/p\u003e\n\n\u003cpre class=\"example\"\u003e\n{\"XDG\" : \n    {\"VTNR\" : 2,\n    \"SESSION\" : \n        {\"ID\" : 5,\n        \"TYPE\" : \"x11\",\n        \"DESKTOP\" : \"plasma\",\n        \"COOKIE\" : \"fe37f2ef4-158904.727668-469753\"},\n    \"DATA\" : \n        {\"DIRS\" : \"/usr/share:/usr/share:/usr/local/share\"},\n    \"CURRENT\" : \n        {\"DESKTOP\" : \"KDE\"},\n    \"SEAT\" : \"seat0\",\n    \"RUNTIME\" : \n        {\"DIR\" : \"/run/user/1000\"}}}\n\u003c/pre\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\n\u003cdiv id=\"outline-container-org93a412b\" class=\"outline-3\"\u003e\n\u003ch3 id=\"split-the-path\"\u003e\u003cspan class=\"section-number-3\"\u003e3.3\u003c/span\u003e Split the PATH\u003c/h3\u003e\n\u003cdiv class=\"outline-text-3\" id=\"text-split-the-path\"\u003e\n\u003cpre class=\"example\"\u003e\n$ echo $PATH | tr ':' '\\n' | ./frangipanni -separators\n\u003c/pre\u003e\n\n\u003cpre class=\"example\"\u003e\n/home/alice\n    /work/gopath/src/github.com/birchb1024/frangipanni\n    /apps\n        /textadept_10.8.x86_64\n        /shellcheck-v0.7.1\n        /Digital/Digital\n        /gradle-4.9/bin\n        /idea-IC-172.4343.14/bin\n        /GoLand-173.3531.21/bin\n        /arduino-1.6.7\n    /yed\n    /bin\n/usr\n    /lib/jvm/java-8-openjdk-amd64/bin\n    /local\n        /bin\n        /games\n        /go/bin\n    /bin\n    /games\n/bin\n\u003c/pre\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\n\u003cdiv id=\"outline-container-org908e1a8\" class=\"outline-3\"\u003e\n\u003ch3 id=\"query-a-csv-triplestore---json\"\u003e\u003cspan class=\"section-number-3\"\u003e3.4\u003c/span\u003e Query a CSV triplestore -\u0026gt; JSON\u003c/h3\u003e\n\u003cdiv class=\"outline-text-3\" id=\"text-query-a-csv-triplestore---json\"\u003e\n\u003cp\u003e\nA CSV tiplestore is a simple way of recording a database of facts about\nobjects. Each line has a Subject, Object, Predicate structure.\n\u003c/p\u003e\n\n\u003cdiv class=\"org-src-container\"\u003e\n\u003cpre class=\"src src-csv\"\u003ejohn1@jupiter,rdf:type,UnixAccount\njoanna,hasAccount,alice1@jupiter\njupiter,defaultAccount,alice1\nalice2,hasAccount,evan1@jupiter\nfelicity,hasAccount,john1@jupiter\nalice1@jupiter,rdf:type,UnixAccount\nkalpana,hasAccount,alice1@jupiter\njohn1@jupiter,hasPassword,felicity-pw-8\nProduction,was_hostname,jupiter\nalice1@jupiter,rdf:type,UnixAccount\nalice1@jupiter,hasPassword,alice-pw-2\n\u003c/pre\u003e\n\u003c/div\u003e\n\n\u003cp\u003e\nIn this example we want the data about the \u003ccode\u003ejupiter\u003c/code\u003e machine. We permute\nthe input records with awk and filter the JSON output with \u003ccode\u003ejq\u003c/code\u003e.\n\u003c/p\u003e\n\n\u003cdiv class=\"org-src-container\"\u003e\n\u003cpre class=\"src src-sg\"\u003e$ cat test/fixtures/triples.csv | \\\n  awk -F, '{print $2,$1,$3; print $1, $2, $3; print $3, $2, $1}' | \\\n  ./frangipanni  -breaks ' ' -order alpha -format json -no-fold | \\\n  jq '.\"jupiter\"'\n\u003c/pre\u003e\n\u003c/div\u003e\n\n\u003cdiv class=\"org-src-container\"\u003e\n\u003cpre class=\"src src-javascript\"\u003e{\n  \"defaultAccount\": \"alice1\",\n  \"hasUser\": [\n    \"alice1\",\n    \"birchb1\",\n    \"john1\"\n  ],\n  \"rdf:type\": [\n    \"UnixMachine\",\n    \"WasDmgr\"\n  ],\n  \"was_hostname\": \"Production\"\n}\n\u003c/pre\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\n\u003cdiv id=\"outline-container-orgde782ee\" class=\"outline-3\"\u003e\n\u003ch3 id=\"security-analysis-of-sudo-use-in-auth-log-file\"\u003e\u003cspan class=\"section-number-3\"\u003e3.5\u003c/span\u003e Security Analysis of sudo use in Auth Log File\u003c/h3\u003e\n\u003cdiv class=\"outline-text-3\" id=\"text-security-analysis-of-sudo-use-in-auth-log-file\"\u003e\n\u003cp\u003e\nThe Linux /var/log/auth.log file has timed records about \u003ccode\u003esudo\u003c/code\u003e which\nlook like this:\n\u003c/p\u003e\n\n\u003cpre class=\"example\"\u003e\nMay 17 00:36:15 localhost sudo:   alice : TTY=pts/2 ; PWD=/home/alice ; USER=root ; COMMAND=/usr/bin/jmtpfs -o allow_other /tmp/s\nMay 17 00:36:15 localhost sudo: pam_unix(sudo:session): session opened for user root by (uid=0)\nMay 17 00:36:15 localhost sudo: pam_unix(sudo:session): session closed for user root\n\u003c/pre\u003e\n\n\u003cp\u003e\nBy skipping the date/time component of the lines, and specifying\n\u003ccode\u003e-counts\u003c/code\u003e we can see a breakdown of the \u003ccode\u003esudo\u003c/code\u003e commands used and how\nmany occurred. By placing the date/time data at the end of the input\nlines we alse get a breakdown of the commands by hour of day.\n\u003c/p\u003e\n\n\u003cdiv class=\"org-src-container\"\u003e\n\u003cpre class=\"src src-sh\"\u003e$ sudo cat /var/log/auth.log | grep sudo | \\\n    awk '{print substr($0,16),substr($0,1,15)}' | \\\n    ./frangipanni -breaks ' ;:'  -depth 5 -counts -separators\n\u003c/pre\u003e\n\u003c/div\u003e\n\n\u003cp\u003e\nProduces\n\u003c/p\u003e\n\n\u003cpre class=\"example\"\u003e\nlocalhost sudo: 125\n   :   alice: 42\n        : TTY=pts/2: 14\n            ; PWD=/home/alice ; USER=root ; COMMAND=/usr/bin/jmtpfs: 5\n            ; PWD=/home/alice/workspace/gopath/src/github.com/akice/frangipanni ; USER=root ; COMMAND=/usr/bin/find /etc -maxdepth 3 May 17 13: 9\n        : TTY=pts/1 ; PWD=/home/alice/workspace/gopath/src/github.com/akice/frangipanni ; USER=root ; COMMAND=/bin/cat: 28\n            /var/log/messages May 17 13:53:34: 1\n            /var/log/auth.log May 17: 27\n   : pam_unix(sudo:session): session: 83\n        opened for user root by (uid=0) May 17: 42\n            00: 5\n            13: 28\n            14: 9\n        closed for user root May 17: 41\n            00: 5\n            13: 28\n            14: 8\n\u003c/pre\u003e\n\n\u003cp\u003e\nWe can see alice has run 42 sudo commands, 28 of whuch were =cat=ing\nfiles from /var.\n\u003c/p\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\n\u003cdiv id=\"outline-container-orgaca6240\" class=\"outline-3\"\u003e\n\u003ch3 id=\"output-for-spreadsheets\"\u003e\u003cspan class=\"section-number-3\"\u003e3.6\u003c/span\u003e Output for Spreadsheets\u003c/h3\u003e\n\u003cdiv class=\"outline-text-3\" id=\"text-output-for-spreadsheets\"\u003e\n\u003cp\u003e\nInevitably you will need to output reports from frangipanni into a\nspreadsheet. You can use the \u003ccode\u003e-spacer\u003c/code\u003e option to specify the\ncharacter(s) to use for indentation and before the counts. So with the\nfile list example from above and this command\n\u003c/p\u003e\n\n\u003cdiv class=\"org-src-container\"\u003e\n\u003cpre class=\"src src-sh\"\u003esudo find /etc -maxdepth 3 | tail -9 | frangipanni -no-fold -counts -indent 1 -spacer $'\\t'\n\u003c/pre\u003e\n\u003c/div\u003e\n\n\u003cp\u003e\nYou will have a tab-separated output which can be imported to your\nspreadsheet.\n\u003c/p\u003e\n\n\u003ctable border=\"2\" cellspacing=\"0\" cellpadding=\"6\" rules=\"groups\" frame=\"hsides\"\u003e\n\n\n\u003ccolgroup\u003e\n\u003ccol  class=\"org-left\" /\u003e\n\n\u003ccol  class=\"org-left\" /\u003e\n\n\u003ccol  class=\"org-right\" /\u003e\n\u003c/colgroup\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd class=\"org-left\"\u003eetc\u003c/td\u003e\n\u003ctd class=\"org-left\"\u003e9\u003c/td\u003e\n\u003ctd class=\"org-right\"\u003e\u0026#xa0;\u003c/td\u003e\n\u003c/tr\u003e\n\n\u003ctr\u003e\n\u003ctd class=\"org-left\"\u003ebluetooth\u003c/td\u003e\n\u003ctd class=\"org-left\"\u003e6\u003c/td\u003e\n\u003ctd class=\"org-right\"\u003e\u0026#xa0;\u003c/td\u003e\n\u003c/tr\u003e\n\n\u003ctr\u003e\n\u003ctd class=\"org-left\"\u003e\u0026#xa0;\u003c/td\u003e\n\u003ctd class=\"org-left\"\u003erfcomm.conf.dpkg-remove\u003c/td\u003e\n\u003ctd class=\"org-right\"\u003e1\u003c/td\u003e\n\u003c/tr\u003e\n\n\u003ctr\u003e\n\u003ctd class=\"org-left\"\u003e\u0026#xa0;\u003c/td\u003e\n\u003ctd class=\"org-left\"\u003eserial.conf.dpkg-remove\u003c/td\u003e\n\u003ctd class=\"org-right\"\u003e1\u003c/td\u003e\n\u003c/tr\u003e\n\n\u003ctr\u003e\n\u003ctd class=\"org-left\"\u003e\u0026#xa0;\u003c/td\u003e\n\u003ctd class=\"org-left\"\u003einput.conf\u003c/td\u003e\n\u003ctd class=\"org-right\"\u003e1\u003c/td\u003e\n\u003c/tr\u003e\n\n\u003ctr\u003e\n\u003ctd class=\"org-left\"\u003e\u0026#xa0;\u003c/td\u003e\n\u003ctd class=\"org-left\"\u003eaudio.conf.dpkg-remove\u003c/td\u003e\n\u003ctd class=\"org-right\"\u003e1\u003c/td\u003e\n\u003c/tr\u003e\n\n\u003ctr\u003e\n\u003ctd class=\"org-left\"\u003e\u0026#xa0;\u003c/td\u003e\n\u003ctd class=\"org-left\"\u003enetwork.conf\u003c/td\u003e\n\u003ctd class=\"org-right\"\u003e1\u003c/td\u003e\n\u003c/tr\u003e\n\n\u003ctr\u003e\n\u003ctd class=\"org-left\"\u003e\u0026#xa0;\u003c/td\u003e\n\u003ctd class=\"org-left\"\u003emain.conf\u003c/td\u003e\n\u003ctd class=\"org-right\"\u003e1\u003c/td\u003e\n\u003c/tr\u003e\n\n\u003ctr\u003e\n\u003ctd class=\"org-left\"\u003efish/completions/task.fish\u003c/td\u003e\n\u003ctd class=\"org-left\"\u003e3\u003c/td\u003e\n\u003ctd class=\"org-right\"\u003e\u0026#xa0;\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\n\u003cdiv id=\"outline-container-orga1bef67\" class=\"outline-3\"\u003e\n\u003ch3 id=\"output-for-markdown\"\u003e\u003cspan class=\"section-number-3\"\u003e3.7\u003c/span\u003e Output for Markdown\u003c/h3\u003e\n\u003cdiv class=\"outline-text-3\" id=\"text-output-for-markdown\"\u003e\n\u003cp\u003e\nTo use the output with markdown or other text-based tools, sepecify the\n\u003ccode\u003e-separator\u003c/code\u003e option. This can be used by tools like \u003ccode\u003esed\u003c/code\u003e to convert the\nleading separator into the markup required. example to get a leading\nminus sign for an un-numbered Markdown list, use \u003ccode\u003esed\u003c/code\u003e to\n\u003c/p\u003e\n\n\u003cdiv class=\"org-src-container\"\u003e\n\u003cpre class=\"src src-sh\"\u003esudo find /etc -maxdepth 3 | tail -9 | frangipanni -separators | sed 's;/; - ;'\n\u003c/pre\u003e\n\u003c/div\u003e\n\n\u003cp\u003e\nWhich results in an indented bullet list:\n\u003c/p\u003e\n\n\u003cblockquote\u003e\n\u003cp\u003e\n\n\u003c/p\u003e\n\n\u003cul class=\"org-ul\"\u003e\n\u003cli\u003eetc\n\n\u003cul class=\"org-ul\"\u003e\n\u003cli\u003ebluetooth\n\n\u003cul class=\"org-ul\"\u003e\n\u003cli\u003erfcomm.conf.dpkg-remove\u003c/li\u003e\n\u003cli\u003eserial.conf.dpkg-remove\u003c/li\u003e\n\u003cli\u003einput.conf\u003c/li\u003e\n\u003cli\u003eaudio.conf.dpkg-remove\u003c/li\u003e\n\u003cli\u003enetwork.conf\u003c/li\u003e\n\u003cli\u003emain.conf\u003c/li\u003e\n\u003c/ul\u003e\u003c/li\u003e\n\n\u003cli\u003efish/completions/task.fish\u003c/li\u003e\n\u003c/ul\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/blockquote\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\n\u003cdiv id=\"outline-container-orge2eaad3\" class=\"outline-3\"\u003e\n\u003ch3 id=\"lua-examples\"\u003e\u003cspan class=\"section-number-3\"\u003e3.8\u003c/span\u003e Lua Examples\u003c/h3\u003e\n\u003cdiv class=\"outline-text-3\" id=\"text-lua-examples\"\u003e\n\u003c/div\u003e\n\n\u003cdiv id=\"outline-container-org7e272dd\" class=\"outline-4\"\u003e\n\u003ch4 id=\"json-again\"\u003e\u003cspan class=\"section-number-4\"\u003e3.8.1\u003c/span\u003e JSON (again)\u003c/h4\u003e\n\u003cdiv class=\"outline-text-4\" id=\"text-json-again\"\u003e\n\u003cp\u003e\nFirst, we are going tell frangipanni to output via a Lua program called\n'json.lua', and we will format the json with the 'jp' program.\n\u003c/p\u003e\n\n\u003cdiv class=\"org-src-container\"\u003e\n\u003cpre class=\"src src-sh\"\u003e$ \u0026lt;test/fixtures/simplechars.txt frangipanni -lua json.lua | jp @\n\u003c/pre\u003e\n\u003c/div\u003e\n\n\u003cp\u003e\nThe Lua script uses the \u003ccode\u003egithub.com/layeh/gopher-json\u003c/code\u003e module which is\nimported in the Lua. The data is made available in the variable\n\u003ccode\u003efrangipanni\u003c/code\u003e which has a table for each node, with fields\n\u003c/p\u003e\n\n\u003cul class=\"org-ul\"\u003e\n\u003cli\u003edepth - in the tree starting from 0\u003c/li\u003e\n\u003cli\u003elineNumber - the token was first detected\u003c/li\u003e\n\u003cli\u003enumMatched - the number of times the token was seen\u003c/li\u003e\n\u003cli\u003esep - separation characters preceding the token\u003c/li\u003e\n\u003cli\u003etext - the token itself\u003c/li\u003e\n\u003cli\u003echildren - a table containing the child nodes\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003cdiv class=\"org-src-container\"\u003e\n\u003cpre class=\"src src-lua\"\u003elocal json = require(\"json\")\n\nprint(json.encode(frangipanni))\n\u003c/pre\u003e\n\u003c/div\u003e\n\n\u003cp\u003e\nThe output shows that all the fields of the parsed nodes are passed to\nLua in a Table. The root node is empty except for it's children. The Lua\nscript is therefore able to use the fields intelligently.\n\u003c/p\u003e\n\n\u003cdiv class=\"org-src-container\"\u003e\n\u003cpre class=\"src src-javascript\"\u003e{\n  \"depth\": 0,\n  \"lineNumber\": -1,\n  \"numMatched\": 1,\n  \"sep\": \"\",\n  \"text\": \"\",\n  \"children\": {\n    \"1.2\": {\n      \"children\": [],\n      \"depth\": 1,\n      \"lineNumber\": 8,\n      \"numMatched\": 1,\n      \"sep\": \"\",\n      \"text\": \"1.2\"\n    },\n    \"A\": {\n      \"children\": [],\n      \"depth\": 1,\n      \"lineNumber\": 1,\n      \"numMatched\": 1,\n      \"sep\": \"\",\n      \"text\": \"A\"\n    },\n\u003c/pre\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\n\u003cdiv id=\"outline-container-orgb9f5565\" class=\"outline-4\"\u003e\n\u003ch4 id=\"markdown\"\u003e\u003cspan class=\"section-number-4\"\u003e3.8.2\u003c/span\u003e Markdown\u003c/h4\u003e\n\u003cdiv class=\"outline-text-4\" id=\"text-markdown\"\u003e\n\u003cpre class=\"example\"\u003e\nfunction indent(n)\n    for i=1, n do\n        io.write(\"   \")\n    end\nend\n\nfunction markdown(node)\n    indent(node.depth)\n    io.write(\"* \")\n    print(node.text)\n    for k, v in pairs(node.children) do\n        markdown(v)\n    end\nend\n\nmarkdown(frangipanni)\n\u003c/pre\u003e\n\n\u003cp\u003e\nThe output can look like this:\n\u003c/p\u003e\n\n\u003cpre class=\"example\"\u003e\n* \n   * A\n   * C\n      * 2\n      * D\n   * x.a\n      * 2\n      * 1\n   * Z\n   * 1.2\n\u003c/pre\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\n\u003cdiv id=\"outline-container-org9f7b928\" class=\"outline-4\"\u003e\n\u003ch4 id=\"xml\"\u003e\u003cspan class=\"section-number-4\"\u003e3.8.3\u003c/span\u003e XML\u003c/h4\u003e\n\u003cdiv class=\"outline-text-4\" id=\"text-xml\"\u003e\n\u003cp\u003e\nThe xml.lua script provided in the release outputs very basic XML format\nwhich might suit simple inputs.\n\u003c/p\u003e\n\n\u003cdiv class=\"org-src-container\"\u003e\n\u003cpre class=\"src src-xml\"\u003e\u0026lt;root count=\"1\" sep=\"\"\u0026gt;\n   \u0026lt;C count=\"2\" sep=\"\"\u0026gt;\n      \u0026lt;2 count=\"1\" sep=\".\"/\u0026gt;\n      \u0026lt;D count=\"1\" sep=\".\"/\u0026gt;\n   \u0026lt;/C\u0026gt;\n   \u0026lt;x.a count=\"3\" sep=\"\"\u0026gt;\n      \u0026lt;1 count=\"1\" sep=\".\"/\u0026gt;\n      \u0026lt;2 count=\"1\" sep=\".\"/\u0026gt;\n   \u0026lt;/x.a\u0026gt;\n   \u0026lt;Z count=\"1\" sep=\"\"/\u0026gt;\n   \u0026lt;1.2 count=\"1\" sep=\"\"/\u0026gt;\n   \u0026lt;A count=\"1\" sep=\"\"/\u0026gt;\n\u0026lt;/root\u0026gt;\n\u003c/pre\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\u003cdiv id=\"postamble\" class=\"status\"\u003e\n\u003cp class=\"date\"\u003eDate: 2021-12-05\u003c/p\u003e\n\u003cp class=\"author\"\u003eAuthor: Peter Birch\u003c/p\u003e\n\u003cp class=\"date\"\u003eCreated: 2021-12-05 Sun 14:51\u003c/p\u003e\n\u003cp class=\"validation\"\u003e\u003ca href=\"http://validator.w3.org/check?uri=referer\"\u003eValidate\u003c/a\u003e\u003c/p\u003e\n\u003c/div\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbirchb1024%2Ffrangipanni","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbirchb1024%2Ffrangipanni","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbirchb1024%2Ffrangipanni/lists"}