sublime_dsl 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/bin/subdsl
ADDED
@@ -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 = { '&' => '&', '"' => '"', '<' => '<', '>' => '>' }
|
32
|
+
|
33
|
+
# Returns the string with characters escaped to HTML entities:
|
34
|
+
# < => <
|
35
|
+
# > => >
|
36
|
+
# & => &
|
37
|
+
# " => " (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
|