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,301 @@
1
+ # encoding: utf-8
2
+
3
+ module SublimeDSL
4
+ module TextMate
5
+
6
+ ##
7
+ # A named set of preferences.
8
+
9
+ class Preference
10
+
11
+ def self.import(file)
12
+ root = PList.import(file)
13
+ pref = new.tap do |p|
14
+ p.name = root.delete('name')
15
+ p.scope = root.delete('scope')
16
+ p.settings = root.delete('settings')
17
+ p.uuid = root.delete('uuid')
18
+ p.bundle_uuid = root.delete('bundleUUID')
19
+ end
20
+ root.empty? or raise Error, "unexpected keys: #{root.keys.inspect}"
21
+ base = File.basename(file, File.extname(file))
22
+ pref.name ||= base
23
+ pref.basename = base
24
+ pref.settings.each_pair do |key, value|
25
+ case key
26
+ when /showInSymbolList|disableDefaultCompletion|spellChecking/
27
+ pref.settings[key] = value.to_s == '1'
28
+ when /completion(s|Command)/i
29
+ # leave as is
30
+ when /pattern|completion|folding(Start|Stop)Marker|foldingIndentedBlock(Start|Ignore)/i
31
+ pref.settings[key] = Tools::RegexpWannabe.new(value)
32
+ when /symbolTransformation|completionCommand/
33
+ pref.settings[key] = value.strip.lines.map(&:strip).join("\n")
34
+ end
35
+ end
36
+
37
+ pref
38
+ end
39
+
40
+ include CustomBaseName
41
+
42
+ attr_accessor :name
43
+ attr_accessor :scope
44
+ attr_accessor :settings
45
+ attr_accessor :uuid
46
+ attr_accessor :bundle_uuid
47
+
48
+ def initialize
49
+ @settings = {}
50
+ end
51
+
52
+ alias to_s name
53
+
54
+ def to_dsl
55
+ out = StringIO.new('', 'wb:utf-8')
56
+ out.puts "# FIXME: no scope" unless scope
57
+ args = "#{name.to_source} => #{scope ? scope.to_source : 'nil'}"
58
+ args << dsl_file_arg
59
+ out.puts "preferences #{args} do"
60
+ settings.each_pair do |key, value|
61
+ if value.is_a?(Array)
62
+ case key
63
+ when 'completions'
64
+ out.puts ' completions %w('
65
+ out.puts "#{value.join(' ').wrap.indent(4)}"
66
+ out.puts ' )'
67
+ when 'shellVariables'
68
+ value.each do |h|
69
+ out.puts " shell_variable #{h['name'].inspect_sq}, #{h['value'].inspect_sq}"
70
+ end
71
+ when 'smartTypingPairs', 'highlightPairs'
72
+ output_pairs key, value, out
73
+ else
74
+ raise Error, "unexpected Array setting: #{key}"
75
+ end
76
+ elsif value.is_a?(Hash)
77
+ key == 'indentedSoftWrap' or raise Error, "unexpected Hash setting: #{key}"
78
+ value.keys.sort == %w(format match) or
79
+ raise Error, "unexpected argument for indentedSoftWrap: #{value.inspect}"
80
+ m = Tools::RegexpWannabe.new(value['match'])
81
+ out.print m.fixme_comments
82
+ out.puts ' indented_soft_wrap match: ' << m.inspect << ', format: ' << value['format'].to_source
83
+ elsif value.is_a?(Tools::RegexpWannabe)
84
+ out.print value.fixme_comments
85
+ out.puts ' ' << key.snake_case << ' ' << value.inspect(true)
86
+ elsif key == 'comment'
87
+ value.each_line { |l| out.puts ' # ' + l.rstrip }
88
+ elsif key =~ /symbolTransformation|completionCommand/ && value.include?("\n")
89
+ out.puts ' ' << key.snake_case << " <<-'TXT'\n#{value.indent(4)}\n TXT"
90
+ else
91
+ out.puts ' ' << key.snake_case << ' ' << (value.is_a?(String) ? value.to_source(true) : value.inspect)
92
+ end
93
+ end
94
+ out.puts " uuid #{uuid.inspect_dq}" if uuid
95
+ out.puts " bundle_uuid #{bundle_uuid.inspect_dq}" if bundle_uuid
96
+ out.puts 'end'
97
+ out.string
98
+ end
99
+
100
+ def export(dir)
101
+ file = "#{dir}/#{basename}.tmPreferences"
102
+ PListWriter.new(self).export(file)
103
+ end
104
+
105
+ def output_pairs(name, array, out)
106
+ simple, complex = array.partition { |o,c| o.length == 1 && c.length == 1 }
107
+ unless simple.empty? && !complex.empty?
108
+ simple = simple.map { |o,c| o+c }.join(' ')
109
+ out.puts " #{name.snake_case} #{simple.inspect_dq}"
110
+ end
111
+ return if complex.empty?
112
+ name = name[0..-2] # remove the 's'
113
+ complex.each do |o,c|
114
+ o =~ %r'^/(.+)/$' or
115
+ raise Error, "unexpected value for a #{name}: #{o.inspect}"
116
+ o = Tools::RegexpWannabe.new($1)
117
+ c =~ %r'^/(.+)/$' or
118
+ raise Error, "unexpected value for a #{name}: #{c.inspect}"
119
+ c = Tools::RegexpWannabe.new($1)
120
+ out.print o.fixme_comments
121
+ out.print c.fixme_comments
122
+ out.puts " #{name.snake_case} #{o.inspect(true)}, #{c.inspect}"
123
+ end
124
+ end
125
+
126
+
127
+ class DSLReader
128
+
129
+ def initialize(file = nil)
130
+ @preferences = []
131
+ @current_pref = nil
132
+ instance_eval File.read(file, encoding: 'utf-8'), file if file
133
+ end
134
+
135
+ def _preferences
136
+ @preferences
137
+ end
138
+
139
+ def method_missing(sym, *args, &block)
140
+ if @current_pref
141
+ store_setting sym.to_s, args
142
+ else
143
+ raise Error, "'#{sym}': only 'preferences' blocks are allowed"
144
+ end
145
+ end
146
+
147
+ def preferences(options = {}, &block)
148
+ @current_pref and raise Error, "preferences blocks cannot be nested"
149
+ file = options.delete(:file)
150
+ options.length == 0 and raise Error, 'missing name & scope'
151
+ name = options.keys.first
152
+ scope = options.delete(name)
153
+ options.length == 0 or
154
+ warn "extraneous 'preferences' arguments ignored: #{options.inspect}"
155
+ @current_pref = Preference.new.tap do |p|
156
+ p.name = name
157
+ p.scope = scope
158
+ p.basename = file
159
+ end
160
+ instance_eval(&block)
161
+ @preferences << @current_pref
162
+ @current_pref = nil
163
+ end
164
+
165
+ def shell_variable(name, value)
166
+ ensure_context __method__
167
+ array = @current_pref.settings['shellVariables'] ||= []
168
+ array << { name: name, value: value }
169
+ end
170
+
171
+ def smart_typing_pairs(string)
172
+ ensure_context __method__
173
+ store_pairs 'smartTypingPairs', string
174
+ end
175
+
176
+ def highlight_pairs(string)
177
+ ensure_context __method__
178
+ store_pairs 'highlightPairs', string
179
+ end
180
+
181
+ def smart_typing_pair(open, close)
182
+ ensure_context __method__
183
+ store_pair 'smartTypingPairs', open, close
184
+ end
185
+
186
+ def highlight_pair(open, close)
187
+ ensure_context __method__
188
+ store_pair 'highlightPairs', open, close
189
+ end
190
+
191
+ def indented_soft_wrap(options = {})
192
+ ensure_context __method__
193
+ match = options.delete(:match)
194
+ format = options.delete(:format)
195
+ match && format or raise Error, "#{__method__} requires 'match' and 'format'"
196
+ options.empty? or warn "#{__method__} options ignored: #{options.inspect}"
197
+ @current_pref.settings['indentedSoftWrap'] =
198
+ { match: _re(match.source), format: format }
199
+ end
200
+
201
+ private
202
+
203
+ def store_pairs(property, string)
204
+ array = @current_pref.settings[property] ||= []
205
+ string.split(/\s+/).each do |pair|
206
+ pair.length == 2 or raise Error, "invalid pair '#{pair}': only 2 characters allowed"
207
+ array << pair.split(//)
208
+ end
209
+ end
210
+
211
+ def store_pair(property, open, close)
212
+ array = @current_pref.settings[property] ||= []
213
+ array << [_re(open.source), _re(close.source)]
214
+ end
215
+
216
+ def store_setting(name, args)
217
+ case name
218
+ when 'uuid', 'bundle_uuid'
219
+ @current_pref.send "#{name}=", args.first
220
+ else
221
+ prop = name.camel_case
222
+ arg = args.first
223
+ if prop =~ /symbolTransformation|completionCommand/
224
+ arg = arg.lines.map(&:strip).join("\n")
225
+ else
226
+ arg = _re(arg.source) if arg.is_a?(Regexp)
227
+ end
228
+ @current_pref.settings[prop] = arg
229
+ end
230
+ # raise Error, "'#{name}' is not a valid preference"
231
+ end
232
+
233
+ def ensure_context(name)
234
+ @current_pref or raise Error, "#{name} is invalid outside a 'preferences' block"
235
+ end
236
+
237
+ def _re(s)
238
+ Tools::RegexpWannabe.new(s)
239
+ end
240
+
241
+ end
242
+
243
+
244
+ class PListWriter
245
+
246
+ attr_reader :preference
247
+ attr_reader :root
248
+
249
+ def initialize(preference)
250
+ @preference = preference
251
+ @root = {}
252
+ convert
253
+ end
254
+
255
+ def export(file)
256
+ PList.export(root, file)
257
+ end
258
+
259
+ private
260
+
261
+ def convert
262
+ root['name'] = preference.name
263
+ root['scope'] = preference.scope if preference.scope
264
+ root['uuid'] = preference.uuid if preference.uuid
265
+ root['bundleUUID'] = preference.bundle_uuid if preference.bundle_uuid
266
+ root['settings'] = convert_hash(preference.settings)
267
+ end
268
+
269
+ def convert_object(object)
270
+ k = object.class.name.split('::').last.snake_case
271
+ send "convert_#{k}", object
272
+ end
273
+
274
+ def convert_array(list)
275
+ list.map { |o| convert_object o }
276
+ end
277
+
278
+ def convert_hash(h)
279
+ out = {}
280
+ h.each_pair { |k,v| out[k] = convert_object(v) }
281
+ out
282
+ end
283
+
284
+ def convert_regexp_wannabe(re)
285
+ re.to_s
286
+ end
287
+
288
+ def convert_string(v) v end
289
+ def convert_fixnum(v) v end
290
+ def convert_nil_class(v) v end
291
+
292
+ # TODO: booleans as 1/0 or true/false?
293
+ def convert_true_class(v) 1 end
294
+ def convert_false_class(v) 0 end
295
+
296
+ end
297
+
298
+ end
299
+
300
+ end
301
+ end