json-yaml-embedded-expressions 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +110 -0
- data/bin/json-embedded-expressions +30 -0
- data/bin/yaml-embedded-expressions +30 -0
- data/lib/easy_hash.rb +24 -0
- data/lib/embedded_expressions.rb +160 -0
- data/lib/json/embedded_expressions.rb +14 -0
- data/lib/yaml/embedded_expressions.rb +14 -0
- metadata +58 -0
data/README.md
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
JSON/YAML/etc. with embedded expressions
|
2
|
+
========================================
|
3
|
+
|
4
|
+
A library for resolving embedded expressions in YAML, JSON and other. Empower your data with calculations!
|
5
|
+
|
6
|
+
Install
|
7
|
+
-------
|
8
|
+
|
9
|
+
$ gem install json-yaml-embedded-expressions
|
10
|
+
|
11
|
+
Usage
|
12
|
+
-----
|
13
|
+
|
14
|
+
`example.yaml`:
|
15
|
+
|
16
|
+
packages:
|
17
|
+
"Office":
|
18
|
+
subpath: utils/office
|
19
|
+
modules:
|
20
|
+
- basename: writer
|
21
|
+
path: ="#{top.install_path}/#{parent.parent.subpath}/#{basename}"
|
22
|
+
- basename: spreadsheet
|
23
|
+
path: ="#{top.install_path}/#{parent.parent.subpath}/#{basename}"
|
24
|
+
modules_count: =modules.size
|
25
|
+
install_path: /home/alice
|
26
|
+
all_paths: =packages["Office"].modules.map(&:path)
|
27
|
+
|
28
|
+
Command line:
|
29
|
+
|
30
|
+
$ yaml-embedded-expressions example.yaml
|
31
|
+
---
|
32
|
+
packages:
|
33
|
+
Office:
|
34
|
+
subpath: utils/office
|
35
|
+
modules:
|
36
|
+
- basename: writer
|
37
|
+
path: /home/alice/utils/office/writer
|
38
|
+
- basename: spreadsheet
|
39
|
+
path: /home/alice/utils/office/spreadsheet
|
40
|
+
modules_count: 2
|
41
|
+
install_path: /home/alice
|
42
|
+
all_paths:
|
43
|
+
- /home/alice/utils/office/writer
|
44
|
+
- /home/alice/utils/office/spreadsheet
|
45
|
+
|
46
|
+
Command line (JSON):
|
47
|
+
|
48
|
+
$ json-embedded-expressions example.json | json_pp
|
49
|
+
{
|
50
|
+
"packages" : {
|
51
|
+
"Office" : {
|
52
|
+
"subpath" : "utils/office",
|
53
|
+
"modules" : [
|
54
|
+
{
|
55
|
+
"path" : "/home/alice/utils/office/writer",
|
56
|
+
"basename" : "writer"
|
57
|
+
},
|
58
|
+
{
|
59
|
+
"basename" : "spreadsheet",
|
60
|
+
"path" : "/home/alice/utils/office/spreadsheet"
|
61
|
+
}
|
62
|
+
],
|
63
|
+
"modules_count" : 2
|
64
|
+
}
|
65
|
+
},
|
66
|
+
"install_path" : "/home/alice",
|
67
|
+
"all_paths" : [
|
68
|
+
"/home/alice/utils/office/writer",
|
69
|
+
"/home/alice/utils/office/spreadsheet"
|
70
|
+
]
|
71
|
+
}
|
72
|
+
|
73
|
+
In Ruby:
|
74
|
+
|
75
|
+
require 'yaml/embedded_expressions'
|
76
|
+
require 'easy_hash'
|
77
|
+
|
78
|
+
d = (YAML.load(File.read('example.yaml'))
|
79
|
+
YAML::EmbeddedExpressions.resolve!(d)
|
80
|
+
puts d
|
81
|
+
# {
|
82
|
+
# "packages" => {
|
83
|
+
# "Office" => {
|
84
|
+
# "subpath" => "utils/office",
|
85
|
+
# "modules" => [
|
86
|
+
# {
|
87
|
+
# "basename" => "writer",
|
88
|
+
# "path" => "/home/alice/utils/office/writer"
|
89
|
+
# },
|
90
|
+
# {
|
91
|
+
# "basename" => "spreadsheet",
|
92
|
+
# "path" => "/home/alice/utils/office/spreadsheet"
|
93
|
+
# }
|
94
|
+
# ],
|
95
|
+
# "modules_count" => 2
|
96
|
+
# }
|
97
|
+
# },
|
98
|
+
# "install_path" => "/home/alice",
|
99
|
+
# "all_paths" => [
|
100
|
+
# "/home/alice/utils/office/writer",
|
101
|
+
# "/home/alice/utils/office/spreadsheet"
|
102
|
+
# ]
|
103
|
+
# }
|
104
|
+
|
105
|
+
### Details ###
|
106
|
+
|
107
|
+
Embedded expression is a string of the form `=expr` (and not `==expr`) where
|
108
|
+
`expr` is a Ruby expression. The expression is evaluated in context of the map/array it is contained in. Inside `expr` you may use `self`, `parent` and `top`. Map entries may be accessed/assigned via `map['key']`. If using command-line utilities or required `easy_hash` then `map.key` also works.
|
109
|
+
|
110
|
+
Embedded expressions in map's keys are ignored, e. g., the following is not evaluated: `{"=2+2": "foobar"}`.
|
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
require 'json'
|
3
|
+
require 'json/embedded_expressions'
|
4
|
+
|
5
|
+
def usage
|
6
|
+
puts <<-USAGE
|
7
|
+
|
8
|
+
Usage: #{File.basename __FILE__} [-h|--help] [file]
|
9
|
+
|
10
|
+
Resolves embedded expressions in JSON. The JSON is read from file or from
|
11
|
+
standard input.
|
12
|
+
|
13
|
+
USAGE
|
14
|
+
end
|
15
|
+
|
16
|
+
if ["-h", "--help"].include? ARGV.first then
|
17
|
+
usage
|
18
|
+
exit
|
19
|
+
end
|
20
|
+
|
21
|
+
input =
|
22
|
+
if ARGV.empty? then STDIN
|
23
|
+
elsif ARGV.size == 1 then File.open(ARGV.shift)
|
24
|
+
else usage; abort
|
25
|
+
end
|
26
|
+
begin
|
27
|
+
puts(JSON::EmbeddedExpressions.resolve!(JSON.parse(input.read)).to_json)
|
28
|
+
ensure
|
29
|
+
input.close
|
30
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
require 'yaml'
|
3
|
+
require 'yaml/embedded_expressions'
|
4
|
+
|
5
|
+
def usage
|
6
|
+
puts <<-USAGE
|
7
|
+
|
8
|
+
Usage: #{File.basename __FILE__} [-h|--help] [file]
|
9
|
+
|
10
|
+
Resolves embedded expressions in YAML. The YAML is read from file or from
|
11
|
+
standard input.
|
12
|
+
|
13
|
+
USAGE
|
14
|
+
end
|
15
|
+
|
16
|
+
if ["-h", "--help"].include? ARGV.first then
|
17
|
+
usage
|
18
|
+
exit
|
19
|
+
end
|
20
|
+
|
21
|
+
input =
|
22
|
+
if ARGV.empty? then STDIN
|
23
|
+
elsif ARGV.size == 1 then File.open(ARGV.shift)
|
24
|
+
else usage; abort
|
25
|
+
end
|
26
|
+
begin
|
27
|
+
puts(YAML::EmbeddedExpressions.resolve!(YAML.load(input.read)).to_yaml)
|
28
|
+
ensure
|
29
|
+
input.close
|
30
|
+
end
|
data/lib/easy_hash.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
#
|
4
|
+
# Refines {Hash} so it behaves like following:
|
5
|
+
# - <code>hash.xxx</code> is the same as <code>hash["xxx"]</code>
|
6
|
+
# - <code>hash.xxx = y</code> is the same as <code>hash["xxx"] = y</code>
|
7
|
+
#
|
8
|
+
module EasyHash
|
9
|
+
|
10
|
+
class ::Hash
|
11
|
+
|
12
|
+
def method_missing(method_id, *args, &block)
|
13
|
+
if method_id.to_s.end_with? '=' and args.size == 1 and block.nil? then
|
14
|
+
self[method_id.to_s.chop] = args.first
|
15
|
+
elsif args.size == 0 and block.nil? then
|
16
|
+
self[method_id.to_s]
|
17
|
+
else
|
18
|
+
super
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
|
2
|
+
module EmbeddedExpressions
|
3
|
+
|
4
|
+
# Resolves embedded expressions in +data+.
|
5
|
+
#
|
6
|
+
# +data+ is a tree, its nodes are {Array}s and {Hash}es, its leaves are
|
7
|
+
# arbitrary {Object}s. Subnodes and leaves are stored as {Array}s' items or
|
8
|
+
# as {Hash}es' values.
|
9
|
+
#
|
10
|
+
# This function does the following:
|
11
|
+
# 1. For each node in +data+ it sets up {BackReferences}: sets
|
12
|
+
# {BackReferences#parent} to the containing node ({Hash} or {Array}) and
|
13
|
+
# {BackReferences#top} to +data.
|
14
|
+
# 2. Then for each leaf which is a {String} of the form +"=expr"+ and not of
|
15
|
+
# the form "==expr" it evaluates +expr+ in context of the containing node
|
16
|
+
# and replaces the leaf with the +expr+'s value. +expr+ which is required
|
17
|
+
# by another +expr+ is evaluated first.
|
18
|
+
#
|
19
|
+
# @return +data+ with embedded expressions resolved as described above.
|
20
|
+
#
|
21
|
+
# @example
|
22
|
+
#
|
23
|
+
# d = {
|
24
|
+
# "a" => [
|
25
|
+
# "= parent['b'] + self.size",
|
26
|
+
# "== ignored"
|
27
|
+
# ],
|
28
|
+
# "b" => "= self['c'] * 2",
|
29
|
+
# "c" => 20
|
30
|
+
# }
|
31
|
+
# EmbeddedExpressions.resolve!(d)
|
32
|
+
# puts d.inspect
|
33
|
+
# #=>
|
34
|
+
# {
|
35
|
+
# "a" => [
|
36
|
+
# 42,
|
37
|
+
# "== ignored"
|
38
|
+
# ],
|
39
|
+
# "b" => 40,
|
40
|
+
# "c" => 20
|
41
|
+
# }
|
42
|
+
#
|
43
|
+
def resolve!(data)
|
44
|
+
# Replace all "=expr" strings with {EmbeddedExpression}s.
|
45
|
+
y = lambda do |data|
|
46
|
+
case data
|
47
|
+
when Array
|
48
|
+
for i in 0...data.size
|
49
|
+
data[i] = y.(data[i])
|
50
|
+
end
|
51
|
+
data
|
52
|
+
when Hash
|
53
|
+
for key in data.keys
|
54
|
+
data[key] = y.(data[key])
|
55
|
+
end
|
56
|
+
data
|
57
|
+
when /^\=\=/
|
58
|
+
data
|
59
|
+
when /^\=/
|
60
|
+
EmbeddedExpression.new(&eval("proc do \n #{data[1..-1]} \n end"))
|
61
|
+
else
|
62
|
+
data
|
63
|
+
end
|
64
|
+
end
|
65
|
+
y.(data)
|
66
|
+
# Populate {BackReferences} and {EmbeddedExpression#__context__}.
|
67
|
+
y = lambda do |data, parent, top|
|
68
|
+
case data
|
69
|
+
when Array
|
70
|
+
data.parent = parent
|
71
|
+
data.top = top
|
72
|
+
data.each { |item| y.(item, data, top) }
|
73
|
+
when Hash
|
74
|
+
data.parent = parent
|
75
|
+
data.top = top
|
76
|
+
data.values.each { |value| y.(value, data, top) }
|
77
|
+
when EmbeddedExpression
|
78
|
+
data.__context__ = parent
|
79
|
+
end
|
80
|
+
end
|
81
|
+
y.(data, nil, data)
|
82
|
+
# Replace all {EmbeddedExpression}s with their values.
|
83
|
+
y = lambda do |data|
|
84
|
+
case data
|
85
|
+
when Array
|
86
|
+
for i in 0...data.size
|
87
|
+
data[i] = y.(data[i])
|
88
|
+
end
|
89
|
+
data
|
90
|
+
when Hash
|
91
|
+
for key in data.keys
|
92
|
+
data[key] = y.(data[key])
|
93
|
+
end
|
94
|
+
data
|
95
|
+
when EmbeddedExpression
|
96
|
+
data.__value__
|
97
|
+
else
|
98
|
+
data
|
99
|
+
end
|
100
|
+
end
|
101
|
+
y.(data)
|
102
|
+
#
|
103
|
+
return data
|
104
|
+
end
|
105
|
+
|
106
|
+
module_function :resolve!
|
107
|
+
|
108
|
+
module BackReferences
|
109
|
+
|
110
|
+
attr_accessor :parent
|
111
|
+
|
112
|
+
attr_accessor :top
|
113
|
+
|
114
|
+
end
|
115
|
+
|
116
|
+
class ::Hash
|
117
|
+
|
118
|
+
include BackReferences
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
class ::Array
|
123
|
+
|
124
|
+
include BackReferences
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
|
130
|
+
# @!visibility private
|
131
|
+
class EmbeddedExpression < BasicObject
|
132
|
+
|
133
|
+
def initialize(&computation)
|
134
|
+
@context = nil
|
135
|
+
@expr = computation
|
136
|
+
@expr_resolved = false
|
137
|
+
end
|
138
|
+
|
139
|
+
def __context__=(value)
|
140
|
+
@context = value
|
141
|
+
end
|
142
|
+
|
143
|
+
def __value__
|
144
|
+
if @expr_resolved then
|
145
|
+
return @expr
|
146
|
+
else
|
147
|
+
@expr = @context.instance_eval(&@expr)
|
148
|
+
@expr_resolved = true
|
149
|
+
@context = nil
|
150
|
+
return @expr
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def method_missing(method_id, *args, &block)
|
155
|
+
__value__.__send__(method_id, *args, &block)
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
metadata
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: json-yaml-embedded-expressions
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Lavir the Whiolet
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2016-05-19 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: ! 'A library for resolving embedded expressions in YAML, JSON and other.
|
15
|
+
Empower your data with calculations!
|
16
|
+
|
17
|
+
'
|
18
|
+
email: Lavir.th.Whiolet@gmail.com
|
19
|
+
executables:
|
20
|
+
- json-embedded-expressions
|
21
|
+
- yaml-embedded-expressions
|
22
|
+
extensions: []
|
23
|
+
extra_rdoc_files: []
|
24
|
+
files:
|
25
|
+
- lib/easy_hash.rb
|
26
|
+
- lib/json/embedded_expressions.rb
|
27
|
+
- lib/yaml/embedded_expressions.rb
|
28
|
+
- lib/embedded_expressions.rb
|
29
|
+
- README.md
|
30
|
+
- bin/json-embedded-expressions
|
31
|
+
- bin/yaml-embedded-expressions
|
32
|
+
homepage:
|
33
|
+
licenses:
|
34
|
+
- MIT
|
35
|
+
post_install_message:
|
36
|
+
rdoc_options: []
|
37
|
+
require_paths:
|
38
|
+
- lib
|
39
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - '='
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: 1.9.3
|
45
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
46
|
+
none: false
|
47
|
+
requirements:
|
48
|
+
- - ! '>='
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '0'
|
51
|
+
requirements: []
|
52
|
+
rubyforge_project:
|
53
|
+
rubygems_version: 1.8.23
|
54
|
+
signing_key:
|
55
|
+
specification_version: 3
|
56
|
+
summary: Embedded expressions in YAML/JSON/etc.
|
57
|
+
test_files: []
|
58
|
+
has_rdoc:
|