neocities-red 1.1.1 → 1.2.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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +29 -15
  3. data/.gitignore +4 -0
  4. data/CHANGELOG.md +95 -0
  5. data/Gemfile +3 -2
  6. data/Gemfile.lock +38 -34
  7. data/README.md +6 -0
  8. data/lib/neocities_red/cli.rb +157 -32
  9. data/lib/neocities_red/cli_display.rb +200 -5
  10. data/lib/neocities_red/client.rb +84 -77
  11. data/lib/neocities_red/errors.rb +20 -0
  12. data/lib/neocities_red/services/common/exclusions.rb +69 -0
  13. data/lib/neocities_red/services/common/pizza.rb +34 -21
  14. data/lib/neocities_red/services/common/worker_pool.rb +54 -0
  15. data/lib/neocities_red/services/file/folder_uploader.rb +39 -25
  16. data/lib/neocities_red/services/file/list.rb +60 -30
  17. data/lib/neocities_red/services/file/remover.rb +27 -12
  18. data/lib/neocities_red/services/file/uploader.rb +33 -13
  19. data/lib/neocities_red/services/site/differencer.rb +42 -12
  20. data/lib/neocities_red/services/site/exporter.rb +120 -7
  21. data/lib/neocities_red/services/site/informer.rb +29 -4
  22. data/lib/neocities_red/services/site/pusher.rb +74 -38
  23. data/lib/neocities_red/version.rb +2 -1
  24. data/lib/neocities_red.rb +32 -13
  25. data/neocities-red.gemspec +12 -7
  26. data/spec/neocities_red/cli_display_spec.rb +0 -6
  27. data/spec/neocities_red/cli_spec.rb +85 -0
  28. data/spec/neocities_red/client_spec.rb +7 -50
  29. data/spec/neocities_red/services/common/exclusions_spec.rb +47 -0
  30. data/spec/neocities_red/services/common/worker_pool_spec.rb +48 -0
  31. data/spec/neocities_red/services/file/folder_uploader_spec.rb +8 -6
  32. data/spec/neocities_red/services/file/list_spec.rb +13 -10
  33. data/spec/neocities_red/services/file/remover_spec.rb +15 -21
  34. data/spec/neocities_red/services/file/uploader_spec.rb +25 -20
  35. data/spec/neocities_red/services/site/differencer_spec.rb +32 -6
  36. data/spec/neocities_red/services/site/exporter_spec.rb +105 -38
  37. data/spec/neocities_red/services/site/informer_spec.rb +3 -3
  38. data/spec/neocities_red/services/site/pusher_spec.rb +1 -1
  39. data/spec/spec_helper.rb +1 -1
  40. metadata +14 -34
@@ -3,6 +3,7 @@
3
3
  require "spec_helper"
4
4
  require "json"
5
5
  require "fileutils"
6
+ require "time"
6
7
 
7
8
  RSpec.describe NeocitiesRed::Services::Site::Exporter do
8
9
  let(:client) { instance_double(NeocitiesRed::Client) }
@@ -11,29 +12,42 @@ RSpec.describe NeocitiesRed::Services::Site::Exporter do
11
12
  let(:tmp_root) { File.join(spec_root, "tmp", "site_exporter_spec") }
12
13
  let(:config_path) { File.join(tmp_root, "config.json") }
13
14
  let(:data) { { "LAST_PULL" => { "time" => nil, "loc" => nil } } }
15
+ let(:display) do
16
+ instance_double(
17
+ NeocitiesRed::CliDisplay,
18
+ display_pull_progress: nil,
19
+ display_pull_no_updates: nil,
20
+ display_pull_success: nil,
21
+ display_pull_failure: nil,
22
+ display_pull_stats: nil
23
+ )
24
+ end
25
+ let(:exporter) { described_class.new(client, sitename, data, config_path, display: display) }
26
+ let(:info_response) { { result: "success", info: { domain: nil } } }
27
+ let(:list_response) { { result: "success", files: [] } }
14
28
 
15
- before do
29
+ around do |example|
16
30
  FileUtils.rm_rf(tmp_root)
17
31
  FileUtils.mkdir_p(tmp_root)
18
32
  File.write(config_path, data.to_json)
19
- end
20
33
 
21
- after do
34
+ original_dir = Dir.pwd
35
+ Dir.chdir(tmp_root)
36
+ example.run
37
+ ensure
38
+ Dir.chdir(original_dir)
22
39
  FileUtils.rm_rf(tmp_root)
23
40
  end
24
41
 
25
- describe "#export" do
26
- context "when pull is successful" do
27
- let(:exporter) { described_class.new(client, sitename, data, config_path) }
28
-
29
- before do
30
- allow(client).to receive(:pull).and_return(nil)
31
- end
42
+ before do
43
+ allow(client).to receive_messages(info: info_response, list: list_response)
44
+ end
32
45
 
33
- it "pulls files from the site and updates the config file" do
46
+ describe "#export" do
47
+ context "when the site has no files" do
48
+ it "updates the config file with last pull data" do
34
49
  expect { exporter.export(quiet: true, last_pull_time: nil, last_pull_loc: nil) }.not_to raise_error
35
50
 
36
- expect(File.exist?(config_path)).to be(true)
37
51
  updated_data = JSON.parse(File.read(config_path))
38
52
  expect(updated_data["LAST_PULL"]).to have_key("time")
39
53
  expect(updated_data["LAST_PULL"]).to have_key("loc")
@@ -62,51 +76,104 @@ RSpec.describe NeocitiesRed::Services::Site::Exporter do
62
76
  expect(Whirly).not_to have_received(:stop)
63
77
  end
64
78
 
65
- context "with last_pull_time and last_pull_loc" do
66
- let(:last_pull_time) { "2024-01-01T00:00:00Z" }
67
- let(:last_pull_loc) { tmp_root }
79
+ it "displays pull stats" do
80
+ exporter.export(quiet: false, last_pull_time: nil, last_pull_loc: nil)
81
+
82
+ expect(display).to have_received(:display_pull_stats).with(0, anything)
83
+ end
84
+ end
85
+
86
+ context "when the site has files" do
87
+ let(:list_response) do
88
+ {
89
+ result: "success",
90
+ files: [
91
+ { path: "index.html", is_directory: false, updated_at: "2024-01-01T00:00:00Z" },
92
+ { path: "assets/", is_directory: true, updated_at: "2024-01-01T00:00:00Z" }
93
+ ]
94
+ }
95
+ end
96
+ let(:file_response) { instance_double(Faraday::Response, status: 200, body: "<h1>Hello</h1>") }
97
+
98
+ it "downloads files and writes them locally" do
99
+ allow(client).to receive(:download).with("https://test-site.neocities.org/index.html").and_return(file_response)
100
+
101
+ exporter.export(quiet: false, last_pull_time: nil, last_pull_loc: nil)
102
+
103
+ expect(File.read(File.join(tmp_root, "index.html"))).to eq("<h1>Hello</h1>")
104
+ expect(File.directory?(File.join(tmp_root, "assets"))).to be(true)
105
+ expect(display).to have_received(:display_pull_success)
106
+ end
107
+
108
+ it "uses the custom domain when present" do
109
+ allow(client).to receive(:info).and_return(result: "success", info: { domain: "example.com" })
110
+ allow(client).to receive(:download).with("https://example.com/index.html").and_return(file_response)
111
+
112
+ exporter.export(quiet: false, last_pull_time: nil, last_pull_loc: nil)
113
+
114
+ expect(client).to have_received(:download).with("https://example.com/index.html")
115
+ end
116
+
117
+ it "skips download when the file has not changed since last pull" do
118
+ allow(client).to receive(:download)
119
+ File.write(File.join(tmp_root, "index.html"), "existing content")
120
+
121
+ last_pull_time = Time.now.iso8601
122
+ exporter.export(quiet: false, last_pull_time: last_pull_time, last_pull_loc: Dir.pwd)
123
+
124
+ expect(display).to have_received(:display_pull_no_updates)
125
+ expect(client).not_to have_received(:download)
126
+ expect(File.read(File.join(tmp_root, "index.html"))).to eq("existing content")
127
+ end
128
+
129
+ it "reports failure when the download fails" do
130
+ allow(client).to receive(:download).with("https://test-site.neocities.org/index.html").and_return(
131
+ instance_double(Faraday::Response, status: 500, body: "")
132
+ )
68
133
 
69
- it "passes last_pull parameters to client.pull" do
70
- expect { exporter.export(quiet: true, last_pull_time: last_pull_time, last_pull_loc: last_pull_loc) }.not_to raise_error
134
+ exporter.export(quiet: false, last_pull_time: nil, last_pull_loc: nil)
71
135
 
72
- expect(client).to have_received(:pull).with(
73
- sitename, last_pull_time, last_pull_loc, quiet: true
74
- )
75
- end
136
+ expect(display).to have_received(:display_pull_failure)
137
+ expect(File.exist?(File.join(tmp_root, "index.html"))).to be(false)
76
138
  end
77
139
  end
78
140
 
79
- context "when pull raises an error" do
80
- let(:exporter) { described_class.new(client, sitename, data, config_path) }
141
+ context "when the API returns an error for info" do
142
+ let(:info_response) { { result: "error", message: "Site not found" } }
81
143
 
82
- it "stops Whirly and returns the error" do
83
- allow(Whirly).to receive(:start)
84
- allow(Whirly).to receive(:stop)
85
- allow(client).to receive(:pull).and_raise(StandardError.new("network error"))
144
+ it "raises APIError" do
145
+ expect do
146
+ exporter.export(quiet: true, last_pull_time: nil, last_pull_loc: nil)
147
+ end.to raise_error(NeocitiesRed::APIError, /Site not found/)
148
+ end
149
+ end
86
150
 
87
- result = exporter.export(quiet: true, last_pull_time: nil, last_pull_loc: nil)
151
+ context "when the API returns an error for list" do
152
+ let(:list_response) { { result: "error", message: "List failed" } }
88
153
 
89
- expect(result).to be_a(StandardError)
90
- expect(result.message).to eq("network error")
91
- expect(Whirly).to have_received(:stop)
154
+ it "raises APIError" do
155
+ expect do
156
+ exporter.export(quiet: true, last_pull_time: nil, last_pull_loc: nil)
157
+ end.to raise_error(NeocitiesRed::APIError, /List failed/)
92
158
  end
159
+ end
93
160
 
94
- it "does not raise and returns an error object" do
161
+ context "when fetching raises an error" do
162
+ it "propagates the error and stops Whirly" do
163
+ allow(client).to receive(:info).and_raise(StandardError.new("network error"))
95
164
  allow(Whirly).to receive(:start)
96
165
  allow(Whirly).to receive(:stop)
97
- allow(client).to receive(:pull).and_raise(StandardError.new("network error"))
98
166
 
99
- result = nil
100
- expect { result = exporter.export(quiet: true, last_pull_time: nil, last_pull_loc: nil) }.not_to raise_error
101
- expect(result).to be_a(StandardError)
167
+ expect do
168
+ exporter.export(quiet: true, last_pull_time: nil, last_pull_loc: nil)
169
+ end.to raise_error(StandardError, "network error")
170
+ expect(Whirly).to have_received(:stop)
102
171
  end
103
172
  end
104
173
  end
105
174
 
106
175
  describe "#initialize" do
107
176
  it "stores client, sitename, data, and app_config_path" do
108
- exporter = described_class.new(client, sitename, data, config_path)
109
-
110
177
  expect(exporter.client).to eq(client)
111
178
  expect(exporter.sitename).to eq(sitename)
112
179
  expect(exporter.data).to eq(data)
@@ -54,10 +54,10 @@ RSpec.describe NeocitiesRed::Services::Site::Informer do
54
54
  allow(client).to receive(:info).and_return(error_response)
55
55
  end
56
56
 
57
- it "raises ClientError" do
57
+ it "raises APIError with the message" do
58
58
  expect { profile_info.stats }.to raise_error(
59
- NeocitiesRed::Services::ClientError,
60
- /site_not_found/
59
+ NeocitiesRed::APIError,
60
+ /could not find site nonexistent/
61
61
  )
62
62
  end
63
63
  end
@@ -47,7 +47,7 @@ RSpec.describe NeocitiesRed::Services::Site::Pusher do
47
47
  end
48
48
 
49
49
  before do
50
- allow(NeocitiesRed::Services::File::Uploader).to receive(:new) do |_client, filepath, remote_path|
50
+ allow(NeocitiesRed::Services::File::Uploader).to receive(:new) do |_client, filepath, remote_path, _display|
51
51
  upload_calls << [filepath.to_s, remote_path.to_s]
52
52
  uploader_instance
53
53
  end
data/spec/spec_helper.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "neocities_red"
4
4
  require "fileutils"
5
- require "webmock"
5
+ require "webmock/rspec"
6
6
 
7
7
  RSpec.configure do |config|
8
8
  # Enable flags like --only-failures and --next-failure
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: neocities-red
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kyle Drake
@@ -16,20 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '2.3'
20
- - - ">="
21
- - !ruby/object:Gem::Version
22
- version: 2.14.0
19
+ version: 2.14.3
23
20
  type: :runtime
24
21
  prerelease: false
25
22
  version_requirements: !ruby/object:Gem::Requirement
26
23
  requirements:
27
24
  - - "~>"
28
25
  - !ruby/object:Gem::Version
29
- version: '2.3'
30
- - - ">="
31
- - !ruby/object:Gem::Version
32
- version: 2.14.0
26
+ version: 2.14.3
33
27
  - !ruby/object:Gem::Dependency
34
28
  name: faraday-follow_redirects
35
29
  requirement: !ruby/object:Gem::Requirement
@@ -90,9 +84,6 @@ dependencies:
90
84
  name: pastel
91
85
  requirement: !ruby/object:Gem::Requirement
92
86
  requirements:
93
- - - "~>"
94
- - !ruby/object:Gem::Version
95
- version: '0.8'
96
87
  - - '='
97
88
  - !ruby/object:Gem::Version
98
89
  version: 0.8.0
@@ -100,9 +91,6 @@ dependencies:
100
91
  prerelease: false
101
92
  version_requirements: !ruby/object:Gem::Requirement
102
93
  requirements:
103
- - - "~>"
104
- - !ruby/object:Gem::Version
105
- version: '0.8'
106
94
  - - '='
107
95
  - !ruby/object:Gem::Version
108
96
  version: 0.8.0
@@ -113,9 +101,6 @@ dependencies:
113
101
  - - "~>"
114
102
  - !ruby/object:Gem::Version
115
103
  version: '13'
116
- - - ">="
117
- - !ruby/object:Gem::Version
118
- version: 13.3.0
119
104
  type: :runtime
120
105
  prerelease: false
121
106
  version_requirements: !ruby/object:Gem::Requirement
@@ -123,9 +108,6 @@ dependencies:
123
108
  - - "~>"
124
109
  - !ruby/object:Gem::Version
125
110
  version: '13'
126
- - - ">="
127
- - !ruby/object:Gem::Version
128
- version: 13.3.0
129
111
  - !ruby/object:Gem::Dependency
130
112
  name: thor
131
113
  requirement: !ruby/object:Gem::Requirement
@@ -150,9 +132,6 @@ dependencies:
150
132
  name: tty-prompt
151
133
  requirement: !ruby/object:Gem::Requirement
152
134
  requirements:
