klipbook 2.1.3 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +9 -0
- data/.ruby-version +1 -1
- data/.travis.yml +1 -1
- data/CHANGELOG.txt +11 -0
- data/Gemfile +2 -15
- data/Gemfile.lock +73 -113
- data/LICENSE.txt +18 -17
- data/README.md +63 -69
- data/Rakefile +3 -30
- data/bin/klipbook +3 -221
- data/klipbook.gemspec +32 -118
- data/lib/klipbook/cli.rb +69 -0
- data/lib/klipbook/commands/command.rb +39 -0
- data/lib/klipbook/commands/export.rb +50 -0
- data/lib/klipbook/commands/exporters/exporter.rb +49 -0
- data/lib/klipbook/{tohtml → commands/exporters}/html_book_summary.erb +1 -1
- data/lib/klipbook/commands/exporters/html_exporter.rb +25 -0
- data/lib/klipbook/commands/exporters/json_exporter.rb +17 -0
- data/lib/klipbook/commands/exporters/markdown_book_summary.erb +12 -0
- data/lib/klipbook/commands/exporters/markdown_exporter.rb +19 -0
- data/lib/klipbook/commands/list.rb +17 -0
- data/lib/klipbook/config.rb +18 -10
- data/lib/klipbook/logger.rb +15 -0
- data/lib/klipbook/sources/amazon_site/book_scraper.rb +0 -2
- data/lib/klipbook/sources/book.rb +36 -21
- data/lib/klipbook/sources/clipping.rb +10 -8
- data/lib/klipbook/sources/kindle_device/entry.rb +8 -6
- data/lib/klipbook/sources/kindle_device/entry_parser.rb +66 -64
- data/lib/klipbook/sources/kindle_device/file.rb +45 -43
- data/lib/klipbook/sources/kindle_device/file_parser.rb +23 -21
- data/lib/klipbook/sources/source.rb +30 -0
- data/lib/klipbook/version.rb +1 -1
- data/lib/klipbook.rb +11 -11
- metadata +56 -113
- data/.document +0 -5
- data/.yardopts +0 -1
- data/Guardfile +0 -19
- data/features/fixtures/clippings-for-three-books.txt +0 -105
- data/features/list.feature +0 -31
- data/features/step_definitions/list_steps.rb +0 -15
- data/features/step_definitions/tohtml_steps.rb +0 -61
- data/features/step_definitions/tojson_steps.rb +0 -17
- data/features/support/env.rb +0 -16
- data/features/tohtml.feature +0 -51
- data/features/tojson.feature +0 -11
- data/lib/klipbook/colours.rb +0 -16
- data/lib/klipbook/commands/list_books.rb +0 -19
- data/lib/klipbook/commands/tohtml.rb +0 -17
- data/lib/klipbook/commands/tojson.rb +0 -18
- data/lib/klipbook/sources/invalid_source_error.rb +0 -12
- data/lib/klipbook/tohtml/html_printer.rb +0 -39
- data/lib/klipbook/tojson/book_file.rb +0 -58
- data/spec/lib/klipbook/commands/list_books_spec.rb +0 -43
- data/spec/lib/klipbook/commands/tohtml_spec.rb +0 -36
- data/spec/lib/klipbook/sources/book_spec.rb +0 -33
- data/spec/lib/klipbook/sources/kindle_device/entry_parser_spec.rb +0 -339
- data/spec/lib/klipbook/sources/kindle_device/file_parser_spec.rb +0 -68
- data/spec/lib/klipbook/sources/kindle_device/file_spec.rb +0 -163
- data/spec/lib/klipbook/tohtml/html_printer_spec.rb +0 -88
- data/spec/lib/klipbook/tojson/book_file_spec.rb +0 -76
- data/spec/spec_helper.rb +0 -7
@@ -0,0 +1,17 @@
|
|
1
|
+
module Klipbook
|
2
|
+
module Commands
|
3
|
+
class List < Command
|
4
|
+
def run_command!(book_source, _options)
|
5
|
+
books = book_source.books
|
6
|
+
if books.empty?
|
7
|
+
logger.info 'No books available'
|
8
|
+
else
|
9
|
+
logger.info 'Book list:'
|
10
|
+
books.each_with_index do |book, index|
|
11
|
+
logger.info "[#{index + 1}] #{book.title_and_author}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/klipbook/config.rb
CHANGED
@@ -1,22 +1,30 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
1
3
|
module Klipbook
|
2
4
|
class Config
|
3
|
-
|
4
|
-
@config_file_name = config_file_name
|
5
|
-
end
|
5
|
+
DEFAULT_MAXBOOKS = 5
|
6
6
|
|
7
7
|
def read
|
8
|
-
merge_config_from_rc_file({
|
8
|
+
merge_config_from_rc_file({
|
9
|
+
count: DEFAULT_MAXBOOKS,
|
10
|
+
output_dir: Dir.pwd,
|
11
|
+
force: false
|
12
|
+
})
|
9
13
|
end
|
10
14
|
|
15
|
+
private
|
16
|
+
|
11
17
|
def merge_config_from_rc_file(config)
|
12
|
-
|
18
|
+
config.merge(file_config)
|
19
|
+
end
|
13
20
|
|
14
|
-
|
15
|
-
|
16
|
-
|
21
|
+
def file_config
|
22
|
+
config_file = File.expand_path("~/.klipbookrc")
|
23
|
+
if File.exist?(config_file)
|
24
|
+
YAML.load(File.read(config_file)) || {}
|
25
|
+
else
|
26
|
+
{}
|
17
27
|
end
|
18
|
-
|
19
|
-
config
|
20
28
|
end
|
21
29
|
end
|
22
30
|
end
|
@@ -1,28 +1,43 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
1
|
+
# coding: utf-8
|
2
|
+
module Klipbook
|
3
|
+
Book = Struct.new(:asin, :author, :title, :last_update, :clippings) do
|
4
|
+
def title_and_author
|
5
|
+
author_txt = author ? " by #{author}" : ''
|
6
|
+
"#{title}#{author_txt}"
|
7
|
+
end
|
6
8
|
|
7
|
-
|
8
|
-
|
9
|
-
|
9
|
+
def sorted_clippings
|
10
|
+
clippings.sort_by(&:location)
|
11
|
+
end
|
10
12
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
def get_binding
|
14
|
+
binding
|
15
|
+
end
|
16
|
+
|
17
|
+
def location_html(location)
|
18
|
+
if asin
|
19
|
+
"<a href=\"kindle://book?action=open&asin=#{asin}&location=#{location}\">loc #{location}</a>"
|
20
|
+
else
|
21
|
+
"loc #{location}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def location_markdown(location)
|
26
|
+
if asin
|
27
|
+
"[∞](kindle://book?action=open&asin=#{asin}&location=#{location})"
|
28
|
+
else
|
29
|
+
""
|
30
|
+
end
|
16
31
|
end
|
17
|
-
end
|
18
32
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
33
|
+
def self.from_hash(hash)
|
34
|
+
self.new.tap do |b|
|
35
|
+
b.asin = hash['asin']
|
36
|
+
b.author = hash['author']
|
37
|
+
b.title = hash['title']
|
38
|
+
b.last_update = hash['last_update']
|
39
|
+
b.clippings = hash['clippings'].map { |clip| Klipbook::Clipping.from_hash(clip) }
|
40
|
+
end
|
26
41
|
end
|
27
42
|
end
|
28
43
|
end
|
@@ -1,11 +1,13 @@
|
|
1
|
-
Klipbook
|
2
|
-
|
3
|
-
self.
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
1
|
+
module Klipbook
|
2
|
+
Clipping = Struct.new(:annotation_id, :text, :location, :type, :page) do
|
3
|
+
def self.from_hash(hash)
|
4
|
+
self.new.tap do |b|
|
5
|
+
b.annotation_id = hash['annotation_id']
|
6
|
+
b.text = hash['text']
|
7
|
+
b.location = hash['location']
|
8
|
+
b.type = hash['type']
|
9
|
+
b.page = hash['page']
|
10
|
+
end
|
9
11
|
end
|
10
12
|
end
|
11
13
|
end
|
@@ -1,10 +1,12 @@
|
|
1
|
-
module Klipbook
|
2
|
-
module
|
3
|
-
|
4
|
-
|
1
|
+
module Klipbook
|
2
|
+
module Sources
|
3
|
+
module KindleDevice
|
4
|
+
class Entry
|
5
|
+
attr_accessor :title, :author, :type, :location, :page, :added_on, :text
|
5
6
|
|
6
|
-
|
7
|
-
|
7
|
+
def initialize
|
8
|
+
yield self if block_given?
|
9
|
+
end
|
8
10
|
end
|
9
11
|
end
|
10
12
|
end
|
@@ -1,84 +1,86 @@
|
|
1
|
-
module Klipbook
|
2
|
-
module
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
1
|
+
module Klipbook
|
2
|
+
module Sources
|
3
|
+
module KindleDevice
|
4
|
+
class EntryParser
|
5
|
+
|
6
|
+
def build_entry(entry_text)
|
7
|
+
return nil if invalid_entry?(entry_text)
|
8
|
+
|
9
|
+
lines = split_text_into_lines(entry_text)
|
10
|
+
title_line = lines[0].strip
|
11
|
+
metadata = lines[1].strip
|
12
|
+
text_lines = lines[3..-1]
|
13
|
+
|
14
|
+
type = extract_type(metadata)
|
15
|
+
|
16
|
+
Klipbook::Sources::KindleDevice::Entry.new do |h|
|
17
|
+
h.title = extract_title(title_line)
|
18
|
+
h.author = extract_author(title_line)
|
19
|
+
h.location = extract_location(metadata)
|
20
|
+
h.page = extract_page(metadata)
|
21
|
+
h.added_on = extract_added_date(metadata)
|
22
|
+
h.text = extract_content(text_lines)
|
23
|
+
h.type = extract_type(metadata)
|
24
|
+
end
|
23
25
|
end
|
24
|
-
end
|
25
26
|
|
26
|
-
|
27
|
+
private
|
27
28
|
|
28
|
-
|
29
|
-
|
30
|
-
|
29
|
+
def invalid_entry?(entry_text)
|
30
|
+
entry_text.blank? || incomplete_entry?(entry_text)
|
31
|
+
end
|
31
32
|
|
32
|
-
|
33
|
-
|
34
|
-
|
33
|
+
def incomplete_entry?(entry_text)
|
34
|
+
split_text_into_lines(entry_text).length < 2
|
35
|
+
end
|
35
36
|
|
36
|
-
|
37
|
-
|
38
|
-
|
37
|
+
def split_text_into_lines(entry_text)
|
38
|
+
entry_text.lstrip.lines.to_a
|
39
|
+
end
|
39
40
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
41
|
+
def extract_title(title_line)
|
42
|
+
if title_line =~ /\(.+\)\Z/
|
43
|
+
title_line.scan(/(.*)\s+\(.+\)\Z/).first.first
|
44
|
+
else
|
45
|
+
title_line
|
46
|
+
end
|
45
47
|
end
|
46
|
-
end
|
47
48
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
49
|
+
def extract_author(title_line)
|
50
|
+
match = title_line.scan /\(([^\(]+)\)\Z/
|
51
|
+
match.empty? ? nil : match.first.first
|
52
|
+
end
|
52
53
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
54
|
+
def extract_type(metadata)
|
55
|
+
type = metadata.scan(/^-( Your)? (\w+)/).first[1]
|
56
|
+
type.downcase.to_sym
|
57
|
+
end
|
57
58
|
|
58
|
-
|
59
|
-
|
59
|
+
def extract_location(metadata)
|
60
|
+
match = metadata.scan(/Loc(ation|\.) ([0-9]+-?)/)
|
60
61
|
|
61
|
-
|
62
|
+
return 0 if match.empty?
|
62
63
|
|
63
|
-
|
64
|
-
|
65
|
-
|
64
|
+
location = match.first[1]
|
65
|
+
location.to_i
|
66
|
+
end
|
66
67
|
|
67
|
-
|
68
|
-
|
68
|
+
def extract_page(metadata)
|
69
|
+
match = metadata.scan(/Page (\d+)/)
|
69
70
|
|
70
|
-
|
71
|
+
return nil if match.empty?
|
71
72
|
|
72
|
-
|
73
|
-
|
74
|
-
|
73
|
+
location = match.first.first
|
74
|
+
location.to_i
|
75
|
+
end
|
75
76
|
|
76
|
-
|
77
|
-
|
78
|
-
|
77
|
+
def extract_content(text_lines)
|
78
|
+
text_lines.join('').rstrip
|
79
|
+
end
|
79
80
|
|
80
|
-
|
81
|
-
|
81
|
+
def extract_added_date(metadata)
|
82
|
+
DateTime.parse(metadata.scan(/Added on (.+)$/i).first.first)
|
83
|
+
end
|
82
84
|
end
|
83
85
|
end
|
84
86
|
end
|
@@ -1,54 +1,56 @@
|
|
1
|
-
module Klipbook
|
2
|
-
module
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
1
|
+
module Klipbook
|
2
|
+
module Sources
|
3
|
+
module KindleDevice
|
4
|
+
class File
|
5
|
+
def initialize(infile, max_books, file_parser=FileParser.new)
|
6
|
+
@file_text = infile.strip
|
7
|
+
@file_parser = file_parser
|
8
|
+
@max_books = max_books
|
9
|
+
end
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
# TODO Shift max books here
|
12
|
+
def books
|
13
|
+
@books ||= build_books.take(@max_books)
|
14
|
+
end
|
14
15
|
|
15
|
-
|
16
|
+
private
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
def build_books
|
19
|
+
sorted_entries = extract_sorted_entries_from_file_text
|
20
|
+
build_sorted_book_list(sorted_entries)
|
21
|
+
end
|
21
22
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
23
|
+
def extract_sorted_entries_from_file_text
|
24
|
+
entries = @file_parser.extract_entries(@file_text)
|
25
|
+
entries.sort { |entry_a, entry_b| entry_a.title <=> entry_b.title }
|
26
|
+
end
|
26
27
|
|
27
|
-
|
28
|
-
|
29
|
-
|
28
|
+
def build_sorted_book_list(sorted_entries)
|
29
|
+
books_from_entries(sorted_entries).sort do |book_a, book_b|
|
30
|
+
book_b.last_update <=> book_a.last_update
|
31
|
+
end
|
30
32
|
end
|
31
|
-
end
|
32
33
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
34
|
+
def books_from_entries(entries)
|
35
|
+
entries.select { |entry| entry.type != :bookmark }
|
36
|
+
.group_by(&:title)
|
37
|
+
.map { |title, book_entries| book_from_entries(book_entries) }
|
38
|
+
end
|
38
39
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
40
|
+
def book_from_entries(entries)
|
41
|
+
entries.sort! { |ea, eb| ea.location <=> eb.location }
|
42
|
+
|
43
|
+
Klipbook::Book.new.tap do |b|
|
44
|
+
b.title = entries.first.title
|
45
|
+
b.author = entries.first.author
|
46
|
+
b.last_update = entries.map(&:added_on).max
|
47
|
+
b.clippings = entries.map do |e|
|
48
|
+
Klipbook::Clipping.new.tap do |c|
|
49
|
+
c.location = e.location
|
50
|
+
c.page = e.page
|
51
|
+
c.text = e.text
|
52
|
+
c.type = e.type
|
53
|
+
end
|
52
54
|
end
|
53
55
|
end
|
54
56
|
end
|
@@ -1,32 +1,34 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
|
-
module Klipbook
|
4
|
-
module
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
3
|
+
module Klipbook
|
4
|
+
module Sources
|
5
|
+
module KindleDevice
|
6
|
+
class FileParser
|
7
|
+
def initialize(entry_parser=EntryParser.new)
|
8
|
+
@entry_parser = entry_parser
|
9
|
+
end
|
9
10
|
|
10
|
-
|
11
|
-
|
11
|
+
def extract_entries(file_text)
|
12
|
+
entries_text = split_into_raw_entries_text(file_text)
|
12
13
|
|
13
|
-
|
14
|
-
|
14
|
+
build_entries(entries_text)
|
15
|
+
end
|
15
16
|
|
16
|
-
|
17
|
+
private
|
17
18
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
19
|
+
def build_entries(entries_text)
|
20
|
+
entries_text.map do |entry_text|
|
21
|
+
@entry_parser.build_entry(entry_text)
|
22
|
+
end.compact
|
23
|
+
end
|
23
24
|
|
24
|
-
|
25
|
-
|
26
|
-
|
25
|
+
def strip_control_characters(file_text)
|
26
|
+
file_text.gsub("\r", '').gsub("\xef\xbb\xbf", '')
|
27
|
+
end
|
27
28
|
|
28
|
-
|
29
|
-
|
29
|
+
def split_into_raw_entries_text(file_text)
|
30
|
+
strip_control_characters(file_text).split('==========')
|
31
|
+
end
|
30
32
|
end
|
31
33
|
end
|
32
34
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Klipbook
|
2
|
+
module Sources
|
3
|
+
class Source
|
4
|
+
def self.build(options)
|
5
|
+
if options.from_file
|
6
|
+
file_source(options.from_file, options.count)
|
7
|
+
elsif options.from_site
|
8
|
+
site_source(options.from_site, options.count)
|
9
|
+
else
|
10
|
+
raise "Unknown source type"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.site_source(credentials, max_books)
|
15
|
+
unless credentials =~ /(.+):(.+)/
|
16
|
+
logger.error "Error: your credentials need to be in username:password format."
|
17
|
+
exit 127
|
18
|
+
end
|
19
|
+
|
20
|
+
username = $1
|
21
|
+
password = $2
|
22
|
+
Sources::AmazonSite::SiteScraper.new(username, password, max_books)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.file_source(file, max_books)
|
26
|
+
Sources::KindleDevice::File.new(File.read(file), max_books)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/klipbook/version.rb
CHANGED
data/lib/klipbook.rb
CHANGED
@@ -1,16 +1,21 @@
|
|
1
|
-
|
1
|
+
require 'klipbook/cli'
|
2
2
|
require 'klipbook/util/blank'
|
3
3
|
require 'klipbook/util/struct_to_json'
|
4
4
|
require 'klipbook/version'
|
5
|
-
require 'klipbook/
|
5
|
+
require 'klipbook/config'
|
6
|
+
require 'klipbook/logger'
|
6
7
|
|
7
|
-
require 'klipbook/commands/
|
8
|
-
require 'klipbook/commands/
|
9
|
-
require 'klipbook/commands/
|
8
|
+
require 'klipbook/commands/command'
|
9
|
+
require 'klipbook/commands/list'
|
10
|
+
require 'klipbook/commands/export'
|
11
|
+
require 'klipbook/commands/exporters/exporter'
|
12
|
+
require 'klipbook/commands/exporters/json_exporter'
|
13
|
+
require 'klipbook/commands/exporters/html_exporter'
|
14
|
+
require 'klipbook/commands/exporters/markdown_exporter'
|
10
15
|
|
16
|
+
require 'klipbook/sources/source'
|
11
17
|
require 'klipbook/sources/book'
|
12
18
|
require 'klipbook/sources/clipping'
|
13
|
-
require 'klipbook/sources/invalid_source_error'
|
14
19
|
|
15
20
|
require 'klipbook/sources/kindle_device/file_parser'
|
16
21
|
require 'klipbook/sources/kindle_device/entry_parser'
|
@@ -19,8 +24,3 @@ require 'klipbook/sources/kindle_device/file'
|
|
19
24
|
|
20
25
|
require 'klipbook/sources/amazon_site/site_scraper'
|
21
26
|
require 'klipbook/sources/amazon_site/book_scraper'
|
22
|
-
|
23
|
-
require 'klipbook/config'
|
24
|
-
|
25
|
-
require 'klipbook/tohtml/html_printer'
|
26
|
-
require 'klipbook/tojson/book_file'
|