mdn_query 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
 - data/.gitignore +9 -0
 - data/.rubocop.yml +34 -0
 - data/.travis.yml +16 -0
 - data/Gemfile +4 -0
 - data/LICENSE.md +25 -0
 - data/README.md +150 -0
 - data/Rakefile +18 -0
 - data/bin/mdn-query +79 -0
 - data/lib/mdn_query.rb +77 -0
 - data/lib/mdn_query/document.rb +36 -0
 - data/lib/mdn_query/entry.rb +62 -0
 - data/lib/mdn_query/errors.rb +42 -0
 - data/lib/mdn_query/list.rb +82 -0
 - data/lib/mdn_query/search.rb +111 -0
 - data/lib/mdn_query/search_result.rb +69 -0
 - data/lib/mdn_query/section.rb +98 -0
 - data/lib/mdn_query/table.rb +114 -0
 - data/lib/mdn_query/traverse_dom.rb +176 -0
 - data/lib/mdn_query/version.rb +4 -0
 - data/mdn_query.gemspec +37 -0
 - data/screenshots/demo.gif +0 -0
 - metadata +234 -0
 
| 
         @@ -0,0 +1,62 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module MdnQuery
         
     | 
| 
      
 2 
     | 
    
         
            +
              # An entry of the Mozilla Developer Network documentation.
         
     | 
| 
      
 3 
     | 
    
         
            +
              class Entry
         
     | 
| 
      
 4 
     | 
    
         
            +
                # @return [String]
         
     | 
| 
      
 5 
     | 
    
         
            +
                attr_reader :title, :description, :url
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                # Creates a new entry.
         
     | 
| 
      
 8 
     | 
    
         
            +
                #
         
     | 
| 
      
 9 
     | 
    
         
            +
                # @param title [String] the title of the entry
         
     | 
| 
      
 10 
     | 
    
         
            +
                # @param description [String] a small excerpt of the entry
         
     | 
| 
      
 11 
     | 
    
         
            +
                # @param url [String] the URL to the entry on the web
         
     | 
| 
      
 12 
     | 
    
         
            +
                # @return [MdnQuery::Entry]
         
     | 
| 
      
 13 
     | 
    
         
            +
                def initialize(title, description, url)
         
     | 
| 
      
 14 
     | 
    
         
            +
                  @title = title
         
     | 
| 
      
 15 
     | 
    
         
            +
                  @description = description
         
     | 
| 
      
 16 
     | 
    
         
            +
                  @url = url
         
     | 
| 
      
 17 
     | 
    
         
            +
                end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                # Returns the string representation of the entry.
         
     | 
| 
      
 20 
     | 
    
         
            +
                #
         
     | 
| 
      
 21 
     | 
    
         
            +
                # @return [String]
         
     | 
| 
      
 22 
     | 
    
         
            +
                def to_s
         
     | 
| 
      
 23 
     | 
    
         
            +
                  "#{title}\n#{description}\n#{url}"
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                # Opens the entry in the default web browser.
         
     | 
| 
      
 27 
     | 
    
         
            +
                #
         
     | 
| 
      
 28 
     | 
    
         
            +
                # @return [void]
         
     | 
| 
      
 29 
     | 
    
         
            +
                def open
         
     | 
| 
      
 30 
     | 
    
         
            +
                  Launchy.open(@url)
         
     | 
| 
      
 31 
     | 
    
         
            +
                end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                # Returns the content of the entry.
         
     | 
| 
      
 34 
     | 
    
         
            +
                #
         
     | 
| 
      
 35 
     | 
    
         
            +
                # The content is fetched from the Mozilla Developer Network's documentation.
         
     | 
| 
      
 36 
     | 
    
         
            +
                # The fetch occurs only once and subsequent calls return the previously
         
     | 
| 
      
 37 
     | 
    
         
            +
                # fetched content.
         
     | 
| 
      
 38 
     | 
    
         
            +
                #
         
     | 
| 
      
 39 
     | 
    
         
            +
                # @raise [MdnQuery::HttpRequestFailed] if a HTTP request fails
         
     | 
| 
      
 40 
     | 
    
         
            +
                # @return [MdnQuery::Document] the content of the entry
         
     | 
| 
      
 41 
     | 
    
         
            +
                def content
         
     | 
| 
      
 42 
     | 
    
         
            +
                  return @content unless @content.nil?
         
     | 
| 
      
 43 
     | 
    
         
            +
                  @content = retrieve(url)
         
     | 
| 
      
 44 
     | 
    
         
            +
                end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                private
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                def retrieve(url)
         
     | 
| 
      
 49 
     | 
    
         
            +
                  begin
         
     | 
| 
      
 50 
     | 
    
         
            +
                    response = RestClient::Request.execute(method: :get, url: url,
         
     | 
| 
      
 51 
     | 
    
         
            +
                                                           headers: { accept: 'text/html' })
         
     | 
| 
      
 52 
     | 
    
         
            +
                  rescue RestClient::Exception, SocketError => e
         
     | 
| 
      
 53 
     | 
    
         
            +
                    raise MdnQuery::HttpRequestFailed.new(url, e),
         
     | 
| 
      
 54 
     | 
    
         
            +
                          'Could not retrieve entry'
         
     | 
| 
      
 55 
     | 
    
         
            +
                  end
         
     | 
| 
      
 56 
     | 
    
         
            +
                  dom = Nokogiri::HTML(response.body)
         
     | 
| 
      
 57 
     | 
    
         
            +
                  title = dom.css('h1').text
         
     | 
| 
      
 58 
     | 
    
         
            +
                  article = dom.css('article')
         
     | 
| 
      
 59 
     | 
    
         
            +
                  MdnQuery::TraverseDom.create_document(article, title, url)
         
     | 
| 
      
 60 
     | 
    
         
            +
                end
         
     | 
| 
      
 61 
     | 
    
         
            +
              end
         
     | 
| 
      
 62 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,42 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module MdnQuery
         
     | 
| 
      
 2 
     | 
    
         
            +
              # The standard error.
         
     | 
| 
      
 3 
     | 
    
         
            +
              class Error < StandardError; end
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
              # The error when no entries were found.
         
     | 
| 
      
 6 
     | 
    
         
            +
              class NoEntryFound < MdnQuery::Error
         
     | 
| 
      
 7 
     | 
    
         
            +
                # @return [String] the query that was searched for
         
     | 
| 
      
 8 
     | 
    
         
            +
                attr_reader :query
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                # @return [Hash] the options used for the search
         
     | 
| 
      
 11 
     | 
    
         
            +
                attr_reader :options
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                # Creates a new NoEntryFound error.
         
     | 
| 
      
 14 
     | 
    
         
            +
                #
         
     | 
| 
      
 15 
     | 
    
         
            +
                # @param query [String] the query that was searched for
         
     | 
| 
      
 16 
     | 
    
         
            +
                # @param options [Hash] the options used for the search
         
     | 
| 
      
 17 
     | 
    
         
            +
                # @return [MdnQuery::NoEntryFound]
         
     | 
| 
      
 18 
     | 
    
         
            +
                def initialize(query, options = {})
         
     | 
| 
      
 19 
     | 
    
         
            +
                  @query = query
         
     | 
| 
      
 20 
     | 
    
         
            +
                  @options = options
         
     | 
| 
      
 21 
     | 
    
         
            +
                end
         
     | 
| 
      
 22 
     | 
    
         
            +
              end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
              # The error for failed HTTP request of any kind.
         
     | 
| 
      
 25 
     | 
    
         
            +
              class HttpRequestFailed < MdnQuery::Error
         
     | 
| 
      
 26 
     | 
    
         
            +
                # @return [String] the URL of the request
         
     | 
| 
      
 27 
     | 
    
         
            +
                attr_reader :url
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                # @return [SocketError, RestClient::Exception] the original error
         
     | 
| 
      
 30 
     | 
    
         
            +
                attr_reader :http_error
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                # Creates a new HttpRequestFailed error.
         
     | 
| 
      
 33 
     | 
    
         
            +
                #
         
     | 
| 
      
 34 
     | 
    
         
            +
                # @param url [String] the URL of the request
         
     | 
| 
      
 35 
     | 
    
         
            +
                # @param error [SocketError, RestClient::Exception] the original error
         
     | 
| 
      
 36 
     | 
    
         
            +
                # @return [MdnQuery::HttpRequestFailed]
         
     | 
| 
      
 37 
     | 
    
         
            +
                def initialize(url, error)
         
     | 
| 
      
 38 
     | 
    
         
            +
                  @url = url
         
     | 
| 
      
 39 
     | 
    
         
            +
                  @http_error = error
         
     | 
| 
      
 40 
     | 
    
         
            +
                end
         
     | 
| 
      
 41 
     | 
    
         
            +
              end
         
     | 
| 
      
 42 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,82 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module MdnQuery
         
     | 
| 
      
 2 
     | 
    
         
            +
              # A list from a search result.
         
     | 
| 
      
 3 
     | 
    
         
            +
              class List
         
     | 
| 
      
 4 
     | 
    
         
            +
                # @return [String] the query that was searched for
         
     | 
| 
      
 5 
     | 
    
         
            +
                attr_reader :query
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                # @return [Array<MdnQuery::Entry>]
         
     | 
| 
      
 8 
     | 
    
         
            +
                attr_reader :items
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                # Creates a new list of search results.
         
     | 
| 
      
 11 
     | 
    
         
            +
                #
         
     | 
| 
      
 12 
     | 
    
         
            +
                # @param query [String] the query that was searched for
         
     | 
| 
      
 13 
     | 
    
         
            +
                # @param items [MdnQuery::Entry] the items in the list
         
     | 
| 
      
 14 
     | 
    
         
            +
                # @return [MdnQuery::List]
         
     | 
| 
      
 15 
     | 
    
         
            +
                def initialize(query, *items)
         
     | 
| 
      
 16 
     | 
    
         
            +
                  items = [] if items.nil?
         
     | 
| 
      
 17 
     | 
    
         
            +
                  @query = query
         
     | 
| 
      
 18 
     | 
    
         
            +
                  @items = items
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                # Returns the first item in the list.
         
     | 
| 
      
 22 
     | 
    
         
            +
                #
         
     | 
| 
      
 23 
     | 
    
         
            +
                # @return [MdnQuery::Entry] the first item
         
     | 
| 
      
 24 
     | 
    
         
            +
                def first
         
     | 
| 
      
 25 
     | 
    
         
            +
                  items.first
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                # Retrieves the item at the given position.
         
     | 
| 
      
 29 
     | 
    
         
            +
                #
         
     | 
| 
      
 30 
     | 
    
         
            +
                # @param pos [Fixnum] the position of the item
         
     | 
| 
      
 31 
     | 
    
         
            +
                # @return [MdnQuery::Entry] the item at position `pos`
         
     | 
| 
      
 32 
     | 
    
         
            +
                def [](pos)
         
     | 
| 
      
 33 
     | 
    
         
            +
                  items[pos]
         
     | 
| 
      
 34 
     | 
    
         
            +
                end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                # Returns whether the list is empty.
         
     | 
| 
      
 37 
     | 
    
         
            +
                #
         
     | 
| 
      
 38 
     | 
    
         
            +
                # @return [Boolean] whether the list is empty
         
     | 
| 
      
 39 
     | 
    
         
            +
                def empty?
         
     | 
| 
      
 40 
     | 
    
         
            +
                  items.empty?
         
     | 
| 
      
 41 
     | 
    
         
            +
                end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                # Returns the number of items in the list.
         
     | 
| 
      
 44 
     | 
    
         
            +
                #
         
     | 
| 
      
 45 
     | 
    
         
            +
                # @return [Fixnum] the number of items
         
     | 
| 
      
 46 
     | 
    
         
            +
                def size
         
     | 
| 
      
 47 
     | 
    
         
            +
                  items.size
         
     | 
| 
      
 48 
     | 
    
         
            +
                end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                # Calls the given block for every item.
         
     | 
| 
      
 51 
     | 
    
         
            +
                #
         
     | 
| 
      
 52 
     | 
    
         
            +
                # @param block [Block] block to be executed for every item.
         
     | 
| 
      
 53 
     | 
    
         
            +
                # @return [void]
         
     | 
| 
      
 54 
     | 
    
         
            +
                def each(&block)
         
     | 
| 
      
 55 
     | 
    
         
            +
                  items.each(&block)
         
     | 
| 
      
 56 
     | 
    
         
            +
                end
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                # Returns the string representation of the list.
         
     | 
| 
      
 59 
     | 
    
         
            +
                #
         
     | 
| 
      
 60 
     | 
    
         
            +
                # @return [String]
         
     | 
| 
      
 61 
     | 
    
         
            +
                def to_s
         
     | 
| 
      
 62 
     | 
    
         
            +
                  return "No results for '#{query}'" if empty?
         
     | 
| 
      
 63 
     | 
    
         
            +
                  "Results for '#{query}':\n#{number_items(items).join("\n")}\n"
         
     | 
| 
      
 64 
     | 
    
         
            +
                end
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                private
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                def number_items(items)
         
     | 
| 
      
 69 
     | 
    
         
            +
                  num_width = items.size / 10 + 1
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
                  items.map.with_index do |item, index|
         
     | 
| 
      
 72 
     | 
    
         
            +
                    entry = "#{(index + 1).to_s.rjust(num_width)}) "
         
     | 
