pretty-git 0.1.5 → 0.1.6

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: 7bd982eee724a56adcb2aa941fdecc2c95309c1bf681a5c3b9a1be0b8bf0306f
4
- data.tar.gz: 1ef12cd144493563bbe99a869e81fe6aa9436136dc7aac52b713d5436975a48a
3
+ metadata.gz: 00b27dd7b14328c3aa2e5d3bf865dd778d512131d3f3b1c7d9fe1ca76939c5e6
4
+ data.tar.gz: 36c9f80ac26f1982bf2a4b2f1616bf5d309dec2c9dcda12603e2e2422500e3a4
5
5
  SHA512:
6
- metadata.gz: fba3dc59a53571d80cf38e13a555b2ad62b44964f29039a1fe68000349f642d024bb854542d62d58608dd8f9a8169548b0444a0fc8488cccc9a8e3497ba68ade
7
- data.tar.gz: 1f8037f3e7ef97e13b60be37c6d447c684586ffb78017d1905fc6b75d4d055ce533d4a87f04baf6e15fa734fa20fcbfe8854f5f4ef429c402106cbbf4a9a70b5
6
+ metadata.gz: 8518117f9b018c89a31db647920d871fe68f1ba1a2ddb90e43f58ec08342c4d345393617144ba29705ef6a13f86c184123c72b99ee2de81ada45824df875593d
7
+ data.tar.gz: 59505cbed77cdf230c6b5c2960caa1bde851a4a17820d886b788b409a04f5b042cfe51e5a4e060471e90a303743369df335206f3fc51b97759c637086775abcf
data/CHANGELOG.md CHANGED
@@ -5,87 +5,144 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
6
6
 
7
7
  ## [Unreleased]
8
+
9
+ ## [0.1.6] - 2025-10-10
10
+
11
+ ### Security
12
+
13
+ - Git::Provider: added input validation for `--author` and `--branch` parameters to prevent command injection attacks
14
+ - Git::Provider: `validate_safe_string` method checks for shell metacharacters (`; & | \` $ ( ) < >`)
15
+ - Git::Provider: `validate_git_ref` method validates git reference format and rejects refs starting with `-`
16
+
8
17
  ### Added
18
+
19
+ - Render::ConsoleRenderer: named constants for ANSI color codes (`Colors::AnsiCodes` module)
20
+ - Render::ReportSchema: centralized module for report headers (eliminates duplication across CSV/Markdown renderers)
21
+ - CLI: verbose mode now includes full backtrace for `StandardError` exceptions (helps with debugging)
22
+ - Analytics::Languages: `safe_file_size` and `safe_count_lines` now log warnings to stderr when `verbose=true`
23
+ - Tests: 20+ negative test cases for input validation (`spec/pretty_git/git/provider_validation_spec.rb`)
24
+ - Tests: CLI error handling coverage (`spec/pretty_git/cli_error_handling_spec.rb`)
25
+
26
+ ### Changed
27
+
28
+ - CLI: enhanced error handling with specific exit codes: `ArgumentError` → 1, file system errors (`ENOENT`, `EACCES`) → 2, other errors → 99
29
+ - CLI: improved error messages for `ArgumentError`, `Errno::ENOENT`, and `Errno::EACCES`
30
+ - Render::CsvRenderer: refactored to use `ReportSchema.headers_for` instead of local `HEADERS` constant
31
+ - Render::MarkdownRenderer: refactored to use `ReportSchema.headers_for` instead of local `HEADERS` constant
32
+
33
+ ### Fixed
34
+
35
+ - Security: command injection vulnerabilities via `--author` and `--branch` parameters accepting unvalidated shell input
36
+
37
+ ### Documentation
38
+
9
39
  - Docs: `docs/testing.md` covering the golden workflow, snapshot update/validation; linked from `README.md`, `README.ru.md`, and `CONTRIBUTING.md`.
10
40
  - Tests: determinism invariants for YAML/XML renderers (stable output regardless of input order).
41
+ - Scripts: `scripts/release.sh` automated release workflow with validation, testing, and git operations
42
+
43
+ ### Refactoring
44
+
45
+ - Code quality: eliminated ~40 lines of duplicated `HEADERS` definitions across renderers
46
+ - Readability: replaced magic numbers (`'1;35'`, `'1;36'`) with descriptive constant names (`BRIGHT_TITLE`, `BASIC_TITLE`)
47
+
48
+ ### Changed (from previous Unreleased)
11
49
 
