djc 0.0.1 → 0.1.0

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.
Files changed (3) hide show
  1. data/README.md +12 -0
  2. data/lib/djc.rb +240 -0
  3. metadata +20 -3
data/README.md ADDED
@@ -0,0 +1,12 @@
1
+ djconvolvs
2
+ ==========
3
+
4
+ JSON to CSV mapping DSL
5
+
6
+ DJ
7
+ CSV
8
+ OL
9
+ N
10
+
11
+ ==========
12
+
data/lib/djc.rb CHANGED
@@ -0,0 +1,240 @@
1
+ require 'json'
2
+ require 'csv'
3
+
4
+ module DJC
5
+
6
+ class ::String
7
+ def ~@
8
+ "##{self}"
9
+ end
10
+
11
+ end
12
+
13
+ class ::Array
14
+ def walk(obj)
15
+ path = self.dup
16
+ key = path.shift.to_s
17
+ val = if obj.is_a? Array
18
+ match = /^[\d,-]+$/.match(key)
19
+ if match
20
+ selected = key.split(',').map do |dex|
21
+ range = /(\d+)(?:-|\.\.\.?)(\d+)/.match(dex)
22
+ if range
23
+ range = range.captures
24
+ obj[range.first.to_i..range.last.to_i]
25
+ else
26
+ obj[dex.to_i]
27
+ end
28
+ end.flatten
29
+ selected.size == 1 ? selected.first : selected
30
+ end
31
+ elsif obj.is_a? Hash
32
+ match = key[/\/(.*)\//, 1]
33
+ if match.nil?
34
+ obj[key]
35
+ else
36
+ found = obj.keys.select { |k| Regexp.new(match).match(k) }
37
+ found = found.map { |k| path.empty? ? obj[k] : path.walk(obj[k]) }
38
+ path.clear
39
+ found = found.first if found.size < 2
40
+ found
41
+ end
42
+ elsif obj.respond_to? key
43
+ obj.send(key)
44
+ end
45
+ path.empty? ? val : path.walk(val)
46
+ end
47
+
48
+ def cross
49
+ crossed = [[]]
50
+
51
+ each do |obj|
52
+ if obj.is_a?(Array)
53
+ adding = []
54
+ obj.each_with_index do |item, index|
55
+ crossed.each do |cross|
56
+ row = cross.dup
57
+ row << item
58
+ adding << row
59
+ end
60
+ end
61
+ crossed = adding
62
+ else
63
+ crossed.each { |cross| cross << obj }
64
+ end
65
+ end
66
+ crossed.size == 1 ? crossed.first : crossed
67
+ end
68
+ end
69
+
70
+ class Rule
71
+ def parse(paths)
72
+ rules = paths.split('|').map do |path|
73
+ path.scan(/\/[^\/]+\/|\<[^\<]\>|[^\[\]\{\}\|\.]+/)
74
+ end
75
+
76
+ rules
77
+ end
78
+
79
+ attr_reader :paths, :type, :block
80
+ def initialize(type='lookup', rules, &block)
81
+ if rules.is_a?(String) && rules[0] == '#'
82
+ @type, @block, @paths = 'literal', proc { rules[1..-1] }, nil
83
+ else
84
+ @type, @block, @paths = type, block, rules.is_a?(String) ? parse(rules) : rules
85
+ end
86
+ end
87
+
88
+ def sum
89
+ @type, @block = 'sum', proc { |array| array.map(&:to_i).inject(0, :+) }
90
+ self
91
+ end
92
+
93
+ def avg
94
+ @type, @block = 'avg', proc { |array| array.map(&:to_i).inject(0.0, :+) / array.size }
95
+ self
96
+ end
97
+
98
+ def each(&each_block)
99
+ @type, @block = 'each', proc { |array| array.map { |val| each_block.call(val) } }
100
+ self
101
+ end
102
+
103
+ def join(sep = '')
104
+ @type, @block = 'join', proc { |vals| vals.is_a?(Array) ? vals.compact.join(sep) : vals }
105
+ self
106
+ end
107
+
108
+ def match(matcher)
109
+ @type, @block = 'match', proc { |val| val.scan(matcher).flatten }
110
+ self
111
+ end
112
+
113
+ def apply(obj)
114
+ value = nil
115
+ if type == 'lookup'
116
+ walker = paths.dup
117
+ value = nil
118
+ while value.nil? && path = walker.shift
119
+ value = path.walk(obj)
120
+ end
121
+ value
122
+ else
123
+ if paths.nil? || paths.empty?
124
+ value = block.call(obj)
125
+ elsif paths.length > 1
126
+ value = [[]]
127
+ paths.each_with_index do |rule, index|
128
+ val = rule.apply(obj)
129
+ if val.is_a?(Array)
130
+ val.each_with_index do |v, row|
131
+ value[row] ||= []
132
+ value[row][index] ||= []
133
+ value[row][index] = v
134
+ end
135
+ else
136
+ value.first << val
137
+ end
138
+ end
139
+ value = value.map { |val| block.call(val) } unless block.nil?
140
+ value = value.first if value.length == 1
141
+ value
142
+ else
143
+ value = paths.first.apply(obj)
144
+ value = block.call(value) unless block.nil?
145
+ end
146
+ end
147
+ value
148
+ end
149
+
150
+ end
151
+
152
+ class Column
153
+ attr_reader :name, :rule
154
+ def initialize(name, rule)
155
+ @name, @rule = name, rule.is_a?(Rule) ? rule : Rule.new(rule)
156
+ end
157
+ end
158
+
159
+ class Builder
160
+ def self.compile(path=nil, &block)
161
+ builder = Builder.new(path)
162
+ builder.instance_eval &block
163
+ builder
164
+ end
165
+
166
+ def initialize(path = nil)
167
+ @path = Rule.new(path) if path
168
+ end
169
+
170
+ attr_reader :columns
171
+ def []=(column, rule)
172
+ @columns ||= []
173
+ @columns << Column.new(column, rule)
174
+ end
175
+
176
+ def header
177
+ columns.map { |column| column.name }
178
+ end
179
+
180
+ def sum(*paths)
181
+ with(*paths).sum
182
+ end
183
+
184
+ def avg(*paths)
185
+ with(*paths).avg
186
+ end
187
+
188
+ def each(*paths, &block)
189
+ with(*paths).each(&block)
190
+ end
191
+
192
+ def with(*paths, &block)
193
+ Rule.new('with', paths.map { |path| Rule.new(path) }, &block)
194
+ end
195
+
196
+ def rule(&block)
197
+ Rule.new('rule', nil, &block)
198
+ end
199
+
200
+ def build(json)
201
+ json = @path.apply(json) if @path
202
+ rows = []
203
+ if json.is_a? Array
204
+ json.each do |row|
205
+ #xxx ensmartern for arrayed vals, x-by-x
206
+ rows << @columns.map do |column|
207
+ column.rule.apply(row)
208
+ end
209
+ end
210
+ else
211
+ rows << @columns.map do |column|
212
+ column.rule.apply(json)
213
+ end
214
+ end
215
+ rows
216
+ end
217
+
218
+ end
219
+
220
+ class << self
221
+
222
+ def build(json = nil, &block)
223
+ json = JSON.parse(json) if json.is_a?(String)
224
+
225
+ builder = Builder.compile(&block)
226
+
227
+ out = CSV.generate do |csv|
228
+ csv << builder.header
229
+ builder.build(json).each do |row|
230
+ csv << row
231
+ end
232
+ end
233
+ out
234
+ end
235
+
236
+ end
237
+
238
+
239
+
240
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: djc
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,8 +9,24 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-09-24 00:00:00.000000000 Z
13
- dependencies: []
12
+ date: 2012-09-27 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
14
30
  description: Map JSON fields into CSV columns easily
15
31
  email: djc@chipped.net
16
32
  executables: []
@@ -18,6 +34,7 @@ extensions: []
18
34
  extra_rdoc_files: []
19
35
  files:
20
36
  - lib/djc.rb
37
+ - README.md
21
38
  homepage: http://rubygems.org/gems/djc
22
39
  licenses: []
23
40
  post_install_message: