repofetch 0.4.4 → 0.5.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: 3ad5af8ce1827ff6413451767fc8040cc710a057ecfde81d2d5db8c33282a15d
4
- data.tar.gz: 3b9658cdeaf490accc551548464ea534e49b86a15dde562b16eec7da0d2d9d05
3
+ metadata.gz: d212500ad7536eede542d6a5c1e8e0f7cd6de302dd6508c6c279896100004b37
4
+ data.tar.gz: b8a63cc0c47c808f937b27041e508dbec59fee6934d6adaf2e1e4e94cbf73be5
5
5
  SHA512:
6
- metadata.gz: 07a62248fa6ff9248b85b01fc7f8376ae6b17fd5b9f30fce25a7e1ad139892b4ee230e84e3eb43b1aa2d1b94c66ee0be8f330100b2949b3ba95edbe7bb7cb2b2
7
- data.tar.gz: 814a0249c286baf5523a461c50acadd75c2f31149185e5c8f8886e2f48feddcc770445fac5f6441092663cd1dbdea6dd40c2a457526d2740774be67bdafdc709
6
+ metadata.gz: fffca813b8f21cdae1229dc8d506ff1fba2d50db37252b1403d20a327a276991567aa46e78dcd52a999d290951162886e7ad6366b930054d7553cf5516eecf92
7
+ data.tar.gz: 788d2d7d46db0e2ed455e170ffaa52a5b392cb87815eb09f906a92e0523d51dc81a2d8f279d850757071046ade142a29e3811734b53931ba1f63691d089746c7
data/CONTRIBUTING.md CHANGED
@@ -62,14 +62,15 @@ The following requirements assume you are *not* manually implementing a plugin's
62
62
  `to_s` method, and you are inheriting from `Repofetch::Plugin`.
63
63
 
64
64
  - `ascii` should return a string for the ASCII art
65
- - `header` should return the header text that will be above the `---` separator on the right side.
65
+ - `header` should return the header text or an array of header text that will be above the `---` separator on the right side.
66
66
  - `stats` should return an array of values that implement `to_s`. These will be
67
67
  the stats displayed to the right of the ASCII art. You can use`Repofetch::Stat` and
68
68
  `Repofetch::TimespanStat` to create a pretty stat.
69
69
 
70
- The following are optional.
70
+ ### Optional Plugin Instance Methods
71
71
 
72
72
  - `theme` can return an instance of `Repofetch::Theme` to use a different color scheme.
73
+ - `primary_color` will set the color for the header and stat labels.
73
74
 
74
75
  ### Authoring ASCII Art
75
76
 
@@ -113,15 +114,16 @@ class MyCoolPlugin < Repofetch::Plugin
113
114
  end
114
115
 
115
116
  def header
116
- # NOTE: theme is provided by the base Plugin class
117
- "stats from #{theme.format(:underline, 'my plugin')}"
117
+ # NOTE: The inherited header_joiner method will make the final header "My Plugin @ Me"
118
+ # You can override this method to use a different joiner, or override formatted_header
119
+ # for more control.
120
+ ['My Plugin', 'Me']
118
121
  end
119
122
 
120
123
  def stats
121
- # if theme is not passed, the stat will not be styled
122
124
  [
123
- Repofetch::Stat.new('git repo detected', @detected_from_git, emoji: '📂', theme: theme),
124
- Repofetch::Stat.new('args passed', @arg_count, theme: theme)
125
+ Repofetch::Stat.new('git repo detected', @detected_from_git, emoji: '📂'),
126
+ Repofetch::Stat.new('args passed', @arg_count)
125
127
  ]
126
128
  end
127
129
  end
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- repofetch (0.4.4)
4
+ repofetch (0.5.0)
5
5
  actionview (~> 7.0, >= 7.0.4)
6
6
  dotenv (~> 2.8)
7
7
  faraday-retry (~> 2.0)
@@ -12,13 +12,13 @@ PATH
12
12
  GEM
13
13
  remote: https://rubygems.org/
14
14
  specs:
15
- actionview (7.0.4)
16
- activesupport (= 7.0.4)
15
+ actionview (7.0.4.1)
16
+ activesupport (= 7.0.4.1)
17
17
  builder (~> 3.1)
18
18
  erubi (~> 1.4)
19
19
  rails-dom-testing (~> 2.0)
20
20
  rails-html-sanitizer (~> 1.1, >= 1.2.0)
21
- activesupport (7.0.4)
21
+ activesupport (7.0.4.1)
22
22
  concurrent-ruby (~> 1.0, >= 1.0.2)
23
23
  i18n (>= 1.6, < 2)
24
24
  minitest (>= 5.1)
@@ -35,7 +35,7 @@ GEM
35
35
  docile (1.4.0)
36
36
  dotenv (2.8.1)
37
37
  erubi (1.12.0)
38
- faraday (2.7.2)
38
+ faraday (2.7.4)
39
39
  faraday-net_http (>= 2.0, < 3.1)
40
40
  ruby2_keywords (>= 0.0.4)
41
41
  faraday-net_http (3.0.2)
@@ -58,7 +58,7 @@ GEM
58
58
  faraday (>= 1, < 3)
59
59
  sawyer (~> 0.9)
60
60
  os (1.1.4)
61
- overcommit (0.59.1)
61
+ overcommit (0.60.0)
62
62
  childprocess (>= 0.6.3, < 5)
63
63
  iniparse (~> 1.4)
64
64
  rexml (~> 3.2)
@@ -70,12 +70,12 @@ GEM
70
70
  rails-dom-testing (2.0.3)
71
71
  activesupport (>= 4.2.0)
72
72
  nokogiri (>= 1.6)
73
- rails-html-sanitizer (1.4.4)
73
+ rails-html-sanitizer (1.5.0)
74
74
  loofah (~> 2.19, >= 2.19.1)
