squared 0.5.25 → 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 +4 -4
- data/CHANGELOG.md +89 -274
- data/README.md +104 -72
- data/lib/squared/common/base.rb +1 -22
- data/lib/squared/common/format.rb +40 -38
- data/lib/squared/common/prompt.rb +58 -40
- data/lib/squared/common/shell.rb +71 -63
- data/lib/squared/common/system.rb +70 -38
- data/lib/squared/common/utils.rb +30 -15
- data/lib/squared/config.rb +27 -34
- data/lib/squared/version.rb +1 -1
- data/lib/squared/workspace/application.rb +87 -113
- data/lib/squared/workspace/project/base.rb +548 -420
- data/lib/squared/workspace/project/docker.rb +410 -296
- data/lib/squared/workspace/project/git.rb +375 -371
- data/lib/squared/workspace/project/node.rb +501 -284
- data/lib/squared/workspace/project/python.rb +375 -255
- data/lib/squared/workspace/project/ruby.rb +697 -397
- data/lib/squared/workspace/project/support/class.rb +210 -213
- data/lib/squared/workspace/repo.rb +47 -44
- data/lib/squared/workspace/series.rb +13 -21
- data/lib/squared/workspace/support/base.rb +3 -24
- data/lib/squared/workspace/support/variables.rb +48 -0
- data/lib/squared/workspace/support.rb +1 -1
- data/lib/squared/workspace.rb +4 -6
- metadata +3 -3
- data/lib/squared/workspace/support/data.rb +0 -11
data/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# squared 0.
|
|
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
|
|
|
@@ -23,9 +24,23 @@ The range chart indicates the latest Ruby tested against at the time of release.
|
|
|
23
24
|
gem install squared
|
|
24
25
|
```
|
|
25
26
|
|
|
27
|
+
### Optional
|
|
28
|
+
|
|
29
|
+
* [Repo](https://source.android.com/docs/setup/reference/repo)
|
|
30
|
+
* https://github.com/anpham6/squared-repo
|
|
31
|
+
* Python 3.6
|
|
32
|
+
* Not compatible with Windows
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
mkdir -p ~/.bin
|
|
36
|
+
PATH="${HOME}/.bin:${PATH}"
|
|
37
|
+
curl https://storage.googleapis.com/git-repo-downloads/repo > ~/.bin/repo
|
|
38
|
+
chmod a+rx ~/.bin/repo
|
|
39
|
+
```
|
|
40
|
+
|
|
26
41
|
## Example - Rakefile
|
|
27
42
|
|
|
28
|
-
Projects from any accessible folder can be added relative to the parent directory or absolutely. Missing projects will simply be excluded from the task runner.
|
|
43
|
+
Projects from any accessible folder can be added relative to the parent directory (e.g. *REPO_ROOT*) or absolutely. The same Rakefile can also manage other similarly cloned `Repo` repositories remotely by setting the `REPO_ROOT` environment variable to the location. Missing projects will simply be excluded from the task runner.
|
|
29
44
|
|
|
30
45
|
```ruby
|
|
31
46
|
require "squared"
|
|
@@ -41,6 +56,14 @@ require "squared/app" # All workspace related mod
|
|
|
41
56
|
|
|
42
57
|
# NODE_ENV = production
|
|
43
58
|
|
|
59
|
+
# REPO_ROOT = /workspaces |
|
|
60
|
+
# REPO_HOME = /workspaces/squared | Dir.pwd
|
|
61
|
+
# rake = /workspaces/squared/Rakefile | main?
|
|
62
|
+
# OR
|
|
63
|
+
# REPO_ROOT = /workspaces | Dir.pwd
|
|
64
|
+
# rake = /workspaces/Rakefile |
|
|
65
|
+
# REPO_HOME = /workspaces/squared | main: "squared"
|
|
66
|
+
|
|
44
67
|
# pathname = /workspaces/pathname
|
|
45
68
|
# optparse = /workspaces/optparse
|
|
46
69
|
# log = /workspaces/logger
|
|
@@ -55,6 +78,7 @@ require "squared/app" # All workspace related mod
|
|
|
55
78
|
Workspace::Application
|
|
56
79
|
.new(Dir.pwd, main: "squared") # Dir.pwd? (main? is implicitly basename)
|
|
57
80
|
.banner("group", "project", styles: ["yellow", "black"], border: "bold") # name | project | path | ref | group? | parent? | version?
|
|
81
|
+
.repo("https://github.com/anpham6/squared-repo", "nightly", script: ["build:dev", "build:prod"], ref: :node) # Repo (optional)
|
|
58
82
|
.run("rake install", ref: :ruby)
|
|
59
83
|
.depend(false, group: "default")
|
|
60
84
|
.clean("rake clean", group: "default")
|
|
@@ -68,7 +92,7 @@ Workspace::Application
|
|
|
68
92
|
.add("e-mc", "emc", copy: { from: "publish", scope: "@e-mc", also: [:pir, "squared-express/"] }, ref: :node) # Node
|
|
69
93
|
.add("pi-r", "pir", copy: { from: "publish", scope: "@pi-r" }, clean: ["publish/**/*.js", "tmp/"]) # Trailing slash required for directories
|
|
70
94
|
.add("pi-r2", "pir2", copy: { from: :npm, also: %i[squared express], files: ["LICENSE", ["README.md.ruby", "README.md"]] }) # Uses dist files from NPM package spec
|
|
71
|
-
.add("squared", init:
|
|
95
|
+
.add("squared", init: 'pnpm', script: ["build:stage1", "build:stage2"], group: "app") do # Use pnpm/yarn/berry for depend + Copy target (main)
|
|
72
96
|
# Repo (global)
|
|
73
97
|
as(:run, "build:dev", "dev") # npm run build:dev -> npm run dev
|
|
74
98
|
as(:run, { "build:dev": "dev", "build:prod": "prod" })
|
|
@@ -119,20 +143,59 @@ Workspace::Application
|
|
|
119
143
|
# android = /workspaces/android-docs
|
|
120
144
|
# chrome = /workspaces/chrome-docs
|
|
121
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
|
+
|
|
122
161
|
Workspace::Application
|
|
123
|
-
.new(ENV["SQUARED_HOME"], prefix: "rb", common: false)
|
|
162
|
+
.new(ENV["SQUARED_HOME"], prefix: "rb", common: false) # Local styles
|
|
124
163
|
.group("ruby", "default", run: "rake build", copy: "rake install", clean: "rake clean", ref: :ruby, override: {
|
|
125
164
|
pathname: {
|
|
126
|
-
run: "rake compile"
|
|
165
|
+
run: "rake compile" # rake rb:pathname:build
|
|
127
166
|
}
|
|
128
167
|
})
|
|
129
|
-
.
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
|
136
199
|
end
|
|
137
200
|
.style("inline", "bold")
|
|
138
201
|
.build
|
|
@@ -473,28 +536,28 @@ Non-task:
|
|
|
473
536
|
|
|
474
537
|
Most project classes will inherit from `Git` which enables these tasks:
|
|
475
538
|
|
|
476
|
-
| Task | Git | Command
|
|
477
|
-
| :--------- | :--------------- |
|
|
478
|
-
| branch | branch | create track delete move copy list current
|
|
479
|
-
| checkout | checkout | commit branch track detach path
|
|
480
|
-
| commit | commit | add all amend amend-orig fixup
|
|
481
|
-
| diff | diff | head branch files view between contain
|
|
482
|
-
| fetch | fetch | origin remote all
|
|
483
|
-
| files | ls-files | cached modified deleted others
|
|
484
|
-
| git | | add blame clean mv revert rm status
|
|
485
|
-
| log | log | view between contain
|
|
486
|
-
| merge | merge | commit no-commit send
|
|
487
|
-
| pull | pull | origin remote all
|
|
488
|
-
| rebase | rebase | branch onto send
|
|
489
|
-
| refs | ls-remote --refs | heads tags remote
|
|
490
|
-
| reset | reset | commit index patch mode undo
|
|
491
|
-
| restore | restore | source staged worktree
|
|
492
|
-
| rev | rev | commit build output
|
|
493
|
-
| show | show | format oneline textconv
|
|
494
|
-
| stash | stash | push pop apply branch drop clear list all
|
|
495
|
-
| submodule | submodule | status update branch url sync
|
|
496
|
-
| switch | switch | branch create detach
|
|
497
|
-
| 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 |
|
|
498
561
|
|
|
499
562
|
You can disable all of them at once using the `exclude` property.
|
|
500
563
|
|
|
@@ -521,7 +584,7 @@ Commands which use commit hashes are parsed using a ":" prefix as to not be conf
|
|
|
521
584
|
|
|
522
585
|
```sh
|
|
523
586
|
rake squared:log:view[:af012345] # git log af012345
|
|
524
|
-
rake squared:log:view[H1,HEAD^5,all,lib,./H12345] # git log --all @~1 @^5 --
|
|
587
|
+
rake squared:log:view[H1,HEAD^5,all,lib,./H12345] # git log --all @~1 @^5 -- 'lib' 'H12345'
|
|
525
588
|
```
|
|
526
589
|
|
|
527
590
|
## Environment
|
|
@@ -609,7 +672,7 @@ LOG_LEVEL # See gem "logger"
|
|
|
609
672
|
|
|
610
673
|
### Git
|
|
611
674
|
|
|
612
|
-
* Version: [2.
|
|
675
|
+
* Version: [2.51](https://github.com/git/git/blob/v2.51.0/Documentation/RelNotes/2.51.0.adoc)
|
|
613
676
|
|
|
614
677
|
```sh
|
|
615
678
|
GIT_OPTIONS=q,strategy=ort # all
|
|
@@ -676,7 +739,7 @@ DOCKER_ALL=1 # list every image/container
|
|
|
676
739
|
DOCKER_Y=1 # confirm all
|
|
677
740
|
|
|
678
741
|
BUILD_SQUARED_OPTS="NODE_TAG=24 RUBY_VERSION=3.4.0" DOCKER_SQUARED_OPTS="--no-cache --label=v1" rake squared:build
|
|
679
|
-
docker build --no-cache --label=v1 --build-arg=
|
|
742
|
+
docker build --no-cache --label=v1 --build-arg='NODE_TAG=24' --build-arg='RUBY_VERSION=3.4.0' .
|
|
680
743
|
```
|
|
681
744
|
|
|
682
745
|
| Command | Flag | ENV |
|
|
@@ -707,58 +770,27 @@ Workspace::Application
|
|
|
707
770
|
|
|
708
771
|
### Repo
|
|
709
772
|
|
|
710
|
-
|
|
711
|
-
* https://github.com/anpham6/squared-repo
|
|
712
|
-
* Python 3.6
|
|
713
|
-
* Not compatible with Windows
|
|
714
|
-
|
|
715
|
-
```sh
|
|
716
|
-
mkdir -p ~/.bin
|
|
717
|
-
PATH="${HOME}/.bin:${PATH}"
|
|
718
|
-
curl https://storage.googleapis.com/git-repo-downloads/repo > ~/.bin/repo
|
|
719
|
-
chmod a+rx ~/.bin/repo
|
|
720
|
-
```
|
|
721
|
-
|
|
722
|
-
```ruby
|
|
723
|
-
# REPO_ROOT = /workspaces |
|
|
724
|
-
# REPO_HOME = /workspaces/squared | Dir.pwd
|
|
725
|
-
# rake = /workspaces/squared/Rakefile | main?
|
|
726
|
-
#
|
|
727
|
-
# OR
|
|
728
|
-
#
|
|
729
|
-
# REPO_ROOT = /workspaces | Dir.pwd
|
|
730
|
-
# rake = /workspaces/Rakefile |
|
|
731
|
-
# REPO_HOME = /workspaces/squared | main: "squared"
|
|
732
|
-
|
|
733
|
-
Workspace::Application
|
|
734
|
-
.new(main: "squared")
|
|
735
|
-
.repo("https://github.com/anpham6/squared-repo", "nightly", script: ["build:dev", "prod"], ref: :node)
|
|
736
|
-
.add("squared", script: ["build:stage1", "build:stage2"])
|
|
737
|
-
.build
|
|
738
|
-
```
|
|
739
|
-
|
|
740
|
-
These global options also can target the application main suffix `${NAME}`. (e.g. *REPO_ROOT_SQUARED*)
|
|
773
|
+
These global options also can target the application main suffix `${NAME}`. (e.g. REPO_ROOT_SQUARED)
|
|
741
774
|
|
|
742
775
|
```sh
|
|
743
776
|
REPO_ROOT # parent dir
|
|
744
777
|
REPO_HOME # project dir (main)
|
|
745
|
-
REPO_BUILD # run,
|
|
778
|
+
REPO_BUILD # script,run (e.g. build:dev | build:dev,make install | make)
|
|
746
779
|
REPO_GROUP # string
|
|
747
780
|
REPO_REF # e.g. ruby,node
|
|
748
781
|
REPO_DEV # pattern,0,1
|
|
749
782
|
REPO_PROD # pattern,0,1
|
|
750
783
|
REPO_WARN # 0,1
|
|
751
784
|
REPO_SYNC # 0,1
|
|
752
|
-
|
|
785
|
+
REPO_URL # manifest repository
|
|
753
786
|
REPO_MANIFEST # e.g. latest,nightly,prod
|
|
754
787
|
REPO_GROUPS # e.g. base,prod,docs
|
|
755
788
|
REPO_STAGE # 0,1,2,3,4
|
|
756
789
|
REPO_SUBMODULLES # 0,1
|
|
790
|
+
REPO_Y # 0,1
|
|
757
791
|
REPO_TIMEOUT # confirm dialog (seconds)
|
|
758
792
|
```
|
|
759
793
|
|
|
760
|
-
Other similarly cloned `Repo` repositories can be managed remotely by setting the `REPO_ROOT` environment variable to the location.
|
|
761
|
-
|
|
762
794
|
## LICENSE
|
|
763
795
|
|
|
764
796
|
BSD 3-Clause
|
data/lib/squared/common/base.rb
CHANGED
|
@@ -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)
|
|
@@ -67,7 +71,6 @@ module Squared
|
|
|
67
71
|
end
|
|
68
72
|
wrap = ->(s, n) { "\x1B[#{n.join(';')}m#{s}\x1B[0m" }
|
|
69
73
|
code = []
|
|
70
|
-
args.clear if args.size == 1 && args.first.nil?
|
|
71
74
|
args.concat(Array(styles)).flatten.each_with_index do |type, i|
|
|
72
75
|
next unless type
|
|
73
76
|
|
|
@@ -86,7 +89,7 @@ module Squared
|
|
|
86
89
|
end
|
|
87
90
|
else
|
|
88
91
|
t = type.to_sym
|
|
89
|
-
if (c = __get__(:colors)[t])
|
|
92
|
+
if (c = __get__(:colors)[t] || __get__(:colors)[t.to_s.sub('bright_', '').to_sym])
|
|
90
93
|
if index == -1
|
|
91
94
|
s = wrap.call(s, [c])
|
|
92
95
|
else
|
|
@@ -95,7 +98,7 @@ module Squared
|
|
|
95
98
|
else
|
|
96
99
|
next unless (n = TEXT_STYLE.index(t))
|
|
97
100
|
|
|
98
|
-
s = "\x1B[#{n
|
|
101
|
+
s = "\x1B[#{n.succ}m#{s}\x1B[#{n == 0 ? 22 : n + 21}m"
|
|
99
102
|
end
|
|
100
103
|
end
|
|
101
104
|
if index == -1
|
|
@@ -127,8 +130,8 @@ module Squared
|
|
|
127
130
|
colors = __get__(:colors)
|
|
128
131
|
Array(args).flatten.compact.each do |val|
|
|
129
132
|
if !val.is_a?(::Numeric)
|
|
130
|
-
|
|
131
|
-
ret <<
|
|
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)
|
|
132
135
|
elsif val.between?(0, 256)
|
|
133
136
|
ret << val
|
|
134
137
|
elsif val < 0 && (b = val.to_s.split('.')[1])
|
|
@@ -152,6 +155,10 @@ module Squared
|
|
|
152
155
|
end
|
|
153
156
|
end
|
|
154
157
|
|
|
158
|
+
def opt_style(styles, pat = nil, index = 1)
|
|
159
|
+
{ styles: styles, pat: pat, index: index }
|
|
160
|
+
end
|
|
161
|
+
|
|
155
162
|
def log_sym(level)
|
|
156
163
|
if level.is_a?(::Numeric)
|
|
157
164
|
case level
|
|
@@ -181,22 +188,23 @@ module Squared
|
|
|
181
188
|
args = args.map(&:to_s)
|
|
182
189
|
if level.is_a?(::Numeric)
|
|
183
190
|
if append && respond_to?(:log)
|
|
184
|
-
|
|
185
|
-
|
|
191
|
+
(log rescue nil).tap do |ref|
|
|
192
|
+
ref.add(level, message(subject, *args, hint: hint, space: ', ')) if ref.is_a?(::Logger)
|
|
193
|
+
end
|
|
186
194
|
end
|
|
187
|
-
return false
|
|
195
|
+
return false if !pass && level < ARG[:LEVEL]
|
|
188
196
|
end
|
|
189
|
-
if
|
|
197
|
+
if (args.size > 1 && !hint) || hint == false
|
|
190
198
|
title = log_title(level, color: false)
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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)
|
|
195
205
|
else
|
|
196
206
|
msg = [log_title(level, color: color)]
|
|
197
|
-
if subject
|
|
198
|
-
msg << (color ? sub_style(subject.to_s, (@theme.is_a?(::Hash) && @theme[:subject]) || :bold) : subject)
|
|
199
|
-
end
|
|
207
|
+
msg << (color ? sub_style(subject.to_s, styles: (@theme && @theme[:subject]) || :bold) : subject) if subject
|
|
200
208
|
msg << args.shift if msg.size == 1
|
|
201
209
|
message(msg.join(' '), *args, hint: hint)
|
|
202
210
|
end
|
|
@@ -209,7 +217,7 @@ module Squared
|
|
|
209
217
|
begin
|
|
210
218
|
File.open(pipe, 'a') do |f|
|
|
211
219
|
br = File::SEPARATOR == '\\' ? "\r\n" : "\n"
|
|
212
|
-
args.flatten.each { |val| f.write(
|
|
220
|
+
args.flatten.each { |val| f.write(val.chomp.stripstyle + br) }
|
|
213
221
|
end
|
|
214
222
|
return
|
|
215
223
|
rescue StandardError
|
|
@@ -219,17 +227,16 @@ module Squared
|
|
|
219
227
|
(pipe == 2 ? $stderr : $stdout).puts(*args)
|
|
220
228
|
end
|
|
221
229
|
|
|
222
|
-
alias puts_oe log_console
|
|
223
|
-
|
|
224
230
|
module_function
|
|
225
231
|
|
|
226
232
|
def message(*args, hint: nil, empty: false, space: ARG[:SPACE])
|
|
227
233
|
(empty ? args.reject { |val| val.nil? || (val.respond_to?(:empty?) && val.empty?) } : args)
|
|
228
|
-
.join(space)
|
|
234
|
+
.join(space)
|
|
235
|
+
.subhint(hint)
|
|
229
236
|
end
|
|
230
237
|
|
|
231
238
|
def emphasize(val, title: nil, footer: nil, right: false, cols: nil, sub: nil, pipe: nil,
|
|
232
|
-
border: @theme
|
|
239
|
+
border: @theme && @theme[:border])
|
|
233
240
|
n = 0
|
|
234
241
|
max = ->(a) { n = [n, a.max_by(&:size).size].max }
|
|
235
242
|
set = ->(s) { Array(s).map(&:to_s).tap { |a| max.call(a) } }
|
|
@@ -241,9 +248,7 @@ module Squared
|
|
|
241
248
|
lines = val.to_s.lines(chomp: true)
|
|
242
249
|
lines[0] = "#{val.class}: #{lines.first}" if (err = val.is_a?(::StandardError))
|
|
243
250
|
end
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
n = cols || max.call(lines)
|
|
251
|
+
n = (cols.is_a?(::Array) ? cols.map(&:size).max : cols) || max.call(lines)
|
|
247
252
|
if $stdout.tty?
|
|
248
253
|
require 'io/console'
|
|
249
254
|
(n = [n, $stdout.winsize[1] - 4].min) rescue nil
|
|
@@ -261,14 +266,14 @@ module Squared
|
|
|
261
266
|
sub.each { |h| s = sub_style(s, **h) }
|
|
262
267
|
s = "#{b0} #{s} #{b0}"
|
|
263
268
|
if border
|
|
264
|
-
s = sub_style(s,
|
|
265
|
-
s = sub_style(s,
|
|
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))
|
|
266
271
|
end
|
|
267
272
|
s
|
|
268
273
|
end
|
|
269
274
|
out << draw.call(b2, b3)
|
|
270
275
|
if title
|
|
271
|
-
out.concat(title.map { |t| pr.call(t) })
|
|
276
|
+
out.concat(title.map! { |t| pr.call(t) })
|
|
272
277
|
out << draw.call(b6, b7)
|
|
273
278
|
end
|
|
274
279
|
lines.each { |line| out << pr.call(line) }
|
|
@@ -296,22 +301,19 @@ module Squared
|
|
|
296
301
|
else
|
|
297
302
|
pipe = $stdout unless pipe.respond_to?(:puts)
|
|
298
303
|
end
|
|
299
|
-
pipe.puts
|
|
304
|
+
pipe.puts out
|
|
300
305
|
else
|
|
301
306
|
err ? warn(out) : puts(out)
|
|
302
307
|
end
|
|
303
308
|
end
|
|
304
309
|
|
|
305
|
-
def
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
def raise_error(*args, hint: nil, kind: ArgumentError)
|
|
314
|
-
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
|
|
315
317
|
end
|
|
316
318
|
end
|
|
317
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:
|
|
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,
|
|
42
|
-
attempts:
|
|
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
|
-
|
|
67
|
+
raise ArgumentError, 'empty selection list' if max == 0
|
|
68
|
+
|
|
56
69
|
min = grep ? 1 : [min, max].min
|
|
57
70
|
if auto
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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)
|
|
73
93
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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] }
|
|
79
108
|
end
|
|
80
109
|
end
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
b.uniq!
|
|
84
|
-
b.sort!
|
|
85
|
-
return items ? b.map! { |i| items[i - 1] } : b 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
|
-
|
|
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
|
|
@@ -113,20 +135,16 @@ module Squared
|
|
|
113
135
|
elsif block_given?
|
|
114
136
|
Readline.readmultiline(msg, history, &blk)
|
|
115
137
|
else
|
|
116
|
-
Readline.readmultiline(msg, history)
|
|
117
|
-
next if line.strip.empty?
|
|
118
|
-
|
|
119
|
-
multiline.any? { |val| line.split.last.end_with?(val.to_s) }
|
|
120
|
-
end
|
|
138
|
+
Readline.readmultiline(msg, history) { |line| multiline.any? { |val| line.split.last.end_with?(val.to_s) } }
|
|
121
139
|
end
|
|
122
140
|
end
|
|
123
141
|
case force
|
|
124
142
|
when ::TrueClass, ::FalseClass
|
|
125
|
-
msg = "#{msg}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
143
|
+
msg = "#{msg}%s%s " % if multiline
|
|
144
|
+
[' ', multiline.is_a?(::Enumerable) ? "{#{multiline.to_a.join('|')}}" : multiline]
|
|
145
|
+
else
|
|
146
|
+
[force ? ':' : '?', '']
|
|
147
|
+
end
|
|
130
148
|
ret = (prompt.call || '').strip
|
|
131
149
|
multiline.each { |val| break if ret.delete_suffix!(val.to_s) } if multiline.is_a?(::Enumerable)
|
|
132
150
|
exit 1 if force && ret.empty?
|