wassup 0.2.1 → 0.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fe6fcd8e497b99430a22d16872f83d9b8a08356436c95f222bb3c9bed1b86d47
4
- data.tar.gz: db98e8f4af5997b228bd8f148b13fc0b09681dcfa5486ad8d37e8c21aae0dc7b
3
+ metadata.gz: f810171df69f9c53ab10416de45b9c539881923be7aa7036c72974faea63518c
4
+ data.tar.gz: af206eb3965efeedf1ef5d388d787baa2e8ed5890ff3786d26b0199e87168656
5
5
  SHA512:
6
- metadata.gz: 7623256788359eac541c314581b9b552257f58af44a36216df2922c80a411a9a0dd078e3ca3291b0b0943706689f06c49ec27898879bca2a2131857e960c8b21
7
- data.tar.gz: 9b4a0cbb77b12eebb1e9a1392d4d0ac6fcc9796aac72c9632bab5d9217aba1d179847220348532106ae4175131207f3f1484041b7527db1cc5e7957f038d3b89
6
+ metadata.gz: 0ef15857c9aecbcb4cd7723e4695604415ce4d86ae47aa95ca701750e1fdc54745bf241d79e39935f38dd0292fb0f6a6eff7f62bef39537cd778ea0c61c79a6c
7
+ data.tar.gz: 627382fd74f6ff0645b508400e6375df42ab5de54b37381153fdc784687f224df23d23d5d1454427281fc143c68e5105dfba547aa3de685c3dfcdaa3e53e3aa8
data/Gemfile.lock CHANGED
@@ -1,15 +1,30 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- wassup (0.1.0)
4
+ wassup (0.2.1)
5
5
  curses
6
+ rest-client
6
7
 
7
8
  GEM
8
9
  remote: https://rubygems.org/
9
10
  specs:
10
11
  curses (1.4.2)
11
12
  diff-lcs (1.4.4)
13
+ domain_name (0.5.20190701)
14
+ unf (>= 0.0.5, < 1.0.0)
15
+ http-accept (1.7.0)
16
+ http-cookie (1.0.4)
17
+ domain_name (~> 0.5)
18
+ mime-types (3.4.1)
19
+ mime-types-data (~> 3.2015)
20
+ mime-types-data (3.2021.1115)
21
+ netrc (0.11.0)
12
22
  rake (12.3.3)
23
+ rest-client (2.1.0)
24
+ http-accept (>= 1.7.0, < 2.0)
25
+ http-cookie (>= 1.0.2, < 2.0)
26
+ mime-types (>= 1.16, < 4.0)
27
+ netrc (~> 0.8)
13
28
  rspec (3.10.0)
14
29
  rspec-core (~> 3.10.0)
15
30
  rspec-expectations (~> 3.10.0)
@@ -23,6 +38,9 @@ GEM
23
38
  diff-lcs (>= 1.2.0, < 2.0)
24
39
  rspec-support (~> 3.10.0)
25
40
  rspec-support (3.10.3)
41
+ unf (0.1.4)
42
+ unf_ext
43
+ unf_ext (0.0.8)
26
44
 
27
45
  PLATFORMS
28
46
  arm64-darwin-21
data/bin/wassup CHANGED
@@ -2,10 +2,15 @@
2
2
 
3
3
  require 'wassup'
4
4
 
5
+ debug = ARGV.delete("--debug")
5
6
  path = ARGV[0] || 'Supfile'
6
7
 
7
8
  unless File.exists?(path)
8
9
  raise "Missing file: #{path}"
9
10
  end
10
11
 
11
- Wassup::App.start(path: path)
12
+ if debug
13
+ Wassup::App.debug(path: path)
14
+ else
15
+ Wassup::App.start(path: path)
16
+ end
@@ -1,8 +1,5 @@
1
- require 'json'
2
- require 'rest-client'
3
-
4
1
  add_pane do |pane|
5
- pane.height = 0.5
2
+ pane.height = 0.25
6
3
  pane.width = 0.5
7
4
  pane.top = 0
8
5
  pane.left = 0
@@ -11,18 +8,188 @@ add_pane do |pane|
11
8
  pane.title = "Open PRs - fastlane/fastlane"
12
9
 
13
10
  pane.interval = 60 * 5
14
- pane.content do
15
- resp = RestClient.get "https://api.github.com/repos/fastlane/fastlane/pulls"
16
- json = JSON.parse(resp)
17
- json.map do |pr|
18
- display = "##{pr["number"]} #{pr["title"]}"
19
-
20
- # First element is displayed
21
- # Second element is passed to pane.selection
22
- [display, pr["html_url"]]
23
- end
24
- end
25
- pane.selection do |url|
11
+ pane.show_refresh = true
12
+
13
+ pane.content do |content|
14
+ prs = Helpers::GitHub.pull_requests(org: 'fastlane', repo: 'fastlane')
15
+ prs.each do |pr|
16
+ display = Helpers::GitHub::Formatter.pr(pr)
17
+ content.add_row(display, pr)
18
+ end
19
+ end
20
+ pane.selection('enter', 'Open PR in browser') do |pr|
21
+ `open #{pr['html_url']}`
22
+ end
23
+ end
24
+
25
+ add_pane do |pane|
26
+ pane.height = 0.25
27
+ pane.width = 0.5
28
+ pane.top = 0.25
29
+ pane.left = 0
30
+
31
+ pane.highlight = true
32
+ pane.title = "Open PRs - fastlane-community"
33
+
34
+ pane.interval = 60 * 5
35
+ pane.show_refresh = true
36
+
37
+ pane.content do |content|
38
+ # Uses GitHub's /search/issues API
39
+ # Docs - https://docs.github.com/en/search-github/searching-on-github/searching-issues-and-pull-requests
40
+ issues = Helpers::GitHub.issues(org: 'fastlane-community', q: 'is:pr is:open')
41
+ issues.each do |issue|
42
+ display = Helpers::GitHub::Formatter.pr(issue, show_repo: true)
43
+ content.add_row(display, issue)
44
+ end
45
+ end
46
+ pane.selection('enter', 'Open PR in browser') do |pr|
47
+ `open #{pr['html_url']}`
48
+ end
49
+ end
50
+
51
+ add_pane do |pane|
52
+ pane.height = 0.25
53
+ pane.width = 0.5
54
+ pane.top = 0.5
55
+ pane.left = 0
56
+
57
+ pane.highlight = true
58
+ pane.title = "High Interaction Issues - fastlane/fastlane"
59
+
60
+ pane.interval = 60 * 5
61
+ pane.show_refresh = true
62
+
63
+ pane.content do |content|
64
+ # Uses GitHub's /search/issues API
65
+ # Doc - https://docs.github.com/en/search-github/searching-on-github/searching-issues-and-pull-requests
66
+ issues = Helpers::GitHub.issues(org: 'fastlane', repo: 'fastlane', q: 'is:issue is:open interactions:>10')
67
+ issues.each do |issue|
68
+ display = Helpers::GitHub::Formatter.issue(issue, show_interactions: true)
69
+ content.add_row(display, issue)
70
+ end
71
+ end
72
+ pane.selection('enter', 'Open issue in browser') do |pr|
73
+ `open #{pr['html_url']}`
74
+ end
75
+ end
76
+
77
+ add_pane do |pane|
78
+ pane.height = 0.25
79
+ pane.width = 0.5
80
+ pane.top = 0
81
+ pane.left = 0.5
82
+
83
+ pane.highlight = true
84
+ pane.title = "Releases - fastlane/fastlane"
85
+
86
+ pane.interval = 60 * 5
87
+ pane.show_refresh = true
88
+
89
+ pane.content do |content|
90
+ releases = Helpers::GitHub.releases(org: 'fastlane', repo: 'fastlane')
91
+ releases.each do |release|
92
+ display = Helpers::GitHub::Formatter.release(release)
93
+ content.add_row(display, release)
94
+ end
95
+ end
96
+ pane.selection('enter', 'Open release in browser') do |pr|
97
+ `open #{pr['html_url']}`
98
+ end
99
+ end
100
+
101
+ add_pane do |pane|
102
+ pane.height = 0.5
103
+ pane.width = 0.5
104
+ pane.top = 0.25
105
+ pane.left = 0.5
106
+
107
+ pane.highlight = true
108
+ pane.title = "CircleCI - fastlane/fastlane"
109
+
110
+ pane.interval = 60 * 5
111
+ pane.show_refresh = true
112
+
113
+ pane.content do |content|
114
+ workflows = Helpers::CircleCI.workflows(vcs: 'github', org: 'fastlane', repo: 'fastlane', limit_days: 14)
115
+ workflows.each do |workflow|
116
+ display = Helpers::CircleCI::Formatter.workflow(workflow)
117
+ content.add_row(display, workflow)
118
+ end
119
+ end
120
+ pane.selection('enter', 'Open workflow in browser') do |workflow|
121
+ slug = workflow["project_slug"]
122
+ pipeline_number = workflow["pipeline_number"]
123
+ workflow_id = workflow["id"]
124
+
125
+ url = "https://app.circleci.com/pipelines/#{slug}/#{pipeline_number}/workflows/#{workflow_id}"
126
+ `open #{url}`
127
+ end
128
+ end
129
+
130
+ add_pane do |pane|
131
+ pane.height = 0.25
132
+ pane.width = 0.5
133
+ pane.top = 0.75
134
+ pane.left = 0
135
+
136
+ pane.highlight = true
137
+ pane.title = "Netlify - wassup"
138
+
139
+ pane.interval = 60 * 5
140
+ pane.show_refresh = true
141
+
142
+ pane.content do |content|
143
+ deploys = Helpers::Netlify.deploys(site_id: '91e8af7d-ea1c-4553-afb0-af7539bed063')
144
+ deploys.each do |deploy|
145
+ display = Helpers::Netlify::Formatter.deploy(deploy)
146
+ content.add_row(display, deploy)
147
+ end
148
+ end
149
+ pane.selection('enter', 'Open in Netlify') do |deploy|
150
+ url = "#{deploy['admin_url']}/deploys/#{deploy['id']}"
151
+ `open #{url}`
152
+ end
153
+ pane.selection('o', 'Open preview') do |deploy|
154
+ if deploy['state'] == 'error'
155
+ # show alert that isn't here yet
156
+ elsif deploy['review_id'].nil?
157
+ `open #{deploy['url']}`
158
+ else
159
+ `open #{deploy['deploy_ssl_url']}`
160
+ end
161
+ end
162
+ end
163
+
164
+ add_pane do |pane|
165
+ pane.height = 0.25
166
+ pane.width = 0.5
167
+ pane.top = 0.75
168
+ pane.left = 0.5
169
+
170
+ pane.highlight = true
171
+ pane.title = "Shortcut - Stories"
172
+
173
+ pane.interval = 60 * 5
174
+ pane.show_refresh = true
175
+
176
+ pane.content do |content|
177
+ # Owned stories
178
+ stories = Helpers::Shortcut.search_stories(query: "owner:joshholtz")
179
+ stories.each do |story|
180
+ display = Helpers::Shortcut::Formatter.story(story)
181
+ content.add_row(display, story, page: "Owned Stories")
182
+ end
183
+
184
+ # Ready for review stories
185
+ stories = Helpers::Shortcut.search_stories(query: "state:\"Ready For Review\" team:\"The\"")
186
+ stories.each do |story|
187
+ display = Helpers::Shortcut::Formatter.story(story)
188
+ content.add_row(display, story, page: "Ready For Review")
189
+ end
190
+ end
191
+ pane.selection('enter', 'Open in Shortcut') do |story|
192
+ url = story['app_url']
26
193
  `open #{url}`
27
194
  end
28
195
  end
@@ -53,6 +53,21 @@ add_pane do |pane|
53
53
  end
54
54
  end
55
55
 
56
+ add_pane do |pane|
57
+ pane.height = 0.25
58
+ pane.width = 0.3
59
+ pane.top = 0.75
60
+ pane.left = 0.0
61
+
62
+ pane.highlight = false
63
+ pane.title = "Always error"
64
+
65
+ pane.interval = 10
66
+ pane.content do |content|
67
+ raise "An error occured! Oh no!"
68
+ end
69
+ end
70
+
56
71
  add_pane do |pane|
