fyodor 0.2.10 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 02a743dcc50a2c528b31aa2d4208cb4f220cb7ee5ee7a1ab19d36ee701c38de5
4
- data.tar.gz: 005baf6cf829812dc8f9ab1d9f5d2761ff3a610e607e11abed66e507f6eee649
3
+ metadata.gz: 7fa1b1e78cc0ab15159e69a7f63c51ff94d549a618059395d29401ddc16ff45c
4
+ data.tar.gz: 325bfb536a54858e628a81094b707f7cd3f0be83d2cf613cf6b8b8de79b33015
5
5
  SHA512:
6
- metadata.gz: d6b6b7784323ac398eb36a4321d23a558c632246d2a5199dec38a28bd22a60fc77b93b76a31f95586ce27aac34538c74da6332fc96b84d61ad1b8f104eba56de
7
- data.tar.gz: 0b6f4ab38b109fc7d64914d8b90ba45d32185b625c66f0099abb95299c38c639d788ff552c6b412c11f6ddf656d7a6cd6e9bb1109d5a6a22b25b7a6ad9021bc5
6
+ metadata.gz: 4e5f8277ece0aa866131788f2702a1dc137cea14d86795e2c71c74947d9c532482b2bc0a89e34d7500af69a15642a774ce3a815882f835b4ce34cd155ec96af2
7
+ data.tar.gz: d8a7b83be1154a5385f8870e94f8973de8f8b744ffb09122d55dae9282b074ede788fba4ca1eccb9d1263786ad478c16d6d02a6bd1466e42aab603ed4fe22d53
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Fyodor
2
2
 
3
- Convert your Amazon Kindle highlights, notes and bookmarks into markdown files.
3
+ Convert your Amazon Kindle highlights, notes and bookmarks into markdown (or any format).
4
4
 
5
5
  ## What is Fyodor
6
6
 
@@ -17,8 +17,9 @@ To read more about the motivation and what problem it tries to solve, [check thi
17
17
  - Orders your entries by location/page on each book (the clippings file is ordered by time).
18
18
  - Easily configurable for your language, allowing you to get all features and beautiful output.
19
19
  - This software goes some length to be locale agnostic: basic parsing should work without configuration for any language. It should also work even if your clippings file has multiple locales.
20
- - Bookmarks are printed together and notes are formatted differently, for better visualization.
21
- - Output in a format that is clean and easy to edit/fiddle around: markdown.
20
+ - By default, output in a format that is clean and easy to edit/fiddle around: markdown.
21
+ - Bookmarks are placed together and notes are formatted differently, for better visualization.
22
+ - Entirely customizable output, in the format you prefer, through templates.
22
23
 
23
24
  This program is based on the clippings file generated by Kindle 2019, but should work with other models.
24
25
 
@@ -33,7 +34,7 @@ We are limited by the data Kindle makes available through `My Clippings.txt`. Th
33
34
 
34
35
  Install Ruby and run:
35
36
 
36
- ```
37
+ ```shell-session
37
38
  $ gem install fyodor
38
39
  ```
39
40
 
@@ -41,29 +42,29 @@ $ gem install fyodor
41
42
 
42
43
  Run:
43
44
 
44
- ```
45
+ ```shell-session
45
46
  $ gem update fyodor
46
47
  ```
47
48
 
48
49
  ## Configuration
49
50
 
50
- Fyodor has an optional configuration file, which is used for the following.
51
-
52
- ### Languages
53
-
54
- If your Kindle is not in English, you should tell Fyodor how some things are called by your `My Clippings.txt` (e.g. highlights, pages, etc). _Fyodor should still work without configuration, but you won't take advantage of many features, resulting in a dirtier output._
51
+ Fyodor reads an optional configuration file at `~/.config/fyodor/fyodor.toml` or `$XDG_CONFIG_HOME/fyodor/fyodor.toml`. This section describes the available parameters (none is required).
55
52
 
56
- 1. Download the sample config to `~/.config/fyodor/fyodor.toml` or `$XDG_CONFIG_HOME/fyodor/fyodor.toml`:
53
+ To download the [sample configuration](docs/fyodor.toml.sample):
57
54
 
58
- ```sh
55
+ ```shell-session
59
56
  $ curl https://raw.githubusercontent.com/rc2dev/fyodor/master/docs/fyodor.toml.sample --create-dirs -o ~/.config/fyodor/fyodor.toml
60
57
  ```
61
58
 
62
- 2. Open both the configuration and your `My Clippings.txt` in your preferred editor. Change the values in the `[parser]` section to mirror what you get in `My Clippings.txt`.
59
+ ### Languages
60
+
61
+ If your Kindle device is not set to English (US), you should tell Fyodor how some things are named on your `My Clippings.txt` (highlights, pages, etc). I went through some length to make Fyodor work regardless of this step. However, if you don't set this correctly, you won't take advantage of many features, resulting in a dirtier output.
62
+
63
+ Open both `fyodor.toml` and your `My Clippings.txt` in your preferred editor. Change the values in the `[parser]` section to mirror what you get in `My Clippings.txt`.
63
64
 
64
65
  For example, this is the configuration for Brazilian Portuguese:
65
66
 
66
- ```
67
+ ```toml
67
68
  [parser]
68
69
  highlight = "Seu destaque"
69
70
  note = "Sua nota"
@@ -76,18 +77,41 @@ time = "Adicionado:"
76
77
 
77
78
  ### Showing the time
78
79
 
79
- In the configuration file you can also set whether to print the time of each entry. On `[output]`, set `time` to `true` or `false`.
80
+ You can also set whether to print the time of each entry. Under `[output]`, set `time` to `true` or `false`:
80
81
 
81
- ## Usage
82
+ ```toml
83
+ [output]
84
+ time = true
85
+ ```
82
86
 
87
+ ### Extension
88
+
89
+ If you want to change the extension of the output files (typically after changing the template), set `extension` under `[output]`. For example, to change it to HTML:
90
+
91
+ ```toml
92
+ [output]
93
+ extension = "html"
83
94
  ```
95
+
96
+ ## Templating
97
+
98
+ You may change the structure of the files output by Fyodor by providing your own template in any format you wish.
99
+
100
+ To do that, place a ERB template at `~/.config/fyodor/template.erb` or `$XDG_CONFIG_HOME/fyodor/template.erb` and Fyodor will use it automatically.
101
+
102
+ The default template can be found [here](share/template.erb). You can use any method or attribute available [on this class](lib/fyodor/output_generator.rb).
103
+
104
+
105
+ ## Usage
106
+
107
+ ```shell-session
84
108
  $ fyodor CLIPPINGS_FILE [OUTPUT_DIR]
85
109
  ```
86
110
 
87
111
  Where:
88
112
 
89
113
  - `CLIPPINGS_FILE` is the path for `My Clippings.txt`.
90
- - `OUTPUT_DIR` is the directory to write the markdown files. If none is supplied, this will be `fyodor_output` under the current directory.
114
+ - `OUTPUT_DIR` is the directory to write the output files. If none is supplied, this will be `fyodor_output` under the current directory.
91
115
 
92
116
  ## PSA: HTML to markdown
93
117
 
@@ -105,4 +129,4 @@ If you like Fyodor, you can show your support here:
105
129
 
106
130
  Licensed under [GPLv3](LICENSE)
107
131
 
108
- Copyright (C) 2019-2020 [Rafael Cavalcanti](https://rafaelc.org/)
132
+ Copyright (C) 2019-2022 [Rafael Cavalcanti](https://rafaelc.org/dev)
data/lib/fyodor/book.rb CHANGED
@@ -15,7 +15,7 @@ module Fyodor
15
15
 
16
16
  @title = title
17
17
  @author = author
18
- @entries = SortedSet.new
18
+ @entries = Set.new
19
19
  @rej_dup = 0
20
20
  end
21
21
 
@@ -26,8 +26,8 @@ module Fyodor
26
26
  end
27
27
 
28
28
  def basename
29
- base = @author.to_s.empty? ? @title : "#{@author} - #{@title}"
30
- base.strip.gsub(/[?*:|\/"<>]/,"_")
29
+ base = @author.to_s.empty? ? "Author N/A - #{@title}" : "#{@author} - #{@title}"
30
+ base.strip
31
31
  end
32
32
 
33
33
  def count_types
@@ -4,7 +4,9 @@ require "toml"
4
4
 
5
5
  module Fyodor
6
6
  class ConfigGetter
7
- DEFAULT = {
7
+ DEFAULT_TEMPLATE_PATH = File.dirname(__FILE__) + "/../../share/template.erb"
8
+
9
+ DEFAULT_CONFIG = {
8
10
  "parser" => {
9
11
  "highlight" => "Your Highlight",
10
12
  "note" => "Your Note",
@@ -15,43 +17,54 @@ module Fyodor
15
17
  "time" => "Added on"
16
18
  },
17
19
  "output" => {
18
- "time" => false
20
+ "time" => false,
21
+ "extension" => "md"
19
22
  }
20
23
  }
21
24
 
22
25
  def config
23
- @config ||= get_config
26
+ return @config if defined?(@config)
27
+
28
+ Hash.include CoreExtensions::Hash::Merging
29
+ config = DEFAULT_CONFIG.deep_merge(user_config)
30
+ config["output"]["template"] = template
31
+
32
+ @config = config
24
33
  end
25
34
 
26
35
 
27
36
  private
28
37
 
29
- def get_config
30
- Hash.include CoreExtensions::Hash::Merging
31
- print_path
38
+ def config_dir
39
+ @config_dir ||= Pathname.new(ENV["XDG_CONFIG_HOME"] || "~/.config").expand_path + "fyodor"
40
+ end
32
41
 
33
- user_config = path.nil? ? {} : TOML.load_file(path)
34
- DEFAULT.deep_merge(user_config)
42
+ def user_config_path
43
+ config_dir + "fyodor.toml"
35
44
  end
36
45
 
37
- def path
38
- @path ||= paths.find { |path| path.exist? }
46
+ def user_template_path
47
+ config_dir + "template.erb"
39
48
  end
40
49
 
41
- def paths
42
- return @paths unless @paths.nil?
50
+ def user_config
51
+ if user_config_path.exist?
52
+ puts "Using config at #{user_config_path}.\n"
53
+ return TOML.load_file(user_config_path)
54
+ end
43
55
 
44
- @paths = []
45
- @paths << Pathname.new(ENV["XDG_CONFIG_HOME"]) + "fyodor/fyodor.toml" unless ENV["XDG_CONFIG_HOME"].nil?
46
- @paths << Pathname.new("~/.config/fyodor/fyodor.toml").expand_path
56
+ puts "No config found: using defaults.\n"
57
+ {}
47
58
  end
48
59
 
49
- def print_path
50
- if path.nil?
51
- puts "No config found: using defaults.\n\n"
52
- else
53
- puts "Using config at #{path}\n\n"
60
+ def template
61
+ if user_template_path.exist?
62
+ puts "Using custom template at #{user_template_path}.\n\n"
63
+ return File.read(user_template_path)
54
64
  end
65
+
66
+ puts "No custom template found: using default.\n\n"
67
+ File.read(DEFAULT_TEMPLATE_PATH)
55
68
  end
56
69
  end
57
70
  end
data/lib/fyodor/entry.rb CHANGED
@@ -48,20 +48,18 @@ module Fyodor
48
48
 
49
49
  # Override this method to use a SortedSet.
50
50
  def <=>(other)
51
- return @page_start <=> other.page_start if @loc_start == 0
51
+ return (@page_start <=> other.page_start) if @loc_start == 0
52
52
 
53
53
  @loc_start <=> other.loc_start
54
54
  end
55
55
 
56
56
  # Override the following methods for deduplication.
57
57
  def ==(other)
58
- return false if @type != other.type || @text != other.text
58
+ return false if (@type != other.type || @text != other.text)
59
59
 
60
- if desc_parsed? && other.desc_parsed?
61
- @loc == other.loc && @page == other.page
62
- else
63
- @desc == other.desc
64
- end
60
+ return (@loc == other.loc && @page == other.page) if desc_parsed? && other.desc_parsed?
61
+
62
+ @desc == other.desc
65
63
  end
66
64
 
67
65
  alias eql? ==
@@ -0,0 +1,72 @@
1
+ require "fyodor/strings"
2
+ require "erb"
3
+
4
+ module Fyodor
5
+ class OutputGenerator
6
+ include Strings
7
+
8
+ def initialize(book, config)
9
+ @book = book
10
+ @config = config
11
+ end
12
+
13
+ def content
14
+ ERB.new(@config["template"], nil, '-').result(binding)
15
+ end
16
+
17
+
18
+ private
19
+
20
+ def regular_entries
21
+ @book.reject { |entry| entry.type == Entry::TYPE[:bookmark] }
22
+ end
23
+
24
+ def bookmarks
25
+ @book.select { |entry| entry.type == Entry::TYPE[:bookmark] }
26
+ end
27
+
28
+ def render_entries(entries)
29
+ output = ""
30
+ entries.sort.each do |entry|
31
+ output += "- #{item_text(entry)}\n\n"
32
+ output += " #{item_desc(entry)}\n\n" unless item_desc(entry).empty?
33
+ end
34
+ output.strip
35
+ end
36
+
37
+ def item_text(entry)
38
+ case entry.type
39
+ when Entry::TYPE[:bookmark]
40
+ "#{page(entry)}"
41
+ when Entry::TYPE[:note]
42
+ "_Note:_ #{entry.text.strip}"
43
+ else
44
+ "#{entry.text.strip}"
45
+ end
46
+ end
47
+
48
+ def item_desc(entry)
49
+ return entry.desc unless entry.desc_parsed?
50
+
51
+ case entry.type
52
+ when Entry::TYPE[:bookmark]
53
+ time(entry)
54
+ else
55
+ (type(entry) + " @ " + page(entry) + " " + time(entry)).strip
56
+ end
57
+ end
58
+
59
+ def page(entry)
60
+ ((entry.page.nil? ? "" : "page #{entry.page}, ") +
61
+ (entry.loc.nil? ? "" : "loc. #{entry.loc}")).delete_suffix(", ")
62
+ end
63
+
64
+ def time(entry)
65
+ @config["time"] ? "[#{entry.time}]" : ""
66
+ end
67
+
68
+ def type(entry)
69
+ SINGULAR[entry.type]
70
+ end
71
+ end
72
+ end
@@ -1,4 +1,4 @@
1
- require "fyodor/md_generator"
1
+ require "fyodor/output_generator"
2
2
 
3
3
  module Fyodor
4
4
  class OutputWriter
@@ -12,7 +12,7 @@ module Fyodor
12
12
  def write_all
13
13
  puts "\nWriting to #{@output_dir}..." unless @library.empty?
14
14
  @library.each do |book|
15
- content = MdGenerator.new(book, @config).content
15
+ content = OutputGenerator.new(book, @config).content
16
16
  File.open(path(book), "w") { |f| f.puts(content) }
17
17
  end
18
18
  end
@@ -21,11 +21,13 @@ module Fyodor
21
21
  private
22
22
 
23
23
  def path(book)
24
- path = @output_dir + "#{book.basename}.md"
24
+ basename = book.basename.gsub(/[?*:|\/"<>]/,"_")
25
+ extension = @config["extension"]
26
+ path = @output_dir + "#{basename}.#{extension}"
25
27
 
26
28
  i = 2
27
29
  while(path.exist?)
28
- path = @output_dir + "#{book.basename} - #{i}.md"
30
+ path = @output_dir + "#{basename} - #{i}.#{extension}"
29
31
  i += 1
30
32
  end
31
33
 
@@ -1,3 +1,3 @@
1
1
  module Fyodor
2
- VERSION = "0.2.10".freeze
2
+ VERSION = "0.3.3".freeze
3
3
  end
@@ -0,0 +1,16 @@
1
+ <%# Fyodor's default template. -%>
2
+ # <%= @book.basename %>
3
+
4
+ ## Highlights and notes
5
+
6
+ <% if regular_entries.size > 0 -%>
7
+ <%= render_entries(regular_entries) %>
8
+ <% else -%>
9
+ None.
10
+ <% end -%>
11
+
12
+ <% if bookmarks.size > 0 -%>
13
+ ## Bookmarks
14
+
15
+ <%= render_entries(bookmarks) %>
16
+ <% end -%>
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fyodor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.10
4
+ version: 0.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rafael Cavalcanti
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-11-23 00:00:00.000000000 Z
11
+ date: 2022-02-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: toml
@@ -38,7 +38,7 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '3.0'
41
- description: Parse Kindle clippings into markdown files
41
+ description: Parse Kindle clippings into markdown (or any format)
42
42
  email: dev@rafaelc.org
43
43
  executables:
44
44
  - fyodor
@@ -56,11 +56,12 @@ files:
56
56
  - lib/fyodor/entry.rb
57
57
  - lib/fyodor/entry_parser.rb
58
58
  - lib/fyodor/library.rb
59
- - lib/fyodor/md_generator.rb
59
+ - lib/fyodor/output_generator.rb
60
60
  - lib/fyodor/output_writer.rb
61
61
  - lib/fyodor/stats_printer.rb
62
62
  - lib/fyodor/strings.rb
63
63
  - lib/fyodor/version.rb
64
+ - share/template.erb
64
65
  homepage: https://rafaelc.org/fyodor
65
66
  licenses:
66
67
  - GPL-3.0-only
@@ -77,14 +78,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
77
78
  version: '2.5'
78
79
  - - "<"
79
80
  - !ruby/object:Gem::Version
80
- version: '3'
81
+ version: '4'
81
82
  required_rubygems_version: !ruby/object:Gem::Requirement
82
83
  requirements:
83
84
  - - ">="
84
85
  - !ruby/object:Gem::Version
85
86
  version: '0'
86
87
  requirements: []
87
- rubygems_version: 3.1.2
88
+ rubygems_version: 3.2.3
88
89
  signing_key:
89
90
  specification_version: 4
90
91
  summary: Kindle clippings parser
@@ -1,96 +0,0 @@
1
- require "fyodor/strings"
2
-
3
- module Fyodor
4
- class MdGenerator
5
- include Strings
6
-
7
- def initialize(book, config)
8
- @book = book
9
- @config = config
10
- end
11
-
12
- def content
13
- header + body + bookmarks
14
- end
15
-
16
-
17
- private
18
-
19
- def header
20
- return <<~EOF
21
- # #{@book.title}
22
- #{"by #{@book.author}" unless @book.author.to_s.empty?}
23
-
24
- #{header_counts}
25
-
26
- EOF
27
- end
28
-
29
- def header_counts
30
- output = ""
31
- @book.count_types.each do |type, n|
32
- output += "#{n} #{pluralize(type, n)}, " if n > 0
33
- end
34
- output.delete_suffix(", ")
35
- end
36
-
37
- def pluralize(type, n)
38
- n == 1 ? SINGULAR[type] : PLURAL[type]
39
- end
40
-
41
- def body
42
- entries = @book.reject { |entry| entry.type == Entry::TYPE[:bookmark] }
43
- entries.size == 0 ? "" : entries_render(entries)
44
- end
45
-
46
- def bookmarks
47
- bookmarks = @book.select { |entry| entry.type == Entry::TYPE[:bookmark] }
48
- bookmarks.size == 0 ? "" : entries_render(bookmarks, "Bookmarks")
49
- end
50
-
51
- def entries_render(entries, title=nil)
52
- output = "---\n\n"
53
- output += "## #{title}\n\n" unless title.nil?
54
- entries.each do |entry|
55
- output += "#{item_text(entry)}\n\n"
56
- output += "<p style=\"text-align: right;\"><sup>#{item_desc(entry)}</sup></p>\n\n"
57
- end
58
- output
59
- end
60
-
61
- def item_text(entry)
62
- case entry.type
63
- when Entry::TYPE[:bookmark]
64
- "* #{page(entry)}"
65
- when Entry::TYPE[:note]
66
- "* _Note:_\n#{entry.text.strip}"
67
- else
68
- "* #{entry.text.strip}"
69
- end
70
- end
71
-
72
- def item_desc(entry)
73
- return entry.desc unless entry.desc_parsed?
74
-
75
- case entry.type
76
- when Entry::TYPE[:bookmark]
77
- time(entry)
78
- else
79
- (type(entry) + " @ " + page(entry) + " " + time(entry)).strip
80
- end
81
- end
82
-
83
- def page(entry)
84
- ((entry.page.nil? ? "" : "page #{entry.page}, ") +
85
- (entry.loc.nil? ? "" : "loc. #{entry.loc}")).delete_suffix(", ")
86
- end
87
-
88
- def time(entry)
89
- @config["time"] ? "[#{entry.time}]" : ""
90
- end
91
-
92
- def type(entry)
93
- SINGULAR[entry.type]
94
- end
95
- end
96
- end