Class: Terse::Scan

Inherits:
Object
  • Object
show all
Defined in:
lib/terse/scan.rb

Overview

This Scan class does the meat of the work TODO generate to_s, ==, initialize

Class Method Summary collapse

Class Method Details

.gen_new_filename(f) ⇒ Object

Rules : Keep base-name, change ext. to .rb, write to current working dir



276
277
278
279
280
281
282
283
284
285
286
287
# File 'lib/terse/scan.rb', line 276

def self.gen_new_filename f
    path = Dir.getwd
    name = File.basename_no_ext f
    ext = ".#{@settings.file_ext}"
    new_f = File.join(path, name) + ext

    if File.file?(new_f)
        puts "New file wll overwrite existing file " + new_f
        raise "Overwrite flag not specified; will not overwrite existing file " + new_f if !@overwrite
    end
    new_f
end

.is_file?(f) ⇒ Boolean

Returns:

  • (Boolean)


270
271
272
# File 'lib/terse/scan.rb', line 270

def self.is_file? f
    raise "Could not locate file at " + File.absolute_path(f) unless File.file?(f)
end

.match_java_lines(lines) ⇒ Object

Attempts to match Java lines against a keyword Allows multiple matches per line Need to proceed in a strict left-to-right manner across the line



155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'lib/terse/scan.rb', line 155

def self.match_java_lines(lines)
    newlines = []
    needs_top_level_end = false # this needs to be outside lines.each

    lines.each do |line_to_be_matched|

        # If we detect the start of a new class/method/etc. before the last one has been closed

        # then insert an end-char-sequence to close it off

        needs_top_level_start = false
        needs_inner_start = false
        needs_inner_end = false

        needs_line_ending = false
    
        line = line_to_be_matched.chomp
        pv "\nNow checking line --------> " + line
        newline = line # keep the orginal line unless we detect a match 

        newline_parts = []
        parts = line.split(" ")
        for i in 0 ... parts.size do
            potential_keyword = parts[i]
            matched = false
            newline_part = ""
            follow_on_match = false
            follow_on_part = ""
            @keywords.each do |k|
                if potential_keyword =~ k.regex          
                    matched = true              
                    pv "\nMatch found against line --> #{line}"
                    pv "Matched -------------------> " + $2

                    if k.needs_inner_start
                        needs_inner_start = true
                    end

                    if k.needs_top_level_start
                        needs_top_level_start = true
                    end

                    if k.needs_inner_end
                        needs_inner_end = true
                    end
                    
                    if k.needs_top_level_end
                        needs_top_level_end = true
                    end
                    
                    # Only add a line ending if no loop start or end is needed

                    if k.needs_line_ending
                        needs_line_ending = true
                    end
                    
                    newline_part = "#{$1}#{k.substitute}"

                    if k.try_follow_on_regex 
                        follow_on_line = parts[i .. -1].join
                        if follow_on_line =~ k.follow_on_regex
                            remainder = follow_on_line[$&.size .. -1]
                            follow_on = (eval k.follow_on_substitute).inspect
                            follow_on_part = "#{follow_on}#{remainder}"
                            follow_on_match = true
                        end
                    end
                    
                    break
                end # end if regex        

            end # end keywords.each

            if matched
                newline_parts << newline_part
                matched = false # reset for next part

                newline_part = ""

                # We do not allow keywords to follow follow-on sections

                if follow_on_match
                    newline_parts << follow_on_part                            
                    break # follow_on_part should comprise the rest of the line, so there are no more parts to try to match

                end
            else
                newline_parts << potential_keyword
            end                
        end # end parts.each


        newline_parts << @settings.loop_start if needs_top_level_start
        
        needs_inner_start = true if !needs_line_ending && parts[-1] =~ /\)\s*$/
        newline_parts << @settings.loop_start if needs_inner_start
        unless newline_parts.empty?
            newline = newline_parts.join(" ")
        end
        # Reevaluate needs_line_ending once whole line has been processed

        # No line ending if needs_inner_start || needs_top_level_start # TODO possibly faulty logic? Seems to work reasonably well...                

        if needs_inner_start || needs_top_level_start
            needs_line_ending = false
        end
        newline << @settings.line_ending if needs_line_ending
        pv "Line will become ----------> " + newline
        newlines << newline
        if needs_inner_end # add after newline, not before

            newlines << "" # deliberately want an empty line before the loop-ending

            newlines << @settings.loop_ending 
        end
    end # end lines.each        

    # Add end lines to end of file if necessary

    # newlines << @settings.loop_ending if needs_inner_end

    if needs_top_level_end # add after newline, not before

        newlines << "" # deliberately want an empty line before the loop-ending

        newlines << @settings.loop_ending 
    end
    newlines
end

.match_ruby_lines(lines) ⇒ Object