57
72
  pane.height = 0.25
58
73
  pane.width = 0.35
@@ -20,6 +20,9 @@ add_pane do |pane|
20
20
  pane.highlight = false
21
21
 
22
22
  pane.title = "Stats: fastlane-community"
23
+ pane.description = [
24
+ "Highlevel stats from fastlane-community about PR count"
25
+ ]
23
26
 
24
27
  pane.interval = 2
25
28
  pane.show_refresh = false
@@ -62,6 +65,9 @@ add_pane do |pane|
62
65
  pane.highlight = false
63
66
 
64
67
  pane.title = "Stats: fastlane/fastlane"
68
+ pane.description = [
69
+ "Highlevel stats from fastlane/fastlane about PR count"
70
+ ]
65
71
 
66
72
  pane.interval = 2
67
73
  pane.show_refresh = false
@@ -113,52 +119,16 @@ add_pane do |pane|
113
119
 
114
120
  pane.interval = 60 * 5
115
121
  pane.show_refresh = true
122
+
116
123
  pane.content do |builder|
117
- resp = RestClient::Request.execute(
118
- method: :get,
119
- url: "https://circleci.com/api/v2/project/github/fastlane/fastlane/pipeline",
120
- headers: { "Circle-Token": ENV["WASSUP_CIRCLE_CI_API_TOKEN"] }
121
- )
122
- json = JSON.parse(resp)
123
- json["items"].select do |item|
124
- date = Time.parse(item["updated_at"])
125
- days = (Time.now - date).to_i / (24 * 60 * 60)
126
- days < 14
127
- end.map do |item|
128
- id = item["id"]
129
- number = item["number"]
130
- message = (item["vcs"]["commit"] || {})["subject"]
131
- login = item["trigger"]["actor"]["login"]
132
-
133
- resp = RestClient::Request.execute(
134
- method: :get,
135
- url: "https://circleci.com/api/v2/pipeline/#{id}/workflow",
136
- headers: { "Circle-Token": ENV["WASSUP_CIRCLE_CI_API_TOKEN"] }
137
- )
138
- json = JSON.parse(resp)
139
- workflow = json["items"].first
140
-
141
- next if workflow.nil?
142
-
143
- status = workflow["status"]
144
-
145
- if status == "failed"
146
- status = "[fg=red]#{status}[fg=white]"
147
- elsif status == "success"
148
- status = "[fg=green]#{status}[fg=white]"
149
- else
150
- status = "[fg=yellow]#{status}[fg=white]"
151
- end
152
-
153
- display = "#{number} (#{status}) by #{login} - #{message}"
154
- object = [item, workflow]
155
-
156
- builder.add_row(display, object)
124
+ workflows = Helpers::CircleCI.workflows(vcs: 'github', org: 'fastlane', repo: 'fastlane', limit_days: 14)
125
+ workflows.each do |workflow|
126
+ display = Helpers::CircleCI::Formatter.workflow(workflow)
127
+ builder.add_row(display, workflow)
157
128
  end
158
129
  end
159
- pane.selection('enter', 'Opens up CirceCI workflow') do |data|
160
- workflow = data[1]
161
130
 
131
+ pane.selection('enter', 'Opens up CirceCI workflow') do |workflow|
162
132
  slug = workflow["project_slug"]
163
133
  pipeline_number = workflow["pipeline_number"]
164
134
  workflow_id = workflow["id"]
@@ -166,8 +136,9 @@ add_pane do |pane|
166
136
  url = "https://app.circleci.com/pipelines/#{slug}/#{pipeline_number}/workflows/#{workflow_id}"
167
137
  `open #{url}`
168
138
  end
169
- pane.selection('o', 'Opens up version control review URL') do |data|
170
- pipeline = data[0]
139
+
140
+ pane.selection('o', 'Opens up version control review URL') do |workflow|
141
+ pipeline = workflow["pipeline"]
171
142
  url = pipeline["vcs"]["review_url"]
172
143
  `open #{url}`
173
144
  end
@@ -185,49 +156,26 @@ add_pane do |pane|
185
156
  pane.highlight = true
186
157
 
187
158
  pane.title = "Open PRs - fastlane-community"
159
+ pane.description = [
160
+ "Open PRs from all the fastlane-community repos"
161
+ ]
188
162
 
189
163
  pane.interval = 60 * 5
190
164
  pane.show_refresh = true
191
165
  pane.content do |builder|
192
166
  fastlane_community_prs = []
193
167
 
194
- resp = RestClient::Request.execute(
195
- method: :get,
196
- url: "https://api.github.com/orgs/fastlane-community/repos",
197
- user: ENV["WASSUP_GITHUB_USERNAME"],
198
- password: ENV["WASSUP_GITHUB_ACCESS_TOKEN"]
199
- )
200
- json = JSON.parse(resp)
201
- json.map do |repo|
202
- name = repo["name"]
203
- full_name = repo["full_name"]
204
-
205
- resp = RestClient::Request.execute(
206
- method: :get,
207
- url: "https://api.github.com/repos/#{full_name}/pulls",
208
- user: ENV["WASSUP_GITHUB_USERNAME"],
209
- password: ENV["WASSUP_GITHUB_ACCESS_TOKEN"]
210
- )
211
- json = JSON.parse(resp)
212
- prs = json.map do |pr|
213
- fastlane_community_prs << pr
214
-
215
- number = pr["number"]
216
- title = pr["title"]
217
- created_at = pr["created_at"]
218
-
219
- number_formatted = '%5.5s' % "##{number}"
220
-
221
- date = Time.parse(created_at)
222
- days = (Time.now - date).to_i / (24 * 60 * 60)
223
- days_formatted = '%3.3s' % days.to_s
224
-
225
- display = "[fg=yellow]#{number_formatted}[fg=cyan] #{days_formatted}d ago[fg=white] #{title}"
226
- builder.add_row(display, pr, page: name)
227
- end
168
+ prs = Wassup::Helpers::GitHub.pull_requests(org: 'fastlane-community')
169
+ prs.each do |pr|
170
+ fastlane_community_prs << pr
171
+
172
+ repo_name = pr["base"]["repo"]["name"]
173
+
174
+ display = Helpers::GitHub::Formatter.pr(pr)
175
+ builder.add_row(display, pr, page: repo_name)
228
176
  end
229
177
  end
230
- pane.selection do |data|
178
+ pane.selection('enter', 'Open PR in web browser') do |data|
231
179
  url = data["html_url"]
232
180
  `open #{url}`
233
181
  end
@@ -246,35 +194,24 @@ add_pane do |pane|
246
194
  pane.highlight = true
247
195
 
248
196
  pane.title = "Open PRs - fastlane/fastlane"
197
+ pane.description = [
198
+ "Open PRs from all the fastlane/fastlane repo"
199
+ ]
249
200
 
250
201
  pane.interval = 60 * 5
251
202
  pane.show_refresh = true
252
203
  pane.content do |builder|
253
204
  fastlane_prs = []
254
205
 
255
- resp = RestClient::Request.execute(
256
- method: :get,
257
- url: "https://api.github.com/repos/fastlane/fastlane/pulls?per_page=100",
258
- user: ENV["WASSUP_GITHUB_USERNAME"],
259
- password: ENV["WASSUP_GITHUB_ACCESS_TOKEN"]
260
- )
261
- json = JSON.parse(resp)
262
- json.map do |pr|
206
+ prs = Wassup::Helpers::GitHub.pull_requests(org: 'fastlane', repo: 'fastlane')
207
+ prs.each do |pr|
263
208
  fastlane_prs << pr
264
209
 
265
- number = pr["number"]
266
- title = pr["title"]
267
- created_at = pr["created_at"]
268
-
269
- date = Time.parse(created_at)
270
- days = (Time.now - date).to_i / (24 * 60 * 60)
271
- days_formatted = '%3.3s' % days.to_s
272
-
273
- display = "[fg=yellow]##{number}[fg=cyan] #{days_formatted}d ago[fg=white] #{title}"
210
+ display = Helpers::GitHub::Formatter.pr(pr)
274
211
  builder.add_row(display, pr)
275
212
  end
276
213
  end
277
- pane.selection do |data|
214
+ pane.selection('enter', 'Open PR in browser') do |data|
278
215
  url = data["html_url"]
279
216
  `open #{url}`
280
217
  end
@@ -0,0 +1,17 @@
1
+ add_pane do |pane|
2
+ pane.height = 0.25
3
+ pane.width = 0.25
4
+ pane.top = 0
5
+ pane.left = 0
6
+
7
+ pane.highlight = false
8
+ pane.title = "The Title"
9
+
10
+ pane.interval = 1
11
+ pane.content do |content|
12
+ date = `date`
13
+ content.add_row(date)
14
+ content.add_row(date, page: "page 2")
15
+ end
16
+ end
17
+
data/lib/wassup/app.rb CHANGED
@@ -20,6 +20,44 @@ module Wassup
20
20
  app = App.new(path: path)
21
21
  end
22
22
 
23
+ def self.debug(path:)
24
+ app = App.new(path: path, debug: true)
25
+
26
+ app.panes.each do |k, pane|
27
+ puts "#{k} - #{pane.title}"
28
+ end
29
+
30
+ puts ""
31
+ puts "Choose a pane to run:"
32
+
33
+ selection = $stdin.gets.chomp.to_s
34
+
35
+ pane = app.panes[selection]
36
+ if pane.nil?
37
+ puts "That was not a valid option"
38
+ else
39
+ puts "Going to run: \"#{pane.title}\""
40
+
41
+ builder = Wassup::PaneBuilder::ContentBuilder.new(pane.contents)
42
+ pane.content_block.call(builder)
43
+
44
+ builder.contents.each_with_index do |content|
45
+ puts "#########################"
46
+ puts "# #{content.title || (idx == 0 ? "Default" : "<No Title>")}"
47
+ puts "#########################"
48
+
49
+ content.data.each do |data|
50
+ puts data.display
51
+ .split(/\[.*?\]/).join('') # Removes colors but make this an option probably
52
+ end
53
+
54
+ puts ""
55
+ puts ""
56
+ puts ""
57
+ end
58
+ end
59
+ end
60
+
23
61
  def add_pane
24
62
  pane_builder = Wassup::PaneBuilder.new
25
63
  yield(pane_builder)
@@ -38,22 +76,46 @@ module Wassup
38
76
  show_refresh: pane_builder.show_refresh,
39
77
  content_block: pane_builder.content_block,
40
78
  selection_blocks: pane_builder.selection_blocks,
41
- selection_blocks_description: pane_builder.selection_blocks_description
79
+ selection_blocks_description: pane_builder.selection_blocks_description,
80
+ debug: debug
42
81
  )
43
82
  pane.focus_handler = @focus_handler
44
83
  @panes[number.to_s] = pane
45
84
  end
46
85
 
47
- def initialize(path:)
86
+ attr_accessor :panes
87
+ attr_accessor :debug
88
+
89
+ def initialize(path:, debug: false)
48
90
  @hidden_pane = nil
49
91
  @help_pane = nil
50
92
  @focused_pane = nil
51
93
  @panes = {}
94
+ @debug = debug
95
+
96
+ if debug
97
+ self.start_debug(path)
98
+ else
99
+ self.start_curses(path)
100
+ end
101
+ end
52
102
 
103
+ def start_debug(path)
104
+ begin
105
+ eval(File.new(path).read)
106
+ rescue => err
107
+ puts err
108
+ puts err.backtrace
109
+ end
110
+ end
111
+
112
+ def start_curses(path)
53
113
  @redraw_panes = false
54
114
 
55
115
  # TODO: this could maybe get replaced with selection_blocks now
56
116
  @focus_handler = Proc.new do |input|
117
+ is_help_open = !@help_pane.nil?
118
+
57
119
  if input == "q"
58
120
  exit
59
121
  elsif input == "?"
@@ -61,6 +123,8 @@ module Wassup
61
123
  next true
62
124
  end
63
125
 
126
+ next true if is_help_open
127
+
64
128
  if (pane = @panes[input.to_s])
65
129
  @focused_pane.focused = false
66
130
 
@@ -113,6 +177,21 @@ module Wassup
113
177
  end
114
178
  end
