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.
@@ -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
@@ -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
@@ -0,0 +1,14 @@
1
+ require 'json'
2
+ require 'embedded_expressions'
3
+
4
+ module JSON
5
+
6
+ # @!parse
7
+ # module EmbeddedExpressions
8
+ # include ::EmbeddedExpressions
9
+ # end
10
+
11
+ # @!visibility private
12
+ EmbeddedExpressions = ::EmbeddedExpressions
13
+
14
+ end
@@ -0,0 +1,14 @@
1
+ require 'yaml'
2
+ require 'embedded_expressions'
3
+
4
+ module YAML
5
+
6
+ # @!parse
7
+ # module EmbeddedExpressions
8
+ # include ::EmbeddedExpressions
9
+ # end
10
+
11
+ # @!visibility private
12
+ EmbeddedExpressions = ::EmbeddedExpressions
13
+
14
+ 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: