# frozen_string_literal: true require_relative '../response' module <%= @namespace %> module Actions # Global setup methods included into every action through {Base}. module GlobalParams <% main.params.each { |param| %><%= partial '_param_method', param %><% } %> end # Base class for all {<%= @namespace %>::Api} actions. "Actions" is MediaWiki's term for # different request types. Everything you are doing with your target MediaWiki installation, you # are doing by _performing actions_. # # Typically, you should never instantiate this class or its descendants directly, but rather by # {<%= @namespace %>::Api Api} methods (included from {<%= @namespace %>::Actions Actions} # module). # # The usual workflow with actions is: # # * Create it with `api.action_name` # * Set action params with subsequent `paramname(paramvalue)` calls; # * Perform action with {#perform} (returns row MediaWiki response as a string) or {#response} # (returns {<%= @namespace %>::Response} class with parsed data and helper methods). # # Note that some of `paramname(value)` calls include new {Modules} into action, which provides # new params & methods. For example: # # ```ruby # api.query.generator(:categorymembers) # includes new methods of GCategorymembers module # .title('Category:DOS_games') # one of GCategorymembers methods, adds gcmtitle=Category:DOS_games to URL # .limit(20) # ``` # # Sometimes new modules inclusion can change a sense of already existing methods: # # ```ruby # api.query.titles('Argentina') # .prop(:revisions) # .prop method from Query action, adds prop=revisions to URL and includes Revisions module # .prop(:content) # .prop method from Revisions module, adds rvprop=content to URL # ``` # # Despite of how questionable this practice looks, it provides the most obvious method chains even # for most complicated cases. # # Some setup methods shared between all the actions (like output format and TTL settings of # response) are defined in {GlobalParams}. # # Full action usage example: # # ```ruby # api = MediaWiktory::Wikipedia::Api.new # action = api.new.query.titles('Argentina').prop(:revisions).prop(:content).meta(:siteinfo) # # => #"Argentina", "prop"=>"revisions", "rvprop"=>"content", "meta"=>"siteinfo"}> # response = action.response # # => # # puts response['pages'].values # all pages... # .first['revisions'] # take revisions from first... # .first['*'] # take content from first revision... # .split("\n").first(3) # and first 3 paragrapahs # # {{other uses}} # # {{pp-semi|small=yes}} # # {{Use dmy dates|date=March 2017}} # response.dig('general', 'sitename') # # => "Wikipedia" # ``` # class Base # @private attr_reader :client # @private def initialize(client, options = {}) @client = client @params = stringify_hash(options) @submodules = [] end # @return [String] def inspect "#<#{self.class.name} #{to_h}>" end # Make new action, with additional params passed as `hash`. No params validations are made. # # @param hash [Hash] Params to merge. All keys and values would be stringified. # @return [Action] Produced action of the same type as current action was, with all passed # params applied. def merge(hash) replace = hash.fetch(:replace, true) hash.delete(:replace) self.class .new(@client, @params.merge(stringify_hash(hash)) { |_, o, n| replace ? n : [o, n].compact.uniq.join('|') }) .tap { |action| @submodules.each { |sm| action.submodule(sm) } } end # All action's params in a ready to passing to URL form (string keys & string values). # # @example # api.query.titles('Argentina', 'Bolivia').format(:json).to_h # # => {"titles"=>"Argentina|Bolivia", "format"=>"json"} # # @return [Hash{String => String}] def to_h @params.dup end # Action's name on MediaWiki API (e.g. "query" for `Query` action, "parsoid-batch" for # `ParsoidBatch` action and so on). # # @return [String] def name # Query # => query # ParsoidBatch # => parsoid-batch self.class.name.split('::').last.gsub(/([a-z])([A-Z])/, '\1-\2').downcase or fail ArgumentError, "Can't guess action name from #{self.class.name}" end # All action's params in a ready to passing to URL form (string keys & string values). Unlike # {#to_h}, includes also action name. # # @example # api.query.titles('Argentina', 'Bolivia').format(:json).to_param # # => {"titles"=>"Argentina|Bolivia", "format"=>"json", "action"=>"query"} # # @return [Hash{String => String}] def to_param to_h.merge('action' => name) end # Full URL for this action invocation. # # @example # api.query.titles('Argentina', 'Bolivia').format(:json).to_url # # => "https://en.wikipedia.org/w/api.php?action=query&format=json&titles=Argentina%7CBolivia" # # @return [String] def to_url url = @client.url url.query_values = to_param url.to_s end # Performs action (through `GET` or `POST` request, depending on action's type) and returns # raw body of response. # # @return [String] def perform fail NotImplementedError, 'Action is abstract, all actions should descend from Actions::Get or Actions::Post' end # Performs action (as in {#perform}) and returns an instance of {Response}. It is a thing # wrapper around parsed action's JSON response, which separates "content" and "meta" (paging, # warnings/erros) fields. # # Note, that not all actions return a JSON suitable for parsing into {Response}. For example, # Wikipedia's [opensearch](https://en.wikipedia.org/w/api.php?action=help&modules=opensearch) # action returns absolutely different JSON structure, corresponding to global # [OpenSearch](https://en.wikipedia.org/wiki/OpenSearch) standard. # # Note also, that on erroneous request (when response contains `"error"` key), this method # will raise {Response::Error}. # # @return [Response] def response jsonable = format(:json) Response.parse(jsonable, jsonable.perform) end include GlobalParams private # Not in indepented module to decrease generated files/modules list def stringify_hash(hash, recursive: false) hash.map { |k, v| [k.to_s, v.is_a?(Hash) && recursive ? stringify_hash(v, recursive: true) : v.to_s] }.to_h end protected def submodule(mod) extend(mod) @submodules << mod self end end class Get < Base def perform client.get(to_param) end end class Post < Base def perform client.post(to_param) end end end end