115
179
 
180
+ def row_help
181
+ {
182
+ "j" => "moves row highlight down",
183
+ "k" => "moves row highlight up",
184
+ "enter" => "perform selection on highlighted row"
185
+ }
186
+ end
187
+
188
+ def page_help
189
+ {
190
+ "h" => "previous page in pane",
191
+ "l" => "next page in pane"
192
+ }
193
+ end
194
+
116
195
  def toggle_help
117
196
  if @help_pane.nil?
118
197
  if @focused_pane == @hidden_pane
@@ -121,12 +200,13 @@ module Wassup
121
200
  "Welcome to Wassup!",
122
201
  "",
123
202
  "Press any number key to focus a pane",
124
- "j - moves row highlight down",
125
- "k - moves row highlight up",
126
- "<Enter> - perform selection on highlighted row",
203
+ "",
204
+ row_help.map { |k,v| "#{k} - #{v}"},
205
+ "",
206
+ page_help.map { |k,v| "#{k} - #{v}"},
127
207
  "",
128
208
  "? - opens help for focused pane"
129
- ]
209
+ ].flatten
130
210
 
131
211
  items.each do |item|
132
212
  content.add_row(item)
@@ -134,12 +214,27 @@ module Wassup
134
214
  end
135
215
  else
136
216
  content_block = Proc.new do |content|
217
+ hash = {}
218
+
219
+ hash = hash.merge(row_help)
220
+ hash = hash.merge(@focused_pane.selection_blocks_description)
221
+
222
+ row_help.map { |k,v| "#{k} - #{v}"}
223
+
224
+ copy_error = @focused_pane.caught_error.nil? ? [] : [
225
+ "c - copy stacktrace to clipboard",
226
+ ""
227
+ ]
228
+
137
229
  items = [
138
230
  @focused_pane.description,
139
231
  "",
140
- @focused_pane.selection_blocks_description.map do |k,v|
232
+ hash.map do |k,v|
141
233
  "#{k} - #{v}"
142
- end
234
+ end,
235
+ "",
236
+ copy_error,
237
+ page_help.map { |k,v| "#{k} - #{v}"},
143
238
  ].flatten.compact
144
239
 
145
240
  items.each do |item|
data/lib/wassup/color.rb CHANGED
@@ -17,6 +17,8 @@ module Wassup
17
17
  NORMAL = 20
18
18
  HIGHLIGHT = 21
19
19
 
20
+ GRAY = 22
21
+
20
22
  BORDER = 20
21
23
  BORDER_FOCUS = 7
22
24
 
@@ -44,6 +46,7 @@ module Wassup
44
46
  Curses.init_pair(Pair::RED, Curses::COLOR_RED, 0)
45
47
  Curses.init_pair(Pair::WHITE, Pair::WHITE, 0)
46
48
  Curses.init_pair(Pair::YELLOW, Curses::COLOR_YELLOW, 0)
49
+ Curses.init_pair(Pair::GRAY, Curses::COLOR_WHITE, 0)
47
50
  end
48
51
 
49
52
  def initialize(string_name)
@@ -64,6 +67,8 @@ module Wassup
64
67
  Pair::WHITE
65
68
  when "yellow"
66
69
  Pair::YELLOW
70
+ when "gray"
71
+ Pair::GRAY
67
72
  else
68
73
  if string_name.to_i.to_s == string_name
69
74
  string_name.to_i