| 
      
 73 
     | 
    
         
            +
                    entry << pad_left(item.to_s, num_width + 2)
         
     | 
| 
      
 74 
     | 
    
         
            +
                  end
         
     | 
| 
      
 75 
     | 
    
         
            +
                end
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
                def pad_left(str, num)
         
     | 
| 
      
 78 
     | 
    
         
            +
                  pad = ' ' * num
         
     | 
| 
      
 79 
     | 
    
         
            +
                  str.gsub("\n", "\n#{pad}")
         
     | 
| 
      
 80 
     | 
    
         
            +
                end
         
     | 
| 
      
 81 
     | 
    
         
            +
              end
         
     | 
| 
      
 82 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,111 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module MdnQuery
         
     | 
| 
      
 2 
     | 
    
         
            +
              # A search request to the Mozilla Developer Network documentation.
         
     | 
| 
      
 3 
     | 
    
         
            +
              class Search
         
     | 
| 
      
 4 
     | 
    
         
            +
                # @return [String] a search option (see {#initialize})
         
     | 
| 
      
 5 
     | 
    
         
            +
                attr_accessor :css_classnames, :locale, :highlight, :html_attributes,
         
     | 
| 
      
 6 
     | 
    
         
            +
                              :query, :result, :topics
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                # rubocop:disable Metrics/LineLength
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                # Creates a new search.
         
     | 
| 
      
 11 
     | 
    
         
            +
                #
         
     | 
| 
      
 12 
     | 
    
         
            +
                # The search request is not automatically executed (use {#execute}).
         
     | 
| 
      
 13 
     | 
    
         
            +
                #
         
     | 
| 
      
 14 
     | 
    
         
            +
                # @param query [String] the query to search for
         
     | 
| 
      
 15 
     | 
    
         
            +
                # @param options [Hash] the search query options (more informations on
         
     | 
| 
      
 16 
     | 
    
         
            +
                #   {https://developer.mozilla.org/en-US/docs/MDN/Contribute/Tools/Search#Search_query_format})
         
     | 
| 
      
 17 
     | 
    
         
            +
                # @option options :css_classnames [String] the CSS classes to match
         
     | 
| 
      
 18 
     | 
    
         
            +
                # @option options :highlight [Boolean] whether the query is highlighted
         
     | 
| 
      
 19 
     | 
    
         
            +
                # @option options :html_attributes [String] the HTML attribute text to match
         
     | 
| 
      
 20 
     | 
    
         
            +
                # @option options :locale [String] the locale to match against
         
     | 
| 
      
 21 
     | 
    
         
            +
                # @option options :topics [Array<String>] the topics to search in
         
     | 
| 
      
 22 
     | 
    
         
            +
                # @return [MdnQuery::Search]
         
     | 
| 
      
 23 
     | 
    
         
            +
                def initialize(query, options = {})
         
     | 
| 
      
 24 
     | 
    
         
            +
                  @url = "#{MdnQuery::BASE_URL}.json"
         
     | 
| 
      
 25 
     | 
    
         
            +
                  @query = query
         
     | 
| 
      
 26 
     | 
    
         
            +
                  @css_classnames = options[:css_classnames]
         
     | 
| 
      
 27 
     | 
    
         
            +
                  @locale = options[:locale] || 'en-US'
         
     | 
| 
      
 28 
     | 
    
         
            +
                  @highlight = options[:highlight] || false
         
     | 
| 
      
 29 
     | 
    
         
            +
                  @html_attributes = options[:html_attributes]
         
     | 
| 
      
 30 
     | 
    
         
            +
                  @topics = options[:topics] || ['js']
         
     | 
| 
      
 31 
     | 
    
         
            +
                end
         
     | 
| 
      
 32 
     | 
    
         
            +
                # rubocop:enable Metrics/LineLength
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                # Creates the URL used for the request.
         
     | 
| 
      
 35 
     | 
    
         
            +
                #
         
     | 
| 
      
 36 
     | 
    
         
            +
                # @return [String] the full URL
         
     | 
| 
      
 37 
     | 
    
         
            +
                def url
         
     | 
| 
      
 38 
     | 
    
         
            +
                  query_url = "#{@url}?q=#{@query}&locale=#{@locale}"
         
     | 
| 
      
 39 
     | 
    
         
            +
                  query_url << @topics.map { |t| "&topic=#{t}" }.join
         
     | 
| 
      
 40 
     | 
    
         
            +
                  unless @css_classnames.nil?
         
     | 
| 
      
 41 
     | 
    
         
            +
                    query_url << "&css_classnames=#{@css_classnames}"
         
     | 
