watson-ruby 1.0.3 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
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