how_is 24.0.0 → 25.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github_changelog_generator +0 -1
- data/.rubocop.yml +37 -12
- data/.travis.yml +6 -3
- data/CHANGELOG.md +56 -0
- data/CONTRIBUTING.md +34 -0
- data/Gemfile +8 -4
- data/ISSUES.md +30 -54
- data/README.md +16 -91
- data/Rakefile +3 -31
- data/bin/prerelease-generate-changelog +1 -1
- data/bin/setup +0 -0
- data/build-debug.rb +20 -0
- data/exe/how_is +25 -22
- data/fixtures/vcr_cassettes/how-is-example-empty-repository.yml +334 -1
- data/fixtures/vcr_cassettes/how-is-example-repository.yml +350 -1
- data/fixtures/vcr_cassettes/how-is-from-config-frontmatter.yml +15234 -1
- data/fixtures/vcr_cassettes/how-is-how-is-travis-api-repos-builds.yml +2694 -1
- data/fixtures/vcr_cassettes/how-is-with-config-file.yml +15234 -1
- data/fixtures/vcr_cassettes/how_is_contributions_additions_count.yml +70 -1
- data/fixtures/vcr_cassettes/how_is_contributions_all_contributors.yml +70 -1
- data/fixtures/vcr_cassettes/how_is_contributions_changed_files.yml +70 -1
- data/fixtures/vcr_cassettes/how_is_contributions_changes.yml +70 -1
- data/fixtures/vcr_cassettes/how_is_contributions_commits.yml +70 -1
- data/fixtures/vcr_cassettes/how_is_contributions_compare_url.yml +70 -1
- data/fixtures/vcr_cassettes/how_is_contributions_default_branch.yml +70 -1
- data/fixtures/vcr_cassettes/how_is_contributions_deletions_count.yml +70 -1
- data/fixtures/vcr_cassettes/how_is_contributions_new_contributors.yml +70 -1
- data/fixtures/vcr_cassettes/how_is_contributions_summary.yml +70 -1
- data/fixtures/vcr_cassettes/how_is_contributions_summary_2.yml +70 -1
- data/how_is.gemspec +12 -6
- data/lib/how_is/cacheable.rb +71 -0
- data/lib/how_is/cli.rb +121 -124
- data/lib/how_is/config.rb +123 -0
- data/lib/how_is/constants.rb +9 -0
- data/lib/how_is/date_time_helpers.rb +48 -0
- data/lib/how_is/frontmatter.rb +14 -9
- data/lib/how_is/report.rb +86 -58
- data/lib/how_is/report_collection.rb +113 -0
- data/lib/how_is/sources/ci/appveyor.rb +88 -0
- data/lib/how_is/sources/ci/travis.rb +159 -0
- data/lib/how_is/sources/github/contributions.rb +169 -128
- data/lib/how_is/sources/github/issue_fetcher.rb +148 -0
- data/lib/how_is/sources/github/issues.rb +86 -235
- data/lib/how_is/sources/github/pulls.rb +19 -18
- data/lib/how_is/sources/github.rb +40 -18
- data/lib/how_is/sources/github_helpers.rb +8 -91
- data/lib/how_is/sources.rb +2 -0
- data/lib/how_is/template.rb +9 -0
- data/lib/how_is/templates/contributions_partial.html +1 -0
- data/lib/how_is/templates/{issues_or_pulls_partial.html_template → issues_or_pulls_partial.html} +0 -0
- data/lib/how_is/templates/new_contributors_partial.html +5 -0
- data/lib/how_is/templates/{report.html_template → report.html} +0 -8
- data/lib/how_is/templates/{report_partial.html_template → report_partial.html} +3 -3
- data/lib/how_is/text.rb +26 -0
- data/lib/how_is/version.rb +2 -1
- data/lib/how_is.rb +33 -60
- metadata +28 -47
- data/.hound.yml +0 -2
- data/.rubocop_todo.yml +0 -21
- data/lib/how_is/sources/travis.rb +0 -37
- data/roadmap.markdown +0 -82
@@ -709,4 +709,73 @@ http_interactions:
|
|
709
709
|
string: "[]"
|
710
710
|
http_version:
|
711
711
|
recorded_at: Sat, 06 Jan 2018 20:10:44 GMT
|
712
|
-
|
712
|
+
- request:
|
713
|
+
method: get
|
714
|
+
uri: https://api.github.com/repos/how-is/example_repository/commits?since=2017-08-01&until=2017-09-01
|
715
|
+
body:
|
716
|
+
encoding: US-ASCII
|
717
|
+
string: ''
|
718
|
+
headers:
|
719
|
+
Accept:
|
720
|
+
- application/vnd.github.v3+json,application/vnd.github.beta+json;q=0.5,application/json;q=0.1
|
721
|
+
Accept-Charset:
|
722
|
+
- utf-8
|
723
|
+
User-Agent:
|
724
|
+
- Github API Ruby Gem 0.18.2
|
725
|
+
Authorization:
|
726
|
+
- Basic ZHVja2luYXRvcjo5MTgyNzc3ZmYzYzAwNjc5NTE5M2E1NzBjZGFjMzI2YjY0NDU5ZGM5
|
727
|
+
Accept-Encoding:
|
728
|
+
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
|
729
|
+
response:
|
730
|
+
status:
|
731
|
+
code: 404
|
732
|
+
message: Not Found
|
733
|
+
headers:
|
734
|
+
Server:
|
735
|
+
- GitHub.com
|
736
|
+
Date:
|
737
|
+
- Thu, 17 Jan 2019 23:31:08 GMT
|
738
|
+
Content-Type:
|
739
|
+
- application/json; charset=utf-8
|
740
|
+
Transfer-Encoding:
|
741
|
+
- chunked
|
742
|
+
Status:
|
743
|
+
- 404 Not Found
|
744
|
+
X-Ratelimit-Limit:
|
745
|
+
- '5000'
|
746
|
+
X-Ratelimit-Remaining:
|
747
|
+
- '4998'
|
748
|
+
X-Ratelimit-Reset:
|
749
|
+
- '1547771467'
|
750
|
+
X-Oauth-Scopes:
|
751
|
+
- ''
|
752
|
+
X-Accepted-Oauth-Scopes:
|
753
|
+
- repo
|
754
|
+
X-Github-Media-Type:
|
755
|
+
- github.v3; format=json
|
756
|
+
Access-Control-Expose-Headers:
|
757
|
+
- ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining,
|
758
|
+
X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval,
|
759
|
+
X-GitHub-Media-Type
|
760
|
+
Access-Control-Allow-Origin:
|
761
|
+
- "*"
|
762
|
+
Strict-Transport-Security:
|
763
|
+
- max-age=31536000; includeSubdomains; preload
|
764
|
+
X-Frame-Options:
|
765
|
+
- deny
|
766
|
+
X-Content-Type-Options:
|
767
|
+
- nosniff
|
768
|
+
X-Xss-Protection:
|
769
|
+
- 1; mode=block
|
770
|
+
Referrer-Policy:
|
771
|
+
- origin-when-cross-origin, strict-origin-when-cross-origin
|
772
|
+
Content-Security-Policy:
|
773
|
+
- default-src 'none'
|
774
|
+
X-Github-Request-Id:
|
775
|
+
- E2DD:74AE:2EA1532:5A89EC1:5C41103C
|
776
|
+
body:
|
777
|
+
encoding: ASCII-8BIT
|
778
|
+
string: '{"message":"Not Found","documentation_url":"https://developer.github.com/v3/repos/commits/#list-commits-on-a-repository"}'
|
779
|
+
http_version:
|
780
|
+
recorded_at: Thu, 17 Jan 2019 23:31:08 GMT
|
781
|
+
recorded_with: VCR 4.0.0
|
@@ -480,4 +480,73 @@ http_interactions:
|
|
480
480
|
commit","tree":{"sha":"8286e548e330cfe01efcf7189f4df1fa53e777a7","url":"https://api.github.com/repos/how-is/example-repository/git/trees/8286e548e330cfe01efcf7189f4df1fa53e777a7"},"url":"https://api.github.com/repos/how-is/example-repository/git/commits/3794aa1c4b76623748faf280abe5760b76823162","comment_count":0,"verification":{"verified":false,"reason":"unsigned","signature":null,"payload":null}},"url":"https://api.github.com/repos/how-is/example-repository/commits/3794aa1c4b76623748faf280abe5760b76823162","html_url":"https://github.com/how-is/example-repository/commit/3794aa1c4b76623748faf280abe5760b76823162","comments_url":"https://api.github.com/repos/how-is/example-repository/commits/3794aa1c4b76623748faf280abe5760b76823162/comments","author":null,"committer":null,"parents":[{"sha":"9e29405efa433529b86722542b8fb4b34dfd9edd","url":"https://api.github.com/repos/how-is/example-repository/commits/9e29405efa433529b86722542b8fb4b34dfd9edd","html_url":"https://github.com/how-is/example-repository/commit/9e29405efa433529b86722542b8fb4b34dfd9edd"}]}]'
|
481
481
|
http_version:
|
482
482
|
recorded_at: Sat, 06 Jan 2018 20:10:45 GMT
|
483
|
-
|
483
|
+
- request:
|
484
|
+
method: get
|
485
|
+
uri: https://api.github.com/repos/how-is/example_repository
|
486
|
+
body:
|
487
|
+
encoding: US-ASCII
|
488
|
+
string: ''
|
489
|
+
headers:
|
490
|
+
Accept:
|
491
|
+
- application/vnd.github.v3+json,application/vnd.github.beta+json;q=0.5,application/json;q=0.1
|
492
|
+
Accept-Charset:
|
493
|
+
- utf-8
|
494
|
+
User-Agent:
|
495
|
+
- Github API Ruby Gem 0.18.2
|
496
|
+
Authorization:
|
497
|
+
- Basic ZHVja2luYXRvcjo5MTgyNzc3ZmYzYzAwNjc5NTE5M2E1NzBjZGFjMzI2YjY0NDU5ZGM5
|
498
|
+
Accept-Encoding:
|
499
|
+
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
|
500
|
+
response:
|
501
|
+
status:
|
502
|
+
code: 404
|
503
|
+
message: Not Found
|
504
|
+
headers:
|
505
|
+
Server:
|
506
|
+
- GitHub.com
|
507
|
+
Date:
|
508
|
+
- Thu, 17 Jan 2019 23:31:09 GMT
|
509
|
+
Content-Type:
|
510
|
+
- application/json; charset=utf-8
|
511
|
+
Transfer-Encoding:
|
512
|
+
- chunked
|
513
|
+
Status:
|
514
|
+
- 404 Not Found
|
515
|
+
X-Ratelimit-Limit:
|
516
|
+
- '5000'
|
517
|
+
X-Ratelimit-Remaining:
|
518
|
+
- '4990'
|
519
|
+
X-Ratelimit-Reset:
|
520
|
+
- '1547771467'
|
521
|
+
X-Oauth-Scopes:
|
522
|
+
- ''
|
523
|
+
X-Accepted-Oauth-Scopes:
|
524
|
+
- repo
|
525
|
+
X-Github-Media-Type:
|
526
|
+
- github.v3; format=json
|
527
|
+
Access-Control-Expose-Headers:
|
528
|
+
- ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining,
|
529
|
+
X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval,
|
530
|
+
X-GitHub-Media-Type
|
531
|
+
Access-Control-Allow-Origin:
|
532
|
+
- "*"
|
533
|
+
Strict-Transport-Security:
|
534
|
+
- max-age=31536000; includeSubdomains; preload
|
535
|
+
X-Frame-Options:
|
536
|
+
- deny
|
537
|
+
X-Content-Type-Options:
|
538
|
+
- nosniff
|
539
|
+
X-Xss-Protection:
|
540
|
+
- 1; mode=block
|
541
|
+
Referrer-Policy:
|
542
|
+
- origin-when-cross-origin, strict-origin-when-cross-origin
|
543
|
+
Content-Security-Policy:
|
544
|
+
- default-src 'none'
|
545
|
+
X-Github-Request-Id:
|
546
|
+
- A3D7:74B0:25D2A89:4E64487:5C41103D
|
547
|
+
body:
|
548
|
+
encoding: ASCII-8BIT
|
549
|
+
string: '{"message":"Not Found","documentation_url":"https://developer.github.com/v3/repos/#get"}'
|
550
|
+
http_version:
|
551
|
+
recorded_at: Thu, 17 Jan 2019 23:31:09 GMT
|
552
|
+
recorded_with: VCR 4.0.0
|
@@ -480,4 +480,73 @@ http_interactions:
|
|
480
480
|
commit","tree":{"sha":"8286e548e330cfe01efcf7189f4df1fa53e777a7","url":"https://api.github.com/repos/how-is/example-repository/git/trees/8286e548e330cfe01efcf7189f4df1fa53e777a7"},"url":"https://api.github.com/repos/how-is/example-repository/git/commits/3794aa1c4b76623748faf280abe5760b76823162","comment_count":0,"verification":{"verified":false,"reason":"unsigned","signature":null,"payload":null}},"url":"https://api.github.com/repos/how-is/example-repository/commits/3794aa1c4b76623748faf280abe5760b76823162","html_url":"https://github.com/how-is/example-repository/commit/3794aa1c4b76623748faf280abe5760b76823162","comments_url":"https://api.github.com/repos/how-is/example-repository/commits/3794aa1c4b76623748faf280abe5760b76823162/comments","author":null,"committer":null,"parents":[{"sha":"9e29405efa433529b86722542b8fb4b34dfd9edd","url":"https://api.github.com/repos/how-is/example-repository/commits/9e29405efa433529b86722542b8fb4b34dfd9edd","html_url":"https://github.com/how-is/example-repository/commit/9e29405efa433529b86722542b8fb4b34dfd9edd"}]}]'
|
481
481
|
http_version:
|
482
482
|
recorded_at: Sat, 06 Jan 2018 20:10:45 GMT
|
483
|
-
|
483
|
+
- request:
|
484
|
+
method: get
|
485
|
+
uri: https://api.github.com/repos/how-is/example_repository
|
486
|
+
body:
|
487
|
+
encoding: US-ASCII
|
488
|
+
string: ''
|
489
|
+
headers:
|
490
|
+
Accept:
|
491
|
+
- application/vnd.github.v3+json,application/vnd.github.beta+json;q=0.5,application/json;q=0.1
|
492
|
+
Accept-Charset:
|
493
|
+
- utf-8
|
494
|
+
User-Agent:
|
495
|
+
- Github API Ruby Gem 0.18.2
|
496
|
+
Authorization:
|
497
|
+
- Basic ZHVja2luYXRvcjo5MTgyNzc3ZmYzYzAwNjc5NTE5M2E1NzBjZGFjMzI2YjY0NDU5ZGM5
|
498
|
+
Accept-Encoding:
|
499
|
+
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
|
500
|
+
response:
|
501
|
+
status:
|
502
|
+
code: 404
|
503
|
+
message: Not Found
|
504
|
+
headers:
|
505
|
+
Server:
|
506
|
+
- GitHub.com
|
507
|
+
Date:
|
508
|
+
- Thu, 17 Jan 2019 23:31:09 GMT
|
509
|
+
Content-Type:
|
510
|
+
- application/json; charset=utf-8
|
511
|
+
Transfer-Encoding:
|
512
|
+
- chunked
|
513
|
+
Status:
|
514
|
+
- 404 Not Found
|
515
|
+
X-Ratelimit-Limit:
|
516
|
+
- '5000'
|
517
|
+
X-Ratelimit-Remaining:
|
518
|
+
- '4989'
|
519
|
+
X-Ratelimit-Reset:
|
520
|
+
- '1547771467'
|
521
|
+
X-Oauth-Scopes:
|
522
|
+
- ''
|
523
|
+
X-Accepted-Oauth-Scopes:
|
524
|
+
- repo
|
525
|
+
X-Github-Media-Type:
|
526
|
+
- github.v3; format=json
|
527
|
+
Access-Control-Expose-Headers:
|
528
|
+
- ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining,
|
529
|
+
X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval,
|
530
|
+
X-GitHub-Media-Type
|
531
|
+
Access-Control-Allow-Origin:
|
532
|
+
- "*"
|
533
|
+
Strict-Transport-Security:
|
534
|
+
- max-age=31536000; includeSubdomains; preload
|
535
|
+
X-Frame-Options:
|
536
|
+
- deny
|
537
|
+
X-Content-Type-Options:
|
538
|
+
- nosniff
|
539
|
+
X-Xss-Protection:
|
540
|
+
- 1; mode=block
|
541
|
+
Referrer-Policy:
|
542
|
+
- origin-when-cross-origin, strict-origin-when-cross-origin
|
543
|
+
Content-Security-Policy:
|
544
|
+
- default-src 'none'
|
545
|
+
X-Github-Request-Id:
|
546
|
+
- B1CE:74B1:1A6A899:3C03775:5C41103D
|
547
|
+
body:
|
548
|
+
encoding: ASCII-8BIT
|
549
|
+
string: '{"message":"Not Found","documentation_url":"https://developer.github.com/v3/repos/#get"}'
|
550
|
+
http_version:
|
551
|
+
recorded_at: Thu, 17 Jan 2019 23:31:09 GMT
|
552
|
+
recorded_with: VCR 4.0.0
|
data/how_is.gemspec
CHANGED
@@ -19,20 +19,26 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
20
|
spec.require_paths = ["lib"]
|
21
21
|
|
22
|
-
|
23
|
-
|
22
|
+
# how_is only supports Ruby versions under "normal maintenance".
|
23
|
+
# This number should be updated when a Ruby version goes into security
|
24
|
+
# maintenance.
|
25
|
+
#
|
26
|
+
# Ruby maintenance info: https://www.ruby-lang.org/en/downloads/branches/
|
27
|
+
#
|
28
|
+
# NOTE: Update Gemfile when this is updated!
|
29
|
+
spec.required_ruby_version = "~> 2.4"
|
24
30
|
|
25
|
-
spec.add_runtime_dependency "
|
31
|
+
spec.add_runtime_dependency "github_api", "~> 0.18.1"
|
32
|
+
spec.add_runtime_dependency "okay", "~> 11.0"
|
26
33
|
|
27
34
|
spec.add_runtime_dependency "json"
|
28
35
|
|
29
|
-
spec.add_development_dependency "bundler", "~>
|
36
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
30
37
|
spec.add_development_dependency "rake", "~> 12.3"
|
31
|
-
spec.add_development_dependency "rspec", "~> 3.
|
38
|
+
spec.add_development_dependency "rspec", "~> 3.8"
|
32
39
|
spec.add_development_dependency "timecop", "~> 0.9.1"
|
33
40
|
spec.add_development_dependency "vcr", "~> 4.0"
|
34
41
|
spec.add_development_dependency "webmock"
|
35
42
|
spec.add_development_dependency "rubocop", "~> 0.49.1"
|
36
43
|
spec.add_development_dependency "github_changelog_generator"
|
37
|
-
spec.add_development_dependency "pry"
|
38
44
|
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "digest"
|
4
|
+
require "tmpdir"
|
5
|
+
|
6
|
+
module HowIs
|
7
|
+
# Class for use in caching expensive operations
|
8
|
+
class Cacheable
|
9
|
+
def initialize(config, start_date, end_date, tmpdir = Dir.mktmpdir)
|
10
|
+
@config = config
|
11
|
+
@start_date = start_date
|
12
|
+
@end_date = end_date
|
13
|
+
@tmpdir = tmpdir
|
14
|
+
end
|
15
|
+
|
16
|
+
def cached(key, extra_digest = nil)
|
17
|
+
cache = @config["cache"]
|
18
|
+
return yield if cache.nil?
|
19
|
+
|
20
|
+
hash_key = []
|
21
|
+
hash_key << Digest::SHA1.hexdigest(extra_digest) if extra_digest
|
22
|
+
hash_key << Digest::SHA1.hexdigest(@config.to_json)
|
23
|
+
cache_key = File.join(@start_date, @end_date, key, hash_key.join("-"))
|
24
|
+
|
25
|
+
case cache["type"]
|
26
|
+
when "marshal"
|
27
|
+
MarshalCache.cached(cache_key, @tmpdir) { yield }
|
28
|
+
when "self"
|
29
|
+
# Can provide your own cache in HowIs.new
|
30
|
+
# e.g.
|
31
|
+
# cache_mechanism = ->(cache_key, config, block) do
|
32
|
+
# if cached?
|
33
|
+
# cached_value
|
34
|
+
# else
|
35
|
+
# block.call
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
# HowIs.new("owner/repo", date, cache_mechanism)
|
39
|
+
cache["cache_mechanism"].call(cache_key, @config, ->() { yield })
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# This is only okay on a local system
|
44
|
+
module MarshalCache
|
45
|
+
class << self
|
46
|
+
# rubocop:disable Security/MarshalLoad
|
47
|
+
def cached(key, tmpdir)
|
48
|
+
require "fileutils"
|
49
|
+
|
50
|
+
path = File.join(tmpdir, "how_is", key)
|
51
|
+
FileUtils.mkdir_p(File.dirname(path))
|
52
|
+
|
53
|
+
ret = nil
|
54
|
+
if File.exist?(path)
|
55
|
+
File.open(path, "rb") do |f|
|
56
|
+
ret = Marshal.load(f)
|
57
|
+
end
|
58
|
+
ret
|
59
|
+
else
|
60
|
+
ret = yield
|
61
|
+
File.open(path, "wb") do |file|
|
62
|
+
Marshal.dump(ret, file)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
ret
|
66
|
+
end
|
67
|
+
# rubocop:enable Security/MarshalLoad
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/lib/how_is/cli.rb
CHANGED
@@ -1,138 +1,135 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "how_is"
|
4
|
-
require "
|
4
|
+
require "how_is/constants"
|
5
|
+
require "okay/simple_opts"
|
5
6
|
|
6
|
-
module HowIs
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
module HowIs
|
8
|
+
##
|
9
|
+
# Parses command-line arguments for how_is.
|
10
|
+
class CLI
|
11
|
+
MissingArgument = Class.new(OptionParser::MissingArgument)
|
10
12
|
|
11
|
-
|
12
|
-
|
13
|
-
end
|
13
|
+
REPO_REGEXP = /.+\/.+/
|
14
|
+
DATE_REGEXP = /\d\d\d\d-\d\d-\d\d/
|
14
15
|
|
15
|
-
|
16
|
-
# a valid JSON report.
|
17
|
-
class InvalidInputFileError < OptionsError
|
18
|
-
end
|
16
|
+
attr_reader :options, :help_text
|
19
17
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
18
|
+
def self.parse(*args)
|
19
|
+
new.parse(*args)
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
@options = nil
|
24
|
+
@help_text = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
# Parses an Array of command-line arguments into an equivalent Hash.
|
28
|
+
#
|
29
|
+
# The results of this can be used to control the behavior of the rest
|
30
|
+
# of the library.
|
31
|
+
#
|
32
|
+
# @params argv [Array] An array of command-line arguments, e.g. +ARGV+.
|
33
|
+
# @return [Hash] A Hash containing data used to control HowIs behavior.
|
34
|
+
def parse(argv)
|
35
|
+
parser, options = parse_main(argv)
|
36
|
+
|
37
|
+
# Options that are mutually-exclusive with everything else.
|
38
|
+
options = {:help => true} if options[:help]
|
39
|
+
options = {:version => true} if options[:version]
|
40
|
+
|
41
|
+
validate_options!(options)
|
42
|
+
|
43
|
+
@options = options
|
44
|
+
@help_text = parser.to_s
|
45
|
+
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
# parse_main() is as short as can be managed. It's fine as-is.
|
52
|
+
# rubocop:disable Metrics/MethodLength
|
24
53
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
opts.
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
| Where:
|
40
|
-
| REPOSITORY is <GitHub username or org>/<repository name>.
|
41
|
-
| and
|
42
|
-
| REPORT_DATE is the last day the report covers, in the format YYYY-mm-dd.
|
43
|
-
|
|
44
|
-
| E.g., if you wanted to check https://github.com/how-is/how_is for
|
45
|
-
| November 01 2016 through December 01 2016, you'd run:
|
46
|
-
| how_is how-is/how_is 2016-12-01
|
47
|
-
|
|
54
|
+
# This does a significant chunk of the work for parse().
|
55
|
+
#
|
56
|
+
# @return [Array] An array containing the +OptionParser+ and the result
|
57
|
+
# of running it.
|
58
|
+
def parse_main(argv)
|
59
|
+
defaults = {
|
60
|
+
report: HowIs::DEFAULT_REPORT_FILE,
|
61
|
+
}
|
62
|
+
|
63
|
+
opts = Okay::SimpleOpts.new(defaults: defaults)
|
64
|
+
|
65
|
+
opts.banner = <<~EOF
|
66
|
+
Usage: how_is --repository REPOSITORY --date REPORT_DATE [--output REPORT_FILE]
|
67
|
+
how_is --config CONFIG_FILE --date REPORT_DATE
|
48
68
|
EOF
|
49
69
|
|
50
|
-
opts.separator ""
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
70
|
+
opts.separator "\nOptions:"
|
71
|
+
|
72
|
+
opts.simple("--config CONFIG_FILE",
|
73
|
+
"YAML config file for automated reports.",
|
74
|
+
:config)
|
75
|
+
|
76
|
+
opts.simple("--no-user-config",
|
77
|
+
"Don't load user configuration file.",
|
78
|
+
:no_user_config)
|
79
|
+
|
80
|
+
opts.simple("--env-config",
|
81
|
+
"Use environment variables for configuration.",
|
82
|
+
"Read first: https://how-is.github.io/config",
|
83
|
+
:env_login)
|
84
|
+
|
85
|
+
opts.simple("--repository USER/REPO", REPO_REGEXP,
|
86
|
+
"Repository to generate a report for.",
|
87
|
+
:repository)
|
88
|
+
|
89
|
+
opts.simple("--date YYYY-MM-DD", DATE_REGEXP, "Last date of the report.",
|
90
|
+
:date)
|
91
|
+
|
92
|
+
opts.simple("--output REPORT_FILE", format_regexp,
|
93
|
+
"Output file for the report.",
|
94
|
+
"Supported file formats: #{formats}.",
|
95
|
+
:report)
|
96
|
+
|
97
|
+
opts.simple("--verbose", "Print debug information.", :verbose)
|
98
|
+
opts.simple("-v", "--version", "Prints version information.", :version)
|
99
|
+
opts.simple("-h", "--help", "Print help text.", :help)
|
100
|
+
|
101
|
+
[opts, opts.parse(argv)]
|
80
102
|
end
|
81
|
-
|
82
|
-
# rubocop:enable Metrics/
|
83
|
-
|
84
|
-
#
|
85
|
-
#
|
86
|
-
#
|
87
|
-
|
88
|
-
|
89
|
-
#
|
90
|
-
|
91
|
-
|
92
|
-
if options
|
93
|
-
|
94
|
-
options
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
elsif options[:config]
|
99
|
-
# If --config is passed, _only_ accept --config.
|
100
|
-
options = keep_only.call(options, :config)
|
101
|
-
if argv.length >= 1
|
102
|
-
options[:date] = argv.delete_at(0)
|
103
|
-
else
|
104
|
-
raise HowIsArgumentError, "Expected date."
|
105
|
-
end
|
106
|
-
else
|
107
|
-
# If we get here, we're generating a report from the command line,
|
108
|
-
# without using --from or --config.
|
109
|
-
|
110
|
-
# If --report isn't specified, default to HowIs::DEFAULT_REPORT_FILE.
|
111
|
-
options[:report] ||= HowIs::DEFAULT_REPORT_FILE
|
112
|
-
|
113
|
-
# If we can't export to the specified file, raise an exception.
|
114
|
-
unless HowIs.can_export_to?(options[:report])
|
115
|
-
raise InvalidOutputFileError, "Invalid file: #{options[:report]}. Supported formats: #{HowIs.supported_formats.join(', ')}"
|
116
|
-
end
|
117
|
-
|
118
|
-
if argv.length >= 2
|
119
|
-
options[:repository] = argv.delete_at(0)
|
120
|
-
options[:date] = argv.delete_at(0)
|
121
|
-
else
|
122
|
-
raise HowIsArgumentError, "Expected both repository and date."
|
123
|
-
end
|
103
|
+
|
104
|
+
# rubocop:enable Metrics/MethodLength
|
105
|
+
|
106
|
+
# Given an +options+ Hash, determine if we got a valid combination of
|
107
|
+
# options.
|
108
|
+
#
|
109
|
+
# 1. Anything with `--help` and `--version` is always valid.
|
110
|
+
# 2. Otherwise, `--repository` or `--config` is required.
|
111
|
+
# 3. If `--repository` or `--config` is required, so is `--date`.
|
112
|
+
#
|
113
|
+
# @param options [Hash] The result of CLI#parse().
|
114
|
+
# @raise [MissingArgument] if we did not get a valid options Hash.
|
115
|
+
def validate_options!(options)
|
116
|
+
return if options[:help] || options[:version]
|
117
|
+
raise MissingArgument, "--date" unless options[:date]
|
118
|
+
raise MissingArgument, "--repository or --config" unless
|
119
|
+
options[:repository] || options[:config]
|
124
120
|
end
|
125
121
|
|
126
|
-
#
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
#
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
122
|
+
# @return [String] A comma-separated list of supported formats.
|
123
|
+
def formats
|
124
|
+
HowIs.supported_formats.join(", ")
|
125
|
+
end
|
126
|
+
|
127
|
+
# @return [Regexp] a +Regexp+ object which matches any path ending
|
128
|
+
# with an extension corresponding to a supported format.
|
129
|
+
def format_regexp
|
130
|
+
regexp_parts = HowIs.supported_formats.map { |x| Regexp.escape(x) }
|
131
|
+
|
132
|
+
/.+\.(#{regexp_parts.join("|")})/
|
133
|
+
end
|
137
134
|
end
|
138
135
|
end
|