watson-ruby 1.0.3 → 1.0.4

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.
data/lib/watson/parser.rb CHANGED
@@ -1,420 +1,419 @@
1
1
  module Watson
2
- # Dir/File parser class
3
- # Contains all necessary methods to parse through files and directories
4
- # for specified tags and generate data structure containing found issues
5
- class Parser
6
-
7
- # Include for debug_print
8
- include Watson
9
-
10
- # [review] - Should this require be required higher up or fine here
11
- # Include for Digest::MD5.hexdigest used in issue creating
12
- require 'digest'
13
- require 'pp'
14
-
15
- # Debug printing for this class
16
- DEBUG = false
17
-
18
- ###########################################################
19
- # Initialize the parser with the current watson config
20
- def initialize(config)
21
- # [review] - Not sure if passing config here is best way to access it
22
-
23
- # Identify method entry
24
- debug_print "#{ self } : #{ __method__ }\n"
25
-
26
- @config = config
27
- end
28
-
29
-
30
- ###########################################################
31
- # Begins parsing of files / dirs specified in the initial dir/file lists
32
- def run
33
-
34
- # Identify method entry
35
- debug_print "#{ self } : #{ __method__ }\n"
36
-
37
- # Go through all files added from CL (sort them first)
38
- # If empty, sort and each will do nothing, no errors
39
- _completed_dirs = Array.new()
40
- _completed_files = Array.new()
41
- if @config.cl_entry_set
42
- @config.file_list.sort.each do | _file |
43
- _completed_files.push(parse_file(_file))
44
- end
45
- end
46
-
47
- # Then go through all the specified directories
48
- # Initial parse depth to parse_dir is 0 (unlimited)
49
- @config.dir_list.sort.each do | _dir |
50
- _completed_dirs.push(parse_dir(_dir, 0))
51
- end
52
-
53
- # Create overall hash for parsed files
54
- _structure = Hash.new()
55
- _structure[:files] = _completed_files
56
- _structure[:subdirs] = _completed_dirs
57
-
58
- debug_print "_structure dump\n\n"
59
- debug_print PP.pp(_structure, "")
60
- debug_print "\n\n"
61
-
62
- return _structure
63
- end
64
-
65
-
66
- ###########################################################
67
- # Parse through specified directory and find all subdirs and files
68
- def parse_dir(dir, depth)
69
-
70
- # Identify method entry
71
- debug_print "#{ self } : #{ __method__ }\n"
72
-
73
- # Error check on input
74
- if !Watson::FS.check_dir(dir)
75
- print "Unable to open #{ dir }, exiting\n"
76
- return false
77
- else
78
- debug_print "Opened #{ dir } for parsing\n"
79
- end
80
-
81
- debug_print "Parsing through all files/directories in #{ dir }\n"
82
-
83
- # [review] - Shifted away from single Dir.glob loop to separate for dir/file
84
- # This duplicates code but is much better for readability
85
- # Not sure which is preferred?
86
-
87
-
88
- # Remove leading . or ./
89
- _glob_dir = dir.gsub(/^\.(\/?)/, '')
90
- debug_print "_glob_dir: #{_glob_dir}\n"
91
-
92
-
93
- # Go through directory to find all files
94
- # Create new array to hold all parsed files
95
- _completed_files = Array.new()
96
- Dir.glob("#{ _glob_dir }{*,.*}").select { | _fn | File.file?(_fn) }.sort.each do | _entry |
97
- debug_print "Entry: #{_entry} is a file\n"
98
-
99
-
100
- # [review] - Warning to user when file is ignored? (outside of debug_print)
101
- # Check against ignore list, if match, set to "" which will be ignored
102
- @config.ignore_list.each do | _ignore |
103
- # [review] - Better "Ruby" way to check for "*"?
104
- # [review] - Probably cleaner way to perform multiple checks below
105
- # Look for *.type on list, regex to match entry
106
- if _ignore[0] == "*"
107
- _cut = _ignore[1..-1]
108
- if _entry.match(/#{ _cut }/)
109
- debug_print "#{ _entry } is on the ignore list, setting to \"\"\n"
110
- _entry = ""
111
- break
112
- end
113
- # Else check for verbose ignore match
114
- else
115
- if _entry == _ignore || File.absolute_path(_entry) == _ignore
116
- debug_print "#{ _entry } is on the ignore list, setting to \"\"\n"
117
- _entry = ""
118
- break
119
- end
120
- end
121
- end
122
-
123
- # If the resulting entry (after filtering) isn't empty, parse it and push into file array
124
- if !_entry.empty?
125
- debug_print "Parsing #{ _entry }\n"
126
- _completed_files.push(parse_file(_entry))
127
- end
128
-
129
- end
130
-
131
-
132
- # Go through directory to find all subdirs
133
- # Create new array to hold all parsed subdirs
134
- _completed_dirs = Array.new()
135
- Dir.glob("#{ _glob_dir }{*, .*}").select { | _fn | File.directory?(_fn) }.sort.each do | _entry |
136
- debug_print "Entry: #{ _entry } is a dir\n"
137
-
138
-
139
-
140
- ## Depth limit logic
141
- # Current depth is depth of previous parse_dir (passed in as second param) + 1
142
- _cur_depth = depth + 1
143
- debug_print "Current Folder depth: #{ _cur_depth }\n"
144
-
145
- # If Config.parse_depth is 0, no limit on subdir parsing
146
- if @config.parse_depth == 0
147
- debug_print "No max depth, parsing directory\n"
148
- _completed_dirs.push(parse_dir("#{ _entry }/", _cur_depth))
149
- # If current depth is less than limit (set in config), parse directory and pass depth
150
- elsif _cur_depth < @config.parse_depth.to_i + 1
151
- debug_print "Depth less than max dept (from config), parsing directory\n"
152
- _completed_dirs.push(parse_dir("#{ _entry }/", _cur_depth))
153
- # Else, depth is greater than limit, ignore the directory
154
- else
155
- debug_print "Depth greater than max depth, ignoring\n"
156
- end
157
-
158
- # Add directory to ignore list so it isn't repeated again accidentally
159
- @config.ignore_list.push(_entry)
160
- end
161
-
162
-
163
- # [review] - Not sure if Dir.glob requires a explicit directory/file close?
164
-
165
- # Create hash to hold all parsed files and directories
166
- _structure = Hash.new()
167
- _structure[:curdir] = dir
168
- _structure[:files] = _completed_files
169
- _structure[:subdirs] = _completed_dirs
170
- return _structure
171
- end
172
-
173
-
174
- ###########################################################
175
- # Parse through individual files looking for issue tags, then generate formatted issue hash
176
- def parse_file(filename)
177
- # [review] - Rename method input param to filename (more verbose?)
178
-
179
- # Identify method entry
180
- debug_print "#{ self } : #{ __method__ }\n"
181
-
182
- _relative_path = filename
183
- _absolute_path = File.absolute_path(filename)
184
-
185
- # Error check on input, use input filename to make sure relative path is correct
186
- if !Watson::FS.check_file(_relative_path)
187
- print "Unable to open #{ _relative_path }, exiting\n"
188
- return false
189
- else
190
- debug_print "Opened #{ _relative_path } for parsing\n"
191
- debug_print "Short path: #{ _relative_path }\n"
192
- end
193
-
194
-
195
- # Get filetype and set corresponding comment type
196
- _comment_type = get_comment_type(_relative_path)
197
- if !_comment_type
198
- debug_print "Using default (#) comment type\n"
199
- _comment_type = "#"
200
- end
201
-
202
-
203
- # Open file and read in entire thing into an array
204
- # Use an array so we can look ahead when creating issues later
205
- # [review] - Not sure if explicit file close is required here
206
- # [review] - Better var name than data for read in file?
207
- _data = Array.new()
208
- File.open(_absolute_path, 'r').read.each_line do | _line |
209
- _data.push(_line)
210
- # If we can't encode (ever) to UTF-8, clear _data and break
211
- begin
212
- _line.encode('UTF-8', :invalid => :replace)
213
- rescue ArgumentError
214
- debug_print "Could not encode to UTF-8, non-text\n"
215
- _data = Array.new()
216
- break
217
- end
218
- end
219
-
220
- # Initialize issue list hash
221
- _issue_list = Hash.new()
222
- _issue_list[:relative_path] = _relative_path
223
- _issue_list[:absolute_path] = _absolute_path
224
- _issue_list[:has_issues] = false
225
- @config.tag_list.each do | _tag |
226
- debug_print "Creating array named #{ _tag }\n"
227
- # [review] - Use to_sym to make tag into symbol instead of string?
228
- _issue_list[_tag] = Array.new
229
- end
230
-
231
- # Loop through all array elements (lines in file) and look for issues
232
- _data.each_with_index do | _line, _i |
233
-
234
- # Find any comment line with [tag] - text (any comb of space and # acceptable)
235
- # Using if match to stay consistent (with config.rb) see there for
236
- # explanation of why I do this (not a good good one persay...)
237
- _mtch = _line.match(/^[#{ _comment_type }+?\s+?]+\[(\w+)\]\s+-\s+(.+)/)
238
- if !_mtch
239
- debug_print "No valid tag found in line, skipping\n"
240
- next
241
- end
242
-
243
- # Set tag
244
- _tag = _mtch[1]
245
-
246
- # Make sure that the tag that was found is something we accept
247
- # If not, skip it but tell user about an unrecognized tag
248
- if !@config.tag_list.include?(_tag)
249
- Printer.print_status "!", RED
250
- print "Unknown tag [#{ _tag }] found, ignoring\n"
251
- print " You might want to include it in your RC or with the -t/--tags flag\n"
252
- next
253
- end
254
-
255
- # Found a valid match (with recognized tag)
256
- # Set flag for this issue_list (for file) to indicate that
257
- _issue_list[:has_issues] = true
258
-
259
- _title = _mtch[2]
260
- debug_print "Issue found\n"
261
- debug_print "Tag: #{ _tag }\n"
262
- debug_print "Issue: #{ _title }\n"
263
-
264
- # Create hash for each issue found
265
- _issue = Hash.new
266
- _issue[:line_number] = _i + 1
267
- _issue[:title] = _title
268
-
269
- # Grab context of issue specified by Config param (+1 to include issue itself)
270
- _context = _data[_i..(_i + @config.context_depth + 1)]
271
-
272
- # [review] - There has got to be a better way to do this...
273
- # Go through each line of context and determine indentation
274
- # Used to preserve indentation in post
275
- _cut = Array.new
276
- _context.each do | _line |
277
- _max = 0
278
- # Until we reach a non indent OR the line is empty, keep slicin'
279
- until !_line.match(/^( |\t|\n)/) || _line.empty?
280
- # [fix] - Replace with inplace slice!
281
- _line = _line.slice(1..-1)
282
- _max = _max + 1
283
-
284
- debug_print "New line: #{ _line }\n"
285
- debug_print "Max indent: #{ _max }\n"
286
- end
287
-
288
- # Push max indent for current line to the _cut array
289
- _cut.push(_max)
290
- end
291
-
292
- # Print old _context
293
- debug_print "\n\n Old Context \n"
294
- debug_print PP.pp(_context, "")
295
- debug_print "\n\n"
296
-
297
- # Trim the context lines to be left aligned but maintain indentation
298
- # Then add a single \t to the beginning so the Markdown is pretty on GitHub/Bitbucket
299
- _context.map! { | _line | "\t#{ _line.slice(_cut.min .. -1) }" }
300
-
301
- # Print new _context
302
- debug_print("\n\n New Context \n")
303
- debug_print PP.pp(_context, "")
304
- debug_print("\n\n")
305
-
306
- _issue[:context] = _context
307
-
308
- # These are accessible from _issue_list, but we pass individual issues
309
- # to the remote poster, so we need this here to reference them for GitHub/Bitbucket
310
- _issue[:tag] = _tag
311
- _issue[:path] = _relative_path
312
-
313
- # Generate md5 hash for each specific issue (for bookkeeping)
314
- _issue[:md5] = ::Digest::MD5.hexdigest("#{ _tag }, #{ _relative_path }, #{ _title }")
315
- debug_print "#{ _issue }\n"
316
-
317
-
318
- # [todo] - Figure out a way to queue up posts so user has a progress bar?
319
- # That way user can tell that wait is because of http calls not app
320
-
321
- # If GitHub is valid, pass _issue to GitHub poster function
322
- # [review] - Keep Remote as a static method and pass config every time?
323
- # Or convert to a regular class and make an instance with @config
324
-
325
- if @config.remote_valid
326
- if @config.github_valid
327
- debug_print "GitHub is valid, posting issue\n"
328
- Remote::GitHub.post_issue(_issue, @config)
329
- else
330
- debug_print "GitHub invalid, not posting issue\n"
331
- end
332
-
333
-
334
- if @config.bitbucket_valid
335
- debug_print "Bitbucket is valid, posting issue\n"
336
- Remote::Bitbucket.post_issue(_issue, @config)
337
- else
338
- debug_print "Bitbucket invalid, not posting issue\n"
339
- end
340
- end
341
-
342
- # [review] - Use _tag string as symbol reference in hash or keep as string?
343
- # Look into to_sym to keep format of all _issue params the same
344
- _issue_list[_tag].push( _issue )
345
-
346
- end
347
-
348
- # [review] - Return of parse_file is different than watson-perl
349
- # Not sure which makes more sense, ruby version seems simpler
350
- # perl version might have to stay since hash scoping is weird in perl
351
- debug_print "\nIssue list: #{ _issue_list }\n"
352
-
353
- return _issue_list
354
- end
355
-
356
-
357
- ###########################################################
358
- # Get comment syntax for given file
359
- def get_comment_type(filename)
360
-
361
- # Identify method entry
362
- debug_print "#{ self } : #{ __method__ }\n"
363
-
364
- # Grab the file extension (.something)
365
- # Check to see whether it is recognized and set comment type
366
- # If unrecognized, try to grab the next .something extension
367
- # This is to account for file.cpp.1 or file.cpp.bak, ect
368
-
369
- # [review] - Matz style while loop a la http://stackoverflow.com/a/10713963/1604424
370
- # Create _mtch var so we can access it outside of the do loop
371
-
372
- _mtch = String.new()
373
- loop do
374
- _mtch = filename.match(/(\.(\w+))$/)
375
- debug_print "Extension: #{ _mtch }\n"
376
-
377
- # Break if we don't find a match
378
- break if _mtch.nil?
379
-
380
- # Determine file type
381
- case _mtch[0]
382
- # C / C++, Java, C#
383
- # [todo] - Add /* style comment
384
- when ".cpp", ".cc", ".c", ".hpp", ".h",
385
- ".java", ".class", ".cs", ".js", ".php"
386
- debug_print "Comment type is: //\n"
387
- return "//"
388
-
389
- when ".hs"
390
- debug_print "Comment type is: --\n"
391
- return "--"
392
-
393
- when ".erl"
394
- debug_print "Comment type is: %\n"
395
- return "%"
396
-
397
- # Bash, Ruby, Perl, Python
398
- when ".sh", ".rb", ".pl", ".py", ".coffee"
399
- debug_print "Comment type is: #\n"
400
- return "#"
401
-
402
- # Can't recognize extension, keep looping in case of .bk, .#, ect
403
- else
404
- filename = filename.gsub(/(\.(\w+))$/, "")
405
- debug_print "Didn't recognize, searching #{ filename }\n"
406
-
407
- end
408
- end
409
-
410
- # We didn't find any matches from the filename, return error (0)
411
- # Deal with what default to use in calling method
412
- # [review] - Is Ruby convention to return 1 or 0 (or -1) on failure/error?
413
- debug_print "Couldn't find any recognized extension type\n"
414
- return false
415
-
416
- end
417
-
418
-
419
- end
2
+ # Dir/File parser class
3
+ # Contains all necessary methods to parse through files and directories
4
+ # for specified tags and generate data structure containing found issues
5
+ class Parser
6
+
7
+ # Include for debug_print
8
+ include Watson
9
+
10
+ # [review] - Should this require be required higher up or fine here
11
+ # Include for Digest::MD5.hexdigest used in issue creating
12
+ require 'digest'
13
+ require 'pp'
14
+
15
+ # Debug printing for this class
16
+ DEBUG = false
17
+
18
+ ###########################################################
19
+ # Initialize the parser with the current watson config
20
+ def initialize(config)
21
+ # [review] - Not sure if passing config here is best way to access it
22
+
23
+ # Identify method entry
24
+ debug_print "#{ self } : #{ __method__ }\n"
25
+
26
+ @config = config
27
+ end
28
+
29
+
30
+ ###########################################################
31
+ # Begins parsing of files / dirs specified in the initial dir/file lists
32
+ def run
33
+
34
+ # Identify method entry
35
+ debug_print "#{ self } : #{ __method__ }\n"
36
+
37
+ # Go through all files added from CL (sort them first)
38
+ # If empty, sort and each will do nothing, no errors
39
+ _completed_dirs = Array.new()
40
+ _completed_files = Array.new()
41
+ if @config.cl_entry_set
42
+ @config.file_list.sort.each do |_file|
43
+ _completed_files.push(parse_file(_file))
44
+ end
45
+ end
46
+
47
+ # Then go through all the specified directories
48
+ # Initial parse depth to parse_dir is 0 (unlimited)
49
+ @config.dir_list.sort.each do |_dir|
50
+ _completed_dirs.push(parse_dir(_dir, 0))
51
+ end
52
+
53
+ # Create overall hash for parsed files
54
+ _structure = Hash.new()
55
+ _structure[:files] = _completed_files
56
+ _structure[:subdirs] = _completed_dirs
57
+
58
+ debug_print "_structure dump\n\n"
59
+ debug_print PP.pp(_structure, '')
60
+ debug_print "\n\n"
61
+
62
+ _structure
63
+ end
64
+
65
+
66
+ ###########################################################
67
+ # Parse through specified directory and find all subdirs and files
68
+ def parse_dir(dir, depth)
69
+
70
+ # Identify method entry
71
+ debug_print "#{ self } : #{ __method__ }\n"
72
+
73
+ # Error check on input
74
+ if Watson::FS.check_dir(dir)
75
+ debug_print "Opened #{ dir } for parsing\n"
76
+ else
77
+ print "Unable to open #{ dir }, exiting\n"
78
+ return false
79
+ end
80
+
81
+ debug_print "Parsing through all files/directories in #{ dir }\n"
82
+
83
+ # [review] - Shifted away from single Dir.glob loop to separate for dir/file
84
+ # This duplicates code but is much better for readability
85
+ # Not sure which is preferred?
86
+
87
+
88
+ # Remove leading . or ./
89
+ _glob_dir = dir.gsub(/^\.(\/?)/, '')
90
+ debug_print "_glob_dir: #{_glob_dir}\n"
91
+
92
+
93
+ # Go through directory to find all files
94
+ # Create new array to hold all parsed files
95
+ _completed_files = Array.new()
96
+ Dir.glob("#{ _glob_dir }{*,.*}").select { |_fn| File.file?(_fn) }.sort.each do |_entry|
97
+ debug_print "Entry: #{_entry} is a file\n"
98
+
99
+
100
+ # [review] - Warning to user when file is ignored? (outside of debug_print)
101
+ # Check against ignore list, if match, set to "" which will be ignored
102
+ @config.ignore_list.each do |_ignore|
103
+ # [review] - Better "Ruby" way to check for "*"?
104
+ # [review] - Probably cleaner way to perform multiple checks below
105
+ # Look for *.type on list, regex to match entry
106
+ if _ignore[0] == '*'
107
+ _cut = _ignore[1..-1]
108
+ if _entry.match(/#{ _cut }/)
109
+ debug_print "#{ _entry } is on the ignore list, setting to \"\"\n"
110
+ _entry = ''
111
+ break
112
+ end
113
+ # Else check for verbose ignore match
114
+ else
115
+ if _entry == _ignore || File.absolute_path(_entry) == _ignore
116
+ debug_print "#{ _entry } is on the ignore list, setting to \"\"\n"
117
+ _entry = ''
118
+ break
119
+ end
120
+ end
121
+ end
122
+
123
+ # If the resulting entry (after filtering) isn't empty, parse it and push into file array
124
+ unless _entry.empty?
125
+ debug_print "Parsing #{ _entry }\n"
126
+ _completed_files.push(parse_file(_entry))
127
+ end
128
+
129
+ end
130
+
131
+
132
+ # Go through directory to find all subdirs
133
+ # Create new array to hold all parsed subdirs
134
+ _completed_dirs = Array.new()
135
+ Dir.glob("#{ _glob_dir }{*, .*}").select { |_fn| File.directory?(_fn) }.sort.each do |_entry|
136
+ debug_print "Entry: #{ _entry } is a dir\n"
137
+
138
+
139
+ ## Depth limit logic
140
+ # Current depth is depth of previous parse_dir (passed in as second param) + 1
141
+ _cur_depth = depth + 1
142
+ debug_print "Current Folder depth: #{ _cur_depth }\n"
143
+
144
+ # If Config.parse_depth is 0, no limit on subdir parsing
145
+ if @config.parse_depth == 0
146
+ debug_print "No max depth, parsing directory\n"
147
+ _completed_dirs.push(parse_dir("#{ _entry }/", _cur_depth))
148
+ # If current depth is less than limit (set in config), parse directory and pass depth
149
+ elsif _cur_depth < @config.parse_depth.to_i + 1
150
+ debug_print "Depth less than max dept (from config), parsing directory\n"
151
+ _completed_dirs.push(parse_dir("#{ _entry }/", _cur_depth))
152
+ # Else, depth is greater than limit, ignore the directory
153
+ else
154
+ debug_print "Depth greater than max depth, ignoring\n"
155
+ end
156
+
157
+ # Add directory to ignore list so it isn't repeated again accidentally
158
+ @config.ignore_list.push(_entry)
159
+ end
160
+
161
+
162
+ # [review] - Not sure if Dir.glob requires a explicit directory/file close?
163
+
164
+ # Create hash to hold all parsed files and directories
165
+ _structure = Hash.new()
166
+ _structure[:curdir] = dir
167
+ _structure[:files] = _completed_files
168
+ _structure[:subdirs] = _completed_dirs
169
+ _structure
170
+ end
171
+
172
+
173
+ ###########################################################
174
+ # Parse through individual files looking for issue tags, then generate formatted issue hash
175
+ #noinspection RubyResolve
176
+ def parse_file(filename)
177
+ # [review] - Rename method input param to filename (more verbose?)
178
+
179
+ # Identify method entry
180
+ debug_print "#{ self } : #{ __method__ }\n"
181
+
182
+ _relative_path = filename
183
+ _absolute_path = File.absolute_path(filename)
184
+
185
+ # Error check on input, use input filename to make sure relative path is correct
186
+ if Watson::FS.check_file(_relative_path)
187
+ debug_print "Opened #{ _relative_path } for parsing\n"
188
+ debug_print "Short path: #{ _relative_path }\n"
189
+ else
190
+ print "Unable to open #{ _relative_path }, exiting\n"
191
+ return false
192
+ end
193
+
194
+
195
+ # Get filetype and set corresponding comment type
196
+ _comment_type = get_comment_type(_relative_path)
197
+ unless _comment_type
198
+ debug_print "Using default (#) comment type\n"
199
+ _comment_type = '#'
200
+ end
201
+
202
+
203
+ # Open file and read in entire thing into an array
204
+ # Use an array so we can look ahead when creating issues later
205
+ # [review] - Not sure if explicit file close is required here
206
+ # [review] - Better var name than data for read in file?
207
+ _data = Array.new()
208
+ File.open(_absolute_path, 'r').read.each_line do |_line|
209
+ _data.push(_line)
210
+ _line.encode('UTF-8', :invalid => :replace)
211
+ end
212
+
213
+ # Initialize issue list hash
214
+ _issue_list = Hash.new()
215
+ _issue_list[:relative_path] = _relative_path
216
+ _issue_list[:absolute_path] = _absolute_path
217
+ _issue_list[:has_issues] = false
218
+ @config.tag_list.each do | _tag |
219
+ debug_print "Creating array named #{ _tag }\n"
220
+ # [review] - Use to_sym to make tag into symbol instead of string?
221
+ _issue_list[_tag] = Array.new
222
+ end
223
+
224
+ # Loop through all array elements (lines in file) and look for issues
225
+ _data.each_with_index do |_line, _i|
226
+
227
+ # Find any comment line with [tag] - text (any comb of space and # acceptable)
228
+ # Using if match to stay consistent (with config.rb) see there for
229
+ # explanation of why I do this (not a good good one persay...)
230
+ begin
231
+ _mtch = _line.match(/^[#{ _comment_type }+?\s+?]+\[(\w+)\]\s+-\s+(.+)/)
232
+ rescue ArgumentError
233
+ debug_print "Could not encode to UTF-8, non-text\n"
234
+ end
235
+
236
+ unless _mtch
237
+ debug_print "No valid tag found in line, skipping\n"
238
+ next
239
+ end
240
+
241
+ # Set tag
242
+ _tag = _mtch[1]
243
+
244
+ # Make sure that the tag that was found is something we accept
245
+ # If not, skip it but tell user about an unrecognized tag
246
+ unless @config.tag_list.include?(_tag)
247
+ Printer.print_status '!', RED
248
+ print "Unknown tag [#{ _tag }] found, ignoring\n"
249
+ print " You might want to include it in your RC or with the -t/--tags flag\n"
250
+ next
251
+ end
252
+
253
+ # Found a valid match (with recognized tag)
254
+ # Set flag for this issue_list (for file) to indicate that
255
+ _issue_list[:has_issues] = true
256
+
257
+ _title = _mtch[2]
258
+ debug_print "Issue found\n"
259
+ debug_print "Tag: #{ _tag }\n"
260
+ debug_print "Issue: #{ _title }\n"
261
+
262
+ # Create hash for each issue found
263
+ _issue = Hash.new
264
+ _issue[:line_number] = _i + 1
265
+ _issue[:title] = _title
266
+
267
+ # Grab context of issue specified by Config param (+1 to include issue itself)
268
+ _context = _data[_i..(_i + @config.context_depth + 1)]
269
+
270
+ # [review] - There has got to be a better way to do this...
271
+ # Go through each line of context and determine indentation
272
+ # Used to preserve indentation in post
273
+ _cut = Array.new
274
+ _context.each do |_line_sub|
275
+ _max = 0
276
+ # Until we reach a non indent OR the line is empty, keep slicin'
277
+ until !_line_sub.match(/^( |\t|\n)/) || _line_sub.empty?
278
+ # [fix] - Replace with inplace slice!
279
+ _line_sub = _line_sub.slice(1..-1)
280
+ _max = _max + 1
281
+
282
+ debug_print "New line: #{ _line_sub }\n"
283
+ debug_print "Max indent: #{ _max }\n"
284
+ end
285
+
286
+ # Push max indent for current line to the _cut array
287
+ _cut.push(_max)
288
+ end
289
+
290
+ # Print old _context
291
+ debug_print "\n\n Old Context \n"
292
+ debug_print PP.pp(_context, '')
293
+ debug_print "\n\n"
294
+
295
+ # Trim the context lines to be left aligned but maintain indentation
296
+ # Then add a single \t to the beginning so the Markdown is pretty on GitHub/Bitbucket
297
+ _context.map! { |_line_sub| "\t#{ _line_sub.slice(_cut.min .. -1) }" }
298
+
299
+ # Print new _context
300
+ debug_print("\n\n New Context \n")
301
+ debug_print PP.pp(_context, '')
302
+ debug_print("\n\n")
303
+
304
+ _issue[:context] = _context
305
+
306
+ # These are accessible from _issue_list, but we pass individual issues
307
+ # to the remote poster, so we need this here to reference them for GitHub/Bitbucket
308
+ _issue[:tag] = _tag
309
+ _issue[:path] = _relative_path
310
+
311
+ # Generate md5 hash for each specific issue (for bookkeeping)
312
+ _issue[:md5] = ::Digest::MD5.hexdigest("#{ _tag }, #{ _relative_path }, #{ _title }")
313
+ debug_print "#{ _issue }\n"
314
+
315
+
316
+ # [todo] - Figure out a way to queue up posts so user has a progress bar?
317
+ # That way user can tell that wait is because of http calls not app
318
+
319
+ # If GitHub is valid, pass _issue to GitHub poster function
320
+ # [review] - Keep Remote as a static method and pass config every time?
321
+ # Or convert to a regular class and make an instance with @config
322
+
323
+ if @config.remote_valid
324
+ if @config.github_valid
325
+ debug_print "GitHub is valid, posting issue\n"
326
+ Remote::GitHub.post_issue(_issue, @config)
327
+ else
328
+ debug_print "GitHub invalid, not posting issue\n"
329
+ end
330
+
331
+
332
+ if @config.bitbucket_valid
333
+ debug_print "Bitbucket is valid, posting issue\n"
334
+ Remote::Bitbucket.post_issue(_issue, @config)
335
+ else
336
+ debug_print "Bitbucket invalid, not posting issue\n"
337
+ end
338
+ end
339
+
340
+ # [review] - Use _tag string as symbol reference in hash or keep as string?
341
+ # Look into to_sym to keep format of all _issue params the same
342
+ _issue_list[_tag].push(_issue)
343
+
344
+ end
345
+
346
+ # [review] - Return of parse_file is different than watson-perl
347
+ # Not sure which makes more sense, ruby version seems simpler
348
+ # perl version might have to stay since hash scoping is weird in perl
349
+ debug_print "\nIssue list: #{ _issue_list }\n"
350
+
351
+ _issue_list
352
+ end
353
+
354
+
355
+ ###########################################################
356
+ # Get comment syntax for given file
357
+ def get_comment_type(filename)
358
+
359
+ # Identify method entry
360
+ debug_print "#{ self } : #{ __method__ }\n"
361
+
362
+ # Grab the file extension (.something)
363
+ # Check to see whether it is recognized and set comment type
364
+ # If unrecognized, try to grab the next .something extension
365
+ # This is to account for file.cpp.1 or file.cpp.bak, ect
366
+
367
+ # [review] - Matz style while loop a la http://stackoverflow.com/a/10713963/1604424
368
+ # Create _mtch var so we can access it outside of the do loop
369
+
370
+
371
+ loop do
372
+ _mtch = filename.match(/(\.(\w+))$/)
373
+ debug_print "Extension: #{ _mtch }\n"
374
+
375
+ # Break if we don't find a match
376
+ break if _mtch.nil?
377
+
378
+ # Determine file type
379
+ case _mtch[0]
380
+ # C / C++, Java, C#
381
+ # [todo] - Add /* style comment
382
+ when '.cpp', '.cc', '.c', '.hpp', '.h',
383
+ '.java', '.class', '.cs', '.js', '.php',
384
+ '.m', '.mm', '.go'
385
+ debug_print "Comment type is: //\n"
386
+ return '//'
387
+
388
+ when '.hs'
389
+ debug_print "Comment type is: --\n"
390
+ return '--'
391
+
392
+ when '.erl'
393
+ debug_print "Comment type is: %\n"
394
+ return '%'
395
+
396
+ # Bash, Ruby, Perl, Python
397
+ when '.sh', '.rb', '.pl', '.py', '.coffee'
398
+ debug_print "Comment type is: #\n"
399
+ return '#'
400
+
401
+ # Can't recognize extension, keep looping in case of .bk, .#, ect
402
+ else
403
+ filename = filename.gsub(/(\.(\w+))$/, '')
404
+ debug_print "Didn't recognize, searching #{ filename }\n"
405
+
406
+ end
407
+ end
408
+
409
+ # We didn't find any matches from the filename, return error (0)
410
+ # Deal with what default to use in calling method
411
+ # [review] - Is Ruby convention to return 1 or 0 (or -1) on failure/error?
412
+ debug_print "Couldn't find any recognized extension type\n"
413
+ false
414
+
415
+ end
416
+
417
+
418
+ end
420
419
  end