wikiwiki 0.6.0 → 0.7.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/CHANGELOG.md +16 -0
- data/README.ja.md +9 -2
- data/README.md +9 -2
- data/lib/wikiwiki/api.rb +4 -0
- data/lib/wikiwiki/cli/commands/attachment/delete.rb +0 -14
- data/lib/wikiwiki/cli/commands/attachment/put.rb +5 -10
- data/lib/wikiwiki/cli/commands/page/delete.rb +32 -0
- data/lib/wikiwiki/cli/commands/page/put.rb +4 -0
- data/lib/wikiwiki/cli.rb +1 -3
- data/lib/wikiwiki/version.rb +1 -1
- data/lib/wikiwiki/wiki.rb +15 -1
- data/lib/wikiwiki.rb +4 -0
- data/sig/wikiwiki/cli/commands/attachment.rbs +0 -9
- data/sig/wikiwiki/cli/commands/page.rbs +15 -0
- data/sig/wikiwiki/wiki.rbs +2 -0
- data/sig/wikiwiki.rbs +3 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9cd510fe9ee02e6521a325c51ed5e586a41a904ea0758fc78b16ad66159c40d9
|
|
4
|
+
data.tar.gz: e3e57832169db7d9644cdcbd2d8c3495576e8ff221338f107b99cb7fde161991
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d3e67b565e5d9414a111dea276ea16f23bffe307c5c02b9de97062ce24a2e926304929bf975d5144cc359190680748cf224aae6f0819c96a21924b076152ca99
|
|
7
|
+
data.tar.gz: 3bfa6e34164386c86b19912170bb450da3dd50b2e610e8d722a3568d433d2877185fec64011cc7f68e283c4f1523286311e832b4c64ffa2102473b37083d2243
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.7.0] - 2025-11-03
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Page deletion support
|
|
8
|
+
- `Wiki#delete_page` method to delete pages
|
|
9
|
+
- `wikiwiki page delete` command in CLI
|
|
10
|
+
- Validation in `Wiki#update_page` and `wikiwiki page put` to prevent accidental deletion with empty content
|
|
11
|
+
- `ConflictError` exception class for HTTP 409 Conflict responses
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- `attachment put --force` behavior: attempts upload first, then deletes and retries on conflict (409 error)
|
|
16
|
+
- Previously: checked existence first, then deleted and uploaded
|
|
17
|
+
- Now: optimistic upload pattern eliminates Time-of-Check-Time-of-Use (TOCTOU) race conditions
|
|
18
|
+
|
|
3
19
|
## [0.6.0] - 2025-11-02
|
|
4
20
|
|
|
5
21
|
### Added
|
data/README.ja.md
CHANGED
|
@@ -87,6 +87,9 @@ wikiwiki page get FrontPage frontpage.txt
|
|
|
87
87
|
wikiwiki page put TestPage < content.txt
|
|
88
88
|
wikiwiki page put TestPage content.txt
|
|
89
89
|
|
|
90
|
+
# ページを削除
|
|
91
|
+
wikiwiki page delete TestPage
|
|
92
|
+
|
|
90
93
|
# 添付ファイル一覧
|
|
91
94
|
wikiwiki attachment list FrontPage
|
|
92
95
|
|
|
@@ -109,8 +112,9 @@ wikiwiki page get FrontPage existing.txt --force
|
|
|
109
112
|
wikiwiki attachment put FrontPage logo.png --force
|
|
110
113
|
|
|
111
114
|
# 注意: --force による添付ファイルの上書きはアトミックではありません。
|
|
112
|
-
#
|
|
113
|
-
#
|
|
115
|
+
# まずアップロードを試み、競合エラー(ファイルが既に存在)が発生した場合、
|
|
116
|
+
# 既存ファイルを削除してアップロードをリトライします。
|
|
117
|
+
# リトライに失敗した場合、添付ファイルは失われます。
|
|
114
118
|
|
|
115
119
|
# コマンドラインで認証情報を指定(環境変数より優先)
|
|
116
120
|
wikiwiki --wiki-id=your-wiki-id --password=your-password page list
|
|
@@ -160,6 +164,9 @@ wiki.update_page(page_name: "TestPage", source: <<~SOURCE)
|
|
|
160
164
|
# Hello World
|
|
161
165
|
SOURCE
|
|
162
166
|
|
|
167
|
+
# ページを削除
|
|
168
|
+
wiki.delete_page(page_name: "TestPage")
|
|
169
|
+
|
|
163
170
|
# 添付ファイル名の一覧を取得
|
|
164
171
|
attachment_names = wiki.attachment_names(page_name: "FrontPage")
|
|
165
172
|
|
data/README.md
CHANGED
|
@@ -87,6 +87,9 @@ wikiwiki page get FrontPage frontpage.txt
|
|
|
87
87
|
wikiwiki page put TestPage < content.txt
|
|
88
88
|
wikiwiki page put TestPage content.txt
|
|
89
89
|
|
|
90
|
+
# Delete page
|
|
91
|
+
wikiwiki page delete TestPage
|
|
92
|
+
|
|
90
93
|
# List attachments
|
|
91
94
|
wikiwiki attachment list FrontPage
|
|
92
95
|
|
|
@@ -109,8 +112,9 @@ wikiwiki page get FrontPage existing.txt --force
|
|
|
109
112
|
wikiwiki attachment put FrontPage logo.png --force
|
|
110
113
|
|
|
111
114
|
# Note: Attachment overwrite with --force is not atomic.
|
|
112
|
-
#
|
|
113
|
-
#
|
|
115
|
+
# First attempts to upload; if it fails due to conflict (file exists),
|
|
116
|
+
# deletes the existing file and retries the upload.
|
|
117
|
+
# If retry fails, the attachment will be lost.
|
|
114
118
|
|
|
115
119
|
# Authentication via command line (overrides environment variables)
|
|
116
120
|
wikiwiki --wiki-id=your-wiki-id --password=your-password page list
|
|
@@ -160,6 +164,9 @@ wiki.update_page(page_name: "TestPage", source: <<~SOURCE)
|
|
|
160
164
|
# Hello World
|
|
161
165
|
SOURCE
|
|
162
166
|
|
|
167
|
+
# Delete a page
|
|
168
|
+
wiki.delete_page(page_name: "TestPage")
|
|
169
|
+
|
|
163
170
|
# List attachment names
|
|
164
171
|
attachment_names = wiki.attachment_names(page_name: "FrontPage")
|
|
165
172
|
|
data/lib/wikiwiki/api.rb
CHANGED
|
@@ -62,6 +62,7 @@ module Wikiwiki
|
|
|
62
62
|
# @param source [String] the page source content
|
|
63
63
|
# @return [void]
|
|
64
64
|
# @raise [Error] if request fails
|
|
65
|
+
# @note Passing an empty string as source will delete the page
|
|
65
66
|
def put_page(encoded_page_name:, source:)
|
|
66
67
|
uri = BASE_URL + "/#{wiki_id}/page/#{encoded_page_name}"
|
|
67
68
|
response = request(:put, uri, body: {"source" => source})
|
|
@@ -171,6 +172,7 @@ module Wikiwiki
|
|
|
171
172
|
# @return [Hash] parsed response body
|
|
172
173
|
# @raise [AuthenticationError] if authentication fails (401)
|
|
173
174
|
# @raise [ResourceNotFoundError] if resource not found (404)
|
|
175
|
+
# @raise [ConflictError] if conflict occurs (409)
|
|
174
176
|
# @raise [ServerError] if server error (5xx)
|
|
175
177
|
# @raise [APIError] if other API request fails
|
|
176
178
|
private def parse_json_response(response)
|
|
@@ -181,6 +183,8 @@ module Wikiwiki
|
|
|
181
183
|
raise AuthenticationError, message
|
|
182
184
|
when 404
|
|
183
185
|
raise ResourceNotFoundError, message
|
|
186
|
+
when 409
|
|
187
|
+
raise ConflictError, message
|
|
184
188
|
when 500..599
|
|
185
189
|
raise ServerError, message
|
|
186
190
|
else
|
|
@@ -21,24 +21,10 @@ module Wikiwiki
|
|
|
21
21
|
def call(page_name:, file_name:, out: $stdout, err: $stderr, **)
|
|
22
22
|
wiki = create_wiki(out:, err:, **)
|
|
23
23
|
|
|
24
|
-
# Check if page exists first
|
|
25
|
-
unless page_exists?(wiki, page_name:)
|
|
26
|
-
raise ArgumentError, "Page '#{page_name}' does not exist"
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
# Check if attachment exists
|
|
30
|
-
unless attachment_exists?(wiki, page_name:, attachment_name: file_name)
|
|
31
|
-
raise ArgumentError, "Attachment '#{file_name}' does not exist on page '#{page_name}'"
|
|
32
|
-
end
|
|
33
|
-
|
|
34
24
|
wiki.delete_attachment(page_name:, attachment_name: file_name)
|
|
35
25
|
|
|
36
26
|
say("Attachment '#{file_name}' deleted from page '#{page_name}'", out:, **)
|
|
37
27
|
end
|
|
38
|
-
|
|
39
|
-
private def page_exists?(wiki, page_name:) = wiki.page_names.include?(page_name)
|
|
40
|
-
|
|
41
|
-
private def attachment_exists?(wiki, page_name:, attachment_name:) = wiki.attachment_names(page_name:).include?(attachment_name)
|
|
42
28
|
end
|
|
43
29
|
end
|
|
44
30
|
|
|
@@ -35,22 +35,17 @@ module Wikiwiki
|
|
|
35
35
|
raise ArgumentError, "File size (#{content.bytesize} bytes) exceeds maximum allowed size (#{MAX_ATTACHMENT_SIZE} bytes / 512 KiB)"
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
begin
|
|
39
|
+
wiki.add_attachment(page_name:, attachment_name:, content:)
|
|
40
|
+
rescue ConflictError
|
|
39
41
|
raise ArgumentError, "Attachment '#{attachment_name}' already exists. Use --force to overwrite." unless force
|
|
40
42
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
rescue ResourceNotFoundError
|
|
44
|
-
# Already deleted by another process, continue
|
|
45
|
-
end
|
|
43
|
+
wiki.delete_attachment(page_name:, attachment_name:)
|
|
44
|
+
retry
|
|
46
45
|
end
|
|
47
46
|
|
|
48
|
-
wiki.add_attachment(page_name:, attachment_name:, content:)
|
|
49
|
-
|
|
50
47
|
say("Attachment '#{attachment_name}' uploaded to page '#{page_name}'", out:, **)
|
|
51
48
|
end
|
|
52
|
-
|
|
53
|
-
private def attachment_exists?(wiki, page_name:, attachment_name:) = wiki.attachment_names(page_name:).include?(attachment_name)
|
|
54
49
|
end
|
|
55
50
|
end
|
|
56
51
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wikiwiki
|
|
4
|
+
class CLI
|
|
5
|
+
module Commands
|
|
6
|
+
module Page
|
|
7
|
+
# Delete a page
|
|
8
|
+
class Delete < Base
|
|
9
|
+
desc "Delete a page"
|
|
10
|
+
|
|
11
|
+
argument :page_name, required: true, desc: "Page name"
|
|
12
|
+
|
|
13
|
+
# Execute the delete command
|
|
14
|
+
#
|
|
15
|
+
# @param page_name [String] name of the page to delete
|
|
16
|
+
# @param out [IO] output stream
|
|
17
|
+
# @param err [IO] error stream
|
|
18
|
+
# @return [void]
|
|
19
|
+
def call(page_name:, out: $stdout, err: $stderr, **)
|
|
20
|
+
wiki = create_wiki(out:, err:, **)
|
|
21
|
+
|
|
22
|
+
wiki.delete_page(page_name:)
|
|
23
|
+
|
|
24
|
+
say("Page '#{page_name}' deleted successfully", out:, **)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
register "page delete", Page::Delete
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -18,6 +18,8 @@ module Wikiwiki
|
|
|
18
18
|
# @param out [IO] output stream
|
|
19
19
|
# @param err [IO] error stream
|
|
20
20
|
# @return [void]
|
|
21
|
+
# @raise [ArgumentError] if source content is empty
|
|
22
|
+
# @note To delete a page, use `wikiwiki page delete` instead
|
|
21
23
|
def call(page_name:, input_file: nil, out: $stdout, err: $stderr, **)
|
|
22
24
|
wiki = create_wiki(out:, err:, **)
|
|
23
25
|
|
|
@@ -27,6 +29,8 @@ module Wikiwiki
|
|
|
27
29
|
$stdin.read
|
|
28
30
|
end
|
|
29
31
|
|
|
32
|
+
raise ArgumentError, "Page source must not be empty. Use 'wikiwiki page delete' to delete a page." if source.empty?
|
|
33
|
+
|
|
30
34
|
wiki.update_page(page_name:, source:)
|
|
31
35
|
|
|
32
36
|
say("Page '#{page_name}' updated successfully", out:, **)
|
data/lib/wikiwiki/cli.rb
CHANGED
|
@@ -9,12 +9,10 @@ module Wikiwiki
|
|
|
9
9
|
# Provides commands for managing wiki pages and attachments through the terminal.
|
|
10
10
|
# Supports authentication via API key or password, with configurable logging and output modes.
|
|
11
11
|
class CLI
|
|
12
|
-
#
|
|
12
|
+
# Initialize the CLI with output streams
|
|
13
13
|
#
|
|
14
|
-
# @param argv [Array<String>] command-line arguments
|
|
15
14
|
# @param out [IO] standard output (defaults to $stdout)
|
|
16
15
|
# @param err [IO] error output (defaults to $stderr)
|
|
17
|
-
# @return [Integer] exit code (0 for success, 1 for error)
|
|
18
16
|
def initialize(out: $stdout, err: $stderr)
|
|
19
17
|
@out = out
|
|
20
18
|
@err = err
|
data/lib/wikiwiki/version.rb
CHANGED
data/lib/wikiwiki/wiki.rb
CHANGED
|
@@ -70,14 +70,28 @@ module Wikiwiki
|
|
|
70
70
|
# Updates a page with new content
|
|
71
71
|
#
|
|
72
72
|
# @param page_name [String] the name of the page to update
|
|
73
|
-
# @param source [String] the new page source content
|
|
73
|
+
# @param source [String] the new page source content (must not be empty)
|
|
74
74
|
# @return [void]
|
|
75
|
+
# @raise [ArgumentError] if source is empty
|
|
75
76
|
# @raise [Wikiwiki::Error] if the update fails
|
|
77
|
+
# @note To delete a page, use {#delete_page} instead
|
|
76
78
|
def update_page(page_name:, source:)
|
|
79
|
+
raise ArgumentError, "Page source must not be empty. Use delete_page to delete a page." if source.empty?
|
|
80
|
+
|
|
77
81
|
encoded_page_name = ERB::Util.url_encode(page_name)
|
|
78
82
|
api.put_page(encoded_page_name:, source:)
|
|
79
83
|
end
|
|
80
84
|
|
|
85
|
+
# Deletes a page
|
|
86
|
+
#
|
|
87
|
+
# @param page_name [String] the name of the page to delete
|
|
88
|
+
# @return [void]
|
|
89
|
+
# @raise [Wikiwiki::Error] if the deletion fails
|
|
90
|
+
def delete_page(page_name:)
|
|
91
|
+
encoded_page_name = ERB::Util.url_encode(page_name)
|
|
92
|
+
api.put_page(encoded_page_name:, source: "")
|
|
93
|
+
end
|
|
94
|
+
|
|
81
95
|
# Retrieves attachment file names on a page
|
|
82
96
|
#
|
|
83
97
|
# @param page_name [String] the name of the page
|
data/lib/wikiwiki.rb
CHANGED
|
@@ -32,6 +32,10 @@ module Wikiwiki
|
|
|
32
32
|
# Raised when HTTP status is 404
|
|
33
33
|
class ResourceNotFoundError < APIError; end
|
|
34
34
|
|
|
35
|
+
# Conflict error
|
|
36
|
+
# Raised when HTTP status is 409
|
|
37
|
+
class ConflictError < APIError; end
|
|
38
|
+
|
|
35
39
|
# Server error
|
|
36
40
|
# Raised when HTTP status is 5xx
|
|
37
41
|
class ServerError < APIError; end
|
|
@@ -65,10 +65,6 @@ module Wikiwiki
|
|
|
65
65
|
?verbose: bool,
|
|
66
66
|
?debug: bool
|
|
67
67
|
) -> void
|
|
68
|
-
|
|
69
|
-
private
|
|
70
|
-
|
|
71
|
-
def attachment_exists?: (Wiki wiki, page_name: String, attachment_name: String) -> bool
|
|
72
68
|
end
|
|
73
69
|
|
|
74
70
|
class Delete < Base
|
|
@@ -84,11 +80,6 @@ module Wikiwiki
|
|
|
84
80
|
?verbose: bool,
|
|
85
81
|
?debug: bool
|
|
86
82
|
) -> void
|
|
87
|
-
|
|
88
|
-
private
|
|
89
|
-
|
|
90
|
-
def page_exists?: (Wiki wiki, page_name: String) -> bool
|
|
91
|
-
def attachment_exists?: (Wiki wiki, page_name: String, attachment_name: String) -> bool
|
|
92
83
|
end
|
|
93
84
|
end
|
|
94
85
|
end
|
|
@@ -61,6 +61,21 @@ module Wikiwiki
|
|
|
61
61
|
?debug: bool
|
|
62
62
|
) -> void
|
|
63
63
|
end
|
|
64
|
+
|
|
65
|
+
class Delete < Base
|
|
66
|
+
def call: (
|
|
67
|
+
page_name: String,
|
|
68
|
+
?out: IO,
|
|
69
|
+
?err: IO,
|
|
70
|
+
?wiki_id: String?,
|
|
71
|
+
?api_key_id: String?,
|
|
72
|
+
?secret: String?,
|
|
73
|
+
?password: String?,
|
|
74
|
+
?token: String?,
|
|
75
|
+
?verbose: bool,
|
|
76
|
+
?debug: bool
|
|
77
|
+
) -> void
|
|
78
|
+
end
|
|
64
79
|
end
|
|
65
80
|
end
|
|
66
81
|
end
|
data/sig/wikiwiki/wiki.rbs
CHANGED
|
@@ -14,6 +14,8 @@ module Wikiwiki
|
|
|
14
14
|
|
|
15
15
|
def update_page: (page_name: String, source: String) -> void
|
|
16
16
|
|
|
17
|
+
def delete_page: (page_name: String) -> void
|
|
18
|
+
|
|
17
19
|
def attachment_names: (page_name: String) -> Array[String]
|
|
18
20
|
|
|
19
21
|
def attachment: (page_name: String, attachment_name: String, ?rev: String?) -> Attachment
|
data/sig/wikiwiki.rbs
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: wikiwiki
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- OZAWA Sakuro
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-11-
|
|
11
|
+
date: 2025-11-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: base64
|
|
@@ -95,6 +95,7 @@ files:
|
|
|
95
95
|
- lib/wikiwiki/cli/commands/attachment/show.rb
|
|
96
96
|
- lib/wikiwiki/cli/commands/auth.rb
|
|
97
97
|
- lib/wikiwiki/cli/commands/base.rb
|
|
98
|
+
- lib/wikiwiki/cli/commands/page/delete.rb
|
|
98
99
|
- lib/wikiwiki/cli/commands/page/get.rb
|
|
99
100
|
- lib/wikiwiki/cli/commands/page/list.rb
|
|
100
101
|
- lib/wikiwiki/cli/commands/page/put.rb
|