Last active
February 21, 2017 01:15
-
-
Save elia/435d1b7e7d840035d53b91d6583cdee3 to your computer and use it in GitHub Desktop.
opal -I. main.rb
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 InitConf | |
# how to add a new parameter | |
# 1. set the default here | |
# 2. maintain neatjson options to get it sorted here as well | |
# 3. update controller_command_defnitions to provide the add / edit commands | |
# 4. update conf_doc_source.rb to provide the documentation and help | |
# 5. update config-form.rb to attach a type | |
# 6. update user-interface.js to add the menu entries | |
def self.init_conf() | |
result = | |
{produce: [0], | |
abc_parser: 'ABC2SVG', | |
restposition: {default: :center, repeatstart: :next, repeatend: :default}, | |
wrap: 60, | |
# here are values for object which occur multiple times | |
# such that there i no expolicit default in the configuration | |
defaults: { | |
notebound: {annotation: {pos: [5, -7]}, | |
partname: {pos: [-4, -7]}, | |
variantend: {pos: [-4, -7]}, | |
tuplet: { | |
cp1: [5, 2], # first control point positive x: point is east of flowline, positive y: point is south of note | |
cp2: [5, -2], # second control point | |
shape: ['c'], # 'c' | 'l' => curve | line | |
show: true | |
} | |
} | |
}, | |
# this is used to upddate / create new objects | |
templates: { | |
notes: {"pos" => [320, 6], "text" => "ENTER_NOTE", "style" => "large"}, # Seitenbeschriftung | |
lyrics: {verses: [1], pos: [350, 70]}, | |
tuplet: {cp1: [5, 2], cp2: [5, -2], shape: ['c'], show: true}, | |
annotations: {text: "_vorlage_", pos: [-5, -6]} # Notenbeschriftungsvorlage | |
}, | |
# this is used to populate a QuickSettings menu | |
# in configuration editor | |
# needs to be handled in contrller_command_definiitions | |
presets: { | |
layout: { | |
layout_compact: { | |
LINE_MEDIUM: 0.2, | |
LINE_THICK: 0.3, | |
# all numbers in mm | |
ELLIPSE_SIZE: [3.5, 1.3], # radii of the largest Ellipse | |
REST_SIZE: [4, 1.5] | |
}, | |
layout_large: { | |
LINE_MEDIUM: 0.3, | |
LINE_THICK: 0.7, | |
ELLIPSE_SIZE: [4, 2], # radii of the largest Ellipse | |
REST_SIZE: [4, 2] | |
} | |
}, | |
notes: { | |
T01_number: { | |
value: { | |
pos: [394, 17], | |
text: "XXX-999-X", | |
style: "bold" | |
}}, | |
T01_number_extract: { | |
value: { | |
pos: [411, 17], | |
text: "-X", | |
style: "bold" | |
}}, | |
T01_number_extract_value: { | |
key: :T01_number_extract, | |
value: { | |
text: "-X", | |
}}, | |
T02_copyright_music: {value: {pos: [372, 227], text: " ", style: "small"}}, | |
T03_copyright_harpnotes: { | |
value: { | |
pos: [344, 208], | |
text: " ", | |
style: "small" | |
}}, | |
T04_to_order: { | |
value: { | |
pos: [369, 224], | |
text: "", | |
style: "small" | |
}}, | |
T99_do_not_copy: { | |
value: { | |
pos: [380, 284], | |
text: "Bitte nicht kopieren", | |
style: "small_bold" | |
}} | |
}, | |
printer: { | |
printer_left: { | |
printer: { | |
a3_offset: [-10, 0], | |
a4_offset: [-5, 0], | |
show_border: false | |
}, | |
layout: {limit_a3: false} | |
}, | |
printer_centric: { | |
printer: { | |
a3_offset: [0, 0], | |
a4_offset: [5, 0], | |
show_border: false | |
}, | |
layout: {limit_a3: true} | |
}, | |
printer_right: { | |
printer: { | |
a3_offset: [10, 0], | |
a4_offset: [5, 0], | |
show_border: false | |
}, | |
layout: {limit_a3: false} | |
} | |
} | |
}, | |
# these are the builtin notebound annotations | |
annotations: { | |
vl: {text: "v", pos: [-5, -5]}, | |
vt: {text: "v", pos: [2, -5]}, | |
vr: {text: "v", pos: [-1, -5]} | |
}, # default for note based annotations | |
extract: { | |
"0" => { | |
title: "alle Stimmen", | |
filenamepart: 'alle-stimmen', | |
startpos: 15, | |
voices: [1, 2, 3, 4], | |
synchlines: [[1, 2], [3, 4]], | |
flowlines: [1, 3], | |
subflowlines: [2, 4], | |
jumplines: [1, 3], | |
repeatsigns: {voices: [], | |
left: {pos: [-7, -2], text: '|:', style: :bold}, | |
right: {pos: [5, -2], text: ':|', style: :bold} | |
}, | |
layoutlines: [1, 2, 3, 4], | |
legend: {spos: [320, 27], pos: [320, 20]}, | |
lyrics: {}, | |
# | |
# this denotes the layout parameters which are intended to bne configured | |
# by the regular user | |
layout: {limit_a3: true, | |
LINE_THIN: 0.1, | |
LINE_MEDIUM: 0.3, | |
LINE_THICK: 0.5, | |
# all numbers in mm | |
ELLIPSE_SIZE: [3.5, 1.7], # radii of the largest Ellipse | |
REST_SIZE: [4, 2], | |
packer: { | |
pack_method: 0, | |
pack_max_spreadfactor: 2, | |
pack_min_increment: 0.2 | |
}, | |
}, | |
nonflowrest: false, | |
notes: {}, | |
barnumbers: { | |
voices: [], | |
pos: [6, -4], | |
autopos: false, | |
style: "small_bold", | |
prefix: "" | |
}, | |
countnotes: {voices: [], pos: [3, -2], autopos: false, style: "smaller"}, | |
stringnames: { | |
text: "G G# A A# B C C# D D# E F F# G G# A A# B C C# D D# E F F# G G# A A# B C C# D D# E F F# G", | |
vpos: [], | |
style: :small, | |
marks: {vpos: [11], hpos: [43, 55, 79]} | |
}, | |
printer: { | |
a3_offset: [0, 0], | |
a4_offset: [-5, 0], | |
show_border: true | |
} | |
}, | |
"1" => { | |
title: "Sopran, Alt", | |
filenamepart: 'sopran-alt', | |
voices: [1, 2] | |
}, | |
"2" => { | |
title: "Tenor, Bass", | |
filenamepart: 'tenor-bass', | |
voices: [3, 4] | |
}, | |
"3" => { | |
title: "Melodie", | |
filenamepart: 'melodie', | |
voices: [1] | |
} | |
}, | |
# this is the builtin default for layout | |
# it is somehow double maintained es | |
# extrat.0.layout defines a default as well. | |
# but the runtime layout has more parameters which | |
# are not intended to be configured by a regular user. | |
# | |
# nevertheless, an expert user could also change the | |
# other parameters | |
layout: { | |
grid: false, | |
packer: { | |
pack_method: 0, | |
pack_max_spreadfactor: 2, | |
pack_min_increment: 0.2 | |
}, | |
limit_a3: true, | |
SHOW_SLUR: false, | |
LINE_THIN: 0.1, | |
LINE_MEDIUM: 0.3, | |
LINE_THICK: 0.5, | |
# all numbers in mm | |
ELLIPSE_SIZE: [3.5, 1.7], # radii of the largest Ellipse | |
REST_SIZE: [4, 2], # radii of the largest Rest Glyph | |
# x-size of one step in a pitch. It is the horizontal | |
# distance between two strings of the harp | |
X_SPACING: 11.5, # Distance of strings | |
# X coordinate of the very first beat | |
X_OFFSET: 2.8, #ELLIPSE_SIZE.first, | |
Y_SCALE: 4, # 4 mm per minimal | |
DRAWING_AREA_SIZE: [400, 282], # Area in which Drawables can be placed | |
# this affects the performance of the harpnote renderer | |
# it also specifies the resolution of note starts | |
# in fact the shortest playable note is 1/16; to display dotted 16, we need 1/32 | |
# in order to at least being able to handle triplets, we need to scale this up by 3 | |
# todo:see if we can speed it up by using 16 ... | |
BEAT_RESOLUTION: 192, # SHORTEST_NOTE * BEAT_PER_DURATION, ## todo use if want to support 5 * 7 * 9 # Resolution of Beatmap | |
SHORTEST_NOTE: 64, # shortest possible note (1/64) do not change this | |
# in particular specifies the range of DURATION_TO_STYLE etc. | |
BEAT_PER_DURATION: 3, # BEAT_RESOLUTION / SHORTEST_NOTE, | |
# this is the negative of midi-pitch of the lowest plaayble note | |
# see http://computermusicresource.com/midikeys.html | |
PITCH_OFFSET: -43, | |
FONT_STYLE_DEF: { | |
bold: {text_color: [0, 0, 0], font_size: 12, font_style: "bold"}, | |
italic: {text_color: [0, 0, 0], font_size: 12, font_style: "italic"}, | |
large: {text_color: [0, 0, 0], font_size: 20, font_style: "bold"}, | |
regular: {text_color: [0, 0, 0], font_size: 12, font_style: "normal"}, | |
small_bold: {text_color: [0, 0, 0], font_size: 9, font_style: "bold"}, | |
small_italic: {text_color: [0, 0, 0], font_size: 9, font_style: "italic"}, | |
small: {text_color: [0, 0, 0], font_size: 9, font_style: "normal"}, | |
smaller: {text_color: [0, 0, 0], font_size: 6, font_style: "normal"} | |
}, | |
MM_PER_POINT: 0.3, | |
# This is a lookup table to map durations to giraphical representation | |
DURATION_TO_STYLE: { | |
#key size fill dot abc duration | |
:err => [2, :filled, FALSE], # 1 1 | |
:d64 => [1, :empty, FALSE], # 1 1 | |
:d48 => [0.75, :empty, TRUE], # 1/2 * | |
:d32 => [0.75, :empty, FALSE], # 1/2 | |
:d24 => [0.75, :filled, TRUE], # 1/4 * | |
:d16 => [0.75, :filled, FALSE], # 1/4 | |
:d12 => [0.5, :filled, TRUE], # 1/8 * | |
:d8 => [0.5, :filled, FALSE], # 1/8 | |
:d6 => [0.3, :filled, TRUE], # 1/16 * | |
:d4 => [0.3, :filled, FALSE], # 1/16 | |
:d3 => [0.1, :filled, TRUE], # 1/32 * | |
:d2 => [0.1, :filled, FALSE], # 1/32 | |
:d1 => [0.05, :filled, FALSE] # 1/64 | |
}, | |
REST_TO_GLYPH: { | |
# this basically determines the white background rectangel | |
# [sizex, sizey], glyph, dot # note that sizex has no effect. | |
:err => [[2, 2], :rest_1, FALSE], # 1 1 | |
:d64 => [[1, 0.8], :rest_1, FALSE], # 1 1 # make it a bit smaller than the note to improve visibility of barover | |
:d48 => [[0.5, 0.4], :rest_1, TRUE], # 1/2 * # make it a bit smaller than the note to improve visibility of barover | |
:d32 => [[0.5, 0.4], :rest_1, FALSE], # 1/2 # make it a bit smaller than the note to improve visibility of barover | |
:d24 => [[0.4, 0.75], :rest_4, TRUE], # 1/4 * | |
:d16 => [[0.4, 0.75], :rest_4, FALSE], # 1/4 | |
:d12 => [[0.4, 0.5], :rest_8, TRUE], # 1/8 * | |
:d8 => [[0.4, 0.5], :rest_8, FALSE], # 1/8 | |
:d6 => [[0.4, 0.3], :rest_16, TRUE], # 1/16 * | |
:d4 => [[0.3, 0.3], :rest_16, FALSE], # 1/16 | |
:d3 => [[0.3, 0.5], :rest_32, TRUE], # 1/32 * | |
:d2 => [[0.3, 0.5], :rest_32, FALSE], # 1/32 | |
:d1 => [[0.3, 0.5], :rest_64, FALSE] # 1/64 | |
} | |
}, | |
neatjson: { | |
wrap: 60, aligned: true, after_comma: 1, after_colon_1: 1, after_colon_n: 1, before_colon_n: 1, sorted: true, | |
decimals: 2, | |
explicit_sort: [[:produce, :annotations, :restposition, :default, :repeatstart, :repeatend, :extract, | |
:title, :filenamepart, :startpos, :voices, :flowlines, :subflowlines, :synchlines, :jumplines, :repeatsigns, :layoutlines, :barnumbers, :countnotes, | |
:legend, :nonflowrest, :lyrics, :notes, :tuplet, :layout, :printer, | |
# | |
:annotation, :partname, :variantend, :countnote, :stringnames, # sort within notebound | |
# sort within layout | |
:limit_a3, :LINE_THIN, :LINE_MEDIUM, :LINE_THICK, :ELLIPSE_SIZE, :REST_SIZE, | |
:DRAWING_AREA_SIZE, | |
:packer, :pack_method, :pack_max_spreadfactor, :pack_min_increment, | |
# sort within printer | |
:a3_offset, :a4_offset, # sort within laoyut | |
"0", "1", "2", "3", "4", "5", "6", :verses, # extracts | |
:cp1, :cp2, :shape, :pos, :hpos, :vpos, :spos, :autopos, :text, :style, :marks # tuplets annotations | |
], | |
[]], | |
} | |
} | |
result | |
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 'init_conf.rb' | |
require 'neatjson.rb' | |
require 'js' | |
if RUBY_ENGINE == 'opal' | |
# `console.log(process.pid)` | |
require 'native' | |
require 'neatJSON' | |
doc = JS.global.JS[:document] | |
doc.JS.write 'start...' if doc | |
end | |
neatJSON = $neatJSON | |
testdata = InitConf.init_conf | |
def bm(name, &block) | |
starttime = Time.now | |
100.times { block.call } | |
result = block.call | |
puts (Time.now - starttime).to_s + " #{name}" | |
result | |
end | |
output = bm :ruby do | |
JSON.neat_generate( | |
testdata, | |
wrap:40, | |
short:false, | |
aligned:true, | |
padding:1, | |
after_comma:1, | |
around_colon_n:1 | |
) | |
end | |
if RUBY_ENGINE == 'opal' | |
inputjs = bm "convert ruby to js" do | |
%x{JSON.parse(JSON.stringify(#{testdata.to_n}))} | |
end | |
outputjs = bm :javascript do | |
out = %x{ | |
neatJSON(JSON.parse(JSON.stringify(#{testdata.to_n})),{ | |
wrap:40, | |
short:false, | |
aligned:true, | |
padding:1, | |
afterComma:1, | |
aroundColonN:1 | |
}) | |
} | |
out | |
# outputjs = %x{neatJSON(#{testdata.to_n},{ | |
# wrap:40, | |
# short:false, | |
# aligned:true, | |
# sorted: true, | |
# padding:1, | |
# afterComma:1, | |
# aroundColonN:1 | |
# })} | |
end | |
puts "result is equal: #{output === outputjs}" | |
end | |
if RUBY_ENGINE == 'opal' | |
doc.JS.write 'ok' if doc | |
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
function neatJSON(value,opts){ | |
opts = opts || {} | |
if (!('wrap' in opts)) opts.wrap = 80; | |
if (opts.wrap==true) opts.wrap = -1; | |
if (!('indent' in opts)) opts.indent = ' '; | |
if (!('arrayPadding' in opts)) opts.arrayPadding = ('padding' in opts) ? opts.padding : 0; | |
if (!('objectPadding' in opts)) opts.objectPadding = ('padding' in opts) ? opts.padding : 0; | |
if (!('beforeComma' in opts)) opts.beforeComma = ('aroundComma' in opts) ? opts.aroundComma : 0; | |
if (!('afterComma' in opts)) opts.afterComma = ('aroundComma' in opts) ? opts.aroundComma : 0; | |
if (!('beforeColon' in opts)) opts.beforeColon = ('aroundColon' in opts) ? opts.aroundColon : 0; | |
if (!('afterColon' in opts)) opts.afterColon = ('aroundColon' in opts) ? opts.aroundColon : 0; | |
if (!('beforeColon1' in opts)) opts.beforeColon1 = ('aroundColon1' in opts) ? opts.aroundColon1 : ('beforeColon' in opts) ? opts.beforeColon : 0; | |
if (!('afterColon1' in opts)) opts.afterColon1 = ('aroundColon1' in opts) ? opts.aroundColon1 : ('afterColon' in opts) ? opts.afterColon : 0; | |
if (!('beforeColonN' in opts)) opts.beforeColonN = ('aroundColonN' in opts) ? opts.aroundColonN : ('beforeColon' in opts) ? opts.beforeColon : 0; | |
if (!('afterColonN' in opts)) opts.afterColonN = ('aroundColonN' in opts) ? opts.aroundColonN : ('afterColon' in opts) ? opts.afterColon : 0; | |
var apad = repeat(' ',opts.arrayPadding), | |
opad = repeat(' ',opts.objectPadding), | |
comma = repeat(' ',opts.beforeComma)+','+repeat(' ',opts.afterComma), | |
colon1 = repeat(' ',opts.beforeColon1)+':'+repeat(' ',opts.afterColon1), | |
colonN = repeat(' ',opts.beforeColonN)+':'+repeat(' ',opts.afterColonN); | |
var build = memoize(); | |
return build(value,''); | |
function memoize(){ | |
var memo = new Map; | |
return function(o,indent){ | |
var byIndent=memo.get(o); | |
if (!byIndent) memo.set(o,byIndent={}); | |
if (!byIndent[indent]) byIndent[indent] = rawBuild(o,indent); | |
return byIndent[indent]; | |
} | |
} | |
function rawBuild(o,indent){ | |
if (o===null || o===undefined) return indent+'null'; | |
else{ | |
if (typeof o==='number'){ | |
var isFloat = (o === +o && o !== (o|0)); | |
return indent + ((isFloat && ('decimals' in opts)) ? o.toFixed(opts.decimals) : (o+'')); | |
}else if (o instanceof Array){ | |
if (!o.length) return indent+"[]"; | |
var pieces = o.map(function(v){ return build(v,'') }); | |
var oneLine = indent+'['+apad+pieces.join(comma)+apad+']'; | |
if (opts.wrap===false || oneLine.length<=opts.wrap) return oneLine; | |
if (opts.short){ | |
var indent2 = indent+' '+apad; | |
pieces = o.map(function(v){ return build(v,indent2) }); | |
pieces[0] = pieces[0].replace(indent2,indent+'['+apad); | |
pieces[pieces.length-1] = pieces[pieces.length-1]+apad+']'; | |
return pieces.join(',\n'); | |
}else{ | |
var indent2 = indent+opts.indent; | |
return indent+'[\n'+o.map(function(v){ return build(v,indent2) }).join(',\n')+'\n'+(opts.indentLast?indent2:indent)+']'; | |
} | |
}else if (o instanceof Object){ | |
var sortedKV=[],i=0; | |
var sort = opts.sort || opts.sorted; | |
for (var k in o){ | |
var kv = sortedKV[i++] = [k,o[k]]; | |
if (sort===true) kv[2] = k; | |
else if (typeof sort==='function') kv[2]=sort(k,o[k],o); | |
} | |
if (!sortedKV.length) return indent+'{}'; | |
if (sort) sortedKV = sortedKV.sort(function(a,b){ a=a[2]; b=b[2]; return a<b?-1:a>b?1:0 }); | |
var keyvals=sortedKV.map(function(kv){ return [JSON.stringify(kv[0]), build(kv[1],'')] }); | |
if (opts.sorted) keyvals = keyvals.sort(function(kv1,kv2){ kv1=kv1[0]; kv2=kv2[0]; return kv1<kv2?-1:kv1>kv2?1:0 }); | |
keyvals = keyvals.map(function(kv){ return kv.join(colon1) }).join(comma); | |
var oneLine = indent+"{"+opad+keyvals+opad+"}"; | |
if (opts.wrap===false || oneLine.length<opts.wrap) return oneLine; | |
if (opts.short){ | |
keyvals = sortedKV.map(function(kv){ return [indent+' '+opad+JSON.stringify(kv[0]), kv[1]] }); | |
keyvals[0][0] = keyvals[0][0].replace(indent+' ',indent+'{'); | |
if (opts.aligned){ | |
var longest = 0; | |
for (var i=keyvals.length;i--;) if (keyvals[i][0].length>longest) longest = keyvals[i][0].length; | |
var padding = repeat(' ',longest); | |
for (var i=keyvals.length;i--;) keyvals[i][0] = padRight(padding,keyvals[i][0]); | |
} | |
for (var i=keyvals.length;i--;){ | |
var k=keyvals[i][0], v=keyvals[i][1]; | |
var indent2 = repeat(' ',(k+colonN).length); | |
var oneLine = k+colonN+build(v,''); | |
keyvals[i] = (opts.wrap===false || oneLine.length<=opts.wrap || !v || typeof v!="object") ? oneLine : (k+colonN+build(v,indent2).replace(/^\s+/,'')); | |
} | |
return keyvals.join(',\n') + opad + '}'; | |
}else{ | |
var keyvals=[],i=0; | |
for (var k in o) keyvals[i++] = [indent+opts.indent+JSON.stringify(k),o[k]]; | |
if (sort) keyvals = keyvals.sort(function(kv1,kv2){ kv1=kv1[0]; kv2=kv2[0]; return kv1<kv2?-1:kv1>kv2?1:0 }); | |
if (opts.aligned){ | |
var longest = 0; | |
for (var i=keyvals.length;i--;) if (keyvals[i][0].length>longest) longest = keyvals[i][0].length; | |
var padding = repeat(' ',longest); | |
for (var i=keyvals.length;i--;) keyvals[i][0] = padRight(padding,keyvals[i][0]); | |
} | |
var indent2 = indent+opts.indent; | |
for (var i=keyvals.length;i--;){ | |
var k=keyvals[i][0], v=keyvals[i][1]; | |
var oneLine = k+colonN+build(v,''); | |
keyvals[i] = (opts.wrap===false || oneLine.length<=opts.wrap || !v || typeof v!="object") ? oneLine : (k+colonN+build(v,indent2).replace(/^\s+/,'')); | |
} | |
return indent+'{\n'+keyvals.join(',\n')+'\n'+(opts.indentLast?indent2:indent)+'}' | |
} | |
}else{ | |
return indent+JSON.stringify(o); | |
} | |
} | |
} | |
function repeat(str,times){ // http://stackoverflow.com/a/17800645/405017 | |
var result = ''; | |
while(true){ | |
if (times & 1) result += str; | |
times >>= 1; | |
if (times) str += str; | |
else break; | |
} | |
return result; | |
} | |
function padRight(pad, str){ | |
return (str + pad).substring(0, pad.length); | |
} | |
} | |
Opal.gvars.neatJSON = neatJSON; |
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 'json' | |
module JSON | |
# Generate the JSON string representation for an object, | |
# with a variety of formatting options. | |
# | |
# @author Gavin Kistner <!@phrogz.net> | |
# @param object [Object] the object to serialize | |
# @param opts [Hash] the formatting options | |
# @option opts [Integer] :wrap (80) The maximum line width before wrapping. Use `false` to never wrap, or `true` to always wrap. | |
# @option opts [String] :indent (" ") Whitespace used to indent each level when wrapping (without the :short option). | |
# @option opts [Boolean] :short (false) Keep the output 'short' when wrapping, putting opening brackets on the same line as the first value, and closing brackets on the same line as the last item. | |
# @option opts [Boolean] :sorted (false) Sort the keys for objects to be in alphabetical order. | |
# @option opts [Boolean] :aligned (false) When wrapping objects, align the colons (only per object). | |
# @option opts [Integer] :decimals (null) Decimal precision to use for floats; omit to keep numberic values precise. | |
# @option opts [Integer] :padding (0) Number of spaces to put inside brackets/braces for both arrays and objects. | |
# @option opts [Integer] :array_padding (0) Number of spaces to put inside brackets for arrays. Overrides `:padding`. | |
# @option opts [Integer] :object_padding (0) Number of spaces to put inside braces for objects. Overrides `:padding`. | |
# @option opts [Integer] :around_comma (0) Number of spaces to put before/after commas (for both arrays and objects). | |
# @option opts [Integer] :before_comma (0) Number of spaces to put before commas (for both arrays and objects). | |
# @option opts [Integer] :after_comma (0) Number of spaces to put after commas (for both arrays and objects). | |
# @option opts [Integer] :around_colon (0) Number of spaces to put before/after colons (for objects). | |
# @option opts [Integer] :before_colon (0) Number of spaces to put before colons (for objects). | |
# @option opts [Integer] :after_colon (0) Number of spaces to put after colons (for objects). | |
# @option opts [Integer] :around_colon_1 (0) Number of spaces to put before/after colons for single-line objects. | |
# @option opts [Integer] :before_colon_1 (0) Number of spaces to put before colons for single-line objects. | |
# @option opts [Integer] :after_colon_1 (0) Number of spaces to put after colons for single-line objects. | |
# @option opts [Integer] :around_colon_n (0) Number of spaces to put before/after colons for multi-line objects. | |
# @option opts [Integer] :before_colon_n (0) Number of spaces to put before colons for multi-line objects. | |
# @option opts [Integer] :after_colon_n (0) Number of spaces to put after colons for multi-line objects. | |
# @return [String] the JSON representation of the object. | |
def self.neat_generate(object,opts={}) | |
opts[:wrap] = 80 unless opts.key?(:wrap) | |
opts[:wrap] = -1 if opts[:wrap]==true | |
opts[:indent] ||= " " | |
opts[:array_padding] ||= opts[:padding] || 0 | |
opts[:object_padding] ||= opts[:padding] || 0 | |
opts[:after_comma] ||= opts[:around_comma] || 0 | |
opts[:before_comma] ||= opts[:around_comma] || 0 | |
opts[:before_colon] ||= opts[:around_colon] || 0 | |
opts[:after_colon] ||= opts[:around_colon] || 0 | |
opts[:before_colon_1] ||= opts[:around_colon_1] || opts[:before_colon] || 0 | |
opts[:after_colon_1] ||= opts[:around_colon_1] || opts[:after_colon] || 0 | |
opts[:before_colon_n] ||= opts[:around_colon_n] || opts[:before_colon] || 0 | |
opts[:after_colon_n] ||= opts[:around_colon_n] || opts[:after_colon] || 0 | |
raise ":indent option must only be whitespace" if opts[:indent]=~/\S/ | |
apad = " " * opts[:array_padding] | |
opad = " " * opts[:object_padding] | |
comma = "#{' '*opts[:before_comma]},#{' '*opts[:after_comma]}" | |
colon1= "#{' '*opts[:before_colon_1]}:#{' '*opts[:after_colon_1]}" | |
colonn= "#{' '*opts[:before_colon_n]}:#{' '*opts[:after_colon_n]}" | |
memoizer = {} | |
build = ->(o,indent) do | |
memoizer[[o,indent]] ||= case o | |
when String,Integer then "#{indent}#{o.inspect}" | |
when Symbol then "#{indent}#{o.to_s.inspect}" | |
when TrueClass,FalseClass then "#{indent}#{o}" | |
when NilClass then "#{indent}null" | |
when Float | |
if (o==o.to_i) && (o.to_s !~ /e/) | |
build[o.to_i,indent] | |
elsif opts[:decimals] | |
"#{indent}%.#{opts[:decimals]}f" % o | |
else | |
"#{indent}#{o}" | |
end | |
when Array | |
pieces = o.map{ |v| build[v,''] } | |
one_line = "#{indent}[#{apad}#{pieces.join comma}#{apad}]" | |
if o.empty? | |
"#{indent}[]" | |
elsif !opts[:wrap] || (one_line.length <= opts[:wrap]) | |
one_line | |
elsif opts[:short] | |
indent2 = "#{indent} #{apad}" | |
pieces = o.map{ |v| build[ v,indent2 ] } | |
pieces[0].sub! indent2, "#{indent}[#{apad}" | |
pieces.last << apad << "]" | |
pieces.join ",\n" | |
else | |
indent2 = "#{indent}#{opts[:indent]}" | |
"#{indent}[\n#{o.map{ |v| build[ v, indent2 ] }.join ",\n"}\n#{indent}]" | |
end | |
when Hash | |
keyvals = o.map{ |k,v| [ k.to_s.inspect, build[v,''] ] } | |
keyvals = keyvals.sort_by(&:first) if opts[:sorted] | |
keyvals = keyvals.map{ |kv| kv.join(colon1) }.join(comma) | |
one_line = "#{indent}{#{opad}#{keyvals}#{opad}}" | |
if o.empty? | |
"#{indent}{}" | |
elsif !opts[:wrap] || (one_line.length <= opts[:wrap]) | |
one_line | |
else | |
if opts[:short] | |
keyvals = o.map{ |k,v| ["#{indent} #{opad}#{k.to_s.inspect}",v] } | |
keyvals = keyvals.sort_by(&:first) if opts[:sorted] | |
keyvals[0][0].sub! "#{indent} ", "#{indent}{" | |
if opts[:aligned] | |
longest = keyvals.map(&:first).map(&:length).max | |
keyvals = keyvals.map{|k, v| ["%-#{longest}s" % k, v] } | |
end | |
keyvals.map! do |k,v| | |
indent2 = " "*"#{k}#{colonn}".length | |
one_line = "#{k}#{colonn}#{build[v,'']}" | |
if opts[:wrap] && (one_line.length > opts[:wrap]) && (v.is_a?(Array) || v.is_a?(Hash)) | |
"#{k}#{colonn}#{build[v,indent2].lstrip}" | |
else | |
one_line | |
end | |
end | |
keyvals.join(",\n") << opad << "}" | |
else | |
keyvals = o.map{ |k,v| ["#{indent}#{opts[:indent]}#{k.to_s.inspect}",v] } | |
keyvals = keyvals.sort_by(&:first) if opts[:sorted] | |
if opts[:aligned] | |
longest = keyvals.map(&:first).map(&:length).max | |
keyvals = keyvals.map{|k, v| ["%-#{longest}s" % k, v] } | |
end | |
indent2 = "#{indent}#{opts[:indent]}" | |
keyvals.map! do |k,v| | |
one_line = "#{k}#{colonn}#{build[v,'']}" | |
if opts[:wrap] && (one_line.length > opts[:wrap]) && (v.is_a?(Array) || v.is_a?(Hash)) | |
"#{k}#{colonn}#{build[v,indent2].lstrip}" | |
else | |
one_line | |
end | |
end | |
"#{indent}{\n#{keyvals.join(",\n")}\n#{indent}}" | |
end | |
end | |
else | |
"#{indent}#{o.to_json(opts)}" | |
end | |
end | |
build[object,''] | |
end | |
end | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment