fyodor 0.2.7 → 0.3.0

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: 88110cfdbb8c0f625d97c460e6b087f1f00b0efa2bc54f244254e9e90dd17102
4
- data.tar.gz: '097449b93162d2be858b05942e41b05904dfdf464c7f5fbc1fc70fa725fbb992'
3
+ metadata.gz: 928a21f90c52229824428fa62a99e0ccd1b3319c954aa9f89696833933920315
4
+ data.tar.gz: ceedbb0b474de8f63f72fb4388f1e4eaf2a540644a61e26ed5a7616c0def2dbc
5
5
  SHA512:
6
- metadata.gz: 265349bddd3c8c4445d8d65684eaacb24cc49d2089842708cfb9d92b748fd3c226e62509d17e2b374cc2d1cbc1682d58be1770bf10a738fa4defeb4a1928bab9
7
- data.tar.gz: e6dd20487302828751669be11669f75bb536439321c292b72f12f390f9c388f3c3ba90ebbab0ce659a6179a5342fc95fbda20feeb3fa832e376c732b35472a65
6
+ metadata.gz: 44a7b6488f71988cdbfdb0b35083ae3e156e66899eee560188fc11da7127ff190edc60e8d9f22b3fab97cc96462efdc7732dbf905ce22b6cf87af14f5b41e61e
7
+ data.tar.gz: 53e882dd8ea580dd5657d088b064a00b45f86b145b8fbf403c0c84bbd9de5d7549933345b8291a7a2bc73260d5a3a5e98f711fd7b235953a2f554f394022f89d
data/README.md CHANGED
@@ -8,7 +8,7 @@ This application parses `My Clippings.txt` from your Kindle and generates a mark
8
8
 
9
9
  [For samples of the output, click here.](docs/output_demo)
10
10
 
