mw_dictionary_api 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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 80f489f6fae5b0f1b982015d98eca2fe4ebb4a2c
4
+ data.tar.gz: b7d1b0ac8af7932169d75b7ba5a40c6b0ff63605
5
+ SHA512:
6
+ metadata.gz: efb2c50f4c1d612e36b1077c0455bde9bfd4df2c3b0e1b8225d8536c8a613557ae5aa12f9b35001262d86cc38c5aae17f5fe3999574616d69bef9f36b354c803
7
+ data.tar.gz: 2a72daf3bcbf6a2e2e4471cb6ca7c528b0193e616d07ecd91adceadd70bde7fb1403c4cf6fe5f1bb782de55148d5c30f789a3eabfbd21d24ce83c6adc7db71c7
@@ -0,0 +1,17 @@
1
+ # require 'active_support/core_ext/class/attribute'
2
+ require 'nokogiri'
3
+ require 'mw_dictionary_api/client'
4
+ require 'mw_dictionary_api/result'
5
+ require "mw_dictionary_api/parsable"
6
+ require 'mw_dictionary_api/memory_cache'
7
+ require 'mw_dictionary_api/parsers/entry_parser'
8
+ require 'mw_dictionary_api/parsers/result_parser'
9
+ require 'mw_dictionary_api/parsers/definition_parser'
10
+
11
+ module MWDictionaryAPI
12
+ API_ENDPOINT = 'http://www.dictionaryapi.com/api/v1/references'
13
+
14
+ class ResponseException < Exception
15
+ end
16
+
17
+ end
@@ -0,0 +1,76 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'cgi'
4
+ require 'open-uri'
5
+
6
+ module MWDictionaryAPI
7
+ class Client
8
+ class << self
9
+ def cache
10
+ @cache ||= MemoryCache
11
+ end
12
+
13
+ def cache=(cache)
14
+ @cache = cache
15
+ end
16
+
17
+ def parser_class
18
+ @parser_class ||= Parsers::ResultParser
19
+ end
20
+
21
+ def parser_class=(parser_class)
22
+ @parser_class = parser_class
23
+ end
24
+ end
25
+
26
+
27
+ attr_accessor :api_key, :api_type, :api_endpoint, :response_format
28
+
29
+ # search_cache is something that should have the following interface
30
+ # search_cache.find(term) -> the raw response for the given term
31
+ # search_cache.add(term, result) -> saves the raw response into the cache
32
+ # search_cache.remove(term) -> remove the cached response for the term
33
+ # (optional) search_cache.clear -> clear the cache
34
+ # attr_accessor :search_cache
35
+
36
+ def initialize(api_key, api_type: "sd4", response_format: "xml", api_endpoint: API_ENDPOINT)
37
+ @api_key = api_key
38
+ @api_type = api_type
39
+ @response_format = response_format
40
+ @api_endpoint = api_endpoint
41
+ end
42
+
43
+ def url_for(word)
44
+ "#{api_endpoint}/#{api_type}/#{response_format}/#{CGI.escape(word)}?key=#{api_key}"
45
+ end
46
+
47
+ def search(term, update_cache: false, parser_class: nil)
48
+ if self.class.cache
49
+ if update_cache
50
+ response = fetch_response(term)
51
+ self.class.cache.remove(term)
52
+ self.class.cache.add(term, response)
53
+ else
54
+ response = self.class.cache.find(term)
55
+ unless response
56
+ response = fetch_response(term)
57
+ self.class.cache.add(term, response)
58
+ end
59
+ end
60
+ else
61
+ response = fetch_response(term)
62
+ end
63
+ parser_class = self.class.parser_class if parser_class.nil?
64
+ Result.new(term, response, api_type: api_type, response_format: response_format, parser_class: parser_class)
65
+ end
66
+
67
+ def fetch_response(term)
68
+ result = open(url_for(term))
69
+ if result.status[0] != "200" or result.meta["content-type"] != response_format
70
+ raise ResponseException, result.read
71
+ else
72
+ result.read
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,27 @@
1
+ module MWDictionaryAPI
2
+
3
+ class MemoryCache
4
+
5
+ def self.find(term)
6
+ cache[term]
7
+ end
8
+
9
+ def self.add(term, result)
10
+ cache[term] = result
11
+ end
12
+
13
+ def self.remove(term)
14
+ cache.delete(term)
15
+ end
16
+
17
+ # following methods are optional
18
+ def self.clear
19
+ cache.clear
20
+ end
21
+
22
+ def self.cache
23
+ @cache ||= {}
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,50 @@
1
+ module MWDictionaryAPI
2
+ module Parsable
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ end
6
+
7
+ module ClassMethods
8
+ def rules
9
+ @rules ||= {}
10
+ end
11
+
12
+ def inherited_rules
13
+ if superclass.respond_to? :inherited_rules
14
+ superclass.inherited_rules.merge(rules)
15
+ else
16
+ rules
17
+ end
18
+ end
19
+
20
+ def rule(attr_name, **options, &block)
21
+ rules[attr_name] = { attr_name: attr_name, options: options, block: block }
22
+ end
23
+
24
+ def apply_rule(attr_name, data, options)
25
+ inherited_rules[attr_name][:block].call(data, options)
26
+ end
27
+
28
+ def rule_helpers(&block)
29
+ instance_eval(&block)
30
+ end
31
+ end
32
+
33
+ def initialize(**options)
34
+ @options = options
35
+ end
36
+
37
+ def parse(data)
38
+ attributes = {}
39
+
40
+ self.class.inherited_rules.each do |attr_name, rule|
41
+ unless rule[:options][:hidden]
42
+ options = @options.merge(rule[:options])
43
+ attributes[attr_name] = rule[:block].call(data, options)
44
+ end
45
+ end
46
+ attributes
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,46 @@
1
+ # encoding: UTF-8
2
+
3
+ module MWDictionaryAPI
4
+ module Parsers
5
+ class DefinitionParser
6
+ include Parsable
7
+
8
+ rule :sense_number do |data, opts|
9
+ current_sn = (data[:sn] or "1")
10
+ previous_sn = data[:prev_sn]
11
+
12
+ current_sn = current_sn.gsub(" ", "")
13
+
14
+ if previous_sn.nil?
15
+ current_sn
16
+ else
17
+ if current_sn =~ /^\d+/ # starts with a digit
18
+ current_sn
19
+ elsif current_sn =~ /^[a-z]+/ # starts with a alphabet
20
+ m = /^(\d+)/.match(previous_sn)
21
+ (m) ? m[1] + current_sn : current_sn
22
+ else # starts with a bracket ( e.g. (1)
23
+ m = /^(\d+)*([a-z]+)*/.match(previous_sn)
24
+ m[1..2].select { |segment| !segment.nil? }.join("") + current_sn
25
+ end
26
+ end
27
+ end
28
+
29
+ rule :text do |data, opts|
30
+ dt_without_vi = data[:dt].dup
31
+ dt_without_vi.css("vi").remove
32
+ dt_without_vi.content.strip
33
+ end
34
+
35
+ rule :verbal_illustration do |data, opts|
36
+ data[:dt].at_css("vi").content if data[:dt].at_css("vi")
37
+ end
38
+
39
+ rule :cross_reference do |data, opts|
40
+ data[:dt].xpath("sx").inject([]) do |xrefs, sx|
41
+ xrefs << sx.content
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,74 @@
1
+ # encoding: UTF-8
2
+
3
+ module MWDictionaryAPI
4
+ module Parsers
5
+ class EntryParser
6
+ include Parsable
7
+
8
+ rule :id_attribute do |data, opts|
9
+ data.attribute("id").value
10
+ end
11
+
12
+ rule :head_word do |data, opts|
13
+ parse_entity(data, "hw")
14
+ end
15
+
16
+ rule :pronunciation do |data, opts|
17
+ parse_entity(data, "pr")
18
+ end
19
+
20
+ rule :part_of_speech do |data, opts|
21
+ parse_entity(data, "fl")
22
+ end
23
+
24
+ rule :sound do |data, opts|
25
+ parse_entity(data, "sound wav")
26
+ end
27
+
28
+ rule :word do |data, opts|
29
+ id_attribute = data.attribute("id").value
30
+ m = /(.*)\[(\d+)\]/.match(id_attribute)
31
+ (m) ? m[1] : id_attribute
32
+ end
33
+
34
+ rule :id do |data, opts|
35
+ id_attribute = data.attribute("id").value
36
+ m = /(.*)\[(\d+)\]/.match(id_attribute)
37
+ (m) ? m[2].to_i : 1
38
+ end
39
+
40
+ rule :definitions do |data, opts|
41
+ # here we assume
42
+ # 1. sense number (sn) alway appear before a definition (dt) in pairs
43
+ # 2. definition (dt) appear by itself
44
+ data.xpath("def//sn | def//dt").each_slice(2).inject([]) do |definitions, nodes|
45
+ hash = Hash[nodes.map {|n| n.name.to_sym}.zip(nodes.map {|n| (n.name == 'sn') ? n.content : n })]
46
+ hash[:prev_sn] = definitions[-1][:sense_number] if definitions[-1]
47
+ definitions << DefinitionParser.new(parser_options(opts)).parse(hash)
48
+ end
49
+ end
50
+
51
+ rule :inflections do |data, opts|
52
+ data.xpath("in//il | in//if").each_slice(2).inject([]) do |hashes, nodes|
53
+ hash = Hash[nodes.map {|n| n.name.to_sym}.zip(nodes.map {|n| n.content})]
54
+ hash[:il] = hashes[-1][:il] if hashes[-1] and hash[:il] == "or"
55
+ hashes << hash
56
+ end.inject({}) do |inflections, hash|
57
+ inflections[hash[:il].to_sym] ||= []
58
+ inflections[hash[:il].to_sym] << hash[:if].gsub(/\W/, "")
59
+ inflections
60
+ end
61
+ end
62
+
63
+ rule_helpers do
64
+ def parser_options(opts)
65
+ { api_type: opts[:api_type], response_format: opts[:response_format] }
66
+ end
67
+
68
+ def parse_entity(data, tag)
69
+ data.at_css(tag).content if data.at_css(tag)
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,23 @@
1
+ # encoding: UTF-8
2
+
3
+ module MWDictionaryAPI
4
+ module Parsers
5
+ class ResultParser
6
+ include Parsable
7
+
8
+ rule :entries do |data, opts|
9
+ data.css("entry").inject([]) do |entries, xml_entry|
10
+ parser = Parsers::EntryParser.new(api_type: opts[:api_type], response_format: opts[:response_format])
11
+ entries << parser.parse(xml_entry)
12
+ entries
13
+ end
14
+ end
15
+
16
+ rule :suggestions do |data, opts|
17
+ data.css("suggestion").inject([]) do |suggestions, suggestion_xml|
18
+ suggestions << suggestion_xml.content
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,35 @@
1
+ # encoding: UTF-8
2
+
3
+ module MWDictionaryAPI
4
+ class Result
5
+
6
+ attr_accessor :parser_class
7
+ attr_reader :term, :raw_response, :api_type, :response_format,
8
+ :entries, :suggestions
9
+
10
+ def initialize(term, raw_response, api_type: "sd4", response_format: "xml", parser_class: Parsers::ResultParser)
11
+ @term = term
12
+ @raw_response = raw_response
13
+ @api_type = api_type
14
+ @response_format = response_format
15
+ @parser_class = parser_class
16
+
17
+ parse!
18
+ end
19
+
20
+ def parse!
21
+ parser = parser_class.new(api_type: api_type, response_format: response_format)
22
+ attributes = parser.parse(Nokogiri::XML(raw_response))
23
+ @entries = attributes[:entries]
24
+ @suggestions = attributes[:suggestions]
25
+ end
26
+
27
+ def to_hash
28
+ {
29
+ term: term,
30
+ entries: entries,
31
+ suggestions: suggestions
32
+ }
33
+ end
34
+ end
35
+ end
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mw_dictionary_api
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Frank Liu
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-09-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: nokogiri
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: A Simple Way to Query Merriam Webster Dictionary API
28
+ email: gniquil@gmail.com
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - lib/mw_dictionary_api.rb
34
+ - lib/mw_dictionary_api/client.rb
35
+ - lib/mw_dictionary_api/memory_cache.rb
36
+ - lib/mw_dictionary_api/parsable.rb
37
+ - lib/mw_dictionary_api/result.rb
38
+ - lib/mw_dictionary_api/parsers/result_parser.rb
39
+ - lib/mw_dictionary_api/parsers/entry_parser.rb
40
+ - lib/mw_dictionary_api/parsers/definition_parser.rb
41
+ homepage: https://github.com/gniquil/mw_dictionary_api
42
+ licenses:
43
+ - MIT
44
+ metadata: {}
45
+ post_install_message:
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - '>='
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements: []
60
+ rubyforge_project:
61
+ rubygems_version: 2.0.6
62
+ signing_key:
63
+ specification_version: 4
64
+ summary: Merriam Webster Dictionary API
65
+ test_files: []