sublime_dsl 0.1.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.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +136 -0
  3. data/Rakefile +248 -0
  4. data/SYNTAX.md +927 -0
  5. data/bin/subdsl +4 -0
  6. data/lib/sublime_dsl/cli/export.rb +134 -0
  7. data/lib/sublime_dsl/cli/import.rb +143 -0
  8. data/lib/sublime_dsl/cli.rb +125 -0
  9. data/lib/sublime_dsl/core_ext/enumerable.rb +24 -0
  10. data/lib/sublime_dsl/core_ext/string.rb +129 -0
  11. data/lib/sublime_dsl/core_ext.rb +4 -0
  12. data/lib/sublime_dsl/sublime_text/command.rb +157 -0
  13. data/lib/sublime_dsl/sublime_text/command_set.rb +112 -0
  14. data/lib/sublime_dsl/sublime_text/keyboard.rb +659 -0
  15. data/lib/sublime_dsl/sublime_text/keymap/dsl_reader.rb +194 -0
  16. data/lib/sublime_dsl/sublime_text/keymap.rb +385 -0
  17. data/lib/sublime_dsl/sublime_text/macro.rb +91 -0
  18. data/lib/sublime_dsl/sublime_text/menu.rb +237 -0
  19. data/lib/sublime_dsl/sublime_text/mouse.rb +149 -0
  20. data/lib/sublime_dsl/sublime_text/mousemap.rb +185 -0
  21. data/lib/sublime_dsl/sublime_text/package/dsl_reader.rb +91 -0
  22. data/lib/sublime_dsl/sublime_text/package/exporter.rb +138 -0
  23. data/lib/sublime_dsl/sublime_text/package/importer.rb +127 -0
  24. data/lib/sublime_dsl/sublime_text/package/reader.rb +102 -0
  25. data/lib/sublime_dsl/sublime_text/package/writer.rb +112 -0
  26. data/lib/sublime_dsl/sublime_text/package.rb +96 -0
  27. data/lib/sublime_dsl/sublime_text/setting_set.rb +123 -0
  28. data/lib/sublime_dsl/sublime_text.rb +48 -0
  29. data/lib/sublime_dsl/textmate/custom_base_name.rb +45 -0
  30. data/lib/sublime_dsl/textmate/grammar/dsl_reader.rb +383 -0
  31. data/lib/sublime_dsl/textmate/grammar/dsl_writer.rb +178 -0
  32. data/lib/sublime_dsl/textmate/grammar/plist_reader.rb +163 -0
  33. data/lib/sublime_dsl/textmate/grammar/plist_writer.rb +153 -0
  34. data/lib/sublime_dsl/textmate/grammar.rb +252 -0
  35. data/lib/sublime_dsl/textmate/plist.rb +141 -0
  36. data/lib/sublime_dsl/textmate/preference.rb +301 -0
  37. data/lib/sublime_dsl/textmate/snippet.rb +437 -0
  38. data/lib/sublime_dsl/textmate/theme/dsl_reader.rb +87 -0
  39. data/lib/sublime_dsl/textmate/theme/item.rb +74 -0
  40. data/lib/sublime_dsl/textmate/theme/plist_writer.rb +53 -0
  41. data/lib/sublime_dsl/textmate/theme.rb +364 -0
  42. data/lib/sublime_dsl/textmate.rb +9 -0
  43. data/lib/sublime_dsl/tools/blank_slate.rb +49 -0
  44. data/lib/sublime_dsl/tools/console.rb +74 -0
  45. data/lib/sublime_dsl/tools/helpers.rb +152 -0
  46. data/lib/sublime_dsl/tools/regexp_wannabe.rb +154 -0
  47. data/lib/sublime_dsl/tools/stable_inspect.rb +20 -0
  48. data/lib/sublime_dsl/tools/value_equality.rb +37 -0
  49. data/lib/sublime_dsl/tools/xml.rb +66 -0
  50. data/lib/sublime_dsl/tools.rb +66 -0
  51. data/lib/sublime_dsl.rb +23 -0
  52. metadata +145 -0
