neocities-red 1.1.2 → 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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +29 -15
- data/.gitignore +4 -0
- data/CHANGELOG.md +95 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +9 -6
- data/README.md +6 -0
- data/lib/neocities_red/cli.rb +156 -31
- data/lib/neocities_red/cli_display.rb +200 -5
- data/lib/neocities_red/client.rb +84 -77
- data/lib/neocities_red/errors.rb +20 -0
- data/lib/neocities_red/services/common/exclusions.rb +69 -0
- data/lib/neocities_red/services/common/pizza.rb +34 -21
- data/lib/neocities_red/services/common/worker_pool.rb +54 -0
- data/lib/neocities_red/services/file/folder_uploader.rb +39 -25
- data/lib/neocities_red/services/file/list.rb +60 -30
- data/lib/neocities_red/services/file/remover.rb +27 -12
- data/lib/neocities_red/services/file/uploader.rb +33 -13
- data/lib/neocities_red/services/site/differencer.rb +42 -12
- data/lib/neocities_red/services/site/exporter.rb +120 -7
- data/lib/neocities_red/services/site/informer.rb +29 -4
- data/lib/neocities_red/services/site/pusher.rb +74 -38
- data/lib/neocities_red/version.rb +2 -1
- data/lib/neocities_red.rb +32 -13
- data/neocities-red.gemspec +11 -6
- data/spec/neocities_red/cli_display_spec.rb +0 -6
- data/spec/neocities_red/cli_spec.rb +85 -0
- data/spec/neocities_red/client_spec.rb +7 -50
- data/spec/neocities_red/services/common/exclusions_spec.rb +47 -0
- data/spec/neocities_red/services/common/worker_pool_spec.rb +48 -0
- data/spec/neocities_red/services/file/folder_uploader_spec.rb +8 -6
- data/spec/neocities_red/services/file/list_spec.rb +13 -10
- data/spec/neocities_red/services/file/remover_spec.rb +15 -21
- data/spec/neocities_red/services/file/uploader_spec.rb +25 -20
- data/spec/neocities_red/services/site/differencer_spec.rb +32 -6
- data/spec/neocities_red/services/site/exporter_spec.rb +105 -38
- data/spec/neocities_red/services/site/informer_spec.rb +3 -3
- data/spec/neocities_red/services/site/pusher_spec.rb +1 -1
- data/spec/spec_helper.rb +1 -1
- metadata +11 -25
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bf561591a6a553947115437ac9ed12e84f72fc4d14ea9532fe3d3465584db764
|
|
4
|
+
data.tar.gz: 9b6b0f645578532ee3d647a8b5f87d38aca66ebd0580f8347c2e39ff4cabee18
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d2c2245f54b0afeb60684c05f5d9d7cb67c0f4d1f0a479472a4a75db1fc95587521d7e7ea3e98f9372296e9ec007dcaba2e970eb3234006f082e054dbcc16ab9
|
|
7
|
+
data.tar.gz: 3d01aee0ffd3d92dd8128dfa6e5cfdfd113df1564b983951c01f350f8d62f4115cc788fe95629d55c5f3aa16f9cb9b4a8f642b47129d9695b82f36610128217a
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -8,17 +8,21 @@ on:
|
|
|
8
8
|
|
|
9
9
|
jobs:
|
|
10
10
|
lint:
|
|
11
|
-
name: Lint (
|
|
11
|
+
name: Lint (Ruby ${{ matrix.ruby-version }})
|
|
12
12
|
runs-on: ubuntu-latest
|
|
13
|
+
strategy:
|
|
14
|
+
fail-fast: false
|
|
15
|
+
matrix:
|
|
16
|
+
ruby-version: ['3.4', '3.5', '4.0']
|
|
13
17
|
|
|
14
18
|
steps:
|
|
15
19
|
- name: Checkout repository
|
|
16
20
|
uses: actions/checkout@v4
|
|
17
21
|
|
|
18
|
-
- name: Set up Ruby
|
|
22
|
+
- name: Set up Ruby ${{ matrix.ruby-version }}
|
|
19
23
|
uses: ruby/setup-ruby@v1
|
|
20
24
|
with:
|
|
21
|
-
ruby-version:
|
|
25
|
+
ruby-version: ${{ matrix.ruby-version }}
|
|
22
26
|
bundler-cache: true
|
|
23
27
|
|
|
24
28
|
- name: Debug versions
|
|
@@ -31,59 +35,65 @@ jobs:
|
|
|
31
35
|
run: bundle exec rubocop
|
|
32
36
|
|
|
33
37
|
test_linux:
|
|
34
|
-
name: Test (ubuntu-latest)
|
|
38
|
+
name: Test (Ruby ${{ matrix.ruby-version }}, ubuntu-latest)
|
|
35
39
|
runs-on: ubuntu-latest
|
|
40
|
+
strategy:
|
|
41
|
+
fail-fast: false
|
|
42
|
+
matrix:
|
|
43
|
+
ruby-version: ['3.4', '3.5', '4.0']
|
|
36
44
|
|
|
37
45
|
steps:
|
|
38
46
|
- name: Checkout repository
|
|
39
47
|
uses: actions/checkout@v4
|
|
40
48
|
|
|
41
|
-
- name: Set up Ruby
|
|
49
|
+
- name: Set up Ruby ${{ matrix.ruby-version }}
|
|
42
50
|
uses: ruby/setup-ruby@v1
|
|
43
51
|
with:
|
|
44
|
-
ruby-version:
|
|
52
|
+
ruby-version: ${{ matrix.ruby-version }}
|
|
45
53
|
bundler-cache: true
|
|
46
54
|
|
|
47
55
|
- name: Run tests
|
|
48
56
|
run: bundle exec rspec
|
|
49
57
|
|
|
50
58
|
test_non_linux:
|
|
51
|
-
name: Test (${{ matrix.os }})
|
|
59
|
+
name: Test (Ruby ${{ matrix.ruby-version }}, ${{ matrix.os }})
|
|
52
60
|
runs-on: ${{ matrix.os }}
|
|
53
61
|
strategy:
|
|
54
62
|
fail-fast: false
|
|
55
63
|
matrix:
|
|
56
64
|
os: [macos-latest, windows-latest]
|
|
65
|
+
ruby-version: ['3.4', '4.0']
|
|
57
66
|
|
|
58
67
|
steps:
|
|
59
68
|
- name: Checkout repository
|
|
60
69
|
uses: actions/checkout@v4
|
|
61
70
|
|
|
62
|
-
- name: Set up Ruby
|
|
71
|
+
- name: Set up Ruby ${{ matrix.ruby-version }}
|
|
63
72
|
uses: ruby/setup-ruby@v1
|
|
64
73
|
with:
|
|
65
|
-
ruby-version:
|
|
74
|
+
ruby-version: ${{ matrix.ruby-version }}
|
|
66
75
|
bundler-cache: true
|
|
67
76
|
|
|
68
77
|
- name: Run tests
|
|
69
78
|
run: bundle exec rspec
|
|
70
79
|
|
|
71
80
|
smoke_unix:
|
|
72
|
-
name: Start CLI (${{ matrix.os }})
|
|
81
|
+
name: Start CLI (Ruby ${{ matrix.ruby-version }}, ${{ matrix.os }})
|
|
73
82
|
runs-on: ${{ matrix.os }}
|
|
74
83
|
strategy:
|
|
75
84
|
fail-fast: false
|
|
76
85
|
matrix:
|
|
77
86
|
os: [ubuntu-latest, macos-latest]
|
|
87
|
+
ruby-version: ['3.4', '4.0']
|
|
78
88
|
|
|
79
89
|
steps:
|
|
80
90
|
- name: Checkout repository
|
|
81
91
|
uses: actions/checkout@v4
|
|
82
92
|
|
|
83
|
-
- name: Set up Ruby
|
|
93
|
+
- name: Set up Ruby ${{ matrix.ruby-version }}
|
|
84
94
|
uses: ruby/setup-ruby@v1
|
|
85
95
|
with:
|
|
86
|
-
ruby-version:
|
|
96
|
+
ruby-version: ${{ matrix.ruby-version }}
|
|
87
97
|
bundler-cache: true
|
|
88
98
|
|
|
89
99
|
- name: Start CLI
|
|
@@ -92,17 +102,21 @@ jobs:
|
|
|
92
102
|
bundle exec bin/neocities-red
|
|
93
103
|
|
|
94
104
|
smoke_windows:
|
|
95
|
-
name: Start CLI (windows-latest)
|
|
105
|
+
name: Start CLI (Ruby ${{ matrix.ruby-version }}, windows-latest)
|
|
96
106
|
runs-on: windows-latest
|
|
107
|
+
strategy:
|
|
108
|
+
fail-fast: false
|
|
109
|
+
matrix:
|
|
110
|
+
ruby-version: ['3.4', '4.0']
|
|
97
111
|
|
|
98
112
|
steps:
|
|
99
113
|
- name: Checkout repository
|
|
100
114
|
uses: actions/checkout@v4
|
|
101
115
|
|
|
102
|
-
- name: Set up Ruby
|
|
116
|
+
- name: Set up Ruby ${{ matrix.ruby-version }}
|
|
103
117
|
uses: ruby/setup-ruby@v1
|
|
104
118
|
with:
|
|
105
|
-
ruby-version:
|
|
119
|
+
ruby-version: ${{ matrix.ruby-version }}
|
|
106
120
|
bundler-cache: true
|
|
107
121
|
|
|
108
122
|
- name: Start CLI
|
data/.gitignore
CHANGED
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [1.2.0] — 2026-08-04
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Major architecture refactor with namespaced services (`file/`, `site/`, `common/`)
|
|
15
|
+
- Renamed gem to `neocities-red`
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- YARD documentation
|
|
20
|
+
|
|
21
|
+
### Removed
|
|
22
|
+
|
|
23
|
+
- Unused legacy code
|
|
24
|
+
|
|
25
|
+
## [1.1.2] — 2026-08-04
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- Bumped rubocop to 1.88.2, rubocop-rspec to 3.10.2, faraday to 2.14.3
|
|
30
|
+
|
|
31
|
+
## [1.1.1] — 2026-06-22
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
|
|
35
|
+
- Config for Windows/FreeBSD platforms
|
|
36
|
+
- Purge no longer removes already-removed files
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
40
|
+
- `upload` accepts a single parameter
|
|
41
|
+
- Bumped rake to 13.4.2
|
|
42
|
+
|
|
43
|
+
## [1.1.0] — 2026-04-25
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
|
|
47
|
+
- Added Rails/Thor CLI framework, refactored `cli.rb`
|
|
48
|
+
|
|
49
|
+
## [1.0.6] — 2026-04-14
|
|
50
|
+
|
|
51
|
+
### Fixed
|
|
52
|
+
|
|
53
|
+
- Tests and lint fixes
|
|
54
|
+
- Added `faraday-retry` for flaky API/SSL handling
|
|
55
|
+
|
|
56
|
+
## [1.0.4] — 2026-03-29
|
|
57
|
+
|
|
58
|
+
### Changed
|
|
59
|
+
|
|
60
|
+
- CLI version display updated
|
|
61
|
+
|
|
62
|
+
## [1.0.2] — 2026-02-11
|
|
63
|
+
|
|
64
|
+
### Added
|
|
65
|
+
|
|
66
|
+
- Multithreaded parallel uploads
|
|
67
|
+
- Fixed recursive uploading in `upload` method
|
|
68
|
+
|
|
69
|
+
## [1.0.1] — 2026-02-07
|
|
70
|
+
|
|
71
|
+
### Added
|
|
72
|
+
|
|
73
|
+
- Folder uploading support
|
|
74
|
+
|
|
75
|
+
## [1.0.0] — 2026-02-11
|
|
76
|
+
|
|
77
|
+
### Added
|
|
78
|
+
|
|
79
|
+
- Initial release of `neocities-red` (fork of `neocities-ruby`)
|
|
80
|
+
- Recursive uploads with smart diffing
|
|
81
|
+
- `push`, `upload`, `delete`, `diff`, `list`, `info`, `pull` commands
|
|
82
|
+
- Parallel upload workers
|
|
83
|
+
- Automatic retries
|
|
84
|
+
- Config file with `NEOCITIES_API_KEY` support
|
|
85
|
+
|
|
86
|
+
[Unreleased]: https://github.com/o-200/neocities-red/compare/v1.2.0...HEAD
|
|
87
|
+
[1.2.0]: https://github.com/o-200/neocities-red/compare/v1.1.2...v1.2.0
|
|
88
|
+
[1.1.2]: https://github.com/o-200/neocities-red/compare/v1.1.1...v1.1.2
|
|
89
|
+
[1.1.1]: https://github.com/o-200/neocities-red/compare/v1.1.0...v1.1.1
|
|
90
|
+
[1.1.0]: https://github.com/o-200/neocities-red/compare/v1.0.6...v1.1.0
|
|
91
|
+
[1.0.6]: https://github.com/o-200/neocities-red/compare/v1.0.4...v1.0.6
|
|
92
|
+
[1.0.4]: https://github.com/o-200/neocities-red/compare/v1.0.2...v1.0.4
|
|
93
|
+
[1.0.2]: https://github.com/o-200/neocities-red/compare/v1.0.1...v1.0.2
|
|
94
|
+
[1.0.1]: https://github.com/o-200/neocities-red/compare/v1.0.0...v1.0.1
|
|
95
|
+
[1.0.0]: https://github.com/o-200/neocities-red/releases/tag/v1.0.0
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
neocities-red (1.
|
|
4
|
+
neocities-red (1.2.0)
|
|
5
5
|
faraday (~> 2.14.3)
|
|
6
6
|
faraday-follow_redirects
|
|
7
7
|
faraday-multipart
|
|
8
8
|
faraday-retry
|
|
9
9
|
fiddle
|
|
10
|
-
pastel (
|
|
11
|
-
rake (~> 13
|
|
10
|
+
pastel (= 0.8.0)
|
|
11
|
+
rake (~> 13)
|
|
12
12
|
thor (~> 1.5.0, >= 1.5.0)
|
|
13
|
-
tty-prompt (
|
|
14
|
-
tty-table (
|
|
13
|
+
tty-prompt (= 0.23.1)
|
|
14
|
+
tty-table (= 0.12.0)
|
|
15
15
|
whirly (~> 0.3, >= 0.3.0)
|
|
16
16
|
|
|
17
17
|
GEM
|
|
@@ -122,6 +122,7 @@ GEM
|
|
|
122
122
|
json
|
|
123
123
|
unicode-display_width (>= 1.1)
|
|
124
124
|
wisper (2.0.1)
|
|
125
|
+
yard (0.9.45)
|
|
125
126
|
|
|
126
127
|
PLATFORMS
|
|
127
128
|
ruby
|
|
@@ -133,6 +134,7 @@ DEPENDENCIES
|
|
|
133
134
|
rubocop (~> 1.88.2)
|
|
134
135
|
rubocop-rspec (~> 3.10.2)
|
|
135
136
|
webmock
|
|
137
|
+
yard
|
|
136
138
|
|
|
137
139
|
CHECKSUMS
|
|
138
140
|
addressable (2.9.0) sha256=7fdf6ac3660f7f4e867a0838be3f6cf722ace541dd97767fa42bc6cfa980c7af
|
|
@@ -153,7 +155,7 @@ CHECKSUMS
|
|
|
153
155
|
lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
|
|
154
156
|
logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
|
|
155
157
|
multipart-post (2.4.1) sha256=9872d03a8e552020ca096adadbf5e3cb1cd1cdd6acd3c161136b8a5737cdb4a8
|
|
156
|
-
neocities-red (1.
|
|
158
|
+
neocities-red (1.2.0)
|
|
157
159
|
net-http (0.9.1) sha256=25ba0b67c63e89df626ed8fac771d0ad24ad151a858af2cc8e6a716ca4336996
|
|
158
160
|
parallel (2.1.0) sha256=b35258865c2e31134c5ecb708beaaf6772adf9d5efae28e93e99260877b09356
|
|
159
161
|
parser (3.3.12.0) sha256=21a6d7f755d5a24dfbdc6e6b772e4e879a52e7631a88bc5a3a134606052c9828
|
|
@@ -189,6 +191,7 @@ CHECKSUMS
|
|
|
189
191
|
webmock (3.26.2) sha256=774556f2ea6371846cca68c01769b2eac0d134492d21f6d0ab5dd643965a4c90
|
|
190
192
|
whirly (0.4.0) sha256=3ffdf9097e711097442f6d83c91b8fc431d73224863f75b48f06fb850b3e596e
|
|
191
193
|
wisper (2.0.1) sha256=ce17bc5c3a166f241a2e6613848b025c8146fce2defba505920c1d1f3f88fae6
|
|
194
|
+
yard (0.9.45) sha256=52e211493f7cb8a3ebf7e104a25a1e73937a3103092545d34cb88fafebb3dc51
|
|
192
195
|
|
|
193
196
|
BUNDLED WITH
|
|
194
197
|
4.0.17
|
data/README.md
CHANGED
data/lib/neocities_red/cli.rb
CHANGED
|
@@ -9,6 +9,21 @@ require "thor"
|
|
|
9
9
|
require_relative "cli_display"
|
|
10
10
|
|
|
11
11
|
module NeocitiesRed
|
|
12
|
+
# Thor-based command-line interface for the NeocitiesRed gem.
|
|
13
|
+
#
|
|
14
|
+
# Provides subcommands for managing a Neocities site: push, upload,
|
|
15
|
+
# delete, diff, list, info, pull, purge, logout, and pizza.
|
|
16
|
+
#
|
|
17
|
+
# Authentication is handled lazily — the first command that requires
|
|
18
|
+
# an API connection will prompt for credentials (or read from config/env).
|
|
19
|
+
#
|
|
20
|
+
# @example Running from shell
|
|
21
|
+
# $ neocities-red push .
|
|
22
|
+
# $ neocities-red list -a
|
|
23
|
+
# $ neocities-red diff --ignore-dotfiles
|
|
24
|
+
#
|
|
25
|
+
# @see NeocitiesRed::Client Underlying API client
|
|
26
|
+
# @see NeocitiesRed::CliDisplay Terminal output helper
|
|
12
27
|
class CLI < Thor
|
|
13
28
|
package_name "neocities-red"
|
|
14
29
|
default_task :help
|
|
@@ -23,11 +38,17 @@ module NeocitiesRed
|
|
|
23
38
|
method_option :help, aliases: "-h", type: :boolean
|
|
24
39
|
method_option :ignore_dotfiles, type: :boolean, default: false
|
|
25
40
|
method_option :exclude, aliases: "-e", type: :string, repeatable: true, default: []
|
|
41
|
+
|
|
42
|
+
# Compares local files with the remote Neocities site and displays
|
|
43
|
+
# added, modified, and removed files.
|
|
44
|
+
#
|
|
45
|
+
# @param path [String] local directory path to compare (defaults to current directory)
|
|
46
|
+
# @return [void]
|
|
26
47
|
def diff(path = ".")
|
|
27
48
|
return display_help_for("diff") if help_requested?(options[:help], path)
|
|
28
49
|
|
|
29
50
|
client = ensure_client!
|
|
30
|
-
exclude =
|
|
51
|
+
exclude = Services::Common::Exclusions.build(Array(options[:exclude]), base_path: path)
|
|
31
52
|
|
|
32
53
|
added, modified, removed = Services::Site::Differencer.new(
|
|
33
54
|
client,
|
|
@@ -42,16 +63,27 @@ module NeocitiesRed
|
|
|
42
63
|
|
|
43
64
|
desc "delete PATH [PATH ...]", "Delete files on your Neocities site"
|
|
44
65
|
method_option :help, aliases: "-h", type: :boolean
|
|
66
|
+
|
|
67
|
+
# Deletes one or more files from the remote Neocities site.
|
|
68
|
+
#
|
|
69
|
+
# @param paths [Array<String>] remote file paths to delete
|
|
70
|
+
# @return [void]
|
|
45
71
|
def delete(*paths)
|
|
46
72
|
return display_help_for("delete") if paths.empty? || help_requested?(options[:help], paths)
|
|
47
73
|
|
|
48
74
|
client = ensure_client!
|
|
49
|
-
paths.each { |path| Services::File::Remover.new(client, path).remove }
|
|
75
|
+
paths.each { |path| Services::File::Remover.new(client, path, display: display).remove }
|
|
50
76
|
end
|
|
51
77
|
|
|
52
78
|
desc "logout", "Remove the site api key from the config"
|
|
53
79
|
method_option :help, aliases: "-h", type: :boolean
|
|
54
80
|
method_option :yes, aliases: "-y", type: :boolean, default: false
|
|
81
|
+
|
|
82
|
+
# Removes the stored API key from the local config file.
|
|
83
|
+
#
|
|
84
|
+
# Requires the +--yes+ / +-y+ flag to confirm the action.
|
|
85
|
+
#
|
|
86
|
+
# @return [void]
|
|
55
87
|
def logout
|
|
56
88
|
return display_help_for("logout") if help_requested?(options[:help]) || !options[:yes]
|
|
57
89
|
|
|
@@ -61,6 +93,13 @@ module NeocitiesRed
|
|
|
61
93
|
|
|
62
94
|
desc "info [SITENAME]", "Get site info"
|
|
63
95
|
method_option :help, aliases: "-h", type: :boolean
|
|
96
|
+
|
|
97
|
+
# Displays information and statistics for a Neocities site.
|
|
98
|
+
#
|
|
99
|
+
# @param sitename [String, nil] site name to query; defaults to the
|
|
100
|
+
# currently authenticated site when omitted
|
|
101
|
+
# @return [void]
|
|
102
|
+
# @raise [NeocitiesRed::APIError] if the API request fails
|
|
64
103
|
def info(sitename = nil)
|
|
65
104
|
return display_help_for("info") if help_requested?(options[:help], sitename)
|
|
66
105
|
|
|
@@ -75,6 +114,13 @@ module NeocitiesRed
|
|
|
75
114
|
method_option :help, aliases: "-h", type: :boolean
|
|
76
115
|
method_option :detail, aliases: "-d", type: :boolean, default: false
|
|
77
116
|
method_option :all, aliases: "-a", type: :boolean, default: false
|
|
117
|
+
|
|
118
|
+
# Lists files on the remote Neocities site.
|
|
119
|
+
#
|
|
120
|
+
# @param path [String, nil] remote directory path to list (nil for root,
|
|
121
|
+
# or when +--all+ is used)
|
|
122
|
+
# @return [void]
|
|
123
|
+
# @raise [NeocitiesRed::APIError] if the API request fails
|
|
78
124
|
def list(path = nil)
|
|
79
125
|
if help_requested?(options[:help], path) || (path.nil? && options[:all].nil? && options[:detail].nil?)
|
|
80
126
|
display_help_for("list")
|
|
@@ -83,7 +129,9 @@ module NeocitiesRed
|
|
|
83
129
|
|
|
84
130
|
client = ensure_client!
|
|
85
131
|
path = nil if options[:all]
|
|
86
|
-
display.say Services::File::List.new(client, path, options[:detail]).show
|
|
132
|
+
display.say Services::File::List.new(client, path, options[:detail], display: display).show
|
|
133
|
+
rescue NeocitiesRed::APIError => e
|
|
134
|
+
display.display_response(e)
|
|
87
135
|
end
|
|
88
136
|
|
|
89
137
|
desc "push PATH", "Recursively upload a local directory to your Neocities site"
|
|
@@ -94,6 +142,18 @@ module NeocitiesRed
|
|
|
94
142
|
method_option :dry_run, type: :boolean, default: false
|
|
95
143
|
method_option :prune, type: :boolean, default: false
|
|
96
144
|
method_option :optimized, type: :boolean, default: false
|
|
145
|
+
|
|
146
|
+
# Recursively uploads a local directory to the Neocities site.
|
|
147
|
+
#
|
|
148
|
+
# Supports +--no-gitignore+ to ignore .gitignore rules,
|
|
149
|
+
# +--ignore-dotfiles+ to skip dot-prefixed files,
|
|
150
|
+
# +--exclude+ to skip specific paths, +--dry-run+ to preview changes,
|
|
151
|
+
# +--prune+ to delete remote files not present locally, and
|
|
152
|
+
# +--optimized+ to skip files whose SHA1 hash matches the server.
|
|
153
|
+
#
|
|
154
|
+
# @param root [String, nil] local directory path to upload
|
|
155
|
+
# @return [void]
|
|
156
|
+
# @raise [ArgumentError] if the path does not exist or is not a directory
|
|
97
157
|
def push(root = nil)
|
|
98
158
|
return display_help_for("push") if help_requested?(options[:help], root)
|
|
99
159
|
|
|
@@ -118,6 +178,15 @@ module NeocitiesRed
|
|
|
118
178
|
|
|
119
179
|
desc "upload LOCAL_PATH [REMOTE_PATH]", "Upload a file/folder to your Neocities site"
|
|
120
180
|
method_option :help, aliases: "-h", type: :boolean
|
|
181
|
+
|
|
182
|
+
# Uploads a single file or an entire folder to the Neocities site.
|
|
183
|
+
#
|
|
184
|
+
# When +LOCAL_PATH+ is a file, uploads it directly.
|
|
185
|
+
# When it is a directory, uploads all files within it in parallel.
|
|
186
|
+
#
|
|
187
|
+
# @param local_path [String, nil] local file or directory path
|
|
188
|
+
# @param remote_path [String, nil] remote destination; defaults to the basename of +local_path+
|
|
189
|
+
# @return [void]
|
|
121
190
|
def upload(local_path = nil, remote_path = nil)
|
|
122
191
|
return display_help_for("upload") if help_requested?(options[:help], [local_path, remote_path])
|
|
123
192
|
return display_help_for("upload") if local_path.nil?
|
|
@@ -125,9 +194,9 @@ module NeocitiesRed
|
|
|
125
194
|
client = ensure_client!
|
|
126
195
|
dest = remote_path || File.basename(local_path)
|
|
127
196
|
if File.file?(local_path)
|
|
128
|
-
Services::File::Uploader.new(client, local_path, dest).upload
|
|
197
|
+
Services::File::Uploader.new(client, local_path, dest, display: display).upload
|
|
129
198
|
elsif File.directory?(local_path)
|
|
130
|
-
folder_uploader = Services::File::FolderUploader.new(client, local_path, dest)
|
|
199
|
+
folder_uploader = Services::File::FolderUploader.new(client, local_path, dest, display: display)
|
|
131
200
|
files_list = folder_uploader.files
|
|
132
201
|
folder_uploader.upload(files_list)
|
|
133
202
|
end
|
|
@@ -136,6 +205,15 @@ module NeocitiesRed
|
|
|
136
205
|
desc "pull", "Get the most recent version of files from your site"
|
|
137
206
|
method_option :help, aliases: "-h", type: :boolean
|
|
138
207
|
method_option :quiet, aliases: "-q", type: :boolean, default: false
|
|
208
|
+
|
|
209
|
+
# Downloads the latest version of site files from the remote Neocities site.
|
|
210
|
+
#
|
|
211
|
+
# Skips files that haven't changed since the last pull (based on stored
|
|
212
|
+
# timestamp and working directory). Use +--quiet+ to suppress per-file
|
|
213
|
+
# output and show a spinner instead.
|
|
214
|
+
#
|
|
215
|
+
# @return [void]
|
|
216
|
+
# @raise [StandardError] on network or API errors
|
|
139
217
|
def pull
|
|
140
218
|
return display_help_for("pull") if help_requested?(options[:help])
|
|
141
219
|
|
|
@@ -145,16 +223,27 @@ module NeocitiesRed
|
|
|
145
223
|
last_pull_time = data.dig("LAST_PULL", "time")
|
|
146
224
|
last_pull_loc = data.dig("LAST_PULL", "loc")
|
|
147
225
|
|
|
148
|
-
Services::Site::Exporter.new(client, @sitename, data, app_config_path)
|
|
226
|
+
Services::Site::Exporter.new(client, @sitename, data, app_config_path, display: display)
|
|
149
227
|
.export(quiet: options[:quiet], last_pull_time: last_pull_time, last_pull_loc: last_pull_loc)
|
|
228
|
+
rescue StandardError => e
|
|
229
|
+
display.display_response(e)
|
|
150
230
|
end
|
|
151
231
|
|
|
152
232
|
desc "purge", "Delete everything from your site (development only)"
|
|
153
233
|
method_option :yes, aliases: "-y", type: :boolean, default: false
|
|
234
|
+
method_option :dry_run, type: :boolean, default: false
|
|
235
|
+
|
|
236
|
+
# Deletes all files from the Neocities site.
|
|
237
|
+
#
|
|
238
|
+
# Requires the +--yes+ / +-y+ flag to confirm the destructive action.
|
|
239
|
+
# Use +--dry-run+ to preview what would be deleted without changes.
|
|
240
|
+
#
|
|
241
|
+
# @return [void]
|
|
154
242
|
def purge
|
|
155
243
|
return display_help_for("purge") unless options[:yes]
|
|
156
244
|
|
|
157
245
|
client = ensure_client!
|
|
246
|
+
display.display_dry_run_notice if options[:dry_run]
|
|
158
247
|
resp = client.list
|
|
159
248
|
deleted_dirs = []
|
|
160
249
|
resp[:files].sort_by { |f| f[:is_directory] ? 0 : 1 }.each do |file|
|
|
@@ -173,10 +262,19 @@ module NeocitiesRed
|
|
|
173
262
|
end
|
|
174
263
|
|
|
175
264
|
desc "pizza", "Order a free pizza"
|
|
265
|
+
|
|
266
|
+
# Easter egg — displays a humorous pizza-related excuse.
|
|
267
|
+
#
|
|
268
|
+
# @return [void]
|
|
176
269
|
def pizza
|
|
177
270
|
display_help_for(__method__)
|
|
178
271
|
end
|
|
179
272
|
|
|
273
|
+
# Returns the platform-specific application config directory path.
|
|
274
|
+
#
|
|
275
|
+
# @param name [String] application name (e.g. "neocities")
|
|
276
|
+
# @return [String, nil] full path to the config directory, or nil if
|
|
277
|
+
# the platform cannot be determined
|
|
180
278
|
def self.app_config_path(name)
|
|
181
279
|
platform = case RUBY_PLATFORM
|
|
182
280
|
when /cygwin|mswin|mingw|bccwin|wince|emx|win32/
|
|
@@ -217,6 +315,12 @@ module NeocitiesRed
|
|
|
217
315
|
end
|
|
218
316
|
|
|
219
317
|
desc "help [COMMAND]", "Show help for a command"
|
|
318
|
+
|
|
319
|
+
# Displays help for a specific command or the general help screen.
|
|
320
|
+
#
|
|
321
|
+
# @param command [String, nil] command name to show help for;
|
|
322
|
+
# nil displays the main help screen
|
|
323
|
+
# @return [void]
|
|
220
324
|
def help(command = nil)
|
|
221
325
|
return display.display_help_and_exit if command.nil?
|
|
222
326
|
|
|
@@ -227,6 +331,10 @@ module NeocitiesRed
|
|
|
227
331
|
end
|
|
228
332
|
|
|
229
333
|
desc "version", "Display neocities-red version"
|
|
334
|
+
|
|
335
|
+
# Prints the current gem version to stdout.
|
|
336
|
+
#
|
|
337
|
+
# @return [void]
|
|
230
338
|
def version
|
|
231
339
|
display.say NeocitiesRed::VERSION
|
|
232
340
|
end
|
|
@@ -234,18 +342,30 @@ module NeocitiesRed
|
|
|
234
342
|
no_commands do
|
|
235
343
|
alias_method :display_help_for, :help
|
|
236
344
|
|
|
345
|
+
# Returns the initialized {CliDisplay} instance.
|
|
346
|
+
#
|
|
347
|
+
# @return [NeocitiesRed::CliDisplay]
|
|
237
348
|
def display
|
|
238
349
|
@display ||= NeocitiesRed::CliDisplay.new
|
|
239
350
|
end
|
|
240
351
|
|
|
352
|
+
# Returns the initialized TTY::Prompt instance for interactive input.
|
|
353
|
+
#
|
|
354
|
+
# @return [TTY::Prompt]
|
|
241
355
|
def prompt
|
|
242
356
|
@prompt ||= TTY::Prompt.new
|
|
243
357
|
end
|
|
244
358
|
|
|
359
|
+
# Returns the full path to the application config file.
|
|
360
|
+
#
|
|
361
|
+
# @return [String] path to +config.json+ inside the platform config directory
|
|
245
362
|
def app_config_path
|
|
246
363
|
@app_config_path ||= File.join(self.class.app_config_path("neocities"), "config.json")
|
|
247
364
|
end
|
|
248
365
|
|
|
366
|
+
# Reads and parses the JSON config file from disk.
|
|
367
|
+
#
|
|
368
|
+
# @return [Hash, nil] parsed config hash, or nil if the file does not exist
|
|
249
369
|
def read_config
|
|
250
370
|
file = File.read(app_config_path)
|
|
251
371
|
JSON.parse(file)
|
|
@@ -253,12 +373,17 @@ module NeocitiesRed
|
|
|
253
373
|
nil
|
|
254
374
|
end
|
|
255
375
|
|
|
376
|
+
# Lazily initializes and returns an authenticated {Client} instance.
|
|
377
|
+
#
|
|
378
|
+
# Reads the API key from (in order): CLI option, environment variable,
|
|
379
|
+
# or stored config. If no key is found, triggers interactive login.
|
|
380
|
+
#
|
|
381
|
+
# @return [NeocitiesRed::Client]
|
|
256
382
|
def ensure_client!
|
|
257
383
|
return @client if @client
|
|
258
384
|
|
|
259
385
|
config = read_config
|
|
260
386
|
@sitename = config && config["SITENAME"]
|
|
261
|
-
@last_pull = config && config["LAST_PULL"]
|
|
262
387
|
|
|
263
388
|
@api_key = options[:api_key] || ENV.fetch("NEOCITIES_API_KEY", nil)
|
|
264
389
|
@api_key ||= config && config["API_KEY"]&.strip
|
|
@@ -272,6 +397,13 @@ module NeocitiesRed
|
|
|
272
397
|
@client
|
|
273
398
|
end
|
|
274
399
|
|
|
400
|
+
# Prompts the user for credentials, obtains an API key, and stores it.
|
|
401
|
+
#
|
|
402
|
+
# Saves the API key and sitename to the local config file with
|
|
403
|
+
# restricted permissions (0600).
|
|
404
|
+
#
|
|
405
|
+
# @return [void]
|
|
406
|
+
# @raise [Thor::Error] if the API key cannot be obtained
|
|
275
407
|
def authenticate_and_persist_key!
|
|
276
408
|
display.display_login_prompt
|
|
277
409
|
|
|
@@ -293,36 +425,24 @@ module NeocitiesRed
|
|
|
293
425
|
}
|
|
294
426
|
|
|
295
427
|
FileUtils.mkdir_p(Pathname(app_config_path).dirname)
|
|
296
|
-
|
|
428
|
+
persist_config(conf)
|
|
297
429
|
display.display_api_key_saved(@sitename, app_config_path)
|
|
298
430
|
@client = NeocitiesRed::Client.new(api_key: @api_key)
|
|
299
431
|
end
|
|
300
432
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
filepath = target.relative_path_from(base).to_s
|
|
310
|
-
|
|
311
|
-
if File.file?(target)
|
|
312
|
-
excludes << filepath
|
|
313
|
-
elsif File.directory?(target)
|
|
314
|
-
excludes.concat(
|
|
315
|
-
Dir.glob(File.join(target, "**", "*"), File::FNM_DOTMATCH).map do |path|
|
|
316
|
-
Pathname.new(path).expand_path.relative_path_from(base).to_s
|
|
317
|
-
end
|
|
318
|
-
)
|
|
319
|
-
excludes << filepath
|
|
320
|
-
end
|
|
321
|
-
end
|
|
322
|
-
|
|
323
|
-
excludes
|
|
433
|
+
# Writes the config hash to disk as JSON and restricts file permissions.
|
|
434
|
+
#
|
|
435
|
+
# @param conf [Hash] configuration data to persist
|
|
436
|
+
# @return [void]
|
|
437
|
+
def persist_config(conf)
|
|
438
|
+
File.write(app_config_path, conf.to_json)
|
|
439
|
+
FileUtils.chmod(0o600, app_config_path)
|
|
324
440
|
end
|
|
325
441
|
|
|
442
|
+
# Checks if the given value contains a help flag.
|
|
443
|
+
#
|
|
444
|
+
# @param value [String, Array<String>, nil] value to inspect
|
|
445
|
+
# @return [Boolean] true if the value is or contains "-h", "--help", or "help"
|
|
326
446
|
def help_requested_for?(value)
|
|
327
447
|
case value
|
|
328
448
|
when Array
|
|
@@ -332,6 +452,11 @@ module NeocitiesRed
|
|
|
332
452
|
end
|
|
333
453
|
end
|
|
334
454
|
|
|
455
|
+
# Determines if help was requested via the +--help+ option or the value.
|
|
456
|
+
#
|
|
457
|
+
# @param help_option [Boolean, nil] the Thor +--help+ option value
|
|
458
|
+
# @param value [String, Array<String>, nil] the positional argument to check
|
|
459
|
+
# @return [Boolean]
|
|
335
460
|
def help_requested?(help_option, value = nil)
|
|
336
461
|
help_option || (value && help_requested_for?(value))
|
|
337
462
|
end
|