@@ -0,0 +1,75 @@
1
+ module Wassup
2
+ module Helpers
3
+ module CircleCI
4
+ def self.workflows(vcs:, org:, repo:, limit_days: nil)
5
+ require 'json'
6
+ require 'rest-client'
7
+
8
+ resp = RestClient::Request.execute(
9
+ method: :get,
10
+ url: "https://circleci.com/api/v2/project/#{vcs}/#{org}/#{repo}/pipeline",
11
+ headers: { "Circle-Token": ENV["WASSUP_CIRCLE_CI_API_TOKEN"] }
12
+ )
13
+ json = JSON.parse(resp)
14
+
15
+ return json["items"].select do |item|
16
+ if !limit_days.nil?
17
+ date = Time.parse(item["updated_at"])
18
+ days = (Time.now - date).to_i / (24 * 60 * 60)
19
+ days < limit_days
20
+ else
21
+ true
22
+ end
23
+ end.map do |pipeline|
24
+ id = pipeline["id"]
25
+
26
+ resp = RestClient::Request.execute(
27
+ method: :get,
28
+ url: "https://circleci.com/api/v2/pipeline/#{id}/workflow",
29
+ headers: { "Circle-Token": ENV["WASSUP_CIRCLE_CI_API_TOKEN"] }
30
+ )
31
+ json = JSON.parse(resp)
32
+ workflow = json["items"].first
33
+
34
+ if workflow
35
+ workflow["pipeline"] = pipeline
36
+ end
37
+
38
+ workflow
39
+ end.compact
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ module Wassup
46
+ module Helpers
47
+ module CircleCI
48
+ module Formatter
49
+ def self.workflow(workflow)
50
+ pipeline = workflow["pipeline"]
51
+ number = pipeline["number"]
52
+ message = (pipeline["vcs"]["commit"] || {})["subject"]
53
+ login = pipeline["trigger"]["actor"]["login"]
54
+
55
+ status = workflow["status"]
56
+ status_formatted = '%-8.8s' % status
57
+
58
+ number_formatted = '%-7.7s' % "##{number}"
59
+
60
+ if status == "failed"
61
+ status_formatted = "[fg=red]#{status_formatted}[fg=white]"
62
+ elsif status == "success"
63
+ status_formatted = "[fg=green]#{status_formatted}[fg=white]"
64
+ else
65
+ status_formatted = "[fg=yellow]#{status_formatted}[fg=white]"
66
+ end
67
+
68
+ display = "[fg=yellow]#{number_formatted} [fg=while]#{status_formatted} [fg=white]#{login} [fg=gray]#{message}"
69
+
70
+ return display
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,137 @@
1
+ module Wassup
2
+ module Helpers
3
+ module GitHub
4
+ require 'json'
5
+ require 'rest-client'
6
+
7
+ # https://docs.github.com/en/search-github/searching-on-github/searching-issues-and-pull-requests
8
+ def self.issues(org:, repo:nil, q: nil)
9
+ q_parts = []
10
+
11
+ if repo.nil?
12
+ q_parts << "org:#{org}"
13
+ else
14
+ q_parts << "repo:#{org}/#{repo}"
15
+ end
16
+
17
+ if q
18
+ q_parts << q
19
+ end
20
+
21
+ q = q_parts.join(' ')
22
+
23
+ items = []
24
+
25
+ resp = RestClient::Request.execute(
26
+ method: :get,
27
+ url: "https://api.github.com/search/issues?q=#{q}",
28
+ user: ENV["WASSUP_GITHUB_USERNAME"],
29
+ password: ENV["WASSUP_GITHUB_ACCESS_TOKEN"],
30
+ headers: { "Accept": "application/vnd.github.v3+json", "Content-Type": "application/json" },
31
+ )
32
+ partial_items = JSON.parse(resp)["items"]
33
+ items += partial_items
34
+
35
+ return items
36
+ end
37
+
38
+ def self.repos(org:)
39
+ resp = RestClient::Request.execute(
40
+ method: :get,
41
+ url: "https://api.github.com/orgs/#{org}/repos",
42
+ user: ENV["WASSUP_GITHUB_USERNAME"],
43
+ password: ENV["WASSUP_GITHUB_ACCESS_TOKEN"]
44
+ )
45
+ return JSON.parse(resp)
46
+ end
47
+
48
+ Repo = Struct.new(:org, :repo)
49
+ def self.pull_requests(org:, repo: nil)
50
+ repos = []
51
+ if repo.nil?
52
+ repos += self.repos(org: org).map do |repo|
53
+ Repo.new(org, repo["name"])
54
+ end
55
+ else
56
+ repos << Repo.new(org, repo)
57
+ end
58
+
59
+ return repos.map do |repo|
60
+ resp = RestClient::Request.execute(
61
+ method: :get,
62
+ url: "https://api.github.com/repos/#{repo.org}/#{repo.repo}/pulls?per_page=100",
63
+ user: ENV["WASSUP_GITHUB_USERNAME"],
64
+ password: ENV["WASSUP_GITHUB_ACCESS_TOKEN"]
65
+ )
66
+
67
+ JSON.parse(resp)
68
+ end.flatten(1)
69
+ end
70
+
71
+ def self.releases(org:, repo:)
72
+ resp = RestClient::Request.execute(
73
+ method: :get,
74
+ url: "https://api.github.com/repos/#{org}/#{repo}/releases",
75
+ user: ENV["WASSUP_GITHUB_USERNAME"],
76
+ password: ENV["WASSUP_GITHUB_ACCESS_TOKEN"]
77
+ )
78
+
79
+ return JSON.parse(resp)
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ module Wassup
86
+ module Helpers
87
+ module GitHub
88
+ module Formatter
89
+ def self.issue(issue, show_repo: false, show_interactions: false)
90
+ self.pr(issue, show_repo: show_repo, show_interactions: show_interactions)
91
+ end
92
+
93
+ def self.pr(pr, show_repo: false, show_interactions: false)
94
+ number = pr["number"]
95
+ title = pr["title"]
96
+ created_at = pr["created_at"]
97
+
98
+ repo_name = ""
99
+ if show_repo
100
+ repo_url_parts = pr["repository_url"].split("/")
101
+ repo_name = "[fg=gray]#{repo_url_parts.last} "
102
+ end
103
+
104
+ interactions = ""
105
+ if show_interactions
106
+ interaction_count = pr["comments"] + pr["reactions"]["total_count"]
107
+ interactions = "[fg=red]#{interaction_count} "
108
+ end
109
+
110
+ number_formatted = '%-7.7s' % "##{number}"
111
+
112
+ date = Time.parse(created_at)
113
+ days = (Time.now - date).to_i / (24 * 60 * 60)
114
+ days_formatted = '%3.3s' % days.to_s
115
+
116
+ display = "[fg=yellow]#{number_formatted}[fg=cyan] #{days_formatted}d ago #{interactions}#{repo_name}[fg=white]#{title}"
117
+
118
+ return display
119
+ end
120
+
121
+ def self.release(release)
122
+ tag_name = release["tag_name"]
123
+ name = release["name"]
124
+ published_at = release["published_at"]
125
+
126
+ date = Time.parse(published_at)
127
+ days = (Time.now - date).to_i / (24 * 60 * 60)
128
+ days_formatted = '%3.3s' % days.to_s
129
+
130
+ display = "[fg=yellow]#{tag_name} [fg=cyan]#{days_formatted} ago [fg=gray]#{name}"
131
+
132
+ return display
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,70 @@
1
+ module Wassup
2
+ module Helpers
3
+ module Netlify
4
+ require 'json'
5
+ require 'rest-client'
6
+
7
+ def self.deploys(site_id:)
8
+ resp = RestClient::Request.execute(
9
+ method: :get,
10
+ url: "https://api.netlify.com/api/v1/sites/#{site_id}/deploys",
11
+ headers: { "Authorization": "Bearer #{ENV['WASSUP_NETLIFY_TOKEN']}", "User-Agent": "Wassup" }
12
+ )
13
+ return JSON.parse(resp)
14
+ end
15
+
16
+ end
17
+ end
18
+ end
19
+
20
+ module Wassup
21
+ module Helpers
22
+ module Netlify
23
+ module Formatter
24
+ def self.deploy(deploy)
25
+ review_id = deploy["review_id"]
26
+ context = deploy["context"]
27
+ state = deploy["state"]
28
+ error_message = deploy["error_message"]
29
+ branch = deploy["branch"]
30
+ commit_ref = deploy["commit_ref"] || "HEAD"
31
+ url = deploy["deploy_url"]
32
+
33
+ if error_message.to_s.downcase.include?("canceled")
34
+ state = "cancelled"
35
+ end
36
+
37
+ color = "green"
38
+ if state == "building"
39
+ color = "yellow"
40
+ elsif state == "enqueued"
41
+ color = "magenta"
42
+ elsif state == "cancelled"
43
+ color = "gray"
44
+ elsif state == "error"
45
+ color = "red"
46
+ end
47
+
48
+ display_context = context.split('-').map(&:capitalize).join(' ')
49
+ display_context = "[fg=#{color}]#{display_context}"
50
+
51
+ if !review_id.nil?
52
+ display_context += " - ##{review_id}"
53
+ end
54
+
55
+ display_context += "[fg=gray]"
56
+ display_context += " (#{state})"
57
+ display_context += "[fg=white]"
58
+
59
+ if !branch.nil? && !commit_ref.nil?
60
+ display_context += "[fg=cyan]: #{branch}@#{commit_ref[0...7]}"
61
+ end
62
+
63
+ display = "#{display_context}"
64
+
65
+ return display
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,114 @@
1
+ module Wassup
2
+ module Helpers
3
+ module Shortcut
4
+ require 'json'
5
+ require 'rest-client'
6
+
7
+ def self.members
8
+ endpoint = "https://api.app.shortcut.com"
9
+ resp = RestClient::Request.execute(
10
+ method: :get,
11
+ url: "#{endpoint}/api/v3/members",
12
+ headers: { "Shortcut-Token": ENV['WASSUP_SHORTCUT_TOKEN'], "Content-Type": "application/json" }
13
+ )
14
+ return JSON.parse(resp)
15
+ end
16
+
17
+ def self.workflows
18
+ endpoint = "https://api.app.shortcut.com"
19
+ resp = RestClient::Request.execute(
20
+ method: :get,
21
+ url: "#{endpoint}/api/v3/workflows",
22
+ headers: { "Shortcut-Token": ENV['WASSUP_SHORTCUT_TOKEN'], "Content-Type": "application/json" }
23
+ )
24
+ return JSON.parse(resp)
25
+ end
26
+
27
+ def self.search_stories(query:, page_size: 25)
28
+ endpoint = "https://api.app.shortcut.com"
29
+
30
+ members = self.members
31
+ workflows = self.workflows
32
+
33
+ stories = []
34
+
35
+ resp = RestClient::Request.execute(
36
+ method: :get,
37
+ url: "#{endpoint}/api/v3/search/stories",
38
+ payload: { page_size: page_size, query: query },
39
+ headers: { "Shortcut-Token": ENV['WASSUP_SHORTCUT_TOKEN'], "Content-Type": "application/json" }
40
+ )
41
+ json = JSON.parse(resp)
42
+ stories += json["data"]
43
+
44
+ next_url = json["next"]
45
+ while !next_url.nil?
46
+ resp = RestClient::Request.execute(
47
+ method: :get,
48
+ url: "#{endpoint}#{next_url}",
49
+ headers: { "Shortcut-Token": ENV['WASSUP_SHORTCUT_TOKEN'], "Content-Type": "application/json" }
50
+ )
51
+ json = JSON.parse(resp)
52
+ stories += json["data"]
53
+ next_url = json["next"]
54
+ end
55
+
56
+ stories = stories.map do |story|
57
+ story["followers"] = story["follower_ids"].map do |owner_id|
58
+ members.find do |member|
59
+ member["id"] == owner_id
60
+ end
61
+ end
62
+
63
+ story["owners"] = story["owner_ids"].map do |owner_id|
64
+ members.find do |member|
65
+ member["id"] == owner_id
66
+ end
67
+ end
68
+
69
+ if (workflow_id = story["workflow_id"]) && (workflow_state_id = story["workflow_state_id"])
70
+ workflow = workflows.find do |workflow|
71
+ workflow["id"] == workflow_id
72
+ end
73
+
74
+ story["workflow"] = workflow
75
+ if workflow
76
+ story["workflow_state"] = workflow["states"].find do |workflow_state|
77
+ workflow_state["id"] == workflow_state_id
78
+ end
79
+ end
80
+ end
81
+
82
+ story
83
+ end
84
+
85
+ return stories
86
+ end
87
+
88
+ end
89
+ end
90
+ end
91
+
92
+ module Wassup
93
+ module Helpers
94
+ module Shortcut
95
+ module Formatter
96
+ def self.story(story)
97
+ id = story["id"]
98
+ name = story["name"]
99
+ state = story["workflow_state"]["name"]
100
+
101
+ mention_name = (story["owners"] || {}).map do |member|
102
+ member["profile"]["mention_name"]
103
+ end.join(", ")
104
+
105
+ id_formatted = '%-7.7s' % "##{id}"
106
+
107
+ display = "[fg=yellow]#{id_formatted} [fg=cyan]#{state} [fg=white]#{mention_name} [fg=gray]#{name}"
108
+
109
+ return display
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
data/lib/wassup/pane.rb CHANGED
@@ -31,6 +31,8 @@ module Wassup
31
31
  attr_accessor :selection_blocks
