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 +4 -4
- data/bin/glibrary +30 -94
- data/lib/book_search.rb +90 -0
- data/{classes → lib}/user_book.rb +10 -11
- data/lib/user_library.rb +123 -0
- data/modules/api_query.rb +25 -0
- data/modules/command_line_parse.rb +66 -0
- data/modules/errors.rb +25 -0
- data/modules/exec_helper.rb +20 -0
- data/{common → modules}/google_api_url.rb +0 -0
- data/modules/user_prompt.rb +79 -0
- metadata +14 -11
- data/classes/book_search.rb +0 -101
- data/classes/user_library.rb +0 -95
- data/common/api_query.rb +0 -22
- data/common/errors.rb +0 -8
- data/common/search_error.rb +0 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0b442b7e44fa855ceb010b7b91b1b986ac63d66e759413080b2130553e231586
|
4
|
+
data.tar.gz: b3125e46eba448ea424c851e3e190133bdaf4a747ea6f11ed831a6815a2e31bf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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 '../
|
7
|
-
require_relative '../
|
8
|
-
require_relative '../
|
9
|
-
require_relative '../
|
10
|
-
require_relative '../
|
11
|
-
require_relative '../
|
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
|
-
|
12
|
+
require_relative '../lib/book_search'
|
13
|
+
require_relative '../lib/user_book'
|
14
|
+
require_relative '../lib/user_library'
|
14
15
|
|
15
|
-
|
16
|
-
|
16
|
+
include ExecHelper
|
17
|
+
include UserPrompt
|
18
|
+
include CommandLineParse
|
17
19
|
|
18
|
-
|
19
|
-
|
20
|
+
CURRENT_DIR = File.join(File.dirname(__FILE__))
|
21
|
+
DEFAULT_FILENAME = "../saved_libraries/library.json"
|
20
22
|
|
21
|
-
|
22
|
-
o = {}
|
23
|
+
ExecHelper::process_args!
|
23
24
|
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
31
|
-
o[:title] = t
|
32
|
-
end
|
29
|
+
ExecHelper::prepare_filename_for_os!(filename)
|
33
30
|
|
34
|
-
|
35
|
-
o[:author] = a
|
36
|
-
end
|
31
|
+
persistent_library = UserLibrary.new(filename: filename)
|
37
32
|
|
38
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
41
|
+
UserPrompt::search_user_prompt(temp_booklist, persistent_library)
|
101
42
|
|
102
|
-
|
103
|
-
|
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
|
data/lib/book_search.rb
ADDED
@@ -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
|
-
|
27
|
-
|
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
|
data/lib/user_library.rb
ADDED
@@ -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.
|
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
|
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
|
-
-
|
22
|
-
-
|
23
|
-
-
|
24
|
-
-
|
25
|
-
-
|
26
|
-
-
|
27
|
-
-
|
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
|
-
-
|
39
|
+
- modules
|
37
40
|
- saved_libraries
|
38
|
-
-
|
41
|
+
- lib
|
39
42
|
required_ruby_version: !ruby/object:Gem::Requirement
|
40
43
|
requirements:
|
41
44
|
- - ">="
|
data/classes/book_search.rb
DELETED
@@ -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
|
data/classes/user_library.rb
DELETED
@@ -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
data/common/search_error.rb
DELETED