klipbook 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.rspec +1 -0
- data/.yardopts +1 -0
- data/Gemfile +19 -0
- data/Gemfile.lock +67 -0
- data/Guardfile +19 -0
- data/LICENSE.txt +20 -0
- data/README.md +51 -0
- data/Rakefile +50 -0
- data/bin/klipbook +7 -0
- data/example.png +0 -0
- data/features/list_books.feature +23 -0
- data/features/print_book_summary.feature +10 -0
- data/features/step_definitions/klipbook_steps.rb +87 -0
- data/features/support/env.rb +8 -0
- data/lib/klipbook/blank.rb +24 -0
- data/lib/klipbook/book_summary.erb +92 -0
- data/lib/klipbook/book_summary.rb +34 -0
- data/lib/klipbook/cli.rb +48 -0
- data/lib/klipbook/clipping.rb +15 -0
- data/lib/klipbook/clippings_file.rb +50 -0
- data/lib/klipbook/clippings_parser.rb +82 -0
- data/lib/klipbook/runner.rb +29 -0
- data/lib/klipbook/version.rb +3 -0
- data/lib/klipbook.rb +10 -0
- data/spec/lib/klipbook/book_summary_spec.rb +30 -0
- data/spec/lib/klipbook/clipping_spec.rb +17 -0
- data/spec/lib/klipbook/clippings_file_spec.rb +60 -0
- data/spec/lib/klipbook/clippings_parser_spec.rb +212 -0
- data/spec/lib/klipbook/runner_spec.rb +87 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/support/rspec2.rb +25 -0
- data/spec/support/with_rr.rb +12 -0
- metadata +239 -0
data/.document
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--colour --format documentation
|
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
-m markdown
|
data/Gemfile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
source 'http://rubygems.org'
|
2
|
+
|
3
|
+
gem 'thor'
|
4
|
+
|
5
|
+
group :development do
|
6
|
+
gem 'rspec'
|
7
|
+
gem 'rr'
|
8
|
+
gem 'bundler'
|
9
|
+
gem 'jeweler', '~> 1.6.4'
|
10
|
+
gem 'rcov', '>= 0'
|
11
|
+
gem 'cucumber'
|
12
|
+
gem 'guard'
|
13
|
+
gem 'guard-rspec'
|
14
|
+
gem 'guard-cucumber'
|
15
|
+
gem 'rb-inotify', :require => false
|
16
|
+
gem 'rb-fsevent', :require => false
|
17
|
+
gem 'rb-fchange', :require => false
|
18
|
+
gem 'growl_notify'
|
19
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
builder (3.0.0)
|
5
|
+
cucumber (1.1.3)
|
6
|
+
builder (>= 2.1.2)
|
7
|
+
diff-lcs (>= 1.1.2)
|
8
|
+
gherkin (~> 2.6.7)
|
9
|
+
json (>= 1.4.6)
|
10
|
+
term-ansicolor (>= 1.0.6)
|
11
|
+
diff-lcs (1.1.3)
|
12
|
+
ffi (1.0.11)
|
13
|
+
gherkin (2.6.7)
|
14
|
+
json (>= 1.4.6)
|
15
|
+
git (1.2.5)
|
16
|
+
growl_notify (0.0.3)
|
17
|
+
rb-appscript
|
18
|
+
guard (0.8.8)
|
19
|
+
thor (~> 0.14.6)
|
20
|
+
guard-cucumber (0.7.4)
|
21
|
+
cucumber (>= 0.10)
|
22
|
+
guard (>= 0.8.3)
|
23
|
+
guard-rspec (0.5.5)
|
24
|
+
guard (>= 0.8.4)
|
25
|
+
jeweler (1.6.4)
|
26
|
+
bundler (~> 1.0)
|
27
|
+
git (>= 1.2.5)
|
28
|
+
rake
|
29
|
+
json (1.6.1)
|
30
|
+
rake (0.9.2.2)
|
31
|
+
rb-appscript (0.6.1)
|
32
|
+
rb-fchange (0.0.5)
|
33
|
+
ffi
|
34
|
+
rb-fsevent (0.4.3.1)
|
35
|
+
rb-inotify (0.8.8)
|
36
|
+
ffi (>= 0.5.0)
|
37
|
+
rcov (0.9.11)
|
38
|
+
rr (1.0.4)
|
39
|
+
rspec (2.7.0)
|
40
|
+
rspec-core (~> 2.7.0)
|
41
|
+
rspec-expectations (~> 2.7.0)
|
42
|
+
rspec-mocks (~> 2.7.0)
|
43
|
+
rspec-core (2.7.1)
|
44
|
+
rspec-expectations (2.7.0)
|
45
|
+
diff-lcs (~> 1.1.2)
|
46
|
+
rspec-mocks (2.7.0)
|
47
|
+
term-ansicolor (1.0.7)
|
48
|
+
thor (0.14.6)
|
49
|
+
|
50
|
+
PLATFORMS
|
51
|
+
ruby
|
52
|
+
|
53
|
+
DEPENDENCIES
|
54
|
+
bundler
|
55
|
+
cucumber
|
56
|
+
growl_notify
|
57
|
+
guard
|
58
|
+
guard-cucumber
|
59
|
+
guard-rspec
|
60
|
+
jeweler (~> 1.6.4)
|
61
|
+
rb-fchange
|
62
|
+
rb-fsevent
|
63
|
+
rb-inotify
|
64
|
+
rcov
|
65
|
+
rr
|
66
|
+
rspec
|
67
|
+
thor
|
data/Guardfile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
|
2
|
+
group 'backend' do
|
3
|
+
|
4
|
+
guard 'rspec', :cli => '--color --format doc' do
|
5
|
+
# Regexp watch patterns are matched with Regexp#match
|
6
|
+
watch(%r{^spec/.+_spec\.rb$})
|
7
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
8
|
+
|
9
|
+
# String watch patterns are matched with simple '=='
|
10
|
+
watch('spec/spec_helper.rb') { "spec" }
|
11
|
+
end
|
12
|
+
|
13
|
+
guard 'cucumber' do
|
14
|
+
watch(%r{^features/.+\.feature$})
|
15
|
+
watch(%r{^features/support/.+$}) { 'features' }
|
16
|
+
watch(%r{^features/step_definitions/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'features' }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Ray Grasso
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# Klipbook
|
2
|
+
|
3
|
+
Klipbook creates a html summary of your Kindle book clippings.
|
4
|
+
|
5
|
+
## How does it work?
|
6
|
+
|
7
|
+
Copy your clippings file (called "My Clippings.txt" on a 3rd generation Kindle) from your Kindle device to your local drive via USB.
|
8
|
+
|
9
|
+
**List the books in your clippings file:**
|
10
|
+
|
11
|
+
$ klipbook list "My Clippings.txt"
|
12
|
+
|
13
|
+
The list of books in your clippings file:
|
14
|
+
[1] The Big Sleep by Raymond Chandler
|
15
|
+
[2] How to jump out of a plane without a parachute and survive by Rip Rockjaw
|
16
|
+
|
17
|
+
**Print a html summary for the book of your choice:**
|
18
|
+
|
19
|
+
Choose the index of the book you are interested in and print a html summary with the `summarise` command:
|
20
|
+
|
21
|
+
$ klipbook summarise "My Clippings.txt" 1 big-sleep-clippings.html
|
22
|
+
|
23
|
+
Keep this nicely formatted html version of your clippings for your own reference.
|
24
|
+
|
25
|
+
## Example of a summary file generated by Klipbook
|
26
|
+
|
27
|
+
<img src="https://github.com/grassdog/klipbook/raw/master/example.png" alt="Example of a summary file" />
|
28
|
+
|
29
|
+
## Installation
|
30
|
+
|
31
|
+
Klipbook is a Ruby gem. To install simply run:
|
32
|
+
|
33
|
+
gem install klipbook
|
34
|
+
|
35
|
+
## Why not just see your clippings on the Amazon site?
|
36
|
+
|
37
|
+
Currently [the Amazon highlights site](https://kindle.amazon.com/your_highlights) only shows clippings for books you purchased on Amazon.
|
38
|
+
|
39
|
+
## Tested platforms
|
40
|
+
|
41
|
+
Klipbook has been tested on clippings files from 3rd generation Kindles and run using MRI 1.9.3 on Mac OSX Lion and Ubuntu.
|
42
|
+
|
43
|
+
|
44
|
+
## Contributing to Klipbook
|
45
|
+
|
46
|
+
Fork the project on [Github](https://github.com/grassdog/klipbook) and submit a pull request.
|
47
|
+
|
48
|
+
## Copyright
|
49
|
+
|
50
|
+
Copyright (c) 2011 Ray Grasso. See LICENSE.txt for further details.
|
51
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'rake'
|
6
|
+
require './lib/klipbook/version.rb'
|
7
|
+
|
8
|
+
require 'jeweler'
|
9
|
+
Jeweler::Tasks.new do |gem|
|
10
|
+
gem.name = 'klipbook'
|
11
|
+
gem.homepage = 'https://github.com/grassdog/klipbook'
|
12
|
+
gem.license = 'MIT'
|
13
|
+
gem.summary = %Q{Klipbook creates a HTML summary of your Kindle book clippings.}
|
14
|
+
gem.description = %Q{Process your Kindle clippings file to generate a nicely formatted compilation of your clippings of the books you've read}
|
15
|
+
gem.email = 'ray.grasso@gmail.com'
|
16
|
+
gem.authors = ['Ray Grasso']
|
17
|
+
gem.version = Klipbook::VERSION
|
18
|
+
# dependencies defined in Gemfile
|
19
|
+
end
|
20
|
+
Jeweler::RubygemsDotOrgTasks.new
|
21
|
+
|
22
|
+
require 'rdoc/task'
|
23
|
+
Rake::RDocTask.new do |rdoc|
|
24
|
+
version = Klipbook::VERSION
|
25
|
+
|
26
|
+
rdoc.rdoc_dir = 'rdoc'
|
27
|
+
rdoc.title = "klipbook #{version}"
|
28
|
+
rdoc.rdoc_files.include('README*')
|
29
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
30
|
+
end
|
31
|
+
|
32
|
+
require 'rspec/core/rake_task'
|
33
|
+
desc 'Generate code coverage'
|
34
|
+
RSpec::Core::RakeTask.new(:coverage) do |t|
|
35
|
+
t.pattern = './spec/**/*_spec.rb' # don't need this, it's default.
|
36
|
+
t.rcov = true
|
37
|
+
t.rcov_opts = ['--exclude', 'spec']
|
38
|
+
end
|
39
|
+
|
40
|
+
desc 'Run specs'
|
41
|
+
RSpec::Core::RakeTask.new(:spec)
|
42
|
+
|
43
|
+
require 'cucumber/rake/task'
|
44
|
+
Cucumber::Rake::Task.new(:features) do |t|
|
45
|
+
t.cucumber_opts = '--format progress'
|
46
|
+
end
|
47
|
+
|
48
|
+
desc 'Default: run specs'
|
49
|
+
task :default => :spec
|
50
|
+
|
data/bin/klipbook
ADDED
data/example.png
ADDED
Binary file
|
@@ -0,0 +1,23 @@
|
|
1
|
+
Feature: klipbook lists the books in a clipping file
|
2
|
+
As an avid reader and note taker
|
3
|
+
I want to be shown an indexed list of books in my clipping file
|
4
|
+
So that I can choose which book I want to extract a pretty collated list of notes from
|
5
|
+
|
6
|
+
Scenario: Empty file
|
7
|
+
Given I have a file that contains no clippings
|
8
|
+
When I list the books in the file with klipbook
|
9
|
+
Then I should see the message "Your clippings file contains no books"
|
10
|
+
|
11
|
+
Scenario: File with one book
|
12
|
+
Given I have a file that contains clippings for the book titled "The life of dogs"
|
13
|
+
When I list the books in the file with klipbook
|
14
|
+
Then I should see the message "The list of books in your clippings file:"
|
15
|
+
And I should see the message "[1] The life of dogs"
|
16
|
+
|
17
|
+
Scenario: File with two books
|
18
|
+
Given I have a file that contains clippings for the book titled "The life of dogs"
|
19
|
+
And I have a file that contains clippings for the book titled "A hard day's night"
|
20
|
+
When I list the books in the file with klipbook
|
21
|
+
Then I should see the message "The list of books in your clippings file:"
|
22
|
+
And I should see the message "[1] A hard day's night"
|
23
|
+
And I should see the message "[2] The life of dogs"
|
@@ -0,0 +1,10 @@
|
|
1
|
+
Feature: klipbook outputs a pretty summary of the clippings for a book
|
2
|
+
As an avid reader and note taker
|
3
|
+
I want to see a pretty summary file of the clippings for a book
|
4
|
+
So that I can read a nice summary of my clippings for a book I've read
|
5
|
+
|
6
|
+
Scenario: File with clippings for a book
|
7
|
+
Given I have a file that contains multiple clippings for the book titled "The life of dogs"
|
8
|
+
When I print the summary for book number "1" with klipbook
|
9
|
+
Then I should see a pretty summary for the book "The life of dogs"
|
10
|
+
|
@@ -0,0 +1,87 @@
|
|
1
|
+
Before do
|
2
|
+
FileUtils.mkdir_p(TEST_DIR)
|
3
|
+
Dir.chdir(TEST_DIR)
|
4
|
+
end
|
5
|
+
|
6
|
+
After do
|
7
|
+
Dir.chdir(TEST_DIR)
|
8
|
+
FileUtils.rm_rf(TEST_DIR)
|
9
|
+
end
|
10
|
+
|
11
|
+
class Output
|
12
|
+
def messages
|
13
|
+
@messages ||= []
|
14
|
+
end
|
15
|
+
|
16
|
+
def puts(message)
|
17
|
+
messages << message
|
18
|
+
end
|
19
|
+
|
20
|
+
def write(message)
|
21
|
+
messages << message
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def output
|
26
|
+
@output ||= Output.new
|
27
|
+
end
|
28
|
+
|
29
|
+
CLIPPING_FILE = 'test_clippings.txt'
|
30
|
+
|
31
|
+
Given /^I have a file that contains no clippings$/ do
|
32
|
+
File.open(CLIPPING_FILE, 'w') do |f|
|
33
|
+
f.write ''
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
When /^I list the books in the file with klipbook$/ do
|
38
|
+
File.open(CLIPPING_FILE, 'r') do |f|
|
39
|
+
Klipbook::Runner.new(f).list_books(output)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
Then /^I should see the message "([^"]*)"$/ do |message|
|
44
|
+
output.messages.should include(message)
|
45
|
+
end
|
46
|
+
|
47
|
+
Given /^I have a file that contains clippings for the book titled "([^"]*)"$/ do |book_title|
|
48
|
+
File.open(CLIPPING_FILE, 'a') do |f|
|
49
|
+
f.write <<EOF
|
50
|
+
#{book_title}
|
51
|
+
- Highlight Loc. 466-69 | Added on Thursday, April 21, 2011, 07:31 AM
|
52
|
+
|
53
|
+
A test highlight
|
54
|
+
==========
|
55
|
+
EOF
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
Given /^I have a file that contains multiple clippings for the book titled "([^"]*)"$/ do |book_title|
|
60
|
+
File.open(CLIPPING_FILE, 'a') do |f|
|
61
|
+
f.write <<EOF
|
62
|
+
#{book_title}
|
63
|
+
- Highlight Loc. 466-69 | Added on Thursday, April 21, 2011, 07:31 AM
|
64
|
+
|
65
|
+
A test highlight
|
66
|
+
==========
|
67
|
+
#{book_title}
|
68
|
+
- Highlight Loc. 490 | Added on Thursday, April 21, 2011, 07:36 AM
|
69
|
+
|
70
|
+
A second highlight
|
71
|
+
==========
|
72
|
+
EOF
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
When /^I print the summary for book number "([^"]+)" with klipbook/ do |book_number|
|
77
|
+
book_number = book_number.to_i
|
78
|
+
|
79
|
+
File.open(CLIPPING_FILE, 'r') do |f|
|
80
|
+
Klipbook::Runner.new(f).print_book_summary(book_number, output)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
Then /^I should see a pretty summary for the book "([^"]*)"$/ do |book_title|
|
85
|
+
output.messages.first.should include("<h1>#{book_title}</h1")
|
86
|
+
end
|
87
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class String
|
2
|
+
|
3
|
+
# A string is blank if it's empty or contains whitespaces only:
|
4
|
+
#
|
5
|
+
# "".blank? # => true
|
6
|
+
# " ".blank? # => true
|
7
|
+
# " something here ".blank? # => false
|
8
|
+
#
|
9
|
+
def blank?
|
10
|
+
self !~ /\S/
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class NilClass
|
15
|
+
|
16
|
+
# +nil+ is blank:
|
17
|
+
#
|
18
|
+
# nil.blank? # => true
|
19
|
+
#
|
20
|
+
def blank?
|
21
|
+
true
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
@@ -0,0 +1,92 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<meta charset="utf-8"/>
|
5
|
+
<meta name="generator" content="Klipbook"/>
|
6
|
+
<title><%= @clippings.first.title %> - Collated Kindle Clippings</title>
|
7
|
+
<style type="text/css">
|
8
|
+
body {
|
9
|
+
color: #333;
|
10
|
+
font-size: 16px;
|
11
|
+
line-height: 1.5em;
|
12
|
+
font-family: Palatino, Georgia, serif;
|
13
|
+
background-color: #fff;
|
14
|
+
margin: 0px;
|
15
|
+
padding: 20px;
|
16
|
+
}
|
17
|
+
a {
|
18
|
+
text-decoration: none;
|
19
|
+
}
|
20
|
+
a, a:link, a:visited {
|
21
|
+
color: #084ab7;
|
22
|
+
}
|
23
|
+
a:hover {
|
24
|
+
text-decoration: underline;
|
25
|
+
}
|
26
|
+
|
27
|
+
h1 {
|
28
|
+
line-height: 1.1em;
|
29
|
+
}
|
30
|
+
|
31
|
+
h2 {
|
32
|
+
line-height: 1.1em;
|
33
|
+
font-size: 1.3em;
|
34
|
+
color: #555;
|
35
|
+
font-style: italic;
|
36
|
+
}
|
37
|
+
|
38
|
+
ul {
|
39
|
+
margin-top: 2em;
|
40
|
+
width: 43em;
|
41
|
+
}
|
42
|
+
|
43
|
+
ul li {
|
44
|
+
margin: 1.6em 0;
|
45
|
+
list-style: none;
|
46
|
+
}
|
47
|
+
|
48
|
+
ul li p {
|
49
|
+
margin-bottom: .5em;
|
50
|
+
}
|
51
|
+
|
52
|
+
ul li footer {
|
53
|
+
text-align: right;
|
54
|
+
font-size: .85em;
|
55
|
+
color: #8C8C8C;
|
56
|
+
}
|
57
|
+
|
58
|
+
footer {
|
59
|
+
font-size: .85em;
|
60
|
+
margin-left: 20em;
|
61
|
+
}
|
62
|
+
|
63
|
+
footer span {
|
64
|
+
font-style: italic;
|
65
|
+
}
|
66
|
+
</style>
|
67
|
+
</head>
|
68
|
+
<body>
|
69
|
+
|
70
|
+
<h1><%= @clippings.first.title %></h1>
|
71
|
+
<% unless @author.blank? %>
|
72
|
+
<h2>by <%= author %></h2>
|
73
|
+
<% end %>
|
74
|
+
|
75
|
+
<ul>
|
76
|
+
<% @clippings.each do |clipping| %>
|
77
|
+
<% unless clipping.text.blank? %>
|
78
|
+
<li>
|
79
|
+
<p>
|
80
|
+
<%= ERB::Util.html_escape(clipping.text) %>
|
81
|
+
</p>
|
82
|
+
<footer><%= clipping.type %><% if clipping.location %> @ loc <%= clipping.location %><% end %></footer>
|
83
|
+
</li>
|
84
|
+
<% end %>
|
85
|
+
<% end %>
|
86
|
+
</ul>
|
87
|
+
|
88
|
+
<footer>
|
89
|
+
Generated by <a href="https://github.com/grassdog/klipbook">Klipbook <%= Klipbook::VERSION %></a> on <span><%= DateTime.now.strftime('%e %b %Y at %l:%M %P') %></span>
|
90
|
+
</footer>
|
91
|
+
</body>
|
92
|
+
</html>
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
module Klipbook
|
4
|
+
class BookSummary
|
5
|
+
attr_accessor :title, :author, :clippings
|
6
|
+
|
7
|
+
def initialize(title, author, clippings=[])
|
8
|
+
@title = title
|
9
|
+
@author = author
|
10
|
+
@clippings = clippings.sort
|
11
|
+
end
|
12
|
+
|
13
|
+
def hash
|
14
|
+
title.hash ^ author.hash
|
15
|
+
end
|
16
|
+
|
17
|
+
def eql?(other)
|
18
|
+
self == other
|
19
|
+
end
|
20
|
+
|
21
|
+
def ==(other)
|
22
|
+
return false unless other.instance_of?(self.class)
|
23
|
+
title == other.title && author == other.author
|
24
|
+
end
|
25
|
+
|
26
|
+
def as_html
|
27
|
+
ERB.new(template, 0, '%<>').result(binding)
|
28
|
+
end
|
29
|
+
|
30
|
+
def template
|
31
|
+
@template ||= File.read(File.join(File.dirname(__FILE__), 'book_summary.erb'))
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/klipbook/cli.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
3
|
+
module Klipbook
|
4
|
+
class CLI < Thor
|
5
|
+
|
6
|
+
desc 'list CLIPPINGS_FILE', 'List the books in the clippings file'
|
7
|
+
def list(clippings_file=nil)
|
8
|
+
if (clippings_file.nil?)
|
9
|
+
puts 'Please provide a CLIPPINGS_FILE'
|
10
|
+
exit 1
|
11
|
+
end
|
12
|
+
|
13
|
+
clippings_file = ensure_clippings_file_exists(clippings_file)
|
14
|
+
|
15
|
+
Klipbook::Runner.new(clippings_file).list_books
|
16
|
+
end
|
17
|
+
|
18
|
+
desc 'summarise CLIPPINGS_FILE BOOK_NUMBER OUTPUT_FILE', 'Output an html summary of the clippings for a book'
|
19
|
+
def summarise(clippings_file=nil, book_number=nil, output_file=nil)
|
20
|
+
if (clippings_file.nil? or book_number.nil? or output_file.nil?)
|
21
|
+
puts 'Please provide a CLIPPINGS_FILE, BOOK_NUMBER, and OUTPUT_FILE'
|
22
|
+
exit 1
|
23
|
+
end
|
24
|
+
|
25
|
+
clippings_file = ensure_clippings_file_exists(clippings_file)
|
26
|
+
|
27
|
+
book_number = book_number.to_i
|
28
|
+
|
29
|
+
Klipbook::Runner.new(clippings_file).print_book_summary(book_number, File.open(output_file, 'w'))
|
30
|
+
end
|
31
|
+
|
32
|
+
map '-v' => :version
|
33
|
+
desc 'version', 'Display Klipbook version'
|
34
|
+
def version
|
35
|
+
puts "Klipbook #{Klipbook::VERSION}"
|
36
|
+
end
|
37
|
+
|
38
|
+
no_tasks do
|
39
|
+
def ensure_clippings_file_exists(clippings_file)
|
40
|
+
unless File.exist?(clippings_file)
|
41
|
+
$stderr.puts "Clippings file doesn't exist: #{clippings_file}"
|
42
|
+
exit 1
|
43
|
+
end
|
44
|
+
File.open(clippings_file, 'r')
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
module Klipbook
|
5
|
+
class Clipping < OpenStruct
|
6
|
+
def initialize(attributes)
|
7
|
+
super(attributes)
|
8
|
+
self.added_on = DateTime.strptime(self.added_on, '%A, %B %d, %Y, %I:%M %p') if self.added_on
|
9
|
+
end
|
10
|
+
|
11
|
+
def <=>(other)
|
12
|
+
(self.location || 0) <=> (other.location || 0)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Klipbook
|
2
|
+
class ClippingsFile
|
3
|
+
attr_accessor :books
|
4
|
+
|
5
|
+
def initialize(file_text, parser=Klipbook::ClippingsParser.new)
|
6
|
+
@books = extract_books_from_file_text(parser, file_text)
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def extract_books_from_file_text(parser, file_text)
|
12
|
+
clippings = parser.extract_clippings_from(file_text)
|
13
|
+
|
14
|
+
group_clippings_into_books(clippings)
|
15
|
+
end
|
16
|
+
|
17
|
+
def group_clippings_into_books(clippings)
|
18
|
+
books = clippings.inject({}) do |new_hash, clipping|
|
19
|
+
new_hash[hash_key_for_book(clipping)] ||= []
|
20
|
+
new_hash[hash_key_for_book(clipping)] << clipping
|
21
|
+
new_hash
|
22
|
+
end
|
23
|
+
|
24
|
+
books = sort_books_by_title(books)
|
25
|
+
books = sort_clippings_by_location(books)
|
26
|
+
build_book_summaries(books)
|
27
|
+
end
|
28
|
+
|
29
|
+
def build_book_summaries(books)
|
30
|
+
books.values.map { |clippings| Klipbook::BookSummary.new(clippings.first.title, clippings.first.author, clippings) }
|
31
|
+
end
|
32
|
+
|
33
|
+
def hash_key_for_book(clipping)
|
34
|
+
"#{clipping.title}#{clipping.author}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def sort_books_by_title(books)
|
38
|
+
Hash[books.sort]
|
39
|
+
end
|
40
|
+
|
41
|
+
def sort_clippings_by_location(books)
|
42
|
+
books.inject({}) do |new_hash, (k, v)|
|
43
|
+
new_hash[k] = v.sort
|
44
|
+
new_hash
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
|