squared 0.0.1 → 0.0.2

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: 867bfab9c2ffade3d9158228c67fca5f0f4675ecd24a3d7201d0b85f42945d8f
4
- data.tar.gz: 0ee6b84388949cd03c7a0667e7bd0d8aab8b3b30c597da6ae8b486fa3063d89e
3
+ metadata.gz: 117c669053bf0539d41840f655c1f7cb9a87af7d90366ded3c19803949184cdc
4
+ data.tar.gz: 84266b0dd2bc5736eb05b5799692fd4fbed756e8a096343dc85f670e9257ae07
5
5
  SHA512:
6
- metadata.gz: ce95fe135ead9dec6e92106955e921442f3a9fe73a8a11863a8d3022187065056236b52449b8e40e30cd34bd3a95c949a296817af30932b9edc64032cb255919
7
- data.tar.gz: 63d8d2d3e22572d614b3a3d214427a1864cb528c5231e107db984949214955efa804376548b769f90ee7390223dd2b7715fd85ad78d2f15b85e77fe4b48b710e
6
+ metadata.gz: 862884efb840359442202f1670ba4b8ff032397a3e6e6d5720289f844f63ec3119045bfb60b1f2c41673472d32861228587e7a33ab0fbf87d2c7df5e5fa37cde
7
+ data.tar.gz: f775903d05491cbe92d80e9db391afa970c866fa4ae885666ffe4e95282d102df5b9faab6f20b17d9368243ff73c6552ff3c56ecf11423d6f68eaeaf4f242288
data/README.md CHANGED
@@ -81,13 +81,13 @@
81
81
  #### Install
82
82
 
83
83
  ```sh
84
- mkdir -p ~/.bin
85
- PATH="${HOME}/.bin:${PATH}"
84
+ mkdir -p ~/bin/repo
85
+ PATH="${HOME}/bin:${PATH}"
86
86
 
87
- curl https://storage.googleapis.com/git-repo-downloads/repo > ~/.bin/repo
88
- chmod a+rx ~/.bin/repo
87
+ curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
88
+ chmod a+rx ~/bin/repo
89
89
  # OR
90
- scripts/repo-install.sh ~/.bin
90
+ scripts/repo-install.sh ~/bin
91
91
  ```
92
92
 
93
93
  #### Usage
