renamed 0.1.0.beta.2
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 +218 -0
- data/lib/renamed/async_job.rb +68 -0
- data/lib/renamed/client.rb +290 -0
- data/lib/renamed/errors.rb +98 -0
- data/lib/renamed/models.rb +274 -0
- data/lib/renamed/version.rb +5 -0
- data/lib/renamed.rb +26 -0
- metadata +174 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 77538a9c46e4722a2be7b056217e87e168464c54317be54c8139cbdf0d5f7a6e
|
|
4
|
+
data.tar.gz: 03b4a3515b130d8d8eed9b47f2d7a2c083c4c9e05388051432ca3748c90d4abe
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 3e1cce89fe45835b1eff235248236cf7229a86cb0f10d79fedd758670a6e5a352476a518baf498fac4cad04e2c93af0be809fecbe9ccdcd386f2355e0c01c06c
|
|
7
|
+
data.tar.gz: 32ce22cb167093c9770cd6ff79aa3d96701bd3099ff060d445189877f534a0991ba1324e9b2d3d1803618c7e532a2f8a376018d76fdc07c0aec61791ade83611
|
data/README.md
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# Renamed Ruby SDK
|
|
2
|
+
|
|
3
|
+
Official Ruby SDK for the [renamed.to](https://renamed.to) API. Rename files intelligently using AI, split PDFs, and extract structured data from documents.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add this line to your application's Gemfile:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem 'renamed'
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
And then execute:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
bundle install
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Or install it yourself as:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
gem install renamed
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
require 'renamed'
|
|
29
|
+
|
|
30
|
+
# Initialize the client with your API key
|
|
31
|
+
client = Renamed::Client.new(api_key: 'rt_your_api_key')
|
|
32
|
+
|
|
33
|
+
# Rename a file using AI
|
|
34
|
+
result = client.rename('invoice.pdf')
|
|
35
|
+
puts result.suggested_filename # => "2025-01-15_AcmeCorp_INV-12345.pdf"
|
|
36
|
+
puts result.folder_path # => "Invoices/2025/January"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Features
|
|
40
|
+
|
|
41
|
+
### Rename Files
|
|
42
|
+
|
|
43
|
+
Intelligently rename files using AI analysis:
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
# Basic rename
|
|
47
|
+
result = client.rename('document.pdf')
|
|
48
|
+
puts result.suggested_filename
|
|
49
|
+
puts result.folder_path
|
|
50
|
+
puts result.confidence
|
|
51
|
+
|
|
52
|
+
# With custom template
|
|
53
|
+
result = client.rename('invoice.pdf', template: '{date}_{vendor}_{type}')
|
|
54
|
+
puts result.suggested_filename
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Split PDFs
|
|
58
|
+
|
|
59
|
+
Split multi-page PDFs into separate documents:
|
|
60
|
+
|
|
61
|
+
```ruby
|
|
62
|
+
# Auto-detect document boundaries
|
|
63
|
+
job = client.pdf_split('multi-page.pdf', mode: 'auto')
|
|
64
|
+
|
|
65
|
+
# Wait for completion with progress updates
|
|
66
|
+
result = job.wait do |status|
|
|
67
|
+
puts "Progress: #{status.progress}%"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Download the split documents
|
|
71
|
+
result.documents.each do |doc|
|
|
72
|
+
puts "#{doc.filename} (pages #{doc.pages})"
|
|
73
|
+
content = client.download_file(doc.download_url)
|
|
74
|
+
File.binwrite(doc.filename, content)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Split by fixed page count
|
|
78
|
+
job = client.pdf_split('document.pdf', mode: 'pages', pages_per_split: 5)
|
|
79
|
+
result = job.wait
|
|
80
|
+
|
|
81
|
+
# Split at blank pages
|
|
82
|
+
job = client.pdf_split('scanned.pdf', mode: 'blank')
|
|
83
|
+
result = job.wait
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Extract Data
|
|
87
|
+
|
|
88
|
+
Extract structured data from documents:
|
|
89
|
+
|
|
90
|
+
```ruby
|
|
91
|
+
# Extract with natural language prompt
|
|
92
|
+
result = client.extract('invoice.pdf', prompt: 'Extract the vendor name, invoice number, and total amount')
|
|
93
|
+
puts result.data
|
|
94
|
+
# => {"vendor" => "Acme Corp", "invoice_number" => "INV-12345", "total" => 1250.00}
|
|
95
|
+
|
|
96
|
+
# Extract with JSON schema
|
|
97
|
+
schema = {
|
|
98
|
+
type: 'object',
|
|
99
|
+
properties: {
|
|
100
|
+
vendor: { type: 'string' },
|
|
101
|
+
invoice_number: { type: 'string' },
|
|
102
|
+
line_items: {
|
|
103
|
+
type: 'array',
|
|
104
|
+
items: {
|
|
105
|
+
type: 'object',
|
|
106
|
+
properties: {
|
|
107
|
+
description: { type: 'string' },
|
|
108
|
+
quantity: { type: 'number' },
|
|
109
|
+
price: { type: 'number' }
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
total: { type: 'number' }
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
result = client.extract('invoice.pdf', schema: schema)
|
|
118
|
+
puts result.data
|
|
119
|
+
puts result.confidence
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Check User Credits
|
|
123
|
+
|
|
124
|
+
```ruby
|
|
125
|
+
user = client.get_user
|
|
126
|
+
puts "Email: #{user.email}"
|
|
127
|
+
puts "Credits remaining: #{user.credits}"
|
|
128
|
+
puts "Team: #{user.team&.name}"
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Configuration
|
|
132
|
+
|
|
133
|
+
```ruby
|
|
134
|
+
client = Renamed::Client.new(
|
|
135
|
+
api_key: 'rt_your_api_key',
|
|
136
|
+
base_url: 'https://www.renamed.to/api/v1', # Default
|
|
137
|
+
timeout: 30, # Request timeout in seconds
|
|
138
|
+
max_retries: 2 # Number of retries for failed requests
|
|
139
|
+
)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Error Handling
|
|
143
|
+
|
|
144
|
+
The SDK provides specific error classes for different error conditions:
|
|
145
|
+
|
|
146
|
+
```ruby
|
|
147
|
+
begin
|
|
148
|
+
result = client.rename('document.pdf')
|
|
149
|
+
rescue Renamed::AuthenticationError => e
|
|
150
|
+
puts "Invalid API key: #{e.message}"
|
|
151
|
+
rescue Renamed::InsufficientCreditsError => e
|
|
152
|
+
puts "Not enough credits: #{e.message}"
|
|
153
|
+
rescue Renamed::RateLimitError => e
|
|
154
|
+
puts "Rate limited. Retry after: #{e.retry_after} seconds"
|
|
155
|
+
rescue Renamed::ValidationError => e
|
|
156
|
+
puts "Invalid request: #{e.message}"
|
|
157
|
+
puts "Details: #{e.details}"
|
|
158
|
+
rescue Renamed::NetworkError => e
|
|
159
|
+
puts "Network error: #{e.message}"
|
|
160
|
+
rescue Renamed::TimeoutError => e
|
|
161
|
+
puts "Request timed out: #{e.message}"
|
|
162
|
+
rescue Renamed::JobError => e
|
|
163
|
+
puts "Job failed: #{e.message}"
|
|
164
|
+
puts "Job ID: #{e.job_id}"
|
|
165
|
+
rescue Renamed::Error => e
|
|
166
|
+
puts "General error: #{e.message}"
|
|
167
|
+
puts "Code: #{e.code}"
|
|
168
|
+
puts "Status: #{e.status_code}"
|
|
169
|
+
end
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## File Input Types
|
|
173
|
+
|
|
174
|
+
All file methods accept multiple input types:
|
|
175
|
+
|
|
176
|
+
```ruby
|
|
177
|
+
# String path
|
|
178
|
+
result = client.rename('/path/to/document.pdf')
|
|
179
|
+
|
|
180
|
+
# File object
|
|
181
|
+
File.open('document.pdf', 'rb') do |file|
|
|
182
|
+
result = client.rename(file)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# IO object
|
|
186
|
+
io = StringIO.new(pdf_content)
|
|
187
|
+
result = client.rename(io)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Supported File Types
|
|
191
|
+
|
|
192
|
+
- PDF (`.pdf`)
|
|
193
|
+
- JPEG (`.jpg`, `.jpeg`)
|
|
194
|
+
- PNG (`.png`)
|
|
195
|
+
- TIFF (`.tif`, `.tiff`)
|
|
196
|
+
|
|
197
|
+
## Development
|
|
198
|
+
|
|
199
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests.
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
bundle install
|
|
203
|
+
bundle exec rake test
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
To generate documentation:
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
bundle exec yard doc
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Contributing
|
|
213
|
+
|
|
214
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/renamed-to/renamed-sdk.
|
|
215
|
+
|
|
216
|
+
## License
|
|
217
|
+
|
|
218
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "models"
|
|
4
|
+
require_relative "errors"
|
|
5
|
+
|
|
6
|
+
module Renamed
|
|
7
|
+
# Async job handle for long-running operations like PDF split.
|
|
8
|
+
class AsyncJob
|
|
9
|
+
DEFAULT_POLL_INTERVAL = 2.0
|
|
10
|
+
MAX_POLL_ATTEMPTS = 150 # 5 minutes at 2s intervals
|
|
11
|
+
|
|
12
|
+
attr_reader :status_url
|
|
13
|
+
|
|
14
|
+
# @param client [Client] The Renamed client instance
|
|
15
|
+
# @param status_url [String] URL to poll for job status
|
|
16
|
+
# @param poll_interval [Float] Interval between status checks in seconds
|
|
17
|
+
# @param max_attempts [Integer] Maximum number of polling attempts
|
|
18
|
+
def initialize(client, status_url, poll_interval: DEFAULT_POLL_INTERVAL, max_attempts: MAX_POLL_ATTEMPTS)
|
|
19
|
+
@client = client
|
|
20
|
+
@status_url = status_url
|
|
21
|
+
@poll_interval = poll_interval
|
|
22
|
+
@max_attempts = max_attempts
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Get current job status.
|
|
26
|
+
#
|
|
27
|
+
# @return [JobStatusResponse] Current job status
|
|
28
|
+
def status
|
|
29
|
+
response = @client.send(:request, :get, @status_url)
|
|
30
|
+
JobStatusResponse.from_response(response)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Wait for job completion, polling at regular intervals.
|
|
34
|
+
#
|
|
35
|
+
# @yield [JobStatusResponse] Called with status updates during polling
|
|
36
|
+
# @return [PdfSplitResult] The completed job result
|
|
37
|
+
# @raise [JobError] If the job fails or times out
|
|
38
|
+
#
|
|
39
|
+
# @example Wait for job completion
|
|
40
|
+
# job = client.pdf_split("multi-page.pdf", mode: "auto")
|
|
41
|
+
# result = job.wait { |status| puts "Progress: #{status.progress}%" }
|
|
42
|
+
# result.documents.each do |doc|
|
|
43
|
+
# puts "#{doc.filename}: #{doc.download_url}"
|
|
44
|
+
# end
|
|
45
|
+
def wait
|
|
46
|
+
attempts = 0
|
|
47
|
+
|
|
48
|
+
while attempts < @max_attempts
|
|
49
|
+
current_status = status
|
|
50
|
+
|
|
51
|
+
yield current_status if block_given?
|
|
52
|
+
|
|
53
|
+
if current_status.status == "completed" && current_status.result
|
|
54
|
+
return current_status.result
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
if current_status.status == "failed"
|
|
58
|
+
raise JobError.new(current_status.error || "Job failed", job_id: current_status.job_id)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
attempts += 1
|
|
62
|
+
sleep(@poll_interval)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
raise JobError.new("Job polling timeout exceeded")
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "faraday"
|
|
4
|
+
require "faraday/multipart"
|
|
5
|
+
require "json"
|
|
6
|
+
|
|
7
|
+
require_relative "errors"
|
|
8
|
+
require_relative "models"
|
|
9
|
+
require_relative "async_job"
|
|
10
|
+
|
|
11
|
+
module Renamed
|
|
12
|
+
# renamed.to API client.
|
|
13
|
+
#
|
|
14
|
+
# @example Basic usage
|
|
15
|
+
# client = Renamed::Client.new(api_key: "rt_your_api_key")
|
|
16
|
+
# result = client.rename("invoice.pdf")
|
|
17
|
+
# puts result.suggested_filename
|
|
18
|
+
#
|
|
19
|
+
# @example With custom configuration
|
|
20
|
+
# client = Renamed::Client.new(
|
|
21
|
+
# api_key: "rt_your_api_key",
|
|
22
|
+
# timeout: 60,
|
|
23
|
+
# max_retries: 3
|
|
24
|
+
# )
|
|
25
|
+
class Client
|
|
26
|
+
DEFAULT_BASE_URL = "https://www.renamed.to/api/v1"
|
|
27
|
+
DEFAULT_TIMEOUT = 30
|
|
28
|
+
DEFAULT_MAX_RETRIES = 2
|
|
29
|
+
|
|
30
|
+
# @param api_key [String] API key for authentication (starts with rt_)
|
|
31
|
+
# @param base_url [String] Base URL for the API
|
|
32
|
+
# @param timeout [Integer] Request timeout in seconds
|
|
33
|
+
# @param max_retries [Integer] Maximum number of retries for failed requests
|
|
34
|
+
def initialize(api_key:, base_url: DEFAULT_BASE_URL, timeout: DEFAULT_TIMEOUT, max_retries: DEFAULT_MAX_RETRIES)
|
|
35
|
+
raise AuthenticationError, "API key is required" if api_key.nil? || api_key.empty?
|
|
36
|
+
|
|
37
|
+
@api_key = api_key
|
|
38
|
+
@base_url = base_url.chomp("/")
|
|
39
|
+
@timeout = timeout
|
|
40
|
+
@max_retries = max_retries
|
|
41
|
+
|
|
42
|
+
@connection = build_connection
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Get current user profile and credits.
|
|
46
|
+
#
|
|
47
|
+
# @return [User] User profile with credits balance
|
|
48
|
+
#
|
|
49
|
+
# @example
|
|
50
|
+
# user = client.get_user
|
|
51
|
+
# puts "Credits remaining: #{user.credits}"
|
|
52
|
+
def get_user
|
|
53
|
+
response = request(:get, "/user")
|
|
54
|
+
User.from_response(response)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Rename a file using AI.
|
|
58
|
+
#
|
|
59
|
+
# @param file [String, File, IO] File to rename (path, File object, or IO)
|
|
60
|
+
# @param options [Hash] Additional options
|
|
61
|
+
# @option options [String] :template Custom template for filename generation
|
|
62
|
+
#
|
|
63
|
+
# @return [RenameResult] Result with suggested filename and folder path
|
|
64
|
+
#
|
|
65
|
+
# @example Rename a file by path
|
|
66
|
+
# result = client.rename("invoice.pdf")
|
|
67
|
+
# puts result.suggested_filename # "2025-01-15_AcmeCorp_INV-12345.pdf"
|
|
68
|
+
#
|
|
69
|
+
# @example Rename with custom template
|
|
70
|
+
# result = client.rename("invoice.pdf", template: "{date}_{vendor}_{type}")
|
|
71
|
+
# puts result.suggested_filename
|
|
72
|
+
def rename(file, options: {})
|
|
73
|
+
additional_fields = {}
|
|
74
|
+
additional_fields["template"] = options[:template] if options[:template]
|
|
75
|
+
|
|
76
|
+
response = upload_file("/rename", file, additional_fields: additional_fields)
|
|
77
|
+
RenameResult.from_response(response)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Split a PDF into multiple documents.
|
|
81
|
+
#
|
|
82
|
+
# Returns an AsyncJob that can be polled for completion.
|
|
83
|
+
#
|
|
84
|
+
# @param file [String, File, IO] PDF file to split
|
|
85
|
+
# @param options [Hash] Additional options
|
|
86
|
+
# @option options [String] :mode Split mode ('auto', 'pages', or 'blank')
|
|
87
|
+
# @option options [Integer] :pages_per_split Number of pages per split (for 'pages' mode)
|
|
88
|
+
#
|
|
89
|
+
# @return [AsyncJob] Job that can be waited on for the result
|
|
90
|
+
#
|
|
91
|
+
# @example Split a PDF with auto-detection
|
|
92
|
+
# job = client.pdf_split("multi-page.pdf", mode: "auto")
|
|
93
|
+
# result = job.wait { |s| puts "Progress: #{s.progress}%" }
|
|
94
|
+
# result.documents.each do |doc|
|
|
95
|
+
# puts "#{doc.filename}: #{doc.download_url}"
|
|
96
|
+
# end
|
|
97
|
+
#
|
|
98
|
+
# @example Split into fixed page chunks
|
|
99
|
+
# job = client.pdf_split("document.pdf", mode: "pages", pages_per_split: 5)
|
|
100
|
+
# result = job.wait
|
|
101
|
+
def pdf_split(file, options: {})
|
|
102
|
+
additional_fields = {}
|
|
103
|
+
additional_fields["mode"] = options[:mode] if options[:mode]
|
|
104
|
+
additional_fields["pagesPerSplit"] = options[:pages_per_split].to_s if options[:pages_per_split]
|
|
105
|
+
|
|
106
|
+
response = upload_file("/pdf-split", file, additional_fields: additional_fields)
|
|
107
|
+
AsyncJob.new(self, response["statusUrl"])
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Extract structured data from a document.
|
|
111
|
+
#
|
|
112
|
+
# @param file [String, File, IO] Document to extract data from
|
|
113
|
+
# @param options [Hash] Additional options
|
|
114
|
+
# @option options [String] :prompt Natural language description of what to extract
|
|
115
|
+
# @option options [Hash] :schema JSON schema defining the structure of data to extract
|
|
116
|
+
#
|
|
117
|
+
# @return [ExtractResult] Result with extracted data
|
|
118
|
+
#
|
|
119
|
+
# @example Extract with a prompt
|
|
120
|
+
# result = client.extract("invoice.pdf", prompt: "Extract invoice details")
|
|
121
|
+
# puts result.data
|
|
122
|
+
#
|
|
123
|
+
# @example Extract with a schema
|
|
124
|
+
# schema = {
|
|
125
|
+
# type: "object",
|
|
126
|
+
# properties: {
|
|
127
|
+
# vendor: { type: "string" },
|
|
128
|
+
# total: { type: "number" }
|
|
129
|
+
# }
|
|
130
|
+
# }
|
|
131
|
+
# result = client.extract("invoice.pdf", schema: schema)
|
|
132
|
+
def extract(file, options: {})
|
|
133
|
+
additional_fields = {}
|
|
134
|
+
additional_fields["prompt"] = options[:prompt] if options[:prompt]
|
|
135
|
+
additional_fields["schema"] = JSON.generate(options[:schema]) if options[:schema]
|
|
136
|
+
|
|
137
|
+
response = upload_file("/extract", file, additional_fields: additional_fields)
|
|
138
|
+
ExtractResult.from_response(response)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Download a file from a URL (e.g., split document).
|
|
142
|
+
#
|
|
143
|
+
# @param url [String] URL to download from
|
|
144
|
+
# @return [String] File content as binary string
|
|
145
|
+
#
|
|
146
|
+
# @example Download split documents
|
|
147
|
+
# result = job.wait
|
|
148
|
+
# result.documents.each do |doc|
|
|
149
|
+
# content = client.download_file(doc.download_url)
|
|
150
|
+
# File.binwrite(doc.filename, content)
|
|
151
|
+
# end
|
|
152
|
+
def download_file(url)
|
|
153
|
+
response = @connection.get(url)
|
|
154
|
+
handle_download_response(response)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
private
|
|
158
|
+
|
|
159
|
+
def build_connection
|
|
160
|
+
Faraday.new(url: @base_url) do |conn|
|
|
161
|
+
conn.request :multipart
|
|
162
|
+
conn.request :url_encoded
|
|
163
|
+
conn.headers["Authorization"] = "Bearer #{@api_key}"
|
|
164
|
+
conn.options.timeout = @timeout
|
|
165
|
+
conn.options.open_timeout = @timeout
|
|
166
|
+
conn.adapter Faraday.default_adapter
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def build_url(path)
|
|
171
|
+
return path if path.start_with?("http://", "https://")
|
|
172
|
+
|
|
173
|
+
path = "/#{path}" unless path.start_with?("/")
|
|
174
|
+
"#{@base_url}#{path}"
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def request(method, path, **options)
|
|
178
|
+
url = build_url(path)
|
|
179
|
+
last_error = nil
|
|
180
|
+
attempts = 0
|
|
181
|
+
|
|
182
|
+
while attempts <= @max_retries
|
|
183
|
+
begin
|
|
184
|
+
response = @connection.send(method, url, options[:body], options[:headers])
|
|
185
|
+
return handle_response(response)
|
|
186
|
+
rescue Faraday::ConnectionFailed => e
|
|
187
|
+
last_error = NetworkError.new(e.message)
|
|
188
|
+
rescue Faraday::TimeoutError => e
|
|
189
|
+
last_error = TimeoutError.new(e.message)
|
|
190
|
+
rescue Renamed::Error => e
|
|
191
|
+
# Don't retry client errors (4xx)
|
|
192
|
+
raise if e.status_code && e.status_code >= 400 && e.status_code < 500
|
|
193
|
+
|
|
194
|
+
last_error = e
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
attempts += 1
|
|
198
|
+
sleep(2**attempts * 0.1) if attempts <= @max_retries
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
raise last_error || NetworkError.new
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def handle_response(response)
|
|
205
|
+
if response.status >= 400
|
|
206
|
+
payload = parse_response_body(response)
|
|
207
|
+
raise Renamed.error_from_http_status(response.status, response.reason_phrase || "Error", payload)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
parse_response_body(response)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def handle_download_response(response)
|
|
214
|
+
if response.status >= 400
|
|
215
|
+
raise Renamed.error_from_http_status(response.status, response.reason_phrase || "Error")
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
response.body
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def parse_response_body(response)
|
|
222
|
+
return {} if response.body.nil? || response.body.empty?
|
|
223
|
+
|
|
224
|
+
JSON.parse(response.body)
|
|
225
|
+
rescue JSON::ParserError
|
|
226
|
+
response.body
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def upload_file(path, file, additional_fields: {})
|
|
230
|
+
filename, content, mime_type = prepare_file(file)
|
|
231
|
+
|
|
232
|
+
payload = {}
|
|
233
|
+
payload[:file] = Faraday::Multipart::FilePart.new(
|
|
234
|
+
StringIO.new(content),
|
|
235
|
+
mime_type,
|
|
236
|
+
filename
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
additional_fields.each do |key, value|
|
|
240
|
+
payload[key] = value
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
url = build_url(path)
|
|
244
|
+
last_error = nil
|
|
245
|
+
attempts = 0
|
|
246
|
+
|
|
247
|
+
while attempts <= @max_retries
|
|
248
|
+
begin
|
|
249
|
+
response = @connection.post(url, payload)
|
|
250
|
+
return handle_response(response)
|
|
251
|
+
rescue Faraday::ConnectionFailed => e
|
|
252
|
+
last_error = NetworkError.new(e.message)
|
|
253
|
+
rescue Faraday::TimeoutError => e
|
|
254
|
+
last_error = TimeoutError.new(e.message)
|
|
255
|
+
rescue Renamed::Error => e
|
|
256
|
+
raise if e.status_code && e.status_code >= 400 && e.status_code < 500
|
|
257
|
+
|
|
258
|
+
last_error = e
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
attempts += 1
|
|
262
|
+
sleep(2**attempts * 0.1) if attempts <= @max_retries
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
raise last_error || NetworkError.new
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def prepare_file(file)
|
|
269
|
+
case file
|
|
270
|
+
when String
|
|
271
|
+
# File path
|
|
272
|
+
path = File.expand_path(file)
|
|
273
|
+
content = File.binread(path)
|
|
274
|
+
filename = File.basename(path)
|
|
275
|
+
[filename, content, get_mime_type(filename)]
|
|
276
|
+
when File, IO
|
|
277
|
+
content = file.read
|
|
278
|
+
filename = file.respond_to?(:path) && file.path ? File.basename(file.path) : "file"
|
|
279
|
+
[filename, content, get_mime_type(filename)]
|
|
280
|
+
else
|
|
281
|
+
raise ValidationError.new("Invalid file input: expected String path, File, or IO object")
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def get_mime_type(filename)
|
|
286
|
+
ext = File.extname(filename).downcase
|
|
287
|
+
MIME_TYPES[ext] || "application/octet-stream"
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Renamed
|
|
4
|
+
# Base exception for all renamed.to SDK errors.
|
|
5
|
+
class Error < StandardError
|
|
6
|
+
attr_reader :message, :code, :status_code, :details
|
|
7
|
+
|
|
8
|
+
def initialize(message = "An error occurred", code: "UNKNOWN_ERROR", status_code: nil, details: nil)
|
|
9
|
+
@message = message
|
|
10
|
+
@code = code
|
|
11
|
+
@status_code = status_code
|
|
12
|
+
@details = details
|
|
13
|
+
super(message)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Authentication error - invalid or missing API key.
|
|
18
|
+
class AuthenticationError < Error
|
|
19
|
+
def initialize(message = "Invalid or missing API key")
|
|
20
|
+
super(message, code: "AUTHENTICATION_ERROR", status_code: 401)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Insufficient credits error.
|
|
25
|
+
class InsufficientCreditsError < Error
|
|
26
|
+
def initialize(message = "Insufficient credits")
|
|
27
|
+
super(message, code: "INSUFFICIENT_CREDITS", status_code: 402)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Rate limit exceeded.
|
|
32
|
+
class RateLimitError < Error
|
|
33
|
+
attr_reader :retry_after
|
|
34
|
+
|
|
35
|
+
def initialize(message = "Rate limit exceeded", retry_after: nil)
|
|
36
|
+
super(message, code: "RATE_LIMIT_ERROR", status_code: 429)
|
|
37
|
+
@retry_after = retry_after
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Validation error - invalid request parameters.
|
|
42
|
+
class ValidationError < Error
|
|
43
|
+
def initialize(message, details: nil)
|
|
44
|
+
super(message, code: "VALIDATION_ERROR", status_code: 400, details: details)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Network error - connection failed.
|
|
49
|
+
class NetworkError < Error
|
|
50
|
+
def initialize(message = "Network request failed")
|
|
51
|
+
super(message, code: "NETWORK_ERROR")
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Timeout error - request took too long.
|
|
56
|
+
class TimeoutError < Error
|
|
57
|
+
def initialize(message = "Request timed out")
|
|
58
|
+
super(message, code: "TIMEOUT_ERROR")
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Job error - async job failed.
|
|
63
|
+
class JobError < Error
|
|
64
|
+
attr_reader :job_id
|
|
65
|
+
|
|
66
|
+
def initialize(message, job_id: nil)
|
|
67
|
+
super(message, code: "JOB_ERROR")
|
|
68
|
+
@job_id = job_id
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Create appropriate error from HTTP status code.
|
|
73
|
+
#
|
|
74
|
+
# @param status [Integer] HTTP status code
|
|
75
|
+
# @param status_text [String] HTTP status text
|
|
76
|
+
# @param payload [Hash, String, nil] Response payload
|
|
77
|
+
# @return [Error] Appropriate error instance
|
|
78
|
+
def self.error_from_http_status(status, status_text, payload = nil)
|
|
79
|
+
message = status_text
|
|
80
|
+
if payload.is_a?(Hash) && payload["error"]
|
|
81
|
+
message = payload["error"].to_s
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
case status
|
|
85
|
+
when 401
|
|
86
|
+
AuthenticationError.new(message)
|
|
87
|
+
when 402
|
|
88
|
+
InsufficientCreditsError.new(message)
|
|
89
|
+
when 400, 422
|
|
90
|
+
ValidationError.new(message, details: payload)
|
|
91
|
+
when 429
|
|
92
|
+
retry_after = payload.is_a?(Hash) ? payload["retryAfter"] : nil
|
|
93
|
+
RateLimitError.new(message, retry_after: retry_after)
|
|
94
|
+
else
|
|
95
|
+
Error.new(message, code: "API_ERROR", status_code: status, details: payload)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Renamed
|
|
4
|
+
# Result of a rename operation.
|
|
5
|
+
class RenameResult
|
|
6
|
+
attr_reader :original_filename, :suggested_filename, :folder_path, :confidence
|
|
7
|
+
|
|
8
|
+
# @param original_filename [String] Original filename that was uploaded
|
|
9
|
+
# @param suggested_filename [String] AI-suggested new filename
|
|
10
|
+
# @param folder_path [String, nil] Suggested folder path for organization
|
|
11
|
+
# @param confidence [Float, nil] Confidence score (0-1) of the suggestion
|
|
12
|
+
def initialize(original_filename:, suggested_filename:, folder_path: nil, confidence: nil)
|
|
13
|
+
@original_filename = original_filename
|
|
14
|
+
@suggested_filename = suggested_filename
|
|
15
|
+
@folder_path = folder_path
|
|
16
|
+
@confidence = confidence
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Create instance from API response hash.
|
|
20
|
+
#
|
|
21
|
+
# @param data [Hash] API response data
|
|
22
|
+
# @return [RenameResult]
|
|
23
|
+
def self.from_response(data)
|
|
24
|
+
new(
|
|
25
|
+
original_filename: data["originalFilename"],
|
|
26
|
+
suggested_filename: data["suggestedFilename"],
|
|
27
|
+
folder_path: data["folderPath"],
|
|
28
|
+
confidence: data["confidence"]
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def to_h
|
|
33
|
+
{
|
|
34
|
+
original_filename: @original_filename,
|
|
35
|
+
suggested_filename: @suggested_filename,
|
|
36
|
+
folder_path: @folder_path,
|
|
37
|
+
confidence: @confidence
|
|
38
|
+
}.compact
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# A single document from PDF split.
|
|
43
|
+
class SplitDocument
|
|
44
|
+
attr_reader :index, :filename, :pages, :download_url, :size
|
|
45
|
+
|
|
46
|
+
# @param index [Integer] Document index (0-based)
|
|
47
|
+
# @param filename [String] Suggested filename for this document
|
|
48
|
+
# @param pages [String] Page range included in this document
|
|
49
|
+
# @param download_url [String] URL to download this document
|
|
50
|
+
# @param size [Integer] Size in bytes
|
|
51
|
+
def initialize(index:, filename:, pages:, download_url:, size:)
|
|
52
|
+
@index = index
|
|
53
|
+
@filename = filename
|
|
54
|
+
@pages = pages
|
|
55
|
+
@download_url = download_url
|
|
56
|
+
@size = size
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Create instance from API response hash.
|
|
60
|
+
#
|
|
61
|
+
# @param data [Hash] API response data
|
|
62
|
+
# @return [SplitDocument]
|
|
63
|
+
def self.from_response(data)
|
|
64
|
+
new(
|
|
65
|
+
index: data["index"],
|
|
66
|
+
filename: data["filename"],
|
|
67
|
+
pages: data["pages"],
|
|
68
|
+
download_url: data["downloadUrl"],
|
|
69
|
+
size: data["size"]
|
|
70
|
+
)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def to_h
|
|
74
|
+
{
|
|
75
|
+
index: @index,
|
|
76
|
+
filename: @filename,
|
|
77
|
+
pages: @pages,
|
|
78
|
+
download_url: @download_url,
|
|
79
|
+
size: @size
|
|
80
|
+
}
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Result of PDF split operation.
|
|
85
|
+
class PdfSplitResult
|
|
86
|
+
attr_reader :original_filename, :documents, :total_pages
|
|
87
|
+
|
|
88
|
+
# @param original_filename [String] Original filename
|
|
89
|
+
# @param documents [Array<SplitDocument>] Split documents
|
|
90
|
+
# @param total_pages [Integer] Total number of pages in original document
|
|
91
|
+
def initialize(original_filename:, documents:, total_pages:)
|
|
92
|
+
@original_filename = original_filename
|
|
93
|
+
@documents = documents
|
|
94
|
+
@total_pages = total_pages
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Create instance from API response hash.
|
|
98
|
+
#
|
|
99
|
+
# @param data [Hash] API response data
|
|
100
|
+
# @return [PdfSplitResult]
|
|
101
|
+
def self.from_response(data)
|
|
102
|
+
documents = (data["documents"] || []).map { |doc| SplitDocument.from_response(doc) }
|
|
103
|
+
new(
|
|
104
|
+
original_filename: data["originalFilename"],
|
|
105
|
+
documents: documents,
|
|
106
|
+
total_pages: data["totalPages"]
|
|
107
|
+
)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def to_h
|
|
111
|
+
{
|
|
112
|
+
original_filename: @original_filename,
|
|
113
|
+
documents: @documents.map(&:to_h),
|
|
114
|
+
total_pages: @total_pages
|
|
115
|
+
}
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Response from job status endpoint.
|
|
120
|
+
class JobStatusResponse
|
|
121
|
+
attr_reader :job_id, :status, :progress, :error, :result
|
|
122
|
+
|
|
123
|
+
# @param job_id [String] Unique job identifier
|
|
124
|
+
# @param status [String] Current job status (pending, processing, completed, failed)
|
|
125
|
+
# @param progress [Integer, nil] Progress percentage (0-100)
|
|
126
|
+
# @param error [String, nil] Error message if job failed
|
|
127
|
+
# @param result [PdfSplitResult, nil] Result data when job is completed
|
|
128
|
+
def initialize(job_id:, status:, progress: nil, error: nil, result: nil)
|
|
129
|
+
@job_id = job_id
|
|
130
|
+
@status = status
|
|
131
|
+
@progress = progress
|
|
132
|
+
@error = error
|
|
133
|
+
@result = result
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Create instance from API response hash.
|
|
137
|
+
#
|
|
138
|
+
# @param data [Hash] API response data
|
|
139
|
+
# @return [JobStatusResponse]
|
|
140
|
+
def self.from_response(data)
|
|
141
|
+
result = data["result"] ? PdfSplitResult.from_response(data["result"]) : nil
|
|
142
|
+
new(
|
|
143
|
+
job_id: data["jobId"],
|
|
144
|
+
status: data["status"],
|
|
145
|
+
progress: data["progress"],
|
|
146
|
+
error: data["error"],
|
|
147
|
+
result: result
|
|
148
|
+
)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def to_h
|
|
152
|
+
{
|
|
153
|
+
job_id: @job_id,
|
|
154
|
+
status: @status,
|
|
155
|
+
progress: @progress,
|
|
156
|
+
error: @error,
|
|
157
|
+
result: @result&.to_h
|
|
158
|
+
}.compact
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Result of extract operation.
|
|
163
|
+
class ExtractResult
|
|
164
|
+
attr_reader :data, :confidence
|
|
165
|
+
|
|
166
|
+
# @param data [Hash] Extracted data matching the schema
|
|
167
|
+
# @param confidence [Float] Confidence score (0-1)
|
|
168
|
+
def initialize(data:, confidence:)
|
|
169
|
+
@data = data
|
|
170
|
+
@confidence = confidence
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Create instance from API response hash.
|
|
174
|
+
#
|
|
175
|
+
# @param response_data [Hash] API response data
|
|
176
|
+
# @return [ExtractResult]
|
|
177
|
+
def self.from_response(response_data)
|
|
178
|
+
new(
|
|
179
|
+
data: response_data["data"],
|
|
180
|
+
confidence: response_data["confidence"]
|
|
181
|
+
)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def to_h
|
|
185
|
+
{
|
|
186
|
+
data: @data,
|
|
187
|
+
confidence: @confidence
|
|
188
|
+
}
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Team information.
|
|
193
|
+
class Team
|
|
194
|
+
attr_reader :id, :name
|
|
195
|
+
|
|
196
|
+
# @param id [String] Team ID
|
|
197
|
+
# @param name [String] Team name
|
|
198
|
+
def initialize(id:, name:)
|
|
199
|
+
@id = id
|
|
200
|
+
@name = name
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Create instance from API response hash.
|
|
204
|
+
#
|
|
205
|
+
# @param data [Hash] API response data
|
|
206
|
+
# @return [Team]
|
|
207
|
+
def self.from_response(data)
|
|
208
|
+
new(
|
|
209
|
+
id: data["id"],
|
|
210
|
+
name: data["name"]
|
|
211
|
+
)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def to_h
|
|
215
|
+
{
|
|
216
|
+
id: @id,
|
|
217
|
+
name: @name
|
|
218
|
+
}
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# User profile information.
|
|
223
|
+
class User
|
|
224
|
+
attr_reader :id, :email, :name, :credits, :team
|
|
225
|
+
|
|
226
|
+
# @param id [String] User ID
|
|
227
|
+
# @param email [String] Email address
|
|
228
|
+
# @param name [String, nil] Display name
|
|
229
|
+
# @param credits [Integer, nil] Available credits
|
|
230
|
+
# @param team [Team, nil] Team information (if applicable)
|
|
231
|
+
def initialize(id:, email:, name: nil, credits: nil, team: nil)
|
|
232
|
+
@id = id
|
|
233
|
+
@email = email
|
|
234
|
+
@name = name
|
|
235
|
+
@credits = credits
|
|
236
|
+
@team = team
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# Create instance from API response hash.
|
|
240
|
+
#
|
|
241
|
+
# @param data [Hash] API response data
|
|
242
|
+
# @return [User]
|
|
243
|
+
def self.from_response(data)
|
|
244
|
+
team = data["team"] ? Team.from_response(data["team"]) : nil
|
|
245
|
+
new(
|
|
246
|
+
id: data["id"],
|
|
247
|
+
email: data["email"],
|
|
248
|
+
name: data["name"],
|
|
249
|
+
credits: data["credits"],
|
|
250
|
+
team: team
|
|
251
|
+
)
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def to_h
|
|
255
|
+
{
|
|
256
|
+
id: @id,
|
|
257
|
+
email: @email,
|
|
258
|
+
name: @name,
|
|
259
|
+
credits: @credits,
|
|
260
|
+
team: @team&.to_h
|
|
261
|
+
}.compact
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# MIME types for supported file formats.
|
|
266
|
+
MIME_TYPES = {
|
|
267
|
+
".pdf" => "application/pdf",
|
|
268
|
+
".jpg" => "image/jpeg",
|
|
269
|
+
".jpeg" => "image/jpeg",
|
|
270
|
+
".png" => "image/png",
|
|
271
|
+
".tiff" => "image/tiff",
|
|
272
|
+
".tif" => "image/tiff"
|
|
273
|
+
}.freeze
|
|
274
|
+
end
|
data/lib/renamed.rb
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "renamed/version"
|
|
4
|
+
require_relative "renamed/errors"
|
|
5
|
+
require_relative "renamed/models"
|
|
6
|
+
require_relative "renamed/async_job"
|
|
7
|
+
require_relative "renamed/client"
|
|
8
|
+
|
|
9
|
+
# Official Ruby SDK for renamed.to API.
|
|
10
|
+
#
|
|
11
|
+
# @example Basic usage
|
|
12
|
+
# require "renamed"
|
|
13
|
+
#
|
|
14
|
+
# client = Renamed::Client.new(api_key: "rt_your_api_key")
|
|
15
|
+
#
|
|
16
|
+
# # Rename a file
|
|
17
|
+
# result = client.rename("invoice.pdf")
|
|
18
|
+
# puts result.suggested_filename
|
|
19
|
+
#
|
|
20
|
+
# # Check credits
|
|
21
|
+
# user = client.get_user
|
|
22
|
+
# puts "Credits: #{user.credits}"
|
|
23
|
+
#
|
|
24
|
+
# @see https://renamed.to/docs API Documentation
|
|
25
|
+
module Renamed
|
|
26
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: renamed
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0.beta.2
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- renamed.to
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-01-11 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: faraday
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '2.0'
|
|
20
|
+
- - "<"
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: '3.0'
|
|
23
|
+
type: :runtime
|
|
24
|
+
prerelease: false
|
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
26
|
+
requirements:
|
|
27
|
+
- - ">="
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: '2.0'
|
|
30
|
+
- - "<"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '3.0'
|
|
33
|
+
- !ruby/object:Gem::Dependency
|
|
34
|
+
name: faraday-multipart
|
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.0'
|
|
40
|
+
type: :runtime
|
|
41
|
+
prerelease: false
|
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '1.0'
|
|
47
|
+
- !ruby/object:Gem::Dependency
|
|
48
|
+
name: bundler
|
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '2.0'
|
|
54
|
+
type: :development
|
|
55
|
+
prerelease: false
|
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '2.0'
|
|
61
|
+
- !ruby/object:Gem::Dependency
|
|
62
|
+
name: minitest
|
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '5.0'
|
|
68
|
+
type: :development
|
|
69
|
+
prerelease: false
|
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '5.0'
|
|
75
|
+
- !ruby/object:Gem::Dependency
|
|
76
|
+
name: rake
|
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '13.0'
|
|
82
|
+
type: :development
|
|
83
|
+
prerelease: false
|
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '13.0'
|
|
89
|
+
- !ruby/object:Gem::Dependency
|
|
90
|
+
name: rubocop
|
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - "~>"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '1.0'
|
|
96
|
+
type: :development
|
|
97
|
+
prerelease: false
|
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - "~>"
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '1.0'
|
|
103
|
+
- !ruby/object:Gem::Dependency
|
|
104
|
+
name: webmock
|
|
105
|
+
requirement: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - "~>"
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '3.0'
|
|
110
|
+
type: :development
|
|
111
|
+
prerelease: false
|
|
112
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
113
|
+
requirements:
|
|
114
|
+
- - "~>"
|
|
115
|
+
- !ruby/object:Gem::Version
|
|
116
|
+
version: '3.0'
|
|
117
|
+
- !ruby/object:Gem::Dependency
|
|
118
|
+
name: yard
|
|
119
|
+
requirement: !ruby/object:Gem::Requirement
|
|
120
|
+
requirements:
|
|
121
|
+
- - "~>"
|
|
122
|
+
- !ruby/object:Gem::Version
|
|
123
|
+
version: '0.9'
|
|
124
|
+
type: :development
|
|
125
|
+
prerelease: false
|
|
126
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
127
|
+
requirements:
|
|
128
|
+
- - "~>"
|
|
129
|
+
- !ruby/object:Gem::Version
|
|
130
|
+
version: '0.9'
|
|
131
|
+
description: Ruby client library for the renamed.to API. Rename files intelligently
|
|
132
|
+
using AI, split PDFs, and extract structured data from documents.
|
|
133
|
+
email:
|
|
134
|
+
- support@renamed.to
|
|
135
|
+
executables: []
|
|
136
|
+
extensions: []
|
|
137
|
+
extra_rdoc_files: []
|
|
138
|
+
files:
|
|
139
|
+
- README.md
|
|
140
|
+
- lib/renamed.rb
|
|
141
|
+
- lib/renamed/async_job.rb
|
|
142
|
+
- lib/renamed/client.rb
|
|
143
|
+
- lib/renamed/errors.rb
|
|
144
|
+
- lib/renamed/models.rb
|
|
145
|
+
- lib/renamed/version.rb
|
|
146
|
+
homepage: https://renamed.to
|
|
147
|
+
licenses:
|
|
148
|
+
- MIT
|
|
149
|
+
metadata:
|
|
150
|
+
homepage_uri: https://renamed.to
|
|
151
|
+
source_code_uri: https://github.com/renamed-to/renamed-sdk
|
|
152
|
+
changelog_uri: https://github.com/renamed-to/renamed-sdk/blob/main/sdks/ruby/CHANGELOG.md
|
|
153
|
+
documentation_uri: https://renamed.to/docs
|
|
154
|
+
rubygems_mfa_required: 'true'
|
|
155
|
+
post_install_message:
|
|
156
|
+
rdoc_options: []
|
|
157
|
+
require_paths:
|
|
158
|
+
- lib
|
|
159
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
160
|
+
requirements:
|
|
161
|
+
- - ">="
|
|
162
|
+
- !ruby/object:Gem::Version
|
|
163
|
+
version: 3.0.0
|
|
164
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
165
|
+
requirements:
|
|
166
|
+
- - ">="
|
|
167
|
+
- !ruby/object:Gem::Version
|
|
168
|
+
version: '0'
|
|
169
|
+
requirements: []
|
|
170
|
+
rubygems_version: 3.5.22
|
|
171
|
+
signing_key:
|
|
172
|
+
specification_version: 4
|
|
173
|
+
summary: Official Ruby SDK for renamed.to API
|
|
174
|
+
test_files: []
|