diff options
| author | TheSiahxyz <164138827+TheSiahxyz@users.noreply.github.com> | 2025-10-06 13:45:27 +0900 |
|---|---|---|
| committer | TheSiahxyz <164138827+TheSiahxyz@users.noreply.github.com> | 2025-10-06 13:45:27 +0900 |
| commit | b40d1b8c322ce927fdbcb0df1b56ebaf1210cd17 (patch) | |
| tree | f7928ee635237dfe853c32b2cd22bc04b09c07ed /mac | |
| parent | eeb6aff35decf8d4978c3403aeb24ab109877e96 (diff) | |
modified yazi/keymap-default.toml, modified yazi/keymap-default.toml, created compress.yazi/, created compress.yazi/
Diffstat (limited to 'mac')
| -rw-r--r-- | mac/.config/yazi/keymap-default.toml | 25 | ||||
| -rw-r--r-- | mac/.config/yazi/plugins/compress.yazi/LICENSE | 21 | ||||
| -rw-r--r-- | mac/.config/yazi/plugins/compress.yazi/README.md | 173 | ||||
| -rw-r--r-- | mac/.config/yazi/plugins/compress.yazi/main.lua | 496 |
4 files changed, 715 insertions, 0 deletions
diff --git a/mac/.config/yazi/keymap-default.toml b/mac/.config/yazi/keymap-default.toml index 480c1e4..92528fc 100644 --- a/mac/.config/yazi/keymap-default.toml +++ b/mac/.config/yazi/keymap-default.toml @@ -254,6 +254,31 @@ run = 'shell -- qlmanage -p "$@"' on = "<C-m>" run = 'shell --block -- neomutt -a "$@"' +[[mgr.prepend_keymap]] +on = [ "c", "p", "a" ] +run = "plugin compress" +desc = "Archive selected files" + +[[mgr.prepend_keymap]] +on = [ "c", "p", "p" ] +run = "plugin compress -p" +desc = "Archive selected files (password)" + +[[mgr.prepend_keymap]] +on = [ "c", "p", "h" ] +run = "plugin compress -ph" +desc = "Archive selected files (password+header)" + +[[mgr.prepend_keymap]] +on = [ "c", "p", "l" ] +run = "plugin compress -l" +desc = "Archive selected files (compression level)" + +[[mgr.prepend_keymap]] +on = [ "c", "p", "u" ] +run = "plugin compress -phl" +desc = "Archive selected files (password+header+level)" + [tasks] keymap = [ diff --git a/mac/.config/yazi/plugins/compress.yazi/LICENSE b/mac/.config/yazi/plugins/compress.yazi/LICENSE new file mode 100644 index 0000000..7ce7a2f --- /dev/null +++ b/mac/.config/yazi/plugins/compress.yazi/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 CiarΓ‘n O'Brien + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/mac/.config/yazi/plugins/compress.yazi/README.md b/mac/.config/yazi/plugins/compress.yazi/README.md new file mode 100644 index 0000000..ae1f329 --- /dev/null +++ b/mac/.config/yazi/plugins/compress.yazi/README.md @@ -0,0 +1,173 @@ +<h1 align="center">ποΈ compress.yazi</h1> +<p align="center"> + <b>A blazing fast, flexible archive plugin for <a href="https://github.com/sxyazi/yazi">Yazi</a></b><br> + <i>Effortlessly compress your files and folders with style!</i> +</p> + +--- + +## π Table of Contents + +- [Features](#-features) +- [Supported File Types](#-supported-file-types) +- [Installation](#%EF%B8%8F-installation) +- [Keymap Example](#-keymap-example) +- [Usage](#%EF%B8%8F-usage) +- [Flags](#%EF%B8%8F-flags) +- [Tips](#-tips) +- [Credits](#-credits) + +--- + +## π Features + +- ποΈ **Multi-format support:** zip, 7z, rar, tar, tar.gz, tar.xz, tar.bz2, tar.zst, tar.lz4, tar.lha +- π **Cross-platform:** Works on Unix & Windows +- π **Password protection:** Secure your archives (zip/7z/rar) +- π‘οΈ **Header encryption:** Hide file lists (7z/rar) +- β‘ **Compression level:** Choose your balance of speed vs. size +- π **Overwrite safety:** Never lose files by accident +- π― **Seamless Yazi integration:** Fast, native-like UX + +--- + +## π¦ Supported File Types + +| Extension | Default Command | 7z Command | Bsdtar Command (Win10+ & Unix) | +| ------------- | ----------------- | -------------- | ------------------------------ | +| `.zip` | `zip -r` | `7z a -tzip` | `tar -caf` | +| `.7z` | `7z a` | `7z a` | | +| `.rar` | `rar a` | | | +| `.tar` | `tar rpf` | | `tar rpf` | +| `.tar.gz` | `tar rpf + gzip` | `7z a -tgzip` | `tar -czf` | +| `.tar.xz` | `tar rpf + xz` | `7z a -txz` | `tar -cJf` | +| `.tar.bz2` | `tar rpf + bzip2` | `7z a -tbzip2` | `tar -cjf` | +| `.tar.zst` | `tar rpf + zstd` | | `tar --zstd -cf` | +| `.tar.lz4` | `tar rpf + lz4` | | | +| `.tar.lha` | `tar rpf + lha` | | | + +--- + +## β‘οΈ Installation + +```bash +# Unix +git clone https://github.com/KKV9/compress.yazi.git ~/.config/yazi/plugins/compress.yazi + +# Windows (CMD, not PowerShell!) +git clone https://github.com/KKV9/compress.yazi.git %AppData%\yazi\config\plugins\compress.yazi + +# Or with yazi plugin manager +ya pkg add KKV9/compress +``` + +--- + +### π§ Extras (Windows) + +To enable additional compression formats and features on Windows, follow these steps: + +1. **Install [7-Zip](https://www.7-zip.org/):** + Add `C:\Program Files\7-Zip` to your `PATH`. + This enables support for `.7z` archives and password-protected `.zip` files. + +2. **Alternative: Install [Nanazip](https://github.com/M2Team/NanaZip):** + A modern alternative to 7-Zip with similar functionality and extra features. + +3. **Install [WinRAR](https://www.win-rar.com/download.html):** + Add `C:\Program Files\WinRAR` to your `PATH`. + This enables support for `.rar` archives. + +4. **Install Additional Tools:** + To use formats like `lha`, `lz4`, `gzip`, etc., install their respective tools and ensure they are added to your `PATH`. + +--- + +## πΉ Keymap Example + +Add this to your `keymap.toml`: + + +```toml +[[mgr.prepend_keymap]] +on = [ "c", "a", "a" ] +run = "plugin compress" +desc = "Archive selected files" + +[[mgr.prepend_keymap]] +on = [ "c", "a", "p" ] +run = "plugin compress -p" +desc = "Archive selected files (password)" + +[[mgr.prepend_keymap]] +on = [ "c", "a", "h" ] +run = "plugin compress -ph" +desc = "Archive selected files (password+header)" + +[[mgr.prepend_keymap]] +on = [ "c", "a", "l" ] +run = "plugin compress -l" +desc = "Archive selected files (compression level)" + +[[mgr.prepend_keymap]] +on = [ "c", "a", "u" ] +run = "plugin compress -phl" +desc = "Archive selected files (password+header+level)" +``` + +--- + +## π οΈ Usage + +1. **Select files/folders** in Yazi. +2. Press <kbd>c</kbd> <kbd>a</kbd> to open the archive dialog. +3. Choose: + - <kbd>a</kbd> for a standard archive + - <kbd>p</kbd> for password protection (zip/7z/rar) + - <kbd>h</kbd> to encrypt header (7z/rar) + - <kbd>l</kbd> to set compression level (all compression algorithims) + - <kbd>u</kbd> for all options together +4. **Type a name** for your archive (or leave blank for suggested name). +5. **Enter password** and/or **compression level** if prompted. +6. **Overwrite protect** if a file already exists, the new file will be given a suffix _#. +7. Enjoy your shiny new archive! + +--- + +## π³οΈβπ Flags + +- Combine flags for more power! +- when separating flags with spaces, make sure to single quote them (eg., `'-ph rar'`) +- `-p` Password protect (zip/7z/rar) +- `-h` Encrypt header (7z/rar) +- `-l` Set compression level (all compression algorithims) +- `<extention>` Specify a default extention (eg., `7z`, `tar.gz`) + +#### Combining multiple flags: +```toml +[[mgr.prepend_keymap]] +on = [ "c", "a", "7" ] +run = "plugin compress '-ph 7z'" +desc = "Archive selected files to 7z (password+header)" +[[mgr.prepend_keymap]] +on = [ "c", "a", "r" ] +run = "plugin compress '-p -l rar'" +desc = "Archive selected files to rar (password+level)" +``` + +--- + +## π‘ Tips + +- The file extension **must** match a supported type. +- The required compression tool **must** be installed and in your `PATH` (7zip/rar etc.). +- If no extention is provided, the default extention (zip) will be appended automatically. + +--- + +## π£ Credits + +Made with β€οΈ for [Yazi](https://github.com/sxyazi/yazi) by [KKV9](https://github.com/KKV9). +Contributions are welcome! Feel free to submit a pull request. + +--- diff --git a/mac/.config/yazi/plugins/compress.yazi/main.lua b/mac/.config/yazi/plugins/compress.yazi/main.lua new file mode 100644 index 0000000..b695b3f --- /dev/null +++ b/mac/.config/yazi/plugins/compress.yazi/main.lua @@ -0,0 +1,496 @@ +-- Check for windows +local is_windows = ya.target_family() == "windows" +-- Define flags and strings +local is_password, is_encrypted, is_level, cmd_password, cmd_level, default_extension = false, false, false, "", "", "zip" + +-- Function to check valid filename +local function is_valid_filename(name) + -- Trim whitespace from both ends + name = name:match("^%s*(.-)%s*$") + if name == "" then + return false + end + if is_windows then + -- Windows forbidden chars and reserved names + if name:find('[<>:"/\\|%?%*]') then + return false + end + else + -- Unix forbidden chars + if name:find("/") or name:find("%z") then + return false + end + end + return true +end + +-- Function to send notifications +local function notify_error(message, urgency) + ya.notify( + { + title = "Archive", + content = message, + level = urgency, + timeout = 5 + } + ) +end + +-- Function to check if command is available +local function is_command_available(cmd) + local stat_cmd + if is_windows then + stat_cmd = string.format("where %s > nul 2>&1", cmd) + else + stat_cmd = string.format("command -v %s >/dev/null 2>&1", cmd) + end + local cmd_exists = os.execute(stat_cmd) + if cmd_exists then + return true + else + return false + end +end + +-- Function to change command arrays --> string -- Use first command available or first command +local function find_command_name(cmd_list) + for _, cmd in ipairs(cmd_list) do + if is_command_available(cmd) then + return cmd + end + end + return cmd_list[1] -- Return first command as fallback +end + +-- Function to append filename to it's parent directory url +local function combine_url(path, file) + path, file = Url(path), Url(file) + return tostring(path:join(file)) +end + +-- Function to make a table of selected or hovered files: path = filenames +local selected_or_hovered = + ya.sync( + function() + local tab, paths, names, path_fnames = cx.active, {}, {}, {} + for _, u in pairs(tab.selected) do + paths[#paths + 1] = tostring(u.parent) + names[#names + 1] = tostring(u.name) + end + if #paths == 0 and tab.current.hovered then + paths[1] = tostring(tab.current.hovered.url.parent) + names[1] = tostring(tab.current.hovered.name) + end + for idx, name in ipairs(names) do + if not path_fnames[paths[idx]] then + path_fnames[paths[idx]] = {} + end + table.insert(path_fnames[paths[idx]], name) + end + return path_fnames, names, tostring(tab.current.cwd) + end +) + +-- Table of archive commands +local archive_commands = { + ["%.zip$"] = { + {command = "zip", args = {"-r"}, level_arg = "-", level_min = 0, level_max = 9, passwordable = true}, + { + command = {"7z", "7zz", "7za"}, + args = {"a", "-tzip"}, + level_arg = "-mx=", + level_min = 0, + level_max = 9, + passwordable = true + }, + { + command = {"tar", "bsdtar"}, + args = {"-caf"}, + level_arg = {"--option", "compression-level="}, + level_min = 1, + level_max = 9 + } + }, + ["%.7z$"] = { + { + command = {"7z", "7zz", "7za"}, + args = {"a"}, + level_arg = "-mx=", + level_min = 0, + level_max = 9, + header_arg = "-mhe=on", + passwordable = true + } + }, + ["%.rar$"] = { + { + command = "rar", + args = {"a"}, + level_arg = "-m", + level_min = 0, + level_max = 5, + header_arg = "-hp", + passwordable = true + } + }, + ["%.tar.gz$"] = { + {command = {"tar", "bsdtar"}, args = {"rpf"}, level_arg = "-", level_min = 1, level_max = 9, compress = "gzip"}, + { + command = {"tar", "bsdtar"}, + args = {"rpf"}, + level_arg = "-mx=", + level_min = 1, + level_max = 9, + compress = "7z", + compress_args = {"a", "-tgzip"} + }, + { + command = {"tar", "bsdtar"}, + args = {"-czf"}, + level_arg = {"--option", "gzip:compression-level="}, + level_min = 1, + level_max = 9 + } + }, + ["%.tar.xz$"] = { + {command = {"tar", "bsdtar"}, args = {"rpf"}, level_arg = "-", level_min = 1, level_max = 9, compress = "xz"}, + { + command = {"tar", "bsdtar"}, + args = {"rpf"}, + level_arg = "-mx=", + level_min = 1, + level_max = 9, + compress = "7z", + compress_args = {"a", "-txz"} + }, + { + command = {"tar", "bsdtar"}, + args = {"-cJf"}, + level_arg = {"--option", "xz:compression-level="}, + level_min = 1, + level_max = 9 + } + }, + ["%.tar.bz2$"] = { + {command = {"tar", "bsdtar"}, args = {"rpf"}, level_arg = "-", level_min = 1, level_max = 9, compress = "bzip2"}, + { + command = {"tar", "bsdtar"}, + args = {"rpf"}, + level_arg = "-mx=", + level_min = 1, + level_max = 9, + compress = "7z", + compress_args = {"a", "-tbzip2"} + }, + { + command = {"tar", "bsdtar"}, + args = {"-cjf"}, + level_arg = {"--option", "bzip2:compression-level="}, + level_min = 1, + level_max = 9 + } + }, + ["%.tar.zst$"] = { + { + command = {"tar", "bsdtar"}, + args = {"rpf"}, + level_arg = "-", + level_min = 1, + level_max = 22, + compress = "zstd", + compress_args = {"--ultra"} + } + }, + ["%.tar.lz4$"] = { + { + command = {"tar", "bsdtar"}, + args = {"rpf"}, + level_arg = "-", + level_min = 1, + level_max = 12, + compress = "lz4" + } + }, + ["%.tar.lha$"] = { + { + command = {"tar", "bsdtar"}, + args = {"rpf"}, + level_arg = "-o", + level_min = 5, + level_max = 7, + compress = "lha", + compress_args = {"-a"} + } + }, + ["%.tar$"] = { + {command = {"tar", "bsdtar"}, args = {"rpf"}} + } +} + +return { + entry = function(_, job) + -- Parse flags and default extension + if job.args ~= nil then + for _, arg in ipairs(job.args) do + if arg:match("^%-(%w+)$") then + -- Handle combined flags (e.g., -phl) + for flag in arg:sub(2):gmatch(".") do + if flag == "p" then + is_password = true + elseif flag == "h" then + is_encrypted = true + elseif flag == "l" then + is_level = true + end + end + elseif arg:match("^%w[%w\\.]*$") then + -- Handle default extension (e.g., 7z, zip) + if archive_commands["%." .. arg .. "$"] then + default_extension = arg + else + notify_error(string.format("Unsupported extension: %s", arg), "warn") + end + else + notify_error(string.format("Unknown argument: %s", arg), "warn") + end + end + end + + -- Exit visual mode + ya.emit("escape", {visual = true}) + -- Define file table and output_dir (pwd) + local path_fnames, fnames, output_dir = selected_or_hovered() + -- Get archive filename + local output_name, event = + ya.input( + { + title = "Create archive:", + position = {"top-center", y = 3, w = 40} + } + ) + if event ~= 1 then + return + end + + -- Determine the default name for the archive + local default_name = #fnames == 1 and fnames[1] or Url(output_dir).name + output_name = output_name == "" and string.format("%s.%s", default_name, default_extension) or output_name + + -- Add default extension if none is specified + if not output_name:match("%.%w+$") then + output_name = string.format("%s.%s", output_name, default_extension) + end + + -- Validate the final archive filename + if not is_valid_filename(output_name) then + notify_error("Invalid archive filename", "error") + return + end + + -- Match user input to archive command + local archive_cmd, + archive_args, + archive_compress, + archive_level_arg, + archive_level_min, + archive_level_max, + archive_header_arg, + archive_passwordable, + archive_compress_args + local matched_pattern = false + for pattern, cmd_list in pairs(archive_commands) do + if output_name:match(pattern) then + matched_pattern = true -- Mark that file extension is correct + for _, cmd in ipairs(cmd_list) do + -- Check if archive_cmd is available + local find_command = type(cmd.command) == "table" and find_command_name(cmd.command) or cmd.command + if is_command_available(find_command) then + -- Check if compress_cmd (if listed) is available + if cmd.compress == nil or is_command_available(cmd.compress) then + archive_cmd = find_command + archive_args = cmd.args + archive_compress = cmd.compress or "" + archive_level_arg = is_level and cmd.level_arg or "" + archive_level_min = cmd.level_min + archive_level_max = cmd.level_max + archive_header_arg = is_encrypted and cmd.header_arg or "" + archive_passwordable = cmd.passwordable or false + archive_compress_args = cmd.compress_args or {} + break + end + end + end + if archive_cmd then + break + end + end + end + + -- Check if no archive command is available for the extension + if not matched_pattern then + notify_error("Unsupported file extension", "error") + return + end + + -- Check if no suitable archive program was found + if not archive_cmd then + notify_error("Could not find a suitable archive program for the selected file extension", "error") + return + end + + -- Check if archive command has multiple names + if type(archive_cmd) == "table" then + archive_cmd = find_command_name(archive_cmd) + end + + -- Exit if archive command is not available + if not is_command_available(archive_cmd) then + notify_error(string.format("%s not available", archive_cmd), "error") + return + end + + -- Exit if compress command is not available + if archive_compress ~= "" and not is_command_available(archive_compress) then + notify_error(string.format("%s compression not available", archive_compress), "error") + return + end + + -- Add password arg if selected + if archive_passwordable and is_password then + local output_password, event = + ya.input( + { + title = "Enter password:", + obscure = true, + position = {"top-center", y = 3, w = 40} + } + ) + if event ~= 1 then + return + end + if output_password ~= "" then + cmd_password = "-P" .. output_password + if archive_cmd == "rar" and is_encrypted then + cmd_password = archive_header_arg .. output_password -- Add archive arg for rar + end + table.insert(archive_args, cmd_password) + end + end + + -- Add header arg if selected for 7z + if is_encrypted and archive_header_arg ~= "" and archive_cmd ~= "rar" then + table.insert(archive_args, archive_header_arg) + end + + -- Add level arg if selected + if archive_level_arg ~= "" and is_level then + local output_level, event = + ya.input( + { + title = string.format("Enter compression level (%s - %s)", archive_level_min, archive_level_max), + position = {"top-center", y = 3, w = 40} + } + ) + if event ~= 1 then + return + end + -- Validate user input for compression level + if + output_level ~= "" and tonumber(output_level) ~= nil and tonumber(output_level) >= archive_level_min and + tonumber(output_level) <= archive_level_max + then + cmd_level = + type(archive_level_arg) == "table" and archive_level_arg[#archive_level_arg] .. output_level or + archive_level_arg .. output_level + local target_args = archive_compress == "" and archive_args or archive_compress_args + if type(archive_level_arg) == "table" then + -- Insert each element of archive_level_arg (except last) into target_args at the correct position + for i = 1, #archive_level_arg - 1 do + table.insert(target_args, i, archive_level_arg[i]) + end + table.insert(target_args, #archive_level_arg, cmd_level) -- Add level at the end + else + -- Insert the compression level argument at the start if not a table + table.insert(target_args, 1, cmd_level) + end + else + notify_error("Invalid level specified. Using defaults.", "warn") + end + end + + -- Store the original output name for later use + local original_name = output_name + + -- If compression is needed, adjust the output name to exclude extensions like ".tar" + if archive_compress ~= "" then + output_name = output_name:match("(.*%.tar)") or output_name + end + + -- Create a temporary directory for intermediate files + local temp_dir_name = ".tmp_compress" + local temp_dir = combine_url(output_dir, temp_dir_name) + local temp_dir, _ = tostring(fs.unique_name(Url(temp_dir))) + + -- Attempt to create the temporary directory + local temp_dir_status, temp_dir_err = fs.create("dir_all", Url(temp_dir)) + if not temp_dir_status then + -- Notify the user if the temporary directory creation fails + notify_error(string.format("Failed to create temp directory, error code: %s", temp_dir_err), "error") + return + end + + -- Define the temporary output file path within the temporary directory + local temp_output_url = combine_url(temp_dir, output_name) + + -- Add files to the output archive + for filepath, filenames in pairs(path_fnames) do + -- Execute the archive command for each path and its respective files + local archive_status, archive_err = + Command(archive_cmd):arg(archive_args):arg(temp_output_url):arg(filenames):cwd(filepath):spawn():wait() + if not archive_status or not archive_status.success then + -- Notify the user if the archiving process fails and clean up the temporary directory + notify_error(string.format("Failed to create archive %s with '%s', error: %s", output_name, archive_cmd, archive_err), "error") + local cleanup_status, cleanup_err = fs.remove("dir_all", Url(temp_dir)) + if not cleanup_status then + notify_error(string.format("Failed to clean up temporary directory %s, error: %s", temp_dir, cleanup_err), "error") + end + return + end + end + + -- If compression is required, execute the compression command + if archive_compress ~= "" then + local compress_status, compress_err = + Command(archive_compress):arg(archive_compress_args):arg(temp_output_url):spawn():wait() + if not compress_status or not compress_status.success then + -- Notify the user if the compression process fails and clean up the temporary directory + notify_error(string.format("Failed to compress archive %s with '%s', error: %s", output_name, archive_compress, compress_err), "error") + local cleanup_status, cleanup_err = fs.remove("dir_all", Url(temp_dir)) + if not cleanup_status then + notify_error(string.format("Failed to clean up temporary directory %s, error: %s", temp_dir, cleanup_err), "error") + end + return + end + end + + -- Move the final file from the temporary directory to the output directory + local final_output_url, temp_url_processed = combine_url(output_dir, original_name), combine_url(temp_dir, original_name) + final_output_url, _ = tostring(fs.unique_name(Url(final_output_url))) + local move_status, move_err = os.rename(temp_url_processed, final_output_url) + if not move_status then + -- Notify the user if the move operation fails and clean up the temporary directory + notify_error(string.format("Failed to move %s to %s, error: %s", temp_url_processed, final_output_url, move_err), "error") + local cleanup_status, cleanup_err = fs.remove("dir_all", Url(temp_dir)) + if not cleanup_status then + notify_error(string.format("Failed to clean up temporary directory %s, error: %s", temp_dir, cleanup_err), "error") + end + return + end + + -- Cleanup the temporary directory after successful operation + local cleanup_status, cleanup_err = fs.remove("dir_all", Url(temp_dir)) + if not cleanup_status then + notify_error(string.format("Failed to clean up temporary directory %s, error: %s", temp_dir, cleanup_err), "error") + end + end +} |
