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
data/bin/subdsl ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'sublime_dsl'
4
+ SublimeDSL::CLI.new(File.basename(__FILE__)).run
@@ -0,0 +1,134 @@
1
+ module SublimeDSL
2
+ class CLI
3
+
4
+ class Export
5
+
6
+ def self.summary
7
+ "convert a set of DSL source files to a Sublime Text package"
8
+ end
9
+
10
+ attr_reader :main_cmd
11
+ attr_reader :reader, :exporter
12
+
13
+ def initialize(main_cmd)
14
+ @main_cmd = main_cmd
15
+ end
16
+
17
+ def run
18
+ parse_options
19
+ package = reader.package
20
+ exit 1 unless package
21
+ exporter.export package
22
+ exit
23
+ rescue OptionError => ex
24
+ Console.error ex.message
25
+ Console.error %(type "#{main_cmd.name} help export" for more information)
26
+ exit 2
27
+ end
28
+
29
+ def error(msg)
30
+ raise OptionError, msg
31
+ end
32
+
33
+ def parse_options
34
+ package_name = ARGV.shift
35
+ error 'missing package name' unless package_name
36
+ @reader = SublimeText::Package::Reader.new(package_name)
37
+ @exporter = SublimeText::Package::Exporter.new
38
+ while (option = ARGV.shift)
39
+ case option
40
+ when '-s', '--source'
41
+ path = ARGV.shift
42
+ path or error "#{option}: missing path"
43
+ reader.root = path
44
+ when '-i', '--include'
45
+ pattern = ARGV.shift
46
+ pattern or error "#{option}: missing pattern"
47
+ reader.include = pattern
48
+ when '-e', '--exclude'
49
+ pattern = ARGV.shift
50
+ pattern or error "#{option}: missing pattern"
51
+ reader.exclude = pattern
52
+ when '-t', '--target'
53
+ path = ARGV.shift
54
+ path or error "#{option}: missing path"
55
+ exporter.root = path
56
+ when '-b', '--backup'
57
+ policy = ARGV.shift
58
+ case policy
59
+ when nil
60
+ policy = :always
61
+ when /^-/
62
+ ARGV.unshift policy
63
+ policy = :always
64
+ end
65
+ exporter.backup = policy
66
+ when '-c', '--cleanup'
67
+ exporter.cleanup = true
68
+ when '-q', '--quiet'
69
+ Console.verbosity = 0
70
+ when '-V', '--verbose'
71
+ Console.verbosity = 2
72
+ else
73
+ error "invalid argument: #{option.inspect}"
74
+ end
75
+ end
76
+ ARGV.empty? or error "invalid arguments: #{ARGV.join(' ')}"
77
+ end
78
+
79
+ def help
80
+ <<-HELP.dedent
81
+ Usage: #{main_cmd.name} export <package> [options]
82
+
83
+ <package>
84
+ The name of a directory containing the DSL files to process.
85
+
86
+ Options:
87
+ -s, --source PATH
88
+ The path to the directory containing the directory <package>.
89
+ By default, it is the current directory.
90
+
91
+ -i, --include PATTERNS
92
+ Files to process or copy. By default, all files are processed
93
+ (except the files defined by the --exclude option).
94
+ PATTERNS must be a list of specifications separated by semicolons,
95
+ for instance "*.rb;*.py".
96
+
97
+ -e, --exclude PATTERNS
98
+ Files not to process nor copy. By default "*.keyboard.rb"
99
+ (keyboard definition files are processed when called from
100
+ keymap definitions).
101
+
102
+ -t, --target PATH
103
+ The path to the target root directory: the generated files
104
+ will be placed there in a subdirectory named <package>.
105
+ By default, it is the Sublime Text Packages directory.
106
+
107
+ -b, --backup [POLICY]
108
+ Define the backup policy if the directory <target_path>/<package>
109
+ already exists and contains files. POLICY may be:
110
+ never: do not create a backup
111
+ always: always create a backup
112
+ once: create a backup if there is not already one
113
+ Not giving a policy (-b or --backup alone) is the same
114
+ as 'always'. The default is 'always'.
115
+ The backup will be a zip file placed in <target_path>,
116
+ named <package_name>.<time_stamp>.zip
117
+
118
+ -c, --cleanup
119
+ Delete any file in the directory <target_path>/<package>
120
+ that is not generated by the export process. By default,
121
+ no file is removed from the target directory.
122
+
123
+ -q, --quiet
124
+ Do not report any information or warning.
125
+
126
+ -V, --verbose
127
+ Report detailed progress information.
128
+ HELP
129
+ end
130
+
131
+ end
132
+
133
+ end
134
+ end
@@ -0,0 +1,143 @@
1
+ module SublimeDSL
2
+ class CLI
3
+
4
+ class Import
5
+
6
+ def self.summary
7
+ "convert a Sublime Text package to a set of DSL source files"
8
+ end
9
+
10
+ attr_reader :main_cmd
11
+ attr_reader :importer, :writer
12
+
13
+ def initialize(main_cmd)
14
+ @main_cmd = main_cmd
15
+ end
16
+
17
+ def run
18
+ parse_options
19
+ package = importer.package
20
+ exit 1 unless package
21
+ writer.write package
22
+ exit
23
+ rescue OptionError => ex
24
+ Console.error ex.message
25
+ Console.error %(type "#{main_cmd.name} help import" for more information)
26
+ exit 2
27
+ end
28
+
29
+ def error(msg)
30
+ raise OptionError, msg
31
+ end
32
+
33
+ def parse_options
34
+ package_name = ARGV.shift
35
+ error 'missing package name' unless package_name
36
+ @importer = SublimeText::Package::Importer.new(package_name)
37
+ @writer = SublimeText::Package::Writer.new
38
+ while (option = ARGV.shift)
39
+ case option
40
+ when '-s', '--source'
41
+ path = ARGV.shift
42
+ path or error "#{option}: missing path"
43
+ importer.root = path
44
+ when '-i', '--include'
45
+ pattern = ARGV.shift
46
+ pattern or error "#{option}: missing pattern"
47
+ importer.include = pattern
48
+ when '-e', '--exclude'
49
+ pattern = ARGV.shift
50
+ pattern or error "#{option}: missing pattern"
51
+ importer.exclude = pattern
52
+ when '-t', '--target'
53
+ path = ARGV.shift
54
+ path or error "#{option}: missing path"
55
+ writer.root = path
56
+ when '-k', '--keyboard'
57
+ keyboard = ARGV.shift
58
+ keyboard or error "#{option}: missing keyboard"
59
+ writer.keyboard = keyboard
60
+ when '-r', '--recognized'
61
+ writer.copy_other_files = false
62
+ when '-b', '--backup'
63
+ policy = ARGV.shift
64
+ case policy
65
+ when nil
66
+ policy = :always
67
+ when /^-/
68
+ ARGV.unshift policy
69
+ policy = :always
70
+ end
71
+ writer.backup = policy
72
+ when '-q', '--quiet'
73
+ Console.verbosity = 0
74
+ when '-V', '--verbose'
75
+ Console.verbosity = 2
76
+ else
77
+ error "invalid argument: #{option.inspect}"
78
+ end
79
+ end
80
+ ARGV.empty? or error "invalid arguments: #{ARGV.join(' ')}"
81
+ end
82
+
83
+ def help
84
+ <<-HELP.dedent
85
+ Usage: #{main_cmd.name} import <package> [options]
86
+
87
+ <package>
88
+ The name of a Sublime Text package directory containing
89
+ files to convert to DSL files.
90
+
91
+ Options:
92
+ -s, --source PATH
93
+ The path to the directory containing the directory <package>.
94
+ By default, it is the Sublime Text Packages directory.
95
+
96
+ -i, --include PATTERNS
97
+ Files to process or copy. By default, all files are processed
98
+ (except the files defined by the --exclude option).
99
+ PATTERNS must be a list of specifications separated by semicolons,
100
+ for instance "*.tmSnippet;*.tmLanguage".
101
+
102
+ -e, --exclude PATTERNS
103
+ Files not to process nor copy. By default "*.cache;*.pyc".
104
+
105
+ -t, --target PATH
106
+ The path to the target root directory: generated DSL files
107
+ will be placed there in a subdirectory named <package>.
108
+ By default, it is the current directory.
109
+
110
+ -k, --keyboard NAME
111
+ Convert keymap definitions for keyboard NAME. By default, no
112
+ keymap conversion is done. A file NAME.keyboard.rb will be
113
+ searched for in the subdirectories of <target_path>.
114
+
115
+ -r, --recognized
116
+ Do not copy non-recognized files to the target directory.
117
+ By default, non-recognized files (e.g., Python scripts)
118
+ are copied "as is" from the source directory to the target
119
+ directory.
120
+
121
+ -b, --backup [POLICY]
122
+ Define the backup policy if the directory <target_path>/<package>
123
+ already exists and contains files. POLICY may be:
124
+ never: do not create a backup
125
+ always: always create a backup
126
+ once: create a backup if there is not already one
127
+ Not giving a policy (-b or --backup alone) is the same
128
+ as 'always'. The default is 'once'.
129
+ The backup will be a zip file placed in <target_path>,
130
+ named <package_name>.<time_stamp>.zip
131
+
132
+ -q, --quiet
133
+ Do not report any information or warning.
134
+
135
+ -V, --verbose
136
+ Report detailed progress information.
137
+ HELP
138
+ end
139
+
140
+ end
141
+
142
+ end
143
+ end
@@ -0,0 +1,125 @@
1
+ # encoding: utf-8
2
+
3
+ module SublimeDSL
4
+
5
+ ##
6
+ # A Command Line Interpreter.
7
+ #
8
+ # Exit codes:
9
+ # * 0: command successful
10
+ # * 1: command did nothing
11
+ # * 2: something went wrong
12
+
13
+ class CLI
14
+
15
+ class << self
16
+
17
+ # Returns a Hash { command_name => command_class }.
18
+ def cmd_class_hash
19
+ @cmd_class_hash ||= load_commands
20
+ end
21
+
22
+ def load_commands
23
+ h = {}
24
+ Dir.chdir File.dirname(__FILE__) do
25
+ Dir["cli/*.rb"].each do |f|
26
+ require_relative f.sub(/\.rb$/, '')
27
+ c = File.basename(f, '.rb')
28
+ h[c] = CLI.const_get(c.pascal_case)
29
+ end
30
+ end
31
+ h
32
+ end
33
+
34
+ end
35
+
36
+ attr_reader :name
37
+
38
+ def initialize(name)
39
+ @name = name
40
+ end
41
+
42
+ def run
43
+
44
+ # honor help & version
45
+ while ARGV.first && ARGV.first.start_with?('-')
46
+ option = ARGV.shift
47
+ case option
48
+ when '-h', '--help'
49
+ puts help
50
+ exit 1
51
+ when '-v', '--version'
52
+ puts version
53
+ exit 1
54
+ else
55
+ Console.error "invalid option: #{option.inspect}"
56
+ exit 2
57
+ end
58
+ end
59
+
60
+ cmd_name = ARGV.shift
61
+
62
+ # if no argument, print help & exit
63
+ unless cmd_name
64
+ puts help
65
+ exit 2
66
+ end
67
+
68
+ if cmd_name == 'help'
69
+ if ARGV.empty?
70
+ puts help
71
+ else
72
+ arg = ARGV.shift
73
+ if arg == 'commands'
74
+ puts "Available commands:"
75
+ CLI.cmd_class_hash.each do |name, klass|
76
+ puts " %-10s %s" % [name, klass.summary]
77
+ end
78
+ else
79
+ puts command(arg).help
80
+ end
81
+ end
82
+ exit 1
83
+ end
84
+
85
+ command(cmd_name).run
86
+ end
87
+
88
+ def command(name)
89
+ cmd_class = CLI.cmd_class_hash[name]
90
+ unless cmd_class
91
+ Console.error "invalid command #{name.inspect}\n" \
92
+ "valid commands: #{CLI.cmd_class_hash.keys.join(', ')}"
93
+ exit 2
94
+ end
95
+
96
+ cmd_class.new(self)
97
+ end
98
+
99
+ def help
100
+ <<-HELP.dedent
101
+ Sublime DSL allows defining Sublime Text packages using a Ruby DSL.
102
+
103
+ Usage:
104
+ #{name} -h/--help
105
+ #{name} -v/--version
106
+ #{name} <command> [arguments...] [options...]
107
+
108
+ Examples:
109
+ #{name} import Ruby
110
+ #{name} export Ruby --cleanup
111
+
112
+ Available commands:
113
+ import import to DSL
114
+ export export from DSL
115
+ help <command> show help on <command>
116
+ HELP
117
+ end
118
+
119
+ def version
120
+ "SublimeDSL #{SublimeDSL::VERSION}"
121
+ end
122
+
123
+ end
124
+
125
+ end
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+
3
+ module Enumerable
4
+
5
+ # Returns a hash <tt>{ key => element }</tt>, where +key+ is the
6
+ # value returned by the block when passed +element+.
7
+ #
8
+ # Raises an exception if two elements return the same key.
9
+ #
10
+ # Examples:
11
+ # %w(a bb ccc).keyed_by(&:length) #=> {1 => 'a', 2 => 'bb', 3 => 'ccc'}
12
+ # %w(a bb cc).keyed_by(&:length) #=> RuntimeError "duplicate key: 2"
13
+
14
+ def keyed_by
15
+ h = {}
16
+ each do |element|
17
+ key = yield(element)
18
+ h.key?(key) and raise "duplicate key: #{key.inspect}"
19
+ h[key] = element
20
+ end
21
+ h
22
+ end
23
+
24
+ end
@@ -0,0 +1,129 @@
1
+ # encoding: utf-8
2
+
3
+ class String
4
+
5
+ # Same as #inspect, but between single quotes,
6
+ # so only \ and single quotes are escaped.
7
+ def inspect_sq
8
+ # escape all \ trailing, or followed by a \ or a '
9
+ # escape single quotes
10
+ "'" << self.gsub(/\\$|\\(?=[\\'])/, '\\\\\\\\').gsub("'", "\\\\'") << "'"
11
+ end
12
+
13
+ # Same as #inspect, but only escapes control characters and double quotes.
14
+ def inspect_dq
15
+ '"' << self.gsub(/[[:cntrl:]\\"]/) { |c| c.inspect[1..-2] } << '"'
16
+ end
17
+
18
+ # Returns the shortest between #inspect_sq and #inspect_dq.
19
+ # If same length, acts according to +prefer_dq+
20
+ # (prefer double quotes?)
21
+ def to_source(prefer_dq = false)
22
+ sq = inspect_sq
23
+ dq = inspect_dq
24
+ case dq.length <=> sq.length
25
+ when 1 then sq
26
+ when -1 then dq
27
+ when 0 then prefer_dq ? dq : sq
28
+ end
29
+ end
30
+
31
+ HTML_ESCAPE_MAP = { '&' => '&amp;', '"' => '&quot;', '<' => '&lt;', '>' => '&gt;' }
32
+
33
+ # Returns the string with characters escaped to HTML entities:
34
+ # < => &lt;
35
+ # > => &gt;
36
+ # & => &amp;
37
+ # " => &quot; (if escape_quotes == true)
38
+ def html_escape(escape_quotes = true)
39
+ if escape_quotes
40
+ self.gsub(/[&><"]/, HTML_ESCAPE_MAP)
41
+ else
42
+ self.gsub(/[&><]/, HTML_ESCAPE_MAP)
43
+ end
44
+ end
45
+
46
+ # <tt>pascal_case('snake_case') # => SnakeCase</tt>.
47
+ def pascal_case
48
+ self.gsub(/(?:^|_)(.)/) { $1.upcase }
49
+ end
50
+
51
+ # <tt>camel_case('snake_case') # => snakeCase</tt>.
52
+ def camel_case
53
+ self.gsub(/_(.)/) { $1.upcase }
54
+ end
55
+
56
+ # <tt>snake_case('PascalCase') # => pascal_case</tt>.
57
+ # <tt>snake_case('camelCase') # => camel_case</tt>.
58
+ def snake_case
59
+ self
60
+ .gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
61
+ .gsub(/([a-z\d])([A-Z])/,'\1_\2')
62
+ .downcase
63
+ end
64
+
65
+ # Shifts all lines left, preserving relative indentation.
66
+ def dedent
67
+ self.dup.dedent!
68
+ end
69
+
70
+ # Shifts all lines left, preserving relative indentation.
71
+ # Returns +self+.
72
+ def dedent!
73
+ if (re = dedent_regexp)
74
+ self.gsub! re, ''
75
+ end
76
+ self
77
+ end
78
+
79
+ # Indents all non-empty lines by the number of spaces passed.
80
+ def indent(spaces)
81
+ self.gsub(/^(.+)/, (' ' * spaces) + '\1')
82
+ end
83
+
84
+ # Indents all non-empty lines by the number of spaces passed.
85
+ # Returns +self+.
86
+ def indent!(spaces)
87
+ self.gsub!(/^(.+)/, (' ' * spaces) + '\1')
88
+ self
89
+ end
90
+
91
+ # Wraps at spaces so that no line is longer than the passed +width+,
92
+ # if possible. Multiple newlines, tabs & spaces are replaced by
93
+ # a single space.
94
+ def wrap(width = 80)
95
+ words = self.split(/\s+/m)
96
+ wrapped = words.shift
97
+ len = wrapped.length
98
+ words.each do |w|
99
+ len += 1 + w.length
100
+ if len > width
101
+ wrapped << "\n" << w
102
+ len = w.length
103
+ else
104
+ wrapped << ' ' << w
105
+ end
106
+ end
107
+ wrapped
108
+ end
109
+
110
+ private
111
+
112
+ # Returns the regexp to dedent +self+, or +nil+ if no dedent is needed,
113
+ # or there are leading tabs on non-empty lines.
114
+ def dedent_regexp
115
+ min_indent = self.length
116
+ self.each_line do |line|
117
+ # if no leading space, no dedent
118
+ line =~ /^\S/ and return nil
119
+ # skip empty lines
120
+ line =~ /^\s*$/ and next
121
+ # do nothing when tabs are present
122
+ line =~ /^ *\t/ and return nil
123
+ indent = line.match(/^ +/).to_s.length
124
+ min_indent = indent if indent < min_indent
125
+ end
126
+ Regexp.new('^' << ' ' * min_indent, Regexp::MULTILINE)
127
+ end
128
+
129
+ end
@@ -0,0 +1,4 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative 'core_ext/enumerable'
4
+ require_relative 'core_ext/string'