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