watson-ruby 1.0.0

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.
@@ -0,0 +1,293 @@
1
+ module Watson
2
+
3
+ # Color definitions for pretty printing
4
+ # Defined here because we need Global scope but makes sense to have them
5
+ # in the printer.rb file at least
6
+
7
+ BOLD = "\e[01m"
8
+ UNDERLINE = "\e[4m"
9
+ RESET = "\e[00m"
10
+
11
+ GRAY = "\e[38;5;0m"
12
+ RED = "\e[38;5;1m"
13
+ GREEN = "\e[38;5;2m"
14
+ YELLOW = "\e[38;5;3m"
15
+ BLUE = "\e[38;5;4m"
16
+ MAGENTA = "\e[38;5;5m"
17
+ CYAN = "\e[38;5;6m"
18
+ WHITE = "\e[38;5;7m"
19
+
20
+
21
+ # Printer class that handles all formatting and printing of parsed dir/file structure
22
+ class Printer
23
+ # [review] - Not sure if the way static methods are defined is correct
24
+ # Ok to have same name as instance methods?
25
+ # Only difference is where the output gets printed to
26
+ # [review] - No real setup in initialize method, combine it and run method?
27
+
28
+ # Include for debug_print (for class methods)
29
+ include Watson
30
+
31
+ # Debug printing for this class
32
+ DEBUG = false
33
+
34
+ class << self
35
+
36
+ # Include for debug_print (for static methods)
37
+ include Watson
38
+
39
+ ###########################################################
40
+ # Custom color print for static call (only writes to STDOUT)
41
+ def cprint (msg = "", color = "")
42
+
43
+ # Identify method entry
44
+ debug_print "#{ self } : #{ __method__ }\n"
45
+
46
+ # This little check will allow us to take a Constant defined color
47
+ # As well as a [0-256] value if specified
48
+ if (color.is_a?(String))
49
+ debug_print "Custom color specified for cprint\n"
50
+ STDOUT.write(color)
51
+ elsif (color.between?(0, 256))
52
+ debug_print "No or Default color specified for cprint\n"
53
+ STDOUT.write("\e[38;5;#{ color }m")
54
+ end
55
+
56
+ STDOUT.write(msg)
57
+ end
58
+
59
+
60
+ ###########################################################
61
+ # Standard header print for static call (uses static cprint)
62
+ def print_header
63
+
64
+ # Identify method entry
65
+ debug_print "#{ self } : #{ __method__ }\n"
66
+
67
+ # Header
68
+ cprint BOLD + "------------------------------\n" + RESET
69
+ cprint BOLD + "watson" + RESET
70
+ cprint " - " + RESET
71
+ cprint BOLD + YELLOW + "inline issue manager\n" + RESET
72
+ cprint BOLD + "------------------------------\n\n" + RESET
73
+
74
+ return true
75
+ end
76
+
77
+
78
+ ###########################################################
79
+ # Status printer for static call (uses static cprint)
80
+ # Print status block in standard format
81
+ def print_status(msg, color)
82
+ cprint RESET + BOLD
83
+ cprint WHITE + "[ "
84
+ cprint "#{ msg } ", color
85
+ cprint WHITE + "] " + RESET
86
+ end
87
+
88
+ end
89
+
90
+ ###########################################################
91
+ # Printer initialization method to setup necessary parameters, states, and vars
92
+ def initialize(config)
93
+
94
+ # Identify method entry
95
+ debug_print "#{ self } : #{ __method__ }\n"
96
+
97
+ @config = config
98
+ return true
99
+ end
100
+
101
+
102
+ ###########################################################
103
+ # Take parsed structure and print out in specified formatting
104
+ def run(structure)
105
+
106
+ # Identify method entry
107
+ debug_print "#{ self } : #{ __method__ }\n"
108
+
109
+ # Check Config to see if we have access to less for printing
110
+ # If so, open our temp file as the output to write to
111
+ # Else, just print out to STDOUT
112
+ if @config.use_less
113
+ debug_print "Unix less avaliable, setting output to #{ @config.tmp_file }\n"
114
+ @output = File.open(@config.tmp_file, 'w')
115
+ elsif
116
+ debug_print "Unix less is unavaliable, setting output to STDOUT\n"
117
+ @output = STDOUT
118
+ end
119
+
120
+ # Print header for output
121
+ debug_print "Printing Header\n"
122
+ print_header
123
+
124
+ # Print out structure that was passed to this Printer
125
+ debug_print "Starting structure printing\n"
126
+ print_structure(structure)
127
+
128
+ # If we are using less, close the output file, display with less, then delete
129
+ if @config.use_less
130
+ @output.close
131
+ # [review] - Way of calling a native Ruby less?
132
+ system("less #{ @config.tmp_file }")
133
+ debug_print "File displayed with less, now deleting...\n"
134
+ File.delete(@config.tmp_file)
135
+ end
136
+
137
+ return true
138
+ end
139
+
140
+
141
+ ###########################################################
142
+ # Custom color print for member call
143
+ # Allows not only for custom color printing but writing to file vs STDOUT
144
+ def cprint (msg = "", color = "")
145
+
146
+ # Identify method entry
147
+ debug_print "#{ self } : #{ __method__ }\n"
148
+
149
+ # This little check will allow us to take a Constant defined color
150
+ # As well as a [0-256] value if specified
151
+ if (color.is_a?(String))
152
+ debug_print "Custom color specified for cprint\n"
153
+ @output.write(color)
154
+ elsif (color.between?(0, 256))
155
+ debug_print "No or Default color specified for cprint\n"
156
+ @output.write("\e[38;5;#{ color }m")
157
+ end
158
+
159
+ @output.write(msg)
160
+ end
161
+
162
+
163
+ ###########################################################
164
+ # Standard header print for class call (uses member cprint)
165
+ def print_header
166
+ # Identify method entry
167
+
168
+ debug_print "#{ self } : #{ __method__ }\n"
169
+
170
+ # Header
171
+ cprint BOLD + "------------------------------\n" + RESET
172
+ cprint BOLD + "watson" + RESET
173
+ cprint " - " + RESET
174
+ cprint BOLD + YELLOW + "inline issue manager\n\n" + RESET
175
+ cprint "Run in: #{ Dir.pwd }\n"
176
+ cprint "Run @ #{ Time.now.asctime }\n"
177
+ cprint BOLD + "------------------------------\n\n" + RESET
178
+
179
+ return true
180
+ end
181
+
182
+
183
+ ###########################################################
184
+ # Status printer for member call (uses member cprint)
185
+ # Print status block in standard format
186
+ def print_status(msg, color)
187
+ cprint RESET + BOLD
188
+ cprint WHITE + "[ "
189
+ cprint "#{ msg } ", color
190
+ cprint WHITE + "] " + RESET
191
+ end
192
+
193
+
194
+ ###########################################################
195
+ # Go through all files and directories and call necessary printing methods
196
+ # Print all individual entries, call print_structure on each subdir
197
+ def print_structure(structure)
198
+
199
+ # Identify method entry
200
+ debug_print "#{ self } : #{ __method__ }\n"
201
+
202
+ # First go through all the files in the current structure
203
+ # The current "structure" should reflect a dir/subdir
204
+ structure[:files].each do | _file |
205
+ debug_print "Printing info for #{ _file }\n"
206
+ print_entry(_file)
207
+ end
208
+
209
+ # Next go through all the subdirs and pass them to print_structure
210
+ structure[:subdirs].each do | _subdir |
211
+ debug_print "Entering #{ _subdir } to print further\n"
212
+ print_structure(_subdir)
213
+ end
214
+ end
215
+
216
+
217
+ ###########################################################
218
+ # Individual entry printer
219
+ # Uses issue hash to format printed output
220
+ def print_entry(entry)
221
+
222
+ # Identify method entry
223
+ debug_print "#{ self } : #{ __method__ }\n"
224
+
225
+ # If no issues for this file, print that and break
226
+ # The filename print is repetative, but reduces another check later
227
+ if entry[:has_issues] == false
228
+ debug_print "No issues for #{ entry }\n"
229
+ print_status "o", GREEN
230
+ cprint BOLD + UNDERLINE + GREEN + "#{ entry[:relative_path] }" + RESET + "\n"
231
+ return true
232
+ else
233
+ debug_print "Issues found for #{ entry }\n"
234
+ cprint "\n"
235
+ print_status "x", RED
236
+ cprint BOLD + UNDERLINE + RED + "#{entry[:relative_path]}" + RESET + "\n"
237
+ end
238
+
239
+
240
+ # [review] - Should the tag structure be self contained in the hash
241
+ # Or is it ok to reference @config to figure out the tags
242
+ @config.tag_list.each do | _tag |
243
+ debug_print "Checking for #{ _tag }\n"
244
+
245
+ # [review] - Better way to ignore tags through structure (hash) data
246
+ # Maybe have individual has_issues for each one?
247
+ if entry[_tag].size.zero?
248
+ debug_print "#{ _tag } has no issues, skipping\n"
249
+ cprint "\n"
250
+ next
251
+ end
252
+
253
+ debug_print "#{ _tag } has issues in it, print!\n"
254
+ print_status "#{ _tag }", BLUE
255
+ cprint "\n"
256
+
257
+ # Go through each issue in tag
258
+ entry[_tag].each do | _issue |
259
+ cprint WHITE + " line #{ _issue[:line_number] } - " + RESET
260
+ cprint BOLD + "#{ _issue[:title] }" + RESET
261
+
262
+
263
+ # Check to see if it has been resolved on GitHub/Bitbucket
264
+ debug_print "Checking if issue has been resolved\n"
265
+ @config.github_issues[:closed].each do | _closed |
266
+ if _closed["body"].include?(_issue[:md5])
267
+ debug_print "Found in #{ _closed[:comment] }, not posting\n"
268
+ cprint BOLD + " [" + RESET
269
+ cprint GREEN + BOLD + "Resolved on GitHub" + RESET
270
+ cprint BOLD + "]" + RESET
271
+ end
272
+ debug_print "Did not find in #{ _closed[:comment] }\n"
273
+ end
274
+
275
+ debug_print "Checking if issue has been resolved\n"
276
+ @config.bitbucket_issues[:closed].each do | _closed |
277
+ if _closed["content"].include?(_issue[:md5])
278
+ debug_print "Found in #{ _closed["content"] }, not posting\n"
279
+ cprint BOLD + " [" + RESET
280
+ cprint GREEN + BOLD + "Resolved on Bitbucket" + RESET
281
+ cprint BOLD + "]\n" + RESET
282
+ end
283
+ debug_print "Did not find in #{ _closed["title"] }\n"
284
+ end
285
+ cprint "\n"
286
+
287
+ end
288
+ cprint "\n"
289
+ end
290
+ end
291
+
292
+ end
293
+ end
@@ -0,0 +1,120 @@
1
+ module Watson
2
+ # Remote class that handles all remote HTTP calls to Bitbucket and GitHub
3
+ class Remote
4
+
5
+ # Debug printing for this class
6
+ DEBUG = false
7
+
8
+ class << self
9
+
10
+ # Include for debug_print
11
+ include Watson
12
+
13
+ # Required libs
14
+ require 'net/https'
15
+ require 'uri'
16
+ require 'json'
17
+
18
+
19
+ # Default options hash for http_call
20
+ # Will get merged with input argument hash to maintain defaults
21
+ HTTP_opts = {
22
+ :url => nil, #--> URL of endpoint [String]
23
+ :ssl => false, #--> Use SSL in connection (HTTPS) (True/False]
24
+ :method => nil, #--> GET or POST for respective HTTP method [String]
25
+ :basic_auth => Array.new(0), #--> Array of username and pw to use for basic authentication
26
+ # If empty, assume no user authentication [Array]
27
+ :auth => nil, #--> Authentication token [String]
28
+ :data => nil, #--> Hash of data to be POST'd in HTTP request [Hash]
29
+ :verbose => false #--> Turn on verbose debug for this call [True/False]
30
+ }
31
+
32
+ ###########################################################
33
+ # Generic HTTP call method
34
+ # Accepts input hash of options that dictate how the HTTP call is to be made
35
+ def http_call( opts )
36
+ # [review] - Don't use DEBUG inside Remote class but pull from calling method's class?
37
+ # [review] - Not sure if this is the best/proper way to do things but it works...
38
+
39
+ # Identify method entry
40
+ debug_print "#{ self.class } : #{ __method__ }\n"
41
+
42
+ # Merge default options with those passed in by user to form complete opt list
43
+ opts = HTTP_opts.merge(opts)
44
+
45
+
46
+ # Check URL in hash and get URI from it, then set up HTTP connection
47
+ if opts[:url] =~ /^#{URI::regexp}$/
48
+ _uri = URI(opts[:url])
49
+ else
50
+ debug_print "No URL specified in input opts, exiting HTTP call\n"
51
+ return false
52
+ end
53
+
54
+ _http = Net::HTTP.new(_uri.host, _uri.port)
55
+
56
+ # Print out verbose HTTP request if :verbose is set
57
+ # For hardcore debugging when shit really doesn't work
58
+ _http.set_debug_output $stderr if opts[:verbose] == true
59
+
60
+ # If SSL is set in hash, set HTTP connection to use SSL
61
+ _http.use_ssl = true if opts[:ssl] == true
62
+
63
+ # Create request based on HTTP method
64
+ # [review] - Not sure if to fail with no method or default to GET?
65
+ case opts[:method].upcase
66
+ when "GET"
67
+ _req = Net::HTTP::Get.new(_uri.request_uri)
68
+
69
+ when "POST"
70
+ _req = Net::HTTP::Post.new(_uri.request_uri)
71
+
72
+ else
73
+ debug_print "No method specified, cannot make HTTP request\n"
74
+ return false
75
+ end
76
+
77
+ # Check for basic authentication key in hash
78
+ if opts[:basic_auth].size == 2
79
+ _req.basic_auth(opts[:basic_auth][0], opts[:basic_auth][1])
80
+ end
81
+
82
+ # Check for Authentication token key in hash to be used in header
83
+ # I think this is pretty universal, but specifically works for GitHub
84
+ if opts[:auth]
85
+ _req["Authorization"] = "token #{ opts[:auth] }"
86
+ end
87
+
88
+ # [review] - Add :data_format to use set_form_data vs json body?
89
+ # For now, use Hash or Array, this is to differentiate between
90
+ # putting post data in body vs putting it in the form
91
+
92
+ # If a POST method, :data is present, and is a Hash, fill request body with data
93
+ if opts[:method].upcase == "POST" && opts[:data] && opts[:data].is_a?(Hash)
94
+ _req.body = opts[:data].to_json
95
+ end
96
+
97
+ # If a POST method, :data is present, and is an Array, use set_form_data
98
+ if opts[:method].upcase == "POST" && opts[:data] && opts[:data].is_a?(Array)
99
+ _req.set_form_data(opts[:data][0])
100
+ end
101
+
102
+ # Make HTTP request
103
+ _resp = _http.request(_req)
104
+
105
+ # Debug prints for status and message
106
+ debug_print "HTTP Response Code: #{ _resp.code }\n"
107
+ debug_print "HTTP Response Msg: #{ _resp.message }\n"
108
+
109
+ # [fix] - Not sure if 401 is the only code that gives nonparseable body?
110
+ # Figure out what other response codes are bad news for JSON.parse
111
+ _json = _resp.code == "401" ? Hash.new : JSON.parse(_resp.body)
112
+ debug_print "JSON: \n #{ _json }\n"
113
+
114
+ # [review] - Returning hash of json + response the right thing to do?
115
+ # return {:json => _json, :resp => _resp}
116
+ return _json, _resp
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,3 @@
1
+ module Watson
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,119 @@
1
+ module Watson
2
+ require_relative 'helper_spec'
3
+
4
+ describe Config do
5
+ before(:each) do
6
+ @config = Config.new
7
+ @file = @config.instance_variable_get(:@rc_file)
8
+ silence_output
9
+ end
10
+
11
+ after(:each) do
12
+ File.delete(@file) if File.exists?(@file)
13
+ enable_output
14
+ end
15
+
16
+
17
+ describe '#create_conf' do
18
+ it 'create config file, return true' do
19
+ @config.create_conf.should be_true
20
+ File.exists?(@file).should be_true
21
+ end
22
+ end
23
+
24
+ describe '#check_conf' do
25
+ context 'config does not exist' do
26
+ it 'create config file, return true' do
27
+ @config.check_conf.should be_true
28
+ File.exists?(@file).should be_true
29
+ end
30
+ end
31
+
32
+ context 'config does exist' do
33
+ it 'should return true' do
34
+ @config.check_conf.should be_true
35
+ File.exists?(@file).should be_true
36
+ end
37
+ end
38
+ end
39
+
40
+ describe '#read_conf' do
41
+ context 'config does not exist' do
42
+ it 'no config, return false' do
43
+ @config.read_conf.should be_false
44
+ end
45
+ end
46
+
47
+ context 'config does exist' do
48
+ before { @config.create_conf }
49
+
50
+ it 'return true, values match default config' do
51
+ @config.create_conf.should be_true
52
+ @config.read_conf.should be_true
53
+ @config.dir_list.should include('.')
54
+ @config.tag_list.should include('fix', 'review', 'todo')
55
+ @config.ignore_list.should include('.git', '*.swp')
56
+
57
+ end
58
+ end
59
+ end
60
+
61
+ describe '#update_conf' do
62
+ before do
63
+ @config.check_conf
64
+ @config.parse_depth = 1000
65
+ @config.update_conf('parse_depth')
66
+ end
67
+
68
+ it 'updated config.parse_depth should be 1000' do
69
+ @new_config = Config.new
70
+ @new_config.check_conf.should be_true
71
+ @new_config.read_conf.should be_true
72
+ @new_config.parse_depth.to_i.should eql 1000
73
+ end
74
+ end
75
+
76
+ # [review] - Should this be #initialize or #new?
77
+ describe '#initialize' do
78
+ it 'should initialize all member vars' do
79
+ @config.cl_entry_set.should be_false
80
+ @config.cl_tag_set.should be_false
81
+ @config.cl_ignore_set.should be_false
82
+
83
+ @config.use_less.should be_false
84
+
85
+ @config.ignore_list.should == []
86
+ @config.dir_list.should == []
87
+ @config.file_list.should == []
88
+ @config.tag_list.should == []
89
+
90
+ @config.remote_valid.should be_false
91
+
92
+ @config.github_valid.should be_false
93
+ @config.github_api.should == ''
94
+ @config.github_repo.should == ''
95
+ @config.github_issues.should == {:open => Hash.new(),
96
+ :closed => Hash.new() }
97
+
98
+ @config.bitbucket_valid.should be_false
99
+ @config.bitbucket_api.should == ''
100
+ @config.bitbucket_repo.should == ''
101
+ @config.bitbucket_issues.should == {:open => Hash.new(),
102
+ :closed => Hash.new() }
103
+
104
+ end
105
+ end
106
+
107
+ describe '#run' do
108
+ it 'should populate all member vars' do
109
+ @config.run
110
+ @config.ignore_list.should include('.git', '*.swp')
111
+ @config.tag_list.should include('fix', 'review', 'todo')
112
+ @config.dir_list.should include('.')
113
+ end
114
+
115
+ end
116
+
117
+ end
118
+
119
+ end