klipbook 2.1.3 → 3.0.0

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