data/README.ruby.md CHANGED
@@ -4,17 +4,14 @@
4
4
  * [manifest](https://github.com/anpham6/squared-repo)
5
5
  * [docs](https://squared.readthedocs.io)
6
6
 
7
- ## Installation
8
-
9
- ```sh
10
- gem install squared
11
- ```
12
-
13
7
  ## Prerequisites
14
8
 
15
9
  * Ruby 2.4+
16
- * Python 3.6+
10
+
11
+ ### Optional
12
+
17
13
  * [Repo](https://source.android.com/docs/setup/reference/repo)
14
+ * Python 3.6+
18
15
 
19
16
  ```sh
20
17
  mkdir -p ~/.bin
@@ -23,26 +20,64 @@ curl https://storage.googleapis.com/git-repo-downloads/repo > ~/.bin/repo
23
20
  chmod a+rx ~/.bin/repo
24
21
  ```
25
22
 
23
+ ## Installation
24
+
25
+ ```sh
26
+ gem install squared
27
+ ```
28
+
26
29
  ## Example - Rakefile
27
30
 
28
31
  ```ruby
29
32
  require "squared"
30
33
 
31
34
  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])
35
+ .new("squared") # REPO_HOME
36
+ .repo("https://github.com/anpham6/squared-repo", "nightly") # Optional
37
+ .run("rake install", ref: :ruby)
38
+ .clean("rake clean", group: "default") # depend test doc
39
+ .clean(["build/"], group: "app")
40
+ .add("pathname", run: "rake compile", copy: "rake install", test: "rake test", group: "default", ref: :ruby) # Ruby (with C extensions)
41
+ .add("optparse", doc: "rake rdoc", group: "default") # Uses bundler/gem_tasks (without C extensions)
42
+ .add("logger", copy: { from: "lib", glob: "**/*.rb", gemdir: "~/.rvm/gems/ruby-3.3.5/gems/logger-1.6.1" }, clean: ["tmp/"]) # autodetect: true
43
+ .add("android", "android-docs", run: false, doc: "make html", ref: :python) # task namespace "android" | directory "android-docs"
44
+ .add("emc", "e-mc", copy: { from: "publish", into: "@e-mc", also: [:pir, "squared-express/"] }, ref: :node) # Node
45
+ .add("pir", "pi-r", copy: { from: "publish", into: "@pi-r" }, clean: ["publish/**/*.js", "tmp/"]) # Trailing slash required for directories
46
+ .add("squared", log: { file: "tmp/%Y-%m-%d.log", level: "debug" }, group: "app") # Copy target (main)
47
+ .style(:banner, 255.255) # 256 colors (fg | fg.bg | -0.bg)
48
+ .build(default: "status", parallel: ["pull", "fetch", "rebase", "copy", "clean"]) do |workspace|
49
+ workspace
50
+ .enable_aixterm
51
+ .style(:banner, "bright_cyan", "bold", "bright_black!")
52
+ .finalize! # Optional
53
+ end
40
54
  ```
41
55
 
56
+ **NOTE**: The use of "**ref**" (class name) is only necessary when running `repo:init` for the first time into an empty directory.
57
+
42
58
  ## Usage
43
59
 
44
60
  ```sh
45
- rake -T
61
+ rake -T # List tasks
62
+ rake # rake status (usually "build")
63
+
64
+ # GIT_OPTIONS=rebase
65
+ rake pull # All except "default" + "app"
66
+ rake pull:ruby # pathname + optparse + logger
67
+ rake pull:default # pathname + optparse
68
+ rake pull:app # squared
69
+ rake pull:node # emc + pir + squared
70
+
71
+ rake build # All except "android"
72
+ rake doc # optparse + android
73
+
74
+ rake build:ruby # rake compile + rake install + rake install
75
+
76
+ rake clean # All except "default" + "app"
77
+ rake clean:ruby # rake clean + rake clean + ["tmp/"]
78
+ rake clean:default # rake clean + rake clean + skip
79
+ rake clean:app # none + skip + ["build/"]
80
+ rake clean:node # none + ["publish/**/*.js", "tmp/"] + ["build/"]
46
81
  ```
47
82
 
48
83
  ## LICENSE
@@ -1,8 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'set'
4
+
3
5
  module Squared
4
6
  module Common
5
7
  class JoinSet < ::Set
8
+ def self.to_s
9
+ /[^:]+$/.match(super.to_s)[0]
10
+ end
11
+
6
12
  def initialize(data = [], delim: ' ')
7
13
  super(data.compact)
8
14
  @delim = delim
@@ -3,6 +3,36 @@
3
3
  module Squared
4
4
  module Common
5
5
  module Format
6
+ include Common
7
+
8
+ AIX_TERM = {
9
+ bright_black: '90',
10
+ bright_red: '91',
11
+ bright_green: '92',
12
+ bright_yellow: '93',
13
+ bright_blue: '94',
14
+ bright_magenta: '95',
15
+ bright_cyan: '96',
16
+ bright_white: '97',
17
+ bright_black!: '100',
18
+ bright_red!: '101',
19
+ bright_green!: '102',
20
+ bright_yellow!: '103',
21
+ bright_blue!: '104',
22
+ bright_magenta!: '105',
23
+ bright_cyan!: '106',
24
+ bright_white!: '107'
25
+ }.freeze
26
+ TEXT_STYLE = [:bold, :dim, :italic, :underline, :blinking, nil, :inverse, :hidden, :strikethrough].freeze
27
+ private_constant :AIX_TERM, :TEXT_STYLE
28
+
29
+ def enable_aixterm
30
+ unless (colors = get!(:colors)).frozen?
31
+ colors.merge!(AIX_TERM)
32
+ end
33
+ block_given? ? yield(self) : self
34
+ end
35
+
6
36
  def emphasize(val, title: nil, cols: nil, sub: nil)
7
37
  n = 0
8
38
  if title
@@ -20,21 +50,23 @@ module Squared
20
50
  require 'io/console'
21
51
  (n = [n, $stdout.winsize[1] - 4].min) rescue nil
22
52
  end
53
+ out = []
23
54
  bord = '-' * (n + 4)
24
- write = ->(line) { err ? warn(line) : puts(line) }
25
55
  sub = as_a(sub)
26
56
  pr = lambda do |line|
27
57
  s = line.ljust(n)
28
58
  sub.each { |h| s = sub_style(s, **h) }
29
59
  "| #{s} |"
30
60
  end
31
- write.(bord)
32
- if title
33
- write.(pr.(title))
34
- write.(bord)
61
+ out << bord
62
+ out.push(pr.(title), bord) if title
63
+ lines.each { |line| out << pr.(line) }
64
+ out << bord
65
+ if block_given?
66
+ yield out
67
+ else
68
+ err ? Warning.warn(out) : puts(out)
35
69
  end
36
- lines.each { |line| write.(pr.(line)) }
37
- write.(bord)
38
70
  end
39
71
 
40
72
  def sub_style(val, *args, pat: nil, styles: nil, index: 1)
@@ -59,20 +91,25 @@ module Squared
59
91
  else
60
92
  s = ret
61
93
  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"
94
+ if type.is_a?(Numeric)
95
+ f, b = type.to_s.split('.')
96
+ s = wrap.(s, ['38', '5', f]) if f[0] != '-' && f.to_i <= 255
97
+ if b
98
+ b = b[0..2]
99
+ s = wrap.(s, ['48', '5', b]) unless b.to_i > 255
100
+ end
69
101
  else
70
- if (c = get(:colors)[t])
102
+ t = type.to_sym
103
+ if (c = get!(:colors)[t])
71
104
  if index == -1
72
105
  s = wrap.(s, [c])
73
106
  else
74
107
  code << c
75
108
  end
109
+ else
110
+ next unless (n = TEXT_STYLE.index { |style| style == t })
111
+
112
+ s = "\x1B[#{n + 1}m#{s}\x1B[#{n == 0 ? 22 : n + 21}m"
76
113
  end
77
114
  end
78
115
  if index == -1
@@ -99,15 +136,18 @@ module Squared
99
136
  out
100
137
  end
101
138
 
102
- def check_style(iter, empty: true)
139
+ def check_style(*args, empty: true)
103
140
  ret = []
104
- colors = get(:colors)
105
- as_a(iter).each do |val|
106
- case (val = val.to_sym)
107
- when :bold, :italic, :underline
141
+ colors = get!(:colors)
142
+ as_a(args, flat: true).each do |val|
143
+ if !val.is_a?(Numeric)
144
+ val = val.to_sym
145
+ ret << val if colors.key?(val) || TEXT_STYLE.include?(val)
146
+ elsif val >= 0 && val <= 256
108
147
  ret << val
109
- else
110
- ret << val if colors.key?(val)
148
+ elsif val < 0 && (b = val.to_s.split('.')[1])
149
+ b = b[0..2]
150
+ ret << "-0.#{b}".to_f unless b.to_i > 255
111
151
  end
112
152
  end
113
153
  !empty && ret.empty? ? nil : ret
@@ -132,7 +172,7 @@ module Squared
132
172
  else
133
173
  level.to_s.downcase.to_sym
134
174
  end
135
- val = get(:logger)[level] || get(:logger)[level = :unknown]
175
+ val = get!(:logger)[level] || get!(:logger)[level = :unknown]
136
176
  level = +level.to_s.upcase
137
177
  case level
138
178
  when 'WARN', 'ERROR', 'FATAL'
@@ -6,16 +6,24 @@ module Squared
6
6
  def shell_escape(val)
7
7
  return val if ::Rake::Win32.windows?
8
8
 
9
+ require 'shellwords'
9
10
  Shellwords.escape(val)
10
11
  end
11
12
 
12
13
  def shell_quote(val, force: true)
13
14
  ret = val.to_s.strip
14
- return ret if (!force && !ret.include?(' ')) || /(?:^|=)(["']).+\1$/m.match?(ret)
15
+ return ret if (!force && !ret.include?(' ')) || ret.match?(/(?:^|=)(["']).+\1$/m)
15
16
 
16
17
  ::Rake::Win32.windows? ? "\"#{double_quote(ret)}\"" : "'#{single_quote(ret)}'"
17
18
  end
18
19
 
20
+ def fill_option(val)
21
+ return "-#{val}" if val.size == 1 || val.match?(/^[a-z]\d+$/i)
22
+
23
+ val = "--#{val}" unless val.start_with?('-')
24
+ shell_escape(val).sub('\\=', '=')
25
+ end
26
+
19
27
  def single_quote(val)
20
28
  val.gsub("'", "'\\\\''")
21
29
  end
@@ -1,8 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'pathname'
4
+
3
5
  module Squared
4
6
  module Common
5
7
  module System
8
+ include Common
9
+
6
10
  def shell(*cmd, **kwargs)
7
11
  if /^2\.[0-5]\./.match?(RUBY_VERSION)
8
12
  exception = kwargs.delete(:exception)
@@ -1,23 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'pathname'
4
- require 'shellwords'
5
4
  require 'logger'
6
- require 'set'
7
5
  require 'rake'
8
6
 
9
7
  module Squared
10
8
  module Common
11
9
  VAR = {
12
10
  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
11
  colors: {
22
12
  black: '30',
23
13
  red: '31',
@@ -35,26 +25,43 @@ module Squared
35
25
  magenta!: '45',
36
26
  cyan!: '46',
37
27
  white!: '47'
28
+ },
29
+ logger: {
30
+ unknown: %i[cyan],
31
+ fatal: %i[white bold red!],
32
+ error: %i[red bold],
33
+ warn: %i[yellow bold],
34
+ info: %i[blue],
35
+ debug: %i[green]
38
36
  }.freeze
39
- }
37
+ }.compare_by_identity
40
38
  private_constant :VAR
41
39
 
42
- def get(key)
40
+ def get!(key)
43
41
  VAR[key.is_a?(::String) ? key.to_sym : key]
44
42
  end
45
43
 
46
- def set(key, val)
44
+ def set!(key, val)
45
+ return if VAR.frozen?
46
+
47
47
  VAR[key.is_a?(::String) ? key.to_sym : key] = val
48
48
  end
49
49
 
50
+ def finalize!
51
+ VAR.each_value(&:freeze)
52
+ VAR.freeze
53
+ end
54
+
50
55
  def message(*args, hint: nil)
51
56
  args.reject(&:empty?).join(' => ') + (hint ? " (#{hint})" : '')
52
57
  end
53
58
 
54
- def as_a(obj)
59
+ def as_a(obj, flat: nil)
55
60
  return [] if obj.nil?
61
+ return [obj] unless obj.is_a?(::Array)
62
+ return obj unless flat
56
63
 
57
- obj.is_a?(::Array) ? obj : [obj]
64
+ obj.flatten(flat == true ? nil : flat)
58
65
  end
59
66
  end
60
67
  end
@@ -3,20 +3,20 @@
3
3
  module Squared
4
4
  module Config
5
5
  class Viewer
6
- include Common
7
- include Format
6
+ include Common::Format
8
7
  include Task
9
8
  include ::Rake::DSL
10
9
 
11
10
  class << self
12
- include Common::Format
13
-
14
11
  attr_reader :styles
15
12
 
16
13
  def style(name, *args)
17
- args = check_style(args)
18
14
  styles[name.to_sym]&.clear&.concat(args)
19
15
  end
16
+
17
+ def to_s
18
+ /[^:]+$/.match(super.to_s)[0]
19
+ end
20
20
  end
21
21
 
22
22
  @styles = {
@@ -34,13 +34,13 @@ module Squared
34
34
 
35
35
  def initialize(main = 'package', project: nil, name: nil)
36
36
  if project
37
- @project = get(:project)[project.to_sym]
37
+ @project = get!(:project)[project.to_sym]
38
38
  @required = true
39
39
  end
40
40
  @name = (name || @project&.name)&.to_s
41
41
  unless @name
42
42
  msg, hint = project ? [project, 'not found'] : %w[name missing]
43
- warn log_message(:warn, msg, subject: 'Config::Viewer', hint: hint, color: !pipe?)
43
+ warn log_message(:warn, msg, subject: self.class, hint: hint, color: !pipe?)
44
44
  @required = true
45
45
  end
46
46
  @main = main
@@ -50,9 +50,9 @@ module Squared
50
50
  def build
51
51
  return unless enabled?
52
52
 
53
- namespace @name do
53
+ namespace name do
54
54
  namespace :view do
55
- if @include['json'] && !::Rake::Task.task_defined?("#{@name}:view:json")
55
+ if @include['json'] && !::Rake::Task.task_defined?("#{name}:view:json")
56
56
  desc format_desc('json')
57
57
  task :json, [:keys] do |_, args|
58
58
  require 'json'
@@ -60,7 +60,7 @@ module Squared
60
60
  end
61
61
  end
62
62
 
63
- if @include['yaml'] && !::Rake::Task.task_defined?("#{@name}:view:yaml")
63
+ if @include['yaml'] && !::Rake::Task.task_defined?("#{name}:view:yaml")
64
64
  desc format_desc('yaml', 'yml')
65
65
  task :yaml, [:keys] do |_, args|
66
66
  require 'yaml'
@@ -69,6 +69,8 @@ module Squared
69
69
  end
70
70
  end
71
71
  end
72
+
73
+ yield self if block_given?
72
74
  end
73
75
 
74
76
  def add(type, gem: nil, parse: nil, ext: [], opts: {}, command: nil, file: nil)
@@ -77,7 +79,7 @@ module Squared
77
79
  require(gem || type)
78
80
  obj = eval(parse)
79
81
  ext = as_a(ext)
80
- namespace @name do
82
+ namespace name do
81
83
  desc format_desc(ext.first || type, command: command)
82
84
  namespace(command || :view) do
83
85
  task type, [:keys] do |_, args|
@@ -115,8 +117,16 @@ module Squared
115
117
  add(type, gem: gem, parse: parse, ext: ext, opts: opts, command: name, file: file)
116
118
  end
117
119
 
120
+ def to_s
121
+ @include.keys.map { |ext| "#{main}.#{ext}" }.join(',')
122
+ end
123
+
124
+ def inspect
125
+ "#<#{self.class}: #{name} => #{main} {#{@include.keys.join(', ')}}>"
126
+ end
127
+
118
128
  def enabled?
119
- !@required || !!@project&.enabled?
129
+ !@required || !!project&.enabled?
120
130
  end
121
131
 
122
132
  protected
@@ -134,20 +144,21 @@ module Squared
134
144
  ext[0] = fmt
135
145
  else
136
146
  keys.unshift(file)
137
- alt = "#{@main}.{#{ext.join(',')}}"
138
- alt = @project.base_path(alt) if @project
147
+ alt = "#{main}.{#{ext.join(',')}}"
148
+ alt = project.base_path(alt) if project
139
149
  file = Dir[alt].first
140
150
  end
141
151
  if !file
142
152
  raise ArgumentError, message(reader.name, "#{File.basename(alt, '.*')}.#{ext.first}", hint: 'not found')
143
153
  end
144
154
  end
155
+ project&.info "#{Viewer}(#{type}) => #{file} {#{keys.join(', ')}}"
145
156
  doc = if reader.respond_to?(:load_file)
146
157
  reader.load_file(file, **@include[type])
147
158
  else
148
159
  reader.parse(File.read(file), **@include[type])
149
160
  end
150
- lines = print_keys(type, doc, keys)
161
+ lines = print_keys(type, doc, keys, file: file)
151
162
  return unless lines
152
163
 
153
164
  title = Pathname.new(file)
@@ -160,18 +171,18 @@ module Squared
160
171
  [
161
172
  { pat: /^([^:]+|(?<! ):(?! ))+$/, styles: Viewer.styles[:banner] },
162
173
  { 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 }
174
+ { pat: /^(.+)( : (?!undefined).+)$/m, styles: Viewer.styles[:key] },
175
+ { pat: /^(.+ : )(-?[\d.]+)(\s*)$/m, styles: Viewer.styles[:number], index: 2 },
176
+ { pat: /^(.+ : ")(.+)("\s*)$/m, styles: Viewer.styles[:string], index: 2 },
177
+ { pat: /^(.+ : \{)(.+)(\}\s*)$/m, styles: Viewer.styles[:hash], index: 2 },
178
+ { pat: /^(.+ : \[)(.+)(\]\s*)$/m, styles: Viewer.styles[:array], index: 2 },
179
+ { pat: /^(.+ : (?!undefined))([^"\[{].*)$/m, styles: Viewer.styles[:value], index: 2 }
169
180
  ]
170
181
  end
171
182
  emphasize(lines, title: title, sub: sub)
172
183
  end
173
184
 
174
- def print_keys(type, data, keys)
185
+ def print_keys(type, data, keys, file: nil)
175
186
  out = []
176
187
  pad = 0
177
188
  symbolize = @include[type][:symbolize_names]
@@ -189,6 +200,7 @@ module Squared
189
200
  end
190
201
  end
191
202
  rescue StandardError
203
+ project&.warn "#{Viewer}(#{type}) => #{file ? "#{file} " : ''}{#{key}: undefined}"
192
204
  val = Regexp.escape($!.message)
193
205
  key = key.sub(/(#{val})\.|\.(#{val})|(#{val})/) do
194
206
  s = "<#{$3 || $2 || $1}>"
@@ -216,11 +228,11 @@ module Squared
216
228
  end
217
229
 
218
230
  def base_path(file)
219
- @project ? @project.base_path(file) : Pathname.new(file).realdirpath
231
+ project ? project.base_path(file) : Pathname.new(file).realdirpath
220
232
  end
221
233
 
222
234
  def format_desc(type, alt = nil, command: nil)
223
- message(@name, command || 'view', "#{type}[#{command.nil? ? "file?=#{@main}.#{alt || type}," : ''}keys*]")
235
+ message(name, command || 'view', "#{type}[#{command.nil? ? "file?=#{main}.#{alt || type}," : ''}keys*]")
224
236
  end
225
237
 
226
238
  def chop_extname(file)
@@ -228,7 +240,7 @@ module Squared
228
240
  end
229
241
 
230
242
  def pipe?
231
- return @project.workspace.pipe? if @project
243
+ return project.workspace.pipe if project
232
244
 
233
245
  val = ENV['PIPE_OUT']
234
246
  !val.nil? && !val.empty? && val != '0'