File: tyr.rb 1 #!/usr/bin/ruby 2 3 # The MIT License (MIT) 4 # 5 # Copyright © 2024 pacman64 6 # 7 # Permission is hereby granted, free of charge, to any person obtaining a copy 8 # of this software and associated documentation files (the “Software”), to deal 9 # in the Software without restriction, including without limitation the rights 10 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 # copies of the Software, and to permit persons to whom the Software is 12 # furnished to do so, subject to the following conditions: 13 # 14 # The above copyright notice and this permission notice shall be included in 15 # all copies or substantial portions of the Software. 16 # 17 # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 # SOFTWARE. 24 25 26 info = <<-EOF 27 tyr [options...] [ruby expression] [filepath/URI...] 28 29 30 Transform Yaml with Ruby loads YAML data, runs a Ruby expression on it, and 31 emits the result as YAML. Parsed input-data are available to the expression 32 as any of the variables named `v`, `value`, `d`, and `data`. 33 34 If no file/URI is given, it loads YAML data from its standard input. If the 35 argument before the expression is a single equals sign (a `=`, without the 36 quotes), no data are read/parsed, and the expression is evaluated as given. 37 38 Options, where leading double-dashes are also allowed, except for alias `=`: 39 40 -h show this help message 41 -help same as -h 42 43 -nil don't read any input 44 -no-input same as -nil 45 -noinput same as -nil 46 -none same as -nil 47 -null same as -nil 48 = same as -nil 49 50 -t show a full traceback of this script for exceptions 51 -trace same as -t 52 -traceback same as -t 53 EOF 54 55 56 if ARGV.any? { |e| ['-h', '--h', '-help', '--help'].include?(e) } 57 STDERR.print(info) 58 exit(0) 59 end 60 61 62 require 'random/formatter' 63 require 'set' 64 require 'uri' 65 require 'yaml' 66 67 68 def self.open(name, &block) 69 throw 'global function `open` is disabled' 70 end 71 72 73 def load_yaml(name) 74 require 'open-uri' 75 if name == '-' 76 return YAML.load(STDIN) 77 else 78 return URI.open(name) { |r| YAML.load(r) } 79 end 80 end 81 82 83 def fix_result(res, value) 84 if res.respond_to?(:call) 85 return res(value) 86 end 87 88 # if res.class == Hash 89 # can_go_deep = res.respond_to?(:deep_stringify_keys) 90 # return can_go_deep ? res.deep_stringify_keys : res.stringify_keys 91 # end 92 93 return res 94 end 95 96 97 def main() 98 expression = nil 99 load_input = true 100 traceback = false 101 input_name = nil 102 103 no_input_opts = [ 104 '=', '-nil', '--nil', '-none', '--none', '-null', '--null', 105 '-noinput', '-noinput', '-no-input', '--no-input', 106 ] 107 traceback_opts = [ 108 '-t', '--t', '-trace', '--trace', '-traceback', '--traceback', 109 ] 110 111 ARGV.each do |e| 112 if no_input_opts.include?(e) 113 load_input = false 114 elsif traceback_opts.include?(e) 115 traceback = true 116 elsif expression.nil? 117 expression = e 118 elsif input_name.nil? 119 input_name = e 120 else 121 STDERR.print("\x1b[31mgiven too many arguments\x1b[0m\n") 122 exit(1) 123 end 124 end 125 126 if expression.nil? 127 STDERR.print("\x1b[31mno transformation expression given\x1b[0m\n") 128 exit(1) 129 end 130 131 begin 132 if !load_input 133 data = nil 134 elsif input_name.nil? || input_name == '-' 135 data = YAML.load(STDIN) 136 else 137 data = load_yaml(input_name) 138 end 139 140 # using the global vars env because of all the extras defined in it 141 TOPLEVEL_BINDING.local_variable_set(:d, data) 142 TOPLEVEL_BINDING.local_variable_set(:data, data) 143 TOPLEVEL_BINDING.local_variable_set(:v, data) 144 TOPLEVEL_BINDING.local_variable_set(:val, data) 145 TOPLEVEL_BINDING.local_variable_set(:value, data) 146 147 if expression.strip != '.' 148 data = fix_result(TOPLEVEL_BINDING.eval(expression), data) 149 end 150 151 YAML.dump(data, STDOUT) 152 STDOUT.print("\n") 153 rescue => e 154 if traceback 155 throw e 156 end 157 STDERR.print("\x1b[31m#{e}\x1b[0m\n") 158 exit(1) 159 end 160 end 161 162 163 d = nil 164 data = nil 165 v = nil 166 val = nil 167 value = nil 168 169 nihil = nil 170 none = nil 171 null = nil 172 s = '' 173 174 months = [ 175 'January', 'February', 'March', 'April', 'May', 'June', 176 'July', 'August', 'September', 'October', 'November', 'December', 177 ] 178 179 monweek = [ 180 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 181 'Saturday', 'Sunday', 182 ] 183 184 sunweek = [ 185 'Sunday', 186 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 187 ] 188 189 module Dottable 190 def method_missing(m, *args, &block) 191 Hash(self)[m] 192 end 193 end 194 195 phy = { 196 'kilo': 1_000, 197 'mega': 1_000_000, 198 'giga': 1_000_000_000, 199 'tera': 1_000_000_000_000, 200 'peta': 1_000_000_000_000_000, 201 'exa': 1_000_000_000_000_000_000, 202 'zetta': 1_000_000_000_000_000_000_000, 203 204 'c': 299_792_458, 205 'kcd': 683, 206 'na': 602214076000000000000000, 207 208 'femto': 1e-15, 209 'pico': 1e-12, 210 'nano': 1e-9, 211 'micro': 1e-6, 212 'milli': 1e-3, 213 214 'e': 1.602176634e-19, 215 'f': 96_485.33212, 216 'h': 6.62607015e-34, 217 'k': 1.380649e-23, 218 'mu': 1.66053906892e-27, 219 220 'ge': 9.7803267715, 221 'gn': 9.80665, 222 } 223 224 phy.extend(Dottable) 225 226 physics = phy 227 228 # using literal strings on the cmd-line is often tricky/annoying: some of 229 # these aliases can help get around multiple levels of string-quoting; no 230 # quotes are needed as the script will later make these values accessible 231 # via the property/dot syntax 232 sym = { 233 'amp': '&', 234 'ampersand': '&', 235 'ansiclear': '\x1b[0m', 236 'ansinormal': '\x1b[0m', 237 'ansireset': '\x1b[0m', 238 'apo': '\'', 239 'apos': '\'', 240 'ast': '*', 241 'asterisk': '*', 242 'at': '@', 243 'backquote': '`', 244 'backslash': '\\', 245 'backtick': '`', 246 'ball': '●', 247 'bang': '!', 248 'bigsigma': 'Σ', 249 'block': '█', 250 'bquo': '`', 251 'bquote': '`', 252 'bslash': '\\', 253 'bullet': '•', 254 'caret': '^', 255 'cdot': '·', 256 'circle': '●', 257 'colon': ':', 258 'comma': ',', 259 'cr': '\r', 260 'crlf': '\r\n', 261 'cross': '×', 262 'cs': ', ', 263 # 'dash': '–', 264 'dash': '—', 265 'dollar': '$', 266 'dot': '.', 267 'dquo': '"', 268 'dquote': '"', 269 'emark': '!', 270 'empty': '', 271 'eq': '=', 272 'et': '&', 273 'euro': '€', 274 'ge': '≥', 275 'geq': '≥', 276 'gt': '>', 277 'hellip': '…', 278 'hole': '○', 279 'infinity': '∞', 280 'le': '≤', 281 'leq': '≤', 282 'lf': '\n', 283 'lt': '<', 284 'mdash': '—', 285 'mdot': '·', 286 'miniball': '•', 287 'minus': '-', 288 'ndash': '–', 289 'neq': '≠', 290 'perc': '%', 291 'percent': '%', 292 'period': '.', 293 'plus': '+', 294 'qmark': '?', 295 'que': '?', 296 'sball': '•', 297 'semi': ';', 298 'semicolon': ';', 299 'sharp': '#', 300 'slash': '/', 301 'space': ' ', 302 'square': '■', 303 'squo': '\'', 304 'squote': '\'', 305 'tab': '\t', 306 'tilde': '~', 307 'underscore': '_', 308 'uscore': '_', 309 'utf8bom': '\xef\xbb\xbf', 310 } 311 312 sym.extend(Dottable) 313 314 symbols = sym 315 316 units = { 317 'cup2l': 0.23658824, 318 'floz2l': 0.0295735295625, 319 'floz2ml': 29.5735295625, 320 'ft2m': 0.3048, 321 'gal2l': 3.785411784, 322 'in2cm': 2.54, 323 'lb2kg': 0.45359237, 324 'mi2km': 1.609344, 325 'mpg2kpl': 0.425143707, 326 'nmi2km': 1.852, 327 'oz2g': 28.34952312, 328 'psi2pa': 6894.757293168, 329 'ton2kg': 907.18474, 330 'yd2m': 0.9144, 331 332 'mol': 602214076000000000000000, 333 'mole': 602214076000000000000000, 334 335 'hour': 3_600, 336 'day': 86_400, 337 'week': 604_800, 338 339 'hr': 3_600, 340 'wk': 604_800, 341 342 'kb': 1024, 343 'mb': 1024**2, 344 'gb': 1024**3, 345 'tb': 1024**4, 346 'pb': 1024**5, 347 } 348 349 units.extend(Dottable) 350 351 352 main()