abcdistill 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.DS_Store +0 -0
- data/.gitignore +8 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +46 -0
- data/Rakefile +2 -0
- data/abcdistill.gemspec +32 -0
- data/bin/abcdistill +18 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/.DS_Store +0 -0
- data/lib/abcdistill.rb +6 -0
- data/lib/abcdistill/book.rb +16 -0
- data/lib/abcdistill/cli.rb +131 -0
- data/lib/abcdistill/genre.rb +75 -0
- data/lib/abcdistill/scraper.rb +149 -0
- data/lib/abcdistill/version.rb +3 -0
- metadata +80 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3818f08e18b6a811e2a32f1edaf7ff307f2c3222
|
4
|
+
data.tar.gz: fd2a307a9b7a0ad3d9b4f97653889baed7ac8d7f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1bc53f04033670eaecff6eac67035a4b59080b5bc5da86b20cf6bc27eb09103ac3d024ba59b2c61620a48b5fff0f6e30649718b3d556fb968a2b518815d3e7f6
|
7
|
+
data.tar.gz: 8a59091c2b13b8505f6ab92cf9efd7e8b04b9ee8fd471df872b6204979ae6da50a6a8e353a3fca109aafd25d4b4e17a8d4b7e7c69760def1e87d2e518305b86f
|
data/.DS_Store
ADDED
Binary file
|
data/.gitignore
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
In the interest of fostering an open and welcoming environment, we as
|
6
|
+
contributors and maintainers pledge to making participation in our project and
|
7
|
+
our community a harassment-free experience for everyone, regardless of age, body
|
8
|
+
size, disability, ethnicity, gender identity and expression, level of experience,
|
9
|
+
nationality, personal appearance, race, religion, or sexual identity and
|
10
|
+
orientation.
|
11
|
+
|
12
|
+
## Our Standards
|
13
|
+
|
14
|
+
Examples of behavior that contributes to creating a positive environment
|
15
|
+
include:
|
16
|
+
|
17
|
+
* Using welcoming and inclusive language
|
18
|
+
* Being respectful of differing viewpoints and experiences
|
19
|
+
* Gracefully accepting constructive criticism
|
20
|
+
* Focusing on what is best for the community
|
21
|
+
* Showing empathy towards other community members
|
22
|
+
|
23
|
+
Examples of unacceptable behavior by participants include:
|
24
|
+
|
25
|
+
* The use of sexualized language or imagery and unwelcome sexual attention or
|
26
|
+
advances
|
27
|
+
* Trolling, insulting/derogatory comments, and personal or political attacks
|
28
|
+
* Public or private harassment
|
29
|
+
* Publishing others' private information, such as a physical or electronic
|
30
|
+
address, without explicit permission
|
31
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
32
|
+
professional setting
|
33
|
+
|
34
|
+
## Our Responsibilities
|
35
|
+
|
36
|
+
Project maintainers are responsible for clarifying the standards of acceptable
|
37
|
+
behavior and are expected to take appropriate and fair corrective action in
|
38
|
+
response to any instances of unacceptable behavior.
|
39
|
+
|
40
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
41
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
42
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
43
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
44
|
+
threatening, offensive, or harmful.
|
45
|
+
|
46
|
+
## Scope
|
47
|
+
|
48
|
+
This Code of Conduct applies both within project spaces and in public spaces
|
49
|
+
when an individual is representing the project or its community. Examples of
|
50
|
+
representing a project or community include using an official project e-mail
|
51
|
+
address, posting via an official social media account, or acting as an appointed
|
52
|
+
representative at an online or offline event. Representation of a project may be
|
53
|
+
further defined and clarified by project maintainers.
|
54
|
+
|
55
|
+
## Enforcement
|
56
|
+
|
57
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
58
|
+
reported by contacting the project team at TODO: Write your email address. All
|
59
|
+
complaints will be reviewed and investigated and will result in a response that
|
60
|
+
is deemed necessary and appropriate to the circumstances. The project team is
|
61
|
+
obligated to maintain confidentiality with regard to the reporter of an incident.
|
62
|
+
Further details of specific enforcement policies may be posted separately.
|
63
|
+
|
64
|
+
Project maintainers who do not follow or enforce the Code of Conduct in good
|
65
|
+
faith may face temporary or permanent repercussions as determined by other
|
66
|
+
members of the project's leadership.
|
67
|
+
|
68
|
+
## Attribution
|
69
|
+
|
70
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
71
|
+
available at [https://contributor-covenant.org/version/1/4][version]
|
72
|
+
|
73
|
+
[homepage]: https://contributor-covenant.org
|
74
|
+
[version]: https://contributor-covenant.org/version/1/4/
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2020: Khoi Nguyen
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# Abcdistill
|
2
|
+
|
3
|
+
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/abcdistill`. To experiment with that code, run `bin/console` for an interactive prompt.
|
4
|
+
|
5
|
+
TODO: Delete this and the text above, and describe your gem
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'abcdistill'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle install
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install abcdistill
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
This gem gives you the most read books this week in each genre from GoodReads. You can then select a book to read further details of that book. To quit anytime, type "quit".
|
26
|
+
|
27
|
+
## Development
|
28
|
+
|
29
|
+
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
30
|
+
|
31
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
32
|
+
|
33
|
+
## Contributing
|
34
|
+
|
35
|
+
Things I'd love to improve is to make the flow of the user experience better. Like functionalities to go "back" to the previous "page" and go to the first page as opposed to having to start the application over.
|
36
|
+
|
37
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/abcdistill. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/abcdistill/blob/master/CODE_OF_CONDUCT.md).
|
38
|
+
|
39
|
+
|
40
|
+
## License
|
41
|
+
|
42
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
43
|
+
|
44
|
+
## Code of Conduct
|
45
|
+
|
46
|
+
Everyone interacting in the Abcdistill project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/abcdistill/blob/master/CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
data/abcdistill.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require_relative 'lib/abcdistill/version'
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "abcdistill"
|
5
|
+
spec.version = Abcdistill::VERSION
|
6
|
+
spec.authors = ["Khoi Nguyen"]
|
7
|
+
spec.email = ["khoinguyenkc@gmail.com"]
|
8
|
+
|
9
|
+
spec.summary = %q{Show most read books of each genre on GoodReads.}
|
10
|
+
spec.description = %q{This gem gives you the most read books this week in each genre from GoodReads. You can then select a book to read further details of that book.}
|
11
|
+
spec.homepage = "https://github.com/khoinguyenkc/abcdistill"
|
12
|
+
spec.license = "MIT"
|
13
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
|
14
|
+
|
15
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
16
|
+
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
18
|
+
spec.metadata["source_code_uri"] = "https://github.com/khoinguyenkc/abcdistill"
|
19
|
+
# spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
|
20
|
+
|
21
|
+
# Specify which files should be added to the gem when it is released.
|
22
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
23
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
24
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
25
|
+
end
|
26
|
+
spec.bindir = "exe"
|
27
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
|
+
spec.require_paths = ["lib"]
|
29
|
+
|
30
|
+
spec.add_dependency "nokogiri"
|
31
|
+
|
32
|
+
end
|
data/bin/abcdistill
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#thats requiring the environment
|
3
|
+
|
4
|
+
require './lib/abcdistill'
|
5
|
+
#this is an ABSOLUTE path! we still dont know hat it work. for now
|
6
|
+
#it's loading the main file in the lib folder. lib/distill.rb
|
7
|
+
#that file itself, distill.rb require other stuff surrounding in, inside distill.rb itself
|
8
|
+
#so you're not loading these files here(version.rb, cli.rb etc..)
|
9
|
+
|
10
|
+
Abcdistill::CLI.new.call()
|
11
|
+
#Distill::Scraper.new.books_in_genre()
|
12
|
+
#Distill::Scraper.new.book_detail(Distill::Book.all[2])
|
13
|
+
#puts "hello"
|
14
|
+
#puts Distill::Book.all[2].title
|
15
|
+
#puts Distill::Book.all[2].authorname
|
16
|
+
#puts Distill::Book.all[2].description
|
17
|
+
|
18
|
+
#Distill::Scraper.new.testing('https://www.goodreads.com/genres/biography')
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "abcdistill"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/lib/.DS_Store
ADDED
Binary file
|
data/lib/abcdistill.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
class Abcdistill::Book
|
2
|
+
attr_accessor :authorname, :pages, :publishdate, :description, :amazonlink
|
3
|
+
attr_reader :title, :link, :genre
|
4
|
+
@@all = []
|
5
|
+
def initialize(title, link = "", genre = "")
|
6
|
+
@title = title
|
7
|
+
@link = link
|
8
|
+
@genre = genre
|
9
|
+
@@all << self
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.all
|
13
|
+
@@all
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
#note that back when we put this class CLI inside the Distill module
|
2
|
+
#we just define it as class cli
|
3
|
+
#but here it's in a seprate file. so things change.
|
4
|
+
#i'm not exactly sure the way things work. i'm just parroting and pryaing it works
|
5
|
+
#apparently putting in like this creates the same effect as if you phsyically put it inside the module
|
6
|
+
#even though it's not, its in a seprate file
|
7
|
+
#hiiiii
|
8
|
+
class Abcdistill::CLI
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
Abcdistill::Genre.addthegenres()
|
12
|
+
#if u add too much at initalize, it slows down the app. but i think here it take same amount either way
|
13
|
+
end
|
14
|
+
|
15
|
+
def call
|
16
|
+
#this is the "HomePage"
|
17
|
+
puts "Hello!"
|
18
|
+
puts "Type quit anytime to quit the app"
|
19
|
+
list_genres
|
20
|
+
process_genrechoice
|
21
|
+
say_goodbye
|
22
|
+
end
|
23
|
+
|
24
|
+
def list_genres
|
25
|
+
Abcdistill::Genre.list_genre_names()
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
|
30
|
+
def say_goodbye
|
31
|
+
puts "Bye. Thanks for using our app."
|
32
|
+
end
|
33
|
+
|
34
|
+
def display_books_in_genre(genre)
|
35
|
+
#aka display books in genre
|
36
|
+
#takes the argument of the genre instance and then display books in that genres
|
37
|
+
#u only need to fetch it once. make sure this happen properly
|
38
|
+
puts genre.name
|
39
|
+
puts genre.genrelink
|
40
|
+
#scrape the books with scraper tool
|
41
|
+
Abcdistill::Scraper.new.books_in_genre(genre)
|
42
|
+
#display the books:
|
43
|
+
Abcdistill::Genre.display_books_of_genre(genre)
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
def display_book_detail(book)
|
48
|
+
puts "Title: #{book.title}"
|
49
|
+
puts "Genre: #{book.genre}"
|
50
|
+
puts "Author: #{book.authorname}"
|
51
|
+
puts "Pages: #{book.pages}"
|
52
|
+
puts "Publish Date: #{book.publishdate}"
|
53
|
+
puts "Amazon Link: #{book.amazonlink}"
|
54
|
+
puts "Description: #{book.description}"
|
55
|
+
end
|
56
|
+
|
57
|
+
def process_bookchoice(genre)
|
58
|
+
#context: user choose a number from a list of 15 books in that genres (from display_genre method)
|
59
|
+
#and the task here is to display the details
|
60
|
+
#besides the number, we need to figure out the context of the list of choices that was displayed
|
61
|
+
#that is needed so that we can figure out what book the user wants
|
62
|
+
#to figure out the context, we'll just ask the genre, we won't ask the for the list, we'll get the source of the list ourselves
|
63
|
+
#cuz we don't want to have code read the list and understand what it means and search it up it's a mess
|
64
|
+
input = nil
|
65
|
+
puts "Type the number of the book you want"
|
66
|
+
while input != "quit"
|
67
|
+
input = gets.strip
|
68
|
+
|
69
|
+
if input.to_i > 0 && input.to_i < 16 #we're strict so we don't run into nil error
|
70
|
+
#figure out what book the user chose:
|
71
|
+
book = Abcdistill::Genre.books_of_genre(genre)[input.to_i - 1]
|
72
|
+
puts "the book we think the user chose is: #{book.title}"
|
73
|
+
# Distill::Genre.display_books_of_genre called in display_books_in_genre actually uses this method above to get its list before it display, so the order should be the same
|
74
|
+
#that is, unless there was a change in the moment jsut before. which shouldn't happen. becasue this app doesn't update anything automatically on itself
|
75
|
+
#fetch more details on that book:
|
76
|
+
Abcdistill::Scraper.new.book_detail(book)
|
77
|
+
#this should update the book with more details
|
78
|
+
#display the book pseudo code:
|
79
|
+
display_book_detail(book)
|
80
|
+
|
81
|
+
process_bookchoice(genre)
|
82
|
+
|
83
|
+
return
|
84
|
+
elsif input == "quit"
|
85
|
+
return
|
86
|
+
else
|
87
|
+
puts "Invalid input. Please re-enter"
|
88
|
+
end #end if
|
89
|
+
|
90
|
+
end #end loop
|
91
|
+
end #end method
|
92
|
+
|
93
|
+
|
94
|
+
|
95
|
+
def process_genrechoice
|
96
|
+
input = nil
|
97
|
+
puts "Type the number of the GENRE you want"
|
98
|
+
while input != "quit"
|
99
|
+
input = gets.strip
|
100
|
+
|
101
|
+
if input.to_i > 0 && input.to_i < Abcdistill::Genre.all.size
|
102
|
+
#fetch the books in that genre
|
103
|
+
genre = Abcdistill::Genre.all[input.to_i - 1] #returns genre instance
|
104
|
+
display_books_in_genre(genre)
|
105
|
+
process_bookchoice(genre) #this will ask for input and process it
|
106
|
+
|
107
|
+
|
108
|
+
return
|
109
|
+
|
110
|
+
|
111
|
+
|
112
|
+
|
113
|
+
# if input == "1"
|
114
|
+
# puts "1. abc"
|
115
|
+
# process_choice()
|
116
|
+
# return #gotta put return otherwise u'll be doing nothing
|
117
|
+
# #cuz u have many recursive loop, but the outerloop still doesn't have the input = exit, only input of the inner loops are exit
|
118
|
+
# elsif input == "2"
|
119
|
+
# puts "2. def"
|
120
|
+
# process_choice()
|
121
|
+
# return #gotta put return otherwise u'll be doing nothing
|
122
|
+
elsif input == "quit"
|
123
|
+
return
|
124
|
+
else
|
125
|
+
puts "Invalid input. Please re-enter"
|
126
|
+
end #end if
|
127
|
+
|
128
|
+
end #end loop
|
129
|
+
end #end method
|
130
|
+
|
131
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
class ABCDistill::Genre
|
2
|
+
attr_reader :name
|
3
|
+
attr_accessor :genrelink
|
4
|
+
@@all = []
|
5
|
+
@@genrelist = [
|
6
|
+
"Art", "Biography","Business","Children's","Christian","Classics","Comics","Cookbooks","Ebooks","Fantasy","Fiction","Graphic Novels","Historical Fiction","History","Horror","Memoir","Music","Mystery","Nonfiction","Poetry","Psychology","Romance","Science","Science Fiction","Self Help","Sports","Thriller","Travel","Young Adult"
|
7
|
+
]
|
8
|
+
@@genrelinks = [
|
9
|
+
"art", "biography","business","children-s","christian","classics","comics","cookbooks","ebooks","fantasy","fiction","graphic-novels","historical-fiction","history","horror","memoir","music","mystery","non-fiction","poetry","psychology","romance","science","science-fiction","self-help","sports","thriller","travel","young-adult"
|
10
|
+
]
|
11
|
+
|
12
|
+
|
13
|
+
def initialize(name)
|
14
|
+
@name = name
|
15
|
+
#there should be some kind of name so that it created by accessed/looked up
|
16
|
+
#perhaps each book should have a genre property
|
17
|
+
#be careful of "grabbing" things, because a book might be severla genre
|
18
|
+
#we have to think about at what point we assign genre
|
19
|
+
#ther emight be a book that show up in severla genre lists
|
20
|
+
#these should be made to be different instnaces? or we have books have the capacity to have diff genres?
|
21
|
+
#right now i will be making multiple book instances
|
22
|
+
@@all << self
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.books_of_genre(genre)
|
26
|
+
ABCDistill::Book.all.select do | book |
|
27
|
+
book.genre == genre
|
28
|
+
end
|
29
|
+
#return all books of this genre
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.display_books_of_genre(genre)
|
33
|
+
books = self.books_of_genre(genre)
|
34
|
+
books.each_with_index do | book, index |
|
35
|
+
puts "#{index+1}. #{book.title}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
def self.find_genre_by_name(genrename) #class method
|
41
|
+
#return the FIRST match, not all matches
|
42
|
+
self.all.find { | instance | instance.name == genrename }
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.addthegenres #class method
|
46
|
+
#THIS METHOD IS MEANT TO BE CALLED ONCE
|
47
|
+
|
48
|
+
# genrelist = [
|
49
|
+
# "Art", "Biography","Business","Children's","Christian","Classics","Comics","Cookbooks","Ebooks","Fantasy","Fiction","Graphic Novels","Historical Fiction","History","Horror","Memoir","Music","Mystery","Nonfiction","Poetry","Psychology","Romance","Science","Science Fiction","Self Help","Sports","Thriller","Travel","Young Adult"
|
50
|
+
# ]
|
51
|
+
# genrelinks = [
|
52
|
+
# "art", "biography","business","children-s","christian","classics","comics","cookbooks","ebooks","fantasy","fiction","graphic-novels","historical-fiction","history","horror","memoir","music","mystery","non-fiction","poetry","psychology","romance","science","science-fiction","self-help","sports","thriller","travel","young-adult"
|
53
|
+
# ]
|
54
|
+
if @@genrelist != [] #this makes it a one-time method.
|
55
|
+
@@genrelist.each_with_index do | genrename, index |
|
56
|
+
newinstance = self.new(genrename)
|
57
|
+
newinstance.genrelink = "https://www.goodreads.com/genres/#{@@genrelinks[index]}"
|
58
|
+
end #end iteration
|
59
|
+
end #end if
|
60
|
+
end #end method
|
61
|
+
|
62
|
+
def self.list_genre_names
|
63
|
+
self.all.each_with_index do | genre, index |
|
64
|
+
puts "#{index+1}. #{genre.name} "
|
65
|
+
end
|
66
|
+
|
67
|
+
end #end method
|
68
|
+
|
69
|
+
def self.all
|
70
|
+
@@all
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
|
75
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
class Abcdistill::Scraper
|
2
|
+
#scraper is a tool. not an object. so it doesn't store anything in some kind of @@all array
|
3
|
+
#i'm gonna make the methods instance methods becuase it feels that way. like u feed it very diff things..
|
4
|
+
#idk..
|
5
|
+
def book_detail(bookinstance)
|
6
|
+
#eventually we'll have this take premade book instance (made with title and link)
|
7
|
+
#and we'll use the link to scrape other info and fill in.
|
8
|
+
|
9
|
+
html = open('https://www.goodreads.com/book/show/45046808-big-lies-in-a-small-town')
|
10
|
+
doc = Nokogiri::HTML(html) #retunrs xml nodeset
|
11
|
+
|
12
|
+
authorname = doc.css("a.authorName").text
|
13
|
+
#omg i'm so excited that this works! u can search by attribute
|
14
|
+
pages = doc.css("div#details span[itemprop=\"numberOfPages\"]").text
|
15
|
+
publishdate = (doc.css("div#details .row")[1].text).split("\n")[2].strip #this should say the date
|
16
|
+
#that piece has really weird strucutre. spaces is asn item too. and u have to strip the space to make it look normal
|
17
|
+
#lets hope this is stable across
|
18
|
+
description = doc.css("div#description span")[1].text
|
19
|
+
|
20
|
+
amazonlinktail = doc.css("ul.buyButtonBar a#buyButton").attribute("href").value
|
21
|
+
amazonlink = addgoodreadsdotcom(amazonlinktail)
|
22
|
+
#yes the link to amazon has a transfer link on goodred
|
23
|
+
|
24
|
+
#i like to separate the task so that if i had to change css stuff, i can easily experiment neatly
|
25
|
+
|
26
|
+
bookinstance.authorname = authorname
|
27
|
+
bookinstance.pages = pages
|
28
|
+
bookinstance.publishdate = publishdate
|
29
|
+
bookinstance.description = description
|
30
|
+
bookinstance.amazonlink = amazonlink
|
31
|
+
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
def addgoodreadsdotcom(linktail)
|
36
|
+
link = "https://www.goodreads.com#{linktail}"
|
37
|
+
end
|
38
|
+
|
39
|
+
# def testing(genrelink)
|
40
|
+
# #we'll be given a page , like https://www.goodreads.com/genres/art
|
41
|
+
# #we'll scrape all the book titles and its link. 15 of them.
|
42
|
+
# #we want this to create 15 book instsances and make sure they are SAVED
|
43
|
+
#
|
44
|
+
# html = open(genrelink)
|
45
|
+
# doc = Nokogiri::HTML(html) #retunrs xml nodeset
|
46
|
+
# allbigboxes = doc.css(".coverBigBox")
|
47
|
+
# puts allbigboxes.size
|
48
|
+
# allbigboxes.each do | box |
|
49
|
+
# puts box.css(".h2Container h2 a").text
|
50
|
+
# end
|
51
|
+
# end
|
52
|
+
|
53
|
+
def books_in_genre(genre)
|
54
|
+
#we'll be given a page , like https://www.goodreads.com/genres/art
|
55
|
+
#we'll scrape all the book titles and its link. 15 of them.
|
56
|
+
#we want this to create 15 book instsances and make sure they are SAVED
|
57
|
+
|
58
|
+
#gatekeeper: to prevent duplicates, check if books in this genre is already fetched.
|
59
|
+
if Abcdistill::Genre.books_of_genre(genre) != [] #cant do truthy falsey because empty array is truthy in ruby
|
60
|
+
return
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
html = open(genre.genrelink)
|
65
|
+
doc = Nokogiri::HTML(html) #retunrs xml nodeset
|
66
|
+
genrename = doc.css(".genreHeader h1").text.strip
|
67
|
+
puts "the genrename that was scraped is -#{genrename}-"
|
68
|
+
if genrename == "Childrens"
|
69
|
+
genrename = "Children's"
|
70
|
+
end
|
71
|
+
puts "new genrename is #{genrename}"
|
72
|
+
#i'm fixing it manually because the url can be unreliable. say ...biography vs ...biography/
|
73
|
+
#using a split tool can be complicated to deal with edge cases like that
|
74
|
+
|
75
|
+
genre = Abcdistill::Genre.find_genre_by_name(genrename)
|
76
|
+
puts "did they find the genre in the list? the name found is #{genre.name}"
|
77
|
+
#the genres were already added at the moment CLI class's list_options is called.
|
78
|
+
#mostread = doc.css(".bigBoxBody")[1]
|
79
|
+
allbigboxes = doc.css(".coverBigBox")
|
80
|
+
mostread = allbigboxes.find do | box |
|
81
|
+
box.css(".h2Container h2 a").text.include?("Most Read This Week")
|
82
|
+
end
|
83
|
+
#i used include because == doesn't work for many cases. it's not uniform. sometiems it's most read this week tagged Christian
|
84
|
+
|
85
|
+
|
86
|
+
#the [0] index is not what we want. we just want the [1]
|
87
|
+
#mostread only contains one "item" which is the bigboxbody we want
|
88
|
+
books = mostread.css(".bookBox a")
|
89
|
+
puts "how many books found in books = mostread.css.... #{books.size}"
|
90
|
+
#this puts is really helpful for debugging. don't remove it
|
91
|
+
|
92
|
+
#Distill::Genre.books_of_genre
|
93
|
+
#we mut setup some how to only fetch books if they're not already fetched
|
94
|
+
#to prevent duplicates
|
95
|
+
#task NOT completed
|
96
|
+
|
97
|
+
books.each do | book |
|
98
|
+
title = book.css("img").attribute("alt").value
|
99
|
+
booklink = addgoodreadsdotcom(book.attribute("href").value)
|
100
|
+
#create a new book instance:
|
101
|
+
Abcdistill::Book.new(title, booklink, genre)
|
102
|
+
end
|
103
|
+
|
104
|
+
puts "how many books of this genre is added and recognized: #{Abcdistill::Genre.books_of_genre(genre).size}"
|
105
|
+
|
106
|
+
#puts Distill::Genre.books_of_genre(genre)[0]
|
107
|
+
# Distill::Genre.books_of_genre(genre).each do | book |
|
108
|
+
# puts book
|
109
|
+
# puts book.title
|
110
|
+
# end
|
111
|
+
|
112
|
+
|
113
|
+
|
114
|
+
#we want to seprate tasks. so this should only fetch the books in that genre. but not display it
|
115
|
+
#display should be a method for Genre or Book
|
116
|
+
|
117
|
+
|
118
|
+
|
119
|
+
#notice how to make this kind of structure, the thing we're looping (titles), need each title to be something that is a ROOT
|
120
|
+
#for ex: the alt value of the img element. the img element is a child of the .bookBox a element. .booxBox a leement is a "root"
|
121
|
+
#the href is also belongs to the .bookbox a element. otherwise we can't make use of the loop structure
|
122
|
+
#metadata
|
123
|
+
#when we scrape each element seprately, we don't have to think about that at all, but as a loop its very different!
|
124
|
+
#so don't think if i got each element down, putting it in a loop is simple. no! gotta see what they have in common and "refactor"
|
125
|
+
|
126
|
+
#puts title = mostread.css(".bookBox a img")[0].attribute("alt").value
|
127
|
+
# attribute("alt").value
|
128
|
+
#linktail = mostread.css(".bookBox a")[0].attribute("href").value
|
129
|
+
#ex: /book/show/53991683-the-woman-in-the-moonlight
|
130
|
+
#puts booklink = addgoodreadsdotcom(linktail)
|
131
|
+
|
132
|
+
#how she we organize this?
|
133
|
+
#we never want to have to scrape anything twice. so we save everything
|
134
|
+
#tehre should be some kinda all that host different genres
|
135
|
+
#each genres has 15 hashes of book title and links
|
136
|
+
#as we use book_detail, we'll add to the 15 hashes other properties, like author, pages, etc
|
137
|
+
#but i dont know how should we organize all this
|
138
|
+
#in classes named genres and books?
|
139
|
+
#apprently they want objects, which is instances of class, so we'll probably do that..
|
140
|
+
#make it life-like i guess
|
141
|
+
|
142
|
+
#we need to loop through each book. this might take more cleaning effort to loop through the right things
|
143
|
+
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
|
148
|
+
|
149
|
+
end
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: abcdistill
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Khoi Nguyen
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-09-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: nokogiri
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
description: This gem gives you the most read books this week in each genre from GoodReads.
|
28
|
+
You can then select a book to read further details of that book.
|
29
|
+
email:
|
30
|
+
- khoinguyenkc@gmail.com
|
31
|
+
executables: []
|
32
|
+
extensions: []
|
33
|
+
extra_rdoc_files: []
|
34
|
+
files:
|
35
|
+
- ".DS_Store"
|
36
|
+
- ".gitignore"
|
37
|
+
- CODE_OF_CONDUCT.md
|
38
|
+
- Gemfile
|
39
|
+
- LICENSE.txt
|
40
|
+
- README.md
|
41
|
+
- Rakefile
|
42
|
+
- abcdistill.gemspec
|
43
|
+
- bin/abcdistill
|
44
|
+
- bin/console
|
45
|
+
- bin/setup
|
46
|
+
- lib/.DS_Store
|
47
|
+
- lib/abcdistill.rb
|
48
|
+
- lib/abcdistill/book.rb
|
49
|
+
- lib/abcdistill/cli.rb
|
50
|
+
- lib/abcdistill/genre.rb
|
51
|
+
- lib/abcdistill/scraper.rb
|
52
|
+
- lib/abcdistill/version.rb
|
53
|
+
homepage: https://github.com/khoinguyenkc/abcdistill
|
54
|
+
licenses:
|
55
|
+
- MIT
|
56
|
+
metadata:
|
57
|
+
allowed_push_host: https://rubygems.org
|
58
|
+
homepage_uri: https://github.com/khoinguyenkc/abcdistill
|
59
|
+
source_code_uri: https://github.com/khoinguyenkc/abcdistill
|
60
|
+
post_install_message:
|
61
|
+
rdoc_options: []
|
62
|
+
require_paths:
|
63
|
+
- lib
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 2.3.0
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
requirements: []
|
75
|
+
rubyforge_project:
|
76
|
+
rubygems_version: 2.5.2.3
|
77
|
+
signing_key:
|
78
|
+
specification_version: 4
|
79
|
+
summary: Show most read books of each genre on GoodReads.
|
80
|
+
test_files: []
|