squared 0.0.9 → 0.0.11

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: 90258e1d46b51b15fcb414cddf85407968d227b5032797e76ead83befa5d302b
4
- data.tar.gz: a01b153f7c26b7f15a24cc66a159e47e42bc117e7b0810d7146c6ca3766f3cd0
3
+ metadata.gz: d000551aa00185dae3ea1bc7e3a61c64af96dce7d717339483d8cb572ca12d43
4
+ data.tar.gz: 229f06052512ee06d16e43688602753a867025e8cf39b6b1c6dfafa69805fdbc
5
5
  SHA512:
6
- metadata.gz: 484c0af12a637957f3ed9ddc43b6f08bee1f3dbcba50a2acdfdd0dcb7bb929b83b64d40fd8bbf17ceddc5d8177a27773e4193198cbc80f2991651bf55366b2c8
7
- data.tar.gz: a9617a969c41e971235b98bc730fd440f6c913a2ce21b9e8acb1907ae170ac72da1bb5d72df8650c1833827599fb0d95ab4aae19e5c75249dac07c0096efe077
6
+ metadata.gz: 5efe60578369457075929b95ba8ea628d39d7324d40f84c397e07606c8c5fe48f07f797b940d7ca34a80321bb3f1435494c36afcd95432cbe8924e4353099f56
7
+ data.tar.gz: daf832c4fbcf0e80459317be7b91781cfea9b653b3368cdbc090101d67ca59aff8b38940c2803100e322ca14bd25f45ccf37ef27bb348312b724c45babeb3393
data/README.md CHANGED
@@ -16,9 +16,6 @@
16
16
  * [E-mc](https://github.com/anpham6/e-mc#readme)
17
17
  * [Pi-r](https://github.com/anpham6/pi-r#readme)
18
18
 
19
- > [!NOTE]
20
- > `squared-express` originally was intended to be used as a HTTP/1.1 insecure development server for Android projects. `E-mc` was created a few years later to produce static outputs for any kind of development accessible through a simple REST API. Gradually any new development that calls the REST API will use [NGINX Unit](https://unit.nginx.org) as the *production* application runtime to efficiently serve static assets. Middleware and application code can be conducted in the same manner with Express and other programming frameworks (e.g. Ruby). Maintanence updates with subsequent `E-mc` releases supporting legacy development environments will be tested only against `Express 4.19` although most existing applications can be upgraded without any problems. Upgrading to `Express 5` is not recommended as it offers no new noticeable features (e.g. HTTP/2) and possibly compatibility issues with template libraries.
21
-
22
19
  ## Installation
23
20
 
24
21
  * NodeJS 16 LTS
@@ -113,14 +110,13 @@ cd workspaces # REPO_ROOT
113
110
 
114
111
  wget https://raw.githubusercontent.com/anpham6/squared/master/Rakefile
115
112
 
116
- # REPO_DOCS=1 (venv)
117
113
  rake -T # List tasks
118
114
 
119
115
  # REPO_BUILD={dev,prod}
120
116
  # PIPE_FAIL=1
121
117
  rake repo:init # nightly
122
- # OR
123
- rake repo:init[latest] # 0.11.x
118
+ rake repo:init[latest] # squared
119
+ rake repo:init[0.11.x] # e-mc
124
120
  # OR
125
121
  REPO_ROOT=/tmp/123 NODE_INSTALL=pnpm repo:init
126
122
  ```
data/README.ruby.md CHANGED
@@ -4,9 +4,17 @@
4
4
  * [manifest](https://github.com/anpham6/squared-repo)
5
5
  * [docs](https://squared.readthedocs.io)
6
6
 
7
- ## Prerequisites
7
+ ## Version Compatibility
8
8
 
9
- * Ruby 2.4
9
+ | Date | squared | Ruby 2 | Ruby 3 |
10
+ | :------: | ------: | -----: | -----: |
11
+ | 12-07-24 | 0.1.0 | 2.4.0 | 3.0.0 |
12
+
13
+ ## Installation
14
+
15
+ ```sh
16
+ gem install squared
17
+ ```
10
18
 
11
19
  ### Optional
12
20
 
@@ -20,42 +28,67 @@ curl https://storage.googleapis.com/git-repo-downloads/repo > ~/.bin/repo
20
28
  chmod a+rx ~/.bin/repo
21
29
  ```
22
30
 
23
- ## Installation
24
-
25
- ```sh
26
- gem install squared
27
- ```
28
-
29
31
  ## Example - Rakefile
30
32
 
31
33
  Projects from any accessible folder can be added either relative to `REPO_ROOT` or absolutely. The same Rakefile can also manage other similarly cloned repositories remotely by setting the `REPO_ROOT` environment variable to the location. Missing projects will simply be excluded from the task runner.
32
34
 
33
35
  ```ruby
34
36
  require "squared"
35
- require "squared/workspace/repo" # Repo (optional)
37
+
38
+ require "squared/workspace"
39
+ require "squared/workspace/repo" # Optional
40
+ require "squared/workspace/project/node" #
41
+ require "squared/workspace/project/python" #
42
+ require "squared/workspace/project/ruby" #
43
+ # OR
44
+ require "squared/app" # All workspace related modules
36
45
 
37
46
  # NODE_ENV = production
38
47
  # REPO_ROOT = /workspaces
39
- # REPO_HOME = /workspaces/squared
48
+ # REPO_HOME = /workspaces/squared (Dir.pwd)
40
49
  # rake = /workspaces/squared/Rakefile
50
+ # pathname = /workspaces/pathname
51
+ # optparse = /workspaces/optparse
52
+ # log = /workspaces/logger
53
+ # emc = /workspaces/e-mc
54
+ # pir = /workspaces/pi-r
55
+ # squared = /workspaces/squared
56
+ # cli = /workspaces/squared/publish/sqd-cli
57
+ # sqd-serve = /workspaces/squared/publish/sqd-serve
58
+ # sqd = /workspaces/squared/sqd
41
59
 
42
60
  Workspace::Application
43
- .new(main: "squared") # Dir.pwd? (main? is implicitly basename)
44
- .banner("group", "project", styles: %i[yellow black], border: "bold") # name | project | path | ref | group?
45
- .repo("https://github.com/anpham6/squared-repo", "nightly", run: "build", ref: :node) # Repo (optional)
61
+ .new(Dir.pwd, main: "squared") # Dir.pwd? (main? is implicitly basename)
62
+ .banner("group", "project", styles: %i[yellow black], border: "bold") # name | project | path | ref | group? | parent? | version?
63
+ .repo("https://github.com/anpham6/squared-repo", "nightly", script: ["build:dev", "build:prod"], ref: :node) # Repo (optional)
46
64
  .run("rake install", ref: :ruby)
47
65
  .depend(false, group: "default")
48
66
  .clean("rake clean", group: "default")
49
67
  .clean(["build/"], group: "app")
50
- .add("pathname", run: "rake compile", copy: "rake install", test: "rake test", group: "default", ref: :ruby) # Ruby (with C extensions)
68
+ .log({ file: "tmp/%Y-%m-%d.log", level: "debug" }, group: "app")
69
+ .add("pathname", run: "rake compile", copy: "rake install", test: "rake test", group: "default", env: { # Ruby (with C extensions)
70
+ "CFLAGS" => "-fPIC -O1"
71
+ })
51
72
  .add("optparse", doc: "rake rdoc", group: "default") # Uses bundler/gem_tasks (without C extensions)
52
- .add("logger", copy: { from: "lib", glob: "**/*.rb", gemdir: "~/.rvm/gems/ruby-3.3.5/gems/logger-1.6.1" }, clean: ["tmp/"]) # autodetect: true
53
- .add("emc", "e-mc", copy: { from: "publish", into: "@e-mc", also: [:pir, "squared-express/"] }, ref: :node) # Node
54
- .add("pir", "pi-r", copy: { from: "publish", into: "@pi-r" }, clean: ["publish/**/*.js", "tmp/"]) # Trailing slash required for directories
55
- .add("squared", log: { file: "tmp/%Y-%m-%d.log", level: "debug" }, group: "app") # Copy target (main)
56
- .add("sqd", "squared/sqd", script: ["build:sqd", "prod:sqd"], depend: false, clean: ["build/sqd/"], exclude: :git) # NPM workspaces
73
+ .add("logger", copy: { from: "lib", glob: "**/*.rb", into: "~/.rvm/gems/ruby-3.3.5/gems/logger-1.6.1" }, clean: ["tmp/"]) # autodetect: true
74
+ .add("e-mc", "emc", copy: { from: "publish", scope: "@e-mc", also: [:pir, "squared-express/"] }, ref: :node) # Node
75
+ .add("pi-r", "pir", copy: { from: "publish", scope: "@pi-r" }, clean: ["publish/**/*.js", "tmp/"]) # Trailing slash required for directories
76
+ .add("squared", script: ["build:stage1", "build:stage2"], group: "app") do # Copy target (main)
77
+ add("publish/sqd-cli", "cli", exclude: [:git]) # rake cli:build
78
+ add("publish/sqd-serve") # rake sqd-serve:build
79
+ add("publish/sqd-admin", group: "sqd", exclude: [:base])
80
+ # OR
81
+ with(exclude: [:base]) { add("publish/*", "packages") } # rake packages:sqd-serve:build
82
+ # OR
83
+ add(["publish/sqd-cli", "publish/sqd-serve", "publish/sqd-admin"], true, exclude: [:base]) # rake squared:sqd-serve:build
84
+ end
85
+ .add("squared/sqd", exclude: [:git]) do
86
+ variable_set :script, "build:sqd" # Override detection
87
+ variable_set :depend, false
88
+ variable_set :clean, ["build/sqd/"]
89
+ end
57
90
  .style("banner", 255.255) # 256 colors (fg | fg.bg | -0.bg)
58
- .build(default: "status", parallel: ["pull", "fetch", "rebase", "copy", "clean"]) do |workspace|
91
+ .build(default: "status", parallel: ["pull", "fetch", "rebase", "copy", "clean", "outdated:ruby"]) do |workspace|
59
92
  workspace
60
93
  .enable_aixterm
61
94
  .style({
@@ -63,45 +96,40 @@ Workspace::Application
63
96
  border: "bright_white"
64
97
  })
65
98
  end
66
- # pathname = /workspaces/pathname
67
- # optparse = /workspaces/optparse
68
- # log = /workspaces/logger
69
- # emc = /workspaces/e-mc
70
- # pir = /workspaces/pi-r
71
- # squared = /workspaces/squared
72
99
 
73
- Workspace::Application
74
- .new(ENV["SQUARED_DIR"], prefix: "rb", common: false) # Local styles
75
- .group("default", "ruby/", run: "rake build", copy: "rake install", clean: "rake clean", ref: :ruby, override: {
76
- pathname: {
77
- run: "rake compile" # rake rb:pathname:build
78
- }
79
- })
80
- .with(:python) do # ref=Symbol | group=String
81
- banner("path", command: false, styles: "yellow") #
82
- doc("make html") # rake rb:doc:python
83
- run(false) # rake rb:build:python (disabled)
84
- exclude(%i[base git]) # Project::Git.ref (superclass)
85
- add("android", "android-docs") # rake rb:android:doc
86
- add("chrome", "chrome-docs") # rake rb:chrome:doc
87
- end #
88
- .style("inline", "bold")
89
- .build
90
100
  # default = /workspaces/ruby/*
91
101
  # pathname = /workspaces/ruby/pathname
92
102
  # optparse = /workspaces/ruby/optparse
93
103
  # logger = /workspaces/ruby/logger
94
104
  # android = /workspaces/android-docs
95
105
  # chrome = /workspaces/chrome-docs
106
+
107
+ Workspace::Application
108
+ .new(ENV["SQUARED_HOME"], prefix: "rb", common: false) # Local styles
109
+ .group("ruby", "default", run: "rake build", copy: "rake install", clean: "rake clean", ref: :ruby, override: {
110
+ pathname: {
111
+ run: "rake compile" # rake rb:pathname:build
112
+ }
113
+ })
114
+ .with(:python) do # ref=Symbol | group=String
115
+ banner([:name, ': ', :version], "path") # chrome-docs: 0.1.0 | /workspaces/chrome-docs
116
+ doc("make html") # rake rb:doc:python
117
+ run(false) # rake rb:build:python (disabled)
118
+ exclude(%i[base git]) # Project::Git.ref (superclass)
119
+ add("android-docs", "android") # rake rb:android:doc
120
+ add("chrome-docs", "chrome") # rake rb:chrome:doc
121
+ end #
122
+ .style("inline", "bold")
123
+ .build
96
124
  ```
97
125
 
98
- **NOTE**: The use of "**ref**" (class name) is only necessary when running `repo:init` for the first time into an empty directory.
126
+ **NOTE**: The use of "**ref**" (class name) is only necessary when initializing an empty directory (e.g. *rake repo:init*).
99
127
 
100
128
  ## Usage
101
129
 
102
130
  ```sh
103
- rake -T # List tasks
104
- rake # rake status (usually "build")
131
+ rake -T # List tasks
132
+ rake # rake status (usually "build")
105
133
 
106
134
  # GIT_OPTIONS=rebase
107
135
  rake pull # All except "default" + "app"
@@ -123,7 +151,17 @@ rake clean:app # none + skip + ["build/"]
123
151
  rake clean:node # none + ["publish/**/*.js", "tmp/"] + ["build/"]
124
152
  ```
125
153
 
126
- ## Commands
154
+ ```sh
155
+ rake build:app # squared + cli + sqd-serve
156
+ rake squared:build:workspace # cli + sqd-serve
157
+ rake pull:sqd # sqd-admin
158
+ rake squared:pull:workspace # sqd-serve + sqd-admin
159
+ rake squared:outdated:workspace # cli + sqd-serve + sqd-admin
160
+ ```
161
+
162
+ ## Methods
163
+
164
+ Task:
127
165
 
128
166
  * run
129
167
  * depend
@@ -131,6 +169,11 @@ rake clean:node # none + ["publish/**/*.js", "tmp/"] + ["build/"]
131
169
  * doc
132
170
  * clean
133
171
 
172
+ Non-task:
173
+
174
+ * log
175
+ * exclude
176
+
134
177
  ## Styles
135
178
 
136
179
  * banner
@@ -139,6 +182,61 @@ rake clean:node # none + ["publish/**/*.js", "tmp/"] + ["build/"]
139
182
  * active
140
183
  * inline
141
184
  * major
185
+ * red
186
+ * yellow
187
+ * green
188
+
189
+ ## Environment
190
+
191
+ ### Build
192
+
193
+ ```ruby
194
+ # :env :run :opts
195
+ # LD_LIBRARY_PATH="path/to/lib" CFLAGS="-Wall" gcc a.c -o a.o -c
196
+ BUILD_${NAME} # gcc a.c -o a.o
197
+ BUILD_OPTS_${NAME} # -c
198
+ BUILD_ENV_${NAME} # {"LD_LIBRARY_PATH":"path/to/lib","CFLAGS":"-Wall"} (hash/json)
199
+
200
+ # :env :opts :script
201
+ # NODE_ENV="production" NO_COLOR="1" npm run --loglevel=error --workspaces=false build:dev
202
+ BUILD_${NAME} # build:dev
203
+ BUILD_OPTS_${NAME} # --loglevel=error --workspaces=false
204
+ BUILD_ENV_${NAME} # {"NODE_ENV":"production","NO_COLOR":"1"} (hash/json)
205
+ BUILD_DEV_${NAME} # pattern,0,1 (:dev)
206
+ BUILD_PROD_${NAME} # pattern,0,1 (:prod)
207
+
208
+ BUILD_${NAME}=0 # skip project
209
+ ```
210
+
211
+ These options also support the project specific suffix `${NAME}`. (e.g. LOG_FILE_SQUARED)
212
+
213
+ ### Logger
214
+
215
+ ```ruby
216
+ LOG_FILE # %Y-%m-%d.log
217
+ # OR
218
+ LOG_AUTO # year,y,month,m,day,d,1
219
+ # Optional
220
+ LOG_DIR # exist?
221
+ LOG_LEVEL # See gem "logger"
222
+ LOG_COLUMNS # terminal width (default: 80)
223
+ ```
224
+
225
+ ### Repo
226
+
227
+ ```ruby
228
+ REPO_ROOT # parent dir
229
+ REPO_HOME # project dir (main)
230
+ REPO_BUILD # run,script
231
+ REPO_GROUP # string
232
+ REPO_REF # e.g. ruby,node
233
+ REPO_DEV # pattern,0,1
234
+ REPO_PROD # pattern,0,1
235
+ REPO_WARN # 0,1
236
+ REPO_SYNC # 0,1
237
+ REPO_MANIFEST # e.g. latest,nightly,prod
238
+ REPO_TIMEOUT # confirm dialog (seconds)
239
+ ```
142
240
 
143
241
  ## LICENSE
144
242
 
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'workspace'
4
+ require_relative 'workspace/repo'
5
+ require_relative 'workspace/project/node'
6
+ require_relative 'workspace/project/python'
7
+ require_relative 'workspace/project/ruby'
8
+ require_relative 'config'
9
+
10
+ Common = Squared::Common
@@ -2,6 +2,15 @@
2
2
 
3
3
  module Squared
4
4
  module Common
5
+ ARG = {
6
+ PIPE: 1,
7
+ OUT: nil,
8
+ FAIL: false,
9
+ COMMON: true,
10
+ BANNER: true,
11
+ SPACE: ' => ',
12
+ COLOR: ENV.fetch('NO_COLOR', '').empty?
13
+ }
5
14
  VAR = {
6
15
  project: {},
7
16
  colors: {
@@ -65,6 +74,7 @@ module Squared
65
74
  end
66
75
 
67
76
  def __freeze__
77
+ ARG.freeze
68
78
  VAR.each_value(&:freeze)
69
79
  VAR[:theme].each_value(&:freeze)
70
80
  VAR.freeze
@@ -72,25 +82,17 @@ module Squared
72
82
 
73
83
  module_function
74
84
 
75
- def env(key, default = nil, equals: nil, ignore: nil, **)
76
- ret = ENV.fetch(key, '')
77
- return ret == equals.to_s unless equals.nil?
78
-
79
- ret.empty? || (ignore && as_a(ignore).any? { |val| ret == val.to_s }) ? default : ret
80
- end
81
-
82
- def message(*args, hint: nil)
83
- args.reject(&:empty?).join(' => ') + (hint ? " (#{hint})" : '')
84
- end
85
-
86
85
  def as_a(obj, meth = nil, flat: nil, compact: false)
87
86
  return [] if obj.nil?
88
87
 
89
- if !obj.is_a?(::Array)
90
- obj = [obj]
91
- elsif flat
92
- obj = obj.flatten(flat == true ? nil : flat)
88
+ unless obj.is_a?(::Array)
89
+ obj = if !obj.is_a?(::Hash) && obj.respond_to?(:to_a) && (val = obj.to_a).is_a?(::Array)
90
+ val
91
+ else
92
+ [obj]
93
+ end
93
94
  end
95
+ obj = obj.flatten(flat.is_a?(::Numeric) ? flat : nil) if flat
94
96
  obj = obj.map(&meth) if meth
95
97
  compact ? obj.compact : obj
96
98
  end
@@ -1,14 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'set'
4
+ require 'forwardable'
4
5
 
5
6
  module Squared
6
7
  module Common
7
- class JoinSet < ::Set
8
- def self.to_s
9
- super.to_s.match(/[^:]+$/)[0]
8
+ class SymSet
9
+ extend Forwardable
10
+
11
+ def_delegators :@data, :each, :each_with_index, :entries, :to_a, :include?
12
+
13
+ def initialize(data = [])
14
+ @data = Set.new(data)
10
15
  end
11
16
 
17
+ def add(val)
18
+ @data.add(val.to_sym)
19
+ end
20
+
21
+ def to_s
22
+ @data.to_s.sub('Set', SymSet.to_s)
23
+ end
24
+
25
+ alias inspect to_s
26
+ end
27
+
28
+ class JoinSet < ::Set
12
29
  def initialize(data = [], delim: ' ')
13
30
  super(data.compact)
14
31
  @delim = delim
@@ -40,7 +40,7 @@ module Squared
40
40
  private
41
41
 
42
42
  def sub_style(val, *args, styles: nil, pat: nil, index: 1)
43
- return val unless ENV.fetch('NO_COLOR', '').empty? && !__get__(:no_color)
43
+ return val unless ARG[:COLOR]
44
44
 
45
45
  if pat && index != 0
46
46
  return val unless (data = pat.match(val))
@@ -61,7 +61,7 @@ module Squared
61
61
  else
62
62
  s = ret
63
63
  end
64
- if type.is_a?(Numeric)
64
+ if type.is_a?(::Numeric)
65
65
  f, b = type.to_s.split('.')
66
66
  s = wrap.(s, ['38', '5', f]) if f[0] != '-' && f.to_i <= 255
67
67
  if b
@@ -77,7 +77,7 @@ module Squared
77
77
  code << c
78
78
  end
79
79
  else
80
- next unless (n = TEXT_STYLE.index { |style| style == t })
80
+ next unless (n = TEXT_STYLE.index(t))
81
81
 
82
82
  s = "\x1B[#{n + 1}m#{s}\x1B[#{n == 0 ? 22 : n + 21}m"
83
83
  end
@@ -106,11 +106,11 @@ module Squared
106
106
  out
107
107
  end
108
108
 
109
- def check_style(*args, empty: true)
109
+ def check_style(args, empty: true)
110
110
  ret = []
111
111
  colors = __get__(:colors)
112
112
  as_a(args, flat: true, compact: true).each do |val|
113
- if !val.is_a?(Numeric)
113
+ if !val.is_a?(::Numeric)
114
114
  val = val.to_sym
115
115
  ret << val if colors.key?(val) || TEXT_STYLE.include?(val)
116
116
  elsif val >= 0 && val <= 256
@@ -123,39 +123,41 @@ module Squared
123
123
  ret if empty || !ret.empty?
124
124
  end
125
125
 
126
- def apply_style(data, key, *args, empty: true)
127
- data = __get__(:theme)[data] if data.is_a?(Symbol)
128
- return unless data
126
+ def apply_style(data, key, args, empty: true)
127
+ return if data.is_a?(::Symbol) && (data = __get__(:theme)[data]).nil?
129
128
 
130
- set = ->(k, v) { data[k.to_sym] = check_style(v, empty: empty) }
131
- if key.is_a?(Hash)
132
- key.each { |k, v| set.(k, v || args.flatten) }
129
+ set = ->(k, v) { data[k] = check_style(v, empty: empty) }
130
+ if key.is_a?(::Hash)
131
+ key.each { |k, v| set.(k, v || args) }
133
132
  else
134
- set.(key, args.flatten)
133
+ set.(key.to_sym, args)
135
134
  end
136
135
  end
137
136
 
138
- def log_title(level, color: true)
139
- level = if level.is_a?(::Numeric)
140
- case level
141
- when Logger::DEBUG
142
- :debug
143
- when Logger::INFO
144
- :info
145
- when Logger::WARN
146
- :warn
147
- when Logger::ERROR
148
- :error
149
- when Logger::FATAL
150
- :fatal
151
- else
152
- :unknown
153
- end
154
- else
155
- level.to_s.downcase.to_sym
156
- end
137
+ def log_sym(level)
138
+ if level.is_a?(::Numeric)
139
+ case level
140
+ when Logger::DEBUG
141
+ :debug
142
+ when Logger::INFO
143
+ :info
144
+ when Logger::WARN
145
+ :warn
146
+ when Logger::ERROR
147
+ :error
148
+ when Logger::FATAL
149
+ :fatal
150
+ else
151
+ :unknown
152
+ end
153
+ else
154
+ level.to_s.downcase.to_sym
155
+ end
156
+ end
157
+
158
+ def log_title(level, color: ARG[:COLOR])
157
159
  theme = __get__(:theme)[:logger]
158
- styles = theme[level] || theme[level = :unknown]
160
+ styles = theme[level = log_sym(level)] || theme[level = :unknown]
159
161
  case (ret = +level.to_s.upcase)
160
162
  when 'WARN', 'ERROR', 'FATAL'
161
163
  ret += '!'
@@ -163,26 +165,56 @@ module Squared
163
165
  color ? sub_style(ret, *styles) : ret
164
166
  end
165
167
 
166
- def log_message(level, *args, subject: nil, hint: nil, color: true)
167
- msg = [log_title(level, color: color)]
168
- msg << (color ? sub_style(subject, :underline) : subject) if subject
169
- message(msg.join(' '), *args, hint: hint)
168
+ def log_message(level, *args, subject: nil, hint: nil, color: ARG[:COLOR])
169
+ args = args.map(&:to_s)
170
+ if args.size > 1
171
+ title = log_title(level, color: false)
172
+ sub = { pat: /^(#{title})(.+)$/, styles: __get__(:theme)[:logger][log_sym(level)] } if color
173
+ emphasize(args, title: title + (subject ? " #{subject}" : ''), sub: sub)
174
+ else
175
+ msg = [log_title(level, color: color)]
176
+ if subject
177
+ msg << (color ? sub_style(subject, :underline) : subject)
178
+ else
179
+ msg += args
180
+ args.clear
181
+ end
182
+ message(msg.join(' '), *args, hint: hint)
183
+ end
170
184
  end
171
185
 
172
- def raise_error(*args, hint: nil, kind: ArgumentError)
173
- raise kind, message(*args, hint: hint)
186
+ def puts_oe(*args, pipe: 1)
187
+ if pipe.is_a?(::Pathname)
188
+ begin
189
+ File.open(pipe, 'a') do |f|
190
+ br = File::SEPARATOR == '\\' ? "\r\n" : "\n"
191
+ args.flatten.each { |val| f.write("#{strip_style(val.chomp)}#{br}") }
192
+ end
193
+ return
194
+ rescue StandardError
195
+ pipe = 2
196
+ end
197
+ end
198
+ (pipe == 2 ? $stderr : $stdout).puts(*args)
174
199
  end
175
200
 
176
201
  module_function
177
202
 
178
- def emphasize(val, title: nil, cols: nil, sub: nil, pipe: nil)
203
+ def message(*args, hint: nil, empty: false)
204
+ (empty ? args.reject { |val| val.nil? || val.empty? } : args).join(ARG[:SPACE]) + (hint ? " (#{hint})" : '')
205
+ end
206
+
207
+ def emphasize(val, title: nil, footer: nil, cols: nil, sub: nil, border: nil, pipe: nil)
179
208
  n = 0
180
- if title
181
- title = title.to_s
182
- n = title.size
209
+ set = lambda do |l|
210
+ ret = as_a(l, :to_s)
211
+ n = ret.max_by(&:size).size
212
+ ret
183
213
  end
214
+ title = set.(title) if title
215
+ footer = set.(footer) if footer
184
216
  if val.is_a?(::Array)
185
- lines = val
217
+ lines = val.map(&:to_s)
186
218
  else
187
219
  lines = val.to_s.lines.map(&:chomp)
188
220
  lines[0] = "#{val.class}: #{lines.first}" if (err = val.is_a?(::StandardError))
@@ -194,24 +226,50 @@ module Squared
194
226
  end
195
227
  out = []
196
228
  bord = '-' * (n + 4)
229
+ bord = sub_style(bord, styles: border) if border
197
230
  sub = [sub] if sub && !sub.is_a?(::Array)
198
231
  pr = lambda do |line|
199
232
  s = line.ljust(n)
200
233
  sub&.each { |h| s = sub_style(s, **h) }
201
- "| #{s} |"
234
+ s = "| #{s} |"
235
+ if border
236
+ s = sub_style(s, pat: /^(\|)(.+)$/m, styles: border)
237
+ s = sub_style(s, pat: /^(.+)(\|)$/m, styles: border, index: 2)
238
+ end
239
+ s
202
240
  end
203
241
  out << bord
204
- out << pr.(title) << bord if title
242
+ if title
243
+ out += title.map { |t| pr.(t) }
244
+ out << bord
245
+ end
205
246
  lines.each { |line| out << pr.(line) }
206
247
  out << bord
248
+ out += footer if footer
207
249
  if block_given?
208
250
  yield out
209
- elsif pipe.respond_to?(:puts)
251
+ elsif pipe
252
+ case pipe
253
+ when 0
254
+ pipe = $stdin
255
+ when 2
256
+ pipe = $stderr
257
+ else
258
+ pipe = $stdout unless pipe.respond_to?(:puts)
259
+ end
210
260
  pipe.puts out
211
261
  else
212
262
  err ? warn(out) : puts(out)
213
263
  end
214
264
  end
265
+
266
+ def strip_style(val)
267
+ val.gsub(/\x1B\[(\d+;?)+m/, '')
268
+ end
269
+
270
+ def raise_error(*args, hint: nil, kind: ArgumentError)
271
+ raise kind, message(*args, hint: hint, empty: true)
272
+ end
215
273
  end
216
274
  end
217
275
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'readline'
4
+
5
+ module Squared
6
+ module Common
7
+ module Prompt
8
+ module_function
9
+
10
+ def confirm(msg, default = nil, agree: 'Y', cancel: 'N', attempts: 5, timeout: 15)
11
+ require 'timeout'
12
+ agree = /^#{agree}$/i if agree.is_a?(::String)
13
+ cancel = /^#{cancel}$/i if cancel.is_a?(::String)
14
+ Timeout.timeout(timeout) do
15
+ begin
16
+ while (ch = Readline.readline(msg, true))
17
+ ch = ch.chomp
18
+ ch = default if ch.empty?
19
+ case ch
20
+ when agree
21
+ return true
22
+ when cancel
23
+ return false
24
+ end
25
+ attempts -= 1
26
+ exit 1 unless attempts >= 0
27
+ end
28
+ rescue Interrupt
29
+ puts
30
+ exit 0
31
+ else
32
+ false
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end