75
75
  rainbow (3.1.1)
76
76
  rake (13.0.6)
77
77
  rchardet (1.8.0)
78
- regexp_parser (2.6.1)
78
+ regexp_parser (2.6.2)
79
79
  rexml (3.2.5)
80
80
  rspec (3.12.0)
81
81
  rspec-core (~> 3.12.0)
@@ -86,7 +86,7 @@ GEM
86
86
  rspec-expectations (3.12.2)
87
87
  diff-lcs (>= 1.2.0, < 2.0)
88
88
  rspec-support (~> 3.12.0)
89
- rspec-mocks (3.12.2)
89
+ rspec-mocks (3.12.3)
90
90
  diff-lcs (>= 1.2.0, < 2.0)
91
91
  rspec-support (~> 3.12.0)
92
92
  rspec-snapshot (2.0.1)
@@ -105,10 +105,13 @@ GEM
105
105
  unicode-display_width (>= 2.4.0, < 3.0)
106
106
  rubocop-ast (1.24.1)
107
107
  parser (>= 3.1.1.0)
108
+ rubocop-capybara (2.17.0)
109
+ rubocop (~> 1.41)
108
110
  rubocop-rake (0.6.0)
109
111
  rubocop (~> 1.0)
110
- rubocop-rspec (2.17.0)
112
+ rubocop-rspec (2.18.1)
111
113
  rubocop (~> 1.33)
114
+ rubocop-capybara (~> 2.17)
112
115
  ruby-progressbar (1.11.0)
113
116
  ruby2_keywords (0.0.5)
114
117
  sawyer (0.9.2)
@@ -149,4 +152,4 @@ DEPENDENCIES
149
152
  yard (~> 0.9.28)
150
153
 
151
154
  BUNDLED WITH
