djc 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: