glibrary 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3ebb0b283f8a02d344327ef0ddb6bb19bfb5e180aecb45403a21a08e182fc42b
4
+ data.tar.gz: a388772cb5f7b81ff05d08efd9ebbdde68114040076054a76268ad6ea879ea72
5
+ SHA512:
6
+ metadata.gz: d305157edaf5fe228fc2c7134359d15057b8086404899619400177c06900dc0edd389ef1b3fc11c48dfb459f1c99f86079cb25a5ef718a8633cfb0d26738e566
7
+ data.tar.gz: 9bd4b1599e77b79ce5f4563776ad15ff1f0a01f93058ec4d20907de61aaf40de11d1c4b1b5c1fb98c125c81902fc72cef44ee32bd143441936b80c0d75a505eb
data/bin/glibrary ADDED
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env ruby
2
+ require 'json'
3
+ require 'optparse'
4
+ require 'io/console'
5
+
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'
12
+
13
+ ARGV << '-h' if ARGV.empty?
14
+
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")
17
+
18
+ #default filename for saved reading list
19
+ filename = File.dirname(File.expand_path(__FILE__)) + "/saved_libraries/library.json"
20
+
21
+ #initialize the hash which will hold the search parameters
22
+ o = {}
23
+
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")
29
+
30
+ opts.on("-t", "--title=TITLE", "Specify a title keyword") do |t|
31
+ o[:title] = t
32
+ end
33
+
34
+ opts.on("-a", "--author=AUTHOR", "Specify an author keyword") do |a|
35
+ o[:author] = a
36
+ end
37
+
38
+ opts.on("-p", "--publisher=PUBLISHER", "Specify a publisher keyword") do |p|
39
+ o[:publisher] = p
40
+ end
41
+
42
+ opts.on("-f", "--lib-file=LIBFILE", "Select a library save file") 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
51
+ \t\t\t\t\tdefault library file is [repository_root]/saved_libraries/library.json") do
52
+
53
+ #substitute \ for / if the machine is running windows
54
+ filename.gsub!(/\//, '\\') if ENV.values.any? { |v| v =~ /[A-Z]:\\Windows/i }
55
+
56
+
57
+ #print out the library and exit the program
58
+ l = UserLibrary.new(filename)
59
+ l.pretty_print
60
+ exit
61
+ end
62
+
63
+ opts.on("-h", "--help", "Prints this help") do
64
+ puts ""
65
+ puts opts
66
+ puts "[query]: all other arguments will be treated as general search keywords"
67
+ puts ""
68
+ exit
69
+ end
70
+
71
+ end.parse!
72
+
73
+ #substitute \ for / if the machine is running windows
74
+ filename.gsub!(/\//, '\\') if ENV.values.any? { |v| v =~ /C:\\Windows/i }
75
+
76
+ # Create or load the library, depending on whether or not the file already exists.
77
+ l = UserLibrary.new(filename)
78
+
79
+ #initialize the
80
+ #perform the search, and display the results.
81
+ begin
82
+ s = BookSearch.new(search: ARGV.join('+'), title: o[:title], author: o[:author], publisher: o[:publisher])
83
+
84
+ #see BookSearch definition in classes/book_search.rb
85
+ #the each method (and all Enumerable methods, like map) only operate on
86
+ #the first five matches.
87
+ books = s.map { |info| UserBook.new(info) }
88
+
89
+ #print the resuls
90
+ puts ""
91
+ books.each_with_index do |b, i|
92
+ keys = %w[title author publisher]
93
+ n = i+1
94
+ puts "-"*5 + "Match ##{n}" + "-"*5
95
+ keys.each do |k|
96
+ puts "%s: %s" % [k.capitalize, b[k]]
97
+ end
98
+ puts ""
99
+ end
100
+
101
+ print "Enter a number (1-5) to add a book to your reading list:\n(or any other key to quit)\n"
102
+
103
+ selection = STDIN.getch.chomp.to_i
104
+ i = selection - 1
105
+ puts ""
106
+ exit if i < 0 or i > 4
107
+
108
+ #add a book to the user's library
109
+ l << books[i]
110
+ l.save
111
+
112
+ rescue SearchError
113
+ puts "\nThere was an error with your query; be careful to format it well"
114
+ puts ""
115
+ exit
116
+ rescue NoResults
117
+ puts "\nNo results."
118
+ puts ""
119
+ exit
120
+ end
@@ -0,0 +1,101 @@
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
@@ -0,0 +1,42 @@
1
+ require 'json'
2
+
3
+ class Hash
4
+ def info
5
+ self
6
+ end
7
+ end
8
+
9
+ class UserBook
10
+
11
+ attr_accessor :title, :publisher, :info, :id
12
+
13
+ def initialize(book_data = {})
14
+ raise ArgumentError unless book_data.is_a?(Hash)
15
+
16
+ #The main attributes of the book:
17
+ @info = book_data
18
+
19
+ #The most commonly attributes, for ease of use
20
+ @id = info['id'] || ""
21
+ @title = info['title'] || ""
22
+ @authors = info['authors'] || []
23
+ @publisher = info['publisher'] || ""
24
+ end
25
+
26
+ #get any attribute, like with a hash
27
+ #ex self['title']
28
+ def [](key)
29
+ return authors if key =~ /authors?/i
30
+ info[key]
31
+ end
32
+
33
+ def authors
34
+ @authors.join(', ')
35
+ end
36
+
37
+ #Be a little bit forgiving if the user types "authors"
38
+ def author
39
+ authors
40
+ end
41
+
42
+ end
@@ -0,0 +1,95 @@
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
+
@@ -0,0 +1,22 @@
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 ADDED
@@ -0,0 +1,8 @@
1
+ class SearchError < StandardError
2
+ end
3
+
4
+ class NoResults < StandardError
5
+ end
6
+
7
+ class DataError < StandardError
8
+ end
@@ -0,0 +1 @@
1
+ BASE_API_URL="https://www.googleapis.com/books/v1/volumes"
@@ -0,0 +1,2 @@
1
+ class SearchError < StandardError
2
+ end
data/glibrary.rb ADDED
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env ruby
2
+ require 'json'
3
+ require 'optparse'
4
+ require 'io/console'
5
+
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'
12
+
13
+ ARGV << '-h' if ARGV.empty?
14
+
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")
17
+
18
+ #default filename for saved reading list
19
+ filename = File.dirname(File.expand_path(__FILE__)) + "/saved_libraries/library.json"
20
+
21
+ #initialize the hash which will hold the search parameters
22
+ o = {}
23
+
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")
29
+
30
+ opts.on("-t", "--title=TITLE", "Specify a title keyword") do |t|
31
+ o[:title] = t
32
+ end
33
+
34
+ opts.on("-a", "--author=AUTHOR", "Specify an author keyword") do |a|
35
+ o[:author] = a
36
+ end
37
+
38
+ opts.on("-p", "--publisher=PUBLISHER", "Specify a publisher keyword") do |p|
39
+ o[:publisher] = p
40
+ end
41
+
42
+ opts.on("-f", "--lib-file=LIBFILE", "Select a library save file") 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
51
+ \t\t\t\t\tdefault library file is [repository_root]/saved_libraries/library.json") do
52
+
53
+ #substitute \ for / if the machine is running windows
54
+ filename.gsub!(/\//, '\\') if ENV.values.any? { |v| v =~ /[A-Z]:\\Windows/i }
55
+
56
+
57
+ #print out the library and exit the program
58
+ l = UserLibrary.new(filename)
59
+ l.pretty_print
60
+ exit
61
+ end
62
+
63
+ opts.on("-h", "--help", "Prints this help") do
64
+ puts ""
65
+ puts opts
66
+ puts "[query]: all other arguments will be treated as general search keywords"
67
+ puts ""
68
+ exit
69
+ end
70
+
71
+ end.parse!
72
+
73
+ #substitute \ for / if the machine is running windows
74
+ filename.gsub!(/\//, '\\') if ENV.values.any? { |v| v =~ /C:\\Windows/i }
75
+
76
+ # Create or load the library, depending on whether or not the file already exists.
77
+ l = UserLibrary.new(filename)
78
+
79
+ #initialize the
80
+ #perform the search, and display the results.
81
+ begin
82
+ s = BookSearch.new(search: ARGV.join('+'), title: o[:title], author: o[:author], publisher: o[:publisher])
83
+
84
+ #see BookSearch definition in classes/book_search.rb
85
+ #the each method (and all Enumerable methods, like map) only operate on
86
+ #the first five matches.
87
+ books = s.map { |info| UserBook.new(info) }
88
+
89
+ #print the resuls
90
+ puts ""
91
+ books.each_with_index do |b, i|
92
+ keys = %w[title author publisher]
93
+ n = i+1
94
+ puts "-"*5 + "Match ##{n}" + "-"*5
95
+ keys.each do |k|
96
+ puts "%s: %s" % [k.capitalize, b[k]]
97
+ end
98
+ puts ""
99
+ end
100
+
101
+ print "Enter a number (1-5) to add a book to your reading list:\n(or any other key to quit)\n"
102
+
103
+ selection = STDIN.getch.chomp.to_i
104
+ i = selection - 1
105
+ puts ""
106
+ exit if i < 0 or i > 4
107
+
108
+ #add a book to the user's library
109
+ l << books[i]
110
+ l.save
111
+
112
+ rescue SearchError
113
+ puts "\nThere was an error with your query; be careful to format it well"
114
+ puts ""
115
+ exit
116
+ rescue NoResults
117
+ puts "\nNo results."
118
+ puts ""
119
+ exit
120
+ end
metadata ADDED
@@ -0,0 +1,52 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: glibrary
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Peter Engelbert
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-10-26 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A simple Google Books API Query program
14
+ email: pmengelbert@gmail.com
15
+ executables:
16
+ - glibrary
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - 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
28
+ - glibrary.rb
29
+ homepage: https://github.com/pmengelbert/g_library
30
+ licenses:
31
+ - MIT
32
+ metadata: {}
33
+ post_install_message:
34
+ rdoc_options: []
35
+ require_paths:
36
+ - lib
37
+ required_ruby_version: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ required_rubygems_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ requirements: []
48
+ rubygems_version: 3.0.6
49
+ signing_key:
50
+ specification_version: 4
51
+ summary: Glibrary
52
+ test_files: []