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.
@@ -1,23 +1,15 @@
1
- module TerseRuby
1
+ require_relative "keyword"
2
2
 
3
- # A class storing all information about a keyword & what it should be transformed into
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
- @keyword = keyword
17
- @regex = gen_regex_from_keyword keyword
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 = Keyword.new key
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
@@ -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
@@ -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
@@ -1,10 +1,43 @@
1
1
  require "argv"
2
- require_relative "terse_ruby/scan"
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
- TerseRuby::Scan.scan_files argv
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
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'terse_ruby'
3
- s.version = '0.1.0'
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