squared 0.5.11 → 0.6.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: 56af2850e96c00e48e1511b20fc61c5f0d9969220b4b070a6c61c02ce6489e97
4
- data.tar.gz: 83749c3414fa0496dfa64fb3ec0846af2205b1c4483ecbd44e72ed2d976c7852
3
+ metadata.gz: ed078210213186745643650b4a73f7f376b5aba7033643c850b48d555c198501
4
+ data.tar.gz: d4d8c0e6308f3bdc3aca66891980259ad52ce11f3a53112c7a22ae4de9e31f40
5
5
  SHA512:
6
- metadata.gz: 4fbc69f9efe06c1d2ceba44c58854ca31b0a7e50a6fe89c01e3aeff05830efae2f7bf07f068163352f6c54f9d347a5db11b308a8f0b3f3b64914ac6b0035743b
7
- data.tar.gz: 7aada08d98786700e652f0a78e5ddeda72ada420ec9d89cffc937f706b27da4800374231d086b67be8ae748199d0229a02fb95a9dfaac349fff6c40997ab5099
6
+ metadata.gz: fcf308f894ee49566103575561b74459afc1aa7e03af8d5c5397c7bb7432dfce996cbb1291f4f81af792d5b1dd8ed6a9075ce7b2fab6f41dca0c56431e38b78c
7
+ data.tar.gz: 19f25542363b33fa567cbfcb0267c79fbc55a313ce0fba8ab25ec17dd43f276b004b00304e8bfe38dba1338a2e554170a8f1535d1bdc5981407fae6e9bff2bf8
data/CHANGELOG.md CHANGED
@@ -1,5 +1,129 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.6.0] - 2025-10-31
4
+
5
+ ### Added
6
+
7
+ - Docker command compose action create was implemented.
8
+ - Docker command compose action ps was implemented.
9
+ - Docker command compose action ps is interactive.
10
+ - Project base asdf commands [latest|where|update] were implemented.
11
+ - Pip command options were updated to 25.2.
12
+ - Command line programs who do not assign with equals are supported.
13
+ - Docker command ls with column formatting was implemented.
14
+ - Git command stash action staged and worktree were implemented.
15
+ - Git command diff action files can output to a patch file.
16
+ - Repo task init with REPO_Y can auto-accept overwrite confirmation.
17
+ - Caller locations for errors can be excluded using ARG[:BACKTRACE].
18
+ - Node command package action reinstall was implemented.
19
+ - Node class method and command tsc was implemented.
20
+ - Python command pip action reinstall was implemented.
21
+ - Project command options can be stored by class ref and project name.
22
+ - Bundler command config is interactive.
23
+ - Ruby method bundle supports stored command options.
24
+ - Ruby method rake supports stored command options.
25
+ - Ruby method ruby supports stored command options.
26
+ - Ruby method gem supports stored command options.
27
+ - Python method pip supports stored command options.
28
+ - Python command pip action wheel was implemented.
29
+ - Ruby method irb supports stored command options.
30
+ - Ruby command gem action command was implemented.
31
+ - ENV supplementary command options can be space delimited.
32
+ - Project command options can extend other stored options.
33
+ - Ruby command gem action outdated can update without being interactive.
34
+ - Ruby command gem action update system can accept a specific version.
35
+ - Ruby command gem action outdated supports threading.
36
+ - Git command grep was implemented.
37
+ - Common format bright colors fallback to standard colors when not enabled.
38
+ - Project base command unpack action gz was implemented.
39
+ - Common format defined String instance method subhint.
40
+ - Application readers [exception|pipe|verbose|warning] were converted into accessors.
41
+ - Node private projects can publish entire workspace with tags.
42
+ - Node command outdated can select a range of packages to update.
43
+ - Gem command outdated can select a range of packages to update.
44
+ - Python command outdated can select a range of packages to update.
45
+ - Node command outdated option diff was implemented.
46
+ - Bundler command outdated can select a range of packages to update.
47
+ - Common prompt method choice can select all items with "\*".
48
+ - Node command bump can auto-commit package.json using GIT_MESSAGE.
49
+ - Git command options were updated to version 2.51.
50
+
51
+ ### Changed
52
+
53
+ - Git method source will raise error when checking exit status.
54
+ - Date gem is no longer globally required.
55
+ - Global task git:all was removed.
56
+ - Common base method as_a was relocated to Utils.
57
+ - Python command build and publish detects only one backend.
58
+ - Docker command compose action ps was renamed service.
59
+ - Docker command image action list was renamed ls.
60
+ - Project base method success? conditionally calls print_success method.
61
+ - Error classes were not specified for invalid operation.
62
+ - Common format methods stripstyle and stripext are String instance methods.
63
+ - Common format method alias puts_oe was removed.
64
+ - Git commands without a valid action will exit with error status.
65
+ - Errno exceptions are used in place of SystemCallError.
66
+ - Common format method raise_error defaults to RuntimeError.
67
+ - OptionPartition delegator method delete_at was renamed remove_at.
68
+ - Node event name "publish" was renamed to "npm:publish".
69
+ - Project task run with a Method passes self as first argument.
70
+ - Ruby command check was removed.
71
+ - Ruby method gem! was renamed gem.
72
+ - Ruby methods ruby and irb first argument was converted into kwargs.
73
+ - Ruby method rake executes all tasks in one run command.
74
+ - Ruby project file methods return nil instead of false.
75
+ - Ruby commands [exec|cache|config] were relocated under bundle namespace.
76
+ - Ruby command gem action update without arguments is not modified.
77
+ - Ruby command install was replaced with reinstall under bundle namespace.
78
+ - Ruby command update was relocated under bundle namespace.
79
+ - Common prompt confirm timeout was revised for non-immediate responses.
80
+ - Project relative paths are stripped from banner when context is displayed.
81
+ - Workspace module Support extend submodules private methods.
82
+ - Workspace method add passes parent through Project constructor.
83
+ - Project base method run_s uses last argument for ENV hash.
84
+ - Project base method inject does not exit program when raised.
85
+ - Reline gem is installed only when using at least Ruby 3.0.
86
+ - Python method install arguments strategy and user were removed.
87
+ - Project command outdated does not support threading when interactive.
88
+ - Project base accessor global was converted into a reader and setter.
89
+ - Repo build can simultaneously use script and run through REPO_BUILD.
90
+ - Repo static method read_manifest was made an instance method named repo_manifest.
91
+
92
+ ### Fixed
93
+
94
+ - Node task update did not check equivalent ENV install options.
95
+
96
+ ## [0.5.12] - 2025-10-31
97
+
98
+ ### Changed
99
+
100
+ - Ruby task depend does not override bundle config without setting.
101
+
102
+ ### Fixed
103
+
104
+ - Node command run did nothing at all when called upon.
105
+
106
+ ## [0.4.26] - 2025-10-31
107
+
108
+ ### Added
109
+
110
+ - Docker command options were updated to 28.5.
111
+
112
+ ### Changed
113
+
114
+ - Node package manager does not override package.json with NODE_INSTALL.
115
+ - Node workspaces can specify more than one package manager through NODE_INSTALL.
116
+
117
+ ### Fixed
118
+
119
+ - Node command add did not provide optional save argument.
120
+ - Python command outdated did not detect synchronous output.
121
+ - Node command outdated auto-commit was completely dysfunctional.
122
+ - Project workspaces caused a complete meltdown on TruffleRuby.
123
+ - Project build initialization global flag was always incoherent.
124
+ - Project base setters did not accept direct values.
125
+ - Project base global graph tasks are not definable by a sub-project.
126
+
3
127
  ## [0.5.11] - 2025-10-18
