squared 0.8.5 → 0.9.0.pre1

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/exe/sqd ADDED
@@ -0,0 +1,224 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $:.unshift(File.expand_path('../lib', __dir__)) if File.exist?(File.expand_path('../squared.gemspec', __dir__))
5
+
6
+ require 'squared'
7
+ require 'rake'
8
+ require 'optparse'
9
+
10
+ class SqdParser < OptionParser
11
+ include Squared::Common::Format
12
+ include Utils
13
+
14
+ attr_reader :options, :global
15
+
16
+ def initialize(*)
17
+ super
18
+ @options = { 'project-args': [], 'project-env': [] }
19
+ @global = []
20
+ @program_name = 'squared'
21
+ @raise_unknown = false
22
+ @require_exact = true
23
+ end
24
+
25
+ def parse_global(list, assert:)
26
+ raise ArgumentError, "cannot be used with --#{assert}" if options.key?(assert)
27
+
28
+ ARGV.each_with_index do |val, index|
29
+ if val == '--'
30
+ separator!(index: index)
31
+ break
32
+ end
33
+ next if val.start_with?('-') || val.include?(':') || val.empty?
34
+
35
+ global.concat(list.map { |name| "#{val}:#{name}" })
36
+ ARGV[index] = ''
37
+ end
38
+ end
39
+
40
+ def parse_common(list, session:, arg:)
41
+ if list && !list.include?('*')
42
+ session[arg].merge!(list.to_h { |key| [key, 'true'] })
43
+ else
44
+ session[arg] = Hash.new('true')
45
+ end
46
+ end
47
+
48
+ def banner_set(val, session:)
49
+ session[:BANNER] = val
50
+ ENV['BANNER'] = val ? 'true' : 'false'
51
+ end
52
+
53
+ def color_set(val, session:)
54
+ if (session[:COLOR] = val)
55
+ enable_aixterm
56
+ enable_drawing
57
+ else
58
+ enable_drawing false
59
+ Squared::Common.const_get(:VAR)[:colors].freeze
60
+ end
61
+ ENV['NO_COLOR'] = val ? nil : '1'
62
+ ENV['FORCE_COLOR'] = val ? '2' : '0'
63
+ end
64
+
65
+ def run
66
+ parse!(into: options)
67
+ if (project = options[:project])
68
+ pattern, project = project.partition do |val|
69
+ next true if val == '*'
70
+
71
+ val = ".#{val}" if val.start_with?('*')
72
+ val.match?(/[*+\[(]|(?<!\\)\.\.|\{\d+\}/) && Regexp.new(val)
73
+ rescue RegexpError
74
+ false
75
+ end
76
+ pattern.each do |val|
77
+ val = if val == '*'
78
+ '[^:]+'
79
+ else
80
+ val.sub(/^\*/, '.*')
81
+ .sub(/(?<!\.)\*$/, '.*')
82
+ .gsub(/\.([*+])/, '[^:]\1?')
83
+ end
84
+ IO.popen("rake -T \"^#{val}:\"").each { |line| project << $1 if line =~ /\Arake (#{val}):/o }
85
+ end
86
+ exit 1 if project.empty? && !pattern.empty?
87
+ project.uniq!
88
+ options[:'project-env'].each do |val|
89
+ n = val.index('=')
90
+ key = val[...n]
91
+ val = val[n.succ..].stripquote
92
+ project.each { |name| ENV["#{key}_#{env_key(name.split(':').first)}"] = val }
93
+ end
94
+ ARGV.each_with_index do |val, index|
95
+ if val == '--'
96
+ separator!(index: index)
97
+ break
98
+ end
99
+ next unless val.start_with?(':')
100
+
101
+ args = options[:'project-args'].shift
102
+ global.concat(project.map { |name| "#{name}#{val}#{"[#{args}]" if args}" })
103
+ ARGV[index] = ''
104
+ end
105
+ end
106
+ ARGV.reject!(&:empty?)
107
+ Rake.application.run(global.concat(ARGV))
108
+ end
109
+
110
+ def separator!(index:)
111
+ ARGV[index] = '' if ARGV.grep('--').size > 1
112
+ end
113
+
114
+ def error_exit(*val, hint)
115
+ raise OptionParser::InvalidArgument, message(*val, hint: hint)
116
+ end
117
+ end
118
+
119
+ SqdParser.new do |sqd|
120
+ session = Squared::Common::ARG
121
+ sqd.banner = 'Usage: sqd [options] [rake options] [global...] [:targets...] -- [rake targets...]'
122
+ sqd.separator ''
123
+ sqd.on('-R', '--ref STR,STR', Array, 'Run global commands by class reference') do |val|
124
+ sqd.parse_global(val, assert: :group)
125
+ session[:APP][:series_ref_min] = 1
126
+ end
127
+ sqd.on('-G', '--group STR,STR', Array, 'Run global commands by project group') do |val|
128
+ sqd.parse_global(val, assert: :ref)
129
+ end
130
+ sqd.separator ''
131
+ sqd.on('-P', '--project STR,PAT', Array, 'Run project commands with common arguments')
132
+ sqd.on('-a', '--project-args STR,STR', 'Provide command arguments for :targets (multiple)') do |val|
133
+ sqd.options[:'project-args'] << val
134
+ end
135
+ sqd.on('-e', '--project-env KEY=STR', 'Provide command ENV variables for :targets (multiple)') do |val|
136
+ sqd.error_exit('missing value', "#{val}=") unless val.include?('=')
137
+
138
+ sqd.options[:'project-env'] << val
139
+ end
140
+ sqd.separator ''
141
+ sqd.on('-s', '--[no-]strict', 'Do not run commands with invalid arguments') do |val|
142
+ session[:STRICT] = val
143
+ ENV['STRICT'] = val ? 'true' : 'false'
144
+ end
145
+ sqd.on('--update', 'Use update strategy for applicable commands') do
146
+ ENV['UPDATE'] = 'true'
147
+ end
148
+ sqd.on('-F', '--force [STR,STR]', Array, 'Enable force through ENV by application') do |val|
149
+ sqd.parse_common(val, session: session, arg: :FORCE)
150
+ end
151
+ sqd.on('-y', '--yes [STR,STR]', Array, 'Enable auto-approve through ENV by application') do |val|
152
+ sqd.parse_common(val, session: session, arg: :YES)
153
+ end
154
+ sqd.on('-d', '--dry-run', 'Run commands without any modifying actions') do
155
+ ENV['DRY_RUN'] = 'true'
156
+ end
157
+ sqd.separator ''
158
+ sqd.on('-S', '--select PAGE:CHOICE', 'Prompt menu selection pagination limits') do |val|
159
+ page, choice = val.split(/\D/, 2).map(&:to_i)
160
+ sqd.error_exit('not an integer', val) if choice.nil? ? page == 0 : choice == 0
161
+
162
+ session[:PAGE] = page if page > 0
163
+ session[:CHOICE] = choice if choice.to_i > 0
164
+ ENV['SELECT'] = "#{page}:#{choice}"
165
+ end
166
+ sqd.separator ''
167
+ sqd.on('-p', '--pipe 0|1|2', Integer, 'Set file descriptor for communication') do |val|
168
+ sqd.error_exit('out of range', val) unless val.between?(0, 2)
169
+
170
+ session[:PIPE] = val
171
+ end
172
+ sqd.on('-o', '--pipe-out FILE', 'Write command data stream to a file') do |val|
173
+ sqd.error_exit("missing an explicit \".#{File::SEPARATOR}\"", val) unless val.include?(File::SEPARATOR)
174
+
175
+ session[:OUT] = val
176
+ end
177
+ sqd.on('--fail 0..4', Integer, 'Set application exception level') do |val|
178
+ sqd.error_exit('out of range', val) unless val.between?(0, 4)
179
+
180
+ session[:FAIL] = val
181
+ end
182
+ %i[debug info warn error fatal].then do |state|
183
+ values = state + (0..4).map(&:to_s)
184
+ sqd.on('--log-level SEVERITY', values, "Set logger and exception state (#{state.join(', ')})") do |val|
185
+ require 'logger'
186
+ severity = case val
187
+ when :debug then Logger::DEBUG
188
+ when :info then Logger::INFO
189
+ when :warn then Logger::WARN
190
+ when :error then Logger::ERROR
191
+ when :fatal then Logger::FATAL
192
+ else
193
+ val.to_i
194
+ end
195
+ session[:LEVEL] = severity
196
+ ENV['LOG_LEVEL'] = severity.to_s
197
+ end
198
+ end
199
+ sqd.separator ''
200
+ sqd.on('--[no-]banner', 'Display executing command attributes') do |val|
201
+ sqd.banner_set(val, session: session)
202
+ end
203
+ sqd.on('-x', '--[no-]color', 'Use enhanced styling for terminal output') do |val|
204
+ sqd.color_set(val, session: session)
205
+ end
206
+ sqd.on('-X', '--plain', 'Disable banner and styling for all commands') do
207
+ sqd.banner_set(false, session: session)
208
+ sqd.color_set(false, session: session)
209
+ end
210
+ sqd.on('-v', '--verbose', 'Increase output verbosity (multiple)') do
211
+ unless session[:VERBOSE]
212
+ session[:VERBOSE] = $VERBOSE ? 1 : 0
213
+ sqd.global.unshift('--verbose')
214
+ end
215
+ ENV['VERBOSE'] = (session[:VERBOSE] += 1).to_s
216
+ end
217
+ sqd.on('--version', 'Print the current version') do
218
+ puts Squared::VERSION
219
+ exit 0
220
+ end
221
+ sqd.on('-h', '--help', 'Print the command line options') do
222
+ sqd.help_exit
223
+ end
224
+ end.run
@@ -11,13 +11,17 @@ module Squared
11
11
  COMMON: true,
12
12
  VERBOSE: nil,
13
13
  STRICT: false,
14
+ FORCE: {},
15
+ YES: {},
16
+ APP: {},
14
17
  BANNER: true,
15
- CHOICE: 25,
18
+ PAGE: 20,
19
+ CHOICE: 100,
16
20
  QUOTE: "'",
17
21
  SPACE: ' => ',
18
22
  SUCCESS: 'Success',
19
- GRAPH: %w[| - | \\ -].freeze,
20
- BORDER: %w[| - - - - - | | - -].freeze,
23
+ GRAPH: %w[| - | \\ -],
24
+ BORDER: %w[| - - - - - | | - -],
21
25
  VIEW: 'view',
22
26
  LEVEL: ENV.fetch('LOG_LEVEL', 0).to_i,
23
27
  COLOR: ENV.fetch('NO_COLOR', '').empty?
@@ -50,9 +50,14 @@ module Squared
50
50
  self
51
51
  end
52
52
 
53
- def enable_drawing
54
- ARG[:GRAPH] = BOX_GRAPH
55
- ARG[:BORDER] = BOX_BORDER
53
+ def enable_drawing(state = true)
54
+ if state
55
+ ARG[:GRAPH] = BOX_GRAPH unless ARG[:GRAPH].frozen?
56
+ ARG[:BORDER] = BOX_BORDER unless ARG[:BORDER].frozen?
57
+ else
58
+ ARG[:GRAPH].freeze
59
+ ARG[:BORDER].freeze
60
+ end
56
61
  self
57
62
  end
58
63
 
@@ -1,27 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'timeout'
4
+
3
5
  module Squared
4
6
  module Common
5
7
  module Prompt
6
8
  def self.reline
7
- if RUBY_ENGINE == 'ruby' && RUBY_VERSION < '2.7'
8
- require 'readline'
9
- Readline
10
- else
11
- begin
12
- require 'reline'
13
- Reline
14
- rescue LoadError
15
- require 'readline'
16
- Readline
17
- end
18
- end
9
+ require 'reline'
10
+ Reline
11
+ rescue LoadError
12
+ require 'readline'
13
+ Readline
19
14
  end
20
15
 
21
16
  module_function
22
17
 
23
18
  def confirm(msg, accept = nil, agree: 'Y', cancel: 'N', force: false, attempts: 3, timeout: 60)
24
- require 'timeout'
25
19
  if agree == 'Y' && cancel == 'N' && !msg.match?(%r{\[(Yn|nY|Y/n|y/N)\]})
26
20
  case accept
27
21
  when 'Y'
@@ -34,7 +28,7 @@ module Squared
34
28
  cancel = /^#{Regexp.escape(cancel)}$/i if cancel.is_a?(::String)
35
29
  Timeout.timeout(timeout) do
36
30
  while (ch = Prompt.reline.readline(msg))
37
- ch = ch.chomp
31
+ ch.chomp!
38
32
  case (ch.empty? ? accept : ch)
39
33
  when agree
40
34
  return true
@@ -54,9 +48,9 @@ module Squared
54
48
  false
55
49
  end
56
50
 
57
- def choice(msg, list = nil, min: 1, max: 1, multiple: false, index: false, grep: nil, border: nil, auto: true,
58
- force: true, attempts: 3, timeout: 0, empty: nil)
59
- require 'timeout'
51
+ def choice(msg, list = nil, min: 1, max: nil, multiple: false, index: false, grep: nil, border: nil, force: true,
52
+ attempts: 3, timeout: 0, pipe: 1, scroll: true, empty: nil, page: 25)
53
+ std = pipe == 2 ? $stderr : $stdout
60
54
  multiple = Array(multiple) if multiple.is_a?(::Numeric)
61
55
  if list
62
56
  grep &&= Array(grep).map { |val| Regexp.new(val) }
@@ -64,33 +58,70 @@ module Squared
64
58
  next if grep&.none? { |pat| pat.match?(val) }
65
59
 
66
60
  out << val.to_s.chomp
67
- puts '%2d. %s' % [out.size, val]
68
61
  end
69
- max = items.size
62
+ max = max ? [max, items.size].min : items.size
70
63
  if max == 0
71
64
  raise ArgumentError, 'empty selection list' unless empty
72
65
 
73
66
  puts empty
74
67
  exit 1
75
68
  end
69
+ tmpl = "%#{[items.size.to_s.size, 2].max}d. %s"
70
+ items = items.map.with_index { |item, i| [item, tmpl % [i.succ, item]] }
76
71
  min = grep ? 1 : [min, max].min
77
- if auto
78
- auto.times { puts } if auto.is_a?(::Numeric)
72
+ require 'io/console'
73
+ if min > 1 || max < items.size
74
+ items = items[min.pred...max]
75
+ min = 1
76
+ end
77
+ page = page ? [page, max].min : max
78
+ cur = 0
79
+ line = 0
80
+ last = (max / page.to_f).ceil
81
+ pre = msg
82
+ scroll = true if border
83
+ clear = -> { std.print "\r\e[K" }
84
+ write = lambda do |s, buf = true|
85
+ line += [s.lines.size, 1].max if buf
86
+ clear.call unless scroll && !border
87
+ std.puts s
88
+ end
89
+ render = lambda do |index, init = false|
90
+ fr = index * page
91
+ to = [fr + page, max].min
92
+ unless init
93
+ std.cursor_up((scroll ? 0 : page) + line.succ)
94
+ clear.call
95
+ end
96
+ line = 0
97
+ items[fr...to].each { |item| write.call(item.last, false) }
79
98
  if border == true
80
- puts print_footer
99
+ write.call(print_footer)
81
100
  elsif border
82
- puts print_footer(border: border)
101
+ write.call(print_footer(border: border))
102
+ end
103
+ fr = scroll ? 1 : fr.succ
104
+ suf = "|,#{multiple.is_a?(::Array) ? "{#{multiple.join(',')}}" : '*'}" if multiple
105
+ msg = "#{pre + (force ? ':' : '?')} [#{fr}-#{to}#{"/#{max}" if scroll && to < max}#{suf}] "
106
+ if cur == last.pred
107
+ cur = -1
108
+ last = 0 if scroll || (attempts -= 1) == 0
109
+ end
110
+ if init && last > 1 && !scroll
111
+ write.call('', false)
112
+ std.cursor_up(1)
83
113
  end
84
- msg = "#{msg + (force ? ':' : '?')} [#{min}-#{max}#{if (n = multiple)
85
- "|,#{n.is_a?(::Array) ? "{#{n.join(',')}}" : '*'}"
86
- end}] "
87
114
  end
115
+ render.call(0, true)
116
+ else
117
+ max ||= 1
118
+ last = 0
88
119
  end
89
120
  between = ->(s) { s.match?(/^\d+$/) && s.to_i.between?(min, max <= min ? Float::INFINITY : max) }
90
121
  Timeout.timeout(timeout) do
91
122
  while (ch = Prompt.reline.readline(msg))
92
123
  ch.strip!
93
- unless ch.empty?
124
+ if !ch.empty?
94
125
  if multiple
95
126
  k = if ch == '*'
96
127
  (min..max).to_a
@@ -112,12 +143,15 @@ module Squared
112
143
  k.uniq!
113
144
  k.sort!
114
145
  unless multiple.is_a?(::Array) && !multiple.include?(k.size)
115
- return index || !items ? k : k.map { |i| items[i.pred] }
146
+ return index || !items ? k : k.map { |i| items[i.pred].first }
116
147
  end
117
148
  end
118
149
  elsif between.call(ch)
119
- return index || !items ? ch.to_i : items[ch.to_i.pred]
150
+ return index || !items ? ch.to_i : items[ch.to_i.pred].first
120
151
  end
152
+ elsif last > 0
153
+ render.call(cur += 1)
154
+ next
121
155
  end
122
156
  attempts -= 1
123
157
  next if attempts > 0
@@ -126,7 +160,7 @@ module Squared
126
160
  break
127
161
  end
128
162
  rescue Interrupt
129
- puts
163
+ std.puts
130
164
  exit 0
131
165
  else
132
166
  [] if multiple
@@ -167,7 +167,9 @@ module Squared
167
167
  end
168
168
 
169
169
  def line_width(lines)
170
- [lines.empty? ? 80 : [lines.max_by(&:size).size, 80].max, Rake.application.terminal_width].min
170
+ require 'io/console/size'
171
+ cols = IO.default_console_size.last
172
+ [lines.empty? ? cols : [lines.max_by(&:size).size, cols].max, Rake.application.terminal_width].min
171
173
  end
172
174
 
173
175
  def fill_option(val, **kwargs)
@@ -28,15 +28,8 @@ module Squared
28
28
  module_function
29
29
 
30
30
  def shell(*args, name: :system, **kwargs)
31
- if name != :system
32
- kwargs.delete(:exception)
33
- elsif RUBY_VERSION < '2.6'
34
- ex = kwargs.delete(:exception)
35
- end
36
- ret = Kernel.send(name, *args, **kwargs)
37
- raise $?.to_s if ex && !ret
38
-
39
- ret
31
+ kwargs.delete(:exception) unless name == :system
32
+ Kernel.send(name, *args, **kwargs)
40
33
  end
41
34
 
42
35
  def shell_t(*args, timeout: 0, **kwargs)
@@ -65,7 +65,7 @@ module Squared
65
65
  command = File.basename(@main)
66
66
  command = ARG[:VIEW] if command == @name
67
67
  end
68
- ext = @ext[1..-1].downcase
68
+ ext = @ext[1..].downcase
69
69
  if (data = @@mime_obj[ext])
70
70
  add(data[1].first, command: command, opts: opts, ext: data[1])
71
71
  else
@@ -98,7 +98,7 @@ module Squared
98
98
  namespace task_name(name) do |ns|
99
99
  @mime.each do |type, items|
100
100
  items.each do |command, file, opts|
101
- next if Rake::Task.task_defined?("#{ns.scope.path}:#{command}:#{type}")
101
+ next if Rake::Task.task_defined?(task_join(ns.scope.path, command, type))
102
102
 
103
103
  namespace command do
104
104
  unless (data = @@mime_obj[type])
@@ -288,7 +288,7 @@ module Squared
288
288
  if project
289
289
  project.workspace.task_name(val)
290
290
  else
291
- @prefix ? "#{@prefix}:#{val}" : val.to_s
291
+ @prefix ? task_join(@prefix, val) : val.to_s
292
292
  end
293
293
  end
294
294
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Squared
4
- VERSION = '0.8.5'
4
+ VERSION = '0.9.0.pre1'
5
5
  end
@@ -163,6 +163,9 @@ module Squared
163
163
  end
164
164
  @pass.items.concat(kwargs[:pass].map { |val| val.is_a?(Regexp) ? val : val.to_s }) if kwargs[:pass]
165
165
  @series = Application.register(self, @base.to_a, exclude: kwargs.fetch(:exclude, []))
166
+ if (n = ARG[:APP][:series_ref_min])
167
+ @series.ref_min = n
168
+ end
166
169
  self.closed = 'begin'
167
170
  pb = @stage.key?('project:begin')
168
171
  pe = @stage.key?('project:end')
@@ -341,7 +344,7 @@ module Squared
341
344
  case val
342
345
  when Symbol
343
346
  found = true
344
- banner_include?(val)
347
+ banner_include?(val, ref: ref)
345
348
  when String
346
349
  true
347
350
  else
@@ -353,7 +356,7 @@ module Squared
353
356
  elsif meth.size == 1
354
357
  meth = meth.first
355
358
  end
356
- elsif !banner_include?(meth = meth.to_sym)
359
+ elsif !banner_include?(meth = meth.to_sym, ref: ref)
357
360
  next
358
361
  end
359
362
  data.order << meth
@@ -409,7 +412,11 @@ module Squared
409
412
  end || @kind[name]&.last
410
413
  if proj
411
414
  @base << proj
412
- @banner.items.concat(Array(proj.bannerargs))
415
+ args = Array(proj.bannerargs)
416
+ unless args.empty?
417
+ @banner.store[proj.ref] = args
418
+ @banner.items.concat(args)
419
+ end
413
420
  else
414
421
  proj = Application.impl_project
415
422
  end
@@ -580,7 +587,7 @@ module Squared
580
587
  def task_namespace(val, first: false)
581
588
  return unless (ret = val.to_s.split(':')).size > 1
582
589
 
583
- first ? ret.first : task_join(*ret[0..-2])
590
+ first ? ret.first : task_join(*ret[..-2])
584
591
  end
585
592
 
586
593
  def task_resolve(obj, key)
@@ -950,7 +957,7 @@ module Squared
950
957
  key = task_name(task_join(name, target))
951
958
  sync = true
952
959
  exception = exception?
953
- if store && (kwargs = store["#{target}:#{name}"])
960
+ if store && (kwargs = store[task_join(target, name)])
954
961
  sync = kwargs[:sync] if kwargs.key?(:sync)
955
962
  exception = kwargs[:exception] if kwargs.key?(:exception)
956
963
  end
@@ -971,9 +978,11 @@ module Squared
971
978
  end
972
979
  end
973
980
  when Array
974
- cmd = args.first
975
- cmd = items.map { |proj| "#{proj.name}:#{cmd}" }
976
- .select { |val| task_defined?(val) }
981
+ cmd = items.filter_map do |proj|
982
+ next unless task_defined?(out = task_join(proj.name, args.first))
983
+
984
+ out
985
+ end
977
986
  next if cmd.empty?
978
987
 
979
988
  args = args.drop(1)
@@ -1014,7 +1023,7 @@ module Squared
1014
1023
  @events[label][name][task] = on
1015
1024
  end
1016
1025
  data[label][name][task] = val
1017
- (data.store[label] ||= {})["#{name}:#{task}"] = store if store
1026
+ (data.store[label] ||= {})[task_join(name, task)] = store if store
1018
1027
  end
1019
1028
  self
1020
1029
  end
@@ -1028,7 +1037,7 @@ module Squared
1028
1037
  Array(group).each do |val|
1029
1038
  val = val.to_s
1030
1039
  if val.start_with?('-')
1031
- target[:exclude] << val[1..-1]
1040
+ target[:exclude] << val[1..]
1032
1041
  else
1033
1042
  target[:group][val] = data
1034
1043
  end
@@ -1037,7 +1046,7 @@ module Squared
1037
1046
  Array(ref).each do |val|
1038
1047
  val = val.to_s
1039
1048
  if val.start_with?('-')
1040
- target[:exclude] << val[1..-1].to_sym
1049
+ target[:exclude] << val[1..].to_sym
1041
1050
  else
1042
1051
  target[:ref][val.to_sym] = data
1043
1052
  end
@@ -1111,8 +1120,10 @@ module Squared
1111
1120
  exception != false && exception != Logger::INFO
1112
1121
  end
1113
1122
 
1114
- def banner_include?(val)
1115
- Application.attr_banner.include?(val) || @banner.items.include?(val)
1123
+ def banner_include?(val, ref: nil)
1124
+ return true if Application.attr_banner.include?(val)
1125
+
1126
+ ref ? Array(ref).any? { |proj| @banner.store[proj]&.include?(val) } : @banner.items.include?(val)
1116
1127
  end
1117
1128
 
1118
1129
  def scriptobj