| 
      
 42 
     | 
    
         
            +
                  end
         
     | 
| 
      
 43 
     | 
    
         
            +
                  unless @html_attributes.nil?
         
     | 
| 
      
 44 
     | 
    
         
            +
                    query_url << "&html_attributes=#{@html_attributes}"
         
     | 
| 
      
 45 
     | 
    
         
            +
                  end
         
     | 
| 
      
 46 
     | 
    
         
            +
                  query_url << "&highlight=#{@highlight}" unless @highlight.nil?
         
     | 
| 
      
 47 
     | 
    
         
            +
                  query_url
         
     | 
| 
      
 48 
     | 
    
         
            +
                end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                # Executes the search request.
         
     | 
| 
      
 51 
     | 
    
         
            +
                #
         
     | 
| 
      
 52 
     | 
    
         
            +
                # @return [MdnQuery::SearchResult] the search result
         
     | 
| 
      
 53 
     | 
    
         
            +
                def execute
         
     | 
| 
      
 54 
     | 
    
         
            +
                  @result = retrieve(url, @query)
         
     | 
| 
      
 55 
     | 
    
         
            +
                end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                # Fetches the next page of the search result.
         
     | 
| 
      
 58 
     | 
    
         
            +
                #
         
     | 
| 
      
 59 
     | 
    
         
            +
                # If there is no search result yet, {#execute} will be called instead.
         
     | 
| 
      
 60 
     | 
    
         
            +
                #
         
     | 
| 
      
 61 
     | 
    
         
            +
                # @return [MdnQuery::SearchResult] if a new result has been acquired
         
     | 
| 
      
 62 
     | 
    
         
            +
                # @return [nil] if there is no next page
         
     | 
| 
      
 63 
     | 
    
         
            +
                def next_page
         
     | 
| 
      
 64 
     | 
    
         
            +
                  if @result.nil?
         
     | 
| 
      
 65 
     | 
    
         
            +
                    execute
         
     | 
| 
      
 66 
     | 
    
         
            +
                  elsif @result.next?
         
     | 
| 
      
 67 
     | 
    
         
            +
                    query_url = url
         
     | 
| 
      
 68 
     | 
    
         
            +
                    query_url << "&page=#{@result.current_page + 1}"
         
     | 
| 
      
 69 
     | 
    
         
            +
                    @result = retrieve(query_url, @query)
         
     | 
| 
      
 70 
     | 
    
         
            +
                  end
         
     | 
| 
      
 71 
     | 
    
         
            +
                end
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
                # Fetches the previous page of the search result.
         
     | 
| 
      
 74 
     | 
    
         
            +
                #
         
     | 
| 
      
 75 
     | 
    
         
            +
                # If there is no search result yet, {#execute} will be called instead.
         
     | 
| 
      
 76 
     | 
    
         
            +
                #
         
     | 
| 
      
 77 
     | 
    
         
            +
                # @return [MdnQuery::SearchResult] if a new result has been acquired
         
     | 
| 
      
 78 
     | 
    
         
            +
                # @return [nil] if there is no previous page
         
     | 
| 
      
 79 
     | 
    
         
            +
                def previous_page
         
     | 
| 
      
 80 
     | 
    
         
            +
                  if @result.nil?
         
     | 
| 
      
 81 
     | 
    
         
            +
                    execute
         
     | 
| 
      
 82 
     | 
    
         
            +
                  elsif @result.previous?
         
     | 
| 
      
 83 
     | 
    
         
            +
                    query_url = url
         
     | 
| 
      
 84 
     | 
    
         
            +
                    query_url << "&page=#{@result.current_page - 1}"
         
     | 
| 
      
 85 
     | 
    
         
            +
                    @result = retrieve(query_url, @query)
         
     | 
| 
      
 86 
     | 
    
         
            +
                  end
         
     | 
| 
      
 87 
     | 
    
         
            +
                end
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
                # Opens the search in the default web browser.
         
     | 
| 
      
 90 
     | 
    
         
            +
                #
         
     | 
| 
      
 91 
     | 
    
         
            +
                # @return [void]
         
     | 
| 
      
 92 
     | 
    
         
            +
                def open
         
     | 
| 
      
 93 
     | 
    
         
            +
                  html_url = url.sub('.json?', '?')
         
     | 
| 
      
 94 
     | 
    
         
            +
                  Launchy.open(html_url)
         
     | 
| 
      
 95 
     | 
    
         
            +
                end
         
     | 
| 
      
 96 
     | 
    
         
            +
             
     | 
