djc 0.4.0 → 1.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.
- data/README.md +12 -8
- data/lib/djc.rb +295 -224
- metadata +4 -3
data/README.md
CHANGED
@@ -1,12 +1,16 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
djc
|
2
|
+
===
|
3
3
|
|
4
|
-
|
4
|
+
Finally, a *D*SL that converts *J*SON into *C*SV
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
---
|
7
|
+
|
8
|
+
DJC.build(objects/files) do
|
9
|
+
dsl(headers) do
|
10
|
+
field do
|
11
|
+
+captured_field
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
10
15
|
|
11
|
-
==========
|
12
16
|
|
data/lib/djc.rb
CHANGED
@@ -1,312 +1,383 @@
|
|
1
1
|
require 'json'
|
2
2
|
require 'csv'
|
3
|
+
require 'ctx'
|
3
4
|
|
4
5
|
module DJC
|
5
6
|
|
6
|
-
class
|
7
|
-
|
8
|
-
|
7
|
+
class Mapper
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def map(objects, &block)
|
11
|
+
self.new(&block).map(objects)
|
12
|
+
end
|
9
13
|
end
|
10
14
|
|
11
|
-
|
15
|
+
class Mapping
|
16
|
+
attr_accessor :left, :right, :matchers, :field
|
17
|
+
def initialize(left, right, field = nil) @left, @right, @field, @matchers = left, right, field, {} end
|
18
|
+
def to_s() "MERGE #@right INTO #@left #{ "%(#{field}) " if field }WHERE #{@matchers.map { |k, v| "#{k} = #{v}" }.join(" AND ")}" end
|
19
|
+
end
|
12
20
|
|
13
|
-
|
21
|
+
class ::String
|
22
|
+
ctx :mapping do
|
23
|
+
def ~@() "~#{self}" end
|
24
|
+
def -@() "^.#{self}" end
|
14
25
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
26
|
+
def &(other)
|
27
|
+
ctx[:mappings] ||= []
|
28
|
+
if other.is_a?(Mapping)
|
29
|
+
other.left = self
|
30
|
+
ctx[:mappings] << other
|
31
|
+
else
|
32
|
+
ctx[:mappings] << Mapping.new(self, other)
|
33
|
+
end
|
34
|
+
# p ctx[:mappings]
|
35
|
+
self
|
36
|
+
end
|
37
|
+
def +(other) self & other end
|
38
|
+
def <=(other) self & other end
|
39
|
+
def <<(other) self & other end
|
40
|
+
def <(other) self & other end
|
41
|
+
|
42
|
+
def %(other) Mapping.new(nil, self, other) end
|
43
|
+
def <=>(other) ctx[:mappings].last.matchers[self] = other end
|
44
|
+
def method_missing(other) "#{self}.#{other}" end
|
20
45
|
end
|
21
|
-
selected
|
22
46
|
end
|
47
|
+
def method_missing(name) name.to_s end
|
23
48
|
|
24
|
-
|
25
|
-
|
26
|
-
|
49
|
+
class ::Class
|
50
|
+
ctx :mapping do
|
51
|
+
def const_missing(name)
|
52
|
+
name
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
27
56
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
57
|
+
attr_accessor :mappings
|
58
|
+
def initialize(&block)
|
59
|
+
ctx :mapping do
|
60
|
+
self.instance_eval(&block)
|
61
|
+
self.mappings = ctx[:mappings]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def map(objects)
|
66
|
+
objects = Mobj::Circle.wrap(objects)
|
67
|
+
mappings.each do |mapping|
|
68
|
+
matched = {}
|
69
|
+
mapping.matchers.each_pair do |left, right|
|
70
|
+
mapping.left.tokenize.walk(objects).each do |lobj|
|
71
|
+
lval = left.tokenize.walk(lobj)
|
72
|
+
mapping.right.tokenize.walk(objects).each do |robj|
|
73
|
+
rval = right.tokenize.walk(robj)
|
74
|
+
if lval == rval
|
75
|
+
matched[left] ||= []
|
76
|
+
matched[left] << [lobj, robj]
|
77
|
+
matched[left].uniq!
|
36
78
|
end
|
37
|
-
path.clear
|
38
|
-
sub
|
39
|
-
end
|
40
|
-
elsif /^[\d,\+-\.]+$/.match(key)
|
41
|
-
locators = key.split(',').map do |dex|
|
42
|
-
range = /(?<start>\d+)(?:-|\+|\.\.(?<exclusive>\.)?)?(?<end>-?\d+)/.match(dex)
|
43
|
-
range ? Range.new(range['start'].to_i, (range['end'] || -1).to_i, range['exclusive']) : dex.to_i
|
44
79
|
end
|
45
|
-
selected = obj.values_at(*locators)
|
46
|
-
selected.size == 1 ? selected.first : selected
|
47
80
|
end
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
81
|
+
end
|
82
|
+
|
83
|
+
matching = matched.values.inject(matched.values.first) { |memo, val| memo & val }
|
84
|
+
matching.each do |val|
|
85
|
+
if mapping.field
|
86
|
+
val.first[mapping.field] = val.last
|
52
87
|
else
|
53
|
-
|
54
|
-
found = found.map { |k| path.empty? ? obj[k] : path.walk(obj[k]) }
|
55
|
-
path.clear
|
56
|
-
found = found.first if found.size < 2
|
57
|
-
found
|
88
|
+
val.first.merge!(val.last)
|
58
89
|
end
|
59
|
-
elsif obj.respond_to? key
|
60
|
-
obj.send(key)
|
61
90
|
end
|
62
91
|
end
|
63
92
|
|
64
|
-
|
93
|
+
objects
|
65
94
|
end
|
95
|
+
end
|
66
96
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
78
|
-
collated.each do |row|
|
79
|
-
each_with_index do |obj, index|
|
80
|
-
unless obj.is_a?(Array)
|
81
|
-
row[index] = obj
|
82
|
-
end
|
83
|
-
end
|
97
|
+
class Builder
|
98
|
+
class Rule
|
99
|
+
attr_accessor :type, :args, :block, :chain
|
100
|
+
def initialize(type, *args, &block) @type, @args, @block, @chain = type.sym, args, block, [] end
|
101
|
+
def to_s() "RULE:#{type.upcase}(#{args.join(', ')})#{ " +" + chain.map(&:to_s).join(" +") unless chain.empty?}" end
|
102
|
+
def method_missing(type, *args, &block)
|
103
|
+
chain << Rule.new(type, args, &block)
|
104
|
+
self
|
84
105
|
end
|
85
|
-
collated.size == 1 ? collated.first : collated
|
86
106
|
end
|
87
107
|
|
108
|
+
def initialize(&block)
|
109
|
+
ctx :compile do
|
110
|
+
self.instance_eval(&block)
|
111
|
+
end
|
112
|
+
end
|
88
113
|
|
89
|
-
def
|
90
|
-
|
114
|
+
def map(&block) @mappings = block end
|
115
|
+
alias :mappings :map
|
91
116
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
117
|
+
def rules(*headers, &block)
|
118
|
+
@headers = headers
|
119
|
+
@dsl = DSL.new(&block)
|
120
|
+
end
|
121
|
+
alias :dsl :rules
|
122
|
+
|
123
|
+
def build(objects)
|
124
|
+
mapped = Mapper.map(objects, &@mappings)
|
125
|
+
rows = @dsl.parse(mapped)
|
126
|
+
keys = @headers || rows.flat_map(&:keys).uniq.sort
|
127
|
+
CSV.generate do |csv|
|
128
|
+
csv << keys
|
129
|
+
rows.each do |row|
|
130
|
+
csv << keys.map { |key| row[key] }
|
105
131
|
end
|
106
132
|
end
|
107
|
-
crossed.size == 1 ? crossed.first : crossed
|
108
133
|
end
|
109
134
|
end
|
110
135
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
136
|
+
def self.build(objects, &block)
|
137
|
+
parsed = [*objects].inject({}) do |memo, (key, val)|
|
138
|
+
memo[key.sym] = if val.is_a?(String)
|
139
|
+
val = File.read(val) if File.exists?(val)
|
140
|
+
JSON.parse(val, max_nesting: false,
|
141
|
+
symbolize_names: true,
|
142
|
+
create_additions: false,
|
143
|
+
object_class: Mobj::CircleHash,
|
144
|
+
array_class: Mobj::CircleRay)
|
145
|
+
else
|
146
|
+
val
|
147
|
+
end
|
148
|
+
memo
|
121
149
|
end
|
122
150
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
151
|
+
Builder.new(&block).build(parsed)
|
152
|
+
end
|
153
|
+
|
154
|
+
class DSL < ::Object
|
155
|
+
class ::String
|
156
|
+
ctx(:djc_dsl_def) do
|
157
|
+
def +@()
|
158
|
+
+ctx[:dsl].__djc_dsl(self.to_sym)
|
159
|
+
end
|
160
|
+
def ~@()
|
161
|
+
ctx[:dsl].find(self)
|
162
|
+
end
|
129
163
|
end
|
130
164
|
end
|
131
165
|
|
132
|
-
def
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
166
|
+
def emit_name(name = nil)
|
167
|
+
if @name.is_a?(Proc)
|
168
|
+
@name.call(name)
|
169
|
+
elsif @finder && name
|
170
|
+
name
|
171
|
+
else
|
172
|
+
"#@name#{ "_#{name}" if name }"
|
173
|
+
end
|
140
174
|
end
|
141
|
-
|
142
|
-
def
|
143
|
-
|
144
|
-
|
175
|
+
def __djc_build_name(rule = nil) @name ? "#{@name}_#{rule}" : rule end
|
176
|
+
def __djc_reparent(parent) @parent = parent end
|
177
|
+
|
178
|
+
def initialize(rule = nil, parent = nil, name = parent.attempt(rule).__djc_build_name(rule), &block)
|
179
|
+
@rule, @parent, @name, @capture, @finder, @nodes, @composer, @splatter, @partials = rule, parent, name, false, false, [], [], false, {}
|
180
|
+
ctx(:djc_dsl_def) do
|
181
|
+
ctx[:dsl] = self
|
182
|
+
instance_eval(&block)
|
183
|
+
end if block
|
145
184
|
self
|
146
185
|
end
|
147
|
-
|
148
|
-
|
149
|
-
@type = 'AVG'
|
150
|
-
@blocks << proc { |array| ( array.map(&:to_i).inject(0.0, :+) / array.size) if array }
|
186
|
+
def ~@()
|
187
|
+
@finder = true
|
151
188
|
self
|
152
189
|
end
|
153
|
-
|
154
|
-
|
155
|
-
@
|
156
|
-
@blocks << proc { |array| array.map { |val| each_block.call(val) } if array }
|
190
|
+
def +@()
|
191
|
+
@capture = true
|
192
|
+
@rule = @rule.gsub(/^\/([^()]*)\/$/, '/(\1)/') if @finder
|
157
193
|
self
|
158
194
|
end
|
159
|
-
|
160
|
-
|
161
|
-
@type = 'JOIN'
|
162
|
-
@blocks << proc { |vals| vals.is_a?(Array) ? vals.compact.join(sep) : vals }
|
195
|
+
def >(other)
|
196
|
+
@name = other
|
163
197
|
self
|
164
198
|
end
|
199
|
+
alias_method :%, :>
|
200
|
+
|
201
|
+
def to_str() to_s end
|
202
|
+
def to_s(depth = 0)
|
203
|
+
str = "DJC:"
|
204
|
+
str += @capture ? "+" : ""
|
205
|
+
str += (@rule || "ROOT").to_s
|
206
|
+
str += "(#@name)" if @name
|
207
|
+
str += " [#{@partials.keys.join(",")}]" unless @partials.empty?
|
208
|
+
str += " {\n#{@nodes.map {|n| (" " * (depth + 1)) + n.to_s(depth + 1)}.join("\n") }\n#{" " * depth}}" unless @nodes.empty?
|
209
|
+
str
|
210
|
+
end
|
165
211
|
|
166
|
-
def
|
167
|
-
|
168
|
-
|
169
|
-
self
|
212
|
+
def find(rule, &block)
|
213
|
+
rule = rule.inspect if rule.is_a?(Regexp)
|
214
|
+
~__djc_dsl(rule, &block)
|
170
215
|
end
|
216
|
+
alias_method :match, :find
|
217
|
+
alias_method :with, :find
|
218
|
+
alias_method :field, :find
|
171
219
|
|
172
|
-
def
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
match = v.scan(matcher).flatten
|
179
|
-
match.size == 1 ? match.first : match
|
180
|
-
end
|
181
|
-
else
|
182
|
-
match = val.scan(matcher).flatten
|
183
|
-
match.size == 1 ? match.first : match
|
184
|
-
end
|
185
|
-
end
|
220
|
+
def compose(*args, &block)
|
221
|
+
if ctx[:dsl] == self
|
222
|
+
__djc_dsl(:compose, *args, &block)
|
223
|
+
else
|
224
|
+
@composer << block
|
225
|
+
self
|
186
226
|
end
|
187
|
-
self
|
188
227
|
end
|
189
228
|
|
190
|
-
def
|
191
|
-
if
|
192
|
-
|
193
|
-
|
194
|
-
while value.nil? && (path = walker.shift)
|
195
|
-
value = path.is_a?(Rule) ? path.apply(obj) : path.walk(obj)
|
196
|
-
end
|
197
|
-
value
|
229
|
+
def join(delimiter=$,, &block)
|
230
|
+
if ctx[:dsl] == self
|
231
|
+
args = delimiter != $, ? [delimiter] : []
|
232
|
+
__djc_dsl(:join, *args, &block)
|
198
233
|
else
|
199
|
-
|
200
|
-
|
201
|
-
elsif paths.length > 1
|
202
|
-
value = [[]]
|
203
|
-
paths.each_with_index do |rule, index|
|
204
|
-
val = rule.apply(obj)
|
205
|
-
if val.is_a?(Array)
|
206
|
-
val.each_with_index do |v, row|
|
207
|
-
value[row] ||= []
|
208
|
-
value[row][index] ||= []
|
209
|
-
value[row][index] = v
|
210
|
-
end
|
211
|
-
else
|
212
|
-
value.first << val
|
213
|
-
end
|
214
|
-
end
|
215
|
-
value = value.map { |val| blocks.inject(val) { |v, block| block.call(v) }} unless blocks.empty?
|
216
|
-
value = value.first if value.length == 1
|
217
|
-
value
|
218
|
-
else
|
219
|
-
value = paths.first.apply(obj)
|
220
|
-
value = blocks.inject(value) { |val, block| block.call(val) } unless blocks.empty?
|
221
|
-
end
|
234
|
+
compose { |*values| [*values].join(delimiter) }
|
235
|
+
self
|
222
236
|
end
|
223
|
-
value
|
224
237
|
end
|
225
|
-
end
|
226
238
|
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
239
|
+
def sum(initial = 0.0, op = :+, &block)
|
240
|
+
if ctx[:dsl] == self
|
241
|
+
args = initial != 0.0 ? [initial] : []
|
242
|
+
__djc_dsl(:sum, *args, &block)
|
243
|
+
else
|
244
|
+
compose { |*values| values.map(&:to_f).inject(initial, block ? block : op) if values }
|
245
|
+
self
|
246
|
+
end
|
231
247
|
end
|
232
|
-
end
|
233
248
|
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
249
|
+
def avg(*args, &block)
|
250
|
+
if ctx[:dsl] == self
|
251
|
+
__djc_dsl(:avg, *args, &block)
|
252
|
+
else
|
253
|
+
compose { |*values| (values.map(&:to_f).inject(0.0, :+) / values.size) if values }
|
254
|
+
self
|
255
|
+
end
|
239
256
|
end
|
240
257
|
|
241
|
-
def
|
242
|
-
|
258
|
+
def sort(*args, &block)
|
259
|
+
if ctx[:dsl] == self
|
260
|
+
__djc_dsl(:sort, *args, &block)
|
261
|
+
else
|
262
|
+
compose { |*sort| sort.compact.sort(&block) }
|
263
|
+
self
|
264
|
+
end
|
243
265
|
end
|
244
266
|
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
267
|
+
def uniq(*args, &block)
|
268
|
+
if ctx[:dsl] == self
|
269
|
+
__djc_dsl(:uniq, *args, &block)
|
270
|
+
else
|
271
|
+
compose { |*values| values.uniq }
|
272
|
+
self
|
273
|
+
end
|
249
274
|
end
|
250
275
|
|
251
|
-
def
|
252
|
-
|
276
|
+
def count(compact = false, &block)
|
277
|
+
if ctx[:dsl] == self
|
278
|
+
args = compact ? [compact] : []
|
279
|
+
__djc_dsl(:count, *args, &block)
|
280
|
+
else
|
281
|
+
compose { |*values| compact ? values.compact.size : values.size }
|
282
|
+
self
|
283
|
+
end
|
253
284
|
end
|
254
285
|
|
255
|
-
def
|
256
|
-
|
286
|
+
def *(*)
|
287
|
+
@splatter = true
|
288
|
+
self
|
257
289
|
end
|
258
290
|
|
259
|
-
def
|
260
|
-
|
291
|
+
def capture(regex = nil, *captures, &block)
|
292
|
+
if ctx[:dsl] == self
|
293
|
+
args = [regex, *captures].compact
|
294
|
+
__djc_dsl(:capture, *args, &block)
|
295
|
+
else
|
296
|
+
compose do |value|
|
297
|
+
if (match = regex.match(value.to_s))
|
298
|
+
if captures.empty?
|
299
|
+
block ? block.call(match) : match.captures
|
300
|
+
else
|
301
|
+
symbols = captures.any? { |i| i.is_a?(String) || i.is_a?(Symbol) }
|
302
|
+
captured = symbols ? captures.map { |name| match[name] } : match.to_a.values_at(*captures)
|
303
|
+
block ? block.call(*captured) : captured
|
304
|
+
end.sequester
|
305
|
+
end
|
306
|
+
end
|
307
|
+
self
|
308
|
+
end
|
261
309
|
end
|
262
310
|
|
263
|
-
def
|
264
|
-
|
311
|
+
def __djc_partial(name)
|
312
|
+
@partials[name] || @parent.__djc_partial(name)
|
265
313
|
end
|
266
314
|
|
267
|
-
def
|
268
|
-
|
315
|
+
def __djc_dsl(name, *args, &block)
|
316
|
+
if name.to_s[0] == '_'
|
317
|
+
if block
|
318
|
+
@partials[name] = block
|
319
|
+
self
|
320
|
+
else
|
321
|
+
dsl = __djc_partial(name).call(*args)
|
322
|
+
dsl.__djc_reparent(self)
|
323
|
+
@nodes << dsl
|
324
|
+
dsl
|
325
|
+
end
|
326
|
+
else
|
327
|
+
dsl = DSL.new(name, self, *args, &block)
|
328
|
+
@nodes << dsl
|
329
|
+
dsl
|
330
|
+
end
|
269
331
|
end
|
270
332
|
|
271
|
-
def
|
272
|
-
|
333
|
+
def method_missing(name, *args, &block)
|
334
|
+
__djc_dsl(name, *args, &block)
|
273
335
|
end
|
274
336
|
|
275
|
-
def
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
json.each do |row|
|
280
|
-
row = @columns.map do |column|
|
281
|
-
column.rule.apply(row)
|
282
|
-
end
|
283
|
-
rows << row
|
337
|
+
def rule_parse(data)
|
338
|
+
if data.is_a?(Array)
|
339
|
+
data.flat_map do |element|
|
340
|
+
rule_parse(element)
|
284
341
|
end
|
285
342
|
else
|
286
|
-
|
287
|
-
|
343
|
+
values = @rule.to_s.walk(data)
|
344
|
+
|
345
|
+
row = if (@splatter || @finder) && values.is_a?(Hash)
|
346
|
+
values.each.with_object({}) { |(k, v), r| r[emit_name(k)] = v }
|
347
|
+
elsif @splatter && values.is_a?(Array)
|
348
|
+
values.each.with_index.with_object({}) { |(v, i), r| r[emit_name(i)] = v }
|
349
|
+
else
|
350
|
+
{ emit_name => values }
|
288
351
|
end
|
352
|
+
|
353
|
+
[ @composer.empty? ? row : row.each.with_object({}) { |(k,v), r| r[k] = @composer.inject(v) { |m,c| c.call(*m) } } ]
|
289
354
|
end
|
290
|
-
rows
|
291
355
|
end
|
292
356
|
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
357
|
+
def parse(data, extract = true)
|
358
|
+
if @capture
|
359
|
+
rule_parse(data)
|
360
|
+
else
|
361
|
+
data = if @finder && extract
|
362
|
+
@rule.to_s.walk(data)
|
363
|
+
elsif data && @rule && extract && data.is_a?(Hash)
|
364
|
+
data[@rule]
|
365
|
+
else
|
366
|
+
data
|
367
|
+
end
|
368
|
+
if data.is_a?(Array)
|
369
|
+
data.flat_map do |element|
|
370
|
+
parse(element, false)
|
371
|
+
end
|
372
|
+
else
|
373
|
+
@nodes.inject([]) do |rows, node|
|
374
|
+
result = node.parse(data)
|
375
|
+
result.flat_map do |res|
|
376
|
+
rows.empty? ? res : rows.map { |row| row.merge(res) }
|
377
|
+
end
|
378
|
+
end
|
306
379
|
end
|
307
380
|
end
|
308
|
-
out
|
309
381
|
end
|
310
382
|
end
|
311
|
-
|
312
383
|
end
|
metadata
CHANGED
@@ -1,15 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: djc
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Mason
|
9
|
+
- Glenna
|
9
10
|
autorequire:
|
10
11
|
bindir: bin
|
11
12
|
cert_chain: []
|
12
|
-
date: 2012-
|
13
|
+
date: 2012-12-08 00:00:00.000000000 Z
|
13
14
|
dependencies:
|
14
15
|
- !ruby/object:Gem::Dependency
|
15
16
|
name: rspec
|
@@ -35,7 +36,7 @@ extra_rdoc_files: []
|
|
35
36
|
files:
|
36
37
|
- lib/djc.rb
|
37
38
|
- README.md
|
38
|
-
homepage:
|
39
|
+
homepage: https://github.com/gnovos/djc
|
39
40
|
licenses: []
|
40
41
|
post_install_message:
|
41
42
|
rdoc_options: []
|