11
- To read more about the motivation and what problem it tries to solve, [check this blog post](http://rafaelc.org/blog/export-all-your-kindle-highlights-and-notes/).
11
+ To read more about the motivation and what problem it tries to solve, [check this blog post](https://rafaelc.org/posts/export-all-your-kindle-highlights-and-notes/).
12
12
 
13
13
  ## Features
14
14
 
@@ -22,6 +22,13 @@ To read more about the motivation and what problem it tries to solve, [check thi
22
22
 
23
23
  This program is based on the clippings file generated by Kindle 2019, but should work with other models.
24
24
 
25
+ ## Limitations
26
+
27
+ We are limited by the data Kindle makes available through `My Clippings.txt`. This means:
28
+
29
+ - We don't have chapter information.
30
+ - We can’t guess all entries that were deleted.
31
+
25
32
  ## Installation
26
33
 
27
34
  Install Ruby and run:
@@ -46,10 +53,10 @@ Fyodor has an optional configuration file, which is used for the following.
46
53
 
47
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._
48
55
 
49
- 1. Download the sample config to `~/.config/fyodor.toml` or `$XDG_CONFIG_HOME/fyodor.toml`:
56
+ 1. Download the sample config to `~/.config/fyodor/fyodor.toml` or `$XDG_CONFIG_HOME/fyodor/fyodor.toml`:
50
57
 
51
- ```
52
- $ wget https://raw.githubusercontent.com/rccavalcanti/fyodor/master/docs/fyodor.toml.sample -O ~/.config/fyodor.toml
58
+ ```sh
59
+ $ curl https://raw.githubusercontent.com/rc2dev/fyodor/master/docs/fyodor.toml.sample --create-dirs -o ~/.config/fyodor/fyodor.toml
53
60
  ```
54
61
 
55
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`.
@@ -71,6 +78,24 @@ time = "Adicionado:"
71
78
 
72
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`.
73
80
 
81
+ ### Templating
82
+
83
+ If you want to change the structure of the files output by Fyodor, you can use custom templates.
84
+
85
+ It should be a ERB template and placed at `~/.config/fyodor/template.erb` or `$XDG_CONFIG_HOME/fyodor/template.erb`. Fyodor will find and use it automatically.
86
+
87
+ You can find a sample close to the default [here](docs/template.erb.sample). You can use any method or attribute available [at this class](lib/fyodor/output_generator.rb).
88
+
89
+
90
+ ### Extension
91
+
92
+ If you want to change the extension of the output files - typically after changing the template -, set `extension` on fyodor.toml. For example, to change it to HTML:
93
+
94
+ ```toml
95
+ [output]
96
+ extension = "html"
97
+ ```
98
+
74
99
  ## Usage
75
100
 
76
101
  ```
@@ -82,14 +107,20 @@ Where:
82
107
  - `CLIPPINGS_FILE` is the path for `My Clippings.txt`.
83
108
  - `OUTPUT_DIR` is the directory to write the markdown files. If none is supplied, this will be `fyodor_output` under the current directory.
84
109
 
110
+ ## PSA: HTML to markdown
111
+
112
+ Did you export your annotations to HTML using the Kindle mobile app?
113
+
114
+ You can convert it to markdown with [a script I wrote specifically for it](https://rafaelc.org/k/kindle2md).
115
+
85
116
  ## Buy me a coffee
86
117
 
87
118
  If you like Fyodor, you can show your support here:
88
119
 
89
- <a href="https://www.buymeacoffee.com/rafaelc" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-orange.png" alt="Buy Me A Coffee" width="200" ></a>
120
+ <a href="https://rafaelc.org/coffee" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-orange.png" alt="Buy Me A Coffee" width="200" ></a>
90
121
 
91
122
  ## License
92
123
 
93
124
  Licensed under [GPLv3](LICENSE)
94
125
 
95
- Copyright (C) 2019-2020 [Rafael Cavalcanti](https://rafaelc.org/)
126
+ Copyright (C) 2019-2022 [Rafael Cavalcanti](https://rafaelc.org/dev)
data/lib/fyodor/book.rb CHANGED
@@ -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,10 @@ require "toml"
4
4
 
5
5
  module Fyodor
6
6
  class ConfigGetter
7
- DEFAULT = {
7
+ CONFIG_NAME = "fyodor.toml"
8
+ TEMPLATE_NAME = "template.erb"
9
+
10
+ DEFAULTS = {
8
11
  "parser" => {
9
12
  "highlight" => "Your Highlight",
10
13
  "note" => "Your Note",
@@ -15,7 +18,8 @@ module Fyodor
15
18
  "time" => "Added on"
16
19
  },
17
20
  "output" => {
18
- "time" => false
21
+ "time" => false,
22
+ "extension" => "md"
19
23
  }
20
24
  }
21
25
 
@@ -28,29 +32,49 @@ module Fyodor
28
32
 
29
33
  def get_config
30
34
  Hash.include CoreExtensions::Hash::Merging
31
- print_path
32
35
 
33
- user_config = path.nil? ? {} : TOML.load_file(path)
34
- DEFAULT.deep_merge(user_config)
36
+ @config_path = get_path(CONFIG_NAME)
37
+ print_config_path
38
+ user_config = @config_path.nil? ? {} : TOML.load_file(@config_path)
39
+
40
+ config = DEFAULTS.deep_merge(user_config)
41
+ @template_path = get_path(TEMPLATE_NAME)
42
+ config["output"]["template_path"] = @template_path
43
+ print_template_path
44
+
45
+ config
35
46
  end
36
47
 
37
- def path
38
- @path ||= paths.find { |path| path.exist? }
48
+ def get_path(name)
49
+ possible_dirs.each do |d|
50
+ path = d + name
51
+ return path if path.exist?
52
+ end
53
+
54
+ return nil
39
55
  end
40
56
 
41
- def paths
42
- return @paths unless @paths.nil?
57
+ def possible_dirs
58
+ return @possible_dirs unless @possible_dirs.nil?
43
59
 
44
- @paths = []
45
- @paths << Pathname.new(ENV["XDG_CONFIG_HOME"]) + "fyodor.toml" unless ENV["XDG_CONFIG_HOME"].nil?
46
- @paths << Pathname.new("~/.config/fyodor.toml").expand_path
60
+ @possible_dirs = []
61
+ @possible_dirs << Pathname.new(ENV["XDG_CONFIG_HOME"]) + "fyodor" unless ENV["XDG_CONFIG_HOME"].nil?
62
+ @possible_dirs << Pathname.new("~/.config/fyodor").expand_path
63
+ end
64
+
65
+ def print_config_path
66
+ if @config_path.nil?
67
+ puts "No config found: using defaults.\n"
68
+ else
69
+ puts "Using config at #{@config_path}.\n"
70
+ end
47
71
  end
48
72
 
49
- def print_path
50
- if path.nil?
51
- puts "No config found: using defaults.\n\n"
73
+ def print_template_path
74
+ if @template_path.nil?
75
+ puts "No template found: using default.\n\n"
52
76
  else
53
- puts "Using config at #{path}\n\n"
77
+ puts "Using template at #{@template_path}.\n\n"
54
78
  end
55
79
  end
56
80
  end
@@ -0,0 +1,93 @@
1
+ require "fyodor/strings"
2
+ require "erb"
3
+
4
+ module Fyodor
5
+ class OutputGenerator
6
+ include Strings
7
+
8
+ # The use of <% - 1 %> is a workaround for trimming indentation before <%=.
9
+ DEFAULT_TEMPLATE = %q{<% -%>
10
+ <%- 1 %><%= "# #{@book.basename}" %>
11
+ <% if regular_entries.size > 0 %>
12
+ <%- 1 %><%= "## Highlights and notes" %>
13
+
14
+ <%- 1 %><%= render_entries(regular_entries) %>
15
+ <% end -%>
16
+ <% if bookmarks.size > 0 %>
17
+ <%- 1 %><%= "## Bookmarks" %>
18
+
19
+ <%- 1 %><%= render_entries(bookmarks) %>
20
+ <% end -%>
21
+ }
22
+
23
+ def initialize(book, config)
24
+ @book = book
25
+ @config = config
26
+ end
27
+
28
+ def content
29
+ ERB.new(template, nil, '-').result(binding)
30
+ end
31
+
32
+
33
+ private
34
+
35
+ def template
36
+ return DEFAULT_TEMPLATE if @config["template_path"].to_s.empty?
37
+
38
+ File.read(@config["template_path"])
39
+ end
40
+
41
+ def regular_entries
42
+ @book.reject { |entry| entry.type == Entry::TYPE[:bookmark] }
43
+ end
44
+
45
+ def bookmarks
46
+ @book.select { |entry| entry.type == Entry::TYPE[:bookmark] }
47
+ end
48
+
49
+ def render_entries(entries)
50
+ output = ""
51
+ entries.each do |entry|
52
+ output += "- #{item_text(entry)}\n\n"
53
+ output += " #{item_desc(entry)}\n\n"
54
+ end
55
+ output
56
+ end
57
+
58
+ def item_text(entry)
59
+ case entry.type
60
+ when Entry::TYPE[:bookmark]
61
+ "#{page(entry)}"
62
+ when Entry::TYPE[:note]
63
+ "_Note:_ #{entry.text.strip}"
64
+ else
65
+ "#{entry.text.strip}"
66
+ end
67
+ end
68
+
69
+ def item_desc(entry)
70
+ return entry.desc unless entry.desc_parsed?
71
+
72
+ case entry.type
73
+ when Entry::TYPE[:bookmark]
74
+ time(entry)
75
+ else
76
+ (type(entry) + " @ " + page(entry) + " " + time(entry)).strip
77
+ end
78
+ end
79
+
80
+ def page(entry)
81
+ ((entry.page.nil? ? "" : "page #{entry.page}, ") +
82
+ (entry.loc.nil? ? "" : "loc. #{entry.loc}")).delete_suffix(", ")
83
+ end
84
+
85
+ def time(entry)
86
+ @config["time"] ? "[#{entry.time}]" : ""
87
+ end
88
+
89
+ def type(entry)
90
+ SINGULAR[entry.type]
91
+ end
92
+ end
93
+ 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.7".freeze
2
+ VERSION = "0.3.0".freeze
3
3
  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.7
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rafael Cavalcanti
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-24 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
@@ -39,7 +39,7 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: '3.0'
41
41
  description: Parse Kindle clippings into markdown files
42
- email: code@rafaelc.org
42
+ email: dev@rafaelc.org
43
43
  executables:
44
44
  - fyodor
45
45
  extensions: []
@@ -56,16 +56,16 @@ 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
- homepage: https://github.com/rccavalcanti/fyodor
64
+ homepage: https://rafaelc.org/fyodor
65
65
  licenses:
66
66
  - GPL-3.0-only
67
67
  metadata:
68
- source_code_uri: https://github.com/rccavalcanti/fyodor
68
+ source_code_uri: https://rafaelc.org/fyodor
69
69
  post_install_message:
70
70
  rdoc_options: []
71
71
  require_paths:
@@ -75,6 +75,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
75
75
  - - ">="
76
76
  - !ruby/object:Gem::Version
77
77
  version: '2.5'
78
+ - - "<"
79
+ - !ruby/object:Gem::Version
80
+ version: '3'
78
81
  required_rubygems_version: !ruby/object:Gem::Requirement
79
82
  requirements:
80
83
  - - ">="
@@ -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