| 
      
 97 
     | 
    
         
            +
                private
         
     | 
| 
      
 98 
     | 
    
         
            +
             
     | 
| 
      
 99 
     | 
    
         
            +
                def retrieve(url, query)
         
     | 
| 
      
 100 
     | 
    
         
            +
                  begin
         
     | 
| 
      
 101 
     | 
    
         
            +
                    response = RestClient::Request.execute(method: :get, url: url,
         
     | 
| 
      
 102 
     | 
    
         
            +
                                                           headers: { accept: 'json' })
         
     | 
| 
      
 103 
     | 
    
         
            +
                  rescue RestClient::Exception, SocketError => e
         
     | 
| 
      
 104 
     | 
    
         
            +
                    raise MdnQuery::HttpRequestFailed.new(url, e),
         
     | 
| 
      
 105 
     | 
    
         
            +
                          'Could not retrieve search result'
         
     | 
| 
      
 106 
     | 
    
         
            +
                  end
         
     | 
| 
      
 107 
     | 
    
         
            +
                  json = JSON.parse(response.body, symbolize_names: true)
         
     | 
| 
      
 108 
     | 
    
         
            +
                  MdnQuery::SearchResult.new(query, json)
         
     | 
| 
      
 109 
     | 
    
         
            +
                end
         
     | 
| 
      
 110 
     | 
    
         
            +
              end
         
     | 
| 
      
 111 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,69 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module MdnQuery
         
     | 
| 
      
 2 
     | 
    
         
            +
              # A result from a search query.
         
     | 
| 
      
 3 
     | 
    
         
            +
              class SearchResult
         
     | 
| 
      
 4 
     | 
    
         
            +
                # @return [Array<Hash>] the raw items of the search result
         
     | 
| 
      
 5 
     | 
    
         
            +
                attr_reader :items
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                # @return [Hash] information about the pages
         
     | 
| 
      
 8 
     | 
    
         
            +
                attr_reader :pages
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                # @return [String] the query that was searched for
         
     | 
| 
      
 11 
     | 
    
         
            +
                attr_reader :query
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                # @return [Fixnum] the total number of entries
         
     | 
| 
      
 14 
     | 
    
         
            +
                attr_reader :total
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                # Creates a new search result.
         
     | 
| 
      
 17 
     | 
    
         
            +
                #
         
     | 
| 
      
 18 
     | 
    
         
            +
                # @param query [String] the query that was searched for
         
     | 
| 
      
 19 
     | 
    
         
            +
                # @param json [Hash] the hash version of the JSON response
         
     | 
| 
      
 20 
     | 
    
         
            +
                # @return [MdnQuery::SearchResult]
         
     | 
| 
      
 21 
     | 
    
         
            +
                def initialize(query, json)
         
     | 
| 
      
 22 
     | 
    
         
            +
                  @query = query
         
     | 
| 
      
 23 
     | 
    
         
            +
                  @pages = {
         
     | 
| 
      
 24 
     | 
    
         
            +
                    count: json[:pages] || 0,
         
     | 
| 
      
 25 
     | 
    
         
            +
                    current: json[:page]
         
     | 
| 
      
 26 
     | 
    
         
            +
                  }
         
     | 
| 
      
 27 
     | 
    
         
            +
                  @total = json[:count]
         
     | 
| 
      
 28 
     | 
    
         
            +
                  @items = json[:documents]
         
     | 
| 
      
 29 
     | 
    
         
            +
                end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                # Returns whether there are any entries.
         
     | 
| 
      
 32 
     | 
    
         
            +
                #
         
     | 
| 
      
 33 
     | 
    
         
            +
                # @return [Boolean]
         
     | 
| 
      
 34 
     | 
    
         
            +
                def empty?
         
     | 
| 
      
 35 
     | 
    
         
            +
                  @pages[:count].zero?
         
     | 
| 
      
 36 
     | 
    
         
            +
                end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                # Returns whether there is a next page.
         
     | 
| 
      
 39 
     | 
    
         
            +
                #
         
     | 
| 
      
 40 
     | 
    
         
            +
                # @return [Boolean]
         
     | 
| 
      
 41 
     | 
    
         
            +
                def next?
         
     | 
| 
      
 42 
     | 
    
         
            +
                  !empty? && @pages[:current] < @pages[:count]
         
     | 
| 
      
 43 
     | 
    
         
            +
                end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                # Returns whether there is a previous page.
         
     | 
| 
      
 46 
     | 
    
         
            +
                #
         
     | 
