captive-release 0.15.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 615663c58dbf71da94afba56edf11a7c269c288e419458ab147109174cb32b70
4
- data.tar.gz: c00fa194187b633dd2b2560cb14f5fce0b9de2247734c3f5cb1caf8ba23cbade
3
+ metadata.gz: 6aec56835e050f42afb89b87243974752868add0b16f258291845acce96f8e75
4
+ data.tar.gz: bdfc4aeba04e48e427938799f76aeb5e172bc4d7894955a9ad470030e12696a0
5
5
  SHA512:
6
- metadata.gz: 98ec33d80bc912c27c262225173c783aa94d34013219377dab148ce633f975f14cd8214e17d7e25608d03ea79c8b2bcde1b3549840ada1665881a2448bd7170e
7
- data.tar.gz: 516607116274624df99f5a98ce15cf13881e1fb75d697fc7e451eb8258f1b14fcede77c5855fb3045bcb78f51755c484fd7f9c7b25876bebd16a6a264d7cefa2
6
+ metadata.gz: ec482bf2fa5e0fa0eb93f23229ab9c9b2c1881965911c8636aaf1a07532b998cd89c0c37c4bbea2f607ab65e16a55123560b318cec9dcf379274b18665ae3f10
7
+ data.tar.gz: 712f8c60ac915352808867261630b3014b2aa42e9e7cb0f28a3421d6471ea06886faf396fb6215c372f9eb3d766f943626f15456b065836992a69c5ad34b0d2d
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CaptiveRelease
4
+ # Le choix au clavier : ↑/↓ déplacent le curseur, Entrée valide, et rend le rang choisi.
5
+ #
6
+ # Cette unité change quand le geste au clavier ou son rendu change, Menu quand la façon de
7
+ # choisir entre ce geste et la liste numérotée change.
8
+ class ArrowSelect
9
+ MOUVEMENTS = { up: -1, down: 1 }.freeze
10
+
11
+ def initialize(terminal:, keys:)
12
+ @terminal = terminal
13
+ @keys = keys
14
+ end
15
+
16
+ def call(libelles)
17
+ rang = 0
18
+ loop do
19
+ draw(libelles, rang)
20
+ touche = @keys.read
21
+ return rang if touche == :enter
22
+
23
+ rang = (rang + MOUVEMENTS.fetch(touche, 0)) % libelles.size
24
+ # Remonte d'autant de lignes que le menu en occupe, pour le redessiner sur place.
25
+ @terminal.prompt "\e[#{libelles.size}A\e[J"
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def draw(libelles, choisi)
32
+ libelles.each_with_index do |libelle, rang|
33
+ @terminal.say rang == choisi ? @terminal.ok(" › #{libelle}") : " #{@terminal.dim(libelle)}"
34
+ end
35
+ end
36
+ end
37
+ end
@@ -11,11 +11,9 @@ module CaptiveRelease
11
11
  end
12
12
 
13
13
  def call(framework, workflow_par_defaut)
14
- @terminal.say "\n#{@terminal.bold(Messages.t(:framework_detected, framework: framework))} " \
15
- "#{Messages.t(:configure_build)}\n"
16
- @terminal.say " #{@terminal.dim("1)")} #{Messages.t(:yes)}"
17
- @terminal.say " #{@terminal.dim("2)")} #{Messages.t(:no)}"
18
- return nil unless @terminal.ask_choice == 1
14
+ titre = "\n#{@terminal.bold(Messages.t(:framework_detected, framework: framework))} " \
15
+ "#{Messages.t(:configure_build)}"
16
+ return nil unless Menu.new(terminal: @terminal).choose(titre, Messages.t(:yes) => true, Messages.t(:no) => false)
19
17
 
20
18
  BuildConfig.new(build_workflow: workflow(workflow_par_defaut))
21
19
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "io/console"
4
+
5
+ module CaptiveRelease
6
+ # Le clavier touche par touche, en mode raw, pour le sélecteur à flèches.
7
+ #
8
+ # Cette unité change quand la lecture des touches change, Terminal quand l'habillage ou la
9
+ # saisie ligne à ligne change.
10
+ class KeyReader
11
+ KEYS = { "\e[A" => :up, "\e[B" => :down, "\r" => :enter, "\n" => :enter, "k" => :up, "j" => :down }.freeze
12
+
13
+ # Une flèche arrive d'un bloc en trois octets (`\e[A`) : les lire en une fois évite
14
+ # d'attendre une suite qui ne viendrait pas après un Échap seul.
15
+ RAFALE = 3
16
+
17
+ def initialize(input: $stdin, output: $stdout)
18
+ @input = input
19
+ @output = output
20
+ end
21
+
22
+ # Le mode raw exige un vrai terminal des deux côtés. Ailleurs (CI, pipe, specs), le menu
23
+ # numéroté prend le relais.
24
+ def interactive? = @input.tty? && @output.tty?
25
+
26
+ # :up, :down, :enter, ou le caractère lu. Ctrl-C reste une interruption, le mode raw
27
+ # l'ayant sinon avalé.
28
+ def read
29
+ touche = @input.raw { |io| io.readpartial(RAFALE) }
30
+ raise Interrupt if touche == "\u0003"
31
+
32
+ KEYS.fetch(touche, touche)
33
+ end
34
+ end
35
+ end
@@ -3,28 +3,31 @@
3
3
  module CaptiveRelease
4
4
  # Une liste numérotée, un choix, et le refus de ce qui n'est pas dans la liste.
5
5
  #
6
- # Un rang négatif est refusé comme un rang trop grand : sans cette garde, une saisie non
6
+ # Un rang hors liste est refusé, négatif compris : sans cette garde, une saisie non
7
7
  # numérique vaut 0, donc l'indice -1, et Ruby rendait silencieusement la dernière entrée.
