atomizr 0.18.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. checksums.yaml +7 -0
  2. data/bin/atomizr +201 -0
  3. data/lib/atomizr.rb +465 -0
  4. metadata +89 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a56751156f8d212eee3aac8f422169a4d2658a27
4
+ data.tar.gz: '09b2ee2faa2025a92ffb7f40a586e416604d189a'
5
+ SHA512:
6
+ metadata.gz: 58a3918e0dcb3d1eb27060690fdcb1dbc99a5fbdb0450585c1a5fa4ff8ab1e35e41379f423a14fa83e0a668656f0e1697c642c22c93fe245ceb09e9de64e390c
7
+ data.tar.gz: 67a1ebee2b3a2117d4c2e0113e0a366153eea4aaa9a58f310ea9f712efa73602ecbc5543363387910e4053f95c1fa2bd34a02f30ee6eca0ea447e9d9207cc04b
data/bin/atomizr ADDED
@@ -0,0 +1,201 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "atomizr"
4
+ require "optparse"
5
+
6
+ # default options
7
+ $input_counter = 0
8
+ $output_counter = 0
9
+ $folder = "_output"
10
+ $silent = false
11
+
12
+ $scope = nil
13
+ $merge = false
14
+ $split = false
15
+ $dupes = true
16
+ $is_tm = false
17
+ $no_tabstops = false
18
+ $no_comment = false
19
+ $no_validation = false
20
+ $delete_input = false
21
+
22
+ args = ARGV.count
23
+
24
+ # parse arguments
25
+ ARGV.options do |opts|
26
+ opts.banner = "\nUsage: atomizr [options]"
27
+
28
+ opts.on("-h", "--help", "prints this help") do
29
+ Atomizr.info()
30
+ puts opts
31
+ exit
32
+ end
33
+
34
+ opts.on("-i", "--input=<file>", Array, "Input file(s)") {
35
+ |input| $input = input
36
+ }
37
+
38
+ opts.on("-o", "--output=<file>", String, "Output file") {
39
+ |output| $output = output
40
+ }
41
+
42
+ opts.on("-s", "--scope=<scope>", String, "overwrite scope") {
43
+ |val| $scope = val
44
+ }
45
+
46
+ opts.on("-S", "--split", "split result into multiple files") {
47
+ if $merge != true
48
+ $split = true
49
+ else
50
+ abort("Error: You can't split AND merge")
51
+ end
52
+ }
53
+
54
+ opts.on("-M", "--merge", "merge results into single file") {
55
+ if $split != true
56
+ $merge = true
57
+ else
58
+ abort("Error: You can't merge AND split")
59
+ end
60
+ }
61
+
62
+ opts.on("-$", "--skip-tabstops", "skip trailing tab-stops") {
63
+ $no_tabstops = true
64
+ }
65
+
66
+ opts.on("-C", "--skip-comments", "skip generator comments") {
67
+ $no_comment = true
68
+ }
69
+
70
+ opts.on("-D", "--skip-duplicates", "skip duplicate triggers") {
71
+ $dupes = false
72
+ }
73
+
74
+ opts.on("-V", "--skip-validation", "skip file validation") {
75
+ $no_validation = true
76
+ }
77
+
78
+ opts.on("-F", "--to-folder=<target>", String, "specify target subfolder") do |folder|
79
+ $folder = folder
80
+ end
81
+
82
+ opts.on("-X", "--delete-input", "delete input file(s) on completion") {
83
+ $delete_input = true
84
+ }
85
+
86
+ opts.on("-T", "--is-textmate", "interprete snippet as TextMate") {
87
+ $is_tm = true
88
+ }
89
+
90
+ opts.on("-Z", "--silent", "run silently") {
91
+ $silent = true
92
+ }
93
+
94
+ opts.on("-v", "--version", "show version") {
95
+ Atomizr.version() unless $silent == true
96
+ exit
97
+ }
98
+
99
+ opts.parse!
100
+ end
101
+
102
+ # let's go
103
+ Atomizr.info() unless $silent == true
104
+
105
+ # error handling
106
+ if args < 1
107
+ abort("\nError: no arguments passed") unless $silent == true
108
+ elsif $input == nil
109
+ abort("\nError: no input argument passed") unless $silent == true
110
+ elsif $output == nil
111
+ abort("\nError: no output argument passed") unless $silent == true
112
+ end
113
+
114
+ # Create output directory, if necessary
115
+ Atomizr.mkdir("#{$folder}")
116
+
117
+ $input.each do |input|
118
+
119
+ if (input.end_with? ".sublime-completions") || (input.end_with? ".json")
120
+
121
+ Atomizr.read_file(input, "json")
122
+ delete_file(input) if $delete_input == true
123
+
124
+ elsif (input.end_with? ".sublime-snippet") || (input.end_with? ".tmSnippet")|| (input.end_with? ".xml")
125
+
126
+ Atomizr.read_file(input, "xml")
127
+ delete_file(input) if $delete_input == true
128
+
129
+ elsif File.directory?(input)
130
+
131
+ puts "\n$ which apm"
132
+
133
+ unless system("which apm")
134
+ abort("Unknown command 'apm'. Please run \"Install Shell Commands\" from the Atom menu.") unless $silent == true
135
+ end
136
+
137
+ unless input.end_with?("/")
138
+ input = input + "/"
139
+ end
140
+
141
+ @tm = Hash.new
142
+ @tm['Preferences'] = Dir.glob("#{input}**/*.tmPreferences")
143
+ @tm['Snippets'] = Dir.glob("#{input}**/*.tmSnippet")
144
+ @tm['Syntaxes'] = Dir.glob("#{input}**/*.tmLanguage")
145
+
146
+ abort if @tm.empty?
147
+
148
+ @tm.each do |item|
149
+
150
+ next if item[1].empty?
151
+
152
+ puts "\nCollecting #{item[0].downcase}:" unless $silent == true
153
+
154
+ item[1].each do |file|
155
+ puts " + #{file}" unless $silent == true
156
+
157
+ @base = File.basename(file)
158
+ @parent = "#{$folder}/#{$output}"
159
+ @target = @parent+'/.tmp/'+item[0]+'/'
160
+
161
+ mkdir(@target)
162
+ copy_file(file, @target+@base, false)
163
+ end
164
+
165
+ end
166
+
167
+ puts "\nConverting collection with apm" unless $silent == true
168
+ system "apm init --package #{@parent} --convert #{@parent}/.tmp"
169
+ FileUtils.rm_rf("#{@parent}/.tmp")
170
+
171
+ @st = Hash.new
172
+ @st['Snippets'] = Dir.glob("#{input}**/*.sublime-snippet")
173
+ @st['Completions'] = Dir.glob("#{input}**/*.sublime-completions")
174
+
175
+ abort if @st.empty?
176
+
177
+ @st.each do |st|
178
+ st[1].each do |item|
179
+ next if item.empty?
180
+ system "#{__FILE__} -i '#{item}' -o cson -Z -F #{@parent}/snippets"
181
+ end
182
+ end
183
+
184
+ delete_file(input) if $delete_input == true
185
+ puts "\nCompleted."
186
+ exit
187
+
188
+ else
189
+ puts "\nError: invalid input '#{input}'" unless $silent == true
190
+ end
191
+
192
+ end
193
+
194
+ # Game Over
195
+ if $input_counter == 0
196
+ puts "\nNo files converted" unless $silent == true
197
+ elsif $input_counter == 1
198
+ puts "\nAtomized #{$input_counter} file, created #{$output_counter}" unless $silent == true
199
+ else
200
+ puts "\nAtomized #{$input_counter} files, created #{$output_counter}" unless $silent == true
201
+ end
data/lib/atomizr.rb ADDED
@@ -0,0 +1,465 @@
1
+ require "json"
2
+ require "nokogiri"
3
+
4
+ class Atomizr
5
+
6
+ @name = "Atomizr"
7
+ @version = "0.18.0"
8
+ @author = "Jan T. Sott"
9
+ @homepage = "https://github.com/idleberg/atomizr.rb"
10
+
11
+ # Arrays of filters to replace characters in strings
12
+ @filename_filter = [
13
+ [/[\x00\/\\:\*\?\"\$<>\|]/, '_'],
14
+ ["\t", "-"]
15
+ ]
16
+ @title_filter = [
17
+ [/\x27/, "\\\\'"], # single-quote
18
+ [/\x22/, "\\\""], # double-quote
19
+ [/\x5C/, "\\\\"], # backslash
20
+ ]
21
+ @prefix_filter = [
22
+ [/[\x00\x22\x27\/\\:\*\?\"\'\$<>\{\}\|]/, '']
23
+ ]
24
+ @body_filter = [
25
+ [/\x27/, "\\\\'"], # single-quote
26
+ [/\x22/, "\\\""], # double-quote
27
+ [/\x5C/, "\\\\"], # backslash
28
+ ]
29
+ @scope_filter = [
30
+ # https://gist.github.com/idleberg/fca633438329cc5ae317
31
+ [',', ''],
32
+ [/\.?source\.c\+\+/, '.source.cpp'],
33
+ [/\.?source\.java-props/, '.source.java-properties'],
34
+ [/\.?source\.objc\+\+/, '.source.objcpp'],
35
+ [/\.?source\.php/, '.text.html.php'],
36
+ [/\.?source\.scss/, '.source.css.scss'],
37
+ [/\.?source\.todo/, '.text.todo'],
38
+ [/\.?text\.html\.markdown/, '.source.gfm']
39
+ ]
40
+
41
+ def self.info
42
+ puts "\n#{@name}, version #{@version}\nThe MIT License\nCopyright (c) 2015, 2016 #{@author}"
43
+ end
44
+
45
+ def self.version
46
+ puts "#{@version}"
47
+ end
48
+
49
+ def self.read_file(input, type)
50
+ if $merge == true
51
+ init_hashes()
52
+ end
53
+
54
+ Dir.glob(input) do |item|
55
+
56
+ if $merge == false
57
+ init_hashes()
58
+ end
59
+
60
+ @data = send("read_#{type}", item)
61
+
62
+ if $merge == false
63
+ write_data(item)
64
+ end
65
+ end
66
+
67
+ if $merge == true
68
+ write_data($output)
69
+ end
70
+ end
71
+
72
+
73
+ def self.read_xml(item)
74
+
75
+ puts "\nReading snippet file '#{item}'" unless $silent == true
76
+
77
+ # read file, parse data
78
+ file = File.read(item)
79
+
80
+ # validate file
81
+ if (valid_xml?(file) == false) && ($no_validation == false)
82
+ abort("\nError: Invalid XML file '#{item}'") unless $silent == true
83
+ end
84
+
85
+ data = Nokogiri::XML(file)
86
+
87
+ if (item.end_with? ".tmSnippet") || ($is_tm == true)
88
+ @data['completions'] = read_textmate_xml(item, data)
89
+ else
90
+ @data['completions'] = read_sublime_xml(item, data)
91
+ end
92
+
93
+ $input_counter += 1
94
+
95
+ return @data
96
+ end
97
+
98
+ def self.read_sublime_xml(item, data)
99
+
100
+ # get scope
101
+ @data['scope'] = get_scope( data.xpath("//scope")[0].text.strip )
102
+
103
+ trigger = data.xpath("//tabTrigger")[0].text.strip
104
+
105
+ if data.xpath("//description").empty?
106
+ description = trigger
107
+ else
108
+ description = data.xpath("//description")[0].text.strip
109
+ end
110
+
111
+ data.xpath("//content").each do |node|
112
+
113
+ title = filter_str(trigger, @title_filter)
114
+ prefix = filter_str(trigger, @prefix_filter)
115
+ body = filter_str(node.text.strip, @body_filter)
116
+
117
+ if @completions.has_key?(title)
118
+ if $dupes == false
119
+ puts " !! Duplicate trigger #{title.dump} in #{item}" unless $silent == true
120
+ else
121
+ abort("\nError: duplicate trigger '#{title.dump}' in #{item}. Triggers must be unique.")
122
+ end
123
+ end
124
+
125
+ @completions[description] = {
126
+ :prefix => prefix,
127
+ :body => body
128
+ }
129
+ end
130
+
131
+ return @completions
132
+ end
133
+
134
+ def self.read_textmate_xml(item, data)
135
+
136
+ data.xpath('//dict').each do | node |
137
+ node.element_children.map(&:content).each_slice(2) do | k, v |
138
+ case k
139
+ when 'scope'
140
+ @data['scope'] = get_scope(v.to_s)
141
+ when 'name'
142
+ @title = filter_str(v.to_s, @title_filter)
143
+ when 'tabTrigger'
144
+ @prefix = filter_str(v.to_s, @prefix_filter)
145
+ when 'content'
146
+ @body = filter_str(v.to_s.strip, @body_filter)
147
+ else
148
+ next
149
+ end
150
+ end
151
+ end
152
+
153
+ @completions[@title] = {
154
+ :prefix => @prefix,
155
+ :body => @body
156
+ }
157
+
158
+ return @completions
159
+ end
160
+
161
+ def self.read_json(item)
162
+
163
+ puts "\nReading completion file '#{item}'" unless $silent == true
164
+
165
+ # read file
166
+ file = File.read(item)
167
+
168
+ # validate file
169
+ if (valid_json?(file) == false) && $no_validation == false
170
+ abort("\nError: Invalid JSON file '#{item}'") unless $silent == true
171
+ else
172
+ data = JSON.load(file)
173
+ end
174
+
175
+ # get scope
176
+ @data['scope'] = get_scope( data["scope"] )
177
+
178
+ data["completions"].each do |line|
179
+ trigger = line["trigger"]
180
+
181
+ # Next if JSON contains non-standard keys
182
+ if trigger == nil
183
+ puts " >> Ignoring line #{line}" unless $silent == true
184
+ next
185
+ end
186
+
187
+ contents = line["contents"]
188
+
189
+ title = filter_str(trigger, @title_filter)
190
+ prefix = filter_str(trigger, @prefix_filter)
191
+ body = filter_str(contents, @body_filter)
192
+
193
+ if @completions.has_key?(title)
194
+ if $dupes == false
195
+ puts " !! Duplicate trigger #{title.dump} in #{item}" unless $silent == true
196
+ else
197
+ abort("\nError: duplicate trigger '#{title.dump}' in #{item}. Triggers must be unique.") unless $silent == true
198
+ end
199
+ end
200
+
201
+ @completions[title] = {
202
+ :prefix => prefix,
203
+ :body => body
204
+ }
205
+ end
206
+
207
+ @data['completions'] = @completions
208
+
209
+ $input_counter += 1
210
+ return @data
211
+ end
212
+
213
+ # via https://gist.github.com/ascendbruce/7070951
214
+ def self.valid_json?(json)
215
+ JSON.parse(json)
216
+ true
217
+ rescue
218
+ false
219
+ end
220
+
221
+ def self.valid_xml?(xml)
222
+ Nokogiri::XML(xml) { |config| config.options = Nokogiri::XML::ParseOptions::STRICT }
223
+ true
224
+ rescue
225
+ false
226
+ end
227
+
228
+ def self.write_data(item)
229
+ if $output == "json"
230
+ file = get_outname('json', item)
231
+ write_json(@data, file, $split)
232
+ else
233
+ file = get_outname('cson', item)
234
+ write_cson(@data, file, $split)
235
+ end
236
+ end
237
+
238
+ def self.write_json(data, file, many = false)
239
+
240
+ if many == false
241
+
242
+ if $no_comment == true
243
+ json = {
244
+ data['scope'] => data['completions']
245
+ }
246
+ else
247
+ json = {
248
+ :generator => "Atomizr v#{@version} - #{@homepage}",
249
+ data['scope'] => data['completions']
250
+ }
251
+ end
252
+
253
+ puts "Writing '#{file}'" unless $silent == true
254
+ File.open("#{$folder}/#{file}","w") do |f|
255
+ f.write(JSON.pretty_generate(json))
256
+ end
257
+ $output_counter += 1
258
+
259
+ elsif many == true
260
+
261
+ scope = data['scope']
262
+
263
+ data['completions'].each do |item|
264
+
265
+ file = filter_str(item[1][:prefix], @filename_filter)
266
+
267
+ json = {
268
+ data['scope'] => {
269
+ item[0] => {
270
+ 'prefix' => item[1][:prefix],
271
+ 'body' => item[1][:body]
272
+ }
273
+ }
274
+ }
275
+
276
+ puts "Writing '#{file}.json'" unless $silent == true
277
+ File.open("#{$folder}/#{file}.json","w") do |f|
278
+ f.write(JSON.pretty_generate(json))
279
+ end
280
+ $output_counter += 1
281
+ end
282
+ end
283
+ end
284
+
285
+ def self.write_cson(data, item, many = false)
286
+
287
+ if many == false
288
+
289
+ if $no_comment == true
290
+ comment = ""
291
+ else
292
+ comment = "# Generated with #{@name} v#{@version} - #{@homepage}\n"
293
+ end
294
+
295
+ cson = comment
296
+ cson += "'"+data['scope']+"':\n"
297
+
298
+ data['completions'].each do |item|
299
+
300
+ title = item[0]
301
+ prefix = item[1][:prefix]
302
+ body = item[1][:body]
303
+
304
+ if $no_tabstops == false
305
+ body = add_trailing_tabstop(body)
306
+ end
307
+
308
+ cson += " '"+title+"':\n"
309
+ cson += " 'prefix': '"+prefix+"'\n"
310
+ if body.lines.count <= 1
311
+ cson += " 'body': '"+body+"'\n"
312
+ else
313
+ cson += " 'body': \"\"\"\n"
314
+ body.each_line do |line|
315
+ cson += " "+line
316
+ end
317
+ cson +="\n \"\"\"\n"
318
+ end
319
+ end
320
+
321
+ if File.directory?($input[0])
322
+ mkdir("#{$folder}/#{$output}/snippets")
323
+ file = $output + '/snippets/' + item + '.cson'
324
+ else
325
+ file = get_outname('cson', item)
326
+ end
327
+
328
+ puts "Writing '#{file}'" unless $silent == true
329
+ File.open("#{$folder}/#{file}","w") do |f|
330
+ f.write(cson)
331
+ end
332
+ $output_counter += 1
333
+
334
+ elsif many == true
335
+
336
+ scope = data['scope']
337
+
338
+ data['completions'].each do |item|
339
+
340
+ cson = "'"+scope+"':\n"
341
+ title = item[0]
342
+ prefix = item[1][:prefix]
343
+ body = item[1][:body]
344
+
345
+ file = filter_str(prefix, @filename_filter)
346
+
347
+ cson += " '"+title+"':\n"
348
+ cson += " 'prefix': '"+prefix+"'\n"
349
+ if body.lines.count <= 1
350
+ cson += " 'body': '"+body+"'\n"
351
+ else
352
+ cson += " 'body': \"\"\"\n"
353
+ body.each_line do |line|
354
+ cson += " "+line
355
+ end
356
+ cson +="\n \"\"\"\n"
357
+ end
358
+
359
+ puts "Writing '#{file}.cson'" unless $silent == true
360
+ File.open("#{$folder}/#{file}.cson","w") do |f|
361
+ f.write(cson)
362
+ end
363
+ $output_counter += 1
364
+ end
365
+ end
366
+ end
367
+
368
+ def self.delete_file(input)
369
+
370
+ if File.directory?(input)
371
+ puts "\nDeleting '#{input[0..-2]}'" unless $silent == true
372
+ FileUtils.rm_rf(input)
373
+ else
374
+ Dir.glob(input) do |item|
375
+ puts "Deleting '#{item}'" unless $silent == true
376
+ File.delete(item)
377
+ end
378
+ end
379
+
380
+ end
381
+
382
+ def self.get_outname(type, item)
383
+ if $output == type
384
+
385
+ if $input[0].include?("*")
386
+ file = item
387
+ else
388
+ file = $input[0]
389
+ end
390
+ output = File.basename(file, ".*")+"."+type
391
+ else
392
+ output = $output
393
+ end
394
+
395
+ return output
396
+ end
397
+
398
+ def self.get_scope(scope)
399
+
400
+ if $scope == nil
401
+ scope = fix_scope(filter_str(scope, @scope_filter))
402
+ puts "Using default scope '"+scope+"'" unless $silent == true
403
+ else
404
+ scope = fix_scope(filter_str($scope, @scope_filter))
405
+ puts "Override scope using '"+scope+"'" unless $silent == true
406
+ end
407
+
408
+ return scope
409
+ end
410
+
411
+ def self.add_trailing_tabstop(input)
412
+ unless input.match(/\$\d+$/) == nil
413
+ # nothing to do here
414
+ return input
415
+ end
416
+
417
+ stops = input.scan(/\${?(\d+)/)
418
+ if stops.length > 0
419
+ highest = stops.sort.flatten.last.to_i + 1
420
+ return "#{input}$#{highest}"
421
+ end
422
+
423
+ return "#{input}$1"
424
+ end
425
+
426
+ # prepend dot to scope
427
+ def self.fix_scope(scope)
428
+ if scope[0] != "."
429
+ scope = "."+ scope
430
+ end
431
+
432
+ return scope
433
+ end
434
+
435
+ def self.filter_str(input, filter)
436
+
437
+ if filter.any?
438
+ filter.each do |needle, replacement|
439
+ input = input.to_s.gsub(needle, replacement)
440
+ end
441
+ end
442
+
443
+ return input
444
+ end
445
+
446
+ def self.init_hashes()
447
+ @data = Hash.new
448
+ @completions = Hash.new
449
+ end
450
+
451
+ def self.mkdir(folder)
452
+ if !Dir.exists?(folder)
453
+ FileUtils.mkdir_p(folder)
454
+ end
455
+ end
456
+
457
+ def self.copy_file(src, dest, del = false)
458
+ File.write(dest, File.read(src))
459
+
460
+ if del == true
461
+ File.delete(src)
462
+ end
463
+ end
464
+
465
+ end
metadata ADDED
@@ -0,0 +1,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: atomizr
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.18.0
5
+ platform: ruby
6
+ authors:
7
+ - Jan T. Sott
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-08-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.12.5
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.12.5
27
+ - !ruby/object:Gem::Dependency
28
+ name: json
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 2.0.2
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 2.0.2
41
+ - !ruby/object:Gem::Dependency
42
+ name: nokogiri
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 1.6.7.2
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 1.6.7.2
55
+ description: A command-line tool to convert Sublime Text snippets and completions,
56
+ as well as and TextMate snippets into Atom snippets
57
+ email:
58
+ executables:
59
+ - atomizr
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - lib/atomizr.rb
64
+ - bin/atomizr
65
+ homepage: https://github.com/idleberg/atomizr.rb
66
+ licenses:
67
+ - MIT
68
+ metadata: {}
69
+ post_install_message:
70
+ rdoc_options: []
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ requirements: []
84
+ rubyforge_project:
85
+ rubygems_version: 2.0.14.1
86
+ signing_key:
87
+ specification_version: 4
88
+ summary: Converts Sublime Text snippets and completions into Atom format
89
+ test_files: []