Created
July 6, 2020 05:02
-
-
Save wakproductions/6dc8ec8d9e6a84abbdb4a1e863eeffa0 to your computer and use it in GitHub Desktop.
Prettify Hash
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module IndentUtil | |
INDENT_SIZE = 2 | |
def indent_chars(indent_level) | |
' ' * INDENT_SIZE * indent_level | |
end | |
end | |
class PrettifyObject | |
MAX_LINE_SIZE = 120 | |
def self.call(object, **options) | |
new.call(object, **options) | |
end | |
def call(_object, **options) | |
@current_indent = options[:indent] || 0 | |
end | |
private | |
attr_reader :current_indent | |
# TODO - for class type items, allow passing in of "Prettify_" processing classes for special handling. | |
def stringify_value(value, **args) | |
case value | |
when String | |
(value =~ /'/).present? ? %{"#{value}"} : "'#{value}'" | |
when BigDecimal | |
"#{value}.to_d" | |
when Hash | |
PrettifyHash.call(value, indent: current_indent + 1, preceding_chars: args[:preceding_chars]) | |
when Array | |
PrettifyArray.call(value, indent: current_indent + 1, preceding_chars: args[:preceding_chars]) | |
when nil | |
'nil' | |
else | |
value.to_s | |
end | |
end | |
end | |
class PrettifyArray < PrettifyObject | |
include IndentUtil | |
def call(array, indent: 0, preceding_chars: '', force_wrap: false) | |
super | |
return stringify_array_as_wrapped_lines(array, indent) if force_wrap | |
as_one_line = stringify_array_as_one_line(array) | |
if string_too_long?(preceding_chars + as_one_line) | |
stringify_array_as_wrapped_lines(array, indent) | |
else | |
as_one_line | |
end | |
end | |
private | |
def stringify_array_as_one_line(array) | |
string_values = | |
array.map do |v| | |
stringify_array_value(v) | |
end | |
.join(', ') | |
"[#{string_values}]" | |
end | |
def stringify_array_value(value, indent_chars='') | |
preceding_chars = "#{indent_chars}" | |
"#{preceding_chars}#{stringify_value(value, preceding_chars: preceding_chars).strip}" | |
end | |
def stringify_array_as_wrapped_lines(array, indent) | |
base_indent = indent_chars(indent) | |
additional_indent = indent_chars(indent + 1) | |
string_values = | |
array.map do |v| | |
stringify_array_value(v, additional_indent) | |
end | |
.join(",\n") | |
"#{base_indent}[\n" \ | |
"#{string_values}\n" \ | |
"#{base_indent}]" | |
end | |
def string_too_long?(string) | |
string.size > MAX_LINE_SIZE | |
end | |
end | |
class PrettifyHash < PrettifyObject | |
include IndentUtil | |
def call(hash, indent: 0, preceding_chars: '', force_wrap: false) | |
super | |
return stringify_hash_as_wrapped_lines(hash, indent) if force_wrap | |
as_one_line = stringify_hash_as_one_line(hash) | |
if string_too_long?(preceding_chars + as_one_line) | |
stringify_hash_as_wrapped_lines(hash, indent) | |
else | |
as_one_line | |
end | |
end | |
private | |
def stringify_hash_as_one_line(hash) | |
string_values = | |
hash.map do |k, v| | |
stringify_hash_key_value_pair(k, v) | |
end | |
.join(', ') | |
"{ #{string_values} }" | |
end | |
def stringify_hash_key_value_pair(key, value, indent_chars='') | |
preceding_chars = "#{indent_chars}#{key}: " | |
"#{indent_chars}#{key}: #{stringify_value(value, preceding_chars: preceding_chars).strip}" | |
end | |
def stringify_hash_as_wrapped_lines(hash, indent) | |
base_indent = indent_chars(indent) | |
additional_indent = indent_chars(indent + 1) | |
string_values = | |
hash.map do |k, v| | |
stringify_hash_key_value_pair(k, v, additional_indent) | |
end | |
.join(",\n") | |
"#{base_indent}{\n" \ | |
"#{string_values}\n" \ | |
"#{base_indent}}" | |
end | |
def string_too_long?(string) | |
string.size > MAX_LINE_SIZE | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'rails_helper' | |
describe PrettifyHash do | |
subject { described_class.call(input) } | |
context 'single line hash' do | |
let(:input) do | |
{ a_hash_key: 'first value', b_hash_key: 'second value', c_hash_key: 333 } | |
end | |
let(:expected_output) do | |
%({ a_hash_key: 'first value', b_hash_key: 'second value', c_hash_key: 333 }) | |
end | |
it { is_expected.to eql(expected_output) } | |
end | |
context 'single line hash, but force first line to wrap' do | |
subject { described_class.call(input, force_wrap: true) } | |
let(:input) do | |
{ a_hash_key: 'first value', b_hash_key: 'second value', c_hash_key: 333 } | |
end | |
let(:expected_output) do | |
<<~TXT.strip | |
{ | |
a_hash_key: 'first value', | |
b_hash_key: 'second value', | |
c_hash_key: 333 | |
} | |
TXT | |
end | |
it { is_expected.to eql(expected_output) } | |
context 'indents 1 unit' do | |
subject { described_class.call(input, indent: 1, force_wrap: true) } | |
let(:expected_output) do | |
%( {\n) + | |
%( a_hash_key: 'first value',\n) + | |
%( b_hash_key: 'second value',\n) + | |
%( c_hash_key: 333\n) + | |
%( }) | |
end | |
it { is_expected.to eql(expected_output) } | |
end | |
context 'indents 2 units' do | |
subject { described_class.call(input, indent: 2, force_wrap: true) } | |
let(:expected_output) do | |
%( {\n) + | |
%( a_hash_key: 'first value',\n) + | |
%( b_hash_key: 'second value',\n) + | |
%( c_hash_key: 333\n) + | |
%( }) | |
end | |
it { is_expected.to eql(expected_output) } | |
end | |
end | |
context 'will automatically wrap when single line exceeds 120 characters' do | |
subject { described_class.call(input) } | |
let(:input) do | |
{ | |
a_hash_key: 'first value', | |
b_hash_key: 'second value', | |
c_hash_key: 333, | |
d_hash_key: 4_444_555, | |
e_hash_key: 'this is a really really really long string', | |
f_hash_key: 'this is another really really long string' | |
} | |
end | |
let(:expected_output) do | |
<<~TXT.strip | |
{ | |
a_hash_key: 'first value', | |
b_hash_key: 'second value', | |
c_hash_key: 333, | |
d_hash_key: 4444555, | |
e_hash_key: 'this is a really really really long string', | |
f_hash_key: 'this is another really really long string' | |
} | |
TXT | |
end | |
it { is_expected.to eql(expected_output) } | |
context 'with 1 level of nesting' do | |
let(:input) do | |
{ | |
a_hash_key: 'first value', | |
b_hash_key: 'second value', | |
c_hash_key: 333, | |
d_hash_key: 4_444_555, | |
e_hash_key: 'this is a really really really long string', | |
f_hash_key: 'this is another really really long string', | |
g_hash_key: { | |
nested_hash_key_1: 'this', | |
nested_hash_key_2: 'is', | |
nested_hash_key_3: 'going', | |
nested_hash_key_4: 'to', | |
nested_hash_key_5: 'have', | |
nested_hash_key_6: 'lots', | |
nested_hash_key_7: 'of', | |
nested_hash_key_8: 'wrap' | |
}, | |
h_hash_key: { nowrap: "this won't wrap" } | |
} | |
end | |
let(:expected_output) do | |
<<~TXT.strip | |
{ | |
a_hash_key: 'first value', | |
b_hash_key: 'second value', | |
c_hash_key: 333, | |
d_hash_key: 4444555, | |
e_hash_key: 'this is a really really really long string', | |
f_hash_key: 'this is another really really long string', | |
g_hash_key: { | |
nested_hash_key_1: 'this', | |
nested_hash_key_2: 'is', | |
nested_hash_key_3: 'going', | |
nested_hash_key_4: 'to', | |
nested_hash_key_5: 'have', | |
nested_hash_key_6: 'lots', | |
nested_hash_key_7: 'of', | |
nested_hash_key_8: 'wrap' | |
}, | |
h_hash_key: { nowrap: "this won't wrap" } | |
} | |
TXT | |
end | |
it { is_expected.to eql(expected_output) } | |
context 'with nested value is < 120 chars, but exceeds with indent' do | |
let(:input) do | |
{ | |
a_hash_key: 'first value', | |
b_hash_key: 'second value', | |
long_hash_key: { n_hash_key_1: 'this', n_hash_key_2: 'text', nested_hash_key_2: 'should', nested_hash_key_3: 'wrap..' }, | |
h_hash_key: { nowrap: "this won't wrap because the total string length including indent is equal to 120 characters." } | |
} | |
end | |
let(:expected_output) do | |
<<~TXT.strip | |
{ | |
a_hash_key: 'first value', | |
b_hash_key: 'second value', | |
long_hash_key: { | |
n_hash_key_1: 'this', | |
n_hash_key_2: 'text', | |
nested_hash_key_2: 'should', | |
nested_hash_key_3: 'wrap..' | |
}, | |
h_hash_key: { nowrap: "this won't wrap because the total string length including indent is equal to 120 characters." } | |
} | |
TXT | |
end | |
it { is_expected.to eql(expected_output) } | |
end | |
end | |
context 'with 2 levels of nesting' do | |
let(:input) do | |
{ | |
a_hash_key: 'first value', | |
b_hash_key: 'second value', | |
c_hash_key: 333, | |
d_hash_key: 4_444_555, | |
e_hash_key: 'this is a really really really long string', | |
f_hash_key: 'this is another really really long string', | |
g_hash_key: { | |
nested_hash_key_1: 'this', | |
nested_hash_key_2: 'is', | |
nested_hash_key_3: 'going', | |
nested_hash_key_4: 'to', | |
nested_hash_key_5: 'have', | |
nested_hash_key_6: 'lots', | |
nested_hash_key_7: 'of', | |
nested_hash_key_8: 'wrap', | |
nested_hash_key_really_long: { | |
nested2_key_1: 'this', | |
nested2_key_2: 'is', | |
nested2_key_3: 'another', | |
nested2_key_4: 'long', | |
nested2_key_5: 'nested', | |
nested2_key_6: 'hash', | |
}, | |
array_hash_key_oneline: [ | |
{ array_hash_key_1: 'just numbers', array_hash_key_2: 3456 }, | |
'this is a string value', | |
], | |
array_hash_key_multiline: [ | |
{ array_hash_key_1: 'this one has a long string', array_hash_key_2: 555555 }, | |
"it's going to be an array going multiple lines", | |
6545645 | |
] | |
}, | |
h_hash_key: { nowrap: "this won't wrap" } | |
} | |
end | |
let(:expected_output) do | |
<<~TXT.strip | |
{ | |
a_hash_key: 'first value', | |
b_hash_key: 'second value', | |
c_hash_key: 333, | |
d_hash_key: 4444555, | |
e_hash_key: 'this is a really really really long string', | |
f_hash_key: 'this is another really really long string', | |
g_hash_key: { | |
nested_hash_key_1: 'this', | |
nested_hash_key_2: 'is', | |
nested_hash_key_3: 'going', | |
nested_hash_key_4: 'to', | |
nested_hash_key_5: 'have', | |
nested_hash_key_6: 'lots', | |
nested_hash_key_7: 'of', | |
nested_hash_key_8: 'wrap', | |
nested_hash_key_really_long: { | |
nested2_key_1: 'this', | |
nested2_key_2: 'is', | |
nested2_key_3: 'another', | |
nested2_key_4: 'long', | |
nested2_key_5: 'nested', | |
nested2_key_6: 'hash' | |
}, | |
array_hash_key_oneline: [{ array_hash_key_1: 'just numbers', array_hash_key_2: 3456 }, 'this is a string value'], | |
array_hash_key_multiline: [ | |
{ array_hash_key_1: 'this one has a long string', array_hash_key_2: 555555 }, | |
"it's going to be an array going multiple lines", | |
6545645 | |
] | |
}, | |
h_hash_key: { nowrap: "this won't wrap" } | |
} | |
TXT | |
end | |
it { is_expected.to eql(expected_output) } | |
end | |
end | |
context '1 level nesting' do | |
let(:input) do | |
{ | |
car: { material_cost: 1.0, labor_cost: 2.0, profit_cost: 3.0 }, | |
truck: { material_cost: 1.0, labor_cost: 2.0, profit_cost: 3.0 }, | |
suv: { material_cost: 1.0, labor_cost: 2.0, profit_cost: 3.0 } | |
} | |
end | |
let(:expected_output) do | |
<<~TXT.strip | |
{ | |
car: { material_cost: 1.0, labor_cost: 2.0, profit_cost: 3.0 }, | |
truck: { material_cost: 1.0, labor_cost: 2.0, profit_cost: 3.0 }, | |
suv: { material_cost: 1.0, labor_cost: 2.0, profit_cost: 3.0 } | |
} | |
TXT | |
end | |
it { is_expected.to eql(expected_output) } | |
end | |
context 'multiple levels nesting' do | |
let(:input) do | |
{ | |
car: { | |
material_cost: 1.0, labor_cost: 2.0, profit_cost: 3.0, special_cost: 4.0, additional_cost: 5.0, | |
nested_cost: { | |
nested_level_2: true, | |
property_detail_level_2: BigDecimal('12345.6789'), | |
long_text_property: 'this is a text string that should wrap onto a new line' | |
}, | |
utility_cost: 6.0, | |
compliance_cost: 7.0 | |
}, | |
truck: { material_cost: 1.0, labor_cost: 2.0, profit_cost: 3.0 }, | |
suv: { material_cost: 1.0, labor_cost: 2.0, profit_cost: 3.0 } | |
} | |
end | |
let(:expected_output) do | |
<<~TXT.strip | |
{ | |
car: { | |
material_cost: 1.0, | |
labor_cost: 2.0, | |
profit_cost: 3.0, | |
special_cost: 4.0, | |
additional_cost: 5.0, | |
nested_cost: { | |
nested_level_2: true, | |
property_detail_level_2: 12345.6789.to_d, | |
long_text_property: 'this is a text string that should wrap onto a new line' | |
}, | |
utility_cost: 6.0, | |
compliance_cost: 7.0 | |
}, | |
truck: { material_cost: 1.0, labor_cost: 2.0, profit_cost: 3.0 }, | |
suv: { material_cost: 1.0, labor_cost: 2.0, profit_cost: 3.0 } | |
} | |
TXT | |
end | |
it { is_expected.to eql(expected_output) } | |
end | |
end |
@TSMMark I'm glad you found this useful! I don't have time to get to it right away. If you wanna package it, go ahead. I'd be interested in seeing the repo. The way I use it right now is I put it in a file in a gitignored directory, and when I want to use it in Rails console, I call require 'gitignore/prettify_hash.rb
and then it makes the class available.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Could you publish this as a gem?