From 93beb4bd1e4a7427f60f4d94217822c591e247df Mon Sep 17 00:00:00 2001 From: Jon Michael Aanes Date: Thu, 20 Jul 2017 12:01:26 +0200 Subject: [PATCH] Finally implemented column alignment It's currently a bit limited in scope, but looks great when active. --- README.md | 2 - analyze_structure.lua | 36 +++++++++++++----- pretty.lua | 87 ++++++++++++++++++++++++++++++++++++++++++- test/test_pretty.lua | 12 ++++++ test/test_sorting.lua | 37 +++++++----------- 5 files changed, 136 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 7b776f8..012cb00 100644 --- a/README.md +++ b/README.md @@ -101,8 +101,6 @@ printers specified below. I'm looking into implementing following features: -- Improve display of medium-long lists with short elements. One option is - something analog to the default results of `ls` on Linux. - Add support for `setmetatable`, and exploring the values accessible through it. - Provide nice formatting for `cdata` datatype in LuaJIT. diff --git a/analyze_structure.lua b/analyze_structure.lua index 1628a26..df9113e 100644 --- a/analyze_structure.lua +++ b/analyze_structure.lua @@ -36,10 +36,11 @@ local RESERVED_LUA_WORDS = { ['goto'] = true, } -local SIMPLE_VALUE_TYPES = { +local LEAF_VALUE_TYPES = { ['nil'] = true, - ['boolean'] = true, ['number'] = true, + ['string'] = true, + ['boolean'] = true, } local SHORT_STRING_MAX_LEN = 7 @@ -186,21 +187,36 @@ local function has_uniform_structure (t) return true end +local function is_leaf_node (t) + -- Predicate: Returns true if table only contains elements of type nil, + -- number, string or boolean + + assert(type(t) == 'table') + + for k,v in pairs(t) do + if not LEAF_VALUE_TYPES[type(k)] or not LEAF_VALUE_TYPES[type(v)] then + return false + end + end + return true +end + -------------------------------------------------------------------------------- local function get_table_info (t) local key_types = get_key_types(t) local info = {} - info.address = tostring(t):sub(8) - info.nr_elems = nr_elements_in_table(t) + info.address = tostring(t):sub(8) + info.nr_elems = nr_elements_in_table(t) info.seq_elems, info.has_holes = nr_elements_in_seq(t) - info.map_elems = info.nr_elems - info.seq_elems - info.has_seq = info.seq_elems > 0 - info.has_map = info.map_elems > 0 - info.is_set = is_set(t) and info.nr_elems >= MINIMUM_NUMBER_OF_SET_ELEMENTS - info.is_tabular = is_tabular(t) - info.is_uniform = has_uniform_structure(t) + info.map_elems = info.nr_elems - info.seq_elems + info.has_seq = info.seq_elems > 0 + info.has_map = info.map_elems > 0 + info.is_set = is_set(t) and info.nr_elems >= MINIMUM_NUMBER_OF_SET_ELEMENTS + info.is_tabular = is_tabular(t) + info.is_uniform = has_uniform_structure(t) + info.is_leaf_node = is_leaf_node(t) -- Determine type of table if not info.has_seq and not info.has_map then info.type = TABLE_TYPE.EMPTY diff --git a/pretty.lua b/pretty.lua index a5c9785..0098a8e 100644 --- a/pretty.lua +++ b/pretty.lua @@ -30,7 +30,7 @@ a table, we have a better idea, but then the output would be cluttered. continue to represent it as `{}`, but `{...}` would be more "honest". 4. Single-line: TODO 5. Multi-line: TODO -6. ls style: TODO +6. Format into columns: (like ls) TODO 7. Tabular: TODO 8. Special cases: (Array-tree, Table-Tree, Linked-List) TODO @@ -268,7 +268,6 @@ local function fix_alignment (l, start_i, stop_i) assert(type(start_i) == 'number') assert(type(start_i) == 'number') - -- Do stuff -- Find maximums local max = {} @@ -286,6 +285,85 @@ local function fix_alignment (l, start_i, stop_i) end end +local function attempt_to_align_into_columns (l, start_i, stop_i, nr_items_pr_row) + assert(type(l) == 'table') + assert(type(start_i) == 'number') + assert(type(stop_i) == 'number') + assert(type(nr_items_pr_row) == 'number') + + local column = {} + --- + local start_of_item_i, item_nr = nil, 0 + 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) + local column_i = (item_nr-1)%nr_items_pr_row+1 + column[column_i] = math.max(column[column_i] or 0, width_of_item) + end + start_of_item_i, item_nr = i + 1, item_nr + 1 + 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 +end + +local function align_into_columns (l, start_i, stop_i) + + -- Argument fixing and Error Checking + assert(type(l) == 'table') + + local start_i, stop_i = start_i or 1, stop_i or #l + + assert(type(start_i) == 'number') + assert(type(start_i) == 'number') + + -- Find columns + local columns = nil + for nr_items_pr_row = 10, 1, -1 do -- TODO: Do this more intelligently. + local column_width + column_width, columns = attempt_to_align_into_columns(l, start_i, stop_i, nr_items_pr_row) + if column_width <= MAX_WIDTH_FOR_SINGLE_LINE_TABLE then break end + end + + -- Change alignment of columns + local start_of_item_i, item_nr = nil, 0 + for i = start_i, stop_i do + if type(l[i]) == 'table' and l[i][1] == 'align' then + local column_i = (item_nr-1)%#columns+1 + l[i][2] = l[i][2] .. '_column_'..column_i + elseif (l[i][1] == 'indent' or l[i][1] == 'seperator' or l[i][1] == 'unindent') then + start_of_item_i, item_nr = i + 1, item_nr + 1 + end + end + + -- Fix newly changed alignment + fix_alignment(l, start_i, stop_i) + + -- Quick-exit on only a single column + if #columns == 1 then return end + + -- Fit into columns. + local start_of_item_i, item_nr = nil, 0 + 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 + if start_of_item_i and l[i][1] == 'seperator' then + local column_i = (item_nr-1)%#columns+1 + if column_i ~= #columns then + local width_of_item = width_of_strings_in_l(l, start_of_item_i, i-1) + l[i] = l[i][2] .. ' ' .. (' '):rep(columns[column_i]-width_of_item) + end + end + start_of_item_i, item_nr = i + 1, item_nr + 1 + end + end +end + local function fix_seperator_info (l, indent_char, max_depth) -- Error Checking @@ -404,6 +482,11 @@ local function format_table (t, depth, l) -- Is short table: Ignore the "width of key"-shit l[start_of_table_i][3] = 'inline' ignore_alignment_info(l, start_of_table_i) + elseif table_info.is_leaf_node then + -- Is leaf node: Can format into columns. + -- 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) else -- Is long table: Fix whitespace alignment fix_alignment(l, start_of_table_i) diff --git a/test/test_pretty.lua b/test/test_pretty.lua index 0c5f2aa..2d3acb8 100644 --- a/test/test_pretty.lua +++ b/test/test_pretty.lua @@ -316,6 +316,18 @@ format_test { expect = '{\n djævle = \'dyr?\',\n europa = \'måne\',\n øå = \'en å på en ø?\'\n}', } +format_test { + name = 'Format table into columns, if leaf node', + input = { + 'hello', 'world', 'how', + 'is', 'it', 'going?', + 'Im', 'doing great', 'thanks', + 'that', 'was', 'what', + 'I', 'were', 'expecting' + }, + expect = '{\n \'hello\', \'world\', \'how\',\n \'is\', \'it\', \'going?\',\n \'Im\', \'doing great\', \'thanks\',\n \'that\', \'was\', \'what\',\n \'I\', \'were\', \'expecting\'\n}', +} + -------------------------------------------------------------------------------- -- Table recursion diff --git a/test/test_sorting.lua b/test/test_sorting.lua index edaee38..9cddb8d 100644 --- a/test/test_sorting.lua +++ b/test/test_sorting.lua @@ -50,7 +50,7 @@ format_test { format_test { name = 'Keys should be sorted such that uppercase and lowercase lies near each other', input = { Dave = 1, dave = 2, mary = 3, Mary = 4, Adam = 5, adam = 6 }, - expect = '{\n Adam = 5,\n adam = 6,\n Dave = 1,\n dave = 2,\n Mary = 4,\n mary = 3\n}', + expect = '{\n Adam = 5, adam = 6, Dave = 1,\n dave = 2, Mary = 4, mary = 3\n}', } format_test { @@ -118,28 +118,17 @@ local EXAMPLE_1_INPUT = { } local EXAMPLE_1_OUTPUT = ([[{ - ['z1.doc'] = 1, - ['z2.doc'] = 1, - ['z3.doc'] = 1, - ['z4.doc'] = 1, - ['z5.doc'] = 1, - ['z6.doc'] = 1, - ['z7.doc'] = 1, - ['z8.doc'] = 1, - ['z9.doc'] = 1, - ['z10.doc'] = 1, - ['z11.doc'] = 1, - ['z12.doc'] = 1, - ['z13.doc'] = 1, - ['z14.doc'] = 1, - ['z15.doc'] = 1, - ['z16.doc'] = 1, - ['z17.doc'] = 1, - ['z18.doc'] = 1, - ['z19.doc'] = 1, - ['z20.doc'] = 1, - ['z100.doc'] = 1, - ['z101.doc'] = 1, + ['z1.doc'] = 1, ['z2.doc'] = 1, + ['z3.doc'] = 1, ['z4.doc'] = 1, + ['z5.doc'] = 1, ['z6.doc'] = 1, + ['z7.doc'] = 1, ['z8.doc'] = 1, + ['z9.doc'] = 1, ['z10.doc'] = 1, + ['z11.doc'] = 1, ['z12.doc'] = 1, + ['z13.doc'] = 1, ['z14.doc'] = 1, + ['z15.doc'] = 1, ['z16.doc'] = 1, + ['z17.doc'] = 1, ['z18.doc'] = 1, + ['z19.doc'] = 1, ['z20.doc'] = 1, + ['z100.doc'] = 1, ['z101.doc'] = 1, ['z102.doc'] = 1 }]]):gsub('\t', ' ') @@ -238,7 +227,7 @@ end) SUITE:addTest('alphanum algorithm extension 1', function () -- This is a test-case taken from http://www.davekoelle.com/alphanum.html - local OUTPUT = "{\n ['z2'] = 1,\n ['z2.'] = 1,\n ['z2.z'] = 1,\n ['z2.0'] = 1\n}" + local OUTPUT = "{\n ['z2'] = 1, ['z2.'] = 1,\n ['z2.z'] = 1, ['z2.0'] = 1\n}" assert_equal(OUTPUT, pretty { ['z2.z'] = 1, ['z2.0'] = 1, ['z2.'] = 1, ['z2'] = 1 }) end)