squared 0.8.6 → 0.9.0.pre2

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,219 @@
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), caller(1)
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
+ sqd.options[:'project-env'] << val
138
+ end
139
+ sqd.separator ''
140
+ sqd.on('-s', '--[no-]strict', 'Do not run commands with invalid arguments') do |val|
141
+ session[:STRICT] = val
142
+ ENV['STRICT'] = val ? 'true' : 'false'
143
+ end
144
+ sqd.on('--update', 'Use update strategy for applicable commands') do
145
+ ENV['UPDATE'] = 'true'
146
+ end
147
+ sqd.on('-F', '--force [STR,STR]', Array, 'Enable force through ENV by application') do |val|
148
+ sqd.parse_common(val, session: session, arg: :FORCE)
149
+ end
150
+ sqd.on('-y', '--yes [STR,STR]', Array, 'Enable auto-approve through ENV by application') do |val|
151
+ sqd.parse_common(val, session: session, arg: :YES)
152
+ end
153
+ sqd.on('-d', '--dry-run', 'Run commands without any modifying actions') do
154
+ ENV['DRY_RUN'] = 'true'
155
+ end
156
+ sqd.separator ''
157
+ sqd.on('-S', '--select PAGE[:CHOICE]', 'Prompt menu selection pagination display (e.g. 15:75)') do |val|
158
+ page, choice = val.split(/\D/, 2).map(&:to_i)
159
+ sqd.error_exit('not an integer', val) if choice.nil? ? page == 0 : choice == 0
160
+ session[:PAGE] = page if page > 0
161
+ session[:CHOICE] = choice if choice.to_i > 0
162
+ ENV['SELECT'] = "#{page}:#{choice}"
163
+ end
164
+ sqd.separator ''
165
+ sqd.on('-p', '--pipe 0|1|2', Integer, 'Set file descriptor for communication') do |val|
166
+ sqd.error_exit('out of range', val) unless val.between?(0, 2)
167
+ session[:PIPE] = val
168
+ end
169
+ sqd.on('-o', '--pipe-out FILE', 'Write command data stream to a file') do |val|
170
+ sqd.error_exit("missing an explicit \".#{File::SEPARATOR}\"", val) unless val.include?(File::SEPARATOR)
171
+ session[:OUT] = val
172
+ end
173
+ sqd.on('--fail 0..4', Integer, 'Set application exception level') do |val|
174
+ sqd.error_exit('out of range', val) unless val.between?(0, 4)
175
+ session[:FAIL] = val
176
+ end
177
+ %i[debug info warn error fatal].then do |state|
178
+ values = state + (0..4).map(&:to_s)
179
+ sqd.on('--log-level SEVERITY', values, "Set logger and exception state (#{state.join(', ')})") do |val|
180
+ require 'logger'
181
+ severity = case val
182
+ when :debug then Logger::DEBUG
183
+ when :info then Logger::INFO
184
+ when :warn then Logger::WARN
185
+ when :error then Logger::ERROR
186
+ when :fatal then Logger::FATAL
187
+ else
188
+ val.to_i
189
+ end
190
+ session[:LEVEL] = severity
191
+ ENV['LOG_LEVEL'] = severity.to_s
192
+ end
193
+ end
194
+ sqd.separator ''
195
+ sqd.on('--[no-]banner', 'Display executing command attributes') do |val|
196
+ sqd.banner_set(val, session: session)
197
+ end
198
+ sqd.on('-x', '--[no-]color', 'Use enhanced styling for terminal output') do |val|
199
+ sqd.color_set(val, session: session)
200
+ end
201
+ sqd.on('-X', '--plain', 'Disable banner and styling for all commands') do
202
+ sqd.banner_set(false, session: session)
203
+ sqd.color_set(false, session: session)
204
+ end
205
+ sqd.on('-v', '--verbose', 'Increase output verbosity (multiple)') do
206
+ unless session[:VERBOSE]
207
+ session[:VERBOSE] = $VERBOSE ? 1 : 0
208
+ sqd.global.unshift('--verbose')
209
+ end
210
+ ENV['VERBOSE'] = (session[:VERBOSE] += 1).to_s
211
+ end
212
+ sqd.on('--version', 'Print the current version') do
213
+ puts Squared::VERSION
214
+ exit 0
215
+ end
216
+ sqd.on('-h', '--help', 'Print the command line options') do
217
+ sqd.help_exit
218
+ end
219
+ 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
@@ -48,15 +42,15 @@ module Squared
48
42
  end
