gemview 1.0.0 → 1.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 +4 -4
- data/CHANGELOG.md +11 -0
- data/README.md +9 -1
- data/lib/gemview/commands.rb +11 -2
- data/lib/gemview/gem.rb +106 -86
- data/lib/gemview/git_repo.rb +139 -59
- data/lib/gemview/terminal.rb +28 -6
- data/lib/gemview/version.rb +1 -1
- data/lib/gemview/view.rb +17 -6
- data/lib/gemview.rb +0 -3
- metadata +4 -35
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 316d82c9b1a5f8b3e567d04b2ef7541ecbd554992bfe8c7c97497d1aefbe5e8c
|
4
|
+
data.tar.gz: e8c2b2b065dd6404694e5218e5130473abdadbd1f71c4f4e13ca36a7c3d4bbf2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7f414319c19f06dfe2e0e5fb158e06371d03f4f930def70ee7340b09cbde9093cf72a2a20522db142d3b517e507126401b0c58ee7ad14dc0eeb74ec4a2a8ca5b
|
7
|
+
data.tar.gz: ebde82434ab61ec53e68d66cc18048d8378663508b67664bf2c58488bc44ec429b2889a051dd011ef7c5c3152f726cc58fe55fd1fa647f8e39db0b856849571c
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,16 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [1.1.0] - 2025-08-16
|
4
|
+
|
5
|
+
- Add better support for showing the changelog
|
6
|
+
- Add ability to sort search results by total and version downloads
|
7
|
+
- Add ruby version to gem version output
|
8
|
+
- Add ability to disable readme and changelog viewing if they are unreachable
|
9
|
+
- Add support for Codeberg readmes and changelogs (before only Github and Gitlab were supported)
|
10
|
+
|
11
|
+
- Change pager to align TUI at the top of the screen
|
12
|
+
- Change default pager to use less for consistent paging
|
13
|
+
|
3
14
|
## [1.0.0] - 2024-12-08
|
4
15
|
|
5
16
|
- Initial release
|
data/README.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# Gemview
|
2
2
|
|
3
|
-
|
3
|
+
[](https://badge.fury.io/rb/gemview)
|
4
|
+
|
5
|
+
An unofficial TUI interface for querying information from rubygems.org. It uses the [gems](https://rubygems.org/gems/gems) gem internally. It requires the presence of the `less` pager and is tested on macOS and Linux.
|
4
6
|
|
5
7
|
Note: This gem is not directly affiliated with `rubygems.org`. It's just a hobby project.
|
6
8
|
|
@@ -20,6 +22,12 @@ Commands:
|
|
20
22
|
|
21
23
|

|
22
24
|
|
25
|
+
## Implementation
|
26
|
+
|
27
|
+
Changelog and readme fetching is only supported for `github.com` and `gitlab.com` currently. This works by parsing the URI associated with one of these two sites from gem metadata, building a new URI associated with the given file and trying to download it as a raw file. Let me know if there are any other platforms I should add support for.
|
28
|
+
|
29
|
+
Markdown highlighting is added on a best effort basis and if the parser fails for some reason it just falls back to the raw text file.
|
30
|
+
|
23
31
|
## Development
|
24
32
|
|
25
33
|
### Testing & Linting
|
data/lib/gemview/commands.rb
CHANGED
@@ -40,15 +40,24 @@ module Gemview
|
|
40
40
|
|
41
41
|
argument :term, type: :string, required: true, desc: "Search term"
|
42
42
|
|
43
|
-
|
43
|
+
option :downloads, values: %w[total version], desc: "Sort results by most downloads"
|
44
44
|
|
45
|
-
|
45
|
+
example ["cli", "json --downloads=total"]
|
46
|
+
|
47
|
+
def call(term:, downloads: nil, **)
|
46
48
|
gems = Gem.search(term: term)
|
47
49
|
|
48
50
|
if gems.empty?
|
49
51
|
abort("Error: No gems found for the search term: #{term}")
|
50
52
|
end
|
51
53
|
|
54
|
+
case downloads
|
55
|
+
when "total"
|
56
|
+
gems.sort_by! { |gem| -gem.downloads }
|
57
|
+
when "version"
|
58
|
+
gems.sort_by! { |gem| -gem.version_downloads }
|
59
|
+
end
|
60
|
+
|
52
61
|
View.list(gems: gems)
|
53
62
|
end
|
54
63
|
end
|
data/lib/gemview/gem.rb
CHANGED
@@ -1,70 +1,76 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "dry-struct"
|
1
|
+
# frozen_string_literal: true"
|
4
2
|
|
5
3
|
module Gemview
|
6
|
-
class Gem
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
4
|
+
class Gem
|
5
|
+
attr_reader(
|
6
|
+
:name,
|
7
|
+
:downloads,
|
8
|
+
:version,
|
9
|
+
:version_downloads,
|
10
|
+
:version_created_at,
|
11
|
+
:authors,
|
12
|
+
:info,
|
13
|
+
:licenses,
|
14
|
+
:project_uri,
|
15
|
+
:homepage_uri,
|
16
|
+
:source_code_uri,
|
17
|
+
:changelog_uri,
|
18
|
+
:development_dependencies,
|
19
|
+
:runtime_dependencies
|
20
|
+
)
|
21
|
+
|
22
|
+
# @param options [Hash]
|
23
|
+
def initialize(options)
|
24
|
+
@name = options.fetch("name")
|
25
|
+
@downloads = options.fetch("downloads")
|
26
|
+
@version = options.fetch("version")
|
27
|
+
@version_downloads = options.fetch("version_downloads")
|
28
|
+
# Note: This is not returned by `Gems.search`.
|
29
|
+
@version_created_at = options["version_created_at"]
|
30
|
+
&.then { |time| Time.parse(time) }
|
31
|
+
@authors = options.fetch("authors")
|
32
|
+
@info = options.fetch("info")
|
33
|
+
# Note: This is occasionally nil so a default value is required.
|
34
|
+
@licenses = (options.fetch("licenses") || []).freeze
|
35
|
+
@project_uri = options.fetch("project_uri")
|
36
|
+
@homepage_uri = options.fetch("homepage_uri")
|
37
|
+
@source_code_uri = options.fetch("source_code_uri")
|
38
|
+
@changelog_uri = options.fetch("changelog_uri")
|
39
|
+
# Note: Dependencies are not returned by `Gems.search`.
|
40
|
+
@development_dependencies = options
|
41
|
+
.dig("dependencies", "development")
|
42
|
+
&.map { |hash| Dependency.new(hash).freeze }
|
43
|
+
&.freeze
|
44
|
+
@runtime_dependencies = options
|
45
|
+
.dig("dependencies", "runtime")
|
46
|
+
&.map { |hash| Dependency.new(hash).freeze }
|
47
|
+
&.freeze
|
48
|
+
end
|
49
|
+
|
50
|
+
class Dependency
|
51
|
+
attr_reader :name, :requirements
|
52
|
+
|
53
|
+
# @param options [Hash]
|
54
|
+
def initialize(options)
|
55
|
+
@name = options.fetch("name")
|
56
|
+
@requirements = options.fetch("requirements")
|
21
57
|
end
|
22
|
-
end
|
23
|
-
|
24
|
-
attribute :name, Types::Strict::String
|
25
|
-
attribute :downloads, Types::Strict::Integer
|
26
|
-
attribute :version, Types::Strict::String
|
27
|
-
# Note: This is not returned by `Gems.search`.
|
28
|
-
attribute? :version_created_at, Types::Params::Time
|
29
|
-
attribute :authors, Types::Strict::String
|
30
|
-
attribute :info, Types::Strict::String
|
31
|
-
# Note: This is occasionally nil so a default value is required.
|
32
|
-
attribute :licenses, Types::Array.of(Types::Strict::String).default([].freeze)
|
33
|
-
attribute :project_uri, Types::Strict::String
|
34
|
-
attribute :homepage_uri, Types::String.optional
|
35
|
-
attribute :source_code_uri, Types::String.optional
|
36
|
-
attribute :changelog_uri, Types::String.optional
|
37
|
-
|
38
|
-
class Dependency < Dry::Struct
|
39
|
-
transform_keys(&:to_sym)
|
40
|
-
|
41
|
-
attribute :name, Types::Strict::String
|
42
|
-
attribute :requirements, Types::Strict::String
|
43
58
|
|
44
59
|
def to_str
|
45
60
|
%(gem "#{name}", "#{requirements}")
|
46
61
|
end
|
47
62
|
end
|
48
63
|
|
49
|
-
|
50
|
-
|
51
|
-
attribute :development, Types::Strict::Array.of(Dependency)
|
52
|
-
attribute :runtime, Types::Strict::Array.of(Dependency)
|
53
|
-
end
|
54
|
-
|
55
|
-
class Version < Dry::Struct
|
56
|
-
transform_keys(&:to_sym)
|
57
|
-
|
58
|
-
attribute :number, Types::Strict::String
|
59
|
-
alias_method :version, :number
|
60
|
-
|
61
|
-
attribute :downloads_count, Types::Strict::Integer
|
62
|
-
alias_method :downloads, :downloads_count
|
64
|
+
class Version
|
65
|
+
attr_reader :version, :downloads, :release_date, :ruby_version
|
63
66
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
67
|
+
# @param options [Hash]
|
68
|
+
def initialize(options)
|
69
|
+
@version = options.fetch("number")
|
70
|
+
@downloads = options.fetch("downloads_count")
|
71
|
+
@release_date = Date.parse(options.fetch("created_at"))
|
72
|
+
@ruby_version = options.fetch("ruby_version") || "(unknown)"
|
73
|
+
end
|
68
74
|
end
|
69
75
|
|
70
76
|
# Ex. 1234567890 -> "1,234,567,890"
|
@@ -75,9 +81,11 @@ module Gemview
|
|
75
81
|
|
76
82
|
# @return [String]
|
77
83
|
def selector_str
|
84
|
+
one_line_info = info.lines.map(&:strip).reject(&:empty?).join(" ").strip
|
85
|
+
|
78
86
|
<<~SELECT
|
79
87
|
#{name} [#{version}]
|
80
|
-
-- #{Strings.truncate(
|
88
|
+
-- #{Strings.truncate(one_line_info, 75)}
|
81
89
|
SELECT
|
82
90
|
end
|
83
91
|
|
@@ -103,16 +111,21 @@ module Gemview
|
|
103
111
|
Terminal.prettify_markdown(header)
|
104
112
|
end
|
105
113
|
|
114
|
+
# @return [Boolean]
|
115
|
+
def dependencies?
|
116
|
+
!runtime_dependencies.nil? && !development_dependencies.nil?
|
117
|
+
end
|
118
|
+
|
106
119
|
# @return [String]
|
107
120
|
def dependencies_str
|
108
|
-
runtime_deps_str =
|
121
|
+
runtime_deps_str = runtime_dependencies.join("\n").strip
|
109
122
|
runtime_deps_str = if runtime_deps_str.empty?
|
110
123
|
"(none)"
|
111
124
|
else
|
112
125
|
"```rb\n#{runtime_deps_str}\n```"
|
113
126
|
end
|
114
127
|
|
115
|
-
dev_deps_str =
|
128
|
+
dev_deps_str = development_dependencies.join("\n").strip
|
116
129
|
dev_deps_str = if dev_deps_str.empty?
|
117
130
|
"(none)"
|
118
131
|
else
|
@@ -132,46 +145,53 @@ module Gemview
|
|
132
145
|
Terminal.prettify_markdown(dependencies)
|
133
146
|
end
|
134
147
|
|
148
|
+
# @return [String]
|
135
149
|
def versions_str
|
136
150
|
rows = self.class.versions(name: name).map do |version|
|
137
151
|
pretty_downloads = Number.humanized_integer(version.downloads)
|
138
|
-
"| #{version.release_date} | #{version.version} | #{pretty_downloads} |"
|
152
|
+
"| #{version.release_date} | #{version.version} | #{pretty_downloads} | #{version.ruby_version}"
|
139
153
|
end
|
140
154
|
|
141
155
|
table = <<~TABLE
|
142
156
|
## [Versions]
|
143
157
|
|
144
|
-
| *Release Date* | *Version* | *Downloads* |
|
145
|
-
|
158
|
+
| *Release Date* | *Gem Version* | *Downloads* | *Ruby Version* |
|
159
|
+
|----------------|---------------|-------------|----------------|
|
146
160
|
#{rows.join("\n")}
|
147
161
|
TABLE
|
148
162
|
|
149
163
|
Terminal.prettify_markdown(table)
|
150
164
|
end
|
151
165
|
|
152
|
-
# @return [
|
153
|
-
def
|
154
|
-
|
155
|
-
homepage_uri,
|
156
|
-
source_code_uri,
|
157
|
-
changelog_uri
|
158
|
-
].compact
|
159
|
-
end
|
166
|
+
# @return [Gemview::GitRepo, nil]
|
167
|
+
def git_repo
|
168
|
+
return @git_repo if defined? @git_repo
|
160
169
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
170
|
+
@git_repo = GitRepo.from_urls(
|
171
|
+
homepage_uri: homepage_uri,
|
172
|
+
source_code_uri: source_code_uri,
|
173
|
+
changelog_uri: changelog_uri,
|
174
|
+
version: version
|
175
|
+
)
|
165
176
|
end
|
166
177
|
|
167
|
-
# @return [
|
168
|
-
def
|
169
|
-
|
170
|
-
|
171
|
-
|
178
|
+
# @return [Boolean]
|
179
|
+
def git_repo? = !git_repo.nil?
|
180
|
+
|
181
|
+
# @return [Boolean]
|
182
|
+
def readme? = git_repo? && git_repo.readme?
|
183
|
+
|
184
|
+
# @return [String, nil]
|
185
|
+
def readme = git_repo&.readme
|
186
|
+
|
187
|
+
# @return [Boolean]
|
188
|
+
def changelog? = git_repo? && git_repo.changelog?
|
189
|
+
|
190
|
+
# @return [String, nil]
|
191
|
+
def changelog = git_repo&.changelog
|
172
192
|
|
173
193
|
# @param name [String]
|
174
|
-
# @param version [String
|
194
|
+
# @param version [String, nil] will default to latest if not provided
|
175
195
|
# @return [Gemview::Gem]
|
176
196
|
def self.find(name:, version: nil)
|
177
197
|
@find ||= {}
|
@@ -186,30 +206,30 @@ module Gemview
|
|
186
206
|
# @param term [String] search term
|
187
207
|
# @return [Array<Gemview::Gem>]
|
188
208
|
def self.search(term:)
|
189
|
-
Client.v1.search(term).map { |gem_hash| new
|
209
|
+
Client.v1.search(term).map { |gem_hash| new(gem_hash) }
|
190
210
|
end
|
191
211
|
|
192
212
|
# @param username [String] rubygems.org username
|
193
213
|
# @return [Array<Gemview::Gem>]
|
194
214
|
def self.author(username:)
|
195
|
-
Client.v1.gems(username).map { |gem_hash| new
|
215
|
+
Client.v1.gems(username).map { |gem_hash| new(gem_hash) }
|
196
216
|
end
|
197
217
|
|
198
218
|
# @return [Array<Gemview::Gem>]
|
199
219
|
def self.latest
|
200
|
-
Client.v1.latest.map { |gem_hash| new
|
220
|
+
Client.v1.latest.map { |gem_hash| new(gem_hash) }
|
201
221
|
end
|
202
222
|
|
203
223
|
# @return [Array<Gemview::Gem>]
|
204
224
|
def self.just_updated
|
205
|
-
Client.v1.just_updated.map { |gem_hash| new
|
225
|
+
Client.v1.just_updated.map { |gem_hash| new(gem_hash) }
|
206
226
|
end
|
207
227
|
|
208
228
|
# @param name [String] gem name
|
209
229
|
# @return [Array<Gemview::Gem::Version>]
|
210
230
|
def self.versions(name:)
|
211
231
|
@versions ||= {}
|
212
|
-
@versions[name] ||= Client.v1.versions(name).map { |gem_hash| Version.new
|
232
|
+
@versions[name] ||= Client.v1.versions(name).map { |gem_hash| Version.new(gem_hash).freeze }.freeze
|
213
233
|
end
|
214
234
|
end
|
215
235
|
end
|
data/lib/gemview/git_repo.rb
CHANGED
@@ -4,21 +4,24 @@ module Gemview
|
|
4
4
|
class GitRepo
|
5
5
|
HOSTS = [
|
6
6
|
GITHUB = :github,
|
7
|
-
GITLAB = :gitlab
|
7
|
+
GITLAB = :gitlab,
|
8
|
+
CODEBERG = :codeberg
|
8
9
|
].freeze
|
9
10
|
|
10
|
-
|
11
|
+
HTTPS_PORT = 443
|
12
|
+
|
13
|
+
# @param homepage_uri [String, nil]
|
14
|
+
# @param source_code_uri [String, nil]
|
15
|
+
# @param changelog_uri [String, nil]
|
11
16
|
# @param version [String]
|
12
|
-
# @return [Gemview::GitRepo
|
13
|
-
def self.from_urls(
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
return @from_urls[base_url] ||= new(
|
21
|
-
base_url: base_url,
|
17
|
+
# @return [Gemview::GitRepo, nil]
|
18
|
+
def self.from_urls(homepage_uri:, source_code_uri:, changelog_uri:, version:)
|
19
|
+
[homepage_uri, source_code_uri, changelog_uri].compact.each do |uri|
|
20
|
+
base_uri, git_host = parse_base_uri(uri)
|
21
|
+
if base_uri && git_host
|
22
|
+
return new(
|
23
|
+
base_uri: base_uri,
|
24
|
+
changelog_uri: changelog_uri,
|
22
25
|
git_host: git_host,
|
23
26
|
version: version
|
24
27
|
)
|
@@ -28,99 +31,176 @@ module Gemview
|
|
28
31
|
end
|
29
32
|
|
30
33
|
# @param [String]
|
31
|
-
# @return [
|
32
|
-
def self.
|
33
|
-
|
34
|
-
return [
|
34
|
+
# @return [base_uri as `String` and git_host as `Symbol`] or nil if unsuccessful
|
35
|
+
def self.parse_base_uri(uri)
|
36
|
+
github_base_uri = uri[%r{^https?://github\.com/[^/]+/[^/]+}, 0]
|
37
|
+
return [github_base_uri, GITHUB] if github_base_uri
|
38
|
+
|
39
|
+
gitlab_base_uri = uri[%r{^https?://gitlab\.com/[^/]+/[^/]+}, 0]
|
40
|
+
return [gitlab_base_uri, GITLAB] if gitlab_base_uri
|
35
41
|
|
36
|
-
|
37
|
-
[
|
42
|
+
codeberg_base_uri = uri[%r{^https?://codeberg\.org/[^/]+/[^/]+}, 0]
|
43
|
+
[codeberg_base_uri, CODEBERG] if codeberg_base_uri
|
38
44
|
end
|
39
45
|
|
40
|
-
private_class_method :new
|
46
|
+
private_class_method :new
|
41
47
|
|
42
|
-
attr_reader :
|
48
|
+
attr_reader :base_uri, :changelog_uri, :git_host, :version
|
43
49
|
|
44
|
-
# @param
|
50
|
+
# @param base_uri [String] base Git repo uri for `HOSTS`
|
51
|
+
# @param changelog_uri [String, nil] from the gem metadata
|
45
52
|
# @param git_host [Symbol] from `HOSTS`
|
46
53
|
# @param version [String]
|
47
|
-
def initialize(
|
54
|
+
def initialize(base_uri:, changelog_uri:, git_host:, version:)
|
48
55
|
raise ArgumentError, "Invalid host: #{git_host}" unless HOSTS.include?(git_host)
|
49
56
|
|
50
|
-
@
|
57
|
+
@base_uri = base_uri.dup.freeze
|
58
|
+
@changelog_uri = changelog_uri.dup.freeze
|
51
59
|
@git_host = git_host
|
52
60
|
@version = version.dup.freeze
|
53
61
|
end
|
54
62
|
|
55
|
-
# @return [
|
63
|
+
# @return [Boolean]
|
64
|
+
def readme? = !defined?(@readme) || !readme.nil?
|
65
|
+
|
66
|
+
# @return [String, nil]
|
56
67
|
def readme
|
57
68
|
return @readme if defined?(@readme)
|
58
69
|
|
59
70
|
@readme = fetch_raw_file("README.md")
|
60
71
|
end
|
61
72
|
|
62
|
-
# @return [
|
73
|
+
# @return [Boolean]
|
74
|
+
def changelog? = !defined?(@changelog) || !changelog.nil?
|
75
|
+
|
76
|
+
# @return [String, nil]
|
63
77
|
def changelog
|
64
78
|
return @changelog if defined?(@changelog)
|
65
79
|
|
66
|
-
|
80
|
+
filenames = [changelog_filename, "CHANGELOG.md"].compact.uniq
|
81
|
+
filenames.each do |filename|
|
82
|
+
break if (@changelog = fetch_raw_file(filename))
|
83
|
+
end
|
84
|
+
|
85
|
+
@changelog
|
67
86
|
end
|
68
87
|
|
69
88
|
private
|
70
89
|
|
90
|
+
def changelog_filename
|
91
|
+
return unless @changelog_uri&.end_with?(".md")
|
92
|
+
|
93
|
+
changelog_base_uri, changelog_git_host = self.class.parse_base_uri(@changelog_uri)
|
94
|
+
return if changelog_base_uri != base_uri
|
95
|
+
return if changelog_git_host != git_host
|
96
|
+
|
97
|
+
@changelog_uri.split("/").last
|
98
|
+
end
|
99
|
+
|
71
100
|
# @param filename [String]
|
72
|
-
# @return [String
|
101
|
+
# @return [String, nil]
|
73
102
|
def fetch_raw_file(filename)
|
74
103
|
case @git_host
|
75
104
|
when GITHUB then github_raw_file(filename)
|
76
105
|
when GITLAB then gitlab_raw_file(filename)
|
106
|
+
when CODEBERG then codeberg_raw_file(filename)
|
77
107
|
end
|
78
108
|
end
|
79
109
|
|
80
110
|
# @param filename [String]
|
81
|
-
# @return [String
|
111
|
+
# @return [String, nil]
|
82
112
|
def github_raw_file(filename)
|
83
113
|
# From: `https://github.com/charmbracelet/bubbles`
|
84
114
|
# To: `https://raw.githubusercontent.com/charmbracelet/bubbles/refs/tags/v0.20.0/README.md`
|
85
|
-
path = @
|
86
|
-
|
87
|
-
|
88
|
-
"
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
115
|
+
path = @base_uri.sub(%r{^https?://github\.com}, "")
|
116
|
+
|
117
|
+
fetch_markdown(
|
118
|
+
host: "raw.githubusercontent.com",
|
119
|
+
tag_paths: [
|
120
|
+
"#{path}/refs/tags/v#{@version}/#{filename}",
|
121
|
+
"#{path}/refs/tags/#{@version}/#{filename}"
|
122
|
+
],
|
123
|
+
head_paths: [
|
124
|
+
"#{path}/refs/heads/main/#{filename}",
|
125
|
+
"#{path}/refs/heads/master/#{filename}"
|
126
|
+
]
|
127
|
+
)
|
95
128
|
end
|
96
129
|
|
97
130
|
# @param filename [String]
|
98
|
-
# @return [String
|
131
|
+
# @return [String, nil]
|
99
132
|
def gitlab_raw_file(filename)
|
100
133
|
# From: `https://gitlab.com/gitlab-org/gitlab`
|
101
|
-
# To: `https://gitlab.com/gitlab-org/gitlab/-/raw/v17.5.1-ee/README.md
|
102
|
-
path = @
|
103
|
-
|
104
|
-
|
105
|
-
"
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
134
|
+
# To: `https://gitlab.com/gitlab-org/gitlab/-/raw/v17.5.1-ee/README.md`
|
135
|
+
path = @base_uri.sub(%r{^https?://gitlab\.com}, "")
|
136
|
+
|
137
|
+
fetch_markdown(
|
138
|
+
host: "gitlab.com",
|
139
|
+
tag_paths: [
|
140
|
+
"#{path}/-/raw/v#{@version}/#{filename}",
|
141
|
+
"#{path}/-/raw/#{@version}/#{filename}"
|
142
|
+
],
|
143
|
+
head_paths: [
|
144
|
+
"#{path}/-/raw/main/#{filename}",
|
145
|
+
"#{path}/-/raw/master/#{filename}"
|
146
|
+
]
|
147
|
+
)
|
148
|
+
end
|
149
|
+
|
150
|
+
# @param filename [String]
|
151
|
+
# @return [String, nil]
|
152
|
+
def codeberg_raw_file(filename)
|
153
|
+
# From: `https://codeberg.org/bendangelo/wiktionary_api`
|
154
|
+
# To: `https://codeberg.org/bendangelo/wiktionary_api/raw/tag/v0.1.1/README.md`
|
155
|
+
path = @base_uri.sub(%r{^https?://codeberg\.org}, "")
|
156
|
+
|
157
|
+
fetch_markdown(
|
158
|
+
host: "codeberg.org",
|
159
|
+
tag_paths: [
|
160
|
+
"#{path}/raw/tag/v#{@version}/#{filename}",
|
161
|
+
"#{path}/raw/tag/#{@version}/#{filename}"
|
162
|
+
],
|
163
|
+
head_paths: [
|
164
|
+
"#{path}/raw/branch/main/#{filename}",
|
165
|
+
"#{path}/raw/branch/master/#{filename}"
|
166
|
+
]
|
167
|
+
)
|
112
168
|
end
|
113
169
|
|
114
|
-
# @param
|
115
|
-
# @
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
170
|
+
# @param host [String]
|
171
|
+
# @param tag_paths [Array<String>]
|
172
|
+
# @param head_paths [Array<String>]
|
173
|
+
# @return [String, nil]
|
174
|
+
def fetch_markdown(host:, tag_paths:, head_paths:)
|
175
|
+
body = nil
|
176
|
+
|
177
|
+
Net::HTTP.start(host, HTTPS_PORT, use_ssl: true, open_timeout: 2, read_timeout: 2) do |http|
|
178
|
+
tag_paths.each do |path|
|
179
|
+
response = http.get(path)
|
180
|
+
if response.is_a?(Net::HTTPSuccess)
|
181
|
+
body = response.body.force_encoding("UTF-8")
|
182
|
+
break
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
unless body
|
187
|
+
head_paths.each do |path|
|
188
|
+
response = http.get(path)
|
189
|
+
if response.is_a?(Net::HTTPSuccess)
|
190
|
+
body = <<~BODY
|
191
|
+
*FYI*: This was fetched from the HEAD branch of the Git repository.
|
192
|
+
|
193
|
+
#{response.body.force_encoding("UTF-8")}
|
194
|
+
BODY
|
195
|
+
break
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
121
199
|
end
|
122
|
-
|
123
|
-
|
200
|
+
|
201
|
+
Terminal.prettify_markdown(body) if body
|
202
|
+
rescue Net::OpenTimeout, Net::ReadTimeout
|
203
|
+
nil # this is best effort so we silence network errors here
|
124
204
|
end
|
125
205
|
end
|
126
206
|
end
|
data/lib/gemview/terminal.rb
CHANGED
@@ -2,6 +2,11 @@
|
|
2
2
|
|
3
3
|
module Gemview
|
4
4
|
module Terminal
|
5
|
+
# Clears the screen using escape codes.
|
6
|
+
def self.clear_screen
|
7
|
+
print "\e[2J\e[f"
|
8
|
+
end
|
9
|
+
|
5
10
|
# @param question [String]
|
6
11
|
# @return [Boolean]
|
7
12
|
def self.confirm(question:)
|
@@ -10,15 +15,18 @@ module Gemview
|
|
10
15
|
|
11
16
|
# @param content [String]
|
12
17
|
def self.page(content)
|
13
|
-
|
18
|
+
# Override the default pager command so that it is top justified to match the choice menus.
|
19
|
+
TTY::Pager::SystemPager.new(command: "less -c -r --tilde").page(content)
|
14
20
|
end
|
15
21
|
|
16
22
|
# @param prompt [String]
|
17
|
-
# @param choices [Array<String
|
23
|
+
# @param choices [Array<String, Hash>, Proc] where all choices are unique
|
18
24
|
# @yield [String] yields until the user exits the prompt gracefully
|
19
25
|
def self.choose(message:, choices:, per_page: 6)
|
20
|
-
|
21
|
-
|
26
|
+
loop do
|
27
|
+
choice_list = choices.is_a?(Proc) ? choices.call : choices
|
28
|
+
choice = selector.select(message, choice_list, per_page)
|
29
|
+
choice ? yield(choice) : break
|
22
30
|
end
|
23
31
|
end
|
24
32
|
|
@@ -82,10 +90,12 @@ module Gemview
|
|
82
90
|
end
|
83
91
|
|
84
92
|
# @param prompt [String]
|
85
|
-
# @param choices [Array<String>] where all choices are unique
|
93
|
+
# @param choices [Array<String, Hash>] where all choices are unique
|
86
94
|
# @param per_page [Integer] results per page
|
87
|
-
# @return [String
|
95
|
+
# @return [String, nil]
|
88
96
|
def select(message, choices, per_page)
|
97
|
+
# TODO: Add support for preserving the previous selection if this ever gets fixed upstream.
|
98
|
+
# Issue: https://github.com/piotrmurach/tty-prompt/issues/206
|
89
99
|
choice = @prompt.select(
|
90
100
|
message,
|
91
101
|
choices,
|
@@ -93,10 +103,22 @@ module Gemview
|
|
93
103
|
help: "(Press Enter to select and Escape to leave)",
|
94
104
|
show_help: :always
|
95
105
|
)
|
106
|
+
|
96
107
|
choice unless @exit
|
97
108
|
ensure
|
98
109
|
@exit = false
|
99
110
|
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
# @param choice [String, nil]
|
115
|
+
# @param choices [Array<String, Hash>]
|
116
|
+
# @return [Boolean]
|
117
|
+
def disabled_choice?(choice, choices)
|
118
|
+
choices.any? do |possible_choice|
|
119
|
+
possible_choice in {name: ^choice, disabled: String}
|
120
|
+
end
|
121
|
+
end
|
100
122
|
end
|
101
123
|
private_constant :Selector
|
102
124
|
end
|
data/lib/gemview/version.rb
CHANGED
data/lib/gemview/view.rb
CHANGED
@@ -3,18 +3,28 @@
|
|
3
3
|
module Gemview
|
4
4
|
module View
|
5
5
|
def self.info(gem:)
|
6
|
-
gem = Gem.find(name: gem.name, version: gem.version)
|
6
|
+
gem = Gem.find(name: gem.name, version: gem.version) unless gem.dependencies?
|
7
7
|
prompt = <<~PROMPT.chomp
|
8
8
|
#{gem.header_str}
|
9
9
|
More info:
|
10
10
|
PROMPT
|
11
11
|
|
12
|
-
|
12
|
+
choices_proc = proc do
|
13
|
+
[
|
14
|
+
{name: "Readme", disabled: gem.readme? ? nil : "(missing)"},
|
15
|
+
{name: "Changelog", disabled: gem.changelog? ? nil : "(missing)"},
|
16
|
+
"Dependencies",
|
17
|
+
"Versions"
|
18
|
+
]
|
19
|
+
end
|
20
|
+
|
21
|
+
Terminal.clear_screen
|
22
|
+
Terminal.choose(message: prompt, choices: choices_proc) do |choice|
|
13
23
|
case choice
|
14
24
|
when "Readme"
|
15
|
-
Terminal.page([gem.header_str, gem.
|
25
|
+
Terminal.page([gem.header_str, gem.readme].join("\n")) if gem.readme
|
16
26
|
when "Changelog"
|
17
|
-
Terminal.page([gem.header_str, gem.
|
27
|
+
Terminal.page([gem.header_str, gem.changelog].join("\n")) if gem.changelog
|
18
28
|
when "Dependencies"
|
19
29
|
Terminal.page([gem.header_str, gem.dependencies_str].join("\n"))
|
20
30
|
when "Versions"
|
@@ -30,8 +40,9 @@ module Gemview
|
|
30
40
|
[gem.selector_str, gem]
|
31
41
|
end
|
32
42
|
|
33
|
-
Terminal.
|
34
|
-
|
43
|
+
Terminal.clear_screen
|
44
|
+
Terminal.choose(message: "Choose a gem:", choices: gems_by_description.keys) do |description|
|
45
|
+
info(gem: gems_by_description.fetch(description))
|
35
46
|
end
|
36
47
|
end
|
37
48
|
end
|
data/lib/gemview.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gemview
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
-
|
8
|
-
autorequire:
|
7
|
+
- Kevin Robell
|
9
8
|
bindir: exe
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 2025-08-17 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: dry-cli
|
@@ -24,20 +23,6 @@ dependencies:
|
|
24
23
|
- - "~>"
|
25
24
|
- !ruby/object:Gem::Version
|
26
25
|
version: 1.2.0
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: dry-struct
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - "~>"
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: 1.6.0
|
34
|
-
type: :runtime
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - "~>"
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: 1.6.0
|
41
26
|
- !ruby/object:Gem::Dependency
|
42
27
|
name: gems
|
43
28
|
requirement: !ruby/object:Gem::Requirement
|
@@ -108,20 +93,6 @@ dependencies:
|
|
108
93
|
- - "~>"
|
109
94
|
- !ruby/object:Gem::Version
|
110
95
|
version: 0.23.1
|
111
|
-
- !ruby/object:Gem::Dependency
|
112
|
-
name: zeitwerk
|
113
|
-
requirement: !ruby/object:Gem::Requirement
|
114
|
-
requirements:
|
115
|
-
- - "<"
|
116
|
-
- !ruby/object:Gem::Version
|
117
|
-
version: '2.7'
|
118
|
-
type: :runtime
|
119
|
-
prerelease: false
|
120
|
-
version_requirements: !ruby/object:Gem::Requirement
|
121
|
-
requirements:
|
122
|
-
- - "<"
|
123
|
-
- !ruby/object:Gem::Version
|
124
|
-
version: '2.7'
|
125
96
|
description: 'An unofficial CLI interface to browse rubygems.org. Search for gems
|
126
97
|
by name, see which ones have been recently updated and look at their dependencies.
|
127
98
|
|
@@ -155,7 +126,6 @@ metadata:
|
|
155
126
|
homepage_uri: https://github.com/apainintheneck/gemview
|
156
127
|
source_code_uri: https://github.com/apainintheneck/gemview
|
157
128
|
changelog_uri: https://github.com/apainintheneck/gemview/blob/main/CHANGELOG.md
|
158
|
-
post_install_message:
|
159
129
|
rdoc_options: []
|
160
130
|
require_paths:
|
161
131
|
- lib
|
@@ -170,8 +140,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
170
140
|
- !ruby/object:Gem::Version
|
171
141
|
version: '0'
|
172
142
|
requirements: []
|
173
|
-
rubygems_version: 3.
|
174
|
-
signing_key:
|
143
|
+
rubygems_version: 3.6.2
|
175
144
|
specification_version: 4
|
176
145
|
summary: An unofficial CLI interface to browse rubygems.org
|
177
146
|
test_files: []
|