| 
      
 47 
     | 
    
         
            +
                # @return [Boolean]
         
     | 
| 
      
 48 
     | 
    
         
            +
                def previous?
         
     | 
| 
      
 49 
     | 
    
         
            +
                  !empty? && @pages[:current] > 1
         
     | 
| 
      
 50 
     | 
    
         
            +
                end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                # Returns the number of the current page.
         
     | 
| 
      
 53 
     | 
    
         
            +
                #
         
     | 
| 
      
 54 
     | 
    
         
            +
                # @return [Fixnum]
         
     | 
| 
      
 55 
     | 
    
         
            +
                def current_page
         
     | 
| 
      
 56 
     | 
    
         
            +
                  @pages[:current]
         
     | 
| 
      
 57 
     | 
    
         
            +
                end
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                # Creates a list with the items.
         
     | 
| 
      
 60 
     | 
    
         
            +
                #
         
     | 
| 
      
 61 
     | 
    
         
            +
                # @return [MdnQuery::List]
         
     | 
| 
      
 62 
     | 
    
         
            +
                def to_list
         
     | 
| 
      
 63 
     | 
    
         
            +
                  items = @items.map do |i|
         
     | 
| 
      
 64 
     | 
    
         
            +
                    MdnQuery::Entry.new(i[:title], i[:excerpt], i[:url])
         
     | 
| 
      
 65 
     | 
    
         
            +
                  end
         
     | 
| 
      
 66 
     | 
    
         
            +
                  MdnQuery::List.new(query, *items)
         
     | 
| 
      
 67 
     | 
    
         
            +
                end
         
     | 
| 
      
 68 
     | 
    
         
            +
              end
         
     | 
| 
      
 69 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,98 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module MdnQuery
         
     | 
| 
      
 2 
     | 
    
         
            +
              # A section of an entry of the Mozilla Developer Network documentation.
         
     | 
| 
      
 3 
     | 
    
         
            +
              class Section
         
     | 
| 
      
 4 
     | 
    
         
            +
                # @return [Array<MdnQuery::Section>] the list of child sections
         
     | 
| 
      
 5 
     | 
    
         
            +
                attr_reader :children
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                # @return [String] the name and title of the section
         
     | 
| 
      
 8 
     | 
    
         
            +
                attr_reader :name
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                # @return [Fixnum] the level of the section
         
     | 
| 
      
 11 
     | 
    
         
            +
                attr_reader :level
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                # @return [MdnQuery::Section] the parent section
         
     | 
| 
      
 14 
     | 
    
         
            +
                attr_reader :parent
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                # @return [Array<String>] the text segments of the section
         
     | 
| 
      
 17 
     | 
    
         
            +
                attr_reader :text
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                # Creates a new section.
         
     | 
| 
      
 20 
     | 
    
         
            +
                #
         
     | 
| 
      
 21 
     | 
    
         
            +
                # @param name [String] the name and title of the section
         
     | 
| 
      
 22 
     | 
    
         
            +
                # @param level [Fixnum] the level of the section
         
     | 
| 
      
 23 
     | 
    
         
            +
                # @param parent [MdnQuery::Section] the parent section
         
     | 
| 
      
 24 
     | 
    
         
            +
                # @return [MdnQuery::Section]
         
     | 
| 
      
 25 
     | 
    
         
            +
                def initialize(name, level: 1, parent: nil)
         
     | 
| 
      
 26 
     | 
    
         
            +
                  @name = name
         
     | 
| 
      
 27 
     | 
    
         
            +
                  @level = level
         
     | 
| 
      
 28 
     | 
    
         
            +
                  @parent = parent
         
     | 
| 
      
 29 
     | 
    
         
            +
                  @text = []
         
     | 
| 
      
 30 
     | 
    
         
            +
                  @children = []
         
     | 
| 
      
 31 
     | 
    
         
            +
                end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                # Creates a new child section.
         
     | 
| 
      
 34 
     | 
    
         
            +
                #
         
     | 
| 
      
 35 
     | 
    
         
            +
                # @param name [String] the name and title of the child section
         
     | 
| 
      
 36 
     | 
    
         
            +
                # @return [MdnQuery::Section] the new child section
         
     | 
| 
      
 37 
     | 
    
         
            +
                def create_child(name)
         
     | 
| 
      
 38 
     | 
    
         
            +
                  child = MdnQuery::Section.new(name, parent: self, level: @level + 1)
         
     | 
| 
      
 39 
     | 
    
         
            +
                  @children << child
         
     | 
| 
      
 40 
     | 
    
         
            +
                  child
         
     | 
