terse_ruby 0.1.0 → 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.
- checksums.yaml +4 -4
- data/README.txt +55 -3
- data/build.rb +50 -0
- data/lib/{terse_ruby → terse}/format.rb +16 -15
- data/lib/terse/keyword.rb +166 -0
- data/lib/terse/keyword_java.rb +103 -0
- data/lib/{terse_ruby/keyword.rb → terse/keyword_ruby.rb} +10 -17
- data/lib/terse/scan.rb +294 -0
- data/lib/terse/settings.rb +20 -0
- data/lib/terse_java.rb +24 -0
- data/lib/terse_ruby.rb +35 -2
- data/terse_ruby.gemspec +3 -1
- data/test/expected_test_file_java.java +22 -0
- data/test/{expected_test_file.rb → expected_test_file_ruby.rb} +4 -4
- data/test/test_expansion_java.rb +75 -0
- data/test/{test_expansion.rb → test_expansion_ruby.rb} +16 -5
- data/test/test_file_java.txt +14 -0
- data/test/{test_file.txt → test_file_ruby.txt} +0 -0
- metadata +60 -12
- data/bin/terse_ruby.rb +0 -59
- data/lib/terse_ruby/scan.rb +0 -147
@@ -1,23 +1,15 @@
|
|
1
|
-
|
1
|
+
require_relative "keyword"
|
2
2
|
|
3
|
-
|
4
|
-
class Keyword
|
5
|
-
attr_accessor :keyword
|
6
|
-
attr_accessor :substitute
|
7
|
-
attr_accessor :regex
|
8
|
-
attr_accessor :follow_on_regex
|
9
|
-
attr_accessor :follow_on_substitute
|
10
|
-
attr_accessor :try_follow_on_regex
|
11
|
-
attr_accessor :needs_top_level_end
|
12
|
-
attr_accessor :needs_inner_end
|
13
|
-
attr_accessor :is_end
|
3
|
+
module Terse
|
14
4
|
|
5
|
+
# A class storing all information about a keyword & what it should be transformed into
|
6
|
+
class KeywordRuby < Keyword
|
7
|
+
|
15
8
|
def initialize(keyword)
|
16
|
-
|
17
|
-
@
|
18
|
-
@follow_on_regex = gen_regex_from_keyword_including_follow_on keyword
|
9
|
+
super(keyword)
|
10
|
+
@loop_ending = "end"
|
19
11
|
end
|
20
|
-
|
12
|
+
|
21
13
|
# TODO this doesn't work, needs to process the thing following the keyword, not the keyword itself
|
22
14
|
def set_substitute
|
23
15
|
case @keyword
|
@@ -58,7 +50,7 @@ module TerseRuby
|
|
58
50
|
keywords = []
|
59
51
|
keys = [:req, :reqr, :i, :c, :m, :d, :e, :a, :r, :w]
|
60
52
|
keys.each do |key|
|
61
|
-
k =
|
53
|
+
k = KeywordRuby.new key
|
62
54
|
# k.keyword = key
|
63
55
|
case key
|
64
56
|
when :req
|
@@ -104,5 +96,6 @@ module TerseRuby
|
|
104
96
|
end # end keys.each
|
105
97
|
keywords
|
106
98
|
end
|
99
|
+
|
107
100
|
end
|
108
101
|
end
|
data/lib/terse/scan.rb
ADDED
@@ -0,0 +1,294 @@
|
|
1
|
+
require "more-ruby"
|
2
|
+
require_relative "keyword_ruby"
|
3
|
+
require_relative "keyword_java"
|
4
|
+
require_relative "format"
|
5
|
+
|
6
|
+
module Terse
|
7
|
+
|
8
|
+
# This Scan class does the meat of the work
|
9
|
+
# TODO generate to_s, ==, initialize
|
10
|
+
class Scan
|
11
|
+
|
12
|
+
# The main method. ARGV from the invokation are passed in to here
|
13
|
+
def self.scan_files(settings, argv)
|
14
|
+
@settings = settings # TODO less bad please!
|
15
|
+
if @settings == nil || @settings.class != Terse::Settings
|
16
|
+
raise "Settings object was not supplied"
|
17
|
+
end
|
18
|
+
|
19
|
+
if @settings.lang == nil || @settings.lang.class != Symbol
|
20
|
+
raise "Target language not specified; options are :ruby, :java"
|
21
|
+
end
|
22
|
+
|
23
|
+
case @settings.lang
|
24
|
+
when :ruby
|
25
|
+
@keywords = Terse::KeywordRuby.generate_keywords
|
26
|
+
when :java
|
27
|
+
@keywords = Terse::KeywordJava.generate_keywords
|
28
|
+
else
|
29
|
+
raise "Unrecognised language #{settings.lang.inspect} specified"
|
30
|
+
end
|
31
|
+
flags = argv.options
|
32
|
+
@verbose = flags.include_any?("v", "verbose")
|
33
|
+
@overwrite = flags.include_any?("o", "overwrite", "w", "write")
|
34
|
+
@no_formatting = flags.include_any?("n", "noformatting")
|
35
|
+
|
36
|
+
files = argv.values
|
37
|
+
|
38
|
+
if files.size < 1
|
39
|
+
puts "No files supplied"
|
40
|
+
else
|
41
|
+
files.each do |f|
|
42
|
+
is_file? f
|
43
|
+
end
|
44
|
+
pv "Will scan #{files.size} file#{plural_or_not files}"
|
45
|
+
files.each do |f|
|
46
|
+
is_file? f
|
47
|
+
filename = File.basename f
|
48
|
+
new_filename = gen_new_filename f
|
49
|
+
lines = File.readlines f
|
50
|
+
pv "File #{filename} has #{lines.size} line#{plural_or_not lines}"
|
51
|
+
|
52
|
+
case @settings.lang
|
53
|
+
when :ruby
|
54
|
+
newlines = match_ruby_lines(lines)
|
55
|
+
when :java
|
56
|
+
newlines = match_java_lines(lines)
|
57
|
+
else
|
58
|
+
raise "Unrecognised language #{settings.lang.inspect} specified"
|
59
|
+
end
|
60
|
+
|
61
|
+
# Add newlines between the end of a method and the start of the next, if needed
|
62
|
+
newlines = Terse::Format.space_lines(newlines, @keywords, @settings) unless @no_formatting
|
63
|
+
|
64
|
+
# Apply indentation
|
65
|
+
newlines = Terse::Format.indent(newlines, @settings) unless @no_formatting
|
66
|
+
|
67
|
+
# Write out the new file
|
68
|
+
File.open(new_filename, "w") do |new_f|
|
69
|
+
newlines.each do |l|
|
70
|
+
new_f.puts l
|
71
|
+
end
|
72
|
+
end
|
73
|
+
puts "\nWrote file #{new_filename}"
|
74
|
+
end # end else
|
75
|
+
end # end ARGV.size
|
76
|
+
puts "\nTerse #{settings.lang.to_s} expansion finished"
|
77
|
+
end # end scan_files
|
78
|
+
|
79
|
+
# Attempts to match Ruby lines against a keyword
|
80
|
+
# Only allows for one match per line
|
81
|
+
# TODO refactor this and match_java_lines
|
82
|
+
def self.match_ruby_lines(lines)
|
83
|
+
newlines = []
|
84
|
+
|
85
|
+
# If we detect the start of a new class/method/etc. before the last one has been closed
|
86
|
+
# then insert an "end" to close it off
|
87
|
+
needs_top_level_end = false
|
88
|
+
needs_inner_end = false
|
89
|
+
insert_end = false
|
90
|
+
|
91
|
+
lines.each do |line_to_be_matched|
|
92
|
+
line = line_to_be_matched.chomp
|
93
|
+
newline = line # keep the orginal line unless we detect a match
|
94
|
+
@keywords.each do |k|
|
95
|
+
if newline =~ k.regex
|
96
|
+
pv "\nMatch found against line --> #{line}"
|
97
|
+
pv "Matched -------------------> " + $2
|
98
|
+
if k.is_end
|
99
|
+
if needs_inner_end
|
100
|
+
needs_inner_end = false
|
101
|
+
elsif needs_top_level_end
|
102
|
+
needs_top_level_end = false
|
103
|
+
end
|
104
|
+
insert_end = false
|
105
|
+
end
|
106
|
+
|
107
|
+
if k.needs_inner_end
|
108
|
+
if needs_inner_end
|
109
|
+
newlines << @settings.loop_ending
|
110
|
+
# keep needs_inner_end true as we need to watch for an end for this new match
|
111
|
+
else
|
112
|
+
needs_inner_end = true # set in readiness for the next match
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
if k.needs_top_level_end
|
117
|
+
if needs_inner_end
|
118
|
+
newlines << @settings.loop_ending
|
119
|
+
needs_inner_end = false
|
120
|
+
end
|
121
|
+
if needs_top_level_end
|
122
|
+
newlines << @settings.loop_ending
|
123
|
+
# keep needs_top_level_end true as we need to watch for an end for this new match
|
124
|
+
else
|
125
|
+
needs_top_level_end = true # set in readiness for the next match
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
if k.try_follow_on_regex && line =~ k.follow_on_regex
|
130
|
+
# $& is the entire match, not just the bits in ( )
|
131
|
+
remainder = line[$&.size .. -1]
|
132
|
+
follow_on = (eval k.follow_on_substitute).inspect
|
133
|
+
newline = "#{$1}#{k.substitute} #{follow_on}#{remainder}"
|
134
|
+
else
|
135
|
+
remainder = line[($1.size + $2.size) .. -1]
|
136
|
+
newline = "#{$1}#{k.substitute}#{remainder}"
|
137
|
+
end
|
138
|
+
|
139
|
+
pv "Line will become ----------> " + newline
|
140
|
+
break
|
141
|
+
end # end if regex
|
142
|
+
end # end keywords.each
|
143
|
+
newlines << newline
|
144
|
+
end # end lines.each
|
145
|
+
# Add end lines to end of file if necessary
|
146
|
+
newlines << @settings.loop_ending if needs_inner_end
|
147
|
+
newlines << @settings.loop_ending if needs_top_level_end
|
148
|
+
newlines
|
149
|
+
end
|
150
|
+
|
151
|
+
|
152
|
+
# Attempts to match Java lines against a keyword
|
153
|
+
# Allows multiple matches per line
|
154
|
+
# Need to proceed in a strict left-to-right manner across the line
|
155
|
+
def self.match_java_lines(lines)
|
156
|
+
newlines = []
|
157
|
+
needs_top_level_end = false # this needs to be outside lines.each
|
158
|
+
lines.each do |line_to_be_matched|
|
159
|
+
|
160
|
+
# If we detect the start of a new class/method/etc. before the last one has been closed
|
161
|
+
# then insert an end-char-sequence to close it off
|
162
|
+
needs_top_level_start = false
|
163
|
+
needs_inner_start = false
|
164
|
+
needs_inner_end = false
|
165
|
+
|
166
|
+
needs_line_ending = false
|
167
|
+
|
168
|
+
line = line_to_be_matched.chomp
|
169
|
+
pv "\nNow checking line --------> " + line
|
170
|
+
newline = line # keep the orginal line unless we detect a match
|
171
|
+
newline_parts = []
|
172
|
+
parts = line.split(" ")
|
173
|
+
for i in 0 ... parts.size do
|
174
|
+
potential_keyword = parts[i]
|
175
|
+
matched = false
|
176
|
+
newline_part = ""
|
177
|
+
follow_on_match = false
|
178
|
+
follow_on_part = ""
|
179
|
+
@keywords.each do |k|
|
180
|
+
if potential_keyword =~ k.regex
|
181
|
+
matched = true
|
182
|
+
pv "\nMatch found against line --> #{line}"
|
183
|
+
pv "Matched -------------------> " + $2
|
184
|
+
|
185
|
+
if k.needs_inner_start
|
186
|
+
needs_inner_start = true
|
187
|
+
end
|
188
|
+
|
189
|
+
if k.needs_top_level_start
|
190
|
+
needs_top_level_start = true
|
191
|
+
end
|
192
|
+
|
193
|
+
if k.needs_inner_end
|
194
|
+
needs_inner_end = true
|
195
|
+
end
|
196
|
+
|
197
|
+
if k.needs_top_level_end
|
198
|
+
needs_top_level_end = true
|
199
|
+
end
|
200
|
+
|
201
|
+
# Only add a line ending if no loop start or end is needed
|
202
|
+
if k.needs_line_ending
|
203
|
+
needs_line_ending = true
|
204
|
+
end
|
205
|
+
|
206
|
+
newline_part = "#{$1}#{k.substitute}"
|
207
|
+
|
208
|
+
if k.try_follow_on_regex
|
209
|
+
follow_on_line = parts[i .. -1].join
|
210
|
+
if follow_on_line =~ k.follow_on_regex
|
211
|
+
remainder = follow_on_line[$&.size .. -1]
|
212
|
+
follow_on = (eval k.follow_on_substitute).inspect
|
213
|
+
follow_on_part = "#{follow_on}#{remainder}"
|
214
|
+
follow_on_match = true
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
break
|
219
|
+
end # end if regex
|
220
|
+
end # end keywords.each
|
221
|
+
if matched
|
222
|
+
newline_parts << newline_part
|
223
|
+
matched = false # reset for next part
|
224
|
+
newline_part = ""
|
225
|
+
|
226
|
+
# We do not allow keywords to follow follow-on sections
|
227
|
+
if follow_on_match
|
228
|
+
newline_parts << follow_on_part
|
229
|
+
break # follow_on_part should comprise the rest of the line, so there are no more parts to try to match
|
230
|
+
end
|
231
|
+
else
|
232
|
+
newline_parts << potential_keyword
|
233
|
+
end
|
234
|
+
end # end parts.each
|
235
|
+
|
236
|
+
newline_parts << @settings.loop_start if needs_top_level_start
|
237
|
+
|
238
|
+
needs_inner_start = true if !needs_line_ending && parts[-1] =~ /\)\s*$/
|
239
|
+
newline_parts << @settings.loop_start if needs_inner_start
|
240
|
+
unless newline_parts.empty?
|
241
|
+
newline = newline_parts.join(" ")
|
242
|
+
end
|
243
|
+
# Reevaluate needs_line_ending once whole line has been processed
|
244
|
+
# No line ending if needs_inner_start || needs_top_level_start # TODO possibly faulty logic? Seems to work reasonably well...
|
245
|
+
if needs_inner_start || needs_top_level_start
|
246
|
+
needs_line_ending = false
|
247
|
+
end
|
248
|
+
newline << @settings.line_ending if needs_line_ending
|
249
|
+
pv "Line will become ----------> " + newline
|
250
|
+
newlines << newline
|
251
|
+
if needs_inner_end # add after newline, not before
|
252
|
+
newlines << "" # deliberately want an empty line before the loop-ending
|
253
|
+
newlines << @settings.loop_ending
|
254
|
+
end
|
255
|
+
end # end lines.each
|
256
|
+
# Add end lines to end of file if necessary
|
257
|
+
# newlines << @settings.loop_ending if needs_inner_end
|
258
|
+
if needs_top_level_end # add after newline, not before
|
259
|
+
newlines << "" # deliberately want an empty line before the loop-ending
|
260
|
+
newlines << @settings.loop_ending
|
261
|
+
end
|
262
|
+
newlines
|
263
|
+
end
|
264
|
+
|
265
|
+
# Returns an "s" string if the size of the item (item.size) is > 1; returns "" otherwise
|
266
|
+
def self.plural_or_not item
|
267
|
+
item.size == 1 ? "" : "s"
|
268
|
+
end
|
269
|
+
|
270
|
+
def self.is_file? f
|
271
|
+
raise "Could not locate file at " + File.absolute_path(f) unless File.file?(f)
|
272
|
+
end
|
273
|
+
|
274
|
+
# Rules :
|
275
|
+
# Keep base-name, change ext. to .rb, write to current working dir
|
276
|
+
def self.gen_new_filename f
|
277
|
+
path = Dir.getwd
|
278
|
+
name = File.basename_no_ext f
|
279
|
+
ext = ".#{@settings.file_ext}"
|
280
|
+
new_f = File.join(path, name) + ext
|
281
|
+
|
282
|
+
if File.file?(new_f)
|
283
|
+
puts "New file wll overwrite existing file " + new_f
|
284
|
+
raise "Overwrite flag not specified; will not overwrite existing file " + new_f if !@overwrite
|
285
|
+
end
|
286
|
+
new_f
|
287
|
+
end
|
288
|
+
|
289
|
+
def self.pv s
|
290
|
+
puts s if @verbose
|
291
|
+
end
|
292
|
+
|
293
|
+
end
|
294
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# Simple class holding settings for the terse file expansion
|
2
|
+
module Terse
|
3
|
+
|
4
|
+
class Settings
|
5
|
+
attr_accessor :lang
|
6
|
+
attr_accessor :file_ext # do not include the leading . , e.g. "rb" not ".rb"
|
7
|
+
attr_accessor :one_keyword_per_line
|
8
|
+
|
9
|
+
attr_accessor :loop_start
|
10
|
+
attr_accessor :loop_ending
|
11
|
+
attr_accessor :line_ending
|
12
|
+
|
13
|
+
attr_accessor :indent_in # array of keywords that should cause inward indentation
|
14
|
+
attr_accessor :indent_in_regex # regex that should match any of @indent_in as closely as possible
|
15
|
+
attr_accessor :indent_out # array of keywords that should cause outward indentation
|
16
|
+
attr_accessor :indent_out_regex # regex that should match any of @indent_out as closely as possible
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
data/lib/terse_java.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require "argv"
|
2
|
+
require_relative "terse/scan"
|
3
|
+
require_relative "terse/settings"
|
4
|
+
|
5
|
+
module TerseJava
|
6
|
+
include TerseRuby
|
7
|
+
|
8
|
+
# Routing method to call the proper method
|
9
|
+
def self.scan_files argv
|
10
|
+
puts "\nTerse Java called"
|
11
|
+
settings = Terse::Settings.new
|
12
|
+
settings.lang = :java
|
13
|
+
settings.file_ext = "java"
|
14
|
+
settings.one_keyword_per_line = false
|
15
|
+
settings.loop_start = "{"
|
16
|
+
settings.loop_ending = "}"
|
17
|
+
settings.line_ending = ";"
|
18
|
+
settings.indent_in = ["{"]
|
19
|
+
settings.indent_in_regex = /({)\s*$/ # indent-in keywords may not be the first words on a given line
|
20
|
+
settings.indent_out = ["}"]
|
21
|
+
settings.indent_out_regex = /(})\s*$/ # } is a relatively unused char (outside of loop-endings) that this regex does nto need to be sophisticated
|
22
|
+
Terse::Scan.scan_files settings, argv
|
23
|
+
end
|
24
|
+
end
|
data/lib/terse_ruby.rb
CHANGED
@@ -1,10 +1,43 @@
|
|
1
1
|
require "argv"
|
2
|
-
require_relative "
|
2
|
+
require_relative "terse/scan"
|
3
|
+
require_relative "terse/settings"
|
3
4
|
|
4
5
|
module TerseRuby
|
5
6
|
|
6
7
|
# Routing method to call the proper method
|
7
8
|
def self.scan_files argv
|
8
|
-
|
9
|
+
puts "\nTerse Ruby called"
|
10
|
+
settings = Terse::Settings.new
|
11
|
+
settings.lang = :ruby
|
12
|
+
settings.file_ext = "rb"
|
13
|
+
settings.one_keyword_per_line = true
|
14
|
+
settings.loop_ending = "end"
|
15
|
+
settings.line_ending = ""
|
16
|
+
settings.indent_in = ["class", "module", "def"] #, "do"] # TODO add "do", adjust indent_in_regex accordingly
|
17
|
+
settings.indent_in_regex = /^\s*([a-zA-Z0-9_]+)\s+|$/ # indent-in keywords should be the first words on a given line
|
18
|
+
settings.indent_out = ["end"]
|
19
|
+
settings.indent_out_regex = /^\s*([a-zA-Z0-9_]+)\s*$/ # indent-out keywords should be the only words on a given line
|
20
|
+
Terse::Scan.scan_files settings, argv
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
module TerseJava
|
25
|
+
include TerseRuby
|
26
|
+
|
27
|
+
# Routing method to call the proper method
|
28
|
+
def self.scan_files argv
|
29
|
+
puts "\nTerse Java called"
|
30
|
+
settings = Terse::Settings.new
|
31
|
+
settings.lang = :java
|
32
|
+
settings.file_ext = "java"
|
33
|
+
settings.one_keyword_per_line = false
|
34
|
+
settings.loop_start = "{"
|
35
|
+
settings.loop_ending = "}"
|
36
|
+
settings.line_ending = ";"
|
37
|
+
settings.indent_in = ["{"]
|
38
|
+
settings.indent_in_regex = /({)\s*$/ # indent-in keywords may not be the first words on a given line
|
39
|
+
settings.indent_out = ["}"]
|
40
|
+
settings.indent_out_regex = /(})\s*$/ # } is a relatively unused char (outside of loop-endings) that this regex does nto need to be sophisticated
|
41
|
+
Terse::Scan.scan_files settings, argv
|
9
42
|
end
|
10
43
|
end
|
data/terse_ruby.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'terse_ruby'
|
3
|
-
s.version = '0.1.
|
3
|
+
s.version = '0.1.1'
|
4
4
|
s.licenses = ['MIT']
|
5
5
|
s.summary = "A utility to convert 'shorthand' Ruby into genuine Ruby."
|
6
6
|
s.description = "A utility to convert 'shorthand' Ruby into genuine Ruby. See README for more details."
|
@@ -11,4 +11,6 @@ Gem::Specification.new do |s|
|
|
11
11
|
s.files = Dir['**/**']
|
12
12
|
s.test_files = Dir["test/test*.rb"]
|
13
13
|
s.has_rdoc = true
|
14
|
+
s.add_runtime_dependency 'more-ruby', '~> 0.1', '>= 0.1.3'
|
15
|
+
s.add_runtime_dependency 'argv', '~> 3.0', '>= 3.0.0'
|
14
16
|
end
|