12
- ### Changed
13
50
  - Completions: refreshed bash/zsh completions — added short flags `-f`/`-o`/`-l`, values for `--time-bucket` (`day|week|month`), and repo path completion as the 2nd positional argument.
14
51
 
15
52
  ## [0.1.5] - 2025-08-17
53
+
16
54
  ### Added
55
+
17
56
  - CLI: warn to stderr when `--theme`/`--no-color` are used with non-console `--format` values.
18
57
  - Docs: expanded Filters documentation (branches, authors, paths, time semantics, verbose diagnostics), schemas/examples pointers, performance and CI usage.
19
58
  - Tests: unit tests for `PrettyGit::Utils::TimeUtils`.
20
59
 
21
60
  ### Changed
61
+
22
62
  - Internals: extracted time parsing/normalization to `PrettyGit::Utils::TimeUtils` and centralized verbose logging via `PrettyGit::Logger`. `Git::Provider` routes verbose messages through the centralized logger (stderr).
23
63
  - Verbose mode: documentation clarified to note that diagnostics are printed to stderr for easier CI parsing.
24
64
 
25
65
  ### Deprecated
66
+
26
67
  - Filters: legacy `:until` keyword in `PrettyGit::Filters` initialization is accepted for backward compatibility and emits a deprecation warning; use `:until_at` instead.
27
68
 
28
69
  ### Fixed
70
+
29
71
  - Filters: allow initialization via a single Hash argument (legacy call sites) while preserving `Struct` keyword semantics.
30
72
 
31
73
  ## [0.1.4] - 2025-08-17
74
+
32
75
  ### Added
76
+
33
77
  - Integration tests for new reports exports: CSV/Markdown/YAML/XML for `hotspots`, `churn`, `ownership`.
34
78
  - Schema validations: `rake validate:json`, `rake validate:xml` to ensure format compatibility.
35
79
  - CI: expanded matrix to include macOS; smoke test for installed binary (`--help`, `--version`).
36
80
 
37
81
  ### Changed
82
+
38
83
  - Renderers (`MarkdownRenderer`, `YamlRenderer`, `XmlRenderer`): deterministic sorting for all new reports according to `docs/determinism.md`.
39
84
  - XML: per-report root elements in XML exports to match XSDs (`hotspotsReport`, `churnReport`, `ownershipReport`, `languagesReport`, etc.).
40
85
  - Documentation: `README.md` and `README.ru.md` updated with sections and examples for new reports and all export formats.
41
86
  - CLI: keep `--time-bucket` permissive; default `time_bucket=nil`.
42
87
 
43
88
  ### Fixed
89
+
44
90
  - Time parsing: interpret date-only inputs (`YYYY-MM-DD`) as UTC midnight and normalize to UTC ISO8601.
45
91
  - CLI UX: error when `--metric` is used outside `languages` report.
46
92
  - Tests/specs: updated XML specs to per-report roots; added timezone edge cases; fixed Open3 `popen3` stubs (`chdir:`) and integration requires.
47
93
 
48
-
49
94
  ## [0.1.3] - 2025-08-14
95
+
50
96
  ### Added
97
+
51
98
  - New analytics reports: `hotspots`, `churn`, `ownership` with sorting, scoring, and limits.
52
99
  - Exporters: CSV and Markdown support for new reports with dynamic headers via mapping constants.
53
100
  - Docs: Detailed sections for new reports in `README.md` and `README.ru.md` with usage and examples (CSV/JSON/YAML/XML).
54
101
 
55
102
  ### Changed
103
+
56
104
  - Console: dispatching and rendering wired for new reports; consistent theming and width handling.
57
105
  - CLI/App: unified analytics dispatch for all reports.
58
- - Docs: public READMEs cleaned up from internal DR-* mentions; anchors and headings aligned (CSV).
106
+ - Docs: public READMEs cleaned up from internal DR-\* mentions; anchors and headings aligned (CSV).
59
107
 
60
108
  ### Fixed
61
- - Minor documentation inaccuracies and anchor mismatches.
62
109
 
110
+ - Minor documentation inaccuracies and anchor mismatches.
63
111
 
64
112
  ## [0.1.2] - 2025-08-13
113
+
65
114
  ### Added
115
+
66
116
  - Languages report: support multiple metrics — `bytes`, `files`, `loc`; dynamic columns in Console/CSV/Markdown; color and percent fields in output.
