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()