guardian_searcher 0.1.1 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 102bea1ce30d48d194d5a647b02e19056a5f050b183e72a56c092aff8a99b892
4
- data.tar.gz: 865d25653fdec0b4d3ac1ccb71356397605bb4293ab6bdcb821090b571b2d85e
3
+ metadata.gz: dd89428bf856036e8d19111810e1a8b0bab64c545db11b6b0779b1f7c7654016
4
+ data.tar.gz: f485ddd884db4a21c9f75a0961640f46143014cbb392ac7de6e894726964cbce
5
5
  SHA512:
6
- metadata.gz: 1aaa54fb01c01802fd8e4f71fc922ef517631edeba4978ee8431613fdaf46115b92bdedb76917142bf3bafe8215c8e38d9c9fe84c945471435c501f0f2b938c4
7
- data.tar.gz: 5df31cf5ebd052c9a2376261a410f2f2298577c4986663832114ccb7f854f7ba50a1063415402972f2e0a6826a82c04ea1ec7606199a417aed01338d00b62032
6
+ metadata.gz: d67c795dca8079a6768b8ab9bfd44335fde907c110dac3a4fbd3be20cab9da5e0f2ebf125f41003c936b8d13bc486ce33eae3c0e1232f3bfd6b9cd66229ead45
7
+ data.tar.gz: 4868618271ecc78d2cfc66c1389bba17504072755a06dbdd563cfa5023450ad0bc5674f09416ca728e915c46f931a65c04a912577c84f4e80035f9a899293943
data/.gitignore CHANGED
@@ -6,7 +6,10 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
-
9
+ # Ignore built gemfiles
10
+ *.gem
10
11
  # rspec failure tracking
11
12
  Gemfile.lock
12
13
  .rspec_status
14
+ .byebug_history
15
+ .DS_Store
data/CHANGELOG.md CHANGED
@@ -1,5 +1,6 @@
1
1
  ## [Unreleased]
2
2
 
3
+
3
4
  ## [0.1.0] - 2022-10-01
4
5
 
5
6
  - Initial release
@@ -7,3 +8,15 @@
7
8
  ## [0.1.1] - 2022-10-01
8
9
 
9
10
  - Fix dependency warnings
11
+
12
+ ## [0.1.2] - 2022-10-04
13
+
14
+ - Moved options parse in their Options class
15
+ - Updated readme
16
+
17
+ ## [0.1.3] - 2022-10-23
18
+
19
+ - Added Content class
20
+ - Added Helpers classes ( Generator & Util)
21
+ - Added some additional methods to Base class - search tags and editons endpoints
22
+ - Improved code coverage (Happy Paths only for now)
data/Guardfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # A sample Guardfile
2
4
  # More info at https://github.com/guard/guard#readme
3
5
 
@@ -14,7 +16,7 @@
14
16
  #
15
17
  # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
16
18
 
17
- # Note: The cmd option is now required due to the increasing number of ways
19
+ # NOTE: The cmd option is now required due to the increasing number of ways
18
20
  # rspec may be run, below are examples of the most common uses.
19
21
  # * bundler: 'bundle exec rspec'
20
22
  # * bundler binstubs: 'bin/rspec'
@@ -40,7 +42,7 @@ guard :rspec, cmd: "bundle exec rspec" do
40
42
  dsl.watch_spec_files_for(ruby.lib_files)
41
43
 
42
44
  # Rails files
43
- rails = dsl.rails(view_extensions: %w(erb haml slim))
45
+ rails = dsl.rails(view_extensions: %w[erb haml slim])
44
46
  dsl.watch_spec_files_for(rails.app_files)
45
47
  dsl.watch_spec_files_for(rails.views)
46
48
 
@@ -69,12 +71,12 @@ guard :rspec, cmd: "bundle exec rspec" do
69
71
  end
70
72
 
71
73
  guard :bundler do
72
- require 'guard/bundler'
73
- require 'guard/bundler/verify'
74
+ require "guard/bundler"
75
+ require "guard/bundler/verify"
74
76
  helper = Guard::Bundler::Verify.new
75
77
 
76
- files = ['Gemfile']
77
- files += Dir['*.gemspec'] if files.any? { |f| helper.uses_gemspec?(f) }
78
+ files = ["Gemfile"]
79
+ files += Dir["*.gemspec"] if files.any? { |f| helper.uses_gemspec?(f) }
78
80
 