| 
      
 41 
     | 
    
         
            +
                end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                # Appends a text segment to the section.
         
     | 
| 
      
 44 
     | 
    
         
            +
                #
         
     | 
| 
      
 45 
     | 
    
         
            +
                # Spaces before and after newlines are removed. If the text segment is empty
         
     | 
| 
      
 46 
     | 
    
         
            +
                # (i.e. consists of just whitespaces), it is not appended.
         
     | 
| 
      
 47 
     | 
    
         
            +
                #
         
     | 
| 
      
 48 
     | 
    
         
            +
                # @param text [String] the text segment to append
         
     | 
| 
      
 49 
     | 
    
         
            +
                # @return [void]
         
     | 
| 
      
 50 
     | 
    
         
            +
                def append_text(text)
         
     | 
| 
      
 51 
     | 
    
         
            +
                  trimmed_text = text.gsub(/\n[[:blank:]]+|[[:blank:]]+\n/, "\n")
         
     | 
| 
      
 52 
     | 
    
         
            +
                  @text << trimmed_text unless text_empty?(trimmed_text)
         
     | 
| 
      
 53 
     | 
    
         
            +
                end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                # Appends a code segment to the section.
         
     | 
| 
      
 56 
     | 
    
         
            +
                #
         
     | 
| 
      
 57 
     | 
    
         
            +
                # If the code segment is empty (i.e. consists of just whitespaces), it is
         
     | 
| 
      
 58 
     | 
    
         
            +
                # not appended. The given snippet is embedded in a Markdown code block.
         
     | 
| 
      
 59 
     | 
    
         
            +
                #
         
     | 
| 
      
 60 
     | 
    
         
            +
                # @example Add a JavaScript snippet
         
     | 
| 
      
 61 
     | 
    
         
            +
                #   append_code("const name = 'My Name';", language: 'javascript')
         
     | 
| 
      
 62 
     | 
    
         
            +
                #   # adds the following text:
         
     | 
| 
      
 63 
     | 
    
         
            +
                #   # ```javascript
         
     | 
| 
      
 64 
     | 
    
         
            +
                #   # const name = 'My Name';
         
     | 
| 
      
 65 
     | 
    
         
            +
                #   # ```
         
     | 
| 
      
 66 
     | 
    
         
            +
                #
         
     | 
| 
      
 67 
     | 
    
         
            +
                # @param snippet [String] the code segment to append
         
     | 
| 
      
 68 
     | 
    
         
            +
                # @param language [String] the language of the code
         
     | 
| 
      
 69 
     | 
    
         
            +
                # @return [void]
         
     | 
| 
      
 70 
     | 
    
         
            +
                def append_code(snippet, language: '')
         
     | 
| 
      
 71 
     | 
    
         
            +
                  @text << "\n```#{language}\n#{snippet}\n```\n" unless text_empty?(snippet)
         
     | 
| 
      
 72 
     | 
    
         
            +
                end
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
                # Returns the string representation of the section.
         
     | 
| 
      
 75 
     | 
    
         
            +
                #
         
     | 
| 
      
 76 
     | 
    
         
            +
                # @return [String]
         
     | 
| 
      
 77 
     | 
    
         
            +
                def to_s
         
     | 
| 
      
 78 
     | 
    
         
            +
                  str = "#{'#' * level} #{name}\n\n#{join_text}\n\n#{join_children}\n"
         
     | 
| 
      
 79 
     | 
    
         
            +
                  str.gsub!(/\n+[[:blank:]]+\n+|\n{3,}/, "\n\n")
         
     | 
| 
      
 80 
     | 
    
         
            +
                  str.strip!
         
     | 
| 
      
 81 
     | 
    
         
            +
                  str
         
     | 
| 
      
 82 
     | 
    
         
            +
                end
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
                private
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
                def join_text
         
     | 
| 
      
 87 
     | 
    
         
            +
                  text.join("\n")
         
     | 
| 
      
 88 
     | 
    
         
            +
                end
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
                def join_children
         
     | 
| 
      
 91 
     | 
    
         
            +
                  children.map(&:to_s).join("\n\n")
         
     | 
| 
      
 92 
     | 
    
         
            +
                end
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
                def text_empty?(text)
         
     | 
| 
      
 95 
     | 
    
         
            +
                  !text.match(/\A\s*\z/).nil?
         
     | 
| 
      
 96 
     | 
    
         
            +
                end
         
     | 
| 
      
 97 
     | 
    
         
            +
              end
         
     | 
| 
      
 98 
     | 
    
         
            +
            end
         
     |