readwise 1.0.0 → 1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c3d14c72c58cb11dfcb79c0b3b232abb8b997dc230493d5e0765f186999accba
4
- data.tar.gz: 037ac6d22c2d195607023e99eebbd71f2885996dd40e16d49f33bc403549ebd9
3
+ metadata.gz: 5e37bd88ff6059f63924f1c72ccb905f1d719fbff923411a00635995fcfe70a3
4
+ data.tar.gz: c3454ae039735d62d06c35889534c41ff76c94843ddeb9849d96b2287fa702d7
5
5
  SHA512:
6
- metadata.gz: 6b25268acdd085ba3252f828a0f85051f492de64fd851e96a5a0e38bfcfb6e25070df8bc38d7fa94aa52c1433003d6dc8d57ca501d7261bef010e510b6711c13
7
- data.tar.gz: 1d1c7e40a53214750405a866832427aadce2ad3dc432a4d9e4ab0610520400b1f5fb14d46d90712cc0219621eb2bef21c4bff40bb62def052e99409bfd0b0b86
6
+ metadata.gz: 45f34de451ad75c000b40ba5a78b7a365e701c8af9a27783128e30a951f8ec6674c8f90b12fb51ad77261c3a9f7378486feae6d5494d50e4b3ef9f0f4e0e8837
7
+ data.tar.gz: a5b2fe3e2a4d6c8ea744065f9dd76414db85858b62ae83be3aa65309281a25aa07fbd4cd72bf86f2581dba12796f890da07100d50a8d6892ee0620d0deae60a4
@@ -22,7 +22,7 @@ jobs:
22
22
  runs-on: ubuntu-latest
23
23
  strategy:
24
24
  matrix:
25
- ruby-version: ['2.6', '2.7', '3.0', '3.1', '3.2']
25
+ ruby-version: ['3.2', '3.3', '3.4']
26
26
 
27
27
  steps:
28
28
  - uses: actions/checkout@v3
data/.gitignore CHANGED
@@ -55,3 +55,5 @@ Gemfile.lock
55
55
 
56
56
  # Used by RuboCop. Remote config files pulled in from inherit_from directive.
57
57
  # .rubocop-https?--*
58
+
59
+ .rspec_status
data/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [1.1.0] - 2025-07-04
6
+ - Add CLI command for creating documents from HTML content or URLs ([#16](https://github.com/joshbeckman/readwise-ruby/pull/16) from [@andyw8](https://github.com/andyw8))
7
+
8
+ ## [1.0.1] - 2025-06-29
9
+ - Handle absent tags in transforming highlights
10
+
5
11
  ## [1.0.0] - 2025-06-29
6
12
  - Add Readwise API v3 support for Reader documents ([#13](https://github.com/joshbeckman/readwise-ruby/pull/13))
7
13
  - Add `daily_review` client method and Review type ([#14](https://github.com/joshbeckman/readwise-ruby/pull/14))
data/README.md CHANGED
@@ -101,7 +101,7 @@ puts document.child? # is this a highlight/note of another doc
101
101
 
102
102
  # Check location
103
103
  puts document.in_new?
104
- puts document.in_later?
104
+ puts document.in_later?
105
105
  puts document.in_archive?
106
106
 
107
107
  # Check category
@@ -142,6 +142,32 @@ document = client.create_document(document: document_create)
142
142
  documents = client.create_documents(documents: [document_create1, document_create2])
143
143
  ```
144
144
 
145
+ ## Command Line Interface
146
+
147
+ This gem includes a `readwise` command-line tool for quickly sending HTML content to Readwise Reader.
148
+
149
+ First, set your API token:
150
+ ```bash
151
+ export READWISE_API_KEY=your_token_here
152
+ ```
153
+
154
+ Then use the CLI to send HTML files:
155
+ ```bash
156
+ # Basic usage
157
+ readwise document create --html-file content.html
158
+ readwise document create --url https://datatracker.ietf.org/doc/html/rfc2324
159
+
160
+ # Short form flag
161
+ readwise document create -f content.html
162
+
163
+ # With options
164
+ readwise document create --html-file content.html --title="My Article" --location=later
165
+
166
+ # See all available options
167
+ readwise --help
168
+ readwise document create --help
169
+ ```
170
+
145
171
  ## Development
146
172
 
147
173
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
data/exe/readwise ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'readwise'
5
+ require 'readwise/cli'
6
+
7
+ Readwise::CLI.start
@@ -0,0 +1,105 @@
1
+ require 'optparse'
2
+
3
+ module Readwise
4
+ class CLI
5
+ class BaseCommand
6
+ def initialize
7
+ @options = {}
8
+ end
9
+
10
+ def execute(args)
11
+ parse_options(args)
12
+ validate_arguments(args)
13
+ run(args)
14
+ rescue OptionParser::InvalidOption => e
15
+ puts "Error: #{e.message}"
16
+ show_help
17
+ exit 1
18
+ rescue ArgumentError => e
19
+ puts "Error: #{e.message}"
20
+ exit 1
21
+ end
22
+
23
+ private
24
+
25
+ attr_reader :options
26
+
27
+ def parse_options(args)
28
+ parser = create_option_parser
29
+ parser.parse!(args)
30
+ end
31
+
32
+ def create_option_parser
33
+ OptionParser.new do |opts|
34
+ opts.banner = banner
35
+ opts.separator ""
36
+ opts.separator description if description
37
+ opts.separator ""
38
+ opts.separator "Options:"
39
+
40
+ add_options(opts)
41
+
42
+ opts.on("-h", "--help", "Show this help message") do
43
+ show_help
44
+ exit
45
+ end
46
+ end
47
+ end
48
+
49
+ def show_help
50
+ puts create_option_parser.help
51
+ end
52
+
53
+ def get_api_client
54
+ token = ENV['READWISE_API_KEY']
55
+ unless token
56
+ puts "Error: READWISE_API_KEY environment variable is not set"
57
+ exit 1
58
+ end
59
+
60
+ Readwise::Client.new(token: token)
61
+ end
62
+
63
+ def read_file(file_path)
64
+ unless File.exist?(file_path)
65
+ puts "Error: File '#{file_path}' not found"
66
+ exit 1
67
+ end
68
+
69
+ File.read(file_path)
70
+ end
71
+
72
+ def handle_api_error(&block)
73
+ yield
74
+ rescue Readwise::Client::Error => e
75
+ puts "API Error: #{e.message}"
76
+ exit 1
77
+ rescue => e
78
+ puts "Unexpected error: #{e.message}"
79
+ exit 1
80
+ end
81
+
82
+ # Override these methods in subclasses
83
+
84
+ def banner
85
+ "Usage: readwise"
86
+ end
87
+
88
+ def description
89
+ nil
90
+ end
91
+
92
+ def add_options(opts)
93
+ # Override in subclasses to add specific options
94
+ end
95
+
96
+ def validate_arguments(args)
97
+ # Override in subclasses to validate arguments
98
+ end
99
+
100
+ def run(args)
101
+ raise NotImplementedError, "Subclasses must implement #run"
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,24 @@
1
+ module Readwise
2
+ class CLI
3
+ class CommandRegistry
4
+ @commands = {}
5
+
6
+ def self.register(resource, action, command_class)
7
+ key = "#{resource}:#{action}"
8
+ @commands[key] = command_class
9
+ end
10
+
11
+ def self.find(resource, action)
12
+ key = "#{resource}:#{action}"
13
+ @commands[key]
14
+ end
15
+
16
+ def self.all_commands
17
+ @commands.keys.map { |key| key.split(':') }
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ # Register available commands
24
+ require_relative 'document/create_command'
@@ -0,0 +1,152 @@
1
+ require_relative '../base_command'
2
+ require_relative '../../constants'
3
+ require 'uri'
4
+ require 'date'
5
+
6
+ module Readwise
7
+ class CLI
8
+ module Document
9
+ class CreateCommand < BaseCommand
10
+ def banner
11
+ "Usage: readwise document create [options]"
12
+ end
13
+
14
+ def description
15
+ "Sends HTML content to Readwise Reader API"
16
+ end
17
+
18
+ def add_options(opts)
19
+ opts.on("-f", "--html-file=FILE", "HTML file path") do |file|
20
+ options[:file] = file
21
+ end
22
+
23
+ opts.on("--title=TITLE", "Document title") do |title|
24
+ options[:title] = title
25
+ end
26
+
27
+ opts.on("--author=AUTHOR", "Document author") do |author|
28
+ options[:author] = author
29
+ end
30
+
31
+ opts.on("-u", "--url=URL", "Source URL (defaults to https://example.com/<filename>)") do |url|
32
+ unless valid_url?(url)
33
+ puts "Error: Invalid URL format. Please provide a valid URL."
34
+ exit 1
35
+ end
36
+ options[:url] = url
37
+ end
38
+
39
+ opts.on("--summary=SUMMARY", "Document summary") do |summary|
40
+ options[:summary] = summary
41
+ end
42
+
43
+ opts.on("--notes=NOTES", "Personal notes") do |notes|
44
+ options[:notes] = notes
45
+ end
46
+
47
+ opts.on("--location=LOCATION", "Document location: #{Readwise::Constants::DOCUMENT_LOCATIONS.join(', ')} (default: new)") do |location|
48
+ unless Readwise::Constants::DOCUMENT_LOCATIONS.include?(location)
49
+ puts "Error: Invalid location. Must be one of: #{Readwise::Constants::DOCUMENT_LOCATIONS.join(', ')}"
50
+ exit 1
51
+ end
52
+ options[:location] = location
53
+ end
54
+
55
+ opts.on("--category=CATEGORY", "Document category: #{Readwise::Constants::DOCUMENT_CATEGORIES.join(', ')}") do |category|
56
+ unless Readwise::Constants::DOCUMENT_CATEGORIES.include?(category)
57
+ puts "Error: Invalid category. Must be one of: #{Readwise::Constants::DOCUMENT_CATEGORIES.join(', ')}"
58
+ exit 1
59
+ end
60
+ options[:category] = category
61
+ end
62
+
63
+ opts.on("--tags=TAGS", "Comma-separated list of tags") do |tags|
64
+ options[:tags] = tags.split(',').map(&:strip)
65
+ end
66
+
67
+ opts.on("--image-url=URL", "Image URL") do |image_url|
68
+ options[:image_url] = image_url
69
+ end
70
+
71
+ opts.on("--published-date=DATE", "Published date (ISO 8601 format)") do |date|
72
+ unless valid_iso8601_date?(date)
73
+ puts "Error: Invalid date format. Please provide a valid ISO 8601 date (e.g., 2023-12-25T10:30:00Z)."
74
+ exit 1
75
+ end
76
+ options[:published_date] = date
77
+ end
78
+
79
+ opts.on("--[no-]clean-html", "Clean HTML (default: true)") do |clean|
80
+ options[:should_clean_html] = clean
81
+ end
82
+
83
+ opts.on("--saved-using=SOURCE", "Saved using source (default: cli)") do |source|
84
+ options[:saved_using] = source
85
+ end
86
+ end
87
+
88
+ def validate_arguments(args)
89
+ unless options[:file] || options[:url]
90
+ puts "Error: File path or URL is required"
91
+ show_help
92
+ exit 1
93
+ end
94
+ end
95
+
96
+ def run(args)
97
+ html_file = options[:file]
98
+ html_content = read_file(html_file) if html_file
99
+
100
+ document_params = build_document_params(html_content, html_file)
101
+
102
+ handle_api_error do
103
+ client = get_api_client
104
+ document_create = Readwise::DocumentCreate.new(**document_params)
105
+ document = client.create_document(document: document_create)
106
+
107
+ puts "Document created successfully!"
108
+ puts "ID: #{document.id}"
109
+ puts "Title: #{document.title}"
110
+ puts "Location: #{document.location}"
111
+ puts "URL: #{document.url}" if document.url
112
+ end
113
+ end
114
+
115
+ private
116
+
117
+ def valid_url?(url)
118
+ uri = URI.parse(url)
119
+ uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
120
+ rescue URI::InvalidURIError
121
+ false
122
+ end
123
+
124
+ def valid_iso8601_date?(date)
125
+ DateTime.iso8601(date)
126
+ true
127
+ rescue Date::Error
128
+ false
129
+ end
130
+
131
+ def build_document_params(html_content, html_file)
132
+ document_params = {
133
+ html: html_content,
134
+ location: options[:location] || 'new',
135
+ should_clean_html: options.key?(:should_clean_html) ? options[:should_clean_html] : true,
136
+ saved_using: options[:saved_using] || 'cli',
137
+ url: options[:url] || "https://example.com/#{File.basename(html_file)}"
138
+ }
139
+
140
+ [:title, :author, :summary, :notes, :category, :tags, :image_url, :published_date].each do |key|
141
+ document_params[key] = options[key] if options[key]
142
+ end
143
+
144
+ document_params
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
150
+
151
+ # Register this command
152
+ Readwise::CLI::CommandRegistry.register('document', 'create', Readwise::CLI::Document::CreateCommand)
@@ -0,0 +1,64 @@
1
+ require_relative 'cli/base_command'
2
+ require_relative 'cli/command_registry'
3
+
4
+ module Readwise
5
+ class CLI
6
+ def self.start(args = ARGV)
7
+ new.start(args)
8
+ end
9
+
10
+ def start(args)
11
+ if args.empty?
12
+ show_help
13
+ exit 1
14
+ end
15
+
16
+ if args.first == '--help' || args.first == '-h'
17
+ show_help
18
+ return
19
+ end
20
+
21
+ resource = args.shift&.downcase
22
+ action = args.shift&.downcase
23
+
24
+ unless resource && action
25
+ puts "Error: Resource and action are required"
26
+ puts "Usage: readwise <resource> <action> [options] [arguments]"
27
+ puts "Run 'readwise --help' for more information"
28
+ exit 1
29
+ end
30
+
31
+ command_class = CommandRegistry.find(resource, action)
32
+ unless command_class
33
+ puts "Error: Unknown command '#{resource} #{action}'"
34
+ puts "Run 'readwise --help' to see available commands"
35
+ exit 1
36
+ end
37
+
38
+ command = command_class.new
39
+ command.execute(args)
40
+ rescue => e
41
+ puts "Unexpected error: #{e.message}"
42
+ exit 1
43
+ end
44
+
45
+ private
46
+
47
+ def show_help
48
+ puts <<~HELP
49
+ Usage: readwise <resource> <action> [options] [arguments]
50
+
51
+ Available commands:
52
+ document create --html-file <file> Send HTML content to Readwise Reader
53
+
54
+ Global options:
55
+ -h, --help Show this help message
56
+
57
+ Examples:
58
+ readwise document create --html-file content.html --title="My Article"
59
+ readwise document create -f content.html --title="My Article"
60
+ readwise document create --help
61
+ HELP
62
+ end
63
+ end
64
+ end
@@ -227,7 +227,7 @@ module Readwise
227
227
  location_type: res['location_type'],
228
228
  note: res['note'],
229
229
  readwise_url: res['readwise_url'],
230
- tags: res['tags'].map { |tag| transform_tag(tag) },
230
+ tags: (res['tags'] || []).map { |tag| transform_tag(tag) },
231
231
  text: res['text'],
232
232
  updated_at: res['updated_at'],
233
233
  url: res['url'],
@@ -0,0 +1,6 @@
1
+ module Readwise
2
+ module Constants
3
+ DOCUMENT_CATEGORIES = %w[article email rss highlight note pdf epub tweet video].freeze
4
+ DOCUMENT_LOCATIONS = %w[new later archive feed].freeze
5
+ end
6
+ end
@@ -1,3 +1,3 @@
1
1
  module Readwise
2
- VERSION = "1.0.0"
2
+ VERSION = "1.1.0"
3
3
  end
data/readwise.gemspec CHANGED
@@ -37,8 +37,8 @@ Gem::Specification.new do |spec|
37
37
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
38
38
  spec.require_paths = ["lib"]
39
39
 
40
- spec.add_development_dependency "bundler", "~> 2.2"
41
- spec.add_development_dependency "rake", "~> 10.0"
40
+ spec.add_development_dependency "bundler", "~> 2.4"
41
+ spec.add_development_dependency "rake", "~> 13.0"
42
42
  spec.add_development_dependency "rspec", "~> 3.0"
43
43
  spec.add_development_dependency "rspec-file_fixtures", "~> 0.1.6"
44
44
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: readwise
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josh Beckman
@@ -15,28 +15,28 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: '2.2'
18
+ version: '2.4'
19
19
  type: :development
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: '2.2'
25
+ version: '2.4'
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: rake
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
30
  - - "~>"
31
31
  - !ruby/object:Gem::Version
32
- version: '10.0'
32
+ version: '13.0'
33
33
  type: :development
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: '10.0'
39
+ version: '13.0'
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: rspec
42
42
  requirement: !ruby/object:Gem::Requirement
@@ -68,7 +68,8 @@ dependencies:
68
68
  description: Minimal Readwise API client and highlight parsing library
69
69
  email:
70
70
  - josh@joshbeckman.org
71
- executables: []
71
+ executables:
72
+ - readwise
72
73
  extensions: []
73
74
  extra_rdoc_files: []
74
75
  files:
@@ -83,9 +84,15 @@ files:
83
84
  - Rakefile
84
85
  - bin/console
85
86
  - bin/setup
87
+ - exe/readwise
86
88
  - lib/readwise.rb
87
89
  - lib/readwise/book.rb
90
+ - lib/readwise/cli.rb
91
+ - lib/readwise/cli/base_command.rb
92
+ - lib/readwise/cli/command_registry.rb
93
+ - lib/readwise/cli/document/create_command.rb
88
94
  - lib/readwise/client.rb
95
+ - lib/readwise/constants.rb
89
96
  - lib/readwise/document.rb
90
97
  - lib/readwise/highlight.rb
91
98
  - lib/readwise/review.rb