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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bc13d22d549b9398dc6573d2731c7c1169fe70a1626f80d9f9b650116deb162c
4
- data.tar.gz: 1495f98f015f7494f7355a76f377f42d082a72e92a4679438b2d8822f6294f91
3
+ metadata.gz: 9cd510fe9ee02e6521a325c51ed5e586a41a904ea0758fc78b16ad66159c40d9
4
+ data.tar.gz: e3e57832169db7d9644cdcbd2d8c3495576e8ff221338f107b99cb7fde161991
5
5
  SHA512:
6
- metadata.gz: ad6ac01ea174e040f151f74d04504314bb4885abb0e66cc915debddee70f5d6ad3de402def7c693ae52c3ba8f1f3dd9fe8497befbb3c736ae5e79d86e79ed56a
7
- data.tar.gz: 0351abf501fed5484cc1b9606610d3ed3fbc789064a11384470ee7c7abd07115c27f315568d42a4ab4ead63f5be05473bef832cfdc350afdfa04c90e76774a68
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
- # The existing attachment is deleted before uploading the new one.
113
- # If the upload fails, the attachment will be lost.
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
- if attachment_exists?(wiki, page_name:, attachment_name:)
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
- begin
42
- wiki.delete_attachment(page_name:, attachment_name:)
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
- # Run the CLI with given arguments
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Wikiwiki
4
4
  # The gem version
5
- VERSION = "0.6.0"
5
+ VERSION = "0.7.0"
6
6
  public_constant :VERSION
7
7
  end
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
@@ -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
@@ -19,6 +19,9 @@ module Wikiwiki
19
19
  class ResourceNotFoundError < APIError
20
20
  end
21
21
 
22
+ class ConflictError < APIError
23
+ end
24
+
22
25
  class ServerError < APIError
23
26
  end
24
27
  end
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.6.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-02 00:00:00.000000000 Z
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