glibrary 0.0.3 → 0.0.4

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: 1ec7bddfc20d32bc1e5a85de36802b1a117697a97861ff4044eb7a3c4e895da8
4
- data.tar.gz: b7f08c7313f7e954825de34a0659c4eeb61402fe072a5407d8b5f17dce93b689
3
+ metadata.gz: 0b442b7e44fa855ceb010b7b91b1b986ac63d66e759413080b2130553e231586
4
+ data.tar.gz: b3125e46eba448ea424c851e3e190133bdaf4a747ea6f11ed831a6815a2e31bf
5
5
  SHA512:
6
- metadata.gz: d86c45871f3607ea227baa5a6466b2d8a3218afc5856b6c589d9399f1b88a3dfcef0888ba465e184c9a139c8095046e0a5bfc621d79601a33dedf77fcee35e73
7
- data.tar.gz: b865e923ba76499bc2f17b559021c5647e3c5557f70d63f2b051435a601502be553649648f5f7f1eba0d8a495d9456d80ba2cdd9cd166f0c453eb9f72ca5df87
6
+ metadata.gz: 6e7c492065ea7d32f4367d1e4ebf7f49220ca165b7081104bc9b3debe06f96b7cdabd241166d68cf5600f2b42eb4546197d5211aea1a81db869c3331bf11f261
7
+ data.tar.gz: f21cc6a1ace2374c2e5a4e558647aacfc8f668509c2f256d09ea07e5e8f54345822047cf36d9c7435ccc3c61356b6789e951a5261c6abc1ae4c6ffc50d8e2cd4
data/bin/glibrary CHANGED
@@ -1,119 +1,55 @@
1
1
  #!/usr/bin/env ruby
2
2
  require 'json'
3
- require 'optparse'
4
3
  require 'io/console'
5
4
 
6
- require_relative '../common/api_query'
7
- require_relative '../common/google_api_url'
8
- require_relative '../classes/book_search'
9
- require_relative '../classes/user_book'
10
- require_relative '../classes/user_library'
11
- require_relative '../common/errors'
5
+ require_relative '../modules/api_query'
6
+ require_relative '../modules/command_line_parse'
7
+ require_relative '../modules/errors'
8
+ require_relative '../modules/exec_helper'
9
+ require_relative '../modules/google_api_url'
10
+ require_relative '../modules/user_prompt'
12
11
 
13
- ARGV << '-h' if ARGV.empty?
12
+ require_relative '../lib/book_search'
13
+ require_relative '../lib/user_book'
14
+ require_relative '../lib/user_library'
14
15
 
15
- #make sure the -l flag is process *after* the -f flag, by putting it at the end
16
- ARGV << ARGV.delete("-l") if ARGV.include?("-l")
16
+ include ExecHelper
17
+ include UserPrompt
18
+ include CommandLineParse
17
19
 
18
- #default filename for saved reading list
19
- filename = File.join(File.dirname(__FILE__), "../saved_libraries/library.json")
20
+ CURRENT_DIR = File.join(File.dirname(__FILE__))
21
+ DEFAULT_FILENAME = "../saved_libraries/library.json"
20
22
 
21
- #initialize the hash which will hold the search parameters
22
- o = {}
23
+ ExecHelper::process_args!
23
24
 
24
- #parse the command-line options
25
- OptionParser.new do |opts|
26
- opts.banner = "Usage: ruby g_library.rb [options...] [query]"
27
- #sets 'library-mode'
28
- library = opts.default_argv.include?("-l")
25
+ options = {}
26
+ filename = File.join(CURRENT_DIR, DEFAULT_FILENAME)
27
+ filename, options = CommandLineParse::command_line_parse!(filename, options)
29
28
 
30
- opts.on("-t", "--title=TITLE", "Specify a title keyword") do |t|
31
- o[:title] = t
32
- end
29
+ ExecHelper::prepare_filename_for_os!(filename)
33
30
 
34
- opts.on("-a", "--author=AUTHOR", "Specify an author keyword") do |a|
35
- o[:author] = a
36
- end
31
+ persistent_library = UserLibrary.new(filename: filename)
37
32
 
38
- opts.on("-p", "--publisher=PUBLISHER", "Specify a publisher keyword") do |p|
39
- o[:publisher] = p
40
- end
33
+ temp_booklist = UserLibrary.new(nonpersistent: true)
41
34
 