79
81
  # Assume files are symlinked from somewhere
80
82
  files.each { |file| watch(helper.real_path(file)) }
data/README.md CHANGED
@@ -1,8 +1,11 @@
1
1
  # GuardianSearcher
2
2
 
3
- This is a work in progress, and its status is currently not even an alpha version. Tests needs to be implemented and the code is not optimal.
3
+
4
+ This is a work in progress, and its status is currently an alpha version. Tests needs to be implemented and the code is not optimal.
4
5
  The goal of this project is to provide a Ruby wrapper to query the Guardian Api and to experiment with some programming techniques.
5
6
 
7
+ Documentation of TheGuardian API is [Here](https://open-platform.theguardian.com/documentation/)
8
+
6
9
  If you wanna try it you need to have an API key and use it as an environment variable.
7
10
 
8
11
  ```bash
@@ -26,7 +29,107 @@ Or install it yourself as:
26
29
 
27
30
  ## Usage
28
31
 
29
- TODO: Write usage instructions here
32
+ ```ruby
33
+ # To include the gem in your code
34
+ require 'guardian_searcher'
35
+
36
+ # To initialise the gem
37
+ searcher = GuardianSearcher::Search.new(api_key: <your-api-key>)
38
+
39
+ # Simplest usage
40
+ results = searcher.search('your keyword')
41
+ ```
42
+
43
+ There are some supported option that will be mapped to the api query and these are in the Options
44
+ class
45
+
46
+ ```ruby
47
+ {
48
+ from_date: "from-date",
49
+ to_date: "to-date",
50
+ page_size: "page-size",
51
+ page: "page"
52
+ }
53
+ ```
54
+
55
+ In this way your search could become something like
56
+
57
+ ```ruby
58
+ results = searcher.search('your keyword', { from_date: '2022-10-01', page_size: 10 })
59
+ ```
60
+
61
+ If you add something unsupported it will throw an `OptionsNotSupportedError`
62
+
63
+ The results of the search can be used as they are, a Farady response object or you can parse them using `GuardianSearcher::SearchResult` in the following way:
64
+
65
+ ```ruby
66
+ response_body = searcher.search('your keyword', { from_date: '2022-10-01', page_size: 10 }).body
67
+ results = GuardianSearcher::SearchResult.parse_results(body: response_body)
68
+ ```
69
+ This will return a `SearchResult` object which the following attributes:
70
+
71
+ ```ruby
72
+ @current_page
73
+ @results # an array with all the search results
74
+ @page_size # paging size
75
+ @pages # number of pages
76
+ @start # starting page
77
+ ```
78
+
79
+ Of interest the structure of a single element of the results array, which is an Hash array similar to this
80
+
81
+ ```ruby
82
+ {"id"=>"football/2022/sep/23/player-mutiny-exposes-deeper-issues-within-spanish-womens-football",
83
+ "type"=>"article",
84
+ "sectionId"=>"football",
85
+ "sectionName"=>"Football",
86
+ "webPublicationDate"=>"2022-09-23T19:20:09Z",
87
+ "webTitle"=>"Player mutiny exposes deeper issues within Spanish women’s football | Sid Lowe",
88
+ "webUrl"=>"https://www.theguardian.com/football/2022/sep/23/player-mutiny-exposes-deeper-issues-within-spanish-womens-football",
89
+ "apiUrl"=>"https://content.guardianapis.com/football/2022/sep/23/player-mutiny-exposes-deeper-issues-within-spanish-womens-football",
90
+ "isHosted"=>false,
91
+ "pillarId"=>"pillar/sport",
92
+ "pillarName"=>"Sport"}
93
+ ```
94
+ At this point you can use the `SearchResult` object as it is or you could convert it to an Array of `Content` objects in the following way:
95
+ ```ruby
96
+ generator = GuardianSearcher::Helpers::Generator.new
97
+ # results is the SearchResult object created before which has an attribute
98
+ # called results. Not a great name choice but sorry about that
99
+ contents = generator.generate(results.results, "GuardianSearcher::Content")
100
+ ```
101
+
102
+ Each element of the `contents` Array will be an instance of the `Content` class, with a number of attributes that depends on the returned results i.e. that
103
+ if an element of the results attribute is something like:
104
+ ```ruby
105
+ {"id"=>"football/2022/jun/27/football-transfer-rumours-chelsea-to-sign-matthijs-de-ligt-from-juventus",
106
+ "type"=>"article",
107
+ "sectionId"=>"football",
108
+ "sectionName"=>"Football",
109
+ "webPublicationDate"=>"2022-06-27T08:42:20Z",
110
+ "webTitle"=>"Football transfer rumours: Chelsea to sign Matthijs de Ligt from Juventus? ",
111
+ "webUrl"=>"https://www.theguardian.com/football/2022/jun/27/football-transfer-rumours-chelsea-to-sign-matthijs-de-ligt-from-juventus",
112
+ "apiUrl"=>"https://content.guardianapis.com/football/2022/jun/27/football-transfer-rumours-chelsea-to-sign-matthijs-de-ligt-from-juventus",
113
+ "isHosted"=>false,
114
+ "pillarId"=>"pillar/sport",
115
+ "pillarName"=>"Sport"}
116
+ ```
117
+ One element of the `contents` array will be something like:
118
+
119
+ ```ruby
120
+ <GuardianSearcher::Content:0x0000000150b7fe70
121
+ @api_url="https://content.guardianapis.com/football/2022/jun/27/football-transfer-rumours-chelsea-to-sign-matthijs-de-ligt-from-juventus",
122
+ @id="football/2022/jun/27/football-transfer-rumours-chelsea-to-sign-matthijs-de-ligt-from-juventus",
123
+ @is_hosted=false,
124
+ @pillar_id="pillar/sport",
125
+ @pillar_name="Sport",
126
+ @section_id="football",
127
+ @section_name="Football",
128
+ @type="article",
129
+ @web_publication_date="2022-06-27T08:42:20Z",
130
+ @web_title="Football transfer rumours: Chelsea to sign Matthijs de Ligt from Juventus? ",
131
+ @web_url="https://www.theguardian.com/football/2022/jun/27/football-transfer-rumours-chelsea-to-sign-matthijs-de-ligt-from-juventus">
132
+ ```
30
133
 
31
134
  ## Development
32
135
 
@@ -8,7 +8,10 @@ Gem::Specification.new do |spec|
8
8
  spec.authors = "Alain Mauri"
9
9
  spec.email = "wildeng@hotmail.com"
10
10
 
11
- spec.summary = "A wrapper to search articles from The Guardian"
11
+ spec.summary = "A wrapper to search articles from The Guardian, using its open API.
12
+ You need to register and get your api key to properly use this gem.
13
+ It uses Faraday to make the API calls and has some classes that should help in formatting
14
+ the results as easy to manage Ruby object."
12
15
  spec.homepage = "https://alainmauri.eu"
13
16
  spec.license = "MIT"
14
17
  spec.required_ruby_version = Gem::Requirement.new(">= 2.7.0")
@@ -30,10 +33,12 @@ Gem::Specification.new do |spec|
30
33
  # spec.add_dependency "example-gem", "~> 1.0"
31
34
  spec.add_dependency "faraday", "~> 2.2"
32
35
 
33
- spec.add_development_dependency 'vcr', "~> 6.1"
34
- spec.add_development_dependency 'guard', "~> 2.18"
35
- spec.add_development_dependency 'guard-bundler', "~> 3.0"
36
- spec.add_development_dependency 'guard-rspec', "~> 4.7"
36
+ spec.add_development_dependency "byebug", "~> 11"
37
+ spec.add_development_dependency "guard", "~> 2.18"
38
+ spec.add_development_dependency "guard-bundler", "~> 3.0"
39
+ spec.add_development_dependency "guard-rspec", "~> 4.7"
40
+ spec.add_development_dependency "simplecov", "~> 0.21"
41
+ spec.add_development_dependency "vcr", "~> 6.1"
37
42
  # For more information and examples about making a new gem, checkout our
38
43
  # guide at: https://bundler.io/guides/creating_gem.html
39
44
  end
@@ -1,42 +1,68 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GuardianSearcher
4
+ # Class that handles the basic functionality for the Guardian Reader gem
4
5
  class Base
5
6
  include Faraday
7
+
6
8
  attr_reader :api_key
7
- attr_accessor :base_uri
8
9
 
9
10
  def initialize(api_key: nil)
10
- @base_uri = "https://content.guardianapis.com"
11
-
12
11
  raise GuardianApyKeyError unless api_key
13
12
 
14
13
  @api_key = api_key
15
14
  end
16
15
 
17
16
  # Options needs to be passed following Guardian API docs
18
- def search(q, options = {})
19
- opt = build_options(options)
17
+ def search(query, options = {})
18
+ url = search_uri + query_string(query, options)
19
+ Faraday.get(url)
20
+ end
20
21
 
21
- url = @base_uri + "/search?q=#{q}&#{opt}&api-key=#{@api_key}"
22
+ def search_sections(query, options = {})
23
+ url = sections_uri + query_string(query, options)
22
24
  Faraday.get(url)
23
25
  end
24
26
 
25
- def search_sections(q, options = {})
26
- opt = build_options(options)
27
- url = @base_uri + "/sections?q=#{q}&#{opt}&api-key=#{@api_key}"
27
+ def search_tags(query, options = {})
28
+ url = tags_uri + query_string(query, options)
29
+ Faraday.get(url)
30
+ end
31
+
32
+ def search_editions(query, options = {})
33
+ url = editions_uri + query_string(query, options)
28
34
  Faraday.get(url)
29
35
  end
30
36
 
31
37
  private
32
38
 
33
- def build_options(options)
34
- return {} if options.empty?
39
+ def base_uri
40
+ "https://content.guardianapis.com"
41
+ end
35
42
 
36
- opt = ""
37
- options.each do |key, value|
38
- opt += "&#{key}=#{value}"
39
- end
43
+ def sections_uri
44
+ "#{base_uri}/sections"
45
+ end
46
+
47
+ def search_uri
48
+ "#{base_uri}/search"
49
+ end
50
+
51
+ def tags_uri
52
+ "#{base_uri}/tags"
53
+ end
54
+
55
+ def editions_uri
56
+ "#{base_uri}/editions"
57
+ end
58
+
59
+ def query_string(q, options = {})
60
+ opt = build_options(options)
61
+ "?q=#{q}&#{opt}&api-key=#{@api_key}"
62
+ end
63
+
64
+ def build_options(options)
65
+ Options.new(options).build_options
40
66
  end
41
67
  end
42
68
  end
@@ -1,6 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GuardianSearcher
4
- class Content < GuardianSearcher::Base
4
+ class Content
5
+ include GuardianSearcher::Helpers::Util
6
+ def initialize(attributes)
7
+ attributes.each do |key, attribute_value|
8
+ attr_name = key
9
+ attr_name = snakecase(key) unless key.is_a? Symbol
10
+ self.class.send(:define_method, "#{attr_name}=".to_sym) do |value|
11
+ instance_variable_set("@#{attr_name}", value)
12
+ end
13
+
14
+ self.class.send(:define_method, attr_name.to_sym) do
15
+ instance_variable_get("@#{attr_name}")
16
+ end
17
+
18
+ send("#{attr_name}=".to_sym, attribute_value)
19
+ end
20
+ end
5
21
  end
6
22
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GuardianSearcher
4
+ module Helpers
5
+ # The class helps generating an array of object from the passed parameters
6
+ # It can be used to generate e.g. an array of Content objects, each one
7
+ # initialised with the data of a single results Hash coming from the Guardian
8
+ # API response
9
+ class Generator
10
+ def generate(results, klass)
11
+ content = []
12
+ results.each do |result|
13
+ content << Object.const_get(klass).new(result)
14
+ end
15
+ content
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GuardianSearcher
4
+ module Helpers
5
+ module Util
6
+ # this method comes from the facets library
7
+ # I took it from there because it was easier for
8
+ # what I have in mind
9
+ #
10
+ # original here https://github.com/rubyworks/facets
11
+ # docs here https://www.rubydoc.info/github/rubyworks/facets/String:snakecase
12
+ def snakecase(key)
13
+ return unless key.is_a? String
14
+
15
+ key.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
16
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
17
+ .tr("-", "_")
18
+ .gsub(/\s/, "_")
19
+ .gsub(/__+/, "_")
20
+ .downcase
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,18 +1,42 @@
1
- module GuardianSearcher
2
- class OptionsNotHashError < StandardError; end
3
- class OptionsNotSupportedError < StandardError; end
1
+ # frozen_string_literal: true
4
2
 
3
+ module GuardianSearcher
5
4
  class Options < Hash
6
5
  private attr_accessor :options
7
6
 
8
7
  def method_missing(method_name, *args, &blk)
9
- return self.options.[](method_name, &blk) if @options.has_key?(method_name)
8
+ return options.[](method_name, &blk) if @options.key?(method_name)
9
+
10
10
  super(method_name, *args, &blk)
11
11
  end
12
-
12
+
13
13
  def initialize(options)
14
14
  raise OptionsNotHashError unless options.is_a?(Hash)
15
+
15
16
  @options = options
16
17
  end
18
+
19
+ def build_options
20
+ return {} if options.empty?
21
+
22
+ opt = ""
23
+ options.each do |key, value|
24
+ valid_option?(key)
25
+ opt += "&#{map_option(key)}=#{value}"
26
+ end
27
+ end
28
+
29
+ def valid_option?(option)
30
+ raise OptionsNotSupportedError unless %i[from_date to_date page_size page].include?(option)
31
+ end
32
+
33
+ def map_option(key)
34
+ {
35
+ from_date: "from-date",
36
+ to_date: "to-date",
37
+ page_size: "page-size",
38
+ page: "page"
39
+ }[key]
40
+ end
17
41
  end
18
42
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GuardianSearcher
4
- VERSION = "0.1.1"
4
+ VERSION = "0.1.3"
5
5
  end
@@ -3,6 +3,9 @@
3
3
  require_relative "guardian_searcher/version"
4
4
  require "faraday"
5
5
  require_relative "guardian_searcher/base"
6
+ require_relative "guardian_searcher/helpers/util"
7
+ require_relative "guardian_searcher/helpers/generator"
8
+ require_relative "guardian_searcher/content"
6
9
  require_relative "guardian_searcher/search"
7
10
  require_relative "guardian_searcher/search_result"
8
11
  require_relative "guardian_searcher/section_result"
@@ -11,4 +14,6 @@ require_relative "guardian_searcher/options"
11
14
  module GuardianSearcher
12
15
  class Error < StandardError; end
13
16
  class GuardianApyKeyError < StandardError; end
17
+ class OptionsNotHashError < StandardError; end
18
+ class OptionsNotSupportedError < StandardError; end
14
19
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: guardian_searcher
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alain Mauri
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-10-01 00:00:00.000000000 Z
11
+ date: 2022-10-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -25,19 +25,19 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '2.2'
27
27
  - !ruby/object:Gem::Dependency
28
- name: vcr
28
+ name: byebug
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '6.1'
33
+ version: '11'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '6.1'
40
+ version: '11'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: guard
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +80,34 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: '4.7'
83
+ - !ruby/object:Gem::Dependency
84
+ name: simplecov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.21'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.21'
97
+ - !ruby/object:Gem::Dependency
98
+ name: vcr
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '6.1'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '6.1'
83
111
  description:
84
112
  email: wildeng@hotmail.com
85
113
  executables: []
@@ -103,6 +131,8 @@ files:
103
131
  - lib/guardian_searcher.rb
104
132
  - lib/guardian_searcher/base.rb
105
133
  - lib/guardian_searcher/content.rb
134
+ - lib/guardian_searcher/helpers/generator.rb
135
+ - lib/guardian_searcher/helpers/util.rb
106
136
  - lib/guardian_searcher/options.rb
107
137
  - lib/guardian_searcher/search.rb
108
138
  - lib/guardian_searcher/search_result.rb
@@ -133,5 +163,8 @@ requirements: []
133
163
  rubygems_version: 3.2.15
134
164
  signing_key:
135
165
  specification_version: 4
136
- summary: A wrapper to search articles from The Guardian
166
+ summary: A wrapper to search articles from The Guardian, using its open API. You need
167
+ to register and get your api key to properly use this gem. It uses Faraday to make
168
+ the API calls and has some classes that should help in formatting the results as
169
+ easy to manage Ruby object.
137
170
  test_files: []