diff options
author | Douwe Maan <douwe@selenight.nl> | 2016-08-13 04:17:18 +0300 |
---|---|---|
committer | Douwe Maan <douwe@selenight.nl> | 2016-08-13 04:17:18 +0300 |
commit | 5a07b760dff04660d9c7da84852c710b1fc2f786 (patch) | |
tree | 6aa67c32c6b80dcc1c7cbfe4f9f62e57edb76b3e /lib/gitlab/slash_commands | |
parent | 5d4993d62357e438b6211247278025040f3ae382 (diff) |
Refactor slash command definition
Diffstat (limited to 'lib/gitlab/slash_commands')
-rw-r--r-- | lib/gitlab/slash_commands/command_definition.rb | 57 | ||||
-rw-r--r-- | lib/gitlab/slash_commands/dsl.rb | 82 | ||||
-rw-r--r-- | lib/gitlab/slash_commands/extractor.rb | 30 |
3 files changed, 97 insertions, 72 deletions
diff --git a/lib/gitlab/slash_commands/command_definition.rb b/lib/gitlab/slash_commands/command_definition.rb new file mode 100644 index 00000000000..5dec6c91869 --- /dev/null +++ b/lib/gitlab/slash_commands/command_definition.rb @@ -0,0 +1,57 @@ +module Gitlab + module SlashCommands + class CommandDefinition + attr_accessor :name, :aliases, :description, :params, :condition_block, :action_block + + def valid? + name.present? + end + + def all_names + [name, *aliases] + end + + def noop? + action_block.nil? + end + + def available?(opts) + return true unless condition_block + + context = OpenStruct.new(opts) + context.instance_exec(&condition_block) + end + + def to_description(opts) + return description unless description.respond_to?(:call) + + context = OpenStruct.new(opts) + context.instance_exec(&description) rescue '' + end + + def execute(context, opts, *args) + return if noop? || !available?(opts) + + block_arity = action_block.arity + return unless block_arity == -1 || block_arity == args.size + + context.instance_exec(*args, &action_block) + end + + def to_h(opts) + desc = description + if desc.respond_to?(:call) + context = OpenStruct.new(opts) + desc = context.instance_exec(&desc) rescue '' + end + + { + name: name, + aliases: aliases, + description: desc, + params: params + } + end + end + end +end diff --git a/lib/gitlab/slash_commands/dsl.rb b/lib/gitlab/slash_commands/dsl.rb index ce659aff1da..58ba7027f84 100644 --- a/lib/gitlab/slash_commands/dsl.rb +++ b/lib/gitlab/slash_commands/dsl.rb @@ -4,64 +4,16 @@ module Gitlab extend ActiveSupport::Concern included do - cattr_accessor :definitions - end - - def execute_command(name, *args) - name = name.to_sym - cmd_def = self.class.definitions.find do |cmd_def| - self.class.command_name_and_aliases(cmd_def).include?(name) + cattr_accessor :command_definitions, instance_accessor: false do + [] end - return unless cmd_def && cmd_def[:action_block] - return if self.class.command_unavailable?(cmd_def, self) - block_arity = cmd_def[:action_block].arity - if block_arity == -1 || block_arity == args.size - instance_exec(*args, &cmd_def[:action_block]) + cattr_accessor :command_definitions_by_name, instance_accessor: false do + {} end end class_methods do - # This method is used to generate the autocompletion menu. - # It returns no-op slash commands (such as `/cc`). - def command_definitions(opts = {}) - self.definitions.map do |cmd_def| - context = OpenStruct.new(opts) - next if command_unavailable?(cmd_def, context) - - cmd_def = cmd_def.dup - - if cmd_def[:description].respond_to?(:call) - cmd_def[:description] = context.instance_exec(&cmd_def[:description]) rescue '' - end - - cmd_def - end.compact - end - - # This method is used to generate a list of valid commands in the current - # context of `opts`. - # It excludes no-op slash commands (such as `/cc`). - # This list can then be given to `Gitlab::SlashCommands::Extractor`. - def command_names(opts = {}) - self.definitions.flat_map do |cmd_def| - next if cmd_def[:opts].fetch(:noop, false) - - context = OpenStruct.new(opts) - next if command_unavailable?(cmd_def, context) - - command_name_and_aliases(cmd_def) - end.compact - end - - def command_unavailable?(cmd_def, context) - cmd_def[:condition_block] && !context.instance_exec(&cmd_def[:condition_block]) - end - - def command_name_and_aliases(cmd_def) - [cmd_def[:name], *cmd_def[:aliases]] - end - # Allows to give a description to the next slash command. # This description is shown in the autocomplete menu. # It accepts a block that will be evaluated with the context given to @@ -119,19 +71,23 @@ module Gitlab # # Awesome code block # end def command(*command_names, &block) - opts = command_names.extract_options! name, *aliases = command_names - self.definitions ||= [] - self.definitions << { - name: name, - aliases: aliases, - description: @description || '', - params: @params || [], - condition_block: @condition_block, - action_block: block, - opts: opts - } + definition = CommandDefinition.new + definition.name = name + definition.aliases = aliases + definition.description = @description || '' + definition.params = @params || [] + definition.condition_block = @condition_block + definition.action_block = block + + return unless definition.valid? + + self.command_definitions << definition + + definition.all_names.each do |name| + self.command_definitions_by_name[name] = definition + end @description = nil @params = nil diff --git a/lib/gitlab/slash_commands/extractor.rb b/lib/gitlab/slash_commands/extractor.rb index ce0a2eba535..a6838cb5e7c 100644 --- a/lib/gitlab/slash_commands/extractor.rb +++ b/lib/gitlab/slash_commands/extractor.rb @@ -7,10 +7,10 @@ module Gitlab # extractor = Gitlab::SlashCommands::Extractor.new([:open, :assign, :labels]) # ``` class Extractor - attr_reader :command_names + attr_reader :command_definitions - def initialize(command_names) - @command_names = command_names + def initialize(command_definitions) + @command_definitions = command_definitions end # Extracts commands from content and return an array of commands. @@ -26,16 +26,18 @@ module Gitlab # ``` # extractor = Gitlab::SlashCommands::Extractor.new([:open, :assign, :labels]) # msg = %(hello\n/labels ~foo ~"bar baz"\nworld) - # commands = extractor.extract_commands! #=> [['labels', '~foo ~"bar baz"']] + # commands = extractor.extract_commands(msg) #=> [['labels', '~foo ~"bar baz"']] # msg #=> "hello\nworld" # ``` - def extract_commands!(content) + def extract_commands(content, opts) return [] unless content + content = content.dup + commands = [] content.delete!("\r") - content.gsub!(commands_regex) do + content.gsub!(commands_regex(opts)) do if $~[:cmd] commands << [$~[:cmd], $~[:args]].reject(&:blank?) '' @@ -44,11 +46,19 @@ module Gitlab end end - commands + [content.strip, commands] end private + def command_names(opts) + command_definitions.flat_map do |command| + next if command.noop? + + command.all_names + end.compact + end + # Builds a regular expression to match known commands. # First match group captures the command name and # second match group captures its arguments. @@ -56,7 +66,9 @@ module Gitlab # It looks something like: # # /^\/(?<cmd>close|reopen|...)(?:( |$))(?<args>[^\/\n]*)(?:\n|$)/ - def commands_regex + def commands_regex(opts) + names = command_names(opts).map(&:to_s) + @commands_regex ||= %r{ (?<code> # Code blocks: @@ -95,7 +107,7 @@ module Gitlab # Command not in a blockquote, blockcode, or HTML tag: # /close - ^\/(?<cmd>#{command_names.join('|')})(?:(\ |$))(?<args>[^\/\n]*)(?:\n|$) + ^\/(?<cmd>#{Regexp.union(names)})(?:$|\ (?<args>[^\/\n]*)$) ) }mx end |