42
- opts.on("-f", "--lib-file=LIBFILE", "Select a library save file. Otherwise, a default save file will be used.") do |libfile|
43
- filename = File.absolute_path(libfile)
44
- unless File.exist?(filename)
45
- (puts "Library file not found, cannot display."; exit) if library
46
- puts "Creating new file"
47
- end
48
- end
49
-
50
- opts.on("-l", "--library", "See your library; ignores all search options") do
51
-
52
- #substitute \ for / if the machine is running windows
53
- filename.gsub!(/\//, '\\') if ENV.values.any? { |v| v =~ /[A-Z]:\\Windows/i }
54
-
55
-
56
- #print out the library and exit the program
57
- l = UserLibrary.new(filename)
58
- l.pretty_print
59
- exit
60
- end
61
-
62
- opts.on("-h", "--help", "Prints this help") do
63
- puts ""
64
- puts opts
65
- puts "[query]: all other arguments will be treated as general search keywords"
66
- puts ""
67
- exit
68
- end
69
-
70
- end.parse!
71
-
72
- #substitute \ for / if the machine is running windows
73
- filename.gsub!(/\//, '\\') if ENV.values.any? { |v| v =~ /[A-Z]:\\Windows/i }
74
-
75
- # Create or load the library, depending on whether or not the file already exists.
76
- l = UserLibrary.new(filename)
77
-
78
- #initialize the
79
- #perform the search, and display the results.
80
35
  begin
81
- s = BookSearch.new(search: ARGV.join('+'), title: o[:title], author: o[:author], publisher: o[:publisher])
82
-
83
- #see BookSearch definition in classes/book_search.rb
84
- #the each method (and all Enumerable methods, like map) only operate on
85
- #the first five matches.
86
- books = s.map { |info| UserBook.new(info) }
36
+ search_results = ExecHelper::perform_search(options)
37
+ search_results.selected_results.each { |info| temp_booklist.add(UserBook.new(info)) }
87
38
 
88
- #print the resuls
89
- puts ""
90
- books.each_with_index do |b, i|
91
- keys = %w[title author publisher]
92
- n = i+1
93
- puts "-"*5 + "Match ##{n}" + "-"*5
94
- keys.each do |k|
95
- puts "%s: %s" % [k.capitalize, b[k]]
96
- end
97
- puts ""
98
- end
39
+ temp_booklist.pretty_print
99
40
 
100
- print "Enter a number (1-5) to add a book to your reading list:\n(or any other key to quit)\n"
41
+ UserPrompt::search_user_prompt(temp_booklist, persistent_library)
101
42
 
102
- selection = STDIN.getch.chomp.to_i
103
- i = selection - 1
43
+ rescue UserQuits
44
+ puts "\nThanks for browsing. Nothing has been added to the reading list."
45
+ rescue NoInternetError
46
+ puts "\nNo internet connection. Please connect to the internet and try again."
104
47
  puts ""
105
- exit if i < 0 or i > 4
106
-
107
- #add a book to the user's library
108
- l << books[i]
109
- l.save
110
-
111
48
  rescue SearchError
112
49
  puts "\nThere was an error with your query; be careful to format it well"
113
50
  puts ""
114
- exit
115
51
  rescue NoResults
116
52
  puts "\nNo results."
117
53
  puts ""
118
- exit
119
54
  end
55
+ exit
@@ -0,0 +1,90 @@
1
+ require 'json'
2
+ require_relative '../modules/api_query'
3
+ require_relative '../modules/google_api_url'
4
+ require_relative '../modules/errors'
5
+
6
+ class BookSearch
7
+ include ApiQuery
8
+ include Enumerable
9
+ include Errors
10
+
11
+ attr_reader :selected_results, :full_results, :url
12
+
13
+ def initialize(args)
14
+ @args = args.dup
15
+
16
+ num_results = @args.delete(:num) || 5
17
+
18
+ raise ArgumentError unless self.class.searchable_arguments?(@args)
19
+
20
+ @args = self.class.format_args(@args)
21
+ @url = self.class.make_url(@args)
22
+
23
+ raise NoInternetError unless connected_to_internet?
24
+
25
+ response = get_response(@url)
26
+ raise SearchError unless correct_response_code?(response.code)
27
+
28
+ @full_results = JSON.parse(response.body)
29
+
30
+ num = full_results['totalItems']
31
+ raise NoResults unless num > 0
32
+
33
+ @selected_results = full_results['items'].first(num_results)
34
+ @selected_results.map! { |res| self.class.format_hash res }
35
+
36
+ end
37
+
38
+ def each
39
+ return to_enum :each unless block_given?
40
+ selected_results.each { |r| yield(r) }
41
+ end
42
+
43
+ def [](index)
44
+ selected_results[index]
45
+ end
46
+
47
+ private
48
+ class << self
49
+ def make_url_arg_list(args)
50
+ url = ["?q="]
51
+
52
+ q = args.delete('search')
53
+ url << q.to_s
54
+
55
+ url << args.map do |k,v|
56
+ k = ("in" + k) if %w[title author publisher].include?(k)
57
+ "%s:%s" % [k, v]
58
+ end
59
+
60
+ url.reject!(&:empty?)
61
+ url[0] + url[1, url.length-1].join("+")
62
+ end
63
+
64
+ def format_args(args)
65
+ result = args.map { |k, v| [k,v].map(&:to_s) }.to_h
66
+ result.reject! { |k, v| v.empty? }
67
+ return result.to_h
68
+ end
69
+
70
+ def searchable_arguments?(args)
71
+ args.keys.any? do |k|
72
+ %i[search title author publisher subject isbn lccn oclc].include?(k)
73
+ end
74
+ end
75
+
76
+
77
+ def format_hash(hash)
78
+ hash['volumeInfo'].merge( { 'id' => hash['id'] } )
79
+ end
80
+
81
+ def make_url(args)
82
+ (BASE_API_URL + make_url_arg_list(args)).gsub(/ /, "+")
83
+ end
84
+
85
+ def args
86
+ @args
87
+ end
88
+ end
89
+
90
+ end
@@ -1,30 +1,30 @@
1
1
  require 'json'
2
-
3
- class Hash
4
- def info
5
- self
6
- end
7
- end
2
+ require_relative '../modules/errors'
8
3
 
9
4
  class UserBook
5
+ include Errors
10
6
 
11
7
  attr_accessor :title, :publisher, :info, :id
12
8
 
13
9
  def initialize(book_data = {})
14
10
  raise ArgumentError unless book_data.is_a?(Hash)
15
11
 
16
- #The main attributes of the book:
17
12
  @info = book_data
18
13
 
19
- #The most commonly attributes, for ease of use
20
14
  @id = info['id'] || ""
21
15
  @title = info['title'] || ""
22
16
  @authors = info['authors'] || []
23
17
  @publisher = info['publisher'] || ""
24
18
  end
25
19
 
26
- #get any attribute, like with a hash
27
- #ex self['title']
20
+ def pretty_export
21
+ str = []
22
+ %w[title author publisher].each do |ind|
23
+ str << "%s: %s" % [ind.capitalize, self[ind]]
24
+ end
25
+ return str.join("\n")
26
+ end
27
+
28
28
  def [](key)
29
29
  return authors if key =~ /authors?/i
30
30
  info[key]
@@ -34,7 +34,6 @@ class UserBook
34
34
  @authors.join(', ')
35
35
  end
36
36
 
37
- #Be a little bit forgiving if the user types "authors"
38
37
  def author
39
38
  authors
40
39
  end
@@ -0,0 +1,123 @@
1
+ require 'json'
2
+ require_relative '../modules/errors'
3
+ require_relative '../modules/exec_helper'
4
+ require_relative 'user_book'
5
+
6
+
7
+ class UserLibrary
8
+ include Enumerable
9
+ include ExecHelper
10
+ include Errors
11
+
12
+ attr_reader :books, :filename
13
+
14
+ def initialize(args = {})
15
+
16
+ @books = []
17
+ @nonpersistent = args[:nonpersistent]
18
+
19
+ unless nonpersistent?
20
+ @filename = args[:filename] ||
21
+ File.join(File.dirname(__FILE__), "../saved_libraries/library.json")
22
+
23
+ determine_filename!
24
+ load_saved_library!
25
+ end
26
+
27
+ end
28
+
29
+ def add(book)
30
+ raise NotABook unless valid?(book)
31
+ raise BookDuplicateError if any? { |b| b['id'] == book['id'] }
32
+ @books << book
33
+ end
34
+
35
+ def delete(index)
36
+ @books.delete_at(index)
37
+ end
38
+
39
+ def nonpersistent?
40
+ @nonpersistent
41
+ end
42
+
43
+ def <<(book)
44
+ add(book)
45
+ end
46
+
47
+ def save
48
+ raise PersistenceError if nonpersistent?
49
+ File.write(@filename, to_json)
50
+ end
51
+
52
+ def size
53
+ @books.size
54
+ end
55
+
56
+ def length
57
+ size
58
+ end
59
+
60
+ def each
61
+ return to_enum :each unless block_given?
62
+ @books.each { |b| yield(b) }
63
+ end
64
+
65
+ def [](index)
66
+ @books[index]
67
+ end
68
+
69
+ def to_json
70
+ a = @books.map do |b|
71
+ b.info
72
+ end
73
+ JSON.pretty_generate(a)
74
+ end
75
+
76
+ def pretty_print
77
+ puts pretty_export
78
+ end
79
+
80
+ def pretty_export
81
+ a = [""]
82
+ each_with_index do |b, i|
83
+ j = i+1
84
+ a << "%s%s%s" % ["#{j}.)--", b['id'], "-----"]
85
+ a << b.pretty_export
86
+ a << ""
87
+ end
88
+ a << ""
89
+ a.join("\n")
90
+ end
91
+
92
+ private
93
+ def valid?(b)
94
+ b.respond_to?(:info)
95
+ end
96
+
97
+ def set_filename(name)
98
+ @nonpersistent = false
99
+ @filename = name
100
+ prepare_filename_for_os!(@filename)
101
+ end
102
+
103
+ def determine_filename!
104
+ @filename = File.absolute_path(@filename)
105
+ prepare_filename_for_os!(@filename)
106
+ end
107
+
108
+ def get_raw_JSON_data
109
+ file = File.open(@filename)
110
+ JSON.parse(file.read)
111
+ end
112
+
113
+ def load_saved_library!
114
+ if File.exist?(@filename)
115
+ book_data_set = get_raw_JSON_data
116
+ book_data_set.each { |info| add(UserBook.new(info)) }
117
+ raise ArgumentError unless books.all? { |b| valid?(b) }
118
+ end
119
+ end
120
+
121
+
122
+ end
123
+
@@ -0,0 +1,25 @@
1
+ module ApiQuery
2
+ require 'net/http'
3
+ require 'json'
4
+ require "resolv"
5
+
6
+ private
7
+ def get_response(url)
8
+ Net::HTTP.get_response(URI.parse(url))
9
+ end
10
+
11
+ def correct_response_code?(response_code)
12
+ return response_code == "200"
13
+ end
14
+
15
+ def connected_to_internet?(address = "google.com")
16
+ dns_resolver = Resolv::DNS.new()
17
+ begin
18
+ dns_resolver.getaddress(address)
19
+ return true
20
+ rescue Resolv::ResolvError
21
+ return false
22
+ end
23
+ end
24
+ end
25
+
@@ -0,0 +1,66 @@
1
+ require_relative 'exec_helper'
2
+ require_relative 'user_prompt'
3
+ require_relative 'errors'
4
+ require_relative '../lib/user_library'
5
+
6
+ module CommandLineParse
7
+ require 'optparse'
8
+ include ExecHelper
9
+ include UserPrompt
10
+ include Errors
11
+
12
+ def handle_nonexistent_file(filename, library)
13
+ (puts "Library file not found, cannot display."; exit) if library
14
+ puts "Creating new file"
15
+ end
16
+
17
+ def print_help_message(opts)
18
+ puts ""
19
+ puts opts
20
+ puts "[query]: all other arguments will be treated as general search keywords"
21
+ puts ""
22
+ end
23
+
24
+
25
+ def command_line_parse!(filename, o, suppress = nil)
26
+
27
+ OptionParser.new do |opts|
28
+ opts.banner = "Usage: glibrary [options...] [query]"
29
+ @library = opts.default_argv.include?("-l")
30
+
31
+ opts.on("-t", "--title=TITLE", "Specify a title keyword") do |t|
32
+ o[:title] = t
33
+ end
34
+
35
+ opts.on("-a", "--author=AUTHOR", "Specify an author keyword") do |a|
36
+ o[:author] = a
37
+ end
38
+
39
+ opts.on("-p", "--publisher=PUBLISHER", "Specify a publisher keyword") do |p|
40
+ o[:publisher] = p
41
+ end
42
+
43
+ opts.on("-f", "--lib-file=LIBFILE",
44
+ "Select a library save file. Otherwise, a default save file will be used.") do |libfile|
45
+ @filename = File.absolute_path(libfile)
46
+ handle_nonexistent_file(@filename, @library) unless File.exist?(@filename)
47
+ end
48
+
49
+ opts.on("-l", "--library", "See your library; ignores all search options") do
50
+ prepare_filename_for_os!(@filename)
51
+ unless suppress
52
+ (l = UserLibrary.new(filename: @filename)).pretty_print
53
+ library_mode_user_prompt(l)
54
+ exit
55
+ end
56
+ end
57
+
58
+ opts.on("-h", "--help", "Prints this help") do
59
+ print_help_message(opts)
60
+ exit
61
+ end
62
+
63
+ end.parse!
64
+ return [@filename, o]
65
+ end
66
+ end
data/modules/errors.rb ADDED
@@ -0,0 +1,25 @@
1
+ module Errors
2
+ class SearchError < StandardError
3
+ end
4
+
5
+ class BookDuplicateError < StandardError
6
+ end
7
+
8
+ class NoInternetError < StandardError
9
+ end
10
+
11
+ class NoResults < StandardError
12
+ end
13
+
14
+ class SelectionError < StandardError
15
+ end
16
+
17
+ class NotABook < StandardError
18
+ end
19
+
20
+ class UserQuits < StandardError
21
+ end
22
+
23
+ class PersistenceError < StandardError
24
+ end
25
+ end
@@ -0,0 +1,20 @@
1
+ require_relative 'errors'
2
+
3
+ module ExecHelper
4
+ include Errors
5
+
6
+ def process_args!(args = ARGV)
7
+ args << '-h' if args.empty?
8
+ args << args.delete("-l") if args.include?("-l")
9
+ end
10
+
11
+ def prepare_filename_for_os!(filename)
12
+ filename.gsub!(/\//, '\\') if ENV.values.any? { |v| v =~ /[A-Z]:\\Windows/i }
13
+ end
14
+
15
+ def perform_search(o)
16
+ search = o[:search] || ARGV.join('+')
17
+ s = BookSearch.new(search: search, title: o[:title], author: o[:author],
18
+ publisher: o[:publisher])
19
+ end
20
+ end
File without changes
@@ -0,0 +1,79 @@
1
+ require_relative 'errors'
2
+
3
+ module UserPrompt
4
+ include Errors
5
+
6
+ def verify_selection(selection, regexp)
7
+ raise SelectionError unless selection =~ regexp
8
+ raise UserQuits if selection =~ /\A[Qq]\Z/
9
+ end
10
+
11
+ def process_selection(selection)
12
+ return selection.to_i - 1
13
+ end
14
+
15
+ def handle_successful_prompt_completion(selected_book)
16
+ puts ""
17
+ puts "The book \"#{selected_book['title']}\" has been added to your reading list."
18
+ end
19
+
20
+ def prompt(prompt, regexp, library, suppress = nil)
21
+ print prompt
22
+ s = suppress || STDIN.gets.strip
23
+ verify_selection(s, regexp)
24
+ s = process_selection(s)
25
+ raise NotABook unless s <= library.size
26
+ return s
27
+ end
28
+
29
+ def library_mode_user_prompt(library)
30
+ while true
31
+ begin
32
+ i = prompt( "Select a book number to delete, or type \"q\" to quit: ", /\A([0-9]+|[qQ])\Z/,
33
+ library )
34
+
35
+ library.delete(i)
36
+ library.pretty_print
37
+
38
+ puts "Your library now looks like this."
39
+ puts ""
40
+ rescue SelectionError
41
+ puts "\nSorry, your selection was invalid. Please try again."
42
+ rescue NotABook
43
+ puts "\nSorry, your selection was invalid. Make sure your selection is in the provided range."
44
+ rescue UserQuits
45
+ library.save
46
+ puts "\nEnjoy your day."
47
+ exit
48
+ end
49
+ end
50
+ end
51
+
52
+
53
+ def search_user_prompt(temp_booklist, persistent_library)
54
+ while true
55
+ begin
56
+
57
+ i = prompt( "Enter a number (1-5) to add a book to your reading list (or q to quit): ",
58
+ /\A[1-5]|[qQ]\Z/, temp_booklist )
59
+ selected_book = temp_booklist[i]
60
+
61
+ persistent_library << selected_book
62
+ persistent_library.save
63
+
64
+ handle_successful_prompt_completion(selected_book)
65
+ puts "Would you like to add another?"
66
+ puts ""
67
+ rescue BookDuplicateError
68
+ puts "\nThat book is already in your reading list. Would you like to add another?"
69
+ puts ""
70
+ rescue SelectionError
71
+ puts "\nSorry, your selection was invalid. Please try again."
72
+ puts ""
73
+ rescue NotABook
74
+ puts "\nSorry, your selection was invalid. Make sure your selection is within the range of available options."
75
+ puts ""
76
+ end
77
+ end
78
+ end
79
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: glibrary
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter Engelbert
@@ -10,7 +10,8 @@ bindir: bin
10
10
  cert_chain: []
11
11
  date: 2019-10-26 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: A simple Google Books API Query program
13
+ description: A simple command line program that queries the Google Books API Query
14
+ and lets you save a list of books you plan to read.
14
15
  email: pmengelbert@gmail.com
15
16
  executables:
16
17
  - glibrary
@@ -18,13 +19,15 @@ extensions: []
18
19
  extra_rdoc_files: []
19
20
  files:
20
21
  - bin/glibrary
21
- - classes/book_search.rb
22
- - classes/user_book.rb
23
- - classes/user_library.rb
24
- - common/api_query.rb
25
- - common/errors.rb
26
- - common/google_api_url.rb
27
- - common/search_error.rb
22
+ - lib/book_search.rb
23
+ - lib/user_book.rb
24
+ - lib/user_library.rb
25
+ - modules/api_query.rb
26
+ - modules/command_line_parse.rb
27
+ - modules/errors.rb
28
+ - modules/exec_helper.rb
29
+ - modules/google_api_url.rb
30
+ - modules/user_prompt.rb
28
31
  - saved_libraries/.gitkeep
29
32
  homepage: https://github.com/pmengelbert/g_library
30
33
  licenses:
@@ -33,9 +36,9 @@ metadata: {}
33
36
  post_install_message:
34
37
  rdoc_options: []
35
38
  require_paths:
36
- - common
39
+ - modules
37
40
  - saved_libraries
38
- - classes
41
+ - lib
39
42
  required_ruby_version: !ruby/object:Gem::Requirement
40
43
  requirements:
41
44
  - - ">="
@@ -1,101 +0,0 @@
1
- require 'json'
2
- require_relative '../common/api_query'
3
- require_relative '../common/google_api_url'
4
- require_relative '../common/errors'
5
-
6
- class BookSearch
7
- include ApiQuery
8
- include Enumerable
9
-
10
- attr_reader :selected_results, :full_results, :url
11
-
12
- def initialize(args)
13
- @args = args.dup
14
-
15
- #the number of results to show:
16
- num_results = @args.delete(:num) || 5
17
-
18
- #make sure at least one argument is searchable
19
- raise ArgumentError unless @args.keys.any? do |k|
20
- %i[search title author publisher subject isbn lccn oclc].include?(k)
21
- end
22
-
23
- #map the keys and values to strings for ease of manipulation
24
- @args = @args.map { |k, v| [k,v].map(&:to_s) }.to_h
25
- #remove any empty values (nil values were converted to
26
- #empty strings by the last operation)
27
- @args = @args.reject { |k, v| v.empty? }.to_h
28
-
29
- #get a nicely formatted url; very important
30
- @url = make_url
31
-
32
- #check for successful response from the API
33
- response = get_response(url)
34
- raise SearchError unless response.code == "200"
35
-
36
- #Puts the results into a hash
37
- @full_results = JSON.parse(response.body)
38
-
39
- num = full_results['totalItems']
40
- raise NoResults unless num > 0
41
-
42
- #the selected results:
43
- @selected_results = full_results['items'].first(num_results)
44
- #The following mapping gets the 'volumeInfo' property from the results,
45
- #and adds the 'id' property.
46
- @selected_results = @selected_results.map { |res| format_hash res }
47
-
48
- end
49
-
50
- #Only five results are iterated over; This behavior is easily changed by either
51
- #passing a :num value when initializing a BookSearch, or by using full_results
52
- #below, instead of selected_results
53
- def each
54
- return to_enum :each unless block_given?
55
- selected_results.each { |r| yield(r) }
56
- end
57
-
58
- #Likewise, only five results are accessible here; see the previous note.
59
- def [](index)
60
- selected_results[index]
61
- end
62
-
63
- def to_json
64
- JSON.pretty_generate(selected_results)
65
- end
66
-
67
- private
68
-
69
- def make_url_arg_list
70
- url = ["?q="]
71
-
72
- q = @args.delete('search')
73
- url << q.to_s
74
-
75
- url << @args.map do |k,v|
76
- k = ("in" + k) if %w[title author publisher].include?(k)
77
- "%s:%s" % [k, v]
78
- end
79
-
80
- url.reject!(&:empty?)
81
- url[0] + url[1, url.length-1].join("+")
82
- end
83
-
84
-
85
- #gets the 'volumeInfo' property from the results, and adds the 'id' property.
86
- def format_hash(hash)
87
- hash['volumeInfo'].merge( { 'id' => hash['id'] } )
88
- end
89
-
90
- #Add the base url and the formatted arg list together, and make sure
91
- #there are no spaces
92
- def make_url
93
- (BASE_API_URL + make_url_arg_list).gsub(/ /, "+")
94
- end
95
-
96
- #for testing purposes only
97
- def args
98
- @args
99
- end
100
-
101
- end
@@ -1,95 +0,0 @@
1
- require 'json'
2
- require_relative '../common/errors'
3
- require_relative 'user_book'
4
-
5
- class UserLibrary
6
- include Enumerable
7
-
8
- attr_reader :books, :filename
9
-
10
- def initialize(sourcefile = nil)
11
-
12
- @books = []
13
-
14
- if sourcefile
15
- @filename = File.absolute_path(sourcefile)
16
-
17
- #If the file already exists, it will be read and the
18
- #@books array will be filled
19
- if File.exist?(filename)
20
- file = File.open(filename)
21
- book_array = JSON.parse(file.read)
22
-
23
- #If the UserBook initialize method changes, so must this; This
24
- #is the only place where there's an explicit dependency on UserBook.
25
- #If this line is changed, the rest of the class may still work as long
26
- #as the elements in @books respond to the ':info' message
27
- @books = book_array.map { |d| UserBook.new(d) }
28
- end
29
-
30
- else
31
- #the default filename
32
- @filename = File.absolute_path("saved_libraries/library.json")
33
- end
34
-
35
- #If the wrong type of data is provided
36
- raise ArgumentError "Invalid Data" unless books.all? { |b| valid?(b) }
37
- end
38
-
39
- def add(book)
40
- (raise DataError "Invalid Data"; return) unless valid?(book)
41
- @books << book
42
- end
43
-
44
- def <<(book)
45
- add(book)
46
- end
47
-
48
- def save
49
- File.write(filename, to_json)
50
- end
51
-
52
- #Iterates over ALL books
53
- def each
54
- return to_enum :each unless block_given?
55
- @books.each { |b| yield(b) }
56
- end
57
-
58
- def [](index)
59
- @books[index]
60
- end
61
-
62
- #For saving to files
63
- def to_json
64
- a = @books.map do |b|
65
- b.info
66
- end
67
- JSON.pretty_generate(a)
68
- end
69
-
70
- #For printing to the console
71
- def pretty_print
72
- puts ""
73
- each do |b|
74
- puts "-"*5 + b['id'] + "-" * 5
75
- %w[title author publisher].each do |ind|
76
- puts "%s: %s" % [ind.capitalize, b[ind]]
77
- end
78
- puts ""
79
- end
80
- puts ""
81
- end
82
-
83
- private
84
- #make sure whatever gets passed in responds to the ':info' message
85
- def valid?(b)
86
- b.respond_to?(:info)
87
- end
88
-
89
- #for testing purposes
90
- def set_filename(name)
91
- @filename = name
92
- end
93
-
94
- end
95
-
data/common/api_query.rb DELETED
@@ -1,22 +0,0 @@
1
- module ApiQuery
2
- require 'net/http'
3
- require 'json'
4
-
5
- private
6
- def get_response(url)
7
- Net::HTTP.get_response(URI.parse(url))
8
- end
9
-
10
- def get_response_hash(url)
11
- JSON.parse(get_response_body(url))
12
- end
13
-
14
- def get_response_code(url)
15
- get_response(url).code
16
- end
17
-
18
- def get_response_body(url)
19
- get_response(url).body
20
- end
21
- end
22
-
data/common/errors.rb DELETED
@@ -1,8 +0,0 @@
1
- class SearchError < StandardError
2
- end
3
-
4
- class NoResults < StandardError
5
- end
6
-
7
- class DataError < StandardError
8
- end
@@ -1,2 +0,0 @@
1
- class SearchError < StandardError
2
- end