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.
- checksums.yaml +7 -0
- data/README.md +136 -0
- data/Rakefile +248 -0
- data/SYNTAX.md +927 -0
- data/bin/subdsl +4 -0
- data/lib/sublime_dsl/cli/export.rb +134 -0
- data/lib/sublime_dsl/cli/import.rb +143 -0
- data/lib/sublime_dsl/cli.rb +125 -0
- data/lib/sublime_dsl/core_ext/enumerable.rb +24 -0
- data/lib/sublime_dsl/core_ext/string.rb +129 -0
- data/lib/sublime_dsl/core_ext.rb +4 -0
- data/lib/sublime_dsl/sublime_text/command.rb +157 -0
- data/lib/sublime_dsl/sublime_text/command_set.rb +112 -0
- data/lib/sublime_dsl/sublime_text/keyboard.rb +659 -0
- data/lib/sublime_dsl/sublime_text/keymap/dsl_reader.rb +194 -0
- data/lib/sublime_dsl/sublime_text/keymap.rb +385 -0
- data/lib/sublime_dsl/sublime_text/macro.rb +91 -0
- data/lib/sublime_dsl/sublime_text/menu.rb +237 -0
- data/lib/sublime_dsl/sublime_text/mouse.rb +149 -0
- data/lib/sublime_dsl/sublime_text/mousemap.rb +185 -0
- data/lib/sublime_dsl/sublime_text/package/dsl_reader.rb +91 -0
- data/lib/sublime_dsl/sublime_text/package/exporter.rb +138 -0
- data/lib/sublime_dsl/sublime_text/package/importer.rb +127 -0
- data/lib/sublime_dsl/sublime_text/package/reader.rb +102 -0
- data/lib/sublime_dsl/sublime_text/package/writer.rb +112 -0
- data/lib/sublime_dsl/sublime_text/package.rb +96 -0
- data/lib/sublime_dsl/sublime_text/setting_set.rb +123 -0
- data/lib/sublime_dsl/sublime_text.rb +48 -0
- data/lib/sublime_dsl/textmate/custom_base_name.rb +45 -0
- data/lib/sublime_dsl/textmate/grammar/dsl_reader.rb +383 -0
- data/lib/sublime_dsl/textmate/grammar/dsl_writer.rb +178 -0
- data/lib/sublime_dsl/textmate/grammar/plist_reader.rb +163 -0
- data/lib/sublime_dsl/textmate/grammar/plist_writer.rb +153 -0
- data/lib/sublime_dsl/textmate/grammar.rb +252 -0
- data/lib/sublime_dsl/textmate/plist.rb +141 -0
- data/lib/sublime_dsl/textmate/preference.rb +301 -0
- data/lib/sublime_dsl/textmate/snippet.rb +437 -0
- data/lib/sublime_dsl/textmate/theme/dsl_reader.rb +87 -0
- data/lib/sublime_dsl/textmate/theme/item.rb +74 -0
- data/lib/sublime_dsl/textmate/theme/plist_writer.rb +53 -0
- data/lib/sublime_dsl/textmate/theme.rb +364 -0
- data/lib/sublime_dsl/textmate.rb +9 -0
- data/lib/sublime_dsl/tools/blank_slate.rb +49 -0
- data/lib/sublime_dsl/tools/console.rb +74 -0
- data/lib/sublime_dsl/tools/helpers.rb +152 -0
- data/lib/sublime_dsl/tools/regexp_wannabe.rb +154 -0
- data/lib/sublime_dsl/tools/stable_inspect.rb +20 -0
- data/lib/sublime_dsl/tools/value_equality.rb +37 -0
- data/lib/sublime_dsl/tools/xml.rb +66 -0
- data/lib/sublime_dsl/tools.rb +66 -0
- data/lib/sublime_dsl.rb +23 -0
- 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
|