4
128
 
5
129
  ### Fixed
@@ -1153,6 +1277,8 @@
1153
1277
 
1154
1278
  - Changelog was created.
1155
1279
 
1280
+ [0.6.0]: https://github.com/anpham6/squared-ruby/releases/tag/v0.6.0
1281
+ [0.5.12]: https://github.com/anpham6/squared-ruby/releases/tag/v0.5.12
1156
1282
  [0.5.11]: https://github.com/anpham6/squared-ruby/releases/tag/v0.5.11
1157
1283
  [0.5.10]: https://github.com/anpham6/squared-ruby/releases/tag/v0.5.10
1158
1284
  [0.5.9]: https://github.com/anpham6/squared-ruby/releases/tag/v0.5.9
@@ -1165,6 +1291,7 @@
1165
1291
  [0.5.2]: https://github.com/anpham6/squared-ruby/releases/tag/v0.5.2-ruby
1166
1292
  [0.5.1]: https://github.com/anpham6/squared-ruby/releases/tag/v0.5.1-ruby
1167
1293
  [0.5.0]: https://github.com/anpham6/squared-ruby/releases/tag/v0.5.0-ruby
1294
+ [0.4.26]: https://github.com/anpham6/squared-ruby/releases/tag/v0.4.26
1168
1295
  [0.4.25]: https://github.com/anpham6/squared-ruby/releases/tag/v0.4.25
