repofetch 0.4.4 → 0.5.0

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