hashcards_readwise 0.1.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 +7 -0
- data/README.md +75 -0
- data/exe/hashcards-readwise +6 -0
- data/lib/hashcards_readwise/cli.rb +185 -0
- data/lib/hashcards_readwise/converter.rb +105 -0
- data/lib/hashcards_readwise/pusher.rb +44 -0
- data/lib/hashcards_readwise/version.rb +5 -0
- data/lib/hashcards_readwise.rb +10 -0
- metadata +94 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 28483db60197d3a33cd5b1884f96df11b624ee600a74bd86490b31232c3c9ec1
|
|
4
|
+
data.tar.gz: fc816269bc4f7243ffce9e880d53bc0c44e236fd3c25f85cc5d21f12a47f6bc4
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: db2f8647c80c5ae77e0c829ec26553d27c5f2595c896ca2b974e39a4916e9bfe42a40d7498845cb62448e35766bacb04393152f505a0696208a17d4f65a8b77e
|
|
7
|
+
data.tar.gz: '075686ab4f1b61fb7b3df46ad00d0c89e3e102ee342bc0a0d0ac92452efeec6e19175990e7967367e2008c51009e6dd8dfed70618c9b1ce99319d541f4d9c611'
|
data/README.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# hashcards-readwise
|
|
2
|
+
|
|
3
|
+
Sync [hashcards](https://github.com/eudoxia0/hashcards) flashcards to [Readwise](https://readwise.io).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
gem install hashcards_readwise
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or add to your Gemfile:
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
gem "hashcards_readwise"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
### All-in-one sync
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Sync a hashcards collection to Readwise
|
|
23
|
+
hashcards-readwise sync --collection ./cards
|
|
24
|
+
|
|
25
|
+
# Dry run (convert but don't push)
|
|
26
|
+
hashcards-readwise sync --collection ./cards --dry-run
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Step-by-step
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# 1. Export hashcards to JSON
|
|
33
|
+
hashcards export ./cards --output export.json
|
|
34
|
+
|
|
35
|
+
# 2. Convert to Readwise format
|
|
36
|
+
hashcards-readwise convert -f export.json -o highlights.json -d ./cards/hashcards.db
|
|
37
|
+
|
|
38
|
+
# 3. Push to Readwise
|
|
39
|
+
hashcards-readwise push -f highlights.json
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Configuration
|
|
43
|
+
|
|
44
|
+
Set your Readwise API token via environment variable:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
export READWISE_TOKEN="your-token-here"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Or pass it directly:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
hashcards-readwise sync --token "your-token-here" --collection ./cards
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Deck Metadata
|
|
57
|
+
|
|
58
|
+
Add TOML frontmatter to your deck files to customize Readwise metadata:
|
|
59
|
+
|
|
60
|
+
```markdown
|
|
61
|
+
---
|
|
62
|
+
name = "My Deck"
|
|
63
|
+
author = "Author Name"
|
|
64
|
+
source_url = "https://example.com/source"
|
|
65
|
+
image_url = "https://example.com/image.png"
|
|
66
|
+
tags = ["topic1", "topic2"]
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
Q: Question here
|
|
70
|
+
A: Answer here
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## License
|
|
74
|
+
|
|
75
|
+
MIT
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "optparse"
|
|
4
|
+
require "logger"
|
|
5
|
+
|
|
6
|
+
module HashcardsReadwise
|
|
7
|
+
class CLI
|
|
8
|
+
def initialize(args = ARGV)
|
|
9
|
+
@args = args
|
|
10
|
+
@logger = Logger.new($stderr, level: Logger::WARN)
|
|
11
|
+
@logger.progname = "hashcards-readwise"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def run
|
|
15
|
+
OptionParser.new do |o|
|
|
16
|
+
o.on("-v", "--verbose", "Enable verbose logging (can be repeated)") { @logger.level -= 1 }
|
|
17
|
+
end.order!(@args)
|
|
18
|
+
|
|
19
|
+
command = @args.shift
|
|
20
|
+
|
|
21
|
+
case command
|
|
22
|
+
when "convert"
|
|
23
|
+
run_convert
|
|
24
|
+
when "push"
|
|
25
|
+
run_push
|
|
26
|
+
when "sync"
|
|
27
|
+
run_sync
|
|
28
|
+
when "version", "-v", "--version"
|
|
29
|
+
puts "hashcards-readwise #{VERSION}"
|
|
30
|
+
when "help", "-h", "--help", nil
|
|
31
|
+
print_help
|
|
32
|
+
else
|
|
33
|
+
@logger.warn("Unknown command: #{command}")
|
|
34
|
+
print_help
|
|
35
|
+
exit 1
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def run_convert
|
|
42
|
+
options = { file: nil, output: nil, db: nil }
|
|
43
|
+
|
|
44
|
+
OptionParser.new do |o|
|
|
45
|
+
o.banner = "Usage: hashcards-readwise convert [options]"
|
|
46
|
+
o.on("-f", "--file FILE", "Path to hashcards JSON export") { |f| options[:file] = f }
|
|
47
|
+
o.on("-o", "--output FILE", "Output file (default: stdout)") { |f| options[:output] = f }
|
|
48
|
+
o.on("-d", "--db FILE", "Path to hashcards SQLite database") { |f| options[:db] = f }
|
|
49
|
+
end.parse!(@args)
|
|
50
|
+
|
|
51
|
+
unless options[:file]
|
|
52
|
+
@logger.error "Error: --file is required"
|
|
53
|
+
exit 1
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
converter = Converter.new(logger: @logger)
|
|
57
|
+
highlights = converter.convert(hashcards_path: options[:file], db_path: options[:db])
|
|
58
|
+
|
|
59
|
+
output = JSON.pretty_generate(highlights)
|
|
60
|
+
if options[:output]
|
|
61
|
+
File.write(options[:output], output)
|
|
62
|
+
@logger.info("Wrote highlights to #{options[:output]}")
|
|
63
|
+
else
|
|
64
|
+
puts output
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def run_push
|
|
69
|
+
options = { file: nil, token: ENV["READWISE_TOKEN"], dry_run: false }
|
|
70
|
+
|
|
71
|
+
OptionParser.new do |o|
|
|
72
|
+
o.banner = "Usage: hashcards-readwise push [options]"
|
|
73
|
+
o.on("-f", "--file FILE", "Path to highlights JSON file") { |f| options[:file] = f }
|
|
74
|
+
o.on("-t", "--token TOKEN", "Readwise API token") { |t| options[:token] = t }
|
|
75
|
+
o.on("-n", "--dry-run", "Print request without sending") { options[:dry_run] = true }
|
|
76
|
+
end.parse!(@args)
|
|
77
|
+
|
|
78
|
+
unless options[:file]
|
|
79
|
+
@logger.error "Error: --file is required"
|
|
80
|
+
exit 1
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
unless options[:token]
|
|
84
|
+
@logger.error "Error: Readwise token required (--token or READWISE_TOKEN env)"
|
|
85
|
+
exit 1
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
highlights = JSON.parse(File.read(options[:file]))
|
|
89
|
+
@logger.info("Loaded #{highlights["highlights"].size} highlights from #{options[:file]}")
|
|
90
|
+
|
|
91
|
+
if options[:dry_run]
|
|
92
|
+
@logger.info("Dry run - would POST to #{Pusher::READWISE_API_URL}")
|
|
93
|
+
@logger.info("Body: #{JSON.pretty_generate(highlights)}")
|
|
94
|
+
return
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
pusher = Pusher.new(token: options[:token], logger: @logger)
|
|
98
|
+
result = pusher.push(highlights)
|
|
99
|
+
|
|
100
|
+
unless result[:success]
|
|
101
|
+
@logger.error result[:body]
|
|
102
|
+
exit 1
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
puts JSON.pretty_generate(result[:body])
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def run_sync
|
|
109
|
+
options = { collection: ".", token: ENV["READWISE_TOKEN"], dry_run: false }
|
|
110
|
+
|
|
111
|
+
OptionParser.new do |o|
|
|
112
|
+
o.banner = "Usage: hashcards-readwise sync [options]"
|
|
113
|
+
o.on("-c", "--collection DIR", "Path to hashcards collection") { |c| options[:collection] = c }
|
|
114
|
+
o.on("-t", "--token TOKEN", "Readwise API token") { |t| options[:token] = t }
|
|
115
|
+
o.on("-n", "--dry-run", "Convert but don't push") { options[:dry_run] = true }
|
|
116
|
+
end.parse!(@args)
|
|
117
|
+
|
|
118
|
+
unless options[:token]
|
|
119
|
+
@logger.error "Error: Readwise token required (--token or READWISE_TOKEN env)"
|
|
120
|
+
exit 1
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
collection = File.expand_path(options[:collection])
|
|
124
|
+
db_path = File.join(collection, "hashcards.db")
|
|
125
|
+
db_path = nil unless File.exist?(db_path)
|
|
126
|
+
|
|
127
|
+
# Export hashcards to temp file
|
|
128
|
+
require "tempfile"
|
|
129
|
+
export_file = Tempfile.new(["hashcards", ".json"])
|
|
130
|
+
|
|
131
|
+
@logger.info("Exporting hashcards from #{collection}...")
|
|
132
|
+
system("hashcards", "export", collection, "--output", export_file.path)
|
|
133
|
+
|
|
134
|
+
unless $?.success?
|
|
135
|
+
@logger.error "Error: hashcards export failed"
|
|
136
|
+
exit 1
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Convert
|
|
140
|
+
converter = Converter.new(logger: @logger)
|
|
141
|
+
highlights = converter.convert(hashcards_path: export_file.path, db_path: db_path)
|
|
142
|
+
|
|
143
|
+
if options[:dry_run]
|
|
144
|
+
puts JSON.pretty_generate(highlights)
|
|
145
|
+
return
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Push
|
|
149
|
+
pusher = Pusher.new(token: options[:token], logger: @logger)
|
|
150
|
+
result = pusher.push(highlights)
|
|
151
|
+
|
|
152
|
+
unless result[:success]
|
|
153
|
+
@logger.error result[:body]
|
|
154
|
+
exit 1
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
@logger.info "Synced #{highlights["highlights"].size} highlights to Readwise"
|
|
158
|
+
ensure
|
|
159
|
+
export_file&.unlink
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def print_help
|
|
163
|
+
puts <<~HELP
|
|
164
|
+
hashcards-readwise - Sync hashcards flashcards to Readwise
|
|
165
|
+
|
|
166
|
+
Usage: hashcards-readwise <command> [options]
|
|
167
|
+
|
|
168
|
+
Commands:
|
|
169
|
+
sync Export, convert, and push to Readwise (all-in-one)
|
|
170
|
+
convert Convert hashcards JSON to Readwise highlights format
|
|
171
|
+
push Push highlights JSON to Readwise API
|
|
172
|
+
version Show version
|
|
173
|
+
help Show this help
|
|
174
|
+
|
|
175
|
+
Examples:
|
|
176
|
+
hashcards-readwise sync --collection ./cards
|
|
177
|
+
hashcards-readwise convert -f export.json -o highlights.json
|
|
178
|
+
hashcards-readwise push -f highlights.json
|
|
179
|
+
|
|
180
|
+
Environment:
|
|
181
|
+
READWISE_TOKEN Readwise API token (or use --token)
|
|
182
|
+
HELP
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "sqlite3"
|
|
5
|
+
require "toml-rb"
|
|
6
|
+
require "logger"
|
|
7
|
+
|
|
8
|
+
module HashcardsReadwise
|
|
9
|
+
class Converter
|
|
10
|
+
DEFAULT_AUTHOR = "Derek Stride"
|
|
11
|
+
|
|
12
|
+
attr_reader :logger
|
|
13
|
+
|
|
14
|
+
def initialize(logger: Logger.new($stderr, level: Logger::WARN))
|
|
15
|
+
@logger = logger
|
|
16
|
+
@frontmatter_cache = {}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def convert(hashcards_path:, db_path: nil)
|
|
20
|
+
hashcards = JSON.parse(File.read(hashcards_path))
|
|
21
|
+
logger.info("Loaded #{hashcards["cards"].size} cards from #{hashcards_path}")
|
|
22
|
+
|
|
23
|
+
timestamps = load_timestamps(db_path)
|
|
24
|
+
logger.info("Loaded #{timestamps.size} timestamps from database") if timestamps.any?
|
|
25
|
+
|
|
26
|
+
highlights = hashcards["cards"].filter_map { |card| card_to_highlight(card, timestamps) }
|
|
27
|
+
logger.info("Converted #{highlights.size} highlights")
|
|
28
|
+
logger.info("Parsed frontmatter from #{@frontmatter_cache.size} files")
|
|
29
|
+
|
|
30
|
+
{ "highlights" => highlights }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def load_timestamps(db_path)
|
|
36
|
+
return {} unless db_path && File.exist?(db_path)
|
|
37
|
+
|
|
38
|
+
db = SQLite3::Database.new(db_path.to_s)
|
|
39
|
+
results = db.execute("SELECT card_hash, added_at FROM cards")
|
|
40
|
+
results.to_h { |row| [row[0], row[1]] }
|
|
41
|
+
rescue SQLite3::Exception => e
|
|
42
|
+
logger.warn("Failed to load timestamps from database: #{e.message}")
|
|
43
|
+
{}
|
|
44
|
+
ensure
|
|
45
|
+
db&.close
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def parse_frontmatter(file_path)
|
|
49
|
+
return {} unless File.exist?(file_path)
|
|
50
|
+
|
|
51
|
+
content = File.read(file_path)
|
|
52
|
+
return {} unless content.start_with?("---")
|
|
53
|
+
|
|
54
|
+
parts = content.split("---", 3)
|
|
55
|
+
return {} if parts.length < 3
|
|
56
|
+
|
|
57
|
+
toml_content = parts[1].strip
|
|
58
|
+
return {} if toml_content.empty?
|
|
59
|
+
|
|
60
|
+
TomlRB.parse(toml_content)
|
|
61
|
+
rescue TomlRB::ParseError => e
|
|
62
|
+
logger.warn("Failed to parse frontmatter from #{file_path}: #{e.message}")
|
|
63
|
+
{}
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def get_frontmatter(file_path)
|
|
67
|
+
@frontmatter_cache[file_path] ||= parse_frontmatter(file_path)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def card_to_highlight(card, timestamps)
|
|
71
|
+
content = card["content"]
|
|
72
|
+
text = if content["cloze"]
|
|
73
|
+
content["cloze"]["text"]
|
|
74
|
+
elsif content["basic"]
|
|
75
|
+
"Q: #{content["basic"]["question"]}\nA: #{content["basic"]["answer"]}"
|
|
76
|
+
else
|
|
77
|
+
logger.warn("Unknown card content type: #{content.keys}")
|
|
78
|
+
return nil
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
file_path = card.dig("location", "filePath")
|
|
82
|
+
fm = file_path ? get_frontmatter(file_path) : {}
|
|
83
|
+
|
|
84
|
+
note_parts = [".hashcard"]
|
|
85
|
+
fm["tags"]&.each { |tag| note_parts << ".#{tag}" }
|
|
86
|
+
note = note_parts.join("\n")
|
|
87
|
+
|
|
88
|
+
hash = card["hash"]
|
|
89
|
+
highlighted_at = timestamps[hash]
|
|
90
|
+
|
|
91
|
+
{
|
|
92
|
+
"text" => text,
|
|
93
|
+
"title" => card["deckName"],
|
|
94
|
+
"author" => fm["author"] || DEFAULT_AUTHOR,
|
|
95
|
+
"source_type" => "hashcards#sync#derek",
|
|
96
|
+
"category" => "articles",
|
|
97
|
+
"note" => note,
|
|
98
|
+
"source_url" => fm["source_url"],
|
|
99
|
+
"image_url" => fm["image_url"],
|
|
100
|
+
"highlighted_at" => highlighted_at,
|
|
101
|
+
"highlight_url" => "hashcards://#{hash}"
|
|
102
|
+
}.compact
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "net/http"
|
|
5
|
+
require "openssl"
|
|
6
|
+
require "logger"
|
|
7
|
+
|
|
8
|
+
module HashcardsReadwise
|
|
9
|
+
class Pusher
|
|
10
|
+
READWISE_API_URL = "https://readwise.io/api/v2/highlights/"
|
|
11
|
+
|
|
12
|
+
attr_reader :logger
|
|
13
|
+
|
|
14
|
+
def initialize(token:, logger: Logger.new($stderr, level: Logger::WARN))
|
|
15
|
+
@token = token
|
|
16
|
+
@logger = logger
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def push(highlights)
|
|
20
|
+
uri = URI.parse(READWISE_API_URL)
|
|
21
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
22
|
+
http.use_ssl = true
|
|
23
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
|
24
|
+
http.cert_store = OpenSSL::X509::Store.new.tap(&:set_default_paths)
|
|
25
|
+
|
|
26
|
+
request = Net::HTTP::Post.new(uri.request_uri)
|
|
27
|
+
request["Authorization"] = "Token #{@token}"
|
|
28
|
+
request["Content-Type"] = "application/json"
|
|
29
|
+
request.body = JSON.generate(highlights)
|
|
30
|
+
|
|
31
|
+
logger.info("POSTing #{highlights["highlights"].size} highlights to Readwise...")
|
|
32
|
+
response = http.request(request)
|
|
33
|
+
|
|
34
|
+
case response
|
|
35
|
+
when Net::HTTPSuccess
|
|
36
|
+
logger.info("Success! Response: #{response.body}")
|
|
37
|
+
{ success: true, body: JSON.parse(response.body) }
|
|
38
|
+
else
|
|
39
|
+
logger.error("Failed with status #{response.code}")
|
|
40
|
+
{ success: false, code: response.code, body: response.body }
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "hashcards_readwise/version"
|
|
4
|
+
require_relative "hashcards_readwise/converter"
|
|
5
|
+
require_relative "hashcards_readwise/pusher"
|
|
6
|
+
require_relative "hashcards_readwise/cli"
|
|
7
|
+
|
|
8
|
+
module HashcardsReadwise
|
|
9
|
+
class Error < StandardError; end
|
|
10
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: hashcards_readwise
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Derek Stride
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: logger
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: sqlite3
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: toml-rb
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
54
|
+
description: Convert hashcards exports to Readwise highlights and push them to the
|
|
55
|
+
Readwise API
|
|
56
|
+
email:
|
|
57
|
+
- derek@stride.host
|
|
58
|
+
executables:
|
|
59
|
+
- hashcards-readwise
|
|
60
|
+
extensions: []
|
|
61
|
+
extra_rdoc_files: []
|
|
62
|
+
files:
|
|
63
|
+
- README.md
|
|
64
|
+
- exe/hashcards-readwise
|
|
65
|
+
- lib/hashcards_readwise.rb
|
|
66
|
+
- lib/hashcards_readwise/cli.rb
|
|
67
|
+
- lib/hashcards_readwise/converter.rb
|
|
68
|
+
- lib/hashcards_readwise/pusher.rb
|
|
69
|
+
- lib/hashcards_readwise/version.rb
|
|
70
|
+
homepage: https://github.com/derekstride/hashcards-readwise
|
|
71
|
+
licenses:
|
|
72
|
+
- MIT
|
|
73
|
+
metadata:
|
|
74
|
+
allowed_push_host: https://rubygems.org
|
|
75
|
+
homepage_uri: https://github.com/derekstride/hashcards-readwise
|
|
76
|
+
source_code_uri: https://github.com/derekstride/hashcards-readwise
|
|
77
|
+
rdoc_options: []
|
|
78
|
+
require_paths:
|
|
79
|
+
- lib
|
|
80
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
81
|
+
requirements:
|
|
82
|
+
- - ">="
|
|
83
|
+
- !ruby/object:Gem::Version
|
|
84
|
+
version: 3.0.0
|
|
85
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - ">="
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '0'
|
|
90
|
+
requirements: []
|
|
91
|
+
rubygems_version: 3.6.9
|
|
92
|
+
specification_version: 4
|
|
93
|
+
summary: Sync hashcards flashcards to Readwise
|
|
94
|
+
test_files: []
|