8
+ # Dans un terminal, le choix se fait aux flèches (ArrowSelect) et ne peut sortir de la liste.
8
9
  class Menu
9
- def initialize(terminal:)
10
+ def initialize(terminal:, keys: KeyReader.new)
10
11
  @terminal = terminal
12
+ @keys = keys
11
13
  end
12
14
 
13
15
  # entries : un Hash {libellé affiché => valeur rendue}, dans l'ordre d'affichage.
14
16
  def choose(title, entries)
15
17
  @terminal.say "#{title}\n"
16
- entries.each_key.with_index do |libelle, rang|
17
- @terminal.say " #{@terminal.dim("#{rang + 1})")} #{libelle}"
18
- end
19
-
20
- rang = @terminal.ask_choice - 1
21
- refuse if rang.negative? || rang >= entries.size
22
-
23
- entries.values[rang]
18
+ libelles = entries.keys
19
+ entries.values[@keys.interactive? ? ArrowSelect.new(terminal: @terminal, keys: @keys).call(libelles) : numbered(libelles)]
24
20
  end
25
21
 
26
22
  private
27
23
 
24
+ def numbered(libelles)
25
+ libelles.each_with_index { |libelle, rang| @terminal.say " #{@terminal.dim("#{rang + 1})")} #{libelle}" }
26
+ rang = @terminal.ask_choice - 1
27
+ refuse unless rang.between?(0, libelles.size - 1)
28
+ rang
29
+ end
30
+
28
31
  def refuse
29
32
  @terminal.say @terminal.error(Messages.t(:invalid_choice))
30
33
  exit 1
@@ -6,9 +6,12 @@ module CaptiveRelease
6
6
  # Cette unité change quand l'habillage change, les commandes quand leur geste change. Les
7
7
  # deux flux sont injectés, ce qui rend chaque commande testable sans toucher à $stdin.
8
8
  class Terminal
9
- def initialize(input: $stdin, output: $stdout)
9
+ # https://no-color.org : la couleur s'éteint avec NO_COLOR, ou quand la sortie n'est pas un
10
+ # terminal (pipe, fichier de log) où les séquences ANSI deviendraient du bruit.
11
+ def initialize(input: $stdin, output: $stdout, color: output.tty? && ENV.fetch("NO_COLOR", "").empty?)
10
12
  @input = input
11
13
  @output = output
14
+ @color = color
12
15
  end
13
16
 
14
17
  def say(message = "") = @output.puts(message)
@@ -33,11 +36,15 @@ module CaptiveRelease
33
36
  # pas eux-mêmes pour les liens OSC 8.
34
37
  def link(url, texte = url) = "\e]8;;#{url}\e\\#{underline(texte)}\e]8;;\e\\"
35
38
 
36
- def bold(str) = "\e[1m#{str}\e[0m"
37
- def underline(str) = "\e[4m#{str}\e[0m"
38
- def dim(str) = "\e[2m#{str}\e[0m"
39
- def ok(str) = "\e[32m#{str}\e[0m"
40
- def warn(str) = "\e[33m#{str}\e[0m"
41
- def error(str) = "\e[31m#{str}\e[0m"
39
+ def bold(str) = paint(1, str)
40
+ def underline(str) = paint(4, str)
41
+ def dim(str) = paint(2, str)
42
+ def ok(str) = paint(32, str)
43
+ def warn(str) = paint(33, str)
44
+ def error(str) = paint(31, str)
45
+
46
+ private
47
+
48
+ def paint(code, str) = @color ? "\e[#{code}m#{str}\e[0m" : str.to_s
42
49
  end
43
50
  end
@@ -29,7 +29,7 @@ module CaptiveRelease
29
29
  derniere = @thread&.join(ATTENTE)&.value
30
30
  return unless derniere && Gem::Version.new(derniere) > Gem::Version.new(courante)
31
31
 
32
- @output.puts "\e[33m#{Messages.t(:update_available, current: courante, latest: derniere)}\e[0m"
32
+ @output.puts Terminal.new(output: @output).warn(Messages.t(:update_available, current: courante, latest: derniere))
33
33
  end
34
34
 
35
35
  private
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CaptiveRelease
4
- VERSION = "0.15.0"
4
+ VERSION = "0.16.0"
5
5
  end
@@ -3,6 +3,8 @@
3
3
  require_relative "captive_release/version"
4
4
  require_relative "captive_release/messages"
5
5
  require_relative "captive_release/semver"
6
+ require_relative "captive_release/arrow_select"
7
+ require_relative "captive_release/key_reader"
6
8
  require_relative "captive_release/prerelease_iteration"
7
9
  require_relative "captive_release/version_bumper"
8
10
  require_relative "captive_release/ruby_gems_version"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: captive-release
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.0
4
+ version: 0.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Captive Studio
@@ -20,6 +20,7 @@ extra_rdoc_files: []
20
20
  files:
21
21
  - bin/captive-release
22
22
  - lib/captive_release.rb
23
+ - lib/captive_release/arrow_select.rb
23
24
  - lib/captive_release/build_command.rb
24
25
  - lib/captive_release/build_config.rb
25
26
  - lib/captive_release/build_config_prompt.rb
@@ -50,6 +51,7 @@ files:
50
51
  - lib/captive_release/gitmoji_severity.rb
51
52
  - lib/captive_release/help.rb
52
53
  - lib/captive_release/init_command.rb
54
+ - lib/captive_release/key_reader.rb
53
55
  - lib/captive_release/locale.rb
54
56
  - lib/captive_release/locales/en.yml
55
57
  - lib/captive_release/locales/fr.yml