changelog-builder 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +66 -0
- data/.gitignore +0 -6
- data/.rubocop.yml +4 -9
- data/.rubocop_todo.yml +89 -0
- data/CHANGELOG.md +2 -119
- data/Gemfile +7 -7
- data/Gemfile.lock +2 -2
- data/README.md +14 -3
- data/Rakefile +5 -5
- data/bin/console +3 -3
- data/bin/release +100 -0
- data/changelog-builder.gemspec +38 -0
- data/exe/changelogger +1 -1
- data/lib/changelogger/branches_window.rb +40 -41
- data/lib/changelogger/changelog_generator.rb +4 -4
- data/lib/changelogger/cli.rb +34 -33
- data/lib/changelogger/git.rb +3 -3
- data/lib/changelogger/header.rb +2 -2
- data/lib/changelogger/main.rb +9 -9
- data/lib/changelogger/preview_window.rb +12 -12
- data/lib/changelogger/repo_info.rb +7 -7
- data/lib/changelogger/tui.rb +3 -3
- data/lib/changelogger/version.rb +1 -1
- data/lib/changelogger/versioner.rb +1 -1
- data/lib/changelogger.rb +1 -1
- data/rakelib/docs.rake +73 -0
- metadata +8 -3
- data/changelogger.gemspec +0 -37
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
|
|
5
|
-
require_relative
|
|
6
|
-
require_relative
|
|
7
|
-
require_relative
|
|
8
|
-
require_relative "repo_info"
|
|
3
|
+
require 'curses'
|
|
4
|
+
require_relative 'git'
|
|
5
|
+
require_relative 'versioner'
|
|
6
|
+
require_relative 'changelog_generator'
|
|
7
|
+
require_relative 'repo_info'
|
|
9
8
|
|
|
10
9
|
module Changelogger
|
|
11
10
|
# +Changelogger::Graph+ caches `git log --graph` output for rendering.
|
|
12
11
|
class Graph
|
|
13
12
|
class << self
|
|
14
13
|
# +Changelogger::Graph::FILENAME+ stores the graph cache file name.
|
|
15
|
-
FILENAME =
|
|
14
|
+
FILENAME = '.graph'
|
|
16
15
|
|
|
17
16
|
# +Changelogger::Graph.ensure!+ -> void
|
|
18
17
|
#
|
|
@@ -23,9 +22,9 @@ module Changelogger
|
|
|
23
22
|
if content.nil? || content.strip.empty?
|
|
24
23
|
content = "(no git graph available — empty repo or not a git repository)\n"
|
|
25
24
|
end
|
|
26
|
-
File.
|
|
25
|
+
File.write(FILENAME, content)
|
|
27
26
|
rescue StandardError => e
|
|
28
|
-
File.
|
|
27
|
+
File.write(FILENAME, "(error generating graph: #{e.message})\n")
|
|
29
28
|
end
|
|
30
29
|
|
|
31
30
|
# +Changelogger::Graph.width+ -> Integer
|
|
@@ -36,7 +35,7 @@ module Changelogger
|
|
|
36
35
|
ensure! unless File.exist?(FILENAME)
|
|
37
36
|
ensure!
|
|
38
37
|
max = 1
|
|
39
|
-
|
|
38
|
+
File.foreach(FILENAME) { |line| max = [max, line.rstrip.length].max }
|
|
40
39
|
max
|
|
41
40
|
end
|
|
42
41
|
|
|
@@ -183,7 +182,7 @@ module Changelogger
|
|
|
183
182
|
# Update titles with repo name/branch/HEAD and remote slug/identifier.
|
|
184
183
|
# @return [void]
|
|
185
184
|
def update_titles
|
|
186
|
-
dirty = @repo.dirty ?
|
|
185
|
+
dirty = @repo.dirty ? '*' : ''
|
|
187
186
|
left_title = " Graph — #{@repo.name} [#{@repo.branch}@#{@repo.head_short}#{dirty}] "
|
|
188
187
|
draw_title(@left_frame, left_title)
|
|
189
188
|
right_id = @repo.remote_slug || @repo.name
|
|
@@ -199,7 +198,7 @@ module Changelogger
|
|
|
199
198
|
width = frame.maxx
|
|
200
199
|
text = label[0, [width - 4, 0].max]
|
|
201
200
|
frame.setpos(0, 2)
|
|
202
|
-
frame.addstr(
|
|
201
|
+
frame.addstr(' ' * [width - 4, 0].max)
|
|
203
202
|
frame.setpos(0, 2)
|
|
204
203
|
frame.addstr(text)
|
|
205
204
|
frame.refresh
|
|
@@ -211,10 +210,10 @@ module Changelogger
|
|
|
211
210
|
update_titles
|
|
212
211
|
if @focus == :left
|
|
213
212
|
@left_frame.setpos(0, 2)
|
|
214
|
-
@left_frame.attron(Curses::A_BOLD) { @left_frame.addstr(
|
|
213
|
+
@left_frame.attron(Curses::A_BOLD) { @left_frame.addstr('') }
|
|
215
214
|
else
|
|
216
215
|
@right_frame.setpos(0, 2)
|
|
217
|
-
@right_frame.attron(Curses::A_BOLD) { @right_frame.addstr(
|
|
216
|
+
@right_frame.attron(Curses::A_BOLD) { @right_frame.addstr('') }
|
|
218
217
|
end
|
|
219
218
|
@left_frame.refresh
|
|
220
219
|
@right_frame.refresh
|
|
@@ -223,12 +222,12 @@ module Changelogger
|
|
|
223
222
|
# Help texts for the help bars.
|
|
224
223
|
# @return [String]
|
|
225
224
|
def left_help_text
|
|
226
|
-
|
|
225
|
+
'↑/↓ j/k move • Space select • Tab focus • Enter generate • PgUp/PgDn • f fit • r refresh • z zebra • </> split'
|
|
227
226
|
end
|
|
228
227
|
|
|
229
228
|
# @return [String]
|
|
230
229
|
def right_help_text
|
|
231
|
-
|
|
230
|
+
'↑/↓ j/k scroll • PgUp/PgDn • g top • G bottom • Tab focus'
|
|
232
231
|
end
|
|
233
232
|
|
|
234
233
|
# Number of content rows (excluding help bar) on the left pane.
|
|
@@ -318,7 +317,7 @@ module Changelogger
|
|
|
318
317
|
# @param [Integer] abs_index
|
|
319
318
|
# @return [String, nil] short or full SHA token
|
|
320
319
|
def header_sha_at(abs_index)
|
|
321
|
-
line = @lines[abs_index] ||
|
|
320
|
+
line = @lines[abs_index] || ''
|
|
322
321
|
m = line.match(/\b([a-f0-9]{7,40})\b/i)
|
|
323
322
|
m && m[1]
|
|
324
323
|
end
|
|
@@ -329,7 +328,7 @@ module Changelogger
|
|
|
329
328
|
def find_header_index_by_sha(sha)
|
|
330
329
|
return nil if sha.nil?
|
|
331
330
|
|
|
332
|
-
@headers.find_index { |abs| (@lines[abs] ||
|
|
331
|
+
@headers.find_index { |abs| (@lines[abs] || '').include?(sha[0, 7]) }
|
|
333
332
|
end
|
|
334
333
|
|
|
335
334
|
# Current commit block line range.
|
|
@@ -400,7 +399,7 @@ module Changelogger
|
|
|
400
399
|
TXT
|
|
401
400
|
end
|
|
402
401
|
|
|
403
|
-
@preview_lines = (content ||
|
|
402
|
+
@preview_lines = (content || '').split("\n")
|
|
404
403
|
@preview_offset = 0 if reset_offset
|
|
405
404
|
clamp_preview_offset
|
|
406
405
|
redraw_right
|
|
@@ -472,19 +471,19 @@ module Changelogger
|
|
|
472
471
|
@left_sub.erase
|
|
473
472
|
|
|
474
473
|
@left_sub.setpos(0, 0)
|
|
475
|
-
help = left_help_text.ljust(@left_sub.maxx,
|
|
474
|
+
help = left_help_text.ljust(@left_sub.maxx, ' ')[0, @left_sub.maxx]
|
|
476
475
|
addstr_with_attr(@left_sub, help, @style_help)
|
|
477
476
|
|
|
478
477
|
content_h = left_content_rows
|
|
479
478
|
highlight = current_commit_range
|
|
480
|
-
selected_header_abs = @selected_header_idxs.
|
|
479
|
+
selected_header_abs = @selected_header_idxs.to_set { |i| @headers[i] }
|
|
481
480
|
|
|
482
481
|
visible = @lines[@offset, content_h] || []
|
|
483
482
|
visible.each_with_index do |line, i|
|
|
484
483
|
idx = @offset + i
|
|
485
484
|
@left_sub.setpos(i + 1, 0)
|
|
486
485
|
|
|
487
|
-
text = line.ljust(@left_sub.maxx,
|
|
486
|
+
text = line.ljust(@left_sub.maxx, ' ')[0, @left_sub.maxx]
|
|
488
487
|
|
|
489
488
|
attr =
|
|
490
489
|
if selected_header_abs.include?(idx)
|
|
@@ -505,13 +504,13 @@ module Changelogger
|
|
|
505
504
|
next unless sep_width.positive?
|
|
506
505
|
|
|
507
506
|
@left_sub.setpos(i + 1, start_col)
|
|
508
|
-
pattern =
|
|
507
|
+
pattern = '┄' * sep_width
|
|
509
508
|
addstr_with_attr(@left_sub, pattern[0, sep_width], @style_sep)
|
|
510
509
|
end
|
|
511
510
|
|
|
512
511
|
(visible.length...content_h).each do |i|
|
|
513
512
|
@left_sub.setpos(i + 1, 0)
|
|
514
|
-
@left_sub.addstr(
|
|
513
|
+
@left_sub.addstr(' ' * @left_sub.maxx)
|
|
515
514
|
end
|
|
516
515
|
|
|
517
516
|
@left_sub.refresh
|
|
@@ -523,7 +522,7 @@ module Changelogger
|
|
|
523
522
|
@right_sub.erase
|
|
524
523
|
|
|
525
524
|
@right_sub.setpos(0, 0)
|
|
526
|
-
help = right_help_text.ljust(@right_sub.maxx,
|
|
525
|
+
help = right_help_text.ljust(@right_sub.maxx, ' ')[0, @right_sub.maxx]
|
|
527
526
|
addstr_with_attr(@right_sub, help, @style_help)
|
|
528
527
|
|
|
529
528
|
content_h = right_content_rows
|
|
@@ -531,12 +530,12 @@ module Changelogger
|
|
|
531
530
|
visible = @preview_lines[@preview_offset, content_h] || []
|
|
532
531
|
visible.each_with_index do |line, i|
|
|
533
532
|
@right_sub.setpos(i + 1, 0)
|
|
534
|
-
@right_sub.addstr(line.ljust(@right_sub.maxx,
|
|
533
|
+
@right_sub.addstr(line.ljust(@right_sub.maxx, ' ')[0, @right_sub.maxx])
|
|
535
534
|
end
|
|
536
535
|
|
|
537
536
|
(visible.length...content_h).each do |i|
|
|
538
537
|
@right_sub.setpos(i + 1, 0)
|
|
539
|
-
@right_sub.addstr(
|
|
538
|
+
@right_sub.addstr(' ' * @right_sub.maxx)
|
|
540
539
|
end
|
|
541
540
|
|
|
542
541
|
@right_sub.refresh
|
|
@@ -550,7 +549,7 @@ module Changelogger
|
|
|
550
549
|
return if win.maxy <= 0 || win.maxx <= 0
|
|
551
550
|
|
|
552
551
|
win.setpos(win.maxy - 1, 0)
|
|
553
|
-
txt = msg.ljust(win.maxx,
|
|
552
|
+
txt = msg.ljust(win.maxx, ' ')[0, win.maxx]
|
|
554
553
|
win.attron(Curses::A_BOLD)
|
|
555
554
|
win.addstr(txt)
|
|
556
555
|
win.attroff(Curses::A_BOLD)
|
|
@@ -574,26 +573,26 @@ module Changelogger
|
|
|
574
573
|
def normalize_key(ch)
|
|
575
574
|
return :none if ch.nil?
|
|
576
575
|
|
|
577
|
-
return :tab if ch == "\t" || ch == 9 || (kc = key_const(:TAB)) && ch == kc
|
|
576
|
+
return :tab if ch == "\t" || ch == 9 || ((kc = key_const(:TAB)) && ch == kc)
|
|
578
577
|
return :shift_tab if (kc = key_const(:BTAB)) && ch == kc
|
|
579
|
-
return :quit if [
|
|
578
|
+
return :quit if ['q', 27].include?(ch)
|
|
580
579
|
|
|
581
580
|
enter_key = key_const(:ENTER)
|
|
582
581
|
return :enter if ch == "\r" || ch == "\n" || ch == 10 || ch == 13 || (enter_key && ch == enter_key)
|
|
583
582
|
|
|
584
|
-
return :up if ch == key_const(:UP) || ch ==
|
|
585
|
-
return :down if ch == key_const(:DOWN) || ch ==
|
|
583
|
+
return :up if ch == key_const(:UP) || ch == 'k'
|
|
584
|
+
return :down if ch == key_const(:DOWN) || ch == 'j'
|
|
586
585
|
return :page_up if ch == key_const(:PPAGE)
|
|
587
586
|
return :page_down if ch == key_const(:NPAGE)
|
|
588
587
|
|
|
589
|
-
return :toggle if ch ==
|
|
590
|
-
return :fit if ch ==
|
|
591
|
-
return :refresh if ch ==
|
|
592
|
-
return :zebra if ch ==
|
|
593
|
-
return :g if ch ==
|
|
594
|
-
return :G if ch ==
|
|
595
|
-
return :lt if ch ==
|
|
596
|
-
return :gt if ch ==
|
|
588
|
+
return :toggle if ch == ' '
|
|
589
|
+
return :fit if ch == 'f'
|
|
590
|
+
return :refresh if ch == 'r'
|
|
591
|
+
return :zebra if ch == 'z'
|
|
592
|
+
return :g if ch == 'g'
|
|
593
|
+
return :G if ch == 'G'
|
|
594
|
+
return :lt if ch == '<'
|
|
595
|
+
return :gt if ch == '>'
|
|
597
596
|
|
|
598
597
|
:other
|
|
599
598
|
end
|
|
@@ -618,7 +617,7 @@ module Changelogger
|
|
|
618
617
|
@selected_shas = shas
|
|
619
618
|
break
|
|
620
619
|
else
|
|
621
|
-
flash_message(@left_sub,
|
|
620
|
+
flash_message(@left_sub, 'Select at least 2 commits (space)')
|
|
622
621
|
end
|
|
623
622
|
when :lt
|
|
624
623
|
adjust_split(-4)
|
|
@@ -14,10 +14,10 @@ module Changelogger
|
|
|
14
14
|
versioned.map do |(_i, c, v)|
|
|
15
15
|
lines = []
|
|
16
16
|
lines << "## [#{v}] - #{c.date}"
|
|
17
|
-
lines <<
|
|
17
|
+
lines << ''
|
|
18
18
|
lines << "- #{c.subject} (#{c.short})"
|
|
19
19
|
c.body.split("\n").each { |b| lines << " #{b}" } unless c.body.nil? || c.body.empty?
|
|
20
|
-
lines <<
|
|
20
|
+
lines << ''
|
|
21
21
|
lines.join("\n")
|
|
22
22
|
end.join("\n")
|
|
23
23
|
end
|
|
@@ -40,7 +40,7 @@ module Changelogger
|
|
|
40
40
|
full_idx = sha_to_idx.index(sha)
|
|
41
41
|
full_idx || short_to_idx.index(sha[0, 7])
|
|
42
42
|
end
|
|
43
|
-
raise
|
|
43
|
+
raise 'Need at least 2 valid commits selected' if anchor_indices.size < 2
|
|
44
44
|
|
|
45
45
|
versioned = Versioner.assign(commits, anchor_indices, major: major, minor_start: minor_start,
|
|
46
46
|
base_patch: base_patch)
|
|
@@ -60,7 +60,7 @@ module Changelogger
|
|
|
60
60
|
# @param [Integer] minor_start
|
|
61
61
|
# @param [Integer] base_patch
|
|
62
62
|
# @return [String] path
|
|
63
|
-
def generate(commits, anchor_shas, path:
|
|
63
|
+
def generate(commits, anchor_shas, path: 'CHANGELOG.md', major: 0, minor_start: 1, base_patch: 10)
|
|
64
64
|
content = render(commits, anchor_shas, major: major, minor_start: minor_start, base_patch: base_patch)
|
|
65
65
|
File.write(path, content)
|
|
66
66
|
path
|
data/lib/changelogger/cli.rb
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
3
|
+
require 'optparse'
|
|
4
|
+
require 'tmpdir'
|
|
5
|
+
require 'fileutils'
|
|
6
6
|
|
|
7
|
-
require_relative
|
|
8
|
-
require_relative
|
|
9
|
-
require_relative
|
|
7
|
+
require_relative 'version'
|
|
8
|
+
require_relative 'git'
|
|
9
|
+
require_relative 'changelog_generator'
|
|
10
10
|
|
|
11
11
|
module Changelogger
|
|
12
12
|
# +Changelogger::CLI+ provides both TUI and non-interactive CLI entrypoints.
|
|
@@ -29,35 +29,35 @@ module Changelogger
|
|
|
29
29
|
def start(argv)
|
|
30
30
|
mode = :tui
|
|
31
31
|
anchors = []
|
|
32
|
-
output =
|
|
32
|
+
output = 'CHANGELOG.md'
|
|
33
33
|
dry_run = false
|
|
34
34
|
major = 0
|
|
35
35
|
minor_start = 1
|
|
36
36
|
base_patch = 10
|
|
37
37
|
|
|
38
38
|
parser = OptionParser.new do |o|
|
|
39
|
-
o.banner =
|
|
40
|
-
o.separator
|
|
41
|
-
o.separator
|
|
42
|
-
o.separator
|
|
43
|
-
o.separator
|
|
44
|
-
o.separator
|
|
45
|
-
o.separator
|
|
46
|
-
o.on(
|
|
47
|
-
o.on(
|
|
39
|
+
o.banner = 'Usage: changelogger [options] [REPO]'
|
|
40
|
+
o.separator ''
|
|
41
|
+
o.separator 'REPO can be:'
|
|
42
|
+
o.separator ' - local path (/path/to/repo)'
|
|
43
|
+
o.separator ' - GitHub slug (owner/repo)'
|
|
44
|
+
o.separator ' - git URL (https://... or git@...)'
|
|
45
|
+
o.separator ''
|
|
46
|
+
o.on('-g', '--generate', 'Non-interactive: generate CHANGELOG from anchors') { mode = :generate }
|
|
47
|
+
o.on('-a', '--anchors x,y,z', Array, 'Anchors (SHA/tag/branch), 2+ required in chronological order') do |v|
|
|
48
48
|
anchors = v || []
|
|
49
49
|
end
|
|
50
|
-
o.on(
|
|
51
|
-
o.on(
|
|
52
|
-
o.on(
|
|
53
|
-
o.on(
|
|
54
|
-
o.on(
|
|
55
|
-
o.on(
|
|
56
|
-
o.on(
|
|
50
|
+
o.on('-o', '--output PATH', 'Output file (default: CHANGELOG.md)') { |v| output = v }
|
|
51
|
+
o.on('--major N', Integer, 'Major version (default: 0)') { |v| major = v }
|
|
52
|
+
o.on('--minor-start N', Integer, 'Minor start index (default: 1)') { |v| minor_start = v }
|
|
53
|
+
o.on('--base-patch N', Integer, 'Patch spacing base (default: 10)') { |v| base_patch = v }
|
|
54
|
+
o.on('--dry-run', 'Print to stdout (do not write file)') { dry_run = true }
|
|
55
|
+
o.on('--tui', 'Force interactive TUI (default if no --generate)') { mode = :tui }
|
|
56
|
+
o.on('-v', '--version', 'Print version') do
|
|
57
57
|
puts Changelogger::VERSION
|
|
58
58
|
return 0
|
|
59
59
|
end
|
|
60
|
-
o.on(
|
|
60
|
+
o.on('-h', '--help', 'Show help') do
|
|
61
61
|
puts o
|
|
62
62
|
return 0
|
|
63
63
|
end
|
|
@@ -93,7 +93,7 @@ module Changelogger
|
|
|
93
93
|
# @param [Integer] base_patch
|
|
94
94
|
# @return [Integer] exit code
|
|
95
95
|
def run_tui(output, major, minor_start, base_patch)
|
|
96
|
-
require_relative
|
|
96
|
+
require_relative 'tui'
|
|
97
97
|
selected = Changelogger::TUI.run
|
|
98
98
|
return 0 if selected.nil? # cancelled
|
|
99
99
|
|
|
@@ -110,7 +110,7 @@ module Changelogger
|
|
|
110
110
|
puts "Wrote #{path} ✅"
|
|
111
111
|
0
|
|
112
112
|
else
|
|
113
|
-
puts
|
|
113
|
+
puts 'No CHANGELOG generated (need at least 2 commits).'
|
|
114
114
|
1
|
|
115
115
|
end
|
|
116
116
|
end
|
|
@@ -127,7 +127,7 @@ module Changelogger
|
|
|
127
127
|
# @return [Integer] exit code
|
|
128
128
|
def run_generate(anchor_tokens, output, dry_run, major, minor_start, base_patch)
|
|
129
129
|
if anchor_tokens.size < 2
|
|
130
|
-
warn
|
|
130
|
+
warn 'Error: --generate requires at least 2 --anchors (SHA/tag/branch).'
|
|
131
131
|
return 2
|
|
132
132
|
end
|
|
133
133
|
|
|
@@ -183,17 +183,18 @@ module Changelogger
|
|
|
183
183
|
else
|
|
184
184
|
url = looks_like_url?(repo_spec) ? repo_spec : github_slug_to_url(repo_spec)
|
|
185
185
|
if url
|
|
186
|
-
tmp_dir = Dir.mktmpdir(
|
|
187
|
-
clone_ok = system(
|
|
186
|
+
tmp_dir = Dir.mktmpdir('changelogger-')
|
|
187
|
+
clone_ok = system('git', 'clone', '--no-checkout', '--filter=blob:none', '--depth=1000', url, tmp_dir,
|
|
188
188
|
out: File::NULL, err: File::NULL)
|
|
189
|
-
clone_ok ||= system(
|
|
189
|
+
clone_ok ||= system('git', 'clone', url, tmp_dir)
|
|
190
190
|
if clone_ok
|
|
191
191
|
Dir.chdir(tmp_dir)
|
|
192
192
|
else
|
|
193
193
|
warn "Failed to clone #{url}. Running in current directory."
|
|
194
194
|
end
|
|
195
195
|
else
|
|
196
|
-
warn "Unrecognized repo argument: #{repo_spec.inspect}. Expected a directory,
|
|
196
|
+
warn "Unrecognized repo argument: #{repo_spec.inspect}. Expected a directory, " \
|
|
197
|
+
'GitHub slug (owner/repo), or git URL.'
|
|
197
198
|
end
|
|
198
199
|
end
|
|
199
200
|
end
|
|
@@ -212,14 +213,14 @@ module Changelogger
|
|
|
212
213
|
# +Changelogger::CLI#inside_git_repo?+ -> Bool
|
|
213
214
|
# @return [Bool] true if inside a git work tree
|
|
214
215
|
def inside_git_repo?
|
|
215
|
-
system(
|
|
216
|
+
system('git', 'rev-parse', '--is-inside-work-tree', out: File::NULL, err: File::NULL)
|
|
216
217
|
end
|
|
217
218
|
|
|
218
219
|
# +Changelogger::CLI#looks_like_url?+ -> Bool
|
|
219
220
|
# @param [String] s
|
|
220
221
|
# @return [Bool]
|
|
221
222
|
def looks_like_url?(s)
|
|
222
|
-
s =~ %r{\Ahttps?://} || s.start_with?(
|
|
223
|
+
s =~ %r{\Ahttps?://} || s.start_with?('git@')
|
|
223
224
|
end
|
|
224
225
|
|
|
225
226
|
# +Changelogger::CLI#github_slug_to_url+ -> String, nil
|
data/lib/changelogger/git.rb
CHANGED
|
@@ -26,7 +26,7 @@ module Changelogger
|
|
|
26
26
|
# Uses: git log --date=short --reverse --pretty=format:'...'
|
|
27
27
|
#
|
|
28
28
|
# @return [Array<Commit>]
|
|
29
|
-
def self.commits
|
|
29
|
+
def self.commits
|
|
30
30
|
format = "%H#{SEP}%h#{SEP}%ad#{SEP}%s#{SEP}%b"
|
|
31
31
|
cmd = "git log --date=short --reverse --pretty=format:'#{format}'"
|
|
32
32
|
out = `#{cmd}`
|
|
@@ -36,8 +36,8 @@ module Changelogger
|
|
|
36
36
|
sha: sha,
|
|
37
37
|
short: short,
|
|
38
38
|
date: date,
|
|
39
|
-
subject: (subject ||
|
|
40
|
-
body: (body ||
|
|
39
|
+
subject: (subject || '').strip,
|
|
40
|
+
body: (body || '').strip
|
|
41
41
|
)
|
|
42
42
|
end
|
|
43
43
|
end
|
data/lib/changelogger/header.rb
CHANGED
|
@@ -29,7 +29,7 @@ module Changelogger
|
|
|
29
29
|
# @return [void]
|
|
30
30
|
def header_win
|
|
31
31
|
@header_win = Curses::Window.new(@height, @width, @top, @left)
|
|
32
|
-
@header_win.box(
|
|
32
|
+
@header_win.box(' ', ' ', ' ')
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
# +Changelogger::Header.line+ -> void
|
|
@@ -38,7 +38,7 @@ module Changelogger
|
|
|
38
38
|
# @return [void]
|
|
39
39
|
def line
|
|
40
40
|
line = @header_win.subwin(@height, @width, @top, @left)
|
|
41
|
-
line.addstr(" Changelogger #{Changelogger::VERSION} ".center(@width,
|
|
41
|
+
line.addstr(" Changelogger #{Changelogger::VERSION} ".center(@width, '='))
|
|
42
42
|
line.refresh
|
|
43
43
|
end
|
|
44
44
|
end
|
data/lib/changelogger/main.rb
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: false
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require_relative
|
|
5
|
-
require_relative
|
|
6
|
-
require_relative
|
|
7
|
-
require_relative
|
|
8
|
-
require_relative
|
|
9
|
-
require_relative
|
|
3
|
+
require 'curses'
|
|
4
|
+
require_relative 'header'
|
|
5
|
+
require_relative 'branches_window'
|
|
6
|
+
require_relative 'git'
|
|
7
|
+
require_relative 'versioner'
|
|
8
|
+
require_relative 'changelog_generator'
|
|
9
|
+
require_relative 'preview_window'
|
|
10
10
|
|
|
11
11
|
Curses.init_screen
|
|
12
12
|
Curses.cbreak
|
|
@@ -22,8 +22,8 @@ if selected.nil?
|
|
|
22
22
|
# ESC/q
|
|
23
23
|
elsif selected.size >= 2
|
|
24
24
|
commits = Changelogger::Git.commits
|
|
25
|
-
path = Changelogger::ChangelogGenerator.generate(commits, selected, path:
|
|
25
|
+
path = Changelogger::ChangelogGenerator.generate(commits, selected, path: 'CHANGELOG.md')
|
|
26
26
|
puts "Wrote #{path} ✅"
|
|
27
27
|
else
|
|
28
|
-
puts
|
|
28
|
+
puts 'No CHANGELOG generated (need at least 2 commits).'
|
|
29
29
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require 'curses'
|
|
4
4
|
|
|
5
5
|
module Changelogger
|
|
6
6
|
# +Changelogger::PreviewWindow+ shows scrollable text in a framed window.
|
|
@@ -13,7 +13,7 @@ module Changelogger
|
|
|
13
13
|
# @param [Integer] left left column position
|
|
14
14
|
# @param [Integer, nil] height window height or computed from screen
|
|
15
15
|
# @param [Integer, nil] width window width or computed from screen
|
|
16
|
-
def initialize(title:
|
|
16
|
+
def initialize(title: 'Preview', content: '', top: 1, left: 0, height: nil, width: nil)
|
|
17
17
|
@title = title
|
|
18
18
|
screen_h = Curses.lines
|
|
19
19
|
screen_w = Curses.cols
|
|
@@ -29,7 +29,7 @@ module Changelogger
|
|
|
29
29
|
@sub_left = @left + 1
|
|
30
30
|
|
|
31
31
|
@offset = 0
|
|
32
|
-
@lines = (content ||
|
|
32
|
+
@lines = (content || '').split("\n")
|
|
33
33
|
|
|
34
34
|
build_windows
|
|
35
35
|
redraw
|
|
@@ -41,7 +41,7 @@ module Changelogger
|
|
|
41
41
|
# @param [String] text new content
|
|
42
42
|
# @return [void]
|
|
43
43
|
def update_content(text)
|
|
44
|
-
@lines = (text ||
|
|
44
|
+
@lines = (text || '').split("\n")
|
|
45
45
|
@offset = 0
|
|
46
46
|
redraw
|
|
47
47
|
end
|
|
@@ -53,10 +53,10 @@ module Changelogger
|
|
|
53
53
|
def run
|
|
54
54
|
loop do
|
|
55
55
|
case @sub.getch
|
|
56
|
-
when Curses::Key::UP,
|
|
56
|
+
when Curses::Key::UP, 'k'
|
|
57
57
|
@offset = [@offset - 1, 0].max
|
|
58
58
|
redraw
|
|
59
|
-
when Curses::Key::DOWN,
|
|
59
|
+
when Curses::Key::DOWN, 'j'
|
|
60
60
|
max_off = [@lines.length - @sub_height, 0].max
|
|
61
61
|
@offset = [@offset + 1, max_off].min
|
|
62
62
|
redraw
|
|
@@ -67,13 +67,13 @@ module Changelogger
|
|
|
67
67
|
max_off = [@lines.length - @sub_height, 0].max
|
|
68
68
|
@offset = [@offset + @sub_height, max_off].min
|
|
69
69
|
redraw
|
|
70
|
-
when
|
|
70
|
+
when 'g'
|
|
71
71
|
@offset = 0
|
|
72
72
|
redraw
|
|
73
|
-
when
|
|
73
|
+
when 'G'
|
|
74
74
|
@offset = [@lines.length - @sub_height, 0].max
|
|
75
75
|
redraw
|
|
76
|
-
when
|
|
76
|
+
when 'q', 27
|
|
77
77
|
break
|
|
78
78
|
end
|
|
79
79
|
end
|
|
@@ -98,7 +98,7 @@ module Changelogger
|
|
|
98
98
|
|
|
99
99
|
def draw_title
|
|
100
100
|
title = " #{@title} "
|
|
101
|
-
bar = title.center(@width - 2,
|
|
101
|
+
bar = title.center(@width - 2, '─')
|
|
102
102
|
@frame.setpos(0, 1)
|
|
103
103
|
@frame.addstr(bar[0, @width - 2])
|
|
104
104
|
end
|
|
@@ -109,12 +109,12 @@ module Changelogger
|
|
|
109
109
|
visible = @lines[@offset, @sub_height] || []
|
|
110
110
|
visible.each_with_index do |line, i|
|
|
111
111
|
@sub.setpos(i, 0)
|
|
112
|
-
@sub.addstr(line.ljust(@sub_width,
|
|
112
|
+
@sub.addstr(line.ljust(@sub_width, ' ')[0, @sub_width])
|
|
113
113
|
end
|
|
114
114
|
|
|
115
115
|
(visible.length...@sub_height).each do |i|
|
|
116
116
|
@sub.setpos(i, 0)
|
|
117
|
-
@sub.addstr(
|
|
117
|
+
@sub.addstr(' ' * @sub_width)
|
|
118
118
|
end
|
|
119
119
|
|
|
120
120
|
@sub.refresh
|
|
@@ -27,13 +27,13 @@ module Changelogger
|
|
|
27
27
|
# Reads repo root, branch, HEAD short sha, origin url, and dirty flag.
|
|
28
28
|
# @return [RepoInfo]
|
|
29
29
|
def info
|
|
30
|
-
path = cmd(
|
|
30
|
+
path = cmd('git rev-parse --show-toplevel').strip
|
|
31
31
|
name = path.empty? ? File.basename(Dir.pwd) : File.basename(path)
|
|
32
|
-
branch = cmd(
|
|
33
|
-
branch =
|
|
34
|
-
head_short = cmd(
|
|
35
|
-
remote = cmd(
|
|
36
|
-
dirty = !cmd(
|
|
32
|
+
branch = cmd('git rev-parse --abbrev-ref HEAD').strip
|
|
33
|
+
branch = '(detached)' if branch.empty? || branch == 'HEAD'
|
|
34
|
+
head_short = cmd('git rev-parse --short HEAD').strip
|
|
35
|
+
remote = cmd('git config --get remote.origin.url').strip
|
|
36
|
+
dirty = !cmd('git status --porcelain').strip.empty?
|
|
37
37
|
|
|
38
38
|
RepoInfo.new(
|
|
39
39
|
name: name,
|
|
@@ -56,7 +56,7 @@ module Changelogger
|
|
|
56
56
|
def cmd(s)
|
|
57
57
|
`#{s} 2>/dev/null`
|
|
58
58
|
rescue StandardError
|
|
59
|
-
|
|
59
|
+
''
|
|
60
60
|
end
|
|
61
61
|
|
|
62
62
|
# +Changelogger::Repo.to_slug+ -> String, nil
|
data/lib/changelogger/tui.rb
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require_relative
|
|
5
|
-
require_relative
|
|
3
|
+
require 'curses'
|
|
4
|
+
require_relative 'header'
|
|
5
|
+
require_relative 'branches_window'
|
|
6
6
|
|
|
7
7
|
module Changelogger
|
|
8
8
|
# +Changelogger::TUI+ wraps curses lifecycle and runs the side-by-side UI.
|
data/lib/changelogger/version.rb
CHANGED
|
@@ -36,7 +36,7 @@ module Changelogger
|
|
|
36
36
|
# @return [Array<(Integer, Changelogger::Commit, String)>] each element is [index, commit, "x.y.z"]
|
|
37
37
|
# @raise [ArgumentError] if fewer than 2 anchors are provided
|
|
38
38
|
def assign(commits, anchor_indices, major: 0, minor_start: 1, base_patch: 10)
|
|
39
|
-
raise ArgumentError,
|
|
39
|
+
raise ArgumentError, 'Need at least 2 anchors' if anchor_indices.size < 2
|
|
40
40
|
|
|
41
41
|
anchor_indices = anchor_indices.sort.uniq
|
|
42
42
|
version_map = {}
|
data/lib/changelogger.rb
CHANGED