nya_pr 0.1.0 → 0.2.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: 74fef078ebf20c6a46a8596b5001a66b744436a33ee407d14168699e577fa7e1
4
- data.tar.gz: 3e686721cb075eead9c7cd7070cf49ee6d0b9eca7ce879b8555380c6d1a52626
3
+ metadata.gz: 72276bc9090fe6feb1925b4d56948ca6521d3d6ddf218b8bde737010d29ed923
4
+ data.tar.gz: b69b2671887a606a6cb09434f7eb242bb6b1c79075755d7492590a7adac8ce22
5
5
  SHA512:
6
- metadata.gz: 40aa1079ac60ac7d0de2d79135eeb75f7b1854e4299277a94ec424268d0fb59719be1f8832546fb6fd28abeac63786b4043a2128ea60749daa183839850658f3
7
- data.tar.gz: 0377b72b765df89b9da1ff33d92224431a1ebe16c10c70e457f4ebdb93381479d9c1a6e0122366075363f1d550ca1491647d9f0cd9b44347d9cca6304249806e
6
+ metadata.gz: 62623d0683a5c23143c219724f980194e2cf6e88d5b1b090c8d34d83494f2d4fb5340b1ee693d2c8464f7bfa8f13b925df714f52a6f0bdefe8b1f634bd8b2019
7
+ data.tar.gz: 02fba8805b5d75b461fad1580e4fb70b71f709c91bc8d10e48d6fe3bdc11203ae48c051a57a67b68689947ca1f0613d7dc36b44873fa988aceb9d22e57c72754
data/README.md CHANGED
@@ -1,11 +1,42 @@
1
1
  # NyaPr ฅ^•ﻌ•^ฅ
2
2
 