Attempts to match Ruby lines against a keyword Only allows for one match per line TODO refactor this and match_java_lines



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/terse/scan.rb', line 82

def self.match_ruby_lines(lines)
    newlines = []

    # If we detect the start of a new class/method/etc. before the last one has been closed

    # then insert an "end" to close it off

    needs_top_level_end = false
    needs_inner_end = false
    insert_end = false
    
    lines.each do |line_to_be_matched|
        line = line_to_be_matched.chomp
        newline = line # keep the orginal line unless we detect a match 

        @keywords.each do |k|
            if newline =~ k.regex                        
                pv "\nMatch found against line --> #{line}"            
                pv "Matched -------------------> " + $2
                if k.is_end
                    if needs_inner_end
                        needs_inner_end = false
                    elsif needs_top_level_end
                        needs_top_level_end = false
                    end
                    insert_end = false
                end
                
                if k.needs_inner_end
                    if needs_inner_end
                        newlines << @settings.loop_ending
                        # keep needs_inner_end true as we need to watch for an end for this new match

                    else
                        needs_inner_end = true # set in readiness for the next match

                    end
                end
                
                if k.needs_top_level_end
                    if needs_inner_end
                        newlines << @settings.loop_ending
                        needs_inner_end = false
                    end
                    if needs_top_level_end
                        newlines << @settings.loop_ending
                        # keep needs_top_level_end true as we need to watch for an end for this new match

                    else
                        needs_top_level_end = true # set in readiness for the next match

                    end
                end
                
                if k.try_follow_on_regex && line =~ k.follow_on_regex
                    # $& is the entire match, not just the bits in ( )

                    remainder = line[$&.size .. -1]
                    follow_on = (eval k.follow_on_substitute).inspect
                    newline = "#{$1}#{k.substitute} #{follow_on}#{remainder}"
                else
                    remainder = line[($1.size + $2.size) .. -1]
                    newline = "#{$1}#{k.substitute}#{remainder}"
                end
                
                pv "Line will become ----------> " + newline
                break
            end # end if regex        

        end # end keywords.each

        newlines << newline
    end # end lines.each        

    # Add end lines to end of file if necessary

    newlines << @settings.loop_ending if needs_inner_end
    newlines << @settings.loop_ending if needs_top_level_end
    newlines
end

.plural_or_not(item) ⇒ Object

Returns an “s” string if the size of the item (item.size) is > 1; returns “” otherwise



266
267
268
# File 'lib/terse/scan.rb', line 266

def self.plural_or_not item
    item.size == 1 ? "" : "s"
end

.pv(s) ⇒ Object



289
290
291
# File 'lib/terse/scan.rb', line 289

def self.pv s
    puts s if @verbose
end

.scan_files(settings, argv) ⇒ Object

The main method. ARGV from the invokation are passed in to here



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/terse/scan.rb', line 13

def self.scan_files(settings, argv)
    @settings = settings # TODO less bad please!

    if @settings == nil || @settings.class != Terse::Settings
        raise "Settings object was not supplied"
    end

    if @settings.lang == nil || @settings.lang.class != Symbol
        raise "Target language not specified; options are :ruby, :java"
    end

    case @settings.lang 
    when :ruby
        @keywords = Terse::KeywordRuby.generate_keywords
    when :java
        @keywords = Terse::KeywordJava.generate_keywords
    else
        raise "Unrecognised language #{settings.lang.inspect} specified"
    end
    flags = argv.options
    @verbose = flags.include_any?("v", "verbose")
    @overwrite = flags.include_any?("o", "overwrite", "w", "write")
    @no_formatting = flags.include_any?("n", "noformatting")

    files = argv.values

    if files.size < 1
        puts "No files supplied"
    else
        files.each do |f|
            is_file? f
        end
        pv "Will scan #{files.size} file#{plural_or_not files}"
        files.each do |f|
            is_file? f
            filename = File.basename f
            new_filename = gen_new_filename f
            lines = File.readlines f
            pv "File #{filename} has #{lines.size} line#{plural_or_not lines}"
            
            case @settings.lang 
            when :ruby
                newlines = match_ruby_lines(lines)
            when :java
                newlines = match_java_lines(lines)
            else
                raise "Unrecognised language #{settings.lang.inspect} specified"
            end
            
            # Add newlines between the end of a method and the start of the next, if needed 

            newlines = Terse::Format.space_lines(newlines, @keywords, @settings) unless @no_formatting
            
            # Apply indentation

            newlines = Terse::Format.indent(newlines, @settings) unless @no_formatting
            
            # Write out the new file

            File.open(new_filename, "w") do |new_f|
                newlines.each do |l|
                    new_f.puts l
                end
            end
            puts "\nWrote file #{new_filename}"
        end # end else

    end # end ARGV.size

    puts "\nTerse #{settings.lang.to_s} expansion finished"
end