153
- - - "~>"
154
- - !ruby/object:Gem::Version
155
- version: '0.23'
156
135
  - - '='
157
136
  - !ruby/object:Gem::Version
158
137
  version: 0.23.1
@@ -160,9 +139,6 @@ dependencies:
160
139
  prerelease: false
161
140
  version_requirements: !ruby/object:Gem::Requirement
162
141
  requirements:
163
- - - "~>"
164
- - !ruby/object:Gem::Version
165
- version: '0.23'
166
142
  - - '='
167
143
  - !ruby/object:Gem::Version
168
144
  version: 0.23.1
@@ -170,9 +146,6 @@ dependencies:
170
146
  name: tty-table
171
147
  requirement: !ruby/object:Gem::Requirement
172
148
  requirements:
173
- - - "~>"
174
- - !ruby/object:Gem::Version
175
- version: '0.12'
176
149
  - - '='
177
150
  - !ruby/object:Gem::Version
178
151
  version: 0.12.0
@@ -180,9 +153,6 @@ dependencies:
180
153
  prerelease: false
181
154
  version_requirements: !ruby/object:Gem::Requirement
182
155
  requirements:
183
- - - "~>"
184
- - !ruby/object:Gem::Version
185
- version: '0.12'
186
156
  - - '='
187
157
  - !ruby/object:Gem::Version
188
158
  version: 0.12.0
@@ -219,6 +189,7 @@ files:
219
189
  - ".gitignore"
220
190
  - ".rspec"
221
191
  - ".rubocop.yml"
192
+ - CHANGELOG.md
222
193
  - CODE_OF_CONDUCT.md
223
194
  - CONTRIBUTING.md
224
195
  - Gemfile
@@ -232,7 +203,10 @@ files:
232
203
  - lib/neocities_red/cli.rb
233
204
  - lib/neocities_red/cli_display.rb
234
205
  - lib/neocities_red/client.rb
206
+ - lib/neocities_red/errors.rb
207
+ - lib/neocities_red/services/common/exclusions.rb
235
208
  - lib/neocities_red/services/common/pizza.rb
209
+ - lib/neocities_red/services/common/worker_pool.rb
236
210
  - lib/neocities_red/services/file/folder_uploader.rb
237
211
  - lib/neocities_red/services/file/list.rb
238
212
  - lib/neocities_red/services/file/remover.rb
@@ -246,7 +220,9 @@ files:
246
220
  - spec/neocities_red/cli_display_spec.rb
247
221
  - spec/neocities_red/cli_spec.rb
248
222
  - spec/neocities_red/client_spec.rb
223
+ - spec/neocities_red/services/common/exclusions_spec.rb
249
224
  - spec/neocities_red/services/common/pizza_spec.rb
225
+ - spec/neocities_red/services/common/worker_pool_spec.rb
250
226
  - spec/neocities_red/services/file/folder_uploader_spec.rb
251
227
  - spec/neocities_red/services/file/list_spec.rb
252
228
  - spec/neocities_red/services/file/remover_spec.rb
@@ -261,6 +237,10 @@ licenses:
261
237
  - MIT
262
238
  metadata:
263
239
  rubygems_mfa_required: 'true'
240
+ homepage_uri: https://github.com/o-200/neocities-red
241
+ source_code_uri: https://github.com/o-200/neocities-red
242
+ changelog_uri: https://github.com/o-200/neocities-red/blob/main/CHANGELOG.md
243
+ documentation_uri: https://github.com/o-200/neocities-red#readme
264
244
  rdoc_options: []
265
245
  require_paths:
266
246
  - lib
@@ -275,7 +255,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
275
255
  - !ruby/object:Gem::Version
276
256
  version: '0'
277
257
  requirements: []
278
- rubygems_version: 4.0.3
258
+ rubygems_version: 4.0.16
279
259
  specification_version: 4
280
260
  summary: Yet Another Neocities.org CLI and API client with improvements
281
261
  test_files: []