3
+ ![Gem Version](https://img.shields.io/gem/v/nya_pr)
4
+ ![CI](https://github.com/bodrovis/nya_pr/actions/workflows/ci.yml/badge.svg)
5
+
3
6
  **NyaPr** is a tiny Ruby tool for people who have too many GitHub repositories, too many organizations, too many pull requests, and absolutely no desire to click through all that manually.
4
7
 
5
8
  It collects repositories you own or contribute to, finds their open pull requests, and puts everything into one place.
6
9
 
7
10
  For maximum kawaii.
8
11
 
12
+ ## Quick start
13
+
14
+ Install the gem:
15
+
16
+ ```bash
17
+ gem install nya_pr
18
+ ```
19
+
20
+ Set your GitHub token:
21
+
22
+ ```bash
23
+ export GITHUB_TOKEN=your_token
24
+ ```
25
+
26
+ Run NyaPr:
27
+
28
+ ```bash
29
+ nya-pr --user your-github-username
30
+ ```
31
+
32
+ To include additional users or organizations:
33
+
34
+ ```bash
35
+ nya-pr --user your-github-username --owners org-one,org-two
36
+ ```
37
+
38
+ NyaPr will collect relevant repositories, find open pull requests, print them to the terminal, and save the results under `data/`.
39
+
9
40
  ## Why NyaPr?
10
41
 
11
42
  GitHub is perfectly happy to show you:
@@ -270,6 +301,7 @@ Logs are written to `stderr`, while actual pull request output goes to `stdout`.
270
301
  -l, --limit N Maximum number of pull requests to collect
271
302
  -r, --refresh Refresh repositories from GitHub
272
303
  --skip-archived Ignore archived repositories
304
+ --skip-drafts Ignore draft pull requests
273
305
  --[no-]progress Show or hide progress bars
274
306
  --log-level LEVEL Logging level
275
307
  -v, --version Show NyaPr version
@@ -3,14 +3,6 @@
3
3
  module NyaPr
4
4
  module Cli
5
5
  class Command < Dry::CLI::Command
6
- LOG_LEVELS = {
7
- 'debug' => Logger::DEBUG,
8
- 'info' => Logger::INFO,
9
- 'warn' => Logger::WARN,
10
- 'error' => Logger::ERROR,
11
- 'fatal' => Logger::FATAL
12
- }.freeze
13
-
14
6
  desc 'Collect open GitHub pull requests'
15
7
 
16
8
  option :username,
@@ -34,7 +26,7 @@ module NyaPr
34
26
 
35
27
  option :limit,
36
28
  aliases: ['-l'],
37
- default: PullRequest::Finder::MAX_PULL_REQUESTS,
29
+ default: PullRequest::Config::DEFAULT_LIMIT,
38
30
  desc: 'Maximum number of pull requests'
39
31
 
40
32
  option :refresh,
@@ -48,13 +40,18 @@ module NyaPr
48
40
  default: false,
49
41
  desc: 'Skip archived repositories'
50
42
 
43
+ option :skip_drafts,
44
+ type: :boolean,
45
+ default: false,
46
+ desc: 'Ignore draft pull requests'
47
+
51
48
  option :progress,
52
49
  type: :boolean,
53
50
  default: true,
54
51
  desc: 'Show progress bars'
55
52
 
56
53
  option :log_level,
57
- values: LOG_LEVELS.keys,
54
+ values: Config::LOG_LEVELS.keys,
58
55
  default: 'info',
59
56
  desc: 'Log level'
60
57
 
@@ -69,41 +66,7 @@ module NyaPr
69
66
  return
70
67
  end
71
68
 
72
- normalize_options!(options)
73
- validate_username!(options)
74
-
75
- Runner.new(options).run
76
- end
77
-
78
- private
79
-
80
- def normalize_options!(options)
81
- options[:owners] = normalize_owners(options[:owners])
82
- options[:limit] = normalize_limit(options[:limit])
83
- options[:log_level] = LOG_LEVELS.fetch(options[:log_level])
84
- end
85
-
86
- def normalize_owners(owners)
87
- Array(owners).
88
- flat_map { |owner| owner.split(',') }.
89
- map(&:strip).
90
- reject(&:empty?)
91
- end
92
-
93
- def normalize_limit(value)
94
- limit = Integer(value)
95
-
96
- return limit if limit.positive?
97
-
98
- raise NyaPr::Error, 'limit must be greater than 0'
99
- rescue ArgumentError, TypeError
100
- raise NyaPr::Error, 'limit must be a positive integer'
101
- end
102
-
103
- def validate_username!(options)
104
- return unless options[:username].to_s.empty?
105
-
106
- raise NyaPr::Error, 'GitHub username is required'
69
+ Runner.new(Config.from_options(options)).run
107
70
  end
108
71
  end
109
72
  end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NyaPr
4
+ class Config < Data.define(
5
+ :username,
6
+ :owners,
7
+ :token,
8
+ :repositories_csv,
9
+ :pull_requests_dir,
10
+ :limit,
11
+ :refresh,
12
+ :skip_archived,
13
+ :skip_drafts,
14
+ :progress,
15
+ :log_level
16
+ )
17
+ LOG_LEVELS = {
18
+ 'debug' => Logger::DEBUG,
19
+ 'info' => Logger::INFO,
20
+ 'warn' => Logger::WARN,
21
+ 'error' => Logger::ERROR,
22
+ 'fatal' => Logger::FATAL
23
+ }.freeze
24
+
25
+ class << self
26
+ def from_options(options)
27
+ username = options[:username].to_s
28
+ validate_username!(username)
29
+
30
+ new(
31
+ username: username,
32
+ owners: normalize_owners(options[:owners]),
33
+ token: options[:token],
34
+ repositories_csv: options[:repositories_csv],
35
+ pull_requests_dir: options[:pull_requests_dir],
36
+ limit: normalize_limit(options[:limit]),
37
+ refresh: options[:refresh],
38
+ skip_archived: options[:skip_archived],
39
+ skip_drafts: options[:skip_drafts],
40
+ progress: options[:progress],
41
+ log_level: LOG_LEVELS.fetch(options[:log_level])
42
+ )
43
+ end
44
+
45
+ private
46
+
47
+ def normalize_owners(owners)
48
+ Array(owners).
49
+ flat_map { |owner| owner.split(',') }.
50
+ map(&:strip).
51
+ reject(&:empty?)
52
+ end
53
+
54
+ def normalize_limit(value)
55
+ limit = Integer(value)
56
+
57
+ return limit if limit.positive?
58
+
59
+ raise NyaPr::Error, 'limit must be greater than 0'
60
+ rescue ArgumentError, TypeError
61
+ raise NyaPr::Error, 'limit must be a positive integer'
62
+ end
63
+
64
+ def validate_username!(username)
65
+ return unless username.empty?
66
+
67
+ raise NyaPr::Error, 'GitHub username is required'
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NyaPr
4
+ module PullRequest
5
+ class Config < Data.define(
6
+ :limit,
7
+ :skip_drafts,
8
+ :progress_enabled
9
+ )
10
+ DEFAULT_LIMIT = 100
11
+
12
+ def self.from_app(config)
13
+ new(
14
+ limit: config.limit,
15
+ skip_drafts: config.skip_drafts,
16
+ progress_enabled: config.progress
17
+ )
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NyaPr
4
+ module PullRequest
5
+ class Filter
6
+ attr_reader :config
7
+
8
+ def initialize(config)
9
+ @config = config
10
+ end
11
+
12
+ def match?(pull_request)
13
+ return false if config.skip_drafts && pull_request['draft']
14
+
15
+ true
16
+ end
17
+
18
+ def ===(pull_request)
19
+ match?(pull_request)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -5,29 +5,23 @@ module NyaPr
5
5
  class Finder
6
6
  include NyaPr::Request
7
7
 
8
- MAX_PULL_REQUESTS = 100
9
8
  MAX_PER_PAGE = 100
10
9
 
11
10
  attr_reader :client,
12
11
  :repositories,
13
- :limit,
14
- :progress_enabled
15
-
16
- def initialize(
17
- client,
18
- repositories,
19
- limit: MAX_PULL_REQUESTS,
20
- progress_enabled: true
21
- )
12
+ :config,
13
+ :filter
14
+
15
+ def initialize(client, repositories, config, filter: nil)
22
16
  @client = client
23
17
  @repositories = repositories
24
- @limit = limit
25
- @progress_enabled = progress_enabled
18
+ @config = config
19
+ @filter = filter || Filter.new(config)
26
20
  end
27
21
 
28
22
  def find
29
23
  NyaPr.logger.info(
30
- "Searching for up to #{limit} open pull requests " \
24
+ "Searching for up to #{config.limit} open pull requests " \
31
25
  "in #{repositories.size} repositories"
32
26
  )
33
27
 
@@ -49,55 +43,84 @@ module NyaPr
49
43
  pull_requests = []
50
44
 
51
45
  repositories.each do |repository|
52
- break if pull_requests.size >= limit
53
-
54
- begin
55
- next unless repository.fetch('has_pull_requests', true)
46
+ break if pull_requests.size >= config.limit
56
47
 
57
- remaining = limit - pull_requests.size
58
-
59
- pull_requests.concat(
60
- pull_requests_for(repository, remaining)
61
- )
62
- ensure
63
- progress_bar.advance
64
- end
48
+ process_repository(
49
+ repository,
50
+ pull_requests,
51
+ progress_bar
52
+ )
65
53
  end
66
54
 
67
55
  pull_requests
68
56
  end
69
57
 
58
+ def process_repository(repository, pull_requests, progress_bar)
59
+ return unless pull_requests_enabled?(repository)
60
+
61
+ remaining = config.limit - pull_requests.size
62
+
63
+ pull_requests.concat(
64
+ pull_requests_for(repository, remaining)
65
+ )
66
+ ensure
67
+ progress_bar.advance
68
+ end
69
+
70
+ def pull_requests_enabled?(repository)
71
+ repository.fetch('has_pull_requests', true)
72
+ end
73
+
70
74
  def pull_requests_for(repository, remaining)
71
75
  NyaPr.logger.debug(
72
76
  "Checking pull requests in #{repository.fetch('full_name')}"
73
77
  )
74
78
 
75
79
  pull_requests = []
80
+ per_page = [remaining, MAX_PER_PAGE].min
81
+
82
+ each_batch(repository, per_page) do |batch|
83
+ append_matches(
84
+ pull_requests,
85
+ repository,
86
+ batch,
87
+ remaining
88
+ )
89
+
90
+ break if pull_requests.size >= remaining
91
+ end
92
+
93
+ pull_requests
94
+ end
95
+
96
+ def each_batch(repository, per_page)
76
97
  page = 1
77
98
 
78
99
  loop do
79
- per_page = page_size(remaining, pull_requests.size)
80
100
  batch = get_batch(repository, per_page, page)
81
101
 
82
102
  break if batch.empty?
83
103
 
84
- pull_requests.concat(
85
- batch.map do |pull_request|
86
- normalize(repository, pull_request)
87
- end
88
- )
104
+ yield batch
89
105
 
90
106
  break if batch.size < per_page
91
- break if pull_requests.size >= remaining
92
107
 
93
108
  page += 1
94
109
  end
110
+ end
95
111
 
96
- pull_requests
112
+ def append_matches(pull_requests, repository, batch, limit)
113
+ needed = limit - pull_requests.size
114
+
115
+ pull_requests.concat(
116
+ filter_batch(repository, batch).first(needed)
117
+ )
97
118
  end
98
119
 
99
- def page_size(remaining, collected)
100
- [remaining - collected, MAX_PER_PAGE].min
120
+ def filter_batch(repository, batch)
121
+ batch.
122
+ map { |pull_request| normalize(repository, pull_request) }.
123
+ grep(filter)
101
124
  end
102
125
 
103
126
  def get_batch(repository, per_page, page)
@@ -115,7 +138,7 @@ module NyaPr
115
138
  Progress.new(
116
139
  '🐾 Checking pull requests',
117
140
  total: repositories.size,
118
- enabled: progress_enabled && $stderr.tty?
141
+ enabled: config.progress_enabled && $stderr.tty?
119
142
  )
120
143
  end
121
144
 
@@ -3,14 +3,12 @@
3
3
  module NyaPr
4
4
  module Repository
5
5
  class Collector
6
- attr_reader :client, :username, :owners, :store, :options
6
+ attr_reader :client, :store, :config
7
7
 
8
- def initialize(client, username, owners, store, **options)
8
+ def initialize(client, store, config)
9
9
  @client = client
10
- @username = username
11
- @owners = owners
12
10
  @store = store
13
- @options = options
11
+ @config = config
14
12
  end
15
13
 
16
14
  def collect
@@ -37,10 +35,12 @@ module NyaPr
37
35
  )
38
36
 
39
37
  repositories = Finder.
40
- new(client, username).
41
- repositories_for(owners)
38
+ new(client, config).
39
+ repositories_for
42
40
 
43
- repositories = filter_contributed(reject_archived(repositories))
41
+ repositories = filter_contributed(
42
+ reject_archived(repositories)
43
+ )
44
44
 
45
45
  store.write(repositories)
46
46
 
@@ -53,14 +53,14 @@ module NyaPr
53
53
 
54
54
  def filter_contributed(repositories)
55
55
  own, external = repositories.partition do |repository|
56
- repository_owner(repository).casecmp?(username)
56
+ repository_owner(repository).casecmp?(config.username)
57
57
  end
58
58
 
59
- own + Filter.new(client, username, progress_enabled: options[:progress]).contributed(external)
59
+ own + Filter.new(client, config).contributed(external)
60
60
  end
61
61
 
62
62
  def reject_archived(repositories)
63
- return repositories unless options[:skip_archived]
63
+ return repositories unless config.skip_archived
64
64
 
65
65
  filtered = repositories.reject do |repository|
66
66
  repository['archived']
@@ -78,7 +78,7 @@ module NyaPr
78
78
  end
79
79
 
80
80
  def use_cache?
81
- store.available? && !options[:refresh]
81
+ store.available? && !config.refresh
82
82
  end
83
83
  end
84
84
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NyaPr
4
+ module Repository
5
+ class Config < Data.define(
6
+ :username,
7
+ :owners,
8
+ :refresh,
9
+ :skip_archived,
10
+ :progress_enabled,
11
+ :workers
12
+ )
13
+ DEFAULT_WORKERS = 10
14
+
15
+ def self.from_app(config)
16
+ new(
17
+ username: config.username,
18
+ owners: ([config.username] + config.owners).uniq,
19
+ refresh: config.refresh,
20
+ skip_archived: config.skip_archived,
21
+ progress_enabled: config.progress,
22
+ workers: DEFAULT_WORKERS
23
+ )
24
+ end
25
+ end
26
+ end
27
+ end
@@ -5,15 +5,11 @@ module NyaPr
5
5
  class Filter
6
6
  include NyaPr::Request
7
7
 
8
- DEFAULT_WORKERS = 10
8
+ attr_reader :client, :config
9
9
 
10
- attr_reader :client, :username, :workers, :progress_enabled
11
-
12
- def initialize(client, username, workers: DEFAULT_WORKERS, progress_enabled: true)
10
+ def initialize(client, config)
13
11
  @client = client
14
- @username = username
15
- @workers = workers
16
- @progress_enabled = progress_enabled
12
+ @config = config
17
13
  end
18
14
 
19
15
  def contributed(repositories)
@@ -30,6 +26,18 @@ module NyaPr
30
26
 
31
27
  private
32
28
 
29
+ def username
30
+ config.username
31
+ end
32
+
33
+ def workers
34
+ config.workers
35
+ end
36
+
37
+ def progress_enabled
38
+ config.progress_enabled
39
+ end
40
+
33
41
  def log_start(repositories)
34
42
  NyaPr.logger.info(
35
43
  "Checking contributions in #{repositories.size} repositories " \
@@ -81,20 +89,23 @@ module NyaPr
81
89
  loop do
82
90
  index, repository = queue.pop(true)
83
91
 
84
- if contributed_to?(repository)
85
- result[index] = repository
86
-
87
- NyaPr.logger.debug(
88
- "Found contributions in #{repository.fetch('full_name')}"
89
- )
90
- end
91
-
92
+ process_repository(repository, index, result)
92
93
  progress_bar.advance
93
94
  rescue ThreadError
94
95
  break
95
96
  end
96
97
  end
97
98
 
99
+ def process_repository(repository, index, result)
100
+ return unless contributed_to?(repository)
101
+
102
+ result[index] = repository
103
+
104
+ NyaPr.logger.debug(
105
+ "Found contributions in #{repository.fetch('full_name')}"
106
+ )
107
+ end
108
+
98
109
  def contributed_to?(repository)
99
110
  commits = get(
100
111
  "/repos/#{repository.fetch('full_name')}/commits",
@@ -5,16 +5,14 @@ module NyaPr
5
5
  class Finder
6
6
  include NyaPr::Request
7
7
 
8
- attr_reader :client, :username
8
+ attr_reader :client, :config
9
9
 
10
- def initialize(client, username)
10
+ def initialize(client, config)
11
11
  @client = client
12
- @username = username
12
+ @config = config
13
13
  end
14
14
 
15
- def repositories_for(owners)
16
- owners = Array(owners)
17
-
15
+ def repositories_for
18
16
  NyaPr.logger.info(
19
17
  "Starting repository collection for #{owners.size} owners"
20
18
  )
@@ -32,6 +30,14 @@ module NyaPr
32
30
 
33
31
  private
34
32
 
33
+ def owners
34
+ config.owners
35
+ end
36
+
37
+ def username
38
+ config.username
39
+ end
40
+
35
41
  def collect_repositories_for(owner)
36
42
  NyaPr.logger.info("Collecting repositories for #{owner}")
37
43
 
data/lib/nya_pr/runner.rb CHANGED
@@ -4,10 +4,10 @@ require 'dotenv'
4
4
 
5
5
  module NyaPr
6
6
  class Runner
7
- attr_reader :options
7
+ attr_reader :config
8
8
 
9
- def initialize(options)
10
- @options = options
9
+ def initialize(config)
10
+ @config = config
11
11
  end
12
12
 
13
13
  def run
@@ -15,7 +15,6 @@ module NyaPr
15
15
  configure_logger
16
16
 
17
17
  pull_requests = find_pull_requests
18
-
19
18
  pull_request_printer.print(pull_requests)
20
19
  save_pull_requests(pull_requests)
21
20
  end
@@ -24,13 +23,13 @@ module NyaPr
24
23
 
25
24
  def paths
26
25
  @paths ||= Paths.new(
27
- repositories_path: options[:repositories_csv],
28
- pull_requests_dir: options[:pull_requests_dir]
26
+ repositories_path: config.repositories_csv,
27
+ pull_requests_dir: config.pull_requests_dir
29
28
  )
30
29
  end
31
30
 
32
31
  def configure_logger
33
- NyaPr.logger.level = options[:log_level]
32
+ NyaPr.logger.level = config.log_level
34
33
  end
35
34
 
36
35
  def target_repositories
@@ -40,15 +39,15 @@ module NyaPr
40
39
  def repository_collector
41
40
  @repository_collector ||= Repository::Collector.new(
42
41
  client,
43
- username,
44
- owners,
45
42
  repository_store,
46
- refresh: options[:refresh],
47
- skip_archived: options[:skip_archived],
48
- progress: options[:progress]
43
+ repository_config
49
44
  )
50
45
  end
51
46
 
47
+ def repository_config
48
+ @repository_config ||= Repository::Config.from_app(config)
49
+ end
50
+
52
51
  def save_pull_requests(pull_requests)
53
52
  pull_request_store.write(pull_requests)
54
53
 
@@ -62,12 +61,15 @@ module NyaPr
62
61
  new(
63
62
  client,
64
63
  target_repositories,
65
- limit: options[:limit],
66
- progress_enabled: options[:progress]
64
+ pull_request_config
67
65
  ).
68
66
  find
69
67
  end
70
68
 
69
+ def pull_request_config
70
+ @pull_request_config ||= PullRequest::Config.from_app(config)
71
+ end
72
+
71
73
  def repository_store
72
74
  @repository_store ||= Repository::CsvStore.new(paths.repositories_csv)
73
75
  end
@@ -85,17 +87,10 @@ module NyaPr
85
87
  end
86
88
 
87
89
  def token
88
- options[:token] || ENV.fetch('GITHUB_TOKEN') do
89
- raise NyaPr::Error, 'GitHub token is required (--token or GITHUB_TOKEN)'
90
+ config.token || ENV.fetch('GITHUB_TOKEN') do
91
+ raise NyaPr::Error,
92
+ 'GitHub token is required (--token or GITHUB_TOKEN)'
90
93
  end
91
94
  end
92
-
93
- def username
94
- options[:username]
95
- end
96
-
97
- def owners
98
- ([username] + options[:owners]).uniq
99
- end
100
95
  end
101
96
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module NyaPr
4
- VERSION = '0.1.0'
4
+ VERSION = '0.2.0'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nya_pr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Elijah S. Krukowski
@@ -109,14 +109,18 @@ files:
109
109
  - lib/nya_pr.rb
110
110
  - lib/nya_pr/cli/command.rb
111
111
  - lib/nya_pr/client.rb
112
+ - lib/nya_pr/config.rb
112
113
  - lib/nya_pr/connection.rb
113
114
  - lib/nya_pr/error.rb
114
115
  - lib/nya_pr/paths.rb
115
116
  - lib/nya_pr/progress.rb
117
+ - lib/nya_pr/pull_request/config.rb
116
118
  - lib/nya_pr/pull_request/csv_store.rb
119
+ - lib/nya_pr/pull_request/filter.rb
117
120
  - lib/nya_pr/pull_request/finder.rb
118
121
  - lib/nya_pr/pull_request/printer.rb
119
122
  - lib/nya_pr/repository/collector.rb
123
+ - lib/nya_pr/repository/config.rb
120
124
  - lib/nya_pr/repository/csv_store.rb
121
125
  - lib/nya_pr/repository/filter.rb
122
126
  - lib/nya_pr/repository/finder.rb