├── .gitignore ├── Makefile.toml ├── Cargo.toml ├── renovate.json ├── .github └── workflows │ ├── ci.yml │ └── release.yml ├── LICENSE ├── README.md ├── src ├── main.rs ├── font.rs ├── prompt.rs └── convert.rs └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # These are backup files generated by rustfmt 6 | **/*.rs.bk 7 | 8 | -------------------------------------------------------------------------------- /Makefile.toml: -------------------------------------------------------------------------------- 1 | [tasks.format] 2 | install_crate = "rustfmt" 3 | command = "cargo" 4 | args = ["fmt"] 5 | 6 | [tasks.clippy] 7 | command = "cargo" 8 | args = ["clippy", "--all-features"] 9 | dependencies = ["format"] 10 | 11 | [tasks.test] 12 | command = "cargo" 13 | args = ["test"] 14 | dependencies = ["clippy"] 15 | 16 | [tasks.check] 17 | dependencies = [ 18 | "format", 19 | "clippy", 20 | "test" 21 | ] 22 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "omekasy" 3 | version = "1.3.3" 4 | authors = ["ikanago "] 5 | description = "Decorate alphanumeric characters in your input with various font; special characters in Unicode" 6 | license = "MIT" 7 | readme = "README.md" 8 | repository = "https://github.com/ikanago/omekasy" 9 | edition = "2021" 10 | 11 | [dependencies] 12 | clap = { version = "4.5.4", features = ["derive"] } 13 | crossterm = { version = "0.29.0", optional = true } 14 | 15 | [features] 16 | default = ["crossterm"] 17 | 18 | [profile.dev] 19 | debug = 0 20 | 21 | [profile.release] 22 | strip = "symbols" 23 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base", 5 | ":prHourlyLimitNone", 6 | ":automergeBranch" 7 | ], 8 | "labels": [ 9 | "dependencies" 10 | ], 11 | "lockFileMaintenance": { 12 | "enabled": true 13 | }, 14 | "packageRules": [ 15 | { 16 | "matchDepTypes": ["dependencies"], 17 | "matchUpdateTypes": ["minor", "patch"], 18 | "automerge": true 19 | }, 20 | { 21 | "matchDepTypes": ["devDependencies"], 22 | "rangeStrategy": "pin", 23 | "automerge": true 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - "**.md" 7 | pull_request: 8 | paths-ignore: 9 | - "**.md" 10 | 11 | env: 12 | CARGO_INCREMENTAL: 0 13 | 14 | jobs: 15 | check: 16 | runs-on: "ubuntu-24.04" 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - uses: dtolnay/rust-toolchain@stable 22 | with: 23 | components: clippy,rustfmt 24 | 25 | - name: Cache dependencies 26 | uses: Swatinem/rust-cache@v2 27 | 28 | - name: Check format 29 | run: cargo fmt --all -- --check 30 | 31 | - name: Build 32 | run: cargo build --locked --verbose 33 | 34 | - name: Run tests 35 | run: cargo test --verbose 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 ikanago 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # omekasy 2 | 3 | [![CI](https://github.com/ikanago/omekasy/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/ikanago/omekasy/actions/workflows/ci.yml) 4 | [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) 5 | [![](https://img.shields.io/crates/v/omekasy.svg)](https://crates.io/crates/omekasy) 6 | 7 | [![asciicast](https://asciinema.org/a/490055.svg)](https://asciinema.org/a/490055) 8 | 9 | `omekasy` is a command line application that converts alphanumeric characters in your input to various styles defined in Unicode. 10 | `omekasy` means "dress up" in Japanese. 11 | 12 | ## Installation 13 | ### Homebrew 14 | Supports macOS(x86_64, aarch64) and Linux(x86_64). 15 | ```bash 16 | brew install ikanago/tap/omekasy 17 | ``` 18 | 19 | ### Cargo 20 | ```bash 21 | cargo install omekasy 22 | ``` 23 | 24 | ### Binaries 25 | You can download binaries from [Releases](https://github.com/ikanago/omekasy/releases). 26 | 27 | ## Usage 28 | Just run without any options or arguments, then a prompt will be shown up. 29 | You can select the style while watching the result like above demo. 30 | ```bash 31 | omekasy 32 | ``` 33 | 34 | To convert to a specific font instantly, give the font name and input. 35 | ```bash 36 | omekasy --font bold-italic "My new gear..." 37 | ``` 38 | Characters other than latin alphabets and numbers in your input remain untouched. 39 | 40 | Available font for now: 41 | - bold 42 | - italic 43 | - bold-italic 44 | - sans 45 | - sans-bold 46 | - sans-italic 47 | - sans-bold 48 | - italic 49 | - script 50 | - bold-script 51 | - fraktur 52 | - bold-fraktur 53 | - monospace 54 | - blackboard 55 | - emoji 56 | 57 | Key bindings in interactive mode: 58 | | Key | Action | 59 | | ------------ | ---------------- | 60 | | Up, Ctrl-K | Move cursor up | 61 | | Down, Ctrl-J | Move cursor down | 62 | | Enter | Select | 63 | | Ctrl-C, Esc | Quit | 64 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use clap::ValueEnum; 3 | use font::Font; 4 | #[cfg(feature = "crossterm")] 5 | use prompt::Prompt; 6 | use std::error::Error; 7 | use std::io::stdin; 8 | use std::io::Read; 9 | 10 | use crate::convert::Converter; 11 | 12 | mod convert; 13 | mod font; 14 | #[cfg(feature = "crossterm")] 15 | mod prompt; 16 | 17 | #[derive(Parser)] 18 | #[clap(author)] 19 | #[clap(version)] 20 | #[clap(about)] 21 | /// Decorate latin alphabet and numbers in your input with various font; special characters in 22 | /// Unicode. 23 | /// 24 | /// If you provide neither font type nor input, interactive prompt is displayed. 25 | struct Cli { 26 | #[clap(short, long, value_enum)] 27 | font: Option, 28 | input: Option, 29 | } 30 | 31 | fn main() -> Result<(), Box> { 32 | let cli: Cli = Cli::parse(); 33 | 34 | match (cli.input, cli.font) { 35 | (Some(input), Some(font)) => { 36 | let converter = Converter::new(&[font]); 37 | print!( 38 | "{}", 39 | converter.convert(&input.chars().collect::>(), font) 40 | ); 41 | } 42 | #[cfg(feature = "crossterm")] 43 | (None, None) => { 44 | let mut prompt = Prompt::new(Font::value_variants()); 45 | prompt.start_prompt()?; 46 | } 47 | #[cfg(not(feature = "crossterm"))] 48 | (None, None) => { 49 | return Err("Compiled without terminal support. Please specify the font as a command line parameter".into()); 50 | } 51 | (Some(input), None) => { 52 | let fonts = Font::value_variants(); 53 | let converter = Converter::new(fonts); 54 | for &font in fonts { 55 | println!( 56 | "{}", 57 | converter.convert(&input.chars().collect::>(), font) 58 | ); 59 | } 60 | } 61 | (None, Some(font)) => { 62 | let converter = Converter::new(&[font]); 63 | let mut input = String::new(); 64 | stdin().read_to_string(&mut input)?; 65 | let input = input.trim_end(); 66 | print!( 67 | "{}", 68 | converter.convert(&input.chars().collect::>(), font) 69 | ); 70 | } 71 | } 72 | 73 | Ok(()) 74 | } 75 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: "Release" 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | jobs: 9 | release: 10 | runs-on: ${{ matrix.job.os }} 11 | env: 12 | PROJECT_NAME: "omekasy" 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | job: 17 | - { 18 | target: "x86_64-unknown-linux-gnu", 19 | os: "ubuntu-22.04", 20 | binary-extension: "", 21 | package-extension: ".tar.gz", 22 | } 23 | - { 24 | target: "x86_64-apple-darwin", 25 | os: "macos-13", 26 | binary-extension: "", 27 | package-extension: ".tar.gz", 28 | } 29 | - { 30 | target: "aarch64-apple-darwin", 31 | os: "macos-14", 32 | binary-extension: "", 33 | package-extension: ".tar.gz", 34 | } 35 | - { 36 | target: "x86_64-pc-windows-msvc", 37 | os: "windows-2022", 38 | binary-extension: ".exe", 39 | package-extension: ".zip", 40 | } 41 | 42 | steps: 43 | - uses: actions/checkout@v4 44 | with: 45 | fetch-depth: 1 46 | 47 | - name: Build release 48 | run: cargo build --locked --verbose --release --target=${{ matrix.job.target }} 49 | 50 | - name: Create tarball 51 | shell: bash 52 | id: create_tarball 53 | run: | 54 | PKG_DIR="archive" 55 | mkdir -p "${PKG_DIR}" 56 | cp {README.md,LICENSE} "${PKG_DIR}" 57 | cp "target/${{ matrix.job.target }}/release/omekasy${{ matrix.job.binary-extension }}" "${PKG_DIR}" 58 | 59 | PROJECT_VERSION="${GITHUB_REF#refs/tags/v}" 60 | TARBALL_PATH="${PROJECT_NAME}-v${PROJECT_VERSION}-${{ matrix.job.target }}${{ matrix.job.package-extension }}" 61 | echo "TARBALL_PATH=${TARBALL_PATH}" >> "${GITHUB_OUTPUT}" 62 | 63 | if [[ "${{ matrix.job.os }}" =~ ^windows- ]]; then 64 | 7z a "${TARBALL_PATH}" "${PKG_DIR}" 65 | else 66 | tar czf "${TARBALL_PATH}" "${PKG_DIR}" 67 | fi 68 | 69 | - name: Publish 70 | if: ${{ contains(github.ref, '/tags/') }} 71 | uses: softprops/action-gh-release@v2 72 | with: 73 | files: ${{ steps.create_tarball.outputs.TARBALL_PATH }} 74 | env: 75 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 76 | 77 | cargo-publish: 78 | runs-on: ubuntu-24.04 79 | needs: release 80 | steps: 81 | - uses: actions/checkout@v4 82 | 83 | - name: Cargo publish 84 | run: | 85 | git config --global user.email "runner@gha.local" 86 | git config --global user.name "Github Action" 87 | cargo publish --allow-dirty --token ${{ secrets.CRATES_IO_TOKEN }} 88 | -------------------------------------------------------------------------------- /src/font.rs: -------------------------------------------------------------------------------- 1 | use clap::ValueEnum; 2 | use std::collections::HashMap; 3 | 4 | pub type FontMap = HashMap; 5 | 6 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, ValueEnum)] 7 | pub enum Font { 8 | Bold, 9 | Italic, 10 | BoldItalic, 11 | Sans, 12 | BoldSans, 13 | ItalicSans, 14 | BoldItalicSans, 15 | Script, 16 | BoldScript, 17 | Fraktur, 18 | BoldFraktur, 19 | Monospace, 20 | Blackboard, 21 | Emoji, 22 | } 23 | 24 | impl Font { 25 | pub fn characters(&self) -> FontMap { 26 | let source = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; 27 | let target = match self { 28 | Font::Bold => "𝐚𝐛𝐜𝐝𝐞𝐟𝐠𝐡𝐢𝐣𝐤𝐥𝐦𝐧𝐨𝐩𝐪𝐫𝐬𝐭𝐮𝐯𝐰𝐱𝐲𝐳𝐀𝐁𝐂𝐃𝐄𝐅𝐆𝐇𝐈𝐉𝐊𝐋𝐌𝐍𝐎𝐏𝐐𝐑𝐒𝐓𝐔𝐕𝐖𝐗𝐘𝐙𝟎𝟏𝟐𝟑𝟒𝟓𝟔𝟕𝟖𝟗", 29 | Font::Italic => "𝑎𝑏𝑐𝑑𝑒𝑓𝑔ℎ𝑖𝑗𝑘𝑙𝑚𝑛𝑜𝑝𝑞𝑟𝑠𝑡𝑢𝑣𝑤𝑥𝑦𝑧𝐴𝐵𝐶𝐷𝐸𝐹𝐺𝐻𝐼𝐽𝐾𝐿𝑀𝑁𝑂𝑃𝑄𝑅𝑆𝑇𝑈𝑉𝑊𝑋𝑌𝑍0123456789", 30 | Font::BoldItalic => "𝒂𝒃𝒄𝒅𝒆𝒇𝒈𝒉𝒊𝒋𝒌𝒍𝒎𝒏𝒐𝒑𝒒𝒓𝒔𝒕𝒖𝒗𝒘𝒙𝒚𝒛𝑨𝑩𝑪𝑫𝑬𝑭𝑮𝑯𝑰𝑱𝑲𝑳𝑴𝑵𝑶𝑷𝑸𝑹𝑺𝑻𝑼𝑽𝑾𝑿𝒀𝒁0123456789", 31 | Font::Sans => "𝖺𝖻𝖼𝖽𝖾𝖿𝗀𝗁𝗂𝗃𝗄𝗅𝗆𝗇𝗈𝗉𝗊𝗋𝗌𝗍𝗎𝗏𝗐𝗑𝗒𝗓𝖠𝖡𝖢𝖣𝖤𝖥𝖦𝖧𝖨𝖩𝖪𝖫𝖬𝖭𝖮𝖯𝖰𝖱𝖲𝖳𝖴𝖵𝖶𝖷𝖸𝖹𝟢𝟣𝟤𝟥𝟦𝟧𝟨𝟩𝟪𝟫", 32 | Font::BoldSans => "𝗮𝗯𝗰𝗱𝗲𝗳𝗴𝗵𝗶𝗷𝗸𝗹𝗺𝗻𝗼𝗽𝗾𝗿𝘀𝘁𝘂𝘃𝘄𝘅𝘆𝘇𝗔𝗕𝗖𝗗𝗘𝗙𝗚𝗛𝗜𝗝𝗞𝗟𝗠𝗡𝗢𝗣𝗤𝗥𝗦𝗧𝗨𝗩𝗪𝗫𝗬𝗭𝟬𝟭𝟮𝟯𝟰𝟱𝟲𝟳𝟴𝟵", 33 | Font::ItalicSans => "𝘢𝘣𝘤𝘥𝘦𝘧𝘨𝘩𝘪𝘫𝘬𝘭𝘮𝘯𝘰𝘱𝘲𝘳𝘴𝘵𝘶𝘷𝘸𝘹𝘺𝘻𝘈𝘉𝘊𝘋𝘌𝘍𝘎𝘏𝘐𝘑𝘒𝘓𝘔𝘕𝘖𝘗𝘘𝘙𝘚𝘛𝘜𝘝𝘞𝘟𝘠𝘡𝟬𝟭𝟮𝟯𝟰𝟱𝟲𝟳𝟴𝟵", 34 | Font::BoldItalicSans => { 35 | "𝙖𝙗𝙘𝙙𝙚𝙛𝙜𝙝𝙞𝙟𝙠𝙡𝙢𝙣𝙤𝙥𝙦𝙧𝙨𝙩𝙪𝙫𝙬𝙭𝙮𝙯𝘼𝘽𝘾𝘿𝙀𝙁𝙂𝙃𝙄𝙅𝙆𝙇𝙈𝙉𝙊𝙋𝙌𝙍𝙎𝙏𝙐𝙑𝙒𝙓𝙔𝙕𝟬𝟭𝟮𝟯𝟰𝟱𝟲𝟳𝟴𝟵" 36 | } 37 | Font::Script => "𝒶𝒷𝒸𝒹ℯ𝒻ℊ𝒽𝒾𝒿𝓀𝓁𝓂𝓃ℴ𝓅𝓆𝓇𝓈𝓉𝓊𝓋𝓌𝓍𝓎𝓏𝒜ℬ𝒞𝒟ℰℱ𝒢ℋℐ𝒥𝒦ℒℳ𝒩𝒪𝒫𝒬ℛ𝒮𝒯𝒰𝒱𝒲𝒳𝒴𝒵0123456789", 38 | Font::BoldScript => "𝓪𝓫𝓬𝓭𝓮𝓯𝓰𝓱𝓲𝓳𝓴𝓵𝓶𝓷𝓸𝓹𝓺𝓻𝓼𝓽𝓾𝓿𝔀𝔁𝔂𝔃𝓐𝓑𝓒𝓓𝓔𝓕𝓖𝓗𝓘𝓙𝓚𝓛𝓜𝓝𝓞𝓟𝓠𝓡𝓢𝓣𝓤𝓥𝓦𝓧𝓨𝓩0123456789", 39 | Font::Fraktur => "𝔞𝔟𝔠𝔡𝔢𝔣𝔤𝔥𝔦𝔧𝔨𝔩𝔪𝔫𝔬𝔭𝔮𝔯𝔰𝔱𝔲𝔳𝔴𝔵𝔶𝔷𝔄𝔅ℭ𝔇𝔈𝔉𝔊ℌℑ𝔍𝔎𝔏𝔐𝔑𝔒𝔓𝔔ℜ𝔖𝔗𝔘𝔙𝔚𝔛𝔜ℨ0123456789", 40 | Font::BoldFraktur => "𝖆𝖇𝖈𝖉𝖊𝖋𝖌𝖍𝖎𝖏𝖐𝖑𝖒𝖓𝖔𝖕𝖖𝖗𝖘𝖙𝖚𝖛𝖜𝖝𝖞𝖟𝕬𝕭𝕮𝕯𝕰𝕱𝕲𝕳𝕴𝕵𝕶𝕷𝕸𝕹𝕺𝕻𝕼𝕽𝕾𝕿𝖀𝖁𝖂𝖃𝖄𝖅0123456789", 41 | Font::Monospace => "𝚊𝚋𝚌𝚍𝚎𝚏𝚐𝚑𝚒𝚓𝚔𝚕𝚖𝚗𝚘𝚙𝚚𝚛𝚜𝚝𝚞𝚟𝚠𝚡𝚢𝚣𝙰𝙱𝙲𝙳𝙴𝙵𝙶𝙷𝙸𝙹𝙺𝙻𝙼𝙽𝙾𝙿𝚀𝚁𝚂𝚃𝚄𝚅𝚆𝚇𝚈𝚉𝟶𝟷𝟸𝟹𝟺𝟻𝟼𝟽𝟾𝟿", 42 | Font::Blackboard => "𝕒𝕓𝕔𝕕𝕖𝕗𝕘𝕙𝕚𝕛𝕜𝕝𝕞𝕟𝕠𝕡𝕢𝕣𝕤𝕥𝕦𝕧𝕨𝕩𝕪𝕫𝔸𝔹ℂ𝔻𝔼𝔽𝔾ℍ𝕀𝕁𝕂𝕃𝕄ℕ𝕆ℙℚℝ𝕊𝕋𝕌𝕍𝕎𝕏𝕐ℤ𝟘𝟙𝟚𝟛𝟜𝟝𝟞𝟟𝟠𝟡", 43 | // Following characters are regional indicator symbols, which is rendered as roman characters surrounded by square in some Websites. 44 | // In some editor such as VSCode, A pair 'Z' and 'A' is rendered as South Africa flag. 45 | Font::Emoji => "🇦🇧🇨🇩🇪🇫🇬🇭🇮🇯🇰🇱🇲🇳🇴🇵🇶🇷🇸🇹🇺🇻🇼🇽🇾🇿🇦🇧🇨🇩🇪🇫🇬🇭🇮🇯🇰🇱🇲🇳🇴🇵🇶🇷🇸🇹🇺🇻🇼🇽🇾🇿0123456789", 46 | }; 47 | 48 | source.chars().zip(target.chars()).collect() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/prompt.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io::{self, Write}, 3 | time::Duration, 4 | }; 5 | 6 | use crate::convert::Converter; 7 | use crate::font::Font; 8 | 9 | use crossterm::{ 10 | cursor::{MoveLeft, MoveRight, MoveToNextLine, MoveToPreviousLine}, 11 | event::{poll, read, Event, KeyCode, KeyEvent, KeyModifiers}, 12 | style::{Print, Stylize}, 13 | terminal::{disable_raw_mode, enable_raw_mode, Clear, ClearType}, 14 | ExecutableCommand, QueueableCommand, 15 | }; 16 | 17 | pub enum Action { 18 | Confirm, 19 | Quit, 20 | Update, 21 | None, 22 | } 23 | 24 | pub struct Prompt { 25 | input: Vec, 26 | fonts: &'static [Font], 27 | converter: Converter, 28 | current_font: usize, 29 | num_whole_lines: usize, 30 | } 31 | 32 | impl Prompt { 33 | const POLL_DURATION_MS: u64 = 50; 34 | const PROMPT_SYMBOL: &'static str = "> "; 35 | 36 | pub fn new(fonts: &'static [Font]) -> Self { 37 | let converter = Converter::new(fonts); 38 | let num_whole_lines = fonts.len() + 1; 39 | 40 | Self { 41 | input: Vec::new(), 42 | fonts, 43 | converter, 44 | current_font: 0, 45 | num_whole_lines, 46 | } 47 | } 48 | 49 | /// Start event loop to wait for user input and render output. 50 | pub fn start_prompt(&mut self) -> io::Result<()> { 51 | enable_raw_mode()?; 52 | 53 | let mut stderr = io::stderr(); 54 | self.initialize_prompt(&mut stderr)?; 55 | self.render_input(&mut stderr)?; 56 | 57 | self.start_event_loop(&mut stderr)?; 58 | 59 | io::stdout() 60 | .execute(MoveLeft(self.input_line_len()))? 61 | .execute(Clear(ClearType::CurrentLine))? 62 | .execute(Print(format!( 63 | "{}\r\n", 64 | self.converter 65 | .convert(&self.input, self.fonts[self.current_font]) 66 | )))? 67 | .execute(Clear(ClearType::FromCursorDown))?; 68 | 69 | disable_raw_mode()?; 70 | 71 | Ok(()) 72 | } 73 | 74 | /// Ahead of event loop, reserve lines to render candidate outputs; 75 | /// `SavePosition` and `RestorePosition` does not work because the saved position is not 76 | /// intended one after rendering a new line. 77 | fn initialize_prompt(&mut self, w: &mut W) -> io::Result<()> 78 | where 79 | W: Write, 80 | { 81 | for _ in 0..self.num_whole_lines { 82 | w.execute(Print("\r\n"))?; 83 | } 84 | w.execute(MoveToPreviousLine(self.num_whole_lines as u16))?; 85 | Ok(()) 86 | } 87 | 88 | fn start_event_loop(&mut self, w: &mut W) -> io::Result<()> 89 | where 90 | W: Write, 91 | { 92 | loop { 93 | match self.handle_key_event(w)? { 94 | Action::Confirm => { 95 | break; 96 | } 97 | Action::Quit => { 98 | self.input = Vec::new(); 99 | break; 100 | } 101 | Action::Update => { 102 | self.render_input(w)?; 103 | 104 | self.render_candidates(w)?; 105 | 106 | w.execute(MoveToPreviousLine((self.num_whole_lines - 1) as u16))? 107 | .execute(MoveRight(self.input_line_len()))?; 108 | } 109 | Action::None => {} 110 | } 111 | } 112 | 113 | Ok(()) 114 | } 115 | 116 | fn input_line_len(&self) -> u16 { 117 | (Self::PROMPT_SYMBOL.len() + self.input.len()) as u16 118 | } 119 | 120 | fn handle_key_event(&mut self, w: &mut W) -> io::Result 121 | where 122 | W: Write, 123 | { 124 | if poll(Duration::from_millis(Self::POLL_DURATION_MS))? { 125 | if let Event::Key(KeyEvent { 126 | code, modifiers, .. 127 | }) = read()? 128 | { 129 | let action = match code { 130 | KeyCode::Enter => Action::Confirm, 131 | KeyCode::Esc => Action::Quit, 132 | KeyCode::Char('c') if modifiers == KeyModifiers::CONTROL => Action::Quit, 133 | KeyCode::Backspace => { 134 | w.execute(MoveLeft(1))?; 135 | self.input.pop(); 136 | Action::Update 137 | } 138 | KeyCode::Up => self.move_up_cursor(), 139 | KeyCode::Char('k') if modifiers == KeyModifiers::CONTROL => { 140 | self.move_up_cursor() 141 | } 142 | KeyCode::Down => self.move_down_cursor(), 143 | KeyCode::Char('j') if modifiers == KeyModifiers::CONTROL => { 144 | self.move_down_cursor() 145 | } 146 | KeyCode::Char(c) => { 147 | self.input.push(c); 148 | Action::Update 149 | } 150 | _ => Action::None, 151 | }; 152 | return Ok(action); 153 | } 154 | } 155 | 156 | Ok(Action::None) 157 | } 158 | 159 | fn move_up_cursor(&mut self) -> Action { 160 | if self.current_font > 0 { 161 | self.current_font -= 1; 162 | Action::Update 163 | } else { 164 | Action::None 165 | } 166 | } 167 | 168 | fn move_down_cursor(&mut self) -> Action { 169 | if self.current_font + 1 < self.fonts.len() { 170 | self.current_font += 1; 171 | Action::Update 172 | } else { 173 | Action::None 174 | } 175 | } 176 | 177 | fn render_input(&mut self, w: &mut W) -> io::Result<()> 178 | where 179 | W: Write, 180 | { 181 | w.execute(MoveLeft(self.input_line_len()))? 182 | .execute(Clear(ClearType::CurrentLine))? 183 | .execute(Print(format!( 184 | "{}{}", 185 | Self::PROMPT_SYMBOL.blue(), 186 | self.input.iter().collect::() 187 | )))?; 188 | Ok(()) 189 | } 190 | 191 | fn render_candidates(&mut self, w: &mut W) -> io::Result<()> 192 | where 193 | W: Write, 194 | { 195 | for i in 0..self.fonts.len() { 196 | let selection = if i == self.current_font { "> " } else { " " }; 197 | 198 | w.queue(MoveToNextLine(1))? 199 | .queue(Clear(ClearType::CurrentLine))? 200 | .queue(Print(format!( 201 | "{}{}", 202 | selection.red(), 203 | self.converter.convert(&self.input, self.fonts[i]) 204 | )))?; 205 | } 206 | w.flush()?; 207 | 208 | Ok(()) 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /src/convert.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use crate::font::{Font, FontMap}; 4 | 5 | /// This struct holds each font's mapping between normal characters to ones of the font. 6 | pub struct Converter { 7 | font_mappings: HashMap, 8 | } 9 | 10 | impl Converter { 11 | pub fn new(fonts: &[Font]) -> Self { 12 | let mut font_mappings = HashMap::new(); 13 | for font in fonts { 14 | font_mappings.insert(*font, font.characters()); 15 | } 16 | 17 | Self { font_mappings } 18 | } 19 | 20 | /// Convert given characters to specified font. 21 | /// Non-alphanumeric characters remain unchanged. 22 | pub fn convert(&self, source: &[char], font: Font) -> String { 23 | let mapping = self 24 | .font_mappings 25 | .get(&font) 26 | .expect("Unexpected font specified"); 27 | let converted = source.iter().map(|original| { 28 | if let Some(converted) = mapping.get(original) { 29 | converted 30 | } else { 31 | original 32 | } 33 | }); 34 | 35 | if font == Font::Emoji { 36 | // In this application, we want reginal indicator symbols to be rendered as emoji. 37 | // To prevent them from being rendered as flags, we insert zero-width joiner(U+200C) between each character. 38 | // For a simple implementation, we U+200C between all characters. 39 | converted 40 | .map(|c| c.to_string()) 41 | .collect::>() 42 | .join('\u{200C}'.to_string().as_str()) 43 | } else { 44 | converted.collect() 45 | } 46 | } 47 | } 48 | 49 | #[cfg(test)] 50 | mod tests { 51 | use super::*; 52 | use clap::ValueEnum; 53 | 54 | fn setup_converter() -> Converter { 55 | Converter::new(Font::value_variants()) 56 | } 57 | 58 | #[test] 59 | fn skip_non_target_chars() { 60 | let converter = setup_converter(); 61 | let source = "ernct_jahmlsz あwgdqi-uxfpvobyk" 62 | .chars() 63 | .collect::>(); 64 | assert_eq!( 65 | "𝐞𝐫𝐧𝐜𝐭_𝐣𝐚𝐡𝐦𝐥𝐬𝐳 あ𝐰𝐠𝐝𝐪𝐢-𝐮𝐱𝐟𝐩𝐯𝐨𝐛𝐲𝐤", 66 | converter.convert(&source, Font::Bold) 67 | ); 68 | } 69 | 70 | #[test] 71 | fn bold() { 72 | let converter = setup_converter(); 73 | let source = "8WymXbLV3nINUhOoQkKGfuY9HsZSC675jzBEtATDFMRgPpeaxiJcr0q4l1w2dv" 74 | .chars() 75 | .collect::>(); 76 | assert_eq!( 77 | "𝟖𝐖𝐲𝐦𝐗𝐛𝐋𝐕𝟑𝐧𝐈𝐍𝐔𝐡𝐎𝐨𝐐𝐤𝐊𝐆𝐟𝐮𝐘𝟗𝐇𝐬𝐙𝐒𝐂𝟔𝟕𝟓𝐣𝐳𝐁𝐄𝐭𝐀𝐓𝐃𝐅𝐌𝐑𝐠𝐏𝐩𝐞𝐚𝐱𝐢𝐉𝐜𝐫𝟎𝐪𝟒𝐥𝟏𝐰𝟐𝐝𝐯", 78 | converter.convert(&source, Font::Bold) 79 | ); 80 | } 81 | 82 | #[test] 83 | fn italic() { 84 | let converter = setup_converter(); 85 | let source = "8WymXbLV3nINUhOoQkKGfuY9HsZSC675jzBEtATDFMRgPpeaxiJcr0q4l1w2dv" 86 | .chars() 87 | .collect::>(); 88 | assert_eq!( 89 | "8𝑊𝑦𝑚𝑋𝑏𝐿𝑉3𝑛𝐼𝑁𝑈ℎ𝑂𝑜𝑄𝑘𝐾𝐺𝑓𝑢𝑌9𝐻𝑠𝑍𝑆𝐶675𝑗𝑧𝐵𝐸𝑡𝐴𝑇𝐷𝐹𝑀𝑅𝑔𝑃𝑝𝑒𝑎𝑥𝑖𝐽𝑐𝑟0𝑞4𝑙1𝑤2𝑑𝑣", 90 | converter.convert(&source, Font::Italic) 91 | ); 92 | } 93 | 94 | #[test] 95 | fn bold_italic() { 96 | let converter = setup_converter(); 97 | let source = "8WymXbLV3nINUhOoQkKGfuY9HsZSC675jzBEtATDFMRgPpeaxiJcr0q4l1w2dv" 98 | .chars() 99 | .collect::>(); 100 | assert_eq!( 101 | "8𝑾𝒚𝒎𝑿𝒃𝑳𝑽3𝒏𝑰𝑵𝑼𝒉𝑶𝒐𝑸𝒌𝑲𝑮𝒇𝒖𝒀9𝑯𝒔𝒁𝑺𝑪675𝒋𝒛𝑩𝑬𝒕𝑨𝑻𝑫𝑭𝑴𝑹𝒈𝑷𝒑𝒆𝒂𝒙𝒊𝑱𝒄𝒓0𝒒4𝒍1𝒘2𝒅𝒗", 102 | converter.convert(&source, Font::BoldItalic) 103 | ); 104 | } 105 | 106 | #[test] 107 | fn sans() { 108 | let converter = setup_converter(); 109 | let source = "8WymXbLV3nINUhOoQkKGfuY9HsZSC675jzBEtATDFMRgPpeaxiJcr0q4l1w2dv" 110 | .chars() 111 | .collect::>(); 112 | assert_eq!( 113 | "𝟪𝖶𝗒𝗆𝖷𝖻𝖫𝖵𝟥𝗇𝖨𝖭𝖴𝗁𝖮𝗈𝖰𝗄𝖪𝖦𝖿𝗎𝖸𝟫𝖧𝗌𝖹𝖲𝖢𝟨𝟩𝟧𝗃𝗓𝖡𝖤𝗍𝖠𝖳𝖣𝖥𝖬𝖱𝗀𝖯𝗉𝖾𝖺𝗑𝗂𝖩𝖼𝗋𝟢𝗊𝟦𝗅𝟣𝗐𝟤𝖽𝗏", 114 | converter.convert(&source, Font::Sans) 115 | ); 116 | } 117 | 118 | #[test] 119 | fn bold_sans() { 120 | let converter = setup_converter(); 121 | let source = "8WymXbLV3nINUhOoQkKGfuY9HsZSC675jzBEtATDFMRgPpeaxiJcr0q4l1w2dv" 122 | .chars() 123 | .collect::>(); 124 | assert_eq!( 125 | "𝟴𝗪𝘆𝗺𝗫𝗯𝗟𝗩𝟯𝗻𝗜𝗡𝗨𝗵𝗢𝗼𝗤𝗸𝗞𝗚𝗳𝘂𝗬𝟵𝗛𝘀𝗭𝗦𝗖𝟲𝟳𝟱𝗷𝘇𝗕𝗘𝘁𝗔𝗧𝗗𝗙𝗠𝗥𝗴𝗣𝗽𝗲𝗮𝘅𝗶𝗝𝗰𝗿𝟬𝗾𝟰𝗹𝟭𝘄𝟮𝗱𝘃", 126 | converter.convert(&source, Font::BoldSans) 127 | ); 128 | } 129 | 130 | #[test] 131 | fn italic_sans() { 132 | let converter = setup_converter(); 133 | let source = "8WymXbLV3nINUhOoQkKGfuY9HsZSC675jzBEtATDFMRgPpeaxiJcr0q4l1w2dv" 134 | .chars() 135 | .collect::>(); 136 | assert_eq!( 137 | "𝟴𝘞𝘺𝘮𝘟𝘣𝘓𝘝𝟯𝘯𝘐𝘕𝘜𝘩𝘖𝘰𝘘𝘬𝘒𝘎𝘧𝘶𝘠𝟵𝘏𝘴𝘡𝘚𝘊𝟲𝟳𝟱𝘫𝘻𝘉𝘌𝘵𝘈𝘛𝘋𝘍𝘔𝘙𝘨𝘗𝘱𝘦𝘢𝘹𝘪𝘑𝘤𝘳𝟬𝘲𝟰𝘭𝟭𝘸𝟮𝘥𝘷", 138 | converter.convert(&source, Font::ItalicSans) 139 | ); 140 | } 141 | 142 | #[test] 143 | fn bold_italic_sans() { 144 | let converter = setup_converter(); 145 | let source = "8WymXbLV3nINUhOoQkKGfuY9HsZSC675jzBEtATDFMRgPpeaxiJcr0q4l1w2dv" 146 | .chars() 147 | .collect::>(); 148 | assert_eq!( 149 | "𝟴𝙒𝙮𝙢𝙓𝙗𝙇𝙑𝟯𝙣𝙄𝙉𝙐𝙝𝙊𝙤𝙌𝙠𝙆𝙂𝙛𝙪𝙔𝟵𝙃𝙨𝙕𝙎𝘾𝟲𝟳𝟱𝙟𝙯𝘽𝙀𝙩𝘼𝙏𝘿𝙁𝙈𝙍𝙜𝙋𝙥𝙚𝙖𝙭𝙞𝙅𝙘𝙧𝟬𝙦𝟰𝙡𝟭𝙬𝟮𝙙𝙫", 150 | converter.convert(&source, Font::BoldItalicSans) 151 | ); 152 | } 153 | 154 | #[test] 155 | fn script() { 156 | let converter = setup_converter(); 157 | let source = "8WymXbLV3nINUhOoQkKGfuY9HsZSC675jzBEtATDFMRgPpeaxiJcr0q4l1w2dv" 158 | .chars() 159 | .collect::>(); 160 | assert_eq!( 161 | "8𝒲𝓎𝓂𝒳𝒷ℒ𝒱3𝓃ℐ𝒩𝒰𝒽𝒪ℴ𝒬𝓀𝒦𝒢𝒻𝓊𝒴9ℋ𝓈𝒵𝒮𝒞675𝒿𝓏ℬℰ𝓉𝒜𝒯𝒟ℱℳℛℊ𝒫𝓅ℯ𝒶𝓍𝒾𝒥𝒸𝓇0𝓆4𝓁1𝓌2𝒹𝓋", 162 | converter.convert(&source, Font::Script) 163 | ); 164 | } 165 | 166 | #[test] 167 | fn bold_script() { 168 | let converter = setup_converter(); 169 | let source = "8WymXbLV3nINUhOoQkKGfuY9HsZSC675jzBEtATDFMRgPpeaxiJcr0q4l1w2dv" 170 | .chars() 171 | .collect::>(); 172 | assert_eq!( 173 | "8𝓦𝔂𝓶𝓧𝓫𝓛𝓥3𝓷𝓘𝓝𝓤𝓱𝓞𝓸𝓠𝓴𝓚𝓖𝓯𝓾𝓨9𝓗𝓼𝓩𝓢𝓒675𝓳𝔃𝓑𝓔𝓽𝓐𝓣𝓓𝓕𝓜𝓡𝓰𝓟𝓹𝓮𝓪𝔁𝓲𝓙𝓬𝓻0𝓺4𝓵1𝔀2𝓭𝓿", 174 | converter.convert(&source, Font::BoldScript) 175 | ); 176 | } 177 | 178 | #[test] 179 | fn fraktur() { 180 | let converter = setup_converter(); 181 | let source = "8WymXbLV3nINUhOoQkKGfuY9HsZSC675jzBEtATDFMRgPpeaxiJcr0q4l1w2dv" 182 | .chars() 183 | .collect::>(); 184 | assert_eq!( 185 | "8𝔚𝔶𝔪𝔛𝔟𝔏𝔙3𝔫ℑ𝔑𝔘𝔥𝔒𝔬𝔔𝔨𝔎𝔊𝔣𝔲𝔜9ℌ𝔰ℨ𝔖ℭ675𝔧𝔷𝔅𝔈𝔱𝔄𝔗𝔇𝔉𝔐ℜ𝔤𝔓𝔭𝔢𝔞𝔵𝔦𝔍𝔠𝔯0𝔮4𝔩1𝔴2𝔡𝔳", 186 | converter.convert(&source, Font::Fraktur) 187 | ); 188 | } 189 | 190 | #[test] 191 | fn bold_fraktur() { 192 | let converter = setup_converter(); 193 | let source = "8WymXbLV3nINUhOoQkKGfuY9HsZSC675jzBEtATDFMRgPpeaxiJcr0q4l1w2dv" 194 | .chars() 195 | .collect::>(); 196 | assert_eq!( 197 | "8𝖂𝖞𝖒𝖃𝖇𝕷𝖁3𝖓𝕴𝕹𝖀𝖍𝕺𝖔𝕼𝖐𝕶𝕲𝖋𝖚𝖄9𝕳𝖘𝖅𝕾𝕮675𝖏𝖟𝕭𝕰𝖙𝕬𝕿𝕯𝕱𝕸𝕽𝖌𝕻𝖕𝖊𝖆𝖝𝖎𝕵𝖈𝖗0𝖖4𝖑1𝖜2𝖉𝖛", 198 | converter.convert(&source, Font::BoldFraktur) 199 | ); 200 | } 201 | 202 | #[test] 203 | fn monospace() { 204 | let converter = setup_converter(); 205 | let source = "8WymXbLV3nINUhOoQkKGfuY9HsZSC675jzBEtATDFMRgPpeaxiJcr0q4l1w2dv" 206 | .chars() 207 | .collect::>(); 208 | assert_eq!( 209 | "𝟾𝚆𝚢𝚖𝚇𝚋𝙻𝚅𝟹𝚗𝙸𝙽𝚄𝚑𝙾𝚘𝚀𝚔𝙺𝙶𝚏𝚞𝚈𝟿𝙷𝚜𝚉𝚂𝙲𝟼𝟽𝟻𝚓𝚣𝙱𝙴𝚝𝙰𝚃𝙳𝙵𝙼𝚁𝚐𝙿𝚙𝚎𝚊𝚡𝚒𝙹𝚌𝚛𝟶𝚚𝟺𝚕𝟷𝚠𝟸𝚍𝚟", 210 | converter.convert(&source, Font::Monospace) 211 | ); 212 | } 213 | 214 | #[test] 215 | fn blackboard() { 216 | let converter = setup_converter(); 217 | let source = "8WymXbLV3nINUhOoQkKGfuY9HsZSC675jzBEtATDFMRgPpeaxiJcr0q4l1w2dv" 218 | .chars() 219 | .collect::>(); 220 | assert_eq!( 221 | "𝟠𝕎𝕪𝕞𝕏𝕓𝕃𝕍𝟛𝕟𝕀ℕ𝕌𝕙𝕆𝕠ℚ𝕜𝕂𝔾𝕗𝕦𝕐𝟡ℍ𝕤ℤ𝕊ℂ𝟞𝟟𝟝𝕛𝕫𝔹𝔼𝕥𝔸𝕋𝔻𝔽𝕄ℝ𝕘ℙ𝕡𝕖𝕒𝕩𝕚𝕁𝕔𝕣𝟘𝕢𝟜𝕝𝟙𝕨𝟚𝕕𝕧", 222 | converter.convert(&source, Font::Blackboard) 223 | ); 224 | } 225 | 226 | #[test] 227 | fn emoji() { 228 | let converter = setup_converter(); 229 | let source = "8WymXbLV3nINUhOoQkKGfuY9HsZSC675jzBEtATDFMRgPpeaxiJcr0q4l1w2dv" 230 | .chars() 231 | .collect::>(); 232 | assert_eq!( 233 | "8‌🇼‌🇾‌🇲‌🇽‌🇧‌🇱‌🇻‌3‌🇳‌🇮‌🇳‌🇺‌🇭‌🇴‌🇴‌🇶‌🇰‌🇰‌🇬‌🇫‌🇺‌🇾‌9‌🇭‌🇸‌🇿‌🇸‌🇨‌6‌7‌5‌🇯‌🇿‌🇧‌🇪‌🇹‌🇦‌🇹‌🇩‌🇫‌🇲‌🇷‌🇬‌🇵‌🇵‌🇪‌🇦‌🇽‌🇮‌🇯‌🇨‌🇷‌0‌🇶‌4‌🇱‌1‌🇼‌2‌🇩‌🇻", 234 | converter.convert(&source, Font::Emoji) 235 | ); 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "anstream" 7 | version = "0.6.19" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" 10 | dependencies = [ 11 | "anstyle", 12 | "anstyle-parse", 13 | "anstyle-query", 14 | "anstyle-wincon", 15 | "colorchoice", 16 | "is_terminal_polyfill", 17 | "utf8parse", 18 | ] 19 | 20 | [[package]] 21 | name = "anstyle" 22 | version = "1.0.11" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" 25 | 26 | [[package]] 27 | name = "anstyle-parse" 28 | version = "0.2.7" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" 31 | dependencies = [ 32 | "utf8parse", 33 | ] 34 | 35 | [[package]] 36 | name = "anstyle-query" 37 | version = "1.1.3" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" 40 | dependencies = [ 41 | "windows-sys", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle-wincon" 46 | version = "3.0.9" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" 49 | dependencies = [ 50 | "anstyle", 51 | "once_cell_polyfill", 52 | "windows-sys", 53 | ] 54 | 55 | [[package]] 56 | name = "autocfg" 57 | version = "1.4.0" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 60 | 61 | [[package]] 62 | name = "bitflags" 63 | version = "2.9.1" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" 66 | 67 | [[package]] 68 | name = "cfg-if" 69 | version = "1.0.0" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 72 | 73 | [[package]] 74 | name = "clap" 75 | version = "4.5.53" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" 78 | dependencies = [ 79 | "clap_builder", 80 | "clap_derive", 81 | ] 82 | 83 | [[package]] 84 | name = "clap_builder" 85 | version = "4.5.53" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" 88 | dependencies = [ 89 | "anstream", 90 | "anstyle", 91 | "clap_lex", 92 | "strsim", 93 | ] 94 | 95 | [[package]] 96 | name = "clap_derive" 97 | version = "4.5.49" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" 100 | dependencies = [ 101 | "heck", 102 | "proc-macro2", 103 | "quote", 104 | "syn", 105 | ] 106 | 107 | [[package]] 108 | name = "clap_lex" 109 | version = "0.7.4" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 112 | 113 | [[package]] 114 | name = "colorchoice" 115 | version = "1.0.4" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" 118 | 119 | [[package]] 120 | name = "convert_case" 121 | version = "0.7.1" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7" 124 | dependencies = [ 125 | "unicode-segmentation", 126 | ] 127 | 128 | [[package]] 129 | name = "crossterm" 130 | version = "0.29.0" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" 133 | dependencies = [ 134 | "bitflags", 135 | "crossterm_winapi", 136 | "derive_more", 137 | "document-features", 138 | "mio", 139 | "parking_lot", 140 | "rustix", 141 | "signal-hook", 142 | "signal-hook-mio", 143 | "winapi", 144 | ] 145 | 146 | [[package]] 147 | name = "crossterm_winapi" 148 | version = "0.9.1" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" 151 | dependencies = [ 152 | "winapi", 153 | ] 154 | 155 | [[package]] 156 | name = "derive_more" 157 | version = "2.0.1" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" 160 | dependencies = [ 161 | "derive_more-impl", 162 | ] 163 | 164 | [[package]] 165 | name = "derive_more-impl" 166 | version = "2.0.1" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" 169 | dependencies = [ 170 | "convert_case", 171 | "proc-macro2", 172 | "quote", 173 | "syn", 174 | ] 175 | 176 | [[package]] 177 | name = "document-features" 178 | version = "0.2.11" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" 181 | dependencies = [ 182 | "litrs", 183 | ] 184 | 185 | [[package]] 186 | name = "errno" 187 | version = "0.3.12" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" 190 | dependencies = [ 191 | "libc", 192 | "windows-sys", 193 | ] 194 | 195 | [[package]] 196 | name = "heck" 197 | version = "0.5.0" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 200 | 201 | [[package]] 202 | name = "is_terminal_polyfill" 203 | version = "1.70.1" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 206 | 207 | [[package]] 208 | name = "libc" 209 | version = "0.2.172" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" 212 | 213 | [[package]] 214 | name = "linux-raw-sys" 215 | version = "0.9.4" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" 218 | 219 | [[package]] 220 | name = "litrs" 221 | version = "0.4.1" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" 224 | 225 | [[package]] 226 | name = "lock_api" 227 | version = "0.4.13" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" 230 | dependencies = [ 231 | "autocfg", 232 | "scopeguard", 233 | ] 234 | 235 | [[package]] 236 | name = "log" 237 | version = "0.4.27" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 240 | 241 | [[package]] 242 | name = "mio" 243 | version = "1.0.4" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" 246 | dependencies = [ 247 | "libc", 248 | "log", 249 | "wasi", 250 | "windows-sys", 251 | ] 252 | 253 | [[package]] 254 | name = "omekasy" 255 | version = "1.3.3" 256 | dependencies = [ 257 | "clap", 258 | "crossterm", 259 | ] 260 | 261 | [[package]] 262 | name = "once_cell_polyfill" 263 | version = "1.70.1" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" 266 | 267 | [[package]] 268 | name = "parking_lot" 269 | version = "0.12.4" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" 272 | dependencies = [ 273 | "lock_api", 274 | "parking_lot_core", 275 | ] 276 | 277 | [[package]] 278 | name = "parking_lot_core" 279 | version = "0.9.11" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" 282 | dependencies = [ 283 | "cfg-if", 284 | "libc", 285 | "redox_syscall", 286 | "smallvec", 287 | "windows-targets", 288 | ] 289 | 290 | [[package]] 291 | name = "proc-macro2" 292 | version = "1.0.95" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" 295 | dependencies = [ 296 | "unicode-ident", 297 | ] 298 | 299 | [[package]] 300 | name = "quote" 301 | version = "1.0.40" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 304 | dependencies = [ 305 | "proc-macro2", 306 | ] 307 | 308 | [[package]] 309 | name = "redox_syscall" 310 | version = "0.5.12" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" 313 | dependencies = [ 314 | "bitflags", 315 | ] 316 | 317 | [[package]] 318 | name = "rustix" 319 | version = "1.0.7" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" 322 | dependencies = [ 323 | "bitflags", 324 | "errno", 325 | "libc", 326 | "linux-raw-sys", 327 | "windows-sys", 328 | ] 329 | 330 | [[package]] 331 | name = "scopeguard" 332 | version = "1.2.0" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 335 | 336 | [[package]] 337 | name = "signal-hook" 338 | version = "0.3.18" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" 341 | dependencies = [ 342 | "libc", 343 | "signal-hook-registry", 344 | ] 345 | 346 | [[package]] 347 | name = "signal-hook-mio" 348 | version = "0.2.4" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" 351 | dependencies = [ 352 | "libc", 353 | "mio", 354 | "signal-hook", 355 | ] 356 | 357 | [[package]] 358 | name = "signal-hook-registry" 359 | version = "1.4.5" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" 362 | dependencies = [ 363 | "libc", 364 | ] 365 | 366 | [[package]] 367 | name = "smallvec" 368 | version = "1.15.0" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" 371 | 372 | [[package]] 373 | name = "strsim" 374 | version = "0.11.1" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 377 | 378 | [[package]] 379 | name = "syn" 380 | version = "2.0.101" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" 383 | dependencies = [ 384 | "proc-macro2", 385 | "quote", 386 | "unicode-ident", 387 | ] 388 | 389 | [[package]] 390 | name = "unicode-ident" 391 | version = "1.0.18" 392 | source = "registry+https://github.com/rust-lang/crates.io-index" 393 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 394 | 395 | [[package]] 396 | name = "unicode-segmentation" 397 | version = "1.12.0" 398 | source = "registry+https://github.com/rust-lang/crates.io-index" 399 | checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 400 | 401 | [[package]] 402 | name = "utf8parse" 403 | version = "0.2.2" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 406 | 407 | [[package]] 408 | name = "wasi" 409 | version = "0.11.0+wasi-snapshot-preview1" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 412 | 413 | [[package]] 414 | name = "winapi" 415 | version = "0.3.9" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 418 | dependencies = [ 419 | "winapi-i686-pc-windows-gnu", 420 | "winapi-x86_64-pc-windows-gnu", 421 | ] 422 | 423 | [[package]] 424 | name = "winapi-i686-pc-windows-gnu" 425 | version = "0.4.0" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 428 | 429 | [[package]] 430 | name = "winapi-x86_64-pc-windows-gnu" 431 | version = "0.4.0" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 434 | 435 | [[package]] 436 | name = "windows-sys" 437 | version = "0.59.0" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 440 | dependencies = [ 441 | "windows-targets", 442 | ] 443 | 444 | [[package]] 445 | name = "windows-targets" 446 | version = "0.52.6" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 449 | dependencies = [ 450 | "windows_aarch64_gnullvm", 451 | "windows_aarch64_msvc", 452 | "windows_i686_gnu", 453 | "windows_i686_gnullvm", 454 | "windows_i686_msvc", 455 | "windows_x86_64_gnu", 456 | "windows_x86_64_gnullvm", 457 | "windows_x86_64_msvc", 458 | ] 459 | 460 | [[package]] 461 | name = "windows_aarch64_gnullvm" 462 | version = "0.52.6" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 465 | 466 | [[package]] 467 | name = "windows_aarch64_msvc" 468 | version = "0.52.6" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 471 | 472 | [[package]] 473 | name = "windows_i686_gnu" 474 | version = "0.52.6" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 477 | 478 | [[package]] 479 | name = "windows_i686_gnullvm" 480 | version = "0.52.6" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 483 | 484 | [[package]] 485 | name = "windows_i686_msvc" 486 | version = "0.52.6" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 489 | 490 | [[package]] 491 | name = "windows_x86_64_gnu" 492 | version = "0.52.6" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 495 | 496 | [[package]] 497 | name = "windows_x86_64_gnullvm" 498 | version = "0.52.6" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 501 | 502 | [[package]] 503 | name = "windows_x86_64_msvc" 504 | version = "0.52.6" 505 | source = "registry+https://github.com/rust-lang/crates.io-index" 506 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 507 | --------------------------------------------------------------------------------