1169
1296
  [0.4.24]: https://github.com/anpham6/squared-ruby/releases/tag/v0.4.24
1170
1297
  [0.4.23]: https://github.com/anpham6/squared-ruby/releases/tag/v0.4.23
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # squared 0.5
1
+ # squared 0.6
2
2
 
3
3
  * [source](https://github.com/anpham6/squared-ruby)
4
4
  * [docs](https://squared.readthedocs.io)
@@ -14,6 +14,7 @@
14
14
  | 2025-06-16 | 0.5.0 | 2.5.0 | 3.4.3 |
15
15
  | ---------- | ----- | ----- | ----- |
16
16
  | 2025-08-23 | 0.5.5 | | 3.4.5 |
17
+ | 2025-10-31 | 0.6.0 | | 3.4.7 |
17
18
 
18
19
  The range chart indicates the latest Ruby tested against at the time of release.
19
20
 
@@ -142,20 +143,59 @@ Workspace::Application
142
143
  # android = /workspaces/android-docs
143
144
  # chrome = /workspaces/chrome-docs
144
145
 
146
+ # ...symbol?, string, ...string?, ...symbol?
147
+ # extend name args project
148
+
149
+ Node = Workspace::Project::Node # tsc
150
+ Node.options("build:dev", "target=es2022", project: "tsconfig.json") # :node (ref)
151
+ Node.options("build:dev", "outDir=tmp", :squared) # squared (project name)
152
+
153
+ Ruby = Workspace::Project::Ruby # ruby | gem | rake | bundle | irb
154
+ Ruby.options("lint", "rubocop", opts: ["gemfile=Gemfile"]) # :ruby
155
+ Ruby.options("lint", "--parallel", :squared)
156
+
157
+ Python = Workspace::Project::Python # pip
158
+ Python.options("build", opts: ["r=requirements.txt"]) # :python
159
+ Python.options(:build, "build:dev", opts: ["no-deps"])
160
+
145
161
  Workspace::Application
146
- .new(ENV["SQUARED_HOME"], prefix: "rb", common: false) # Local styles
162
+ .new(ENV["SQUARED_HOME"], prefix: "rb", common: false) # Local styles
147
163
  .group("ruby", "default", run: "rake build", copy: "rake install", clean: "rake clean", ref: :ruby, override: {
148
164
  pathname: {
149
- run: "rake compile" # rake rb:pathname:build
165
+ run: "rake compile" # rake rb:pathname:build
150
166
  }
151
167
  })
152
- .with(:python, editable: false) do # ref=Symbol | group=String
153
- banner([:name, ": ", :version], "path") # chrome-docs: 0.1.0 | /workspaces/chrome-docs
154
- doc("make html") # rake rb:doc:python
155
- run(false) # rake rb:build:python (disabled)
156
- exclude(%i[base git]) # Project::Git.ref (superclass)
157
- add("android-docs", "android") # rake rb:android:doc
158
- add("chrome-docs", "chrome") # rake rb:chrome:doc
168
+ .add("squared") do
169
+ apply :run, proc { # rake rb:squared:build
170
+ tsc(with: scriptname || "build:dev", verbose: true) # Repo (global)
171
+ # OR
172
+ tsc("target=es2022", "outDir=tmp", project: "tsconfig.json") # 1
173
+ tsc("clean=true", "./sqd/tsconfig.json", build: true) # 2
174
+ run("npm run build:dev", { "RUBYOPT" => "-W2" }, from: :run) # 3
175
+
176
+ # Parallel
177
+ [
178
+ Thread.new { tsc(sync: false) },
179
+ Thread.new { tsc(sync: false) },
180
+ Thread.new { run(sync: false) }
181
+ ]
182
+ .each(&:join)
183
+ }
184
+ apply :lint, proc {
185
+ bundle("exec", "-A --cache=true", with: "lint", verbose: true) # bundle exec --gemfile='/squared/Gemfile' rubocop --parallel -A --cache=true
186
+ }
187
+ end
188
+ .with(:python, editable: false) do # ref=Symbol | group=String
189
+ banner([:name, ": ", :version], "path") # chrome-docs: 0.1.0 | /workspaces/chrome-docs
190
+ doc("make html") # rake rb:doc:python
191
+ run(false) # rake rb:build:python (disabled)
192
+ exclude(%i[base git]) # Project::Git.ref (superclass)
193
+ add("android-docs", "android") # rake rb:android:doc
194
+ add("chrome-docs", "chrome") do # rake rb:chrome:doc
195
+ apply :run, proc {
196
+ pip("wheel", with: "build:dev") # pip wheel -r '/chrome-docs/requirements.txt' --no-deps
197
+ }
198
+ end
159
199
  end
160
200
  .style("inline", "bold")
161
201
  .build
@@ -496,28 +536,28 @@ Non-task:
496
536
 
497
537
  Most project classes will inherit from `Git` which enables these tasks:
498
538
 
499
- | Task | Git | Command |
500
- | :--------- | :--------------- | :-------------------------------------------- |
501
- | branch | branch | create track delete move copy list current |
502
- | checkout | checkout | commit branch track detach path |
503
- | commit | commit | add all amend amend-orig fixup |
504
- | diff | diff | head branch files view between contain |
505
- | fetch | fetch | origin remote all |
506
- | files | ls-files | cached modified deleted others |
507
- | git | | add blame clean mv revert rm status |
508
- | log | log | view between contain |
509
- | merge | merge | commit no-commit send |
510
- | pull | pull | origin remote all |
511
- | rebase | rebase | branch onto send |
512
- | refs | ls-remote --refs | heads tags remote |
513
- | reset | reset | commit index patch mode undo |
514
- | restore | restore | source staged worktree |
515
- | rev | rev | commit build output |
516
- | show | show | format oneline textconv |
517
- | stash | stash | push pop apply branch drop clear list all |
518
- | submodule | submodule | status update branch url sync |
519
- | switch | switch | branch create detach |
520
- | tag | tag | add sign delete list |
539
+ | Task | Git | Command |
540
+ | :--------- | :--------------- | :-------------------------------------------------------- |
541
+ | branch | branch | create track delete move copy list current |
542
+ | checkout | checkout | commit branch track detach path |
543
+ | commit | commit | add all amend amend-orig fixup |
544
+ | diff | diff | head branch files view between contain |
545
+ | fetch | fetch | origin remote all |
546
+ | files | ls-files | cached modified deleted others |
547
+ | git | | add blame clean grep mv revert rm status |
548
+ | log | log | view between contain |
549
+ | merge | merge | commit no-commit send |
550
+ | pull | pull | origin remote all |
551
+ | rebase | rebase | branch onto send |
552
+ | refs | ls-remote --refs | heads tags remote |
553
+ | reset | reset | commit index patch mode undo |
554
+ | restore | restore | source staged worktree |
555
+ | rev | rev | commit build output |
556
+ | show | show | format oneline textconv |
557
+ | stash | stash | push pop apply branch drop clear list all staged worktree |
558
+ | submodule | submodule | status update branch url sync |
559
+ | switch | switch | branch create detach |
560
+ | tag | tag | add sign delete list |
521
561
 
522
562
  You can disable all of them at once using the `exclude` property.
523
563
 
@@ -632,7 +672,7 @@ LOG_LEVEL # See gem "logger"
632
672
 
633
673
  ### Git
634
674
 
635
- * Version: [2.50](https://github.com/git/git/blob/v2.50.0/Documentation/RelNotes/2.50.0.adoc)
675
+ * Version: [2.51](https://github.com/git/git/blob/v2.51.0/Documentation/RelNotes/2.51.0.adoc)
636
676
 
637
677
  ```sh
638
678
  GIT_OPTIONS=q,strategy=ort # all
@@ -688,7 +728,7 @@ GIT_AUTOSTASH_${NAME}=0 # rebase (project only)
688
728
 
689
729
  ### Docker
690
730
 
691
- * Version: [28.3](https://docs.docker.com/engine/release-notes/28)
731
+ * Version: [28.5](https://docs.docker.com/engine/release-notes/28)
692
732
 
693
733
  ```sh
694
734
  DOCKER_OPTIONS=q,no-cache # all
@@ -735,7 +775,7 @@ These global options also can target the application main suffix `${NAME}`. (e.g
735
775
  ```sh
736
776
  REPO_ROOT # parent dir
737
777
  REPO_HOME # project dir (main)
738
- REPO_BUILD # run,script
778
+ REPO_BUILD # script,run (e.g. build:dev | build:dev,make install | make)
739
779
  REPO_GROUP # string
740
780
  REPO_REF # e.g. ruby,node
741
781
  REPO_DEV # pattern,0,1
@@ -747,6 +787,7 @@ REPO_MANIFEST # e.g. latest,nightly,prod
747
787
  REPO_GROUPS # e.g. base,prod,docs
748
788
  REPO_STAGE # 0,1,2,3,4
749
789
  REPO_SUBMODULLES # 0,1
790
+ REPO_Y # 0,1
750
791
  REPO_TIMEOUT # confirm dialog (seconds)
751
792
  ```
752
793
 
@@ -17,6 +17,7 @@ module Squared
17
17
  GRAPH: ['|', '-', '|', '\\', '-'].freeze,
18
18
  BORDER: ['|', '-', '-', '-', '-', '-', '|', '|', '-', '-'].freeze,
19
19
  VIEW: 'view',
20
+ BACKTRACE: $DEBUG || !$VERBOSE.nil?,
20
21
  LEVEL: ENV.fetch('LOG_LEVEL', 0).to_i,
21
22
  COLOR: ENV.fetch('NO_COLOR', '').empty?
22
23
  }
@@ -97,27 +98,5 @@ module Squared
97
98
  VAR[:theme].each_value { |val| val.freeze.each_value(&:freeze) }
98
99
  VAR.freeze
99
100
  end
100
-
101
- module_function
102
-
103
- def as_a(obj, *meth, flat: nil, compact: false, &blk)
104
- return [] if obj.nil?
105
-
106
- unless obj.is_a?(::Array)
107
- obj = if obj.respond_to?(:to_ary)
108
- obj.to_ary
109
- elsif obj.respond_to?(:to_a) && !obj.is_a?(::Hash) && (val = obj.to_a).is_a?(::Array)
110
- val
111
- else
112
- [obj]
113
- end
114
- end
115
- obj = flat.is_a?(::Numeric) ? obj.flatten(flat) : obj.flatten if flat
116
- obj = obj.compact if compact
117
- obj = obj.map(&meth.shift) until meth.empty?
118
- return obj unless block_given?
119
-
120
- obj.select(&blk)
121
- end
122
101
  end
123
102
  end
@@ -39,6 +39,10 @@ module Squared
39
39
  TEXT_STYLE = [:bold, :dim, :italic, :underline, :blinking, nil, :inverse, :hidden, :strikethrough].freeze
40
40
  private_constant :AIX_TERM, :BOX_GRAPH, :BOX_BORDER, :TEXT_STYLE
41
41
 
42
+ String.define_method(:stripstyle) { gsub(/\x1B\[(?:\d+;?)+m/, '') }
43
+ String.define_method(:stripext) { File.basename(self, '.*') }
44
+ String.define_method(:subhint) { |s| s.nil? || (s.is_a?(::String) && s.empty?) ? self : "#{self} (#{s})" }
45
+
42
46
  def enable_aixterm
43
47
  unless (colors = __get__(:colors)).frozen?
44
48
  colors.update(AIX_TERM)
@@ -85,7 +89,7 @@ module Squared
85
89
  end
86
90
  else
87
91
  t = type.to_sym
88
- if (c = __get__(:colors)[t])
92
+ if (c = __get__(:colors)[t] || __get__(:colors)[t.to_s.sub('bright_', '').to_sym])
89
93
  if index == -1
90
94
  s = wrap.call(s, [c])
91
95
  else
@@ -94,7 +98,7 @@ module Squared
94
98
  else
95
99
  next unless (n = TEXT_STYLE.index(t))
96
100
 
97
- s = "\x1B[#{n + 1}m#{s}\x1B[#{n == 0 ? 22 : n + 21}m"
101
+ s = "\x1B[#{n.succ}m#{s}\x1B[#{n == 0 ? 22 : n + 21}m"
98
102
  end
99
103
  end
100
104
  if index == -1
@@ -108,7 +112,7 @@ module Squared
108
112
  ret = wrap.call(ret, code) unless code.empty?
109
113
  return ret unless data
110
114
 
111
- out = ''.dup
115
+ out = +''
112
116
  data.to_a.each_with_index do |group, i|
113
117
  next if i == 0
114
118
 
@@ -126,8 +130,8 @@ module Squared
126
130
  colors = __get__(:colors)
127
131
  Array(args).flatten.compact.each do |val|
128
132
  if !val.is_a?(::Numeric)
129
- val = val.to_sym
130
- ret << val if colors.key?(val) || TEXT_STYLE.include?(val)
133
+ k = val.to_sym
134
+ ret << k if colors.key?(k) || colors.key?(k.to_s.sub('bright_', '').to_sym) || TEXT_STYLE.include?(k)
131
135
  elsif val.between?(0, 256)
132
136
  ret << val
133
137
  elsif val < 0 && (b = val.to_s.split('.')[1])
@@ -151,6 +155,10 @@ module Squared
151
155
  end
152
156
  end
153
157
 
158
+ def opt_style(styles, pat = nil, index = 1)
159
+ { styles: styles, pat: pat, index: index }
160
+ end
161
+
154
162
  def log_sym(level)
155
163
  if level.is_a?(::Numeric)
156
164
  case level
@@ -188,8 +196,12 @@ module Squared
188
196
  end
189
197
  if (args.size > 1 && !hint) || hint == false
190
198
  title = log_title(level, color: false)
191
- sub = [pat: /\A(#{Regexp.escape(title)})(.*)\z/m, styles: __get__(:theme)[:logger][log_sym(level)]] if color
192
- emphasize(args, title: title + (subject ? " #{subject}" : ''), sub: sub, pipe: -1)
199
+ emphasize(args,
200
+ title: title + (subject ? " #{subject}" : ''),
201
+ pipe: -1,
202
+ sub: if color
203
+ opt_style(__get__(:theme)[:logger][log_sym(level)], /\A(#{Regexp.escape(title)})(.*)\z/m)
204
+ end)
193
205
  else
194
206
  msg = [log_title(level, color: color)]
195
207
  msg << (color ? sub_style(subject.to_s, styles: (@theme && @theme[:subject]) || :bold) : subject) if subject
@@ -205,7 +217,7 @@ module Squared
205
217
  begin
206
218
  File.open(pipe, 'a') do |f|
207
219
  br = File::SEPARATOR == '\\' ? "\r\n" : "\n"
208
- args.flatten.each { |val| f.write(strip_style(val.chomp) + br) }
220
+ args.flatten.each { |val| f.write(val.chomp.stripstyle + br) }
209
221
  end
210
222
  return
211
223
  rescue StandardError
@@ -215,13 +227,12 @@ module Squared
215
227
  (pipe == 2 ? $stderr : $stdout).puts(*args)
216
228
  end
217
229
 
218
- alias puts_oe log_console
219
-
220
230
  module_function
221
231
 
222
232
  def message(*args, hint: nil, empty: false, space: ARG[:SPACE])
223
233
  (empty ? args.reject { |val| val.nil? || (val.respond_to?(:empty?) && val.empty?) } : args)
224
- .join(space) + (hint ? " (#{hint})" : '')
234
+ .join(space)
235
+ .subhint(hint)
225
236
  end
226
237
 
227
238
  def emphasize(val, title: nil, footer: nil, right: false, cols: nil, sub: nil, pipe: nil,
@@ -237,7 +248,7 @@ module Squared
237
248
  lines = val.to_s.lines(chomp: true)
238
249
  lines[0] = "#{val.class}: #{lines.first}" if (err = val.is_a?(::StandardError))
239
250
  end
240
- n = cols || max.call(lines)
251
+ n = (cols.is_a?(::Array) ? cols.map(&:size).max : cols) || max.call(lines)
241
252
  if $stdout.tty?
242
253
  require 'io/console'
243
254
  (n = [n, $stdout.winsize[1] - 4].min) rescue nil
@@ -255,14 +266,14 @@ module Squared
255
266
  sub.each { |h| s = sub_style(s, **h) }
256
267
  s = "#{b0} #{s} #{b0}"
257
268
  if border
258
- s = sub_style(s, pat: /\A(#{Regexp.escape(b0)})(.+)\z/m, styles: border)
259
- s = sub_style(s, pat: /\A(.+)(#{Regexp.escape(b0)})\z/m, styles: border, index: 2)
269
+ s = sub_style(s, **opt_style(border, /\A(#{Regexp.escape(b0)})(.+)\z/m))
270
+ s = sub_style(s, **opt_style(border, /\A(.+)(#{Regexp.escape(b0)})\z/m, 2))
260
271
  end
261
272
  s
262
273
  end
263
274
  out << draw.call(b2, b3)
264
275
  if title
265
- out.concat(title.map { |t| pr.call(t) })
276
+ out.concat(title.map! { |t| pr.call(t) })
266
277
  out << draw.call(b6, b7)
267
278
  end
268
279
  lines.each { |line| out << pr.call(line) }
@@ -296,16 +307,13 @@ module Squared
296
307
  end
297
308
  end
298
309
 
299
- def strip_style(val)
300
- val.gsub(/\x1B\[(\d+;?)+m/, '')
301
- end
302
-
303
- def stripext(val)
304
- File.basename(val, '.*')
305
- end
306
-
307
- def raise_error(*args, hint: nil, kind: ArgumentError)
308
- raise kind, message(*args, hint: hint, empty: true), caller_locations(1).map(&:to_s)
310
+ def raise_error(*args, hint: nil, kind: RuntimeError, start: 0)
311
+ kind = args.shift if args.first.is_a?(::Class) && args.first < ::Exception
312
+ raise kind, message(*args, hint: hint, empty: true), if ARG[:BACKTRACE]
313
+ caller(start.succ)
314
+ else
315
+ caller_locations(start.succ, 1).first&.base_label
316
+ end
309
317
  end
310
318
  end
311
319
  end
@@ -1,12 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ unless defined?(Readline)
4
+ if RUBY_ENGINE == 'ruby' && RUBY_VERSION < '2.7'
5
+ require 'readline'
6
+ else
7
+ begin
8
+ require 'reline'
9
+ Object.send(:remove_const, :Readline) if Object.const_defined?(:Readline)
10
+ Readline = Reline
11
+ rescue LoadError
12
+ require 'readline'
13
+ end
14
+ end
15
+ end
16
+
3
17
  module Squared
4
18
  module Common
5
19
  module Prompt
6
20
  module_function
7
21
 
8
- def confirm(msg, default = nil, agree: 'Y', cancel: 'N', attempts: 5, timeout: 30)
9
- require 'readline'
22
+ def confirm(msg, default = nil, agree: 'Y', cancel: 'N', attempts: 3, timeout: 60)
10
23
  require 'timeout'
11
24
  if agree == 'Y' && cancel == 'N' && !msg.match?(%r{\[(?:Yn|nY|Y/n|y/N)\]})
12
25
  case default
@@ -38,9 +51,8 @@ module Squared
38
51
  end
39
52
  end
40
53
 
41
- def choice(msg, list = nil, min: 1, max: 1, multiple: false, force: true, grep: nil, auto: true,
42
- attempts: 5, timeout: 0)
43
- require 'readline'
54
+ def choice(msg, list = nil, min: 1, max: 1, multiple: false, index: false, grep: nil, border: nil, auto: true,
55
+ force: true, attempts: 3, timeout: 0)
44
56
  require 'timeout'
45
57
  if list
46
58
  grep &&= Array(grep).map { |val| Regexp.new(val) }
@@ -52,40 +64,51 @@ module Squared
52
64
  puts '%2d. %s' % [items.size, val]
53
65
  end
54
66
  max = items.size
55
- raise_error 'empty selection list' if max == 0
56
- min = [min, max].min
67
+ raise ArgumentError, 'empty selection list' if max == 0
68
+
69
+ min = grep ? 1 : [min, max].min
57
70
  if auto
58
- msg = "#{msg}: [1-#{max}#{if multiple
59
- "|,#{multiple.is_a?(::Numeric) ? "{#{multiple}}" : ''}"
60
- end}] "
71
+ auto.times { puts } if auto.is_a?(::Numeric)
72
+ if border == true
73
+ puts print_footer
74
+ elsif border
75
+ puts print_footer(border: border)
76
+ end
77
+ msg = "#{msg + (force ? ':' : '?')} [#{min}-#{max}#{if (n = multiple)
78
+ "|,#{n.is_a?(::Numeric) ? "{#{n}}" : '*'}"
79
+ end}] "
61
80
  end
62
81
  end
63
- valid = ->(s) { s.match?(/^\d+$/) && s.to_i.between?(min, max) }
82
+ between = ->(s) { s.match?(/^\d+$/) && s.to_i.between?(min, max) }
64
83
  Timeout.timeout(timeout) do
65
84
  while (ch = Readline.readline(msg))
66
85
  unless (ch = ch.strip).empty?
67
86
  if multiple
68
- a = ch.split(/\s*,\s*/)
69
- b = a.map do |s|
70
- if s =~ /^(\d+)-(\d+)$/
71
- next unless valid.call($1) && valid.call($2)
87
+ k = if ch == '*'
88
+ (min..max).to_a
89
+ else
90
+ ch.split(/\s*,\s*/).map! do |s|
91
+ if s =~ /^(\d+)-(\d+)$/
92
+ next unless between.call($1) && between.call($2)
72
93
 
73
- c = $1.to_i
74
- d = $2.to_i
75
- next (c..d).to_a if c < d
76
- elsif valid.call(s)
77
- s.to_i
94
+ i = $1.to_i
95
+ j = $2.to_i
96
+ next (i..j).to_a if i < j
97
+ elsif between.call(s)
98
+ s.to_i
99
+ end
100
+ end
101
+ end
102
+ unless k.include?(nil)
103
+ k.flatten!
104
+ k.uniq!
105
+ k.sort!
106
+ unless multiple.is_a?(::Numeric) && multiple != k.size
107
+ return index || !items ? k : k.map! { |i| items[i.pred] }
78
108
  end
79
109
  end
80
- unless b.include?(nil)
81
- b.flatten!
82
- b.uniq!
83
- b.sort!
84
- return b unless items
85
- return b.map! { |i| items[i - 1] } unless multiple.is_a?(::Numeric) && multiple != b.size
86
- end
87
- elsif valid.call(ch)
88
- return items ? items[ch.to_i - 1] : ch.to_i
110
+ elsif between.call(ch)
111
+ return index || !items ? ch.to_i : items[ch.to_i.pred]
89
112
  end
90
113
  end
91
114
  attempts -= 1
@@ -98,12 +121,11 @@ module Squared
98
121
  puts
99
122
  exit 0
100
123
  else
101
- multiple ? [] : nil
124
+ [] if multiple
102
125
  end
103
126
  end
104
127
 
105
128
  def readline(msg, history = false, force: nil, multiline: nil, &blk)
106
- require 'readline'
107
129
  multiline = if multiline && Readline.respond_to?(:readmultiline)
108
130
  multiline.is_a?(::Enumerable) || block_given? ? multiline : [multiline.to_s]
109
131
  end
@@ -118,11 +140,11 @@ module Squared
118
140
  end
119
141
  case force
120
142
  when ::TrueClass, ::FalseClass
121
- msg = "#{msg} %s " % if multiline
122
- multiline.is_a?(::Enumerable) ? "{#{multiline.to_a.join('|')}}" : multiline
123
- else
124
- "(#{force ? 'required' : 'optional'}):"
125
- end
143
+ msg = "#{msg}%s%s " % if multiline
144
+ [' ', multiline.is_a?(::Enumerable) ? "{#{multiline.to_a.join('|')}}" : multiline]
145
+ else
146
+ [force ? ':' : '?', '']
147
+ end
126
148
  ret = (prompt.call || '').strip
127
149
  multiline.each { |val| break if ret.delete_suffix!(val.to_s) } if multiline.is_a?(::Enumerable)
128
150
  exit 1 if force && ret.empty?