32
32
  attr_accessor :selection_blocks_description
33
33
 
34
+ attr_accessor :caught_error
35
+
34
36
  attr_accessor :content_thread
35
37
  attr_accessor :show_refresh
36
38
 
@@ -62,14 +64,17 @@ module Wassup
62
64
  end
63
65
  end
64
66
 
65
- def initialize(height, width, top, left, title: nil, description: nil, highlight: true, focus_number: nil, interval:, show_refresh:, content_block:, selection_blocks:, selection_blocks_description:)
66
- self.win_height = Curses.lines * height
67
- self.win_width = Curses.cols * width
68
- self.win_top = Curses.lines * top
69
- self.win_left = Curses.cols * left
67
+ def initialize(height, width, top, left, title: nil, description: nil, highlight: true, focus_number: nil, interval:, show_refresh:, content_block:, selection_blocks:, selection_blocks_description:, debug: false)
70
68
 
71
- self.win = Curses::Window.new(self.win_height, self.win_width, self.win_top, self.win_left)
72
- self.setup_subwin()
69
+ if !debug
70
+ self.win_height = Curses.lines * height
71
+ self.win_width = Curses.cols * width
72
+ self.win_top = Curses.lines * top
73
+ self.win_left = Curses.cols * left
74
+
75
+ self.win = Curses::Window.new(self.win_height, self.win_width, self.win_top, self.win_left)
76
+ self.setup_subwin()
77
+ end
73
78
 
74
79
  self.focused = false
75
80
  self.focus_number = focus_number
@@ -84,8 +89,10 @@ module Wassup
84
89
 
85
90
  self.selected_view_index = 0
86
91
 
87
- self.win.refresh
88
- self.subwin.refresh
92
+ if !debug
93
+ self.win.refresh
94
+ self.subwin.refresh
95
+ end
89
96
 
90
97
  self.title = title
91
98
  self.description = description
@@ -176,7 +183,7 @@ module Wassup
176
183
  def redraw
177
184
  self.update_box
178
185
  self.update_title
179
- self.refresh_content(nil)
186
+ self.load_current_view()
180
187
  end
181
188
 
182
189
  def refresh(force: false)
@@ -198,15 +205,27 @@ module Wassup
198
205
  elsif thread.status == false
199
206
  rtn = thread.value
200
207
  if rtn.is_a?(Ope)
201
- content = Wassup::Pane::Content.new
202
-
208
+ self.caught_error = rtn.error
209
+ content = Wassup::Pane::Content.new("Overview")
203
210
  content.add_row("[fg=red]Error during refersh[fg=white]")
204
211
  content.add_row("[fg=red]at #{Time.now}[fg=while]")
205
212
  content.add_row("")
206
213
  content.add_row("[fg=yellow]Will try again next interval[fg=white]")
207
214
 
208
- self.refresh_content([content])
215
+ content_directions = Wassup::Pane::Content.new("Directions")
216
+ content_directions.add_row("1. Press 'c' to copy the stacktrace")
217
+ content_directions.add_row("2. Debug pane content block with:")
218
+ content_directions.add_row(" $: wassup --debug")
219
+ content_directions.add_row("3. Stacktrace viewable in next page")
220
+
221
+ content_stacktrace = Wassup::Pane::Content.new("Stacktrace")
222
+ rtn.error.backtrace.each do |line|
223
+ content_stacktrace.add_row(line)
224
+ end
225
+
226
+ self.refresh_content([content, content_directions, content_stacktrace])
209
227
  elsif rtn.is_a?(Wassup::PaneBuilder::ContentBuilder)
228
+ self.caught_error = nil
210
229
  self.refresh_content(rtn.contents)
211
230
  end
212
231
 
@@ -235,7 +254,7 @@ module Wassup
235
254
  end
236
255
 
237
256
  def refresh_content(contents)
238
- self.contents = contents unless contents.nil?
257
+ self.contents = contents
239
258
 
240
259
  self.load_current_view()
241
260
  self.last_refreshed = Time.now
@@ -257,6 +276,13 @@ module Wassup
257
276
  self.update_title()
258
277
  end
259
278
 
279
+ def highlight
280
+ if self.caught_error
281
+ return true
282
+ end
283
+ return @highlight
284
+ end
285
+
260
286
  attr_accessor :refresh_char_count
261
287
  def refresh_char
262
288
  return "" unless self.show_refresh
@@ -535,6 +561,15 @@ module Wassup
535
561
  self.scroll_right
536
562
  elsif input == "r"
537
563
  self.refresh(force: true)
564
+ elsif input == "c"
565
+ if !self.caught_error.nil?
566
+ text = self.caught_error.backtrace.join("\n")
567
+ if RUBY_PLATFORM.downcase =~ /win32/
568
+ IO.popen('clip', 'w') { |pipe| pipe.puts text }
569
+ else
570
+ IO.popen('pbcopy', 'w') { |pipe| pipe.puts text }
571
+ end
572
+ end
538
573
  else
539
574
  selection_block = self.selection_blocks[input]
540
575
  if !selection_block.nil? && !self.highlighted_line.nil?
@@ -85,11 +85,11 @@ module Wassup
85
85
 
86
86
  description_input = input
87
87
  if input.to_s == "10"
88
- input = "<Enter>"
88
+ description_input = "enter"
89
89
  end
90
90
 
91
91
  self.selection_blocks[input] = block
92
- self.selection_blocks_description[input] = description
92
+ self.selection_blocks_description[description_input] = description
93
93
  end
94
94
  end
95
95
  end
@@ -1,3 +1,3 @@
1
1
  module Wassup
2
- VERSION = "0.2.1"
2
+ VERSION = "0.3.0"
3
3
  end
data/lib/wassup.rb CHANGED
@@ -4,6 +4,11 @@ require "wassup/color"
4
4
  require "wassup/pane"
5
5
  require "wassup/pane_builder"
6
6
 
7
+ require "wassup/helpers/circleci"
8
+ require "wassup/helpers/github"
9
+ require "wassup/helpers/netlify"
10
+ require "wassup/helpers/shortcut"
11
+
7
12
  module Wassup
8
13
  class Error < StandardError; end
9
14
  # Your code goes here...
data/wassup.gemspec CHANGED
@@ -16,6 +16,7 @@ Gem::Specification.new do |spec|
16
16
  spec.metadata["source_code_uri"] = "https://github.com/joshdholtz/wassup"
17
17
 
18
18
  spec.add_runtime_dependency 'curses'
19
+ spec.add_runtime_dependency 'rest-client'
19
20
 
20
21
  # Specify which files should be added to the gem when it is released.
21
22
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wassup
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josh Holtz
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-12-21 00:00:00.000000000 Z
11
+ date: 2021-12-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: curses
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rest-client
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  description: A scriptable terminal dashboard
28
42
  email:
29
43
  - me@joshholtz.com
@@ -83,10 +97,15 @@ files:
83
97
  - examples/josh-fastlane/README.md
84
98
  - examples/josh-fastlane/Supfile
85
99
  - examples/josh-fastlane/demo.png
100
+ - examples/simple/Supfile
86
101
  - examples/starter/Supfile
87
102
  - lib/wassup.rb
88
103
  - lib/wassup/app.rb
89
104
  - lib/wassup/color.rb
105
+ - lib/wassup/helpers/circleci.rb
106
+ - lib/wassup/helpers/github.rb
107
+ - lib/wassup/helpers/netlify.rb
108
+ - lib/wassup/helpers/shortcut.rb
90
109
  - lib/wassup/pane.rb
91
110
  - lib/wassup/pane_builder.rb
92
111
  - lib/wassup/version.rb