sublime_dsl 0.1.1

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