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
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