49
43
  rescue Interrupt
50
44
  puts
51
- exit 0
45
+ exit
52
46
  else
53
47
  exit 1 if force
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 ch == '-' && !force
95
126
  break
96
127
  elsif multiple
@@ -114,12 +145,15 @@ module Squared
114
145
  k.uniq!
115
146
  k.sort!
116
147
  unless multiple.is_a?(::Array) && !multiple.include?(k.size)
117
- return index || !items ? k : k.map { |i| items[i.pred] }
148
+ return index || !items ? k : k.map { |i| items[i.pred].first }
118
149
  end
119
150
  end
120
151
  elsif between.call(ch)
121
- return index || !items ? ch.to_i : items[ch.to_i.pred]
152
+ return index || !items ? ch.to_i : items[ch.to_i.pred].first
122
153
  end
154
+ elsif last > 0
155
+ render.call(cur += 1)
156
+ next
123
157
  end
124
158
  attempts -= 1
125
159
  next if attempts > 0
@@ -128,8 +162,8 @@ module Squared
128
162
  break
129
163
  end
130
164
  rescue Interrupt
131
- puts
132
- exit 0
165
+ std.puts
166
+ exit
133
167
  else
134
168
  [] if multiple
135
169
  end
@@ -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.6'
4
+ VERSION = '0.9.0.pre2'
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
@@ -590,7 +597,7 @@ module Squared
590
597
  def task_namespace(val, first: false)
591
598
  return unless (ret = val.to_s.split(':')).size > 1
592
599
 
593
- first ? ret.first : task_join(*ret[0..-2])
600
+ first ? ret.first : task_join(*ret[..-2])
594
601
  end
595
602
 
596
603
  def task_resolve(obj, key)
@@ -960,7 +967,7 @@ module Squared
960
967
  key = task_name(task_join(name, target))
961
968
  sync = true
962
969
  exception = exception?
963
- if store && (kwargs = store["#{target}:#{name}"])
970
+ if store && (kwargs = store[task_join(target, name)])
964
971
  sync = kwargs[:sync] if kwargs.key?(:sync)
965
972
  exception = kwargs[:exception] if kwargs.key?(:exception)
966
973
  end
@@ -981,9 +988,11 @@ module Squared
981
988
  end
982
989
  end
983
990
  when Array
984
- cmd = args.first
985
- cmd = items.map { |proj| "#{proj.name}:#{cmd}" }
986
- .select { |val| task_defined?(val) }
991
+ cmd = items.filter_map do |proj|
992
+ next unless task_defined?(out = task_join(proj.name, args.first))
993
+
994
+ out
995
+ end
987
996
  next if cmd.empty?
988
997
 
989
998
  args = args.drop(1)
@@ -1024,7 +1033,7 @@ module Squared
1024
1033
  @events[label][name][task] = on
1025
1034
  end
1026
1035
  data[label][name][task] = val
1027
- (data.store[label] ||= {})["#{name}:#{task}"] = store if store
1036
+ (data.store[label] ||= {})[task_join(name, task)] = store if store
1028
1037
  end
1029
1038
  self
1030
1039
  end
@@ -1038,7 +1047,7 @@ module Squared
1038
1047
  Array(group).each do |val|
1039
1048
  val = val.to_s
1040
1049
  if val.start_with?('-')
1041
- target[:exclude] << val[1..-1]
1050
+ target[:exclude] << val[1..]
1042
1051
  else
1043
1052
  target[:group][val] = data
1044
1053
  end
@@ -1047,7 +1056,7 @@ module Squared
1047
1056
  Array(ref).each do |val|
1048
1057
  val = val.to_s
1049
1058
  if val.start_with?('-')
1050
- target[:exclude] << val[1..-1].to_sym
1059
+ target[:exclude] << val[1..].to_sym
1051
1060
  else
1052
1061
  target[:ref][val.to_sym] = data
1053
1062
  end
@@ -1125,8 +1134,10 @@ module Squared
1125
1134
  exception != false && exception != Logger::INFO
1126
1135
  end
1127
1136
 
1128
- def banner_include?(val)
1129
- Application.attr_banner.include?(val) || @banner.items.include?(val)
1137
+ def banner_include?(val, ref: nil)
1138
+ return true if Application.attr_banner.include?(val)
1139
+
1140
+ ref ? Array(ref).any? { |proj| @banner.store[proj]&.include?(val) } : @banner.items.include?(val)
1130
1141
  end
1131
1142
 
1132
1143
  def scriptobj