152
- 2.4.3
155
+ 2.4.4
data/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  [![GitHub contributors (via allcontributors.org)](https://img.shields.io/github/all-contributors/spenserblack/repofetch)](./CREDITS.md)
5
5
  ![CI](https://github.com/spenserblack/repofetch/workflows/CI/badge.svg)
6
6
  [![CodeQL](https://github.com/spenserblack/repofetch/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/spenserblack/repofetch/actions/workflows/github-code-scanning/codeql)
7
- [![codecov](https://codecov.io/gh/spenserblack/repofetch/branch/master/graph/badge.svg?token=3572AEWQAY)](https://codecov.io/gh/spenserblack/repofetch)
7
+ [![codecov](https://codecov.io/gh/spenserblack/repofetch/branch/main/graph/badge.svg?token=3572AEWQAY)](https://codecov.io/gh/spenserblack/repofetch)
8
8
 
9
9
  Fetch details about your remote repository.
10
10
 
data/RELEASE_NOTES CHANGED
@@ -1,6 +1,28 @@
1
- Bitbucket Cloud Bugfix
1
+ 0.5.0
2
+
3
+ ## Added
4
+
5
+ - `Stat#format(theme)` to create styled stats
6
+ - `Stat#style_label` to style the label without mutating the original `Stat` instance
7
+ - `Plugin#matches_path?` and `Plugin#from_path` to allow plugins to initialize from paths, not
8
+ just git repositories
9
+
10
+ ## Changed
11
+
12
+ - Bitbucket Cloud header to be bold and blue
13
+ - `Stat#style_label!` to accept one or more styles as parameters
14
+ - `Plugin#matches_repo?` to return `false` instead of raising an error when it is not overridden
2
15
 
3
16
  ## Fixed
4
17
 
5
- - Bitbucket Cloud not getting automatically detected when running `repofetch` in a
6
- Bitbucket Cloud repository
18
+ - length of separator to match *visual* length of header
19
+
20
+ ## Potentially Breaking for 3rd-party Plugins
21
+
22
+ ### Changed
23
+
24
+ - `Repofetch::Util` to be a module
25
+ - `clean_s` to be called `remove_format_params`
26
+ - Moved `default_remote` and `default_remote_url` to be in `Repofetch::Util`
27
+ - `Stat#to_s` to return an unstyled value
28
+ - Header text and stats will now automatically be bold and use the value returned by `Plugin#primary_color`
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'repofetch'
4
+
5
+ class Repofetch
6
+ class BitbucketCloud < Repofetch::Plugin
7
+ # Methods to get Bitbucket Cloud stats.
8
+ module Stats
9
+ protected
10
+
11
+ def repo_data
12
+ @repo_data ||= agent.call(:get, "repositories/#{@repo_identifier}").data
13
+ end
14
+
15
+ def clone_urls
16
+ @clone_urls ||= repo_data['links']['clone'].to_h { |clone| [clone['name'].to_sym, clone['href']] }
17
+ end
18
+
19
+ def http_clone_url
20
+ Repofetch::Stat.new('HTTP(S)', clone_urls[:https], emoji: '🌐')
21
+ end
22
+
23
+ def ssh_clone_url
24
+ Repofetch::Stat.new('SSH', clone_urls[:ssh], emoji: '🔑')
25
+ end
26
+
27
+ def watchers
28
+ @watcher_data ||= agent.call(:get, "repositories/#{@repo_identifier}/watchers").data
29
+ Repofetch::Stat.new('subscribers', @watcher_data['size'], emoji: '👀')
30
+ end
31
+
32
+ def forks
33
+ @fork_data ||= agent.call(:get, "repositories/#{@repo_identifier}/forks").data
34
+ Repofetch::Stat.new('forks', @fork_data['size'], emoji: '🔱')
35
+ end
36
+
37
+ def created
38
+ Repofetch::TimespanStat.new('created', repo_data['created_on'], emoji: '🐣')
39
+ end
40
+
41
+ def updated
42
+ Repofetch::TimespanStat.new('updated', repo_data['updated_on'], emoji: '📤')
43
+ end
44
+
45
+ def size
46
+ # NOTE: Size is in bytes
47
+ # TODO: Move this somewhere else instead of using a copy-paste
48
+ byte_size = number_to_human_size(repo_data['size'] || 0, precision: 2, significant: false,
49
+ strip_insignificant_zeros: false)
50
+ Repofetch::Stat.new('size', byte_size, emoji: '💽')
51
+ end
52
+
53
+ def issues
54
+ @issue_data ||= agent.call(:get, "repositories/#{@repo_identifier}/issues").data
55
+ Repofetch::Stat.new('issues', @issue_data['size'], emoji: '❗')
56
+ end
57
+
58
+ def pull_requests
59
+ @pull_request_data ||= agent.call(:get, "repositories/#{@repo_identifier}/pullrequests").data
60
+ Repofetch::Stat.new('pull requests', @pull_request_data['size'], emoji: '🔀')
61
+ end
62
+ end
63
+ end
64
+ end
@@ -2,14 +2,19 @@
2
2
 
3
3
  require 'action_view'
4
4
  require 'optparse'
5
- require 'repofetch'
5
+ require 'repofetch/bitbucketcloud/stats'
6
6
  require 'repofetch/exceptions'
7
+ require 'repofetch/plugin'
8
+ require 'repofetch/util'
7
9
  require 'sawyer'
8
10
 
9
11
  class Repofetch
10
12
  # Adds support for Bitbucket repositories.
11
13
  class BitbucketCloud < Repofetch::Plugin
12
14
  include ActionView::Helpers::NumberHelper
15
+ include Repofetch::BitbucketCloud::Stats
16
+ include Repofetch::Util
17
+ extend Repofetch::Util
13
18
 
14
19
  HTTP_REMOTE_REGEX = %r{https?://bitbucket\.org/(?<owner>[\w._-]+)/(?<repo>[\w._-]+)}.freeze
15
20
  SSH_REMOTE_REGEX = %r{git@bitbucket\.org:(?<owner>[\w._-]+)/(?<repo>[\w._-]+)}.freeze
@@ -24,13 +29,15 @@ class Repofetch
24
29
  end
25
30
 
26
31
  def header
27
- "#{repo_data['owner']['display_name']}/#{repo_data['name']} @ Bitbucket"
32
+ ["#{repo_data['owner']['display_name']}/#{repo_data['name']}", 'Bitbucket']
28
33
  end
29
34
 
30
- def stats
31
- stats = [http_clone_url, ssh_clone_url, watchers, forks, created, updated, size, issues, pull_requests]
35
+ def primary_color
36
+ :blue
37
+ end
32
38
 
33
- stats.each { |stat| %i[bold blue].each { |style| stat.style_label!(style) } }
39
+ def stats
40
+ [http_clone_url, ssh_clone_url, watchers, forks, created, updated, size, issues, pull_requests]
34
41
  end
35
42
 
36
43
  def ascii
@@ -49,8 +56,7 @@ class Repofetch
49
56
 
50
57
  # Detects that the repository is a Bitbucket repository.
51
58
  def self.matches_repo?(git)
52
- default_remote = Repofetch.default_remote(git)
53
- matches_remote?(default_remote&.url)
59
+ matches_remote?(default_remote_url(git))
54
60
  end
55
61
 
56
62
  # Detects that the remote URL is for a Bitbucket Cloud repository.
@@ -60,8 +66,7 @@ class Repofetch
60
66
 
61
67
  # Gets the owner and repository from a GitHub local repository.
62
68
  def self.repo_identifiers(git)
63
- default_remote = Repofetch.default_remote(git)
64
- remote_identifiers(default_remote&.url)
69
+ remote_identifiers(default_remote_url(git))
65
70
  end
66
71
 
67
72
  # Gets the owner and repository from a GitHub remote URL.
@@ -98,60 +103,6 @@ class Repofetch
98
103
 
99
104
  new(args[0])
100
105
  end
101
-
102
- protected
103
-
104
- def repo_data
105
- @repo_data ||= agent.call(:get, "repositories/#{@repo_identifier}").data
106
- end
107
-
108
- def clone_urls
109
- @clone_urls ||= repo_data['links']['clone'].to_h { |clone| [clone['name'].to_sym, clone['href']] }
110
- end
111
-
112
- def http_clone_url
113
- Repofetch::Stat.new('HTTP(S)', clone_urls[:https], emoji: '🌐')
114
- end
115
-
116
- def ssh_clone_url
117
- Repofetch::Stat.new('SSH', clone_urls[:ssh], emoji: '🔑')
118
- end
119
-
120
- def watchers
121
- @watcher_data ||= agent.call(:get, "repositories/#{@repo_identifier}/watchers").data
122
- Repofetch::Stat.new('subscribers', @watcher_data['size'], emoji: '👀')
123
- end
124
-
125
- def forks
126
- @fork_data ||= agent.call(:get, "repositories/#{@repo_identifier}/forks").data
127
- Repofetch::Stat.new('forks', @fork_data['size'], emoji: '🔱')
128
- end
129
-
130
- def created
131
- Repofetch::TimespanStat.new('created', repo_data['created_on'], emoji: '🐣')
132
- end
133
-
134
- def updated
135
- Repofetch::TimespanStat.new('updated', repo_data['updated_on'], emoji: '📤')
136
- end
137
-
138
- def size
139
- # NOTE: Size is in bytes
140
- # TODO: Move this somewhere else instead of using a copy-paste
141
- byte_size = number_to_human_size(repo_data['size'] || 0, precision: 2, significant: false,
142
- strip_insignificant_zeros: false)
143
- Repofetch::Stat.new('size', byte_size, emoji: '💽')
144
- end
145
-
146
- def issues
147
- @issue_data ||= agent.call(:get, "repositories/#{@repo_identifier}/issues").data
148
- Repofetch::Stat.new('issues', @issue_data['size'], emoji: '❗')
149
- end
150
-
151
- def pull_requests
152
- @pull_request_data ||= agent.call(:get, "repositories/#{@repo_identifier}/pullrequests").data
153
- Repofetch::Stat.new('pull requests', @pull_request_data['size'], emoji: '🔀')
154
- end
155
106
  end
156
107
  end
157
108
 
data/lib/repofetch/cli.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'git'
4
3
  require 'optparse'
5
4
  require 'repofetch'
6
5
  require 'repofetch/config'
@@ -56,8 +55,7 @@ class Repofetch
56
55
  def new_plugin
57
56
  return @plugin.from_args(@args) unless @plugin.nil?
58
57
 
59
- git = Git.open(@repository_path)
60
- Repofetch.get_plugin(git, @args)
58
+ Repofetch.get_plugin(@repository_path, @args)
61
59
  end
62
60
 
63
61
  private
@@ -3,13 +3,16 @@
3
3
  require 'action_view'
4
4
  require 'octokit'
5
5
  require 'optparse'
6
- require 'repofetch'
7
6
  require 'repofetch/exceptions'
7
+ require 'repofetch/plugin'
8
+ require 'repofetch/util'
8
9
 
9
10
  class Repofetch
10
11
  # Adds support for GitHub repositories.
11
12
  class Github < Repofetch::Plugin
12
13
  include ActionView::Helpers::NumberHelper
14
+ include Repofetch::Util
15
+ extend Repofetch::Util
13
16
 
14
17
  HTTP_REMOTE_REGEX = %r{https?://github\.com/(?<owner>[\w.-]+)/(?<repository>[\w.-]+)}.freeze
15
18
  SSH_REMOTE_REGEX = %r{git@github\.com:(?<owner>[\w.-]+)/(?<repository>[\w.-]+)}.freeze
@@ -31,15 +34,12 @@ class Repofetch
31
34
  end
32
35
 
33
36
  def stats
34
- stats = [http_clone_url, ssh_clone_url, stargazers, subscribers, forks, created, updated, size, issues,
35
- pull_requests]
36
- stats.each { |stat| stat.style_label!(:bold) }
37
+ [http_clone_url, ssh_clone_url, stargazers, subscribers, forks, created, updated, size, issues, pull_requests]
37
38
  end
38
39
 
39
40
  # Detects that the repository is a GitHub repository.
40
41
  def self.matches_repo?(git)
41
- default_remote = Repofetch.default_remote(git)
42
- matches_remote?(default_remote&.url)
42
+ matches_remote?(default_remote_url(git))
43
43
  end
44
44
 
45
45
  # Detects that the remote URL is for a GitHub repository.
@@ -49,8 +49,7 @@ class Repofetch
49
49
 
50
50
  # Gets the owner and repository from a GitHub local repository.
51
51
  def self.repo_identifiers(git)
52
- default_remote = Repofetch.default_remote(git)
53
- remote_identifiers(default_remote&.url)
52
+ remote_identifiers(default_remote_url(git))
54
53
  end
55
54
 
56
55
  # Gets the owner and repository from a GitHub remote URL.
@@ -93,7 +92,7 @@ class Repofetch
93
92
  end
94
93
 
95
94
  def header
96
- "#{theme.format(:bold, "#{owner}/#{repository}")} @ #{theme.format(:bold, 'GitHub')}"
95
+ ["#{owner}/#{repository}", 'GitHub']
97
96
  end
98
97
 
99
98
  def ascii
@@ -1,13 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'cgi'
4
- require 'repofetch'
5
4
  require 'repofetch/exceptions'
5
+ require 'repofetch/plugin'
6
+ require 'repofetch/util'
6
7
  require 'sawyer'
7
8
 
8
9
  class Repofetch
9
10
  # Adds support for GitLab repositories.
10
11
  class Gitlab < Repofetch::Plugin
12
+ include Repofetch::Util
13
+ extend Repofetch::Util
14
+
11
15
  HTTP_REMOTE_REGEX = %r{https?://gitlab\.com/(?<path>[\w.-][\w.\-/]+)}.freeze
12
16
  SSH_REMOTE_REGEX = %r{git@gitlab\.com:(?<path>[\w.-][\w.\-/]+)}.freeze
13
17
  ASCII = File.read(File.expand_path('gitlab/ASCII', __dir__))
@@ -22,11 +26,11 @@ class Repofetch
22
26
  end
23
27
 
24
28
  def header
25
- "#{header_format(repo_data['name_with_namespace'])} @ #{header_format('GitLab')}"
29
+ [repo_data['name_with_namespace'], 'GitLab']
26
30
  end
27
31
 
28
- def header_format(text)
29
- theme.format(:bold, theme.format(:red, text))
32
+ def primary_color
33
+ :red
30
34
  end
31
35
 
32
36
  def stats
@@ -35,7 +39,7 @@ class Repofetch
35
39
  # NOTE: Stats that require authentication
36
40
  stats << open_issues unless token.nil?
37
41
 
38
- stats.each { |stat| %i[bold red].each { |style| stat.style_label!(style) } }
42
+ stats
39
43
  end
40
44
 
41
45
  def ascii
@@ -87,9 +91,7 @@ class Repofetch
87
91
 
88
92
  # Gets the path (+owner/subproject/repo+) of the repository.
89
93
  def self.repo_identifier(git)
90
- default_remote = Repofetch.default_remote(git)
91
- url = default_remote&.url
92
- remote_identifier(url)
94
+ remote_identifier(default_remote_url(git))
93
95
  end
94
96
 
95
97
  # Gets the path (+owner/subproject/repo+) of the repository.
@@ -105,9 +107,7 @@ class Repofetch
105
107
 
106
108
  # Detects that the repository is a GitHub repository.
107
109
  def self.matches_repo?(git)
108
- default_remote = Repofetch.default_remote(git)
109
- url = default_remote&.url
110
- matches_remote?(url)
110
+ matches_remote?(default_remote_url(git))
111
111
  end
112
112
 
113
113
  # Detects that the remote URL is for a GitHub repository.
@@ -0,0 +1,180 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'repofetch'
4
+ require 'repofetch/util'
5
+ require 'repofetch/stat'
6
+
7
+ class Repofetch
8
+ # @abstract Subclass to create a plugin.
9
+ class Plugin
10
+ include Repofetch::Util
11
+
12
+ # Plugin intializer arguments should come from the +from_git+ or +from_args+
13
+ # class methods.
14
+ def initialize(*) end
15
+
16
+ # Registers this plugin class for repofetch.
17
+ def self.register
18
+ Repofetch.register_plugin(self)
19
+ end
20
+
21
+ # Tries to replace another plugin. An example use case might be if this plugin
22
+ # extends another registered plugin.
23
+ #
24
+ # @param [Plugin] old The plugin to replace
25
+ def self.replace_or_register(old)
26
+ Repofetch.replace_or_register_plugin(old, self)
27
+ end
28
+
29
+ # @abstract Detects that this plugin should be used. Should be overridden by subclasses.
30
+ #
31
+ # An example implementation is checking if +Repofetch.default_remote_url+ matches
32
+ # a regular expression.
33
+ #
34
+ # @param [Git::Base] _git The Git repository object
35
+ def self.matches_repo?(_git)
36
+ false
37
+ end
38
+
39
+ # @abstract Detects that this plugin should be used. Should be overridden by subclasses.
40
+ #
41
+ # This is intended to be more generic than +matches_repo?+, and support any path.
42
+ #
43
+ # An example implementation is checking if an expected file exists in this path.
44
+ # @param [String] _path The path to check
45
+ def self.matches_path?(_path)
46
+ false
47
+ end
48
+
49
+ # @abstract This should use a git instance and call +Plugin.new+.
50
+ #
51
+ # @param [Git::Base] _git The Git repository object to use when calling +Plugin.new+.
52
+ # @param [Array] _args The arguments to process.
53
+ #
54
+ # @return [Plugin]
55
+ def self.from_git(_git, _args)
56
+ raise NoMethodError, 'from_git must be overridden by the plugin subclass'
57
+ end
58
+
59
+ # @abstract This should use a path and call +Plugin.new+.
60
+ #
61
+ # @param [String] _path The path to use when calling +Plugin.new+.
62
+ # @param [Array] _args The arguments to process.
63
+ #
64
+ # @return [Plugin]
65
+ def self.from_path(_path, _args)
66
+ raise NoMethodError, 'from_path must be overridden by the plugin subclass'
67
+ end
68
+
69
+ # @abstract This will receive an array of strings (e.g. +ARGV+) and call +Plugin.new+.
70
+ #
71
+ # @param [Array] _args The arguments to process.
72
+ #
73
+ # @return [Plugin]
74
+ def self.from_args(_args)
75
+ raise NoMethodError, 'from_args must be overridden by the plugin subclass'
76
+ end
77
+
78
+ # Gets the plugin's theme. Override to use a theme besides the default.
79
+ def theme
80
+ Repofetch::DEFAULT_THEME
81
+ end
82
+
83
+ # @abstract The ASCII to be printed alongside the stats.
84
+ #
85
+ # This should be overridden by the plugin subclass.
86
+ # Should be within the bounds 40x20 (width x height).
87
+ def ascii
88
+ raise NoMethodError, 'ascii must be overridden by the plugin subclass'
89
+ end
90
+
91
+ # @abstract The header to show for the plugin.
92
+ #
93
+ # This should be overridden by the plugin subclass.
94
+ #
95
+ # If an array is returned, it will be joined by +header_joiner+.
96
+ #
97
+ # @return [String, Array<String>]
98
+ def header
99
+ raise NoMethodError, 'header must be overridden by the plugin subclass'
100
+ end
101
+
102
+ # A string to join header text.
103
+ #
104
+ # Override to use a different string.
105
+ # @return [String]
106
+ def header_joiner
107
+ ' @ '
108
+ end
109
+
110
+ # Creates the separator that appears underneath the header
111
+ def separator
112
+ return '-' * header.length unless header.is_a?(Array)
113
+
114
+ header_length = header.map(&:length).sum + ((header.length - 1) * header_joiner.length)
115
+
116
+ '-' * header_length
117
+ end
118
+
119
+ def to_s
120
+ zipped_lines.map do |ascii_line, stat_line|
121
+ cleaned_ascii = remove_format_params(ascii_line)
122
+ styled_ascii = (ascii_line % theme.to_h) + theme.style(:reset)
123
+ aligned_stat_line = "#{' ' * (MAX_ASCII_WIDTH + 5)}#{stat_line}"
124
+ "#{styled_ascii}#{aligned_stat_line.slice(cleaned_ascii.length..)}\n"
125
+ end.join
126
+ end
127
+
128
+ # @abstract An array of stats that will be displayed to the right of the ASCII art.
129
+ #
130
+ # @return [Array<Stat>]
131
+ def stats
132
+ []
133
+ end
134
+
135
+ # The primary color to use for the header and stats.
136
+ #
137
+ # Override to use a different color from the theme.
138
+ # @see Repofetch::Theme
139
+ #
140
+ # @return [Symbol]
141
+ def primary_color
142
+ :default
143
+ end
144
+
145
+ # Returns the header, formatted with the primary color and bold, and joined with the header joiner.
146
+ #
147
+ # @return [String]
148
+ def formatted_header
149
+ (header.is_a?(Array) ? header : [header]).map { |h| apply_styles(h, :bold, primary_color) }.join(header_joiner)
150
+ end
151
+
152
+ # Makes an array of stat lines, including the header and separator.
153
+ def stat_lines
154
+ styled_stats = stats.map do |stat|
155
+ next stat unless stat.is_a?(Repofetch::Stat)
156
+
157
+ stat.style_label(:bold, primary_color).format(theme)
158
+ end
159
+ [formatted_header, separator, *styled_stats]
160
+ end
161
+
162
+ # Zips ASCII lines with stat lines.
163
+ #
164
+ # If there are more of one than the other, than the zip will be padded with empty strings.
165
+ def zipped_lines
166
+ ascii_lines = ascii.lines.map(&:chomp)
167
+ if ascii_lines.length > stat_lines.length
168
+ ascii_lines.zip(stat_lines)
169
+ else
170
+ stat_lines.zip(ascii_lines).map(&:reverse)
171
+ end.map { |ascii, stat| [ascii.to_s, stat.to_s] }
172
+ end
173
+
174
+ private
175
+
176
+ def apply_styles(str, *styles)
177
+ styles.inject(str) { |style, s| theme.format(s, style) }
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Repofetch
4
+ # Base class for stats.
5
+ class Stat
6
+ attr_reader :label, :value, :emoji
7
+ attr_writer :theme
8
+
9
+ # Creates a stat
10
+ #
11
+ # @param [String] label The label of the stat
12
+ # @param value The value of the stat
13
+ # @param [String] emoji An optional emoji for the stat
14
+ def initialize(label, value, emoji: nil)
15
+ @label = label
16
+ @value = value
17
+ @emoji = emoji
18
+ @label_styles = []
19
+ end
20
+
21
+ def format(theme = nil)
22
+ return to_s if theme.nil?
23
+
24
+ emoji = @emoji
25
+ emoji = nil unless Repofetch.config.nil? || Repofetch.config.emojis?
26
+ styled_label = @label_styles.inject(@label) { |label, style| theme.format(style, label) }
27
+ "#{emoji}#{styled_label}: #{format_value}"
28
+ end
29
+
30
+ # Formats the value of the stat
31
+ #
32
+ # Simply calls +to_s+, but can be overridden by subclasses.
33
+ def format_value
34
+ @value.to_s
35
+ end
36
+
37
+ def to_s
38
+ emoji = @emoji
39
+ emoji = nil unless Repofetch.config.nil? || Repofetch.config.emojis?
40
+ "#{emoji}#{@label}: #{@value}"
41
+ end
42
+
43
+ # Adds one or more styles for the label
44
+ #
45
+ # @param [Symbol] style The theme's style to add
46
+ # @param [Symbol] styles Additional styles to add
47
+ def style_label!(style, *styles)
48
+ @label_styles << style
49
+ @label_styles.concat(styles)
50
+ end
51
+
52
+ # Adds one or more styles for the label, returning a new stat
53
+ #
54
+ # @param [Symbol] style The theme's style to add
55
+ # @return [Stat] A new stat with the style added
56
+ def style_label(style, *styles)
57
+ dup.tap { |stat| stat.style_label!(style, *styles) }
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'repofetch/stat'
4
+
5
+ class Repofetch
6
+ # Timespan stat for "x units ago" stats.
7
+ class TimespanStat < Stat
8
+ include ActionView::Helpers::DateHelper
9
+
10
+ # Formats the value as "x units ago".
11
+ def format_value(now = nil)
12
+ now = Time.now if now.nil?
13
+ "#{distance_of_time_in_words(@value, now)} ago"
14
+ end
15
+ end
16
+ end
@@ -2,10 +2,41 @@
2
2
 
3
3
  class Repofetch
4
4
  # Provides uncategorized utilities.
5
- class Util
6
- # Cleans a string with style parameters (e.g. +"%{green}OK"+ -> +"OK"+)
7
- def self.clean_s(str)
5
+ module Util
6
+ # Converts a format string into a plain string (e.g. +"%{green}OK"+ -> +"OK"+)
7
+ def remove_format_params(str)
8
8
  str.gsub(/%{[\w\d]+?}/, '')
9
9
  end
10
+
11
+ # Removes ANSI escape sequences from +str+.
12
+ def clean_ansi(str)
13
+ str.gsub("\e", '').gsub(/\[\d+(;\d+)*m/, '')
14
+ end
15
+
16
+ # Gets the name of the default remote to use.
17
+ #
18
+ # Will try to pick "origin", but if that is not found then it will
19
+ # pick the first one found, or nil if there aren't any available.
20
+ #
21
+ # @param [Git::Base] git The repository instance.
22
+ #
23
+ # @return [Git::Remote]
24
+ def default_remote(git)
25
+ remotes = git.remotes
26
+ found_remote = remotes.find { |remote| remote.name == 'origin' }
27
+ found_remote = remotes[0] if found_remote.nil?
28
+ found_remote
29
+ end
30
+
31
+ # Just wrapper around +default_remote+ since this is likely the most common
32
+ # use case (and it's easier than referencing the +Git::Remote+ docs to ensure
33
+ # correct usage in each plugin).
34
+ #
35
+ # @param [Git::Base] git The repository instance.
36
+ #
37
+ # @return [String]
38
+ def default_remote_url(git)
39
+ default_remote(git)&.url
40
+ end
10
41
  end
11
42
  end
data/lib/repofetch.rb CHANGED
@@ -5,6 +5,9 @@ require 'git'
5
5
  require 'repofetch/config'
6
6
  require 'repofetch/env'
7
7
  require 'repofetch/exceptions'
8
+ require 'repofetch/plugin'
9
+ require 'repofetch/stat'
10
+ require 'repofetch/timespan_stat'
8
11
  require 'repofetch/theme'
9
12
  require 'repofetch/util'
10
13
 
@@ -56,230 +59,75 @@ class Repofetch
56
59
  # Raises a +Repofetch::NoPluginsError+ if no plugins are found.
57
60
  # Raises a +Repofetch::TooManyPluginsError+ if more than one plugin is found.
58
61
  #
59
- # @param [Git::Base] git A repository instance.
62
+ # @param [String] path The path to check.
60
63
  # @param [Array<String>] args The arguments passed to the program.
61
64
  #
62
65
  # @raise [NoPluginsError] If no plugins were selected.
63
66
  # @raise [TooManyPluginsError] If more than one plugin was selected.
64
67
  #
65
68
  # @return [Plugin] A plugin to use.
66
- def self.get_plugin(git, args)
67
- available_plugins = @plugins.filter do |plugin_class|
68
- plugin_class.matches_repo?(git)
69
- rescue NoMethodError
70
- warn "#{plugin_class} Does not implement +matches_repo?+"
71
- false
72
- end
73
- raise NoPluginsError if available_plugins.empty?
69
+ def self.get_plugin(path, args)
70
+ path_plugin = get_plugin_for_path(path)
71
+ repo_plugin = get_plugin_for_repo(path)
72
+
73
+ raise TooManyPluginsError if path_plugin && repo_plugin
74
+
75
+ raise NoPluginsError if path_plugin.nil? && repo_plugin.nil?
74
76
 
75
- raise TooManyPluginsError if available_plugins.length > 1
77
+ return path_plugin.from_path(path, args) unless path_plugin.nil?
76
78
 
77
- available_plugins[0].from_git(git, args)
79
+ git = Git.open(path)
80
+ repo_plugin.from_git(git, args)
78
81
  end
79
82
 
80
- # Gets the name of the default remote to use.
83
+ # Gets the plugins that matches the given path.
81
84
  #
82
- # Will try to pick "origin", but if that is not found then it will
83
- # pick the first one found, or nil if there aren't any available.
85
+ # @param [String] path The path to check.
84
86
  #
85
- # @param [Git::Base] git The repository instance.
86
- #
87
- # @return [Git::Remote]
88
- def self.default_remote(git)
89
- remotes = git.remotes
90
- found_remote = remotes.find { |remote| remote.name == 'origin' }
91
- found_remote = remotes[0] if found_remote.nil?
92
- found_remote
87
+ # @return [Array<Plugin>] The plugins that match the path.
88
+ def self.get_plugins_for_path(path)
89
+ @plugins.filter { |plugin_class| plugin_class.matches_path?(path) }
93
90
  end
94
91
 
95
- # Just wrapper around +default_remote+ since this is likely the most common
96
- # use case (and it's easier than referencing the +Git::Remote+ docs to ensure
97
- # correct usage in each plugin).
92
+ # Gets a single plugin that matches the given path.
98
93
  #
99
- # @param [String] path The path to the repository.
94
+ # @param [String] path The path to check.
100
95
  #
101
- # @return [String]
102
- def self.default_remote_url(path)
103
- default_remote(path)&.url
104
- end
105
-
106
- # @abstract Subclass to create a plugin.
107
- class Plugin
108
- # Plugin intializer arguments should come from the +from_git+ or +from_args+
109
- # class methods.
110
- def initialize(*) end
111
-
112
- # Registers this plugin class for repofetch.
113
- def self.register
114
- Repofetch.register_plugin(self)
115
- end
116
-
117
- # Tries to replace another plugin. An example use case might be if this plugin
118
- # extends another registered plugin.
119
- #
120
- # @param [Plugin] old The plugin to replace
121
- def self.replace_or_register(old)
122
- Repofetch.replace_or_register_plugin(old, self)
123
- end
124
-
125
- # @abstract Detects that this plugin should be used. Should be overridden by subclasses.
126
- #
127
- # An example implementation is checking if +Repofetch.default_remote_url+ matches
128
- # a regular expression.
129
- #
130
- # @param [Git::Base] _git The Git repository object
131
- def self.matches_repo?(_git)
132
- raise NoMethodError, 'matches_repo? must be overridden by the plugin subclass'
133
- end
96
+ # @return [Plugin, nil] The plugin that matches the path.
97
+ def self.get_plugin_for_path(path)
98
+ plugins = get_plugins_for_path(path)
134
99
 
135
- # @abstract This should use a git instance and call +Plugin.new+.
136
- #
137
- # @param [Git::Base] _git The Git repository object to use when calling +Plugin.new+.
138
- # @param [Array] _args The arguments to process.
139
- #
140
- # @return [Plugin]
141
- def self.from_git(_git, _args)
142
- raise NoMethodError, 'from_git must be overridden by the plugin subclass'
143
- end
144
-
145
- # @abstract This will receive an array of strings (e.g. +ARGV+) and call +Plugin.new+.
146
- #
147
- # @param [Array] _args The arguments to process.
148
- #
149
- # @return [Plugin]
150
- def self.from_args(_args)
151
- raise NoMethodError, 'from_args must be overridden by the plugin subclass'
152
- end
153
-
154
- # Gets the plugin's theme. Override to use a theme besides the default.
155
- def theme
156
- Repofetch::DEFAULT_THEME
157
- end
158
-
159
- # @abstract The ASCII to be printed alongside the stats.
160
- #
161
- # This should be overridden by the plugin subclass.
162
- # Should be within the bounds 40x20 (width x height).
163
- def ascii
164
- raise NoMethodError, 'ascii must be overridden by the plugin subclass'
165
- end
166
-
167
- # @abstract The header to show for the plugin.
168
- #
169
- # This should be overridden by the plugin subclass.
170
- # For example, "foo/bar @ GitHub".
171
- def header
172
- raise NoMethodError, 'header must be overridden by the plugin subclass'
173
- end
100
+ raise TooManyPluginsError if plugins.length > 1
174
101
 
175
- # Creates the separator that appears underneath the header
176
- def separator
177
- '-' * Repofetch::Util.clean_s(header).length
178
- end
179
-
180
- def to_s
181
- zipped_lines.map do |ascii_line, stat_line|
182
- cleaned_ascii = Repofetch::Util.clean_s(ascii_line)
183
- styled_ascii = (ascii_line % theme.to_h) + theme.style(:reset)
184
- aligned_stat_line = "#{' ' * (MAX_ASCII_WIDTH + 5)}#{stat_line}"
185
- "#{styled_ascii}#{aligned_stat_line.slice(cleaned_ascii.length..)}\n"
186
- end.join
187
- end
188
-
189
- # @abstract An array of stats that will be displayed to the right of the ASCII art.
190
- #
191
- # @return [Array<Stat>]
192
- def stats
193
- []
194
- end
195
-
196
- # Adds +theme+ to the stats, mutating those stats.
197
- #
198
- # @return [Array<Stat>]
199
- def theme_stats!
200
- stats.each do |stat|
201
- stat.theme = theme if stat.respond_to?(:theme=)
202
- end
203
- end
204
-
205
- # Makes an array of stat lines, including the header and separator.
206
- #
207
- # Mutates +stats+ to add the +theme+.
208
- def stat_lines!
209
- [header, separator, *theme_stats!.map(&:to_s)]
210
- end
211
-
212
- # Zips ASCII lines with stat lines.
213
- #
214
- # If there are more of one than the other, than the zip will be padded with empty strings.
215
- def zipped_lines
216
- ascii_lines = ascii.lines.map(&:chomp)
217
- stat_lines = stat_lines!
218
- if ascii_lines.length > stat_lines.length
219
- ascii_lines.zip(stat_lines)
220
- else
221
- stat_lines.zip(ascii_lines).map(&:reverse)
222
- end.map { |ascii, stat| [ascii.to_s, stat.to_s] }
223
- end
102
+ plugins[0]
224
103
  end
225
104
 
226
- # Base class for stats.
227
- class Stat
228
- attr_reader :label, :value, :emoji
229
- attr_writer :theme
230
-
231
- # Creates a stat
232
- #
233
- # @param [String] label The label of the stat
234
- # @param value The value of the stat
235
- # @param [String] emoji An optional emoji for the stat
236
- def initialize(label, value, emoji: nil)
237
- @label = label
238
- @value = value
239
- @emoji = emoji
240
- @label_styles = []
241
- end
242
-
243
- def to_s
244
- emoji = @emoji
245
- emoji = nil unless Repofetch.config.nil? || Repofetch.config.emojis?
246
- "#{emoji}#{format_label}: #{format_value}"
247
- end
248
-
249
- # Adds a style for the label
250
- #
251
- # @param [Symbol] style The theme's style to add
252
- def style_label!(style)
253
- @label_styles << style
254
- end
255
-
256
- # Formats the label, including styles.
257
- #
258
- # @return [String]
259
- def format_label
260
- return @label if @theme.nil?
261
-
262
- @label_styles.inject(@label) { |label, style| @theme.format(style, label) }
105
+ # Gets the plugins that matches the given repository.
106
+ #
107
+ # @param [String] path The repository to check.
108
+ #
109
+ # @return [Array<Plugin>] The plugins that match the repository.
110
+ def self.get_plugins_for_repo(path)
111
+ begin
112
+ git = Git.open(path)
113
+ rescue ArgumentError
114
+ return []
263
115
  end
264
116
 
265
- # Formats the value
266
- #
267
- # This simply converts the value to a string, but can be overridden but
268
- # subclasses to affect +to_s+.
269
- def format_value
270
- @value.to_s
271
- end
117
+ @plugins.filter { |plugin_class| plugin_class.matches_repo?(git) }
272
118
  end
273
119
 
274
- # Timespan stat for "x units ago" stats.
275
- class TimespanStat < Stat
276
- include ActionView::Helpers::DateHelper
120
+ # Gets a single plugin that matches the given repository.
121
+ #
122
+ # @param [String] path The repository to check.
123
+ #
124
+ # @return [Plugin, nil] The plugin that matches the repository.
125
+ def self.get_plugin_for_repo(path)
126
+ plugins = get_plugins_for_repo(path)
277
127
 
278
- # Formats the value as "x units ago".
279
- def format_value(now = nil)
280
- now = Time.now if now.nil?
281
- "#{distance_of_time_in_words(@value, now)} ago"
282
- end
128
+ raise TooManyPluginsError if plugins.length > 1
129
+
130
+ plugins[0]
283
131
  end
284
132
 
285
133
  def self.clear_plugins
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: repofetch
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.4
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Spenser Black
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-01-13 00:00:00.000000000 Z
11
+ date: 2023-01-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionview
@@ -298,6 +298,7 @@ files:
298
298
  - lib/repofetch/DEFAULT_CONFIG
299
299
  - lib/repofetch/bitbucketcloud.rb
300
300
  - lib/repofetch/bitbucketcloud/ASCII
301
+ - lib/repofetch/bitbucketcloud/stats.rb
301
302
  - lib/repofetch/cli.rb
302
303
  - lib/repofetch/config.rb
303
304
  - lib/repofetch/env.rb
@@ -306,7 +307,10 @@ files:
306
307
  - lib/repofetch/github/ASCII
307
308
  - lib/repofetch/gitlab.rb
308
309
  - lib/repofetch/gitlab/ASCII
310
+ - lib/repofetch/plugin.rb
311
+ - lib/repofetch/stat.rb
309
312
  - lib/repofetch/theme.rb
313
+ - lib/repofetch/timespan_stat.rb
310
314
  - lib/repofetch/util.rb
311
315
  homepage: https://github.com/spenserblack/repofetch
312
316
  licenses: