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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5b577ed966febadd2751d13f7d5b16f8912d5306
4
+ data.tar.gz: b3f7a8b90827cffb1cdd42e68d38bd648ec67c37
5
+ SHA512:
6
+ metadata.gz: d0ac519cf9c60fb106de04a6d72b894b63174d96cd3c176ed62f17a87a60a2832aba0ec80b4425496c97b7df3a7858d983444deb9e7631e975900df81e4560c4
7
+ data.tar.gz: f593fa22c1652903ba2b5c136d479ebd05178259b1e22906c9c37e976a78faf7715bb582261f9229c27039708cb1c6d17285bac0da2b298b87e32172244a0f63
data/README.md ADDED
@@ -0,0 +1,136 @@
1
+ # Sublime DSL
2
+
3
+ Configuration of Sublime Text using Ruby DSLs.
4
+
5
+ ## Motivation
6
+
7
+ The configuration of Sublime Text uses horrible PList files (TextMate legacy),
8
+ and verbose JSON files. I first used TextMate to develop grammars, but even there,
9
+ typing grammars or specifying themes was a pain.
10
+
11
+ So I had the idea to create a Ruby DSL for TextMate grammars, themes, etc.
12
+ Once you have it, you wonder how you could do without it: all DSL statements
13
+ are regular method calls, so you can use the power of Ruby instead of copy/paste,
14
+ defining and calling your own methods.
15
+
16
+ Refer to SYNTAX.md for more information on the DSLs.
17
+
18
+ ## Installation
19
+
20
+ ```
21
+ $ gem install sublime_dsl
22
+ ```
23
+
24
+ ## Synopsis
25
+
26
+ ```
27
+ $ subdsl import Ruby
28
+ ```
29
+
30
+ This converts the Ruby package (from your ST packages directory) to a set of DSL files
31
+ inside ./Ruby. You can then review and modify the files. Have a look at
32
+ test/DSL.custom/Ruby.tmLanguage.rb for an example of refactoring (here-docs).
33
+ Have a look at test/DSL.custom/SAS for a package created from
34
+ scratch directly with the DSLs.
35
+
36
+ ```
37
+ $ subdsl export Ruby
38
+ ```
39
+
40
+ This converts the Ruby package in ./Ruby and places the files in the
41
+ ST Ruby packages directory (the previous content as backed up into a
42
+ zip archive).
43
+
44
+ Type `subdsl help` for more info on the command.
45
+
46
+ ## Import: Converting to DSL
47
+
48
+ When importing a ST package, some files are grouped into one DSL file:
49
+
50
+ - all macros (*.sublime-macro) are grouped into macros.rb
51
+ - all snippets (*.sublime-snippet, *.tmSnippet) are grouped into snippets.rb
52
+ - all commands (*.sublime-commands) are grouped into commands.rb
53
+ - all preferences (*.tmPreferences) are grouped into preferences.rb
54
+ - all settings (*.sublime-settings) are grouped into settings.rb
55
+
56
+ Other files generate one DSL file:
57
+
58
+ - *.tmLanguage => *.tmLanguage.rb
59
+ - *.sublime-menu => *.menu.rb
60
+ - *.sublime-keymap => *.keymap.rb
61
+ - *.sublime-mousemap => *.mousemap.rb
62
+
63
+ Finally, files with an extension not listed above (e.g., *.py) are copied "as is".
64
+ In particular, there is no DSL (yet?) for *.sublime-build and *.sublime-completions.
65
+
66
+ ## Export: Converting from DSL
67
+
68
+ The export processes all *.rb files located in the DSL directory, and copies
69
+ other files "as is". Therefore, the organization of DSL files is free: a whole
70
+ package can be specified into one source file, or into several source files.
71
+
72
+ ## Tests
73
+
74
+ I don't believe in unit tests. At least not at this stage, where I can change my mind
75
+ and rework the API extensively. I do believe into extensive integration tests, so the
76
+ tests are done as follows:
77
+
78
+ - test/Packages.original and test/Packages.textmate contain the packages coming
79
+ from Sublime Text 2 and TextMate, respectively.
80
+
81
+ - They were converted into DSL equivalents in test/DSL.original and test/DSL.textmate.
82
+
83
+ - Then I tried to convert them back to PList/JSON packages into
84
+ test/Packages.original-round-trip and test/Packages.textmate-round-trip.
85
+
86
+ - Everytime there was an error in the DSL (for instance because of illegal regular expressions),
87
+ I created a fixed DSL file in test/DSL.original-fixes or test/DSL.textmate-fixes, that is read
88
+ instead of the one in test/DSL.original or test/DSL.textmate, to allow round-tripping.
89
+
90
+ - I then compared the result of the round-trip to the original, and checked it was OK.
91
+
92
+ - I then fixed some warnings (FIXME comments in the grammar DSL files) and placed the result
93
+ in test/DSL.modified, exported them in test/Packages.modified, and checked the result.
94
+
95
+ - I also created some tests using my French keyboard definition file, generating DSL in
96
+ test/DSL.original.kb-fr, and exporting them to test/Packages.original.kb-fr-round-trip.
97
+
98
+ - Finally, I placed some custom DSL files in test/DSL.custom, with the export in
99
+ test/Packages.custom.
100
+
101
+ So testing is simple and brute force: run `rake test`, and check that nothing changes,
102
+ or that the changes are expected.
103
+
104
+ ## Contributing
105
+
106
+ I welcome (constructive) criticism and ideas. This was an experience for me to create Ruby DSLs,
107
+ and retrospectively, I'm even more impressed by Ruby. Many thanks to Matz for creating a language
108
+ that makes programming a pleasure.
109
+
110
+ The documentation is sparse, to say the least. Feel free to ask for clarifications:
111
+ a heavy use of `method_missing` does not create an easy to understand piece of software.
112
+
113
+ ## License
114
+
115
+ (The MIT License)
116
+
117
+ Copyright (c) 2014 Thierry Lambert
118
+
119
+ Permission is hereby granted, free of charge, to any person obtaining
120
+ a copy of this software and associated documentation files (the
121
+ 'Software'), to deal in the Software without restriction, including
122
+ without limitation the rights to use, copy, modify, merge, publish,
123
+ distribute, sublicense, and/or sell copies of the Software, and to
124
+ permit persons to whom the Software is furnished to do so, subject to
125
+ the following conditions:
126
+
127
+ The above copyright notice and this permission notice shall be
128
+ included in all copies or substantial portions of the Software.
129
+
130
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
131
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
132
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
133
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
134
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
135
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
136
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,248 @@
1
+ # encoding: utf-8
2
+ $VERBOSE = true
3
+ $LOAD_PATH.unshift File.expand_path('./lib')
4
+ require 'sublime_dsl'
5
+
6
+ include SublimeDSL
7
+ Console.set_ruby_verbosity = false
8
+
9
+ undef rule if self.respond_to? :rule
10
+
11
+ desc 'run a command'
12
+ task 'cmd', 'arguments' do |t, args|
13
+ cmd = Dir['bin/*'].first
14
+ cmd << ' ' << args.arguments if args.arguments
15
+ puts cmd
16
+ system "ruby -w -Ilib #{cmd}"
17
+ end
18
+
19
+ desc 'build & install gem'
20
+ task 'gem' do
21
+ system 'gem build sublime_dsl.gemspec'
22
+ system 'gem install sublime_dsl --local'
23
+ end
24
+
25
+ desc 'create RDoc documentation'
26
+ task 'rdoc' do
27
+ require 'rdoc/rdoc'
28
+ # require 'rdoc/generator/babel'
29
+ FileUtils.rm_r 'doc' if File.directory?('doc')
30
+ rdoc = RDoc::RDoc.new
31
+ args = %w(--force-update --force-output --all) # --hyperlink-all
32
+ args << '-f' << 'babel'
33
+ args << '-c' << 'utf-8'
34
+ args << '--see-standard-ancestors'
35
+ args << '-o' << 'doc'
36
+ args << '-t' << 'Sublime DSL'
37
+ args.concat %w(README.md SYNTAX.md lib)
38
+ rdoc.document(args)
39
+ end
40
+
41
+ desc 'run all tests'
42
+ task 'test' do
43
+ Rake::Task['test:import'].invoke
44
+ Rake::Task['test:export'].invoke
45
+ Rake::Task['test:compare'].invoke
46
+ end
47
+
48
+ namespace 'test' do
49
+
50
+ desc 'import all packages to DSL'
51
+ task 'import', 'profile' do |t, args|
52
+
53
+ if args.profile
54
+ profiling('import', 'original') { import_packages 'original' }
55
+ profiling('import', 'keyboard') { import_packages 'original', true }
56
+ profiling('import', 'textmate') { import_packages 'textmate' }
57
+ else
58
+ import_packages 'original'
59
+ import_packages 'original', true
60
+ import_packages 'textmate'
61
+ end
62
+
63
+ end
64
+
65
+ desc 'import with custom keyboards'
66
+ task 'import_keyboards' do
67
+ import_packages 'original', true
68
+ end
69
+
70
+ desc 'export all DSL to packages'
71
+ task 'export', 'profile' do |t, args|
72
+ if args.profile then
73
+ profiling('export', 'original') { export_packages 'original' }
74
+ profiling('export', 'keyboard') { export_packages 'original.kb-fr' }
75
+ profiling('export', 'modified') { export_packages 'modified' }
76
+ profiling('export', 'textmate') { export_packages 'textmate' }
77
+ profiling('export', 'custom') { export_packages 'custom' }
78
+ else
79
+ export_packages 'original'
80
+ export_packages 'original.kb-fr'
81
+ export_packages 'modified'
82
+ export_packages 'textmate'
83
+ export_packages 'custom'
84
+ end
85
+ end
86
+
87
+ desc 'export custom DSL packages'
88
+ task 'export_custom' do
89
+ export_packages 'custom'
90
+ end
91
+
92
+ desc 'compare JSON after round-trip'
93
+ task 'compare' do
94
+ compare 'original'
95
+ end
96
+
97
+ end
98
+
99
+ def sublime_dir
100
+ SublimeText.packages_dir
101
+ end
102
+
103
+ def capturing_stderr(file)
104
+ prev_stderr, $stderr = $stderr, File.open(file, 'wb:utf-8')
105
+ yield
106
+ $stderr.close
107
+ ensure
108
+ $stderr = prev_stderr
109
+ end
110
+
111
+ def profiling(task, subtask)
112
+ require 'ruby-prof'
113
+ result = RubyProf.profile { yield }
114
+ profile_report "#{task}-#{subtask}", result
115
+ end
116
+
117
+ def profile_report(file, result)
118
+ printer = RubyProf::CallStackPrinter.new(result)
119
+ Dir.mkdir 'prof' unless File.directory?('prof')
120
+ File.open("prof/#{file}.html", 'wb') do |f|
121
+ printer.print f, title: file, application: 'Sublime DSL'
122
+ end
123
+ end
124
+
125
+ def import_packages(suffix, custom_keyboard = false)
126
+ inroot = "test/Packages.#{suffix}"
127
+ altroot = "test/Packages.#{suffix}-fixes"
128
+ altroot = nil unless File.directory?(altroot)
129
+ import_options = { root: inroot, alt_root: altroot }
130
+ write_options = {}
131
+ if custom_keyboard
132
+ suffix << '.kb-fr'
133
+ import_options[:include] = '*.sublime-keymap'
134
+ write_options[:keyboard] = 'AZERTY-fr-FR-v6 (Windows 7)'
135
+ end
136
+ outroot = "test/DSL.#{suffix}"
137
+ write_options.merge! root: outroot, backup: :never
138
+ log = "test/#{suffix}.import.log"
139
+ Console.verbosity = 0
140
+ Console.info "importing #{inroot}", true
141
+ capturing_stderr(log) do
142
+ dirs = Dir[inroot + '/*']
143
+ dirs.each.with_index(1) do |indir, index|
144
+ Console.progress index, dirs.length, indir, true
145
+ name = File.basename(indir)
146
+ p = SublimeText::Package.import(name, import_options)
147
+ p.write write_options if p
148
+ end
149
+ end
150
+ end
151
+
152
+ def export_packages(suffix)
153
+ inroot = "test/DSL.#{suffix}"
154
+ altroot = "test/DSL.#{suffix}-fixes"
155
+ outroot = "test/Packages.#{suffix}"
156
+ outroot << '-round-trip' unless suffix =~ /custom|modified/
157
+ read_options = { root: inroot }
158
+ read_options[:alt_root] = altroot if File.directory?(altroot)
159
+ export_options = { root: outroot, backup: :never, cleanup: true }
160
+ log = "test/#{suffix}.export.log"
161
+ Console.verbosity = 0
162
+ Console.info "exporting #{inroot}", true
163
+ capturing_stderr(log) do
164
+ dirs = Dir[inroot + '/*']
165
+ dirs.each.with_index(1) do |indir, index|
166
+ Console.progress index, dirs.length, indir, true
167
+ name = File.basename(indir)
168
+ p = SublimeText::Package.read(name, read_options)
169
+ p.export export_options if p
170
+ end
171
+ end
172
+ end
173
+
174
+ def compare(suffix)
175
+ extensions = 'macro,commands,menu,keymap,mousemap,settings,build,completions'
176
+ inroot = "test/Packages.#{suffix}"
177
+ altroot = "test/Packages.#{suffix}-fixes"
178
+ outroot = "test/Packages.#{suffix}-round-trip"
179
+ Dir[inroot + "/**/*.sublime-{#{extensions}}"].each do |infile|
180
+ indir = File.dirname(infile)
181
+ altdir = altroot + '/' << File.basename(indir)
182
+ altfile = altdir + '/' << File.basename(infile)
183
+ infile = altfile if File.exist?(altfile)
184
+ outdir = outroot + '/' << File.basename(indir)
185
+ outfile = outdir + '/' << File.basename(infile)
186
+ if File.exist?(outfile)
187
+ compare_json infile, outfile
188
+ else
189
+ # puts outfile
190
+ # puts ' no found'
191
+ end
192
+ end
193
+ end
194
+
195
+ def compare_json(infile, outfile)
196
+ in_text = File.read(infile, encoding: 'utf-8')
197
+ out_text = File.read(outfile, encoding: 'utf-8')
198
+ out_json = JSON[out_text]
199
+ if in_text.empty?
200
+ puts "*** mismatch: #{infile}" unless out_json.empty?
201
+ return
202
+ end
203
+ in_json = JSON[in_text]
204
+ normalize in_json
205
+ normalize out_json
206
+ return if in_json == out_json
207
+ puts "*** mismatch: #{infile}"
208
+ file = infile.gsub('/', '_')
209
+ File.open("#{file}.in", 'wb:utf-8') { |f| f.write JSON.pretty_generate(in_json) }
210
+ File.open("#{file}.out", 'wb:utf-8') { |f| f.write JSON.pretty_generate(out_json) }
211
+ end
212
+
213
+ def normalize(json)
214
+ case json
215
+ when Hash
216
+ h = {}
217
+ json.keys.sort.each do |k|
218
+ v = json[k]
219
+ normalize v
220
+ if k == 'mnemonic'
221
+ v = v.upcase
222
+ elsif k == 'keys' && v.is_a?(Array)
223
+ v = v.map do |spec|
224
+ *mods, key = spec.split(/\+(?!$)/)
225
+ if key == '+' || key == 'plus'
226
+ mods << 'shift'
227
+ key = '='
228
+ elsif key =~ /^(forward_slash|backquote|equals|minus)$/
229
+ map = { 'forward_slash' => '/', 'backquote' => '`', 'equals' => '=', 'minus' => '-' }
230
+ key = map[$1]
231
+ end
232
+ (mods.sort << key).join('+')
233
+ end
234
+ end
235
+ h[k] = v unless
236
+ (k == 'modifiers' && v.empty?) ||
237
+ (k == 'match_all' && v == false)
238
+ end
239
+ json.clear
240
+ json.merge! h
241
+ when Array
242
+ if json.all? { |e| e.is_a?(String) }
243
+ json.sort!
244
+ else
245
+ json.each { |e| normalize e }
246
+ end
247
+ end
248
+ end