@@ -0,0 +1,48 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative 'sublime_text/command'
4
+ require_relative 'sublime_text/command_set'
5
+ require_relative 'sublime_text/keyboard'
6
+ require_relative 'sublime_text/keymap'
7
+ require_relative 'sublime_text/macro'
8
+ require_relative 'sublime_text/menu'
9
+ require_relative 'sublime_text/mouse'
10
+ require_relative 'sublime_text/mousemap'
11
+ require_relative 'sublime_text/package'
12
+ require_relative 'sublime_text/setting_set'
13
+
14
+ module SublimeDSL
15
+ module SublimeText
16
+
17
+ # Path to the Packages directory.
18
+
19
+ def self.packages_dir
20
+ @packages_dir ||=
21
+ case Tools.os
22
+ when :Windows
23
+ ENV['APPDATA'].gsub('\\', '/') << '/Sublime Text 2/Packages'
24
+ when :OSX
25
+ "~/Library/Application Support/Sublime Text 2/Packages"
26
+ when :Linux
27
+ "~/.config/sublime-text-2/Packages"
28
+ else
29
+ raise NotImplementedError,
30
+ "don't know the location of Sublime Text packages on #{os}"
31
+ end
32
+ end
33
+
34
+ # Order a series of config files like ST does:
35
+ # - Default/* first,
36
+ # - then packages alphabetically,
37
+ # - then User/Default*,
38
+ # - then User/* alphabetically.
39
+
40
+ def self.order_config(files)
41
+ default, other = files.partition { |f| f.start_with?('Default') }
42
+ user, other = other.partition { |f| f.start_with?('User') }
43
+ user_default, user_other = user.partition { |f| f.start_with?('User/Default') }
44
+ default.sort + other.sort + user_default.sort + user_other.sort
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,45 @@
1
+ module SublimeDSL
2
+ module TextMate
3
+
4
+ ##
5
+ # Mix-in to define a custom file base name.
6
+ #
7
+ # The includer has a #name method.
8
+
9
+ module CustomBaseName
10
+
11
+
12
+ # Returns #custom_basename if defined, otherwise Tools.filename(name).
13
+ def basename
14
+ custom_basename || Tools.filename(name)
15
+ end
16
+
17
+ # Sets #custom_basename to +base+.
18
+ def basename=(base)
19
+ @basename = base
20
+ end
21
+
22
+ # Returns the custom base name, or +nil+ if none.
23
+ def custom_basename
24
+ # avoid warning on uninitialized instance variable
25
+ if defined?(@basename)
26
+ @basename
27
+ else
28
+ @basename = nil
29
+ end
30
+ end
31
+
32
+ # Returns ", file: '<basename>'" if #basename different from
33
+ # Tools.filename(name). Otherwise sets it to +nil+
34
+ def dsl_file_arg
35
+ if basename != Tools.filename(name)
36
+ ", file: #{custom_basename.to_source}"
37
+ else
38
+ ''
39
+ end
40
+ end
41
+
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,383 @@
1
+ # encoding: utf-8
2
+
3
+ module SublimeDSL
4
+ module TextMate
5
+ class Grammar
6
+
7
+ class DSLReader
8
+
9
+ def initialize(file = nil)
10
+ @grammars = []
11
+ @context_stack = []
12
+ instance_eval File.read(file, encoding: 'utf-8'), file if file
13
+ end
14
+
15
+ def _grammars
16
+ @grammars
17
+ end
18
+
19
+ def _context
20
+ @context_stack.last
21
+ end
22
+
23
+ def _grammar
24
+ @context_stack.first
25
+ end
26
+
27
+ def method_missing(sym, *args, &block)
28
+ raise Error, "'#{sym}' is not a valid grammar DSL statement"
29
+ end
30
+
31
+ def language(options = {}, &block)
32
+ _context and raise Error, "'language' blocks cannot be nested"
33
+ base = options.delete(:file)
34
+ options.length == 0 and raise Error, 'missing name & scope'
35
+ name = options.keys.first
36
+ scope = options.delete(name)
37
+ options.empty? or warn "invalid options: #{options.inspect}"
38
+ g = Grammar.new(name, scope)
39
+ g.basename = base
40
+ @context_stack.push g
41
+ instance_eval(&block)
42
+ @context_stack.pop
43
+ g.complete!
44
+ @grammars << g
45
+ end
46
+
47
+ def file_types(array)
48
+ ensure_writer
49
+ _grammar.file_types = array
50
+ end
51
+
52
+ def first_line_match(re)
53
+ ensure_writer
54
+ _grammar.first_line_match = Tools::RegexpWannabe.new(re.source)
55
+ end
56
+
57
+ def folding_start_marker(re)
58
+ ensure_writer
59
+ _grammar.folding_start_marker = Tools::RegexpWannabe.new(re.source)
60
+ end
61
+
62
+ def folding_stop_marker(re)
63
+ ensure_writer
64
+ _grammar.folding_stop_marker = Tools::RegexpWannabe.new(re.source)
65
+ end
66
+
67
+ def key_equivalent(str)
68
+ ensure_writer
69
+ _grammar.key_equivalent = str
70
+ end
71
+
72
+ def uuid(str)
73
+ ensure_writer
74
+ _grammar.uuid = str
75
+ end
76
+
77
+ def bundle_uuid(str)
78
+ ensure_writer
79
+ _grammar.bundle_uuid = str
80
+ end
81
+
82
+ def include(name_or_module)
83
+ if name_or_module.is_a?(Module)
84
+ self.class.send :include, name_or_module
85
+ else
86
+ ensure_reader :patterns
87
+ _context.patterns << Include.new(name_or_module)
88
+ end
89
+ end
90
+
91
+ def fragment(name)
92
+ ensure_reader :fragments
93
+ f = Fragment.new(name.to_s) # FIXME pas la peine de sortir des :sym pour faire du to_s!
94
+ _grammar.fragments << f
95
+ @context_stack.push f
96
+ yield self # block.call
97
+ @context_stack.pop
98
+ end
99
+
100
+ def rule(scope = nil)
101
+ ensure_reader :patterns
102
+ b = RuleBuilder.new(scope)
103
+ @context_stack.push b
104
+ yield self # block.call
105
+ @context_stack.pop
106
+ b.rule.complete! _grammar
107
+ _context.patterns << b.rule
108
+ end
109
+
110
+ def match(re, captures = {})
111
+ ensure_reader
112
+ _context.match re, captures
113
+ end
114
+
115
+ def from(re, captures = {})
116
+ ensure_reader
117
+ _context.from re, captures
118
+ end
119
+
120
+ def to(re, captures = {})
121
+ ensure_reader
122
+ _context.to re, captures
123
+ end
124
+
125
+ def both(captures)
126
+ ensure_reader
127
+ _context.both captures
128
+ end
129
+
130
+ def content_scope(scope)
131
+ ensure_writer
132
+ _context.content_scope = scope
133
+ end
134
+
135
+ def to_last(value)
136
+ ensure_writer
137
+ _context.to_last = value
138
+ end
139
+
140
+ def disabled(value)
141
+ ensure_writer
142
+ _context.disabled = value
143
+ end
144
+
145
+ def ensure_reader(method = nil)
146
+ name = caller[0].gsub(/.*`(.*)'$/, '\1')
147
+ method ||= name
148
+ _context.respond_to? method or raise invalid_context(name)
149
+ end
150
+
151
+ def ensure_writer(method = nil)
152
+ name = caller[0].gsub(/.*`(.*)'$/, '\1')
153
+ method ||= name + '='
154
+ _context.respond_to? method or raise invalid_context(name)
155
+ end
156
+
157
+ def invalid_context(name)
158
+ if _context
159
+ c = _context.class.name.split('::').last.snake_case
160
+ c = 'rule' if c == 'rule_builder'
161
+ "#{name} is invalid inside '#{c}'"
162
+ else
163
+ "#{name} is invalid outside 'language'"
164
+ end
165
+ end
166
+
167
+ end
168
+
169
+ class RuleBuilder
170
+
171
+ attr_accessor :state
172
+
173
+ def initialize(scope)
174
+ @state = State.new(self)
175
+ @state.scope = scope
176
+ end
177
+
178
+ def disabled=(value)
179
+ state.disabled = value
180
+ end
181
+
182
+ def match(re, captures)
183
+ state.match re, captures
184
+ end
185
+
186
+ def from(re, captures)
187
+ state.from re, captures
188
+ end
189
+
190
+ def to(re, captures)
191
+ state.to re, captures
192
+ end
193
+
194
+ def both(captures)
195
+ state.both captures
196
+ end
197
+
198
+ def content_scope=(scope)
199
+ state.content_scope = scope
200
+ end
201
+
202
+ def to_last=(value)
203
+ state.to_last = value
204
+ end
205
+
206
+ def patterns
207
+ state.patterns
208
+ end
209
+
210
+ def rule
211
+ state.rule
212
+ end
213
+
214
+ class State
215
+
216
+ attr_accessor :builder
217
+ attr_accessor :scope, :comment, :disabled
218
+
219
+ def initialize(builder)
220
+ @scope = @comment = @disabled = nil
221
+ @builder = builder
222
+ @patterns = []
223
+ end
224
+
225
+ def match(re, captures)
226
+ @patterns.empty? or raise Error, "a 'match' rule cannot contain 'include' or 'rule'"
227
+ s = state_for(MatchState)
228
+ s.match re, captures
229
+ end
230
+
231
+ def from(re, captures)
232
+ s = state_for(BeginEndState)
233
+ s.from re, captures
234
+ end
235
+
236
+ def to(re, captures)
237
+ s = state_for(BeginEndState)
238
+ s.to re, captures
239
+ end
240
+
241
+ def both(captures)
242
+ s = state_for(BeginEndState)
243
+ s.both captures
244
+ end
245
+
246
+ def content_scope=(scope)
247
+ s = state_for(BeginEndState)
248
+ s.content_scope = scope
249
+ end
250
+
251
+ def to_last=(value)
252
+ s = state_for(BeginEndState)
253
+ s.to_last = value
254
+ end
255
+
256
+ def patterns
257
+ @patterns
258
+ end
259
+
260
+ def rule
261
+ r = NoMatchRule.new
262
+ init r
263
+ r.patterns.concat @patterns
264
+ r
265
+ end
266
+
267
+ private
268
+
269
+ def state_for(klass)
270
+ s = klass.new(builder)
271
+ s.scope = scope
272
+ s.comment = comment
273
+ s.disabled = disabled
274
+ s.patterns.concat @patterns unless @patterns.empty?
275
+ @builder.state = s
276
+ end
277
+
278
+ def init(rule)
279
+ rule.scope = @scope
280
+ rule.comment = @comment
281
+ rule.disabled = @disabled
282
+ end
283
+
284
+ end
285
+
286
+ class MatchState < State
287
+
288
+ def match(re, captures)
289
+ @match = Match.new(Tools::RegexpWannabe.new(re.source))
290
+ @match.captures.merge! captures
291
+ end
292
+
293
+ def from(re, captures)
294
+ raise Error, "'from' is invalid with 'match'"
295
+ end
296
+
297
+ def to(re, captures)
298
+ raise Error, "'to' is invalid with 'match'"
299
+ end
300
+
301
+ def both(captures)
302
+ raise Error, "'both' is invalid with 'match'"
303
+ end
304
+
305
+ def content_scope=(scope)
306
+ raise Error, "'content_scope' is invalid with 'match'"
307
+ end
308
+
309
+ def to_last=(value)
310
+ raise Error, "'to_last' is invalid with 'match'"
311
+ end
312
+
313
+ def patterns
314
+ raise Error, "'patterns' is invalid with 'match'"
315
+ end
316
+
317
+ def rule
318
+ r = MatchRule.new
319
+ init r
320
+ r.match = @match
321
+ r
322
+ end
323
+
324
+ end
325
+
326
+ class BeginEndState < State
327
+
328
+ def initialize(builder)
329
+ super builder
330
+ @from = @to = nil
331
+ @both = {}
332
+ @content_scope = nil
333
+ @to_last = nil
334
+ end
335
+
336
+ def match(re, captures)
337
+ raise Error, "'match' is invalid with 'from' or 'to'"
338
+ end
339
+
340
+ def from(re, captures)
341
+ @from and raise Error, "'from' called twice"
342
+ @from = Match.new(Tools::RegexpWannabe.new(re.source))
343
+ @from.captures.merge! captures
344
+ end
345
+
346
+ def to(re, captures)
347
+ @to and raise Error, "'to' called twice"
348
+ @to = Match.new(Tools::RegexpWannabe.new(re.source, @from))
349
+ @to.captures.merge! captures
350
+ end
351
+
352
+ def both(captures)
353
+ @both.empty? or raise Error, "'both' already specified"
354
+ @both.merge! captures
355
+ end
356
+
357
+ def content_scope=(scope)
358
+ @content_scope = scope
359
+ end
360
+
361
+ def to_last=(value)
362
+ @to_last = value
363
+ end
364
+
365
+ def rule
366
+ r = BeginEndRule.new
367
+ init r
368
+ r.content_scope = @content_scope
369
+ r.from = @from
370
+ r.to = @to
371
+ r.to_last = @to_last
372
+ r.captures.merge! @both
373
+ r.patterns.concat @patterns
374
+ r
375
+ end
376
+
377
+ end
378
+
379
+ end
380
+
381
+ end
382
+ end
383
+ end
@@ -0,0 +1,178 @@
1
+ # encoding: utf-8
2
+
3
+ module SublimeDSL
4
+ module TextMate
5
+ class Grammar
6
+
7
+ ##
8
+ # Creates the DSL for a grammar.
9
+
10
+ class DSLWriter
11
+
12
+ attr_reader :grammar
13
+ attr_reader :io
14
+
15
+ def initialize(grammar)
16
+ @grammar = grammar
17
+ @io = StringIO.new('', 'wb:utf-8')
18
+ output_grammar
19
+ end
20
+
21
+ def dsl
22
+ io.string
23
+ end
24
+
25
+ private
26
+
27
+ def output_grammar
28
+
29
+ args = "#{grammar.name.to_source} => #{grammar.scope.to_source}"
30
+ args << grammar.dsl_file_arg
31
+
32
+ # grammar start
33
+ io.puts "language #{args} do"
34
+ indent = ' '
35
+
36
+ # comment
37
+ output_comment grammar.comment, indent
38
+
39
+ # file types
40
+ if grammar.file_types && !grammar.file_types.empty?
41
+ if grammar.file_types.any? { |t| t =~ /\s/ }
42
+ array = grammar.file_types.map { |t| "'" << t << "'" }.join(', ')
43
+ io.puts indent + "file_types [#{array}]"
44
+ else
45
+ list = grammar.file_types.join(' ')
46
+ if list.length <= 80
47
+ io.puts indent + "file_types %w(#{list})"
48
+ else
49
+ io.puts indent + "file_types %w("
50
+ io.puts list.wrap.indent(indent.length + 2)
51
+ io.puts indent + ")"
52
+ end
53
+ end
54
+ else
55
+ io.puts indent + '# FIXME: no file types'
56
+ end
57
+
58
+ # regexps
59
+ %w(firstLineMatch foldingStartMarker foldingStopMarker).each do |att|
60
+ met = att.snake_case
61
+ re = grammar.send(met)
62
+ next unless re
63
+ io.print re.fixme_comments(indent)
64
+ io.puts indent + met + ' ' + re.inspect(true)
65
+ end
66
+
67
+ # TextMate stuff
68
+ %w(keyEquivalent uuid bundleUUID).each do |att|
69
+ met = att.snake_case
70
+ str = grammar.send(met)
71
+ next unless str
72
+ io.puts indent + met + ' ' + str.inspect + ' # TextMate only'
73
+ end
74
+
75
+ # patterns
76
+ io.puts
77
+ output_array grammar.patterns, indent
78
+
79
+ # repository
80
+ grammar.fragments.each { |f| output_fragment f, indent }
81
+
82
+ # grammar end
83
+ io.puts 'end'
84
+
85
+ end
86
+
87
+ def output_object(object, indent)
88
+ k = object.class.name.split('::').last.snake_case
89
+ send "output_#{k}", object, indent
90
+ end
91
+
92
+ def output_array(list, indent)
93
+ list.each { |o| output_object o, indent }
94
+ end
95
+
96
+ def output_fragment(f, indent)
97
+ io.puts
98
+ io.puts indent + '# FIXME: this fragment is never referenced' unless f.used
99
+ io.puts "#{indent}fragment #{f.name.to_sym.inspect} do"
100
+ i = indent + ' '
101
+ output_comment f.comment, i
102
+ output_array f.patterns, i
103
+ io.puts indent + 'end'
104
+ end
105
+
106
+ def output_include(inc, indent)
107
+ output_comment inc.comment, indent
108
+ io.puts indent + 'include ' + inc.fragment_name.inspect
109
+ end
110
+
111
+ def output_no_match_rule(rule, indent)
112
+ io.puts indent + '# FIXME: no "match" nor "begin/end"' unless !rule.patterns.empty? &&
113
+ (rule.scope && rule.scope.start_with?('meta.') || !rule.disabled.nil?)
114
+ output_rule_start rule, indent
115
+ output_array rule.patterns, indent + ' '
116
+ io.puts indent + 'end'
117
+ end
118
+
119
+ def output_match_rule(rule, indent)
120
+ output_rule_start rule, indent
121
+ output_match 'match', rule.match, indent + ' '
122
+ io.puts indent + 'end'
123
+ end
124
+
125
+ def output_begin_end_rule(rule, indent)
126
+ output_rule_start rule, indent
127
+ i = indent + ' '
128
+ io.puts i + 'content_scope ' + rule.content_scope.inspect if rule.content_scope
129
+ output_match 'from', rule.from, i
130
+ output_match 'to', rule.to, i
131
+ unless rule.captures.empty?
132
+ io.print i + 'both '
133
+ output_captures rule.captures, i + ' '
134
+ end
135
+ output_boolean 'to_last', rule.to_last, i
136
+ output_array rule.patterns, i
137
+ io.puts indent + 'end'
138
+ end
139
+
140
+ def output_rule_start(rule, indent)
141
+ io.puts indent + (rule.scope ? "rule '#{rule.scope}' do" : 'rule do')
142
+ indent += ' '
143
+ output_comment rule.comment, indent
144
+ output_boolean 'disabled', rule.disabled, indent
145
+ end
146
+
147
+ def output_match(name, match, indent)
148
+ io.print match.regexp.fixme_comments(indent)
149
+ io.print indent + name + ' ' + match.regexp.inspect(true)
150
+ if match.captures.empty?
151
+ io.puts
152
+ else
153
+ io.print ",\n" << indent + ' '
154
+ output_captures match.captures, indent + ' '
155
+ end
156
+ end
157
+
158
+ def output_captures(captures, indent)
159
+ lines = []
160
+ captures.each_pair do |number, scope|
161
+ lines << number.to_s + " => '" + scope + "'"
162
+ end
163
+ io.puts lines.join(",\n" + indent)
164
+ end
165
+
166
+ def output_boolean(name, value, indent)
167
+ io.puts indent + name + ' ' + (value.to_i == 0 ? 'false' : 'true') if value
168
+ end
169
+
170
+ def output_comment(text, indent)
171
+ text and text.each_line { |l| io.puts indent + ('# ' + l).rstrip }
172
+ end
173
+
174
+ end
175
+
176
+ end
177
+ end
178
+ end