guardian_searcher 0.1.1 → 0.1.3

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 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: []