67
117
  - CLI: `--metric` option for the `languages` report with value validation.
68
118
 
69
119
  ### Changed
120
+
70
121
  - Languages: JSON language reinstated in the mapping and color scheme; sorting and percent calculations are based on the selected metric; percentages rounded to two decimals.
71
122
  - Renderers: updated `csv`, `markdown`, and console renderers to work with dynamic metrics.
72
123
  - Internal specs updated: `docs/output_formats.md`, `docs/cli_spec.md`, `docs/languages_map.md`.
73
124
 
74
125
  ### Fixed
126
+
75
127
  - Git provider: correct commit counting — emit a new commit when a header is read and remove the record separator from the subject (`lib/pretty_git/git/provider.rb`).
76
128
  - RuboCop: targeted suppressions for complex methods/classes and style fixes in `cli_helpers.rb`.
77
129
 
78
130
  ## [0.1.1] - 2025-08-13
131
+
79
132
  ### Changed
133
+
80
134
  - Release automation: added GitHub Actions workflow to publish gem on tags and open PR to Homebrew tap (`.github/workflows/release.yml`).
81
135
  - Documentation: README badges and installation instructions for Homebrew and RubyGems in `README.md` and `README.ru.md`.
82
136
  - Gemspec: bounded runtime dependencies for `csv` and `rexml` to satisfy RubyGems recommendations.
83
137
 
84
138
  ### Fixed
139
+
85
140
  - Homebrew formula installation stability: formula installs gem into `libexec/vendor` and wraps `pretty-git` binary to avoid file collisions on reinstall.
86
141
 
87
142
  ## [0.1.0] - 2025-08-13
143
+
88
144
  ### Added
145
+
89
146
  - Languages report: bytes per language, percentages, sorting, limit.
90
147
  - Console: colorized languages section; terminal width handling via `TerminalWidth`.
91
148
  - Export: languages in Markdown/CSV/JSON/YAML/XML.
@@ -96,10 +153,12 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve
96
153
  - Docs: Added screenshot `PrettyGitConsoleLanguages.png`.
97
154
 
98
155
  ### Changed
156
+
99
157
  - Analytics: exclude JSON from language mapping by default to avoid data skew.
100
158
  - Analytics: ignore Python env/cache directories by default.
101
159
  - Refactor: `ConsoleRenderer` split (LanguagesSection, TerminalWidth); reduced complexity.
102
160
  - App: extracted `analytics_for` from `App#run`.
103
161
 
104
162
  ### Fixed
163
+
105
164
  - RuboCop violations in new specs and minor guard clause spacing.
@@ -112,6 +112,7 @@ module PrettyGit
112
112
  # rubocop:disable Metrics/AbcSize
113
113
  def self.calculate(repo_path, include_globs:, exclude_globs:, metric: 'bytes')
114
114
  by_lang = Hash.new { |h, k| h[k] = { bytes: 0, files: 0, loc: 0 } }
115
+ verbose = ENV['PG_VERBOSE'] == '1'
115
116
  Dir.chdir(repo_path) do
116
117
  each_source_file(include_globs, exclude_globs) do |path|
117
118
  basename = File.basename(path)
@@ -119,8 +120,8 @@ module PrettyGit
119
120
  lang = FILENAME_TO_LANG[basename] || EXT_TO_LANG[ext]
120
121
  next unless lang
121
122
 
122
- size = safe_file_size(path)
123
- lines = metric == 'loc' ? safe_count_lines(path) : 0
123
+ size = safe_file_size(path, verbose: verbose)
124
+ lines = metric == 'loc' ? safe_count_lines(path, verbose: verbose) : 0
124
125
  agg = by_lang[lang]
125
126
  agg[:bytes] += size
126
127
  agg[:files] += 1
@@ -159,17 +160,19 @@ module PrettyGit
159
160
  end
160
161
  # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
161
162
 
162
- def self.safe_file_size(path)
163
+ def self.safe_file_size(path, verbose: false)
163
164
  File.size(path)
164
- rescue StandardError
165
+ rescue StandardError => e
166
+ warn("Warning: failed to read size of #{path}: #{e.message}") if verbose
165
167
  0
166
168
  end
167
169
 
168
- def self.safe_count_lines(path)
170
+ def self.safe_count_lines(path, verbose: false)
169
171
  count = 0
170
172
  File.foreach(path) { |_l| count += 1 }
171
173
  count
172
- rescue StandardError
174
+ rescue StandardError => e
175
+ warn("Warning: failed to count lines in #{path}: #{e.message}") if verbose
173
176
  0
174
177
  end
175
178
 
@@ -53,11 +53,24 @@ module PrettyGit
53
53
  filters = CLIHelpers.build_filters(options)
54
54
  CLIHelpers.execute(options[:report], filters, options, out, err)
55
55
  rescue ArgumentError => e
56
- err.puts e.message
56
+ err.puts "Error: #{e.message}"
57
57
  1
58
- rescue StandardError => e
59
- err.puts e.message
58
+ rescue Errno::ENOENT => e
59
+ # Repository not found is user input error (exit 1), file write error is system error (exit 2)
60
+ if e.message.include?('chdir') || options[:repo]
61
+ err.puts "Not a git repository: #{e.message}"
62
+ 1
63
+ else
64
+ err.puts "File system error: #{e.message}"
65
+ 2
66
+ end
67
+ rescue Errno::EACCES => e
68
+ err.puts "Permission denied: #{e.message}"
60
69
  2
70
+ rescue StandardError => e
71
+ err.puts "Unexpected error: #{e.message}"
72
+ err.puts e.backtrace.first(5).join("\n") if options[:_verbose]
73
+ 99
61
74
  end
62
75
  # rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity
63
76
  end
@@ -269,6 +269,9 @@ module PrettyGit
269
269
  File.open(options[:out], 'w') do |f|
270
270
  return PrettyGit::App.new.run(report, filters, out: f, err: err)
271
271
  end
272
+ rescue Errno::EISDIR
273
+ err.puts "Cannot write to: #{options[:out]} (is a directory)"
274
+ return 2
272
275
  rescue Errno::EACCES
273
276
  err.puts "Cannot write to: #{options[:out]} (permission denied)"
274
277
  return 2
@@ -57,7 +57,14 @@ module PrettyGit
57
57
  status = wait_thr.value
58
58
  unless status.success?
59
59
  err = stderr.read
60
- raise StandardError, (err && !err.empty? ? err : "git log failed with status #{status.exitstatus}")
60
+ error_msg = err && !err.empty? ? err : "git log failed with status #{status.exitstatus}"
61
+ # Repository errors are ArgumentError (invalid input), other git errors are StandardError
62
+ if error_msg.include?('not a git repository') || error_msg.include?('Not a git repository')
63
+ raise ArgumentError, error_msg
64
+ end
65
+
66
+ raise StandardError, error_msg
67
+
61
68
  end
62
69
  end
63
70
 
@@ -146,9 +153,15 @@ module PrettyGit
146
153
  end
147
154
 
148
155
  def add_author_and_branch_filters(args)
149
- @filters.authors&.each { |a| args << "--author=#{a}" }
156
+ @filters.authors&.each do |a|
157
+ validate_safe_string(a, 'author')
158
+ args << "--author=#{a}"
159
+ end
150
160
  # Treat branches as explicit revisions to include
151
- @filters.branches&.each { |b| args << b }
161
+ @filters.branches&.each do |b|
162
+ validate_git_ref(b)
163
+ args << b
164
+ end
152
165
  end
153
166
 
154
167
  def add_path_filters(args)
@@ -183,6 +196,31 @@ module PrettyGit
183
196
  end
184
197
  end
185
198
  # rubocop:enable Metrics/CyclomaticComplexity
199
+
200
+ # Validates that a string doesn't contain shell metacharacters that could be exploited
201
+ def validate_safe_string(value, name)
202
+ return if value.nil? || value.empty?
203
+
204
+ # Check for dangerous shell metacharacters
205
+ return unless value.to_s =~ /[;&|`$()<>]/
206
+
207
+ raise ArgumentError, "Invalid #{name}: contains shell metacharacters"
208
+ end
209
+
210
+ # Validates git references (branch names, tags, commit SHAs)
211
+ # Allows alphanumeric, dash, underscore, slash, dot, @, ^, ~, and :
212
+ def validate_git_ref(ref)
213
+ return if ref.nil? || ref.empty?
214
+
215
+ # Git ref naming rules are complex, but we allow a safe subset
216
+ # See: https://git-scm.com/docs/git-check-ref-format
217
+ raise ArgumentError, "Invalid git ref: #{ref}" unless ref.to_s =~ %r{\A[a-zA-Z0-9/_.\-@^~:]+\z}
218
+
219
+ # Additional safety: reject refs that look like options
220
+ return unless ref.to_s.start_with?('-')
221
+
222
+ raise ArgumentError, "Invalid git ref: #{ref} (looks like an option)"
223
+ end
186
224
  end
187
225
  end
188
226
  end
@@ -7,6 +7,24 @@ module PrettyGit
7
7
  module Render
8
8
  # Simple color helpers used by console components.
9
9
  module Colors
10
+ # ANSI color codes for different themes
11
+ module AnsiCodes
12
+ # Title colors
13
+ BRIGHT_TITLE = '1;35' # Bright magenta
14
+ BASIC_TITLE = '1;36' # Cyan
15
+
16
+ # Header colors
17
+ BRIGHT_HEADER = '1;36' # Bright cyan
18
+ BASIC_HEADER = '1;34' # Blue
19
+
20
+ # Text styles
21
+ DIM = '2;37' # Dim white
22
+ GREEN = '32' # Green
23
+ RED = '31' # Red
24
+ YELLOW = '33' # Yellow
25
+ BOLD = '1' # Bold
26
+ end
27
+
10
28
  module_function
11
29
 
12
30
  def apply(code, text, enabled)
@@ -16,33 +34,33 @@ module PrettyGit
16
34
  end
17
35
 
18
36
  def title(text, enabled, theme = 'basic')
19
- code = theme == 'bright' ? '1;35' : '1;36'
37
+ code = theme == 'bright' ? AnsiCodes::BRIGHT_TITLE : AnsiCodes::BASIC_TITLE
20
38
  apply(code, text, enabled)
21
39
  end
22
40
 
23
41
  def header(text, enabled, theme = 'basic')
24
- code = theme == 'bright' ? '1;36' : '1;34'
42
+ code = theme == 'bright' ? AnsiCodes::BRIGHT_HEADER : AnsiCodes::BASIC_HEADER
25
43
  apply(code, text, enabled)
26
44
  end
27
45
 
28
46
  def dim(text, enabled)
29
- apply('2;37', text, enabled)
47
+ apply(AnsiCodes::DIM, text, enabled)
30
48
  end
31
49
 
32
50
  def green(text, enabled)
33
- apply('32', text, enabled)
51
+ apply(AnsiCodes::GREEN, text, enabled)
34
52
  end
35
53
 
36
54
  def red(text, enabled)
37
- apply('31', text, enabled)
55
+ apply(AnsiCodes::RED, text, enabled)
38
56
  end
39
57
 
40
58
  def yellow(text, enabled)
41
- apply('33', text, enabled)
59
+ apply(AnsiCodes::YELLOW, text, enabled)
42
60
  end
43
61
 
44
62
  def bold(text, enabled)
45
- apply('1', text, enabled)
63
+ apply(AnsiCodes::BOLD, text, enabled)
46
64
  end
47
65
  end
48
66
 
@@ -1,37 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'csv'
4
+ require_relative 'report_schema'
4
5
 
5
6
  module PrettyGit
6
7
  module Render
7
8
  # Renders CSV according to docs/output_formats.md and DR-001
8
9
  class CsvRenderer
9
- HEADERS = {
10
- 'activity' => %w[bucket timestamp commits additions deletions],
11
- 'authors' => %w[author author_email commits additions deletions avg_commit_size],
12
- 'files' => %w[path commits additions deletions changes],
13
- 'heatmap' => %w[dow hour commits],
14
- 'hotspots' => %w[path score commits additions deletions changes],
15
- 'churn' => %w[path churn commits additions deletions],
16
- 'ownership' => %w[path owner owner_share authors]
17
- }.freeze
18
10
  def initialize(io: $stdout)
19
11
  @io = io
20
12
  end
21
13
 
22
14
  def call(report, result, _filters)
23
- headers = headers_for(report, result)
15
+ headers = ReportSchema.headers_for(report, result)
24
16
  write_csv(headers, result[:items])
25
17
  end
26
18
 
27
19
  private
28
20
 
29
21
  def headers_for(report, result)
30
- return ['language', (result[:metric] || 'bytes').to_s, 'percent', 'color'] if report == 'languages'
31
-
32
- HEADERS.fetch(report) do
33
- raise ArgumentError, "CSV output for report '#{report}' is not supported yet"
34
- end
22
+ # Deprecated: use ReportSchema.headers_for instead
23
+ ReportSchema.headers_for(report, result)
35
24
  end
36
25
 
37
26
  def write_csv(headers, rows)
@@ -1,9 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'report_schema'
4
+
3
5
  module PrettyGit
4
6
  module Render
5
7
  # Renders Markdown tables and sections per docs/output_formats.md
6
- # rubocop:disable Metrics/ClassLength
7
8
  class MarkdownRenderer
8
9
  TITLES = {
9
10
  'activity' => 'Activity',
@@ -16,15 +17,6 @@ module PrettyGit
16
17
  'ownership' => 'Ownership'
17
18
  }.freeze
18
19
 
19
- HEADERS = {
20
- 'activity' => %w[bucket timestamp commits additions deletions],
21
- 'authors' => %w[author author_email commits additions deletions avg_commit_size],
22
- 'files' => %w[path commits additions deletions changes],
23
- 'heatmap' => %w[dow hour commits],
24
- 'hotspots' => %w[path score commits additions deletions changes],
25
- 'churn' => %w[path churn commits additions deletions],
26
- 'ownership' => %w[path owner owner_share authors]
27
- }.freeze
28
20
  def initialize(io: $stdout)
29
21
  @io = io
30
22
  end
@@ -32,7 +24,7 @@ module PrettyGit
32
24
  def call(report, result, _filters)
33
25
  return render_summary(result) if report == 'summary'
34
26
 
35
- headers = headers_for(report, result)
27
+ headers = ReportSchema.headers_for(report, result)
36
28
  title = title_for(report)
37
29
  rows = sort_rows(report, result[:items], result)
38
30
  render_table(title, headers, rows)
@@ -41,9 +33,8 @@ module PrettyGit
41
33
  private
42
34
 
43
35
  def headers_for(report, result)
44
- return ['language', (result[:metric] || 'bytes').to_s, 'percent', 'color'] if report == 'languages'
45
-
46
- HEADERS.fetch(report, [])
36
+ # Deprecated: use ReportSchema.headers_for instead
37
+ ReportSchema.headers_for(report, result)
47
38
  end
48
39
 
49
40
  def title_for(report)
@@ -131,6 +122,5 @@ module PrettyGit
131
122
  (val || '').to_s
132
123
  end
133
124
  end
134
- # rubocop:enable Metrics/ClassLength
135
125
  end
136
126
  end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrettyGit
4
+ module Render
5
+ # Centralized schema definitions for report headers and structure.
6
+ # Used by all renderers (CSV, Markdown, etc.) to ensure consistency.
7
+ module ReportSchema
8
+ # Standard headers for each report type
9
+ HEADERS = {
10
+ 'activity' => %w[bucket timestamp commits additions deletions],
11
+ 'authors' => %w[author author_email commits additions deletions avg_commit_size],
12
+ 'files' => %w[path commits additions deletions changes],
13
+ 'heatmap' => %w[dow hour commits],
14
+ 'hotspots' => %w[path score commits additions deletions changes],
15
+ 'churn' => %w[path churn commits additions deletions],
16
+ 'ownership' => %w[path owner owner_share authors],
17
+ 'summary' => %w[metric value] # not typically tabular but defined for completeness
18
+ }.freeze
19
+
20
+ module_function
21
+
22
+ # Returns headers for the given report type
23
+ # @param report [String] Report name
24
+ # @param result [Hash] Result hash (used for languages metric detection)
25
+ # @return [Array<String>] Array of header names
26
+ def headers_for(report, result = nil)
27
+ return languages_headers(result) if report == 'languages'
28
+
29
+ HEADERS[report] || raise(ArgumentError, "Unknown report type: #{report}")
30
+ end
31
+
32
+ # Languages report has dynamic headers based on metric
33
+ def languages_headers(result)
34
+ metric = result&.dig(:metric) || 'bytes'
35
+ ['language', metric.to_s, 'percent', 'color']
36
+ end
37
+ end
38
+ end
39
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PrettyGit
4
- VERSION = '0.1.5'
4
+ VERSION = '0.1.6'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pretty-git
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pretty Git Authors
@@ -83,6 +83,7 @@ files:
83
83
  - lib/pretty_git/render/json_renderer.rb
84
84
  - lib/pretty_git/render/languages_section.rb
85
85
  - lib/pretty_git/render/markdown_renderer.rb
86
+ - lib/pretty_git/render/report_schema.rb
86
87
  - lib/pretty_git/render/terminal_width.rb
87
88
  - lib/pretty_git/render/xml_renderer.rb
88
89
  - lib/pretty_git/render/yaml_renderer.rb