squared 0.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.
data/README.ruby.md ADDED
@@ -0,0 +1,50 @@
1
+ # squared 0.1
2
+
3
+ * [source](https://github.com/anpham6/squared)
4
+ * [manifest](https://github.com/anpham6/squared-repo)
5
+ * [docs](https://squared.readthedocs.io)
6
+
7
+ ## Installation
8
+
9
+ ```sh
10
+ gem install squared
11
+ ```
12
+
13
+ ## Prerequisites
14
+
15
+ * Ruby 2.4+
16
+ * Python 3.6+
17
+ * [Repo](https://source.android.com/docs/setup/reference/repo)
18
+
19
+ ```sh
20
+ mkdir -p ~/.bin
21
+ PATH="${HOME}/.bin:${PATH}"
22
+ curl https://storage.googleapis.com/git-repo-downloads/repo > ~/.bin/repo
23
+ chmod a+rx ~/.bin/repo
24
+ ```
25
+
26
+ ## Example - Rakefile
27
+
28
+ ```ruby
29
+ require "squared"
30
+
31
+ Repo::Workspace
32
+ .new("squared")
33
+ .repo("https://github.com/anpham6/squared-repo", "nightly")
34
+ .add(:pathname, run: "rake compile", copy: "rake install", clean: "rake clean", ref: :ruby, group: "default") # Ruby
35
+ .add(:optparse, group: "default")
36
+ .add(:emc, "e-mc", copy: { from: "publish", into: "@e-mc", also: [:pir] }) # Node
37
+ .add(:pir, "pi-r", copy: { from: "publish", into: "@pi-r" })
38
+ .add(:squared)
39
+ .build(parallel: %i[pull fetch rebase copy clean])
40
+ ```
41
+
42
+ ## Usage
43
+
44
+ ```sh
45
+ rake -T
46
+ ```
47
+
48
+ ## LICENSE
49
+
50
+ BSD 3-Clause
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Squared
4
+ module Common
5
+ class JoinSet < ::Set
6
+ def initialize(data = [], delim: ' ')
7
+ super(data.compact)
8
+ @delim = delim
9
+ end
10
+
11
+ def done
12
+ ret = to_a.map!(&:to_s).reject(&:empty?).join(@delim)
13
+ clear
14
+ ret
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Squared
4
+ module Common
5
+ module Format
6
+ def emphasize(val, title: nil, cols: nil, sub: nil)
7
+ n = 0
8
+ if title
9
+ title = title.to_s
10
+ n = title.size
11
+ end
12
+ if val.is_a?(::Array)
13
+ lines = val
14
+ else
15
+ lines = val.to_s.lines.map(&:chomp)
16
+ lines[0] = "#{val.class}: #{lines.first}" if (err = val.is_a?(::StandardError))
17
+ end
18
+ n = cols || [n, lines.max_by(&:size).size].max
19
+ if $stdout.tty?
20
+ require 'io/console'
21
+ (n = [n, $stdout.winsize[1] - 4].min) rescue nil
22
+ end
23
+ bord = '-' * (n + 4)
24
+ write = ->(line) { err ? warn(line) : puts(line) }
25
+ sub = as_a(sub)
26
+ pr = lambda do |line|
27
+ s = line.ljust(n)
28
+ sub.each { |h| s = sub_style(s, **h) }
29
+ "| #{s} |"
30
+ end
31
+ write.(bord)
32
+ if title
33
+ write.(pr.(title))
34
+ write.(bord)
35
+ end
36
+ lines.each { |line| write.(pr.(line)) }
37
+ write.(bord)
38
+ end
39
+
40
+ def sub_style(val, *args, pat: nil, styles: nil, index: 1)
41
+ return val unless ENV['NO_COLOR'].to_s.empty?
42
+
43
+ if pat && index != 0
44
+ return val unless (data = pat.match(val))
45
+
46
+ ret = index == -1 ? data.to_a[1..-1] : data[index]
47
+ else
48
+ ret = val
49
+ index = 0
50
+ end
51
+ wrap = ->(s, n) { "\x1B[#{n.join(';')}m#{s}\x1B[0m" }
52
+ code = []
53
+ args.concat(as_a(styles)).each_with_index do |type, i|
54
+ next unless type
55
+
56
+ if index == -1
57
+ s = ret[i]
58
+ next ret[i] = '' if s.nil?
59
+ else
60
+ s = ret
61
+ end
62
+ case (t = type.to_sym)
63
+ when :bold
64
+ s = "\x1B[1m#{s}\x1B[22m"
65
+ when :italic
66
+ s = "\x1B[3m#{s}\x1B[23m"
67
+ when :underline
68
+ s = "\x1B[4m#{s}\x1B[24m"
69
+ else
70
+ if (c = get(:colors)[t])
71
+ if index == -1
72
+ s = wrap.(s, [c])
73
+ else
74
+ code << c
75
+ end
76
+ end
77
+ end
78
+ if index == -1
79
+ ret[i] = s
80
+ else
81
+ ret = s
82
+ end
83
+ end
84
+ return ret.join if index == -1
85
+
86
+ ret = wrap.(ret, code) unless code.empty?
87
+ return ret unless data
88
+
89
+ out = +''
90
+ data.to_a.each_with_index do |group, i|
91
+ next if i == 0
92
+
93
+ if i == index
94
+ out << ret
95
+ elsif !group.nil?
96
+ out << group
97
+ end
98
+ end
99
+ out
100
+ end
101
+
102
+ def check_style(iter, empty: true)
103
+ ret = []
104
+ colors = get(:colors)
105
+ as_a(iter).each do |val|
106
+ case (val = val.to_sym)
107
+ when :bold, :italic, :underline
108
+ ret << val
109
+ else
110
+ ret << val if colors.key?(val)
111
+ end
112
+ end
113
+ !empty && ret.empty? ? nil : ret
114
+ end
115
+
116
+ def log_title(level, color: true)
117
+ level = if level.is_a?(::Numeric)
118
+ case level
119
+ when Logger::DEBUG
120
+ :debug
121
+ when Logger::INFO
122
+ :info
123
+ when Logger::WARN
124
+ :warn
125
+ when Logger::ERROR
126
+ :error
127
+ when Logger::FATAL
128
+ :fatal
129
+ else
130
+ :unknown
131
+ end
132
+ else
133
+ level.to_s.downcase.to_sym
134
+ end
135
+ val = get(:logger)[level] || get(:logger)[level = :unknown]
136
+ level = +level.to_s.upcase
137
+ case level
138
+ when 'WARN', 'ERROR', 'FATAL'
139
+ level += '!'
140
+ end
141
+ color ? sub_style(level, *val) : level
142
+ end
143
+
144
+ def log_message(level, *args, subject: nil, hint: nil, color: true)
145
+ msg = [log_title(level, color: color)]
146
+ msg << (color ? sub_style(subject, :underline) : subject) if subject
147
+ message(msg.join(' '), *args, hint: hint)
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Squared
4
+ module Common
5
+ module Shell
6
+ def shell_escape(val)
7
+ return val if ::Rake::Win32.windows?
8
+
9
+ Shellwords.escape(val)
10
+ end
11
+
12
+ def shell_quote(val, force: true)
13
+ ret = val.to_s.strip
14
+ return ret if (!force && !ret.include?(' ')) || /(?:^|=)(["']).+\1$/m.match?(ret)
15
+
16
+ ::Rake::Win32.windows? ? "\"#{double_quote(ret)}\"" : "'#{single_quote(ret)}'"
17
+ end
18
+
19
+ def single_quote(val)
20
+ val.gsub("'", "'\\\\''")
21
+ end
22
+
23
+ def double_quote(val)
24
+ val.gsub(/(?<!\\)"/, '\\"')
25
+ end
26
+
27
+ def split_escape(val, char: ',')
28
+ val.split(/\s*(?<!\\)#{char}\s*/)
29
+ end
30
+
31
+ def trailing_slash(val)
32
+ val.to_s.chomp(::File::SEPARATOR) + ::File::SEPARATOR
33
+ end
34
+
35
+ def sanitize_args(*opts)
36
+ opts.map { |val| val.include?(' ') ? shell_quote(val) : shell_escape(val) }.join(' ')
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Squared
4
+ module Common
5
+ module System
6
+ def shell(*cmd, **kwargs)
7
+ if /^2\.[0-5]\./.match?(RUBY_VERSION)
8
+ exception = kwargs.delete(:exception)
9
+ ret = system(*cmd, **kwargs)
10
+ raise $?.to_s if !ret && exception
11
+
12
+ ret
13
+ else
14
+ system(*cmd, **kwargs)
15
+ end
16
+ end
17
+
18
+ def copy_d(src, dest, glob: ['**/*'], create: false, verbose: true)
19
+ raise message(src, dest, hint: 'not found') if !create && !dest.exist?
20
+
21
+ subdir = []
22
+ files = 0
23
+ dest.mkpath if create
24
+ as_a(glob).each do |val|
25
+ Dir.glob(src.join(val)) do |path|
26
+ ent = Pathname.new(path)
27
+ next if ent.directory?
28
+
29
+ target = dest.join(ent.relative_path_from(src))
30
+ dir = target.dirname
31
+ if !subdir.include?(dir.to_s)
32
+ dir.mkpath
33
+ subdir << dir.to_s
34
+ end
35
+ copy_f ent, target
36
+ files += 1
37
+ end
38
+ end
39
+ puts message(dest.realpath, subdir.size.to_s, files.to_s) if verbose
40
+ end
41
+
42
+ def copy_f(src, dest, overwrite: true, verbose: false)
43
+ if !overwrite
44
+ path = Pathname.new(dest)
45
+ if path.directory?
46
+ src = as_a(src).reject { |val| path.join(File.basename(val)).exist? }
47
+ elsif path.exist?
48
+ return
49
+ end
50
+ end
51
+ FileUtils.cp(src, dest, verbose: verbose)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Squared
4
+ module Common
5
+ module Task
6
+ def collect_args(args, *keys)
7
+ ret = []
8
+ return ret unless args.is_a?(::Rake::TaskArguments)
9
+
10
+ keys.each { |key| ret << args[key] if args[key] }
11
+ ret += args.extras
12
+ end
13
+
14
+ def invoke(name, *args, exception: true)
15
+ ::Rake::Task[name].invoke(*args)
16
+ rescue StandardError => e
17
+ raise if exception
18
+
19
+ warn e
20
+ end
21
+
22
+ def invoked?(name)
23
+ ::Rake::Task.tasks.any? { |item| item.already_invoked && item.name == name.to_s }
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathname'
4
+ require 'shellwords'
5
+ require 'logger'
6
+ require 'set'
7
+ require 'rake'
8
+
9
+ module Squared
10
+ module Common
11
+ VAR = {
12
+ project: {},
13
+ logger: {
14
+ unknown: %i[cyan],
15
+ fatal: %i[white bold red!],
16
+ error: %i[red bold],
17
+ warn: %i[yellow bold],
18
+ info: %i[blue],
19
+ debug: %i[green]
20
+ },
21
+ colors: {
22
+ black: '30',
23
+ red: '31',
24
+ green: '32',
25
+ yellow: '33',
26
+ blue: '34',
27
+ magenta: '35',
28
+ cyan: '36',
29
+ white: '37',
30
+ black!: '40',
31
+ red!: '41',
32
+ green!: '42',
33
+ yellow!: '43',
34
+ blue!: '44',
35
+ magenta!: '45',
36
+ cyan!: '46',
37
+ white!: '47'
38
+ }.freeze
39
+ }
40
+ private_constant :VAR
41
+
42
+ def get(key)
43
+ VAR[key.is_a?(::String) ? key.to_sym : key]
44
+ end
45
+
46
+ def set(key, val)
47
+ VAR[key.is_a?(::String) ? key.to_sym : key] = val
48
+ end
49
+
50
+ def message(*args, hint: nil)
51
+ args.reject(&:empty?).join(' => ') + (hint ? " (#{hint})" : '')
52
+ end
53
+
54
+ def as_a(obj)
55
+ return [] if obj.nil?
56
+
57
+ obj.is_a?(::Array) ? obj : [obj]
58
+ end
59
+ end
60
+ end
61
+
62
+ require_relative 'common/class'
63
+ require_relative 'common/format'
64
+ require_relative 'common/shell'
65
+ require_relative 'common/system'
66
+ require_relative 'common/task'
@@ -0,0 +1,240 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Squared
4
+ module Config
5
+ class Viewer
6
+ include Common
7
+ include Format
8
+ include Task
9
+ include ::Rake::DSL
10
+
11
+ class << self
12
+ include Common::Format
13
+
14
+ attr_reader :styles
15
+
16
+ def style(name, *args)
17
+ args = check_style(args)
18
+ styles[name.to_sym]&.clear&.concat(args)
19
+ end
20
+ end
21
+
22
+ @styles = {
23
+ banner: %i[blue],
24
+ key: %i[bold],
25
+ value: %i[green],
26
+ string: %i[yellow],
27
+ hash: %i[green black!],
28
+ array: %i[blue black!],
29
+ number: %i[magenta],
30
+ undefined: %i[red italic]
31
+ }.freeze
32
+
33
+ attr_reader :main, :project, :name
34
+
35
+ def initialize(main = 'package', project: nil, name: nil)
36
+ if project
37
+ @project = get(:project)[project.to_sym]
38
+ @required = true
39
+ end
40
+ @name = (name || @project&.name)&.to_s
41
+ unless @name
42
+ msg, hint = project ? [project, 'not found'] : %w[name missing]
43
+ warn log_message(:warn, msg, subject: 'Config::Viewer', hint: hint, color: !pipe?)
44
+ @required = true
45
+ end
46
+ @main = main
47
+ @include = {}
48
+ end
49
+
50
+ def build
51
+ return unless enabled?
52
+
53
+ namespace @name do
54
+ namespace :view do
55
+ if @include['json'] && !::Rake::Task.task_defined?("#{@name}:view:json")
56
+ desc format_desc('json')
57
+ task :json, [:keys] do |_, args|
58
+ require 'json'
59
+ read_keys JSON, 'json', args.keys, args.extras
60
+ end
61
+ end
62
+
63
+ if @include['yaml'] && !::Rake::Task.task_defined?("#{@name}:view:yaml")
64
+ desc format_desc('yaml', 'yml')
65
+ task :yaml, [:keys] do |_, args|
66
+ require 'yaml'
67
+ read_keys YAML, 'yaml', args.keys, args.extras, 'yml', 'yaml'
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ def add(type, gem: nil, parse: nil, ext: [], opts: {}, command: nil, file: nil)
75
+ type = type.to_s
76
+ if parse && enabled?
77
+ require(gem || type)
78
+ obj = eval(parse)
79
+ ext = as_a(ext)
80
+ namespace @name do
81
+ desc format_desc(ext.first || type, command: command)
82
+ namespace(command || :view) do
83
+ task type, [:keys] do |_, args|
84
+ if file
85
+ read_keys obj, type, file.to_s, collect_args(args, :keys), *ext
86
+ else
87
+ read_keys obj, type, args.keys, args.extras, *ext
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ rescue LoadError, NameError
94
+ self
95
+ else
96
+ @include[type] = opts
97
+ self
98
+ end
99
+
100
+ def also(path, type = nil, name: nil, gem: nil, parse: nil, opts: {})
101
+ return self unless (file = base_path(path)).exist?
102
+
103
+ ext = chop_extname(file).downcase
104
+ name ||= file.basename.to_s.chomp(".#{ext}")
105
+ type = (type || ext).to_s
106
+ if !parse
107
+ case type
108
+ when 'json'
109
+ parse = 'JSON'
110
+ when 'yaml', 'yml'
111
+ type = 'yaml'
112
+ parse = 'YAML'
113
+ end
114
+ end
115
+ add(type, gem: gem, parse: parse, ext: ext, opts: opts, command: name, file: file)
116
+ end
117
+
118
+ def enabled?
119
+ !@required || !!@project&.enabled?
120
+ end
121
+
122
+ protected
123
+
124
+ def read_keys(reader, type, file, keys, *ext)
125
+ path = base_path(file)
126
+ fmt = chop_extname(file)
127
+ ext << type if ext.empty?
128
+ if path.exist? && path.basename.to_s.include?('.')
129
+ raise ArgumentError, message(file, fmt, hint: 'invalid') unless ext.include?(fmt)
130
+ else
131
+ if ext.include?(fmt)
132
+ alt = file
133
+ file = nil
134
+ ext[0] = fmt
135
+ else
136
+ keys.unshift(file)
137
+ alt = "#{@main}.{#{ext.join(',')}}"
138
+ alt = @project.base_path(alt) if @project
139
+ file = Dir[alt].first
140
+ end
141
+ if !file
142
+ raise ArgumentError, message(reader.name, "#{File.basename(alt, '.*')}.#{ext.first}", hint: 'not found')
143
+ end
144
+ end
145
+ doc = if reader.respond_to?(:load_file)
146
+ reader.load_file(file, **@include[type])
147
+ else
148
+ reader.parse(File.read(file), **@include[type])
149
+ end
150
+ lines = print_keys(type, doc, keys)
151
+ return unless lines
152
+
153
+ title = Pathname.new(file)
154
+ .realpath
155
+ .to_s
156
+ .sub(/^#{Regexp.escape(Dir.pwd + ::File::SEPARATOR)}/, '')
157
+ sub = if pipe?
158
+ nil
159
+ else
160
+ [
161
+ { pat: /^([^:]+|(?<! ):(?! ))+$/, styles: Viewer.styles[:banner] },
162
+ { pat: /^(.*?)(<[^>]+>)(.+)$/m, styles: Viewer.styles[:undefined], index: 2 },
163
+ { pat: /^(.+)( : (?!undefined))(.+)$/m, styles: Viewer.styles[:key] },
164
+ { pat: /^(.+)( : )(-?[\d.]+)(\s*)$/m, styles: Viewer.styles[:number], index: 3 },
165
+ { pat: /^(.+)( : ")(.+)("\s*)$/m, styles: Viewer.styles[:string], index: 3 },
166
+ { pat: /^(.+)( : \{)(.+)(\}\s*)$/m, styles: Viewer.styles[:hash], index: 3 },
167
+ { pat: /^(.+)( : \[)(.+)(\]\s*)$/m, styles: Viewer.styles[:array], index: 3 },
168
+ { pat: /^(.+)( : (?!undefined))([^"\[{].*)$/m, styles: Viewer.styles[:value], index: 3 }
169
+ ]
170
+ end
171
+ emphasize(lines, title: title, sub: sub)
172
+ end
173
+
174
+ def print_keys(type, data, keys)
175
+ out = []
176
+ pad = 0
177
+ symbolize = @include[type][:symbolize_names]
178
+ keys.each do |key|
179
+ begin
180
+ items = key.split('.')
181
+ items = items.map(&:to_sym) if symbolize
182
+ val = data.dig(*items)
183
+ if val.nil?
184
+ val = data
185
+ items.each do |name|
186
+ raise name unless val.is_a?(::Hash) && val.key?(name)
187
+
188
+ val = val[name]
189
+ end
190
+ end
191
+ rescue StandardError
192
+ val = Regexp.escape($!.message)
193
+ key = key.sub(/(#{val})\.|\.(#{val})|(#{val})/) do
194
+ s = "<#{$3 || $2 || $1}>"
195
+ if $3
196
+ s
197
+ else
198
+ $2 ? ".#{s}" : "#{s}."
199
+ end
200
+ end
201
+ out << [key, 'undefined']
202
+ else
203
+ out << [key, val.inspect]
204
+ end
205
+ pad = key.size if key.size > pad
206
+ end
207
+ if pipe?
208
+ out = out.map do |item|
209
+ val = item.last
210
+ val.start_with?('"') && val.end_with?('"') ? val[1..-2] : val
211
+ end
212
+ puts out.join("\n")
213
+ else
214
+ out.map { |item| "#{item.first.ljust(pad)} : #{item.last}" }
215
+ end
216
+ end
217
+
218
+ def base_path(file)
219
+ @project ? @project.base_path(file) : Pathname.new(file).realdirpath
220
+ end
221
+
222
+ def format_desc(type, alt = nil, command: nil)
223
+ message(@name, command || 'view', "#{type}[#{command.nil? ? "file?=#{@main}.#{alt || type}," : ''}keys*]")
224
+ end
225
+
226
+ def chop_extname(file)
227
+ File.extname(file).sub('.', '')
228
+ end
229
+
230
+ def pipe?
231
+ return @project.workspace.pipe? if @project
232
+
233
+ val = ENV['PIPE_OUT']
234
+ !val.nil? && !val.empty? && val != '0'
235
+ end
236
+ end
237
+ end
238
+ end
239
+
240
+ Viewer = Squared::Config::Viewer