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 +4 -4
- data/CHANGELOG.md +63 -4
- data/lib/pretty_git/analytics/languages.rb +9 -6
- data/lib/pretty_git/cli.rb +16 -3
- data/lib/pretty_git/cli_helpers.rb +3 -0
- data/lib/pretty_git/git/provider.rb +41 -3
- data/lib/pretty_git/render/console_renderer.rb +25 -7
- data/lib/pretty_git/render/csv_renderer.rb +4 -15
- data/lib/pretty_git/render/markdown_renderer.rb +5 -15
- data/lib/pretty_git/render/report_schema.rb +39 -0
- data/lib/pretty_git/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 00b27dd7b14328c3aa2e5d3bf865dd778d512131d3f3b1c7d9fe1ca76939c5e6
|
4
|
+
data.tar.gz: 36c9f80ac26f1982bf2a4b2f1616bf5d309dec2c9dcda12603e2e2422500e3a4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
|
data/lib/pretty_git/cli.rb
CHANGED
@@ -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
|
59
|
-
|
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
|
-
|
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
|
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
|
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' ?
|
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' ?
|
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(
|
47
|
+
apply(AnsiCodes::DIM, text, enabled)
|
30
48
|
end
|
31
49
|
|
32
50
|
def green(text, enabled)
|
33
|
-
apply(
|
51
|
+
apply(AnsiCodes::GREEN, text, enabled)
|
34
52
|
end
|
35
53
|
|
36
54
|
def red(text, enabled)
|
37
|
-
apply(
|
55
|
+
apply(AnsiCodes::RED, text, enabled)
|
38
56
|
end
|
39
57
|
|
40
58
|
def yellow(text, enabled)
|
41
|
-
apply(
|
59
|
+
apply(AnsiCodes::YELLOW, text, enabled)
|
42
60
|
end
|
43
61
|
|
44
62
|
def bold(text, enabled)
|
45
|
-
apply(
|
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
|
-
|
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
|
-
|
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
|
data/lib/pretty_git/version.rb
CHANGED
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.
|
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
|