diff options
author | Fire Demon <firedemon@creature.cafe> | 2020-07-26 03:23:54 -0500 |
---|---|---|
committer | Fire Demon <firedemon@creature.cafe> | 2020-08-30 05:45:16 -0500 |
commit | 1af0cdcd02de9e1dcfca78ac0c8aff209def980f (patch) | |
tree | 1ded845b028e3dda975230863f0087e25c61930f | |
parent | 173ec3d8c765239aa54c12d8a06e7dc404a1ac9d (diff) |
[Command Tags] Fix parsing of of multi-line argument; add support for variables and templating
-rw-r--r-- | app/lib/command_tag/commands/variables.rb | 24 | ||||
-rw-r--r-- | app/lib/command_tag/processor.rb | 69 |
2 files changed, 88 insertions, 5 deletions
diff --git a/app/lib/command_tag/commands/variables.rb b/app/lib/command_tag/commands/variables.rb new file mode 100644 index 000000000..ab90ca13b --- /dev/null +++ b/app/lib/command_tag/commands/variables.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module CommandTag::Commands::Variables + def handle_set_at_start(args) + return if args.blank? + + args[0] = normalize(args[0]) + + case args.count + when 1 + @vars.delete(args[0]) + when 2 + @vars[args[0]] = [args.last] + else + @vars[args[0]] = args[1..-1] + end + end + + def do_unset_at_start(args) + args.each do |arg| + @vars.delete(normalize(arg)) + end + end +end diff --git a/app/lib/command_tag/processor.rb b/app/lib/command_tag/processor.rb index a0d9af66b..e6bd672c1 100644 --- a/app/lib/command_tag/processor.rb +++ b/app/lib/command_tag/processor.rb @@ -17,6 +17,13 @@ class CommandTag::Processor STATEMENT_RE = /^\s*#!\s*([^\n]+ (?:start|begin|do)$.*)\n\s*#!\s*(?:end|stop|done)\s*$|^\s*#!\s*(.*?)\s*$/im.freeze STATEMENT_STRIP_RE = /^\s*#!\s*(?:[^\n]+ (?:start|begin|do)$.*)\n\s*#!\s*(?:end|stop|done)\s*$\n?|^\s*#!\s*(?:.*?)\s*$\n?/im.freeze + TEMPLATE_RE = /\{%\s*(.*?)\s*%\}/.freeze + ESCAPE_MAP = { + '\n' => "\n", + '\r' => "\r", + '\t' => "\t", + '\\\\' => '\\', + }.freeze def initialize(account, status) @account = account @@ -38,7 +45,7 @@ class CommandTag::Processor @text = @text.gsub(STATEMENT_STRIP_RE, '').split("\n") %w(at_start once_at_start).each { |suffix| execute_statements(suffix) } - @text = @text.join("\n").rstrip + @text = parse_templates(@text.join("\n")).rstrip %w(before_save once_before_save).each { |suffix| execute_statements(suffix) } if @text.blank? || @text.gsub(Account::MENTION_RE, '').strip.blank? @@ -72,13 +79,48 @@ class CommandTag::Processor .gsub(/^\s*((?:(?:#{Account::MENTION_RE}|#{Tag::HASHTAG_RE})\s*)+)#!/, "\\1\n#!") end + def parse_templates(text) + text.gsub(TEMPLATE_RE) do + return if Regexp.last_match(1).blank? + + template = Regexp.last_match(1).scan(/'([^']*)'|"([^"]*)"|(\S+)/).flatten.compact + return if template[0].blank? + + name = normalize(template[0]) + separator = "\n" + + if template.count > 2 + if %w(by with using sep separator delim delimiter).include?(template[-2].downcase) + separator = unescape_literals(template[-1]) + template = template[0..-3] + elsif !template[-1].match?(/\A[-+]?[0-9]+\z/) + separator = unescape_literals(template[-1]) + template.pop + end + end + + index_start = to_integer(template[1]) + index_end = to_integer(template[2]) + + if ['all', '[]'].include?(template[1]) + var(name).join(separator) + elsif index_end.zero? + var(name)[index_start].presence || '' + else + var(name)[index_start..index_end].presence || '' + end + end + end + def parse_statements - @text.scan(STATEMENT_RE).flatten.each do |statement| - next if statement.blank? || statement[0]&.strip.blank? + @text.scan(STATEMENT_RE).flatten.compact.each do |statement| + next if statement.blank? - statement = statement.scan(/^(.*) (?:start|begin|do)$(.*)|'([^']*)'|"([^"]*)"|(\S+)/im).flatten.compact + statement = statement.scan(/'([^']*)'|"([^"]*)"|(\S+)|\s+(?:start|begin|do)\s*$[\r\n]+(.*)/im).flatten.compact statement[0] = statement[0].strip.tr(':.\- ', '_').gsub(/__+/, '_').downcase - add_statement_handlers_for(statement) + statement[-1].rstrip! if statement.count > 1 + + add_statement_handlers_for(statement) if statement[0].present? end end @@ -132,6 +174,23 @@ class CommandTag::Processor @status.destroy unless @status.destroyed? end + def normalize(text) + text.to_s.strip.downcase + end + + def to_integer(text) + text&.strip.to_i + end + + def unescape_literals(text) + ESCAPE_MAP.each { |escaped, unescaped| text.gsub!(escaped, unescaped) } + text + end + + def var(name) + @vars[name].presence || [] + end + def read_visibility_from(arg) return if arg.strip.blank? |