watson-ruby 1.0.0

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