klipbook 2.1.3 → 3.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 +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'
|