2016-06-30 15:34:19 +00:00
|
|
|
module Gitlab
|
|
|
|
module SlashCommands
|
|
|
|
# This class takes an array of commands that should be extracted from a
|
|
|
|
# given text.
|
|
|
|
#
|
|
|
|
# ```
|
|
|
|
# extractor = Gitlab::SlashCommands::Extractor.new([:open, :assign, :labels])
|
|
|
|
# ```
|
|
|
|
class Extractor
|
2016-08-13 01:17:18 +00:00
|
|
|
attr_reader :command_definitions
|
2016-06-30 15:34:19 +00:00
|
|
|
|
2016-08-13 01:17:18 +00:00
|
|
|
def initialize(command_definitions)
|
|
|
|
@command_definitions = command_definitions
|
2016-06-30 15:34:19 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# Extracts commands from content and return an array of commands.
|
|
|
|
# The array looks like the following:
|
|
|
|
# [
|
|
|
|
# ['command1'],
|
|
|
|
# ['command3', 'arg1 arg2'],
|
|
|
|
# ]
|
|
|
|
# The command and the arguments are stripped.
|
|
|
|
# The original command text is removed from the given `content`.
|
|
|
|
#
|
|
|
|
# Usage:
|
|
|
|
# ```
|
|
|
|
# extractor = Gitlab::SlashCommands::Extractor.new([:open, :assign, :labels])
|
|
|
|
# msg = %(hello\n/labels ~foo ~"bar baz"\nworld)
|
2016-08-13 01:17:18 +00:00
|
|
|
# commands = extractor.extract_commands(msg) #=> [['labels', '~foo ~"bar baz"']]
|
2016-06-30 15:34:19 +00:00
|
|
|
# msg #=> "hello\nworld"
|
|
|
|
# ```
|
2016-08-13 01:17:18 +00:00
|
|
|
def extract_commands(content, opts)
|
2016-06-30 15:34:19 +00:00
|
|
|
return [] unless content
|
|
|
|
|
2016-08-13 01:17:18 +00:00
|
|
|
content = content.dup
|
|
|
|
|
2016-06-30 15:34:19 +00:00
|
|
|
commands = []
|
|
|
|
|
2016-08-09 20:47:29 +00:00
|
|
|
content.delete!("\r")
|
2016-08-13 01:17:18 +00:00
|
|
|
content.gsub!(commands_regex(opts)) do
|
2016-08-09 20:47:29 +00:00
|
|
|
if $~[:cmd]
|
|
|
|
commands << [$~[:cmd], $~[:args]].reject(&:blank?)
|
|
|
|
''
|
|
|
|
else
|
|
|
|
$~[0]
|
|
|
|
end
|
2016-06-30 15:34:19 +00:00
|
|
|
end
|
|
|
|
|
2016-08-13 01:17:18 +00:00
|
|
|
[content.strip, commands]
|
2016-06-30 15:34:19 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
2016-08-13 01:17:18 +00:00
|
|
|
def command_names(opts)
|
|
|
|
command_definitions.flat_map do |command|
|
|
|
|
next if command.noop?
|
|
|
|
|
|
|
|
command.all_names
|
|
|
|
end.compact
|
|
|
|
end
|
|
|
|
|
2016-06-30 15:34:19 +00:00
|
|
|
# Builds a regular expression to match known commands.
|
|
|
|
# First match group captures the command name and
|
|
|
|
# second match group captures its arguments.
|
|
|
|
#
|
|
|
|
# It looks something like:
|
|
|
|
#
|
|
|
|
# /^\/(?<cmd>close|reopen|...)(?:( |$))(?<args>[^\/\n]*)(?:\n|$)/
|
2016-08-13 01:17:18 +00:00
|
|
|
def commands_regex(opts)
|
|
|
|
names = command_names(opts).map(&:to_s)
|
|
|
|
|
2016-08-09 20:47:29 +00:00
|
|
|
@commands_regex ||= %r{
|
|
|
|
(?<code>
|
|
|
|
# Code blocks:
|
|
|
|
# ```
|
|
|
|
# Anything, including `/cmd args` which are ignored by this filter
|
|
|
|
# ```
|
|
|
|
|
|
|
|
^```
|
|
|
|
.+?
|
|
|
|
\n```$
|
|
|
|
)
|
|
|
|
|
|
|
|
|
(?<html>
|
|
|
|
# HTML block:
|
|
|
|
# <tag>
|
|
|
|
# Anything, including `/cmd args` which are ignored by this filter
|
|
|
|
# </tag>
|
|
|
|
|
|
|
|
^<[^>]+?>\n
|
|
|
|
.+?
|
|
|
|
\n<\/[^>]+?>$
|
|
|
|
)
|
|
|
|
|
|
|
|
|
(?<html>
|
|
|
|
# Quote block:
|
|
|
|
# >>>
|
|
|
|
# Anything, including `/cmd args` which are ignored by this filter
|
|
|
|
# >>>
|
|
|
|
|
|
|
|
^>>>
|
|
|
|
.+?
|
|
|
|
\n>>>$
|
|
|
|
)
|
|
|
|
|
|
|
|
|
(?:
|
|
|
|
# Command not in a blockquote, blockcode, or HTML tag:
|
|
|
|
# /close
|
|
|
|
|
2016-08-13 01:17:18 +00:00
|
|
|
^\/(?<cmd>#{Regexp.union(names)})(?:$|\ (?<args>[^\/\n]*)$)
|
2016-08-09 20:47:29 +00:00
|
|
|
)
|
|
|
|
}mx
|
2016-06-30 15:34:19 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|