diff --git a/pretty.lua b/pretty.lua index 463c584..0d35a3b 100644 --- a/pretty.lua +++ b/pretty.lua @@ -1,205 +1,206 @@ ---- Pretty --- --- `pretty` is an advanced pretty printer for [Lua](lua.org). It's primarily a --- debugging tool, aiming for human readability, by detecting pattern in the input --- data, and creating an output string utilizing and highlighting those patterns. --- --- ## Code Example --- --- Setup is simple, use `pretty = require 'pretty'`, and you're good to go. --- --- ```lua --- > print(pretty( { 1, 2, 3 } )) --- { 1, 2, 3 } --- --- > print(pretty( { hello = 'world', num = 42 } )) --- { --- num = 42 --- hello = 'world' --- } --- --- > print(pretty( { abs = math.abs, max = math.max, some = function() end } )) --- { --- abs = builtin function (x) ... end --- max = builtin function (x, ...) ... end --- some = function () ... end --- } --- --- > print(pretty( math.abs )) --- builtin function (x) --- -- math.abs --- -- Returns the absolute value of x --- --- ... --- end --- ``` --- --- ## Motivation --- --- This project is the outcome of my frustration with existing pretty printers, and --- a desire to expand upon the pretty printer I developed for --- [Xenoterm](https://gitfub.space/takunomi/Xenoterm). The original Xenoterm pretty --- printer was much simpler than `pretty` - and the current is even simpler - but --- the enhancements I make, when compared to other pretty printers, inspired me to --- create `pretty`. --- --- `pretty` sorts it's priorities like so: --- --- 1. Human readability. --- 2. Lua-compatible output. --- 3. Customization. --- --- I'd rather have good defaults than provide a ton of customization options. If an --- structure avoids easy representation in Lua, I'd rather extend the syntax, than --- lose the info. --- --- Another aspect where `pretty` shines is in exploratory programming, when --- attempting to avoid reliance on outside documentation. The amount of information --- `pretty` exposes varies by the data you are inspecting. If you're inspecting --- a list of functions, their function signatures are visible, but if you're --- inspecting a single function, documentation and source location may appear if --- available. --- --- ## Features --- --- - Written in good-old pureblood Lua, with support for PUC Lua 5.0+ and --- LuaJIT 2.0+. --- - Redefining what it means to be "human readable": --- * Is multi-line centric, to aid readablitiy. --- * Indention and alignment of keys-value pairs. --- * Keys-value pairs are [properly](http://www.davekoelle.com/alphanum.html) --- sorted by key type and thereafter alphabetically. --- * The format and structure of output changes depending upon the input. --- Maps appear differently to deeply nested tables to long sequences --- with short strings to short lists. --- * Uses the standard `debug` library to gain information about functions --- and other advanced structures. --- --- ## Installation --- --- `pretty` is loadable directly with `require`. Either clone or download this --- repository. Where you place it, depends upon what you want to do: --- --- 1. **You want `pretty` in a specific project**: Place the `pretty` folder --- somewhere in your project, and `require` it from one of your project files. --- 2. **You want `pretty` on your system**: Place the `pretty` folder such that --- it's visible from your Lua-path. On my system this might be --- `/usr/local/share/lua/5.1/`. Now you can `require` it from anywhere. --- --- ## API Documentation --- --- `pretty` exposes a single function, the `pretty` function itself. It's function --- signature is `pretty(value, options)`. `value` can be any Lua value. `options` --- must be a table. --- --- ### List of options --- --- `pretty` is sure to complain if you give it an unknown option, or if you give an --- option a bad value. --- --- - `indent: string`: The string to indent with. Four spaces by default. --- --- ## TODO --- --- Tasks to be done before `pretty` can be called version 1.0.0, in order of --- priority: --- --- - Add a dedicated unicode submodule, to handle some minor alignment and --- character escaping issues. `pretty` should escape all malformed unicode --- sequences. --- - Align numbers towards right for tabular views. --- - Add support for `setmetatable`, and exploring values in metatables. --- - Provide nice formatting for `cdata` datatype in LuaJIT. --- - Find a better name than `pretty`. --- - Enhance internal structure some amount. See `TODO` markers in files. --- --- It would be nice to have the following, but these are secondary: --- --- - Add option for colored output. Primarily syntax highlighting, but also --- [BlueJ-style](www.bluej.org/about.html) scope highlighting, with some faint --- background colors. --- - Expand on the comment output in output, for `__tostring` methods, and global --- namespaces like `io` or `math`. --- - Fit output within a predefined width limit. Default to 80. --- - Look into tool for understanding complex structures with recursive --- definitions. Whatever modes are thought up, they should be automatic modes, --- not an options. Should at least include modes for self-referential tables --- and Directed-Acyclic-Graphs. --- --- ## Alternative pretty printers --- --- `pretty` is large, slow, and requires the debug library to work. It's not --- designed for serialization purposes, nor is it concerned with offering the same --- level of customization as other libraries do. --- --- If you want a sleek, fast, customizable or embeddable library, there are --- thankfully other options. --- --- - [inspect.lua](github.com/kikito/inspect.lua): One of the classic debugging --- pretty printers. --- - [pprint.lua](github.com/jagt/pprint.lua): Reimplementation of `inspect.lua` --- - [serpent](github.com/pkulchenko/serpent): Advanced and fast pretty printer. --- - [pluto](lua-users.org/wiki/PlutoLibrary): Can serialize arbitrary parts of --- Lua, including functions, upvalues, and proper lexical scoping. Not written --- in native Lua. --- - [binser](github.com/bakpakin/binser): Library for special purpose --- serialization. --- --- Even more are available at [the lua-users wiki](lua-users.org/wiki/TableSerialization). --- --- ## Thoughts on displaying tables in an intuitive way. --- --- Lua's table data-structure is likely to be the most concise data structure ever --- invented. (If not, please send me a link!) Lists, maps, objects, classes, --- proxies, etc. This obviously brings about it some difficulty when attempting to --- represent these tables. What do we want to highlight, and what do we choose to --- avoid? --- --- One notable issue is whether to show every key that a table answers (to lift --- some Smalltalk terms) to, or to just display those it contains. That is, do we --- think about `__index` in the table's metatable and what it returns, or do we --- ignore `__index`? For cases where `__index` is a function, we cannot say --- anything about the keys that the table answers to. If `__index` is a table, we --- have a better idea, but it would be cluttered to display both types of keys side --- by side. --- --- 1. Native representation: Lua's native representation includes the type and --- address of the table. It allows for distinguishing between unique tables, --- but won't tell us anything about the contents. --- 2. Omission: By representing tables as the pseudo-parsable `{...}`, it's --- clear we are talking about a table. We disregard the ability to --- distinguish between tables. --- 2A. If the table is empty, we could represent it as `{}`. But what if the table --- has a metatable with `__index` defined? We could continue to represent it as --- `{}`, but `{...}` would be more "honest". --- 3. Single-line: TODO --- 4. Multi-line: TODO --- 5. Columns: For some highly-regular structures, like lists of short strings, --- giving each string it's own line would be too long, but formatting them as a --- single-line list would be too cluttered. Thus we can take inspiration from --- the classic `ls` unix tool, and place the output into columns, to help guide --- the eyes. --- 6. Tabular: Other structures are formatted like actual tables of data, e.g. a --- sequence of tuples, like one would see in an SQL database. For these --- structures it's an obvious choice to align them based on the keys. --- 7. Pseudo-Tabular: Some structures are almost tabular, e.g. they are sequences --- of tuples, but some of the tuples differ in their structure. For these --- structures it's still useful to tabulate the keys that all tuples share. To --- do this we should sort the key order descending by the number of tuples with --- the key. --- But what do we do about the the outlier keys? We can either justify the --- entire table, and give specific spots for the outlier keys, thereby --- significantly increasing the size of the table, or we can leave the table --- unjustified, abandoning it's eye-guiding attributes. --- 8. Special cases: (Array-tree, Table-Tree, Linked-List, Predictive Sequences) TODO +--[[-- + +# Pretty + +`pretty` is an advanced pretty printer for [Lua](https://lua.org). It's primarily a +debugging tool, aiming for human readability, by detecting pattern in the input +data, and creating an output string utilizing and highlighting those patterns. + +## Code Example + +Setup is simple, use `pretty = require 'pretty'`, and you're good to go. + +```lua +$ print(pretty( { 1, 2, 3 } )) +{ 1, 2, 3 } + +> print(pretty( { hello = 'world', num = 42 } )) +{ + num = 42 + hello = 'world' +} + +> print(pretty( { abs = math.abs, max = math.max, some = function() end } )) +{ + abs = builtin function (x) ... end + max = builtin function (x, ...) ... end + some = function () ... end +} + +> print(pretty( math.abs )) +builtin function (x) + -- math.abs + -- Returns the absolute value of x + + ... +end +``` + +## Motivation + +This project is the outcome of my frustration with existing pretty printers, and +a desire to expand upon the pretty printer I developed for +[Xenoterm](https://gitfub.space/takunomi/Xenoterm). The original Xenoterm pretty +printer was much simpler than `pretty` - and the current is even simpler - but +the enhancements I make, when compared to other pretty printers, inspired me to +create `pretty`. + +`pretty` sorts it's priorities like so: + +1. Human readability. +2. Lua-compatible output. +3. Customization. + +I'd rather have good defaults than provide a ton of customization options. If an +structure avoids easy representation in Lua, I'd rather extend the syntax, than +lose the info. + +Another aspect where `pretty` shines is in exploratory programming, when +attempting to avoid reliance on outside documentation. The amount of information +`pretty` exposes varies by the data you are inspecting. If you're inspecting +a list of functions, their function signatures are visible, but if you're +inspecting a single function, documentation and source location may appear if +available. + +## Features + +- Written in good-old pure-blood Lua, with support for PUC Lua 5.0+ and LuaJIT 2.0+. +- Redefining what it means to be "human readable": + * Is multi-line centric, to aid readability. + * Indention and alignment of keys-value pairs. + * Keys-value pairs are + [alpha-numerically](http://www.davekoelle.com/alphanum.html) sorted by key + type and thereafter alphabetically. + * The format and structure of output changes depending upon the input. Maps + appear differently to deeply nested tables to long sequences with short + strings to short lists. + * Uses the standard `debug` library to gain information about functions and + other advanced structures. + +## Installation + +`pretty` is loadable directly with `require`. Either clone or download this +repository. Where you place it, depends upon what you want to do: + +1. **You want `pretty` in a specific project**: Place the `pretty` folder +somewhere in your project, and `require` it from one of your project files. +2. **You want `pretty` on your system**: Place the `pretty` folder such that +it's visible from your Lua-path. On my system this might be +`/usr/local/share/lua/5.1/`. Now you can `require` it from anywhere. + +## API Documentation + +`pretty` exposes a single function, the `pretty` function itself. It's function +signature is `pretty(value, options)`. `value` can be any Lua value. `options` +must be a table. + +### List of options + +`pretty` is sure to complain if you give it an unknown option, or if you give an +option a bad value. + +- `indent: string`: The string to indent with. Four spaces by default. + +## TODO + +Tasks to be done before `pretty` can be called version 1.0.0, in order of +priority: + +- [ ] Add dedicated unicode submodule, to handle some minor alignment and +character escaping issues. `pretty` should escape all malformed unicode +sequences. +- [ ] Align numbers towards right for tabular views. +- [ ] Add support for `setmetatable`, and exploring values in metatables. +- [ ] Provide nice formatting for `cdata` datatype in LuaJIT. +- [ ] Find a better name than `pretty`. +- [ ] Enhance internal structure some amount. See `TODO` markers in files. + +It would be nice to have the following, but these are secondary: + +- [ ] Add option for colored output. Primarily syntax highlighting, but also +[BlueJ-style](www.bluej.org/about.html) scope highlighting, with some faint +background colors. +- [ ] Expand on the comment output in output, for `__tostring` methods, and +global namespaces like `io` or `math`. +- [ ] Fit output within a predefined width limit. Default to 80. +- [ ] Look into tool for understanding complex structures with recursive +definitions. Whatever modes are thought up, they should be automatic modes, not +an options. Should at least include modes for self-referential tables and +Directed-Acyclic-Graphs. + +## Alternative pretty printers + +`pretty` is large, slow, and requires the debug library to work. It's not +designed for serialization purposes, nor is it concerned with offering the same +level of customization as other libraries do. + +If you want a sleek, fast, customizable or embeddable library, there are +thankfully other options. + +- [inspect.lua](github.com/kikito/inspect.lua): One of the classic debugging pretty printers. +- [pprint.lua](github.com/jagt/pprint.lua): Reimplementation of `inspect.lua` +- [serpent](github.com/pkulchenko/serpent): Advanced and fast pretty printer. +- [pluto](lua-users.org/wiki/PlutoLibrary): Can serialize arbitrary parts of +Lua, including functions, upvalues, and proper lexical scoping. Not written in +native Lua. +- [binser](github.com/bakpakin/binser): Library for special purpose serialization. + +Even more are available at [the lua-users wiki](lua-users.org/wiki/TableSerialization). + +## Thoughts on displaying tables in an intuitive way. + +Lua's table data-structure is likely to be the most concise data structure ever +invented. (If not, please send me a link!) Lists, maps, objects, classes, +proxies, etc. This obviously brings about it some difficulty when attempting to +represent these tables. What do we want to highlight, and what do we choose to +avoid? + +One notable issue is whether to show every key that a table answers (to lift +some Smalltalk terms) to, or to just display those it contains. That is, do we +think about `__index` in the table's metatable and what it returns, or do we +ignore `__index`? For cases where `__index` is a function, we cannot say +anything about the keys that the table answers to. If `__index` is a table, we +have a better idea, but it would be cluttered to display both types of keys side +by side. + +1. Native representation: Lua's native representation includes the type and +address of the table. It allows for distinguishing between unique tables, +but won't tell us anything about the contents. +2. Omission: By representing tables as the pseudo-parsable `{...}`, it's +clear we are talking about a table. We disregard the ability to +distinguish between tables. +2. If the table is empty, we could represent it as `{}`. But what if the table +has a metatable with `__index` defined? We could continue to represent it as +`{}`, but `{...}` would be more "honest". +3. Single-line: TODO +4. Multi-line: TODO +5. Columns: For some highly-regular structures, like lists of short strings, +giving each string it's own line would be too long, but formatting them as a +single-line list would be too cluttered. Thus we can take inspiration from +the classic `ls` unix tool, and place the output into columns, to help guide +the eyes. +6. Tabular: Other structures are formatted like actual tables of data, e.g. a +sequence of tuples, like one would see in an SQL database. For these +structures it's an obvious choice to align them based on the keys. +7. Pseudo-Tabular: Some structures are almost tabular, e.g. they are sequences +of tuples, but some of the tuples differ in their structure. For these +structures it's still useful to tabulate the keys that all tuples share. To +do this we should sort the key order descending by the number of tuples with +the key. +But what do we do about the the outlier keys? We can either justify the +entire table, and give specific spots for the outlier keys, thereby +significantly increasing the size of the table, or we can leave the table +unjustified, abandoning it's eye-guiding attributes. +8. Special cases: (Array-tree, Table-Tree, Linked-List, Predictive Sequences) TODO +--]] -------------------------------------------------------------------------------- -- Import files local import do - local thispath = ... and select('1', ...):match('.+%.') or '' - import = function (name, ignore_failure) return require(thispath..name) end + local this_path = ... and select('1', ...):match('.+%.') or '' + import = function (name, ignore_failure) return require(this_path..name) end end -------------------------------------------------------------------------------- @@ -421,10 +422,10 @@ local function attempt_to_align_into_columns (l, start_i, stop_i, nr_items_pr_ro assert(type(stop_i) == 'number') assert(type(nr_items_pr_row) == 'number') - local column = {} + local column = {} --- local start_of_item_i, item_nr = nil, 0 - for i = start_i, stop_i do + for i = start_i, stop_i do if type(l[i]) == 'table' and (l[i][1] == 'indent' or l[i][1] == 'seperator' or l[i][1] == 'unindent') then if start_of_item_i then local width_of_item = width_of_strings_in_l(l, start_of_item_i, i-1) @@ -433,12 +434,12 @@ local function attempt_to_align_into_columns (l, start_i, stop_i, nr_items_pr_ro end start_of_item_i, item_nr = i + 1, item_nr + 1 end - end + end --- local width = nr_items_pr_row * 2 - 1 -- FIXME: Magic numbers: 2 = #', ', 1 = #' ' for i = 1, #column do width = width + column[i] end -- - return width, column + return width, column end local function align_into_columns (l, start_i, stop_i) @@ -448,8 +449,8 @@ local function align_into_columns (l, start_i, stop_i) local start_i, stop_i = start_i or 1, stop_i or #l - assert(type(start_i) == 'number') - assert(type(stop_i) == 'number') + assert(type(start_i) == 'number') + assert(type(stop_i) == 'number') insert_alignment_estimations(l, start_i, stop_i) @@ -480,7 +481,7 @@ local function align_into_columns (l, start_i, stop_i) -- Fit into columns. local start_of_item_i, item_nr = nil, 0 - for i = start_i, stop_i do + for i = start_i, stop_i do if type(l[i]) ~= 'table' then -- Do nothing elseif (l[i][1] == 'indent' or l[i][1] == 'seperator' or l[i][1] == 'unindent') then @@ -493,47 +494,47 @@ local function align_into_columns (l, start_i, stop_i) end start_of_item_i, item_nr = i + 1, item_nr + 1 end - end + end end local function align_into_tabular_style (l, start_i, stop_i) - -- Adds alignment after separators, to create nicely aligned tabular-format. + -- Adds alignment after separators, to create nicely aligned tabular-format. -- Argument fixing and Error Checking - local start_i, stop_i = start_i or 1, stop_i or #l + local start_i, stop_i = start_i or 1, stop_i or #l - assert(type(l) == 'table') - assert(type(start_i) == 'number') - assert(type(stop_i) == 'number') - assert(type(l[start_i]) == 'table' and l[start_i][1] == 'indent') - assert(type(l[stop_i]) == 'table' and l[stop_i][1] == 'unindent') + assert(type(l) == 'table') + assert(type(start_i) == 'number') + assert(type(stop_i) == 'number') + assert(type(l[start_i]) == 'table' and l[start_i][1] == 'indent') + assert(type(l[stop_i]) == 'table' and l[stop_i][1] == 'unindent') - -- Calculate where to insert new alignment. - local indent, key_nr, index_of_last_meta, insert_later = 0, 0, 1, {} - for i = start_i + 1, stop_i - 1 do - if type(l[i]) ~= 'table' then - -- Do nothing - elseif l[i][1] == 'indent' then - indent = indent + 1 - if indent == 1 then key_nr = 1 end - index_of_last_meta = i - elseif l[i][1] == 'unindent' then - insert_later[#insert_later+1] = {'align', 'end_subtable_'..key_nr, width_of_strings_in_l(l, index_of_last_meta+1, i), i} - index_of_last_meta, key_nr = i, key_nr + 1 - indent = indent - 1 - elseif l[i][1] == 'seperator' and indent ~= 0 then - insert_later[#insert_later+1] = {'align', 'key_'..key_nr, width_of_strings_in_l(l, index_of_last_meta+1, i), i+1} - index_of_last_meta, key_nr = i, key_nr + 1 - end + -- Calculate where to insert new alignment. + local indent, key_nr, index_of_last_meta, insert_later = 0, 0, 1, {} + for i = start_i + 1, stop_i - 1 do + if type(l[i]) ~= 'table' then + -- Do nothing + elseif l[i][1] == 'indent' then + indent = indent + 1 + if indent == 1 then key_nr = 1 end + index_of_last_meta = i + elseif l[i][1] == 'unindent' then + insert_later[#insert_later+1] = {'align', 'end_subtable_'..key_nr, width_of_strings_in_l(l, index_of_last_meta+1, i), i} + index_of_last_meta, key_nr = i, key_nr + 1 + indent = indent - 1 + elseif l[i][1] == 'seperator' and indent ~= 0 then + insert_later[#insert_later+1] = {'align', 'key_'..key_nr, width_of_strings_in_l(l, index_of_last_meta+1, i), i+1} + index_of_last_meta, key_nr = i, key_nr + 1 + end end - -- Insert new alignment. - for i = #insert_later, 1, -1 do - local dat = insert_later[i] - table.insert(l, dat[#dat], dat) - dat[#dat] = nil - end - -- Fix that alignemnt - return fix_alignment(l, start_i) + -- Insert new alignment. + for i = #insert_later, 1, -1 do + local dat = insert_later[i] + table.insert(l, dat[#dat], dat) + dat[#dat] = nil + end + -- Fix that alignemnt + return fix_alignment(l, start_i) end local function fix_seperator_info (l, indent_char) @@ -562,9 +563,9 @@ end -------------------------------------------------------------------------------- -local analyze_structure = import 'analyze_structure' -local TABLE_TYPE = import 'common' . TABLE_TYPE -local DISPLAY = import 'common' . DISPLAY +local analyze_structure = import 'analyze_structure' +local TABLE_TYPE = import 'common' . TABLE_TYPE +local DISPLAY = import 'common' . DISPLAY -------------------------------------------------------------------------------- -- Key-value pair formatting. @@ -657,8 +658,8 @@ local function format_table (t, display, l, format_value) -- NOTE: Currently we only allow leaf-nodes to format into columns, due -- to issues with table alignment. align_into_columns(l, start_of_table_i) - elseif table_info.is_tabular then - align_into_tabular_style(l, start_of_table_i, #l) + elseif table_info.is_tabular then + align_into_tabular_style(l, start_of_table_i, #l) else -- Is long table: Fix whitespace alignment fix_alignment(l, start_of_table_i)