how_is 24.0.0 → 25.0.0

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