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.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.github_changelog_generator +0 -1
  3. data/.rubocop.yml +37 -12
  4. data/.travis.yml +6 -3
  5. data/CHANGELOG.md +56 -0
  6. data/CONTRIBUTING.md +34 -0
  7. data/Gemfile +8 -4
  8. data/ISSUES.md +30 -54
  9. data/README.md +16 -91
  10. data/Rakefile +3 -31
  11. data/bin/prerelease-generate-changelog +1 -1
  12. data/bin/setup +0 -0
  13. data/build-debug.rb +20 -0
  14. data/exe/how_is +25 -22
  15. data/fixtures/vcr_cassettes/how-is-example-empty-repository.yml +334 -1
  16. data/fixtures/vcr_cassettes/how-is-example-repository.yml +350 -1
  17. data/fixtures/vcr_cassettes/how-is-from-config-frontmatter.yml +15234 -1
  18. data/fixtures/vcr_cassettes/how-is-how-is-travis-api-repos-builds.yml +2694 -1
  19. data/fixtures/vcr_cassettes/how-is-with-config-file.yml +15234 -1
  20. data/fixtures/vcr_cassettes/how_is_contributions_additions_count.yml +70 -1
  21. data/fixtures/vcr_cassettes/how_is_contributions_all_contributors.yml +70 -1
  22. data/fixtures/vcr_cassettes/how_is_contributions_changed_files.yml +70 -1
  23. data/fixtures/vcr_cassettes/how_is_contributions_changes.yml +70 -1
  24. data/fixtures/vcr_cassettes/how_is_contributions_commits.yml +70 -1
  25. data/fixtures/vcr_cassettes/how_is_contributions_compare_url.yml +70 -1
  26. data/fixtures/vcr_cassettes/how_is_contributions_default_branch.yml +70 -1
  27. data/fixtures/vcr_cassettes/how_is_contributions_deletions_count.yml +70 -1
  28. data/fixtures/vcr_cassettes/how_is_contributions_new_contributors.yml +70 -1
  29. data/fixtures/vcr_cassettes/how_is_contributions_summary.yml +70 -1
  30. data/fixtures/vcr_cassettes/how_is_contributions_summary_2.yml +70 -1
  31. data/how_is.gemspec +12 -6
  32. data/lib/how_is/cacheable.rb +71 -0
  33. data/lib/how_is/cli.rb +121 -124
  34. data/lib/how_is/config.rb +123 -0
  35. data/lib/how_is/constants.rb +9 -0
  36. data/lib/how_is/date_time_helpers.rb +48 -0
  37. data/lib/how_is/frontmatter.rb +14 -9
  38. data/lib/how_is/report.rb +86 -58
  39. data/lib/how_is/report_collection.rb +113 -0
  40. data/lib/how_is/sources/ci/appveyor.rb +88 -0
  41. data/lib/how_is/sources/ci/travis.rb +159 -0
  42. data/lib/how_is/sources/github/contributions.rb +169 -128
  43. data/lib/how_is/sources/github/issue_fetcher.rb +148 -0
  44. data/lib/how_is/sources/github/issues.rb +86 -235
  45. data/lib/how_is/sources/github/pulls.rb +19 -18
  46. data/lib/how_is/sources/github.rb +40 -18
  47. data/lib/how_is/sources/github_helpers.rb +8 -91
  48. data/lib/how_is/sources.rb +2 -0
  49. data/lib/how_is/template.rb +9 -0
  50. data/lib/how_is/templates/contributions_partial.html +1 -0
  51. data/lib/how_is/templates/{issues_or_pulls_partial.html_template → issues_or_pulls_partial.html} +0 -0
  52. data/lib/how_is/templates/new_contributors_partial.html +5 -0
  53. data/lib/how_is/templates/{report.html_template → report.html} +0 -8
  54. data/lib/how_is/templates/{report_partial.html_template → report_partial.html} +3 -3
  55. data/lib/how_is/text.rb +26 -0
  56. data/lib/how_is/version.rb +2 -1
  57. data/lib/how_is.rb +33 -60
  58. metadata +28 -47
  59. data/.hound.yml +0 -2
  60. data/.rubocop_todo.yml +0 -21
  61. data/lib/how_is/sources/travis.rb +0 -37
  62. 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
- recorded_with: VCR 3.0.3
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
- recorded_with: VCR 3.0.3
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
- recorded_with: VCR 3.0.3
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
- spec.add_runtime_dependency "github_api", "~> 0.18.1"
23
- spec.add_runtime_dependency "contracts", "~> 0.16.0"
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 "okay", "~> 7.0.0"
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", "~> 1.16"
36
+ spec.add_development_dependency "bundler", "~> 2.0"
30
37
  spec.add_development_dependency "rake", "~> 12.3"
31
- spec.add_development_dependency "rspec", "~> 3.7"
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
- # NOPE THIS IS BROKEN // frozen_string_literal: // true
1
+ # frozen_string_literal: true
2
2
 
3
3
  require "how_is"
4
- require "optparse"
4
+ require "how_is/constants"
5
+ require "okay/simple_opts"
5
6
 
6
- module HowIs::CLI
7
- # Parent class of all exceptions raised in HowIs::CLI.
8
- class OptionsError < StandardError
9
- end
7
+ module HowIs
8
+ ##
9
+ # Parses command-line arguments for how_is.
10
+ class CLI
11
+ MissingArgument = Class.new(OptionParser::MissingArgument)
10
12
 
11
- # Raised when the specified output file can't be used.
12
- class InvalidOutputFileError < OptionsError
13
- end
13
+ REPO_REGEXP = /.+\/.+/
14
+ DATE_REGEXP = /\d\d\d\d-\d\d-\d\d/
14
15
 
15
- # Raised when the specified input file doesn't contain
16
- # a valid JSON report.
17
- class InvalidInputFileError < OptionsError
18
- end
16
+ attr_reader :options, :help_text
19
17
 
20
- # Raised when no repository is specified, but one is required.
21
- # (It's _not_ required, e.g., when +--config+ is passed.)
22
- class HowIsArgumentError < OptionsError
23
- end
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
- # Parses +argv+ to generate an options Hash to control the behavior of
26
- # the library.
27
- def self.parse(argv)
28
- options = {}
29
- opts_ = nil
30
-
31
- opt_parser = OptionParser.new do |opts|
32
- opts_ = opts
33
- # General usage information.
34
- opts.banner =
35
- <<-EOF.gsub(/ *\| ?/, '')
36
- | Usage: how_is REPOSITORY REPORT_DATE [--output REPORT_FILE]
37
- | how_is REPORT_DATE --config CONFIG_FILE
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
- opts.separator "Options:"
52
-
53
- # The extra spaces make this a lot easier to comprehend, so we don't want
54
- # RuboCop to complain about them.
55
- #
56
- # Same for line length.
57
- #
58
- # rubocop:disable Style/SpaceBeforeFirstArg
59
- # rubocop:disable Metrics/LineLength
60
-
61
- opts.on("--config CONFIG_FILE",
62
- "YAML config file, used to generate a group of reports") do |filename|
63
- options[:config] = filename
64
- end
65
-
66
- opts.on("--output REPORT_FILE",
67
- "Output file for the report (valid extensions: #{HowIs.supported_formats.join(', ')}; default: #{HowIs::DEFAULT_REPORT_FILE})") do |filename|
68
- options[:report] = filename
69
- end
70
-
71
- opts.on("-v", "--version",
72
- "Prints version information") do
73
- options[:version] = true
74
- end
75
-
76
- opts.on("-h", "--help",
77
- "Print help text") do
78
- options[:help] = true
79
- end
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
- # rubocop:enable Style/SpaceBeforeFirstArg
82
- # rubocop:enable Metrics/LineLength
83
-
84
- # `.parse!` populates the `options` Hash that was
85
- # created above, and the return value is any non-flag
86
- # arguments.
87
- arguments = opt_parser.parse!(argv)
88
-
89
- # TODO: Should this raise an exception instead?
90
- keep_only = lambda { |options_, key| options_.select { |k, v| k == key } }
91
-
92
- if options[:help]
93
- # If --help is passed, _only_ accept --help.
94
- options = keep_only.call(options, :help)
95
- elsif options[:version]
96
- # If --version is passed, _only_ accept --version.
97
- options = keep_only.call(options, :version)
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
- # Return a Hash with:
127
- # +opts+: the original Slop::Options object.
128
- # +options+: the Hash of flags/values (e.g. +--foo bar+ becomes
129
- # +options[:foo]+ with the value of +"bar"+).
130
- # +arguments+: an Array of arguments that don't have a
131
- # corresponding flags.
132
- {
133
- opts: opts_,
134
- options: options,
135
- arguments: arguments,
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