wassup 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 941e170c1128cdd099c74de0c068bddabe50ef1921dedcd4f7c278cb6032bd94
4
+ data.tar.gz: b76965eec5063a77ec8d6bca7dee5081a7c380af28b95732298039d1958b5ab6
5
+ SHA512:
6
+ metadata.gz: 8371c29c1c55e96837e07f2c904d27ac22ccec2f848ee2540f9caf0cf4c5d43a746fab12d23a742222dc56c49584eb5a2dead3599c6db86786ed5aa667446bee
7
+ data.tar.gz: 0ad9c8dade67a8a83236c01c1c252bffb96218175eae256268c366f5f7078efa4dbb0d2c1a82fbe0936cfc3a75a52d0aa362814ddb5d6b15b380b825b27e21d3
data/.gitignore ADDED
@@ -0,0 +1,13 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+
13
+ *.swp
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ ---
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 2.6.8
6
+ before_install: gem install bundler -v 2.1.4
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at me@joshholtz.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [https://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: https://contributor-covenant.org
74
+ [version]: https://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in wassup.gemspec
4
+ gemspec
5
+
6
+ gem "rake", "~> 12.0"
7
+ gem "rspec", "~> 3.0"
data/Gemfile.lock ADDED
@@ -0,0 +1,36 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ wassup (0.1.0)
5
+ curses
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ curses (1.4.2)
11
+ diff-lcs (1.4.4)
12
+ rake (12.3.3)
13
+ rspec (3.10.0)
14
+ rspec-core (~> 3.10.0)
15
+ rspec-expectations (~> 3.10.0)
16
+ rspec-mocks (~> 3.10.0)
17
+ rspec-core (3.10.1)
18
+ rspec-support (~> 3.10.0)
19
+ rspec-expectations (3.10.1)
20
+ diff-lcs (>= 1.2.0, < 2.0)
21
+ rspec-support (~> 3.10.0)
22
+ rspec-mocks (3.10.2)
23
+ diff-lcs (>= 1.2.0, < 2.0)
24
+ rspec-support (~> 3.10.0)
25
+ rspec-support (3.10.3)
26
+
27
+ PLATFORMS
28
+ arm64-darwin-21
29
+
30
+ DEPENDENCIES
31
+ rake (~> 12.0)
32
+ rspec (~> 3.0)
33
+ wassup!
34
+
35
+ BUNDLED WITH
36
+ 2.2.3
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 Josh Holtz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,44 @@
1
+ # Wassup
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/wassup`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'wassup'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle install
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install wassup
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/wassup. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/wassup/blob/master/CODE_OF_CONDUCT.md).
36
+
37
+
38
+ ## License
39
+
40
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
41
+
42
+ ## Code of Conduct
43
+
44
+ Everyone interacting in the Wassup project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/wassup/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "wassup"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/bin/wassup ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'wassup'
4
+ Wassup::App.start(path: ARGV[0])
@@ -0,0 +1,257 @@
1
+ require 'time'
2
+ require 'rest-client'
3
+ require 'json'
4
+ require 'colorize'
5
+
6
+ fastlane_community_prs = []
7
+ fastlane_prs = []
8
+
9
+ #
10
+ # Top Left 1
11
+ #
12
+ add_pane do |pane|
13
+ pane.height = 0.25
14
+ pane.width = 0.35
15
+ pane.top = 0
16
+ pane.left = 0
17
+
18
+ pane.highlight = false
19
+
20
+ pane.title = "Stats: fastlane-community"
21
+
22
+ pane.interval = 1
23
+ pane.content do
24
+ days_1 = 0
25
+ days_7 = 0
26
+ days_30 = 0
27
+ days_60 = 0
28
+ days_365 = 0
29
+
30
+ fastlane_community_prs.each do |pr|
31
+ created_at = pr["created_at"]
32
+ date = Time.parse(created_at)
33
+ days = (Time.now - date).to_i / (24 * 60 * 60)
34
+
35
+ days_1 +=1 if days <= 1
36
+ days_7 +=1 if days <= 7
37
+ days_30 +=1 if days <= 30
38
+ days_60 +=1 if days <= 60
39
+ days_365 +=1 if days <= 365
40
+ end
41
+
42
+ [
43
+ "Opened today: #{days_1}",
44
+ "Opened 7 days: #{days_7}",
45
+ "Opened 30 days: #{days_30}",
46
+ "Opened 60 days: #{days_60}",
47
+ "Opened 365 days: #{days_365}"
48
+ ]
49
+ end
50
+ end
51
+
52
+ #
53
+ # Top Left 2
54
+ #
55
+ add_pane do |pane|
56
+ pane.height = 0.25
57
+ pane.width = 0.35
58
+ pane.top = 0.25
59
+ pane.left = 0
60
+
61
+ pane.highlight = false
62
+
63
+ pane.title = "Stats: fastlane/fastlane"
64
+
65
+ pane.interval = 1
66
+ pane.content do
67
+ days_1 = 0
68
+ days_7 = 0
69
+ days_30 = 0
70
+ days_45 = 0
71
+ days_60 = 0
72
+ days_60_plus = 0
73
+
74
+ fastlane_prs.each do |pr|
75
+ created_at = pr["created_at"]
76
+ date = Time.parse(created_at)
77
+ days = (Time.now - date).to_i / (24 * 60 * 60)
78
+
79
+ days_1 +=1 if days <= 1
80
+ days_7 +=1 if days <= 7
81
+ days_30 +=1 if days <= 30
82
+ days_45 +=1 if days <= 45
83
+ days_60 +=1 if days <= 60
84
+ days_60_plus +=1 if days > 60
85
+ end
86
+
87
+ [
88
+ "Opened today: #{days_1}",
89
+ "Opened 7 days: #{days_7}",
90
+ "Opened 30 days: #{days_30}",
91
+ "Opened 45 days: #{days_45}",
92
+ "Opened 60 days: #{days_60}",
93
+ "More than 60 days: #{days_60_plus}",
94
+ ]
95
+ end
96
+ end
97
+
98
+ #
99
+ # Top Right
100
+ #
101
+ add_pane do |pane|
102
+ pane.height = 0.5
103
+ pane.width = 0.65
104
+ pane.top = 0
105
+ pane.left = 0.35
106
+
107
+ pane.highlight = true
108
+
109
+ pane.title = "Circle CI - fastlane/fastlane"
110
+
111
+ pane.interval = 60 * 5
112
+ pane.content do
113
+ resp = RestClient::Request.execute(
114
+ method: :get,
115
+ url: "https://circleci.com/api/v2/project/github/fastlane/fastlane/pipeline",
116
+ headers: { "Circle-Token": ENV["WASSUP_CIRCLE_CI_API_TOKEN"] }
117
+ )
118
+ json = JSON.parse(resp)
119
+ json["items"].select do |item|
120
+ date = Time.parse(item["updated_at"])
121
+ days = (Time.now - date).to_i / (24 * 60 * 60)
122
+ days < 14
123
+ end.map do |item|
124
+ id = item["id"]
125
+ number = item["number"]
126
+ message = item["vcs"]["commit"]["subject"]
127
+ login = item["trigger"]["actor"]["login"]
128
+
129
+ resp = RestClient::Request.execute(
130
+ method: :get,
131
+ url: "https://circleci.com/api/v2/pipeline/#{id}/workflow",
132
+ headers: { "Circle-Token": ENV["WASSUP_CIRCLE_CI_API_TOKEN"] }
133
+ )
134
+ json = JSON.parse(resp)
135
+ workflow = json["items"].first
136
+ status = workflow["status"]
137
+
138
+ ["#{number} (#{status}) by #{login} - #{message}", workflow]
139
+ end
140
+ end
141
+ pane.selection do |data|
142
+ slug = data["project_slug"]
143
+ pipeline_number = data["pipeline_number"]
144
+ workflow_id = data["id"]
145
+
146
+ url = "https://app.circleci.com/pipelines/#{slug}/#{pipeline_number}/workflows/#{workflow_id}"
147
+ `open #{url}`
148
+ end
149
+ end
150
+
151
+ #
152
+ # Bottom Right
153
+ #
154
+ add_pane do |pane|
155
+ pane.height = 0.5
156
+ pane.width = 0.5
157
+ pane.top = 0.5
158
+ pane.left = 0
159
+
160
+ pane.highlight = true
161
+
162
+ pane.title = "Open PRs - fastlane-community"
163
+
164
+ pane.interval = 60 * 5
165
+ pane.content do
166
+ fastlane_community_prs = []
167
+
168
+ resp = RestClient::Request.execute(
169
+ method: :get,
170
+ url: "https://api.github.com/orgs/fastlane-community/repos",
171
+ user: ENV["WASSUP_GITHUB_USERNAME"],
172
+ password: ENV["WASSUP_GITHUB_ACCESS_TOKEN"]
173
+ )
174
+ json = JSON.parse(resp)
175
+ json.map do |repo|
176
+ name = repo["name"]
177
+ full_name = repo["full_name"]
178
+
179
+ resp = RestClient::Request.execute(
180
+ method: :get,
181
+ url: "https://api.github.com/repos/#{full_name}/pulls",
182
+ user: ENV["WASSUP_GITHUB_USERNAME"],
183
+ password: ENV["WASSUP_GITHUB_ACCESS_TOKEN"]
184
+ )
185
+ json = JSON.parse(resp)
186
+ prs = json.map do |pr|
187
+ fastlane_community_prs << pr
188
+
189
+ number = pr["number"]
190
+ title = pr["title"]
191
+ created_at = pr["created_at"]
192
+
193
+ number_formatted = '%5.5s' % "##{number}"
194
+
195
+ date = Time.parse(created_at)
196
+ days = (Time.now - date).to_i / (24 * 60 * 60)
197
+ days_formatted = '%3.3s' % days.to_s
198
+
199
+ ["#{number_formatted} #{days_formatted}d ago #{title}",pr]
200
+ end
201
+
202
+ {
203
+ title: name,
204
+ content: prs
205
+ }
206
+ end
207
+ end
208
+ pane.selection do |data|
209
+ url = data["html_url"]
210
+ `open #{url}`
211
+ end
212
+ end
213
+
214
+
215
+ #
216
+ # Bottom Right
217
+ #
218
+ add_pane do |pane|
219
+ pane.height = 0.5
220
+ pane.width = 0.5
221
+ pane.top = 0.5
222
+ pane.left = 0.5
223
+
224
+ pane.highlight = true
225
+
226
+ pane.title = "Open PRs - fastlane/fastlane"
227
+
228
+ pane.interval = 60 * 5
229
+ pane.content do
230
+ fastlane_prs = []
231
+
232
+ resp = RestClient::Request.execute(
233
+ method: :get,
234
+ url: "https://api.github.com/repos/fastlane/fastlane/pulls?per_page=100",
235
+ user: ENV["WASSUP_GITHUB_USERNAME"],
236
+ password: ENV["WASSUP_GITHUB_ACCESS_TOKEN"]
237
+ )
238
+ json = JSON.parse(resp)
239
+ json.map do |pr|
240
+ fastlane_prs << pr
241
+
242
+ number = pr["number"]
243
+ title = pr["title"]
244
+ created_at = pr["created_at"]
245
+
246
+ date = Time.parse(created_at)
247
+ days = (Time.now - date).to_i / (24 * 60 * 60)
248
+ days_formatted = '%3.3s' % days.to_s
249
+
250
+ ["##{number} #{days_formatted}d ago #{title}",pr]
251
+ end
252
+ end
253
+ pane.selection do |data|
254
+ url = data["html_url"]
255
+ `open #{url}`
256
+ end
257
+ end
data/lib/wassup/app.rb ADDED
@@ -0,0 +1,87 @@
1
+ require 'curses'
2
+
3
+ module Wassup
4
+ class App
5
+ def self.start(path:)
6
+ Curses.init_screen
7
+ Curses.start_color
8
+ Curses.curs_set(0) # Invisible cursor
9
+ Curses.noecho
10
+
11
+ Curses.stdscr.scrollok true
12
+
13
+ Curses.init_pair(1, 15, 0) #white foreground, black background
14
+ Curses.init_pair(2, 2, 0) # red foreground, black background
15
+ Curses.init_pair(3, 7, 3) # white foreground, yellow background
16
+ Curses.init_pair(4, 0, 15) # white foreground, yellow background
17
+
18
+ app = App.new(path: path)
19
+ end
20
+
21
+ def add_pane
22
+ pane_builder = Wassup::PaneBuilder.new
23
+ yield(pane_builder)
24
+
25
+ number = @panes.size + 1
26
+ pane = Pane.new(
27
+ pane_builder.height,
28
+ pane_builder.width,
29
+ pane_builder.top,
30
+ pane_builder.left,
31
+ title: pane_builder.title,
32
+ highlight: pane_builder.highlight,
33
+ focus_number: number,
34
+ interval: pane_builder.interval,
35
+ content_block: pane_builder.content_block,
36
+ selection_block: pane_builder.selection_block
37
+ )
38
+ pane.focus_handler = @focus_handler
39
+ @panes[number.to_s] = pane
40
+ end
41
+
42
+ def initialize(path:)
43
+ @hidden_pane = nil
44
+ @focused_pane = nil
45
+ @panes = {}
46
+
47
+ @focus_handler = Proc.new do |input|
48
+ if (pane = @panes[input.to_s])
49
+ @focused_pane.focused = false
50
+
51
+ if @focused_pane != pane
52
+ @focused_pane = pane
53
+ @focused_pane.focused = true
54
+ else
55
+ @focused_pane = @hidden_pane
56
+ end
57
+
58
+ true
59
+ end
60
+
61
+ if @focused_pane == @hidden_pane
62
+ true
63
+ else
64
+ false
65
+ end
66
+ end
67
+
68
+ begin
69
+
70
+ @hidden_pane = Pane.new(0, 0, 0, 0, highlight: false, focus_number: 0, interval: nil, content_block: nil, selection_block: nil)
71
+ @hidden_pane.focus_handler = @focus_handler
72
+ @focused_pane = @hidden_pane
73
+
74
+ eval(File.new(path).read)
75
+
76
+ loop do
77
+ @focused_pane.handle_keyboard
78
+ @panes.each do |id, pane|
79
+ pane.refresh()
80
+ end
81
+ end
82
+ ensure
83
+ Curses.close_screen
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,376 @@
1
+ require 'curses'
2
+
3
+ require 'time'
4
+
5
+ module Wassup
6
+ class Pane
7
+ attr_accessor :win
8
+ attr_accessor :subwin
9
+ attr_accessor :data_lines
10
+ attr_accessor :data_objects
11
+ attr_accessor :top
12
+
13
+ attr_accessor :title
14
+
15
+ attr_accessor :focused
16
+ attr_accessor :focus_number
17
+
18
+ attr_accessor :should_box
19
+
20
+ attr_accessor :highlighted_line
21
+
22
+ attr_accessor :highlight
23
+ attr_accessor :virtual_scroll
24
+
25
+ attr_accessor :focus_handler
26
+
27
+ attr_accessor :interval
28
+ attr_accessor :last_refreshed
29
+ attr_accessor :content_block
30
+ attr_accessor :selection_block
31
+
32
+ attr_accessor :selected_view_index
33
+ attr_accessor :all_view_data_objects
34
+
35
+ attr_accessor :win_height, :win_width, :win_top, :win_left
36
+
37
+ def initialize(height, width, top, left, title: nil, highlight: true, focus_number: nil, interval:, content_block:, selection_block:)
38
+ self.win_height = Curses.lines * height
39
+ self.win_width = Curses.cols * width
40
+ self.win_top = Curses.lines * top
41
+ self.win_left = Curses.cols * left
42
+
43
+ self.win = Curses::Window.new(self.win_height, self.win_width, self.win_top, self.win_left)
44
+ self.setup_subwin()
45
+
46
+ self.focused = false
47
+ self.focus_number = focus_number
48
+
49
+ self.highlight = highlight
50
+ self.virtual_scroll = true
51
+
52
+ self.top = 0
53
+ self.data_lines = []
54
+ self.data_objects = []
55
+
56
+ self.selected_view_index = 0
57
+ self.all_view_data_objects = [] # Array of array
58
+
59
+ self.win.refresh
60
+ self.subwin.refresh
61
+
62
+ self.title = title
63
+
64
+ self.interval = interval
65
+ self.content_block = content_block
66
+ self.selection_block = selection_block
67
+ end
68
+
69
+ def setup_subwin
70
+ top_bump = 0
71
+
72
+ unless self.subwin.nil?
73
+ self.subwin.clear()
74
+ self.subwin.close()
75
+ self.subwin = nil
76
+ end
77
+
78
+ if (@all_view_data_objects || []).size > 1
79
+ top_bump = 4
80
+
81
+ view_title = (self.all_view_data_objects[self.selected_view_index] || {})[:title]
82
+ view_title += " " * 100
83
+
84
+ self.win.setpos(2, 2)
85
+ self.win.addstr(view_title[0...self.win.maxx()-3])
86
+
87
+ subtitle = "(#{self.selected_view_index + 1} out of #{self.all_view_data_objects.size})"
88
+ subtitle += " " * 100
89
+ self.win.setpos(3, 2)
90
+ self.win.addstr(subtitle[0...self.win.maxx()-3])
91
+
92
+ self.win.refresh
93
+ end
94
+
95
+ if self.win_height == 0
96
+ self.should_box = false
97
+ self.subwin = self.win.subwin(0, 0, 0, 0)
98
+ else
99
+ self.should_box = true
100
+ self.subwin = self.win.subwin(self.win_height - 2 - top_bump, self.win_width - 4, self.win_top + 1 + top_bump, self.win_left + 2)
101
+ end
102
+ self.subwin.nodelay=true
103
+ self.subwin.idlok(true)
104
+ self.subwin.scrollok(true)
105
+ end
106
+
107
+ def needs_refresh?
108
+ return false if self.content_block.nil?
109
+ return false if self.interval.nil?
110
+ return true if self.last_refreshed.nil?
111
+ return Time.now - self.interval > self.last_refreshed
112
+ end
113
+
114
+ def refresh
115
+ return unless needs_refresh?
116
+
117
+ content = self.content_block.call()
118
+
119
+ return unless content.is_a?(Array)
120
+ return if content.first.nil?
121
+
122
+ if content.first.is_a?(Hash)
123
+ self.all_view_data_objects = content
124
+ else
125
+ self.all_view_data_objects = [
126
+ content
127
+ ]
128
+ end
129
+
130
+ self.load_current_view()
131
+
132
+ self.last_refreshed = Time.now
133
+ end
134
+
135
+ def load_current_view
136
+ self.setup_subwin()
137
+ view_content = self.all_view_data_objects[self.selected_view_index]
138
+
139
+ # this might be bad
140
+ self.highlighted_line = nil
141
+
142
+ require 'pp'
143
+ if view_content.is_a?(Hash) && view_content[:content]
144
+ view_content = view_content[:content]
145
+ end
146
+
147
+ @data_lines = []
148
+ @data_objects = []
149
+ view_content.each do |item|
150
+ if item.is_a?(String)
151
+ @data_objects << item
152
+ self.add_line(item)
153
+ elsif item.is_a?(Array)
154
+ @data_objects << item[1]
155
+ self.add_line(item[0])
156
+ end
157
+ end
158
+ end
159
+
160
+ def title=(title)
161
+ @title = title
162
+ self.update_box()
163
+ self.update_title()
164
+ end
165
+
166
+ def update_title
167
+ title = self.title || "<No Title>"
168
+ full_title = "#{self.focus_number} - #{title}"
169
+
170
+ self.win.setpos(0, 3)
171
+ self.win.addstr(full_title)
172
+ self.win.refresh
173
+ end
174
+
175
+ def focused=(focused)
176
+ @focused = focused
177
+ self.update_box()
178
+ self.update_title()
179
+ self.virtual_reload()
180
+ end
181
+
182
+ def update_box
183
+ return unless self.should_box
184
+
185
+ self.win.attron(self.focused ? Curses.color_pair(2) : Curses.color_pair(1))
186
+ self.win.box()
187
+ self.win.attron(Curses.color_pair(1))
188
+
189
+ self.win.refresh
190
+ end
191
+
192
+ def add_line(text)
193
+ data_lines << text
194
+
195
+ if self.virtual_scroll
196
+ self.virtual_reload
197
+ else
198
+ self.load_thing
199
+ end
200
+ end
201
+
202
+ # Load the file into memory and
203
+ # put the first part on the curses display.
204
+ def load_thing
205
+ self.data_lines[0..self.subwin.maxy-1].each_with_index do |line, idx|
206
+ self.subwin.setpos(idx, 0)
207
+ self.subwin.addstr(line)
208
+ end
209
+ self.subwin.setpos(0, 0)
210
+ self.subwin.refresh
211
+ end
212
+
213
+ def virtual_reload
214
+ return if self.data_lines.nil?
215
+
216
+ self.data_lines[self.top..(self.top+self.subwin.maxy-1)].each_with_index do |line, idx|
217
+
218
+ if self.focused && self.highlight && (idx + self.top) == self.highlighted_line
219
+ self.subwin.attron(Curses.color_pair(4))
220
+ else
221
+ self.subwin.attron(Curses.color_pair(1))
222
+ end
223
+
224
+ short_line = line[0...self.subwin.maxx()-3]
225
+
226
+ self.subwin.setpos(idx, 0)
227
+ self.subwin.addstr(short_line)
228
+ self.subwin.clrtoeol()
229
+
230
+ self.subwin.attron(Curses.color_pair(1))
231
+ end
232
+ self.subwin.refresh
233
+ end
234
+
235
+ def virtual_scroll_down
236
+ self.update_highlight(1)
237
+
238
+ bottom = self.top + self.subwin.maxy
239
+ if bottom < self.data_lines.size
240
+ # only move highlight and dont scroll if near top
241
+ if !self.highlight || self.highlighted_line > highlight_scroll_padding
242
+ self.top += 1
243
+ end
244
+ end
245
+
246
+ self.virtual_reload
247
+ end
248
+
249
+ def highlight_scroll_padding
250
+ return (self.subwin.maxy * 0.3).to_i
251
+ end
252
+
253
+ def virtual_scroll_up
254
+ self.update_highlight(-1)
255
+
256
+ if self.top > 0
257
+ # only move highlight and dont scroll if near bottom
258
+ if !self.highlight || (self.data_lines.size - self.highlighted_line) >= highlight_scroll_padding
259
+ self.top -= 1
260
+ end
261
+ end
262
+
263
+ self.virtual_reload
264
+ end
265
+
266
+ def update_highlight(inc)
267
+ return unless self.highlight
268
+
269
+ prev = self.highlighted_line
270
+
271
+ new = nil
272
+ if prev.nil?
273
+ new = 0
274
+ elsif inc.nil?
275
+ new = nil
276
+ elsif !inc.nil?
277
+ new = prev + inc
278
+ end
279
+
280
+ if new < 0 || new >= self.data_lines.size
281
+ new = prev
282
+ end
283
+
284
+ self.highlighted_line = new
285
+
286
+ return new
287
+ end
288
+
289
+ def scroll_left
290
+ self.selected_view_index -= 1
291
+ if self.selected_view_index < 0
292
+ self.selected_view_index = self.all_view_data_objects.size - 1
293
+ end
294
+
295
+ self.load_current_view
296
+ end
297
+
298
+ def scroll_right
299
+ self.selected_view_index += 1
300
+ if self.selected_view_index >= self.all_view_data_objects.size
301
+ self.selected_view_index = 0
302
+ end
303
+
304
+ self.load_current_view
305
+ end
306
+
307
+ # Scroll the display up by one line.
308
+ def scroll_up
309
+ if self.top > 0
310
+ self.subwin.scrl(-1)
311
+ self.top -= 1
312
+ str = self.data_lines[top]
313
+ if str
314
+ self.subwin.setpos(0, 0)
315
+ self.subwin.addstr(str)
316
+ end
317
+ return true
318
+ else
319
+ return false
320
+ end
321
+ end
322
+
323
+ # Scroll the display down by one line.
324
+ def scroll_down
325
+ if self.top + self.subwin.maxy < self.data_lines.length
326
+ self.subwin.scrl(1)
327
+ self.top += 1
328
+ str = self.data_lines[self.top + self.subwin.maxy - 1]
329
+ if str
330
+ self.subwin.attron(Curses.color_pair(1))
331
+ self.subwin.setpos(self.subwin.maxy - 1, 0)
332
+ self.subwin.addstr(str)
333
+ self.subwin.attron(Curses.color_pair(1))
334
+ end
335
+ return true
336
+ else
337
+ return false
338
+ end
339
+ end
340
+
341
+ def handle_keyboard
342
+ input = self.subwin.getch
343
+
344
+ unless focus_handler.nil?
345
+ handled = focus_handler.call(input)
346
+ return if handled
347
+ end
348
+
349
+ if input == "j"
350
+ if virtual_scroll
351
+ virtual_scroll_down
352
+ else
353
+ scroll_down
354
+ end
355
+ elsif input == "k"
356
+ if virtual_scroll
357
+ virtual_scroll_up
358
+ else
359
+ scroll_up
360
+ end
361
+ elsif input == "h"
362
+ scroll_left
363
+ elsif input == "l"
364
+ scroll_right
365
+ elsif input == "q"
366
+ # TODO: This needs to quit
367
+ # Need to kill the loop or something
368
+ elsif input == 10 # enter
369
+ if !self.selection_block.nil? && !self.highlighted_line.nil?
370
+ data = @data_objects[self.highlighted_line]
371
+ self.selection_block.call(data)
372
+ end
373
+ end
374
+ end
375
+ end
376
+ end
@@ -0,0 +1,24 @@
1
+
2
+ module Wassup
3
+ class PaneBuilder
4
+ attr_accessor :height
5
+ attr_accessor :width
6
+ attr_accessor :top
7
+ attr_accessor :left
8
+
9
+ attr_accessor :highlight
10
+
11
+ attr_accessor :title
12
+
13
+ attr_accessor :interval
14
+ attr_accessor :content_block
15
+ attr_accessor :selection_block
16
+
17
+ def content(&block)
18
+ self.content_block = block
19
+ end
20
+ def selection(&block)
21
+ self.selection_block = block
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,3 @@
1
+ module Wassup
2
+ VERSION = "0.1.0"
3
+ end
data/lib/wassup.rb ADDED
@@ -0,0 +1,9 @@
1
+ require "wassup/version"
2
+ require "wassup/app"
3
+ require "wassup/pane"
4
+ require "wassup/pane_builder"
5
+
6
+ module Wassup
7
+ class Error < StandardError; end
8
+ # Your code goes here...
9
+ end
data/wassup.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ require_relative 'lib/wassup/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "wassup"
5
+ spec.version = Wassup::VERSION
6
+ spec.authors = ["Josh Holtz"]
7
+ spec.email = ["me@joshholtz.com"]
8
+
9
+ spec.summary = %q{A scriptable terminal dashboard}
10
+ spec.description = %q{A scriptable terminal dashboard}
11
+ spec.homepage = "https://github.com/joshdholtz/wassup"
12
+ spec.license = "MIT"
13
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
14
+
15
+ spec.metadata["homepage_uri"] = spec.homepage
16
+ spec.metadata["source_code_uri"] = "https://github.com/joshdholtz/wassup"
17
+
18
+ spec.add_runtime_dependency 'curses'
19
+
20
+ # Specify which files should be added to the gem when it is released.
21
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
23
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
24
+ end
25
+ spec.bindir = "bin"
26
+ spec.executables = ["wassup"]
27
+ spec.require_paths = ["lib"]
28
+ end
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: wassup
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Josh Holtz
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-12-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: curses
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: A scriptable terminal dashboard
28
+ email:
29
+ - me@joshholtz.com
30
+ executables:
31
+ - wassup
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - ".gitignore"
36
+ - ".rspec"
37
+ - ".travis.yml"
38
+ - CODE_OF_CONDUCT.md
39
+ - Gemfile
40
+ - Gemfile.lock
41
+ - LICENSE.txt
42
+ - README.md
43
+ - Rakefile
44
+ - bin/console
45
+ - bin/setup
46
+ - bin/wassup
47
+ - examples/josh-fastlane/Supfile
48
+ - lib/wassup.rb
49
+ - lib/wassup/app.rb
50
+ - lib/wassup/pane.rb
51
+ - lib/wassup/pane_builder.rb
52
+ - lib/wassup/version.rb
53
+ - wassup.gemspec
54
+ homepage: https://github.com/joshdholtz/wassup
55
+ licenses:
56
+ - MIT
57
+ metadata:
58
+ homepage_uri: https://github.com/joshdholtz/wassup
59
+ source_code_uri: https://github.com/joshdholtz/wassup
60
+ post_install_message:
61
+ rdoc_options: []
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 2.3.0
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ requirements: []
75
+ rubygems_version: 3.2.3
76
+ signing_key:
77
+ specification_version: 4
78
+ summary: A scriptable terminal dashboard
79
+ test_files: []