diff options
| author | 2025-05-25 02:03:56 +0800 | |
|---|---|---|
| committer | 2025-05-25 02:03:56 +0800 | |
| commit | 14918d4d997eac2b5cc8aa4bc279482da62a5f97 (patch) | |
| tree | 70ba7d151d5070b692d9e856ecc73a10b7c3492e | |
| parent | 048d776c89a53b624fc64d31e6aa431fb2117e35 (diff) | |
| download | soon-14918d4d997eac2b5cc8aa4bc279482da62a5f97.tar.gz soon-14918d4d997eac2b5cc8aa4bc279482da62a5f97.zip | |
feat: Add initial project files and CI configuration
- Created CI workflow for continuous integration using GitHub Actions.
- Added Python version specification.
- Initialized Cargo.toml and Cargo.lock for Rust project dependencies.
- Implemented main functionality in Rust with command-line interface using Clap.
- Added Python project configuration with Maturin for building and publishing.
- Implemented command history prediction feature in Python.
| -rw-r--r-- | .github/workflows/CI.yml | 168 | ||||
| -rw-r--r-- | .python-version | 1 | ||||
| -rw-r--r-- | Cargo.lock | 383 | ||||
| -rw-r--r-- | Cargo.toml | 16 | ||||
| -rw-r--r-- | pyproject.toml | 23 | ||||
| -rw-r--r-- | soon/__init__.py | 0 | ||||
| -rw-r--r-- | soon/__main__.py | 25 | ||||
| -rw-r--r-- | src/main.rs | 213 | ||||
| -rw-r--r-- | uv.lock | 66 |
9 files changed, 895 insertions, 0 deletions
diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 0000000..34d6b29 --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,168 @@ +# This file is autogenerated by maturin v1.8.2 +# To update, run +# +# maturin generate-ci github +# +name: CI + +on: + push: + branches: + - main + - master + tags: + - '*' + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + linux: + runs-on: ${{ matrix.platform.runner }} + strategy: + matrix: + platform: + - runner: ubuntu-22.04 + target: x86_64 + - runner: ubuntu-22.04 + target: x86 + - runner: ubuntu-22.04 + target: aarch64 + - runner: ubuntu-22.04 + target: armv7 + - runner: ubuntu-22.04 + target: s390x + - runner: ubuntu-22.04 + target: ppc64le + steps: + - uses: actions/checkout@v4 + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.platform.target }} + args: --release --out dist + sccache: ${{ !startsWith(github.ref, 'refs/tags/') }} + manylinux: auto + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-linux-${{ matrix.platform.target }} + path: dist + + musllinux: + runs-on: ${{ matrix.platform.runner }} + strategy: + matrix: + platform: + - runner: ubuntu-22.04 + target: x86_64 + - runner: ubuntu-22.04 + target: x86 + - runner: ubuntu-22.04 + target: aarch64 + - runner: ubuntu-22.04 + target: armv7 + steps: + - uses: actions/checkout@v4 + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.platform.target }} + args: --release --out dist + sccache: ${{ !startsWith(github.ref, 'refs/tags/') }} + manylinux: musllinux_1_2 + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-musllinux-${{ matrix.platform.target }} + path: dist + + windows: + runs-on: ${{ matrix.platform.runner }} + strategy: + matrix: + platform: + - runner: windows-latest + target: x64 + - runner: windows-latest + target: x86 + steps: + - uses: actions/checkout@v4 + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.platform.target }} + args: --release --out dist + sccache: ${{ !startsWith(github.ref, 'refs/tags/') }} + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-windows-${{ matrix.platform.target }} + path: dist + + macos: + runs-on: ${{ matrix.platform.runner }} + strategy: + matrix: + platform: + - runner: macos-13 + target: x86_64 + - runner: macos-14 + target: aarch64 + steps: + - uses: actions/checkout@v4 + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.platform.target }} + args: --release --out dist + sccache: ${{ !startsWith(github.ref, 'refs/tags/') }} + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-macos-${{ matrix.platform.target }} + path: dist + + sdist: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Build sdist + uses: PyO3/maturin-action@v1 + with: + command: sdist + args: --out dist + - name: Upload sdist + uses: actions/upload-artifact@v4 + with: + name: wheels-sdist + path: dist + + release: + name: Release + runs-on: ubuntu-latest + if: ${{ startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' }} + needs: [linux, musllinux, windows, macos, sdist] + permissions: + # Use to sign the release artifacts + id-token: write + # Used to upload release artifacts + contents: write + # Used to generate artifact attestation + attestations: write + steps: + - uses: actions/download-artifact@v4 + - name: Generate artifact attestation + uses: actions/attest-build-provenance@v1 + with: + subject-path: 'wheels-*/*' + - name: Publish to PyPI + if: ${{ startsWith(github.ref, 'refs/tags/') }} + uses: PyO3/maturin-action@v1 + env: + MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }} + with: + command: upload + args: --non-interactive --skip-existing wheels-*/* diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..c8cfe39 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.10 diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..1240bcc --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,383 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6680de5231bd6ee4c6191b8a1325daa282b415391ec9d3a37bd34f2060dc73fa" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.5.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "colored" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "counter" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f009fcafa949dc1fc46a762dae84d0c2687d3b550906b633c4979d58d2c6ae52" +dependencies = [ + "num-traits", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "libc" +version = "0.2.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags", + "libc", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_users" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[package]] +name = "soon" +version = "0.1.0" +dependencies = [ + "clap", + "colored", + "counter", + "dirs", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..2ec763e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "soon" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +# 生成 CLI 可执行文件 +[[bin]] +name = "soon" # CLI 名称与 Python 包名一致 +path = "src/main.rs" + +[dependencies] +clap = { version = "4.0", features = ["derive"] } +colored = "3.0.0" +counter = "0.6.0" +dirs = "6.0.0" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..4b5793b --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,23 @@ +[build-system] +requires = ["maturin>=1.8,<2.0"] +build-backend = "maturin" + +[project] +name = "soon" +requires-python = ">=3.10" +classifiers = [ + "Programming Language :: Rust", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] +dynamic = ["version"] +dependencies = [ + "rich>=14.0.0", +] + +[tool.maturin] +bindings = "bin" +module-name = "soon" + +# [project.scripts] +# soon = "soon:__main__" diff --git a/soon/__init__.py b/soon/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/soon/__init__.py diff --git a/soon/__main__.py b/soon/__main__.py new file mode 100644 index 0000000..113d6a1 --- /dev/null +++ b/soon/__main__.py @@ -0,0 +1,25 @@ +import subprocess +import sys +from rich.console import Console +from rich.panel import Panel + +def run_cli(*args): + result = subprocess.run( + ["soon", *args], + capture_output=True, + text=True + ) + return result + +def main(): + args = sys.argv[1:] + result = run_cli(*args) + console = Console() + if result.returncode == 0: + console.print(Panel(result.stdout.strip(), title="Result")) + else: + console.print(Panel(result.stderr.strip() or "Unknown error", title="Error", style="red")) + return result.returncode + +if __name__ == "__main__": + sys.exit(main())
\ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..a16d18a --- /dev/null +++ b/src/main.rs @@ -0,0 +1,213 @@ +use clap::{Parser, Subcommand}; +use counter::Counter; +use std::collections::HashMap; +use std::env; +use std::fs::File; +use std::io::{BufRead, BufReader}; +use std::path::PathBuf; +use colored::*; + +#[derive(Parser, Debug)] +#[command(name = "soon", about = "Predict your next shell command based on history")] +struct Cli { + #[command(subcommand)] + command: Option<Commands>, + #[arg(long)] + shell: Option<String>, +} + +#[derive(Subcommand, Debug)] +enum Commands { + /// Show the most likely next command + Now, + /// Show most used commands + Stats, + /// Train prediction (WIP) + Learn, + /// Display detected current shell + Which, +} + +fn detect_shell() -> String { + if let Ok(shell) = env::var("SHELL") { + let shell = shell.to_lowercase(); + if shell.contains("zsh") { + "zsh".to_string() + } else if shell.contains("bash") { + "bash".to_string() + } else if shell.contains("fish") { + "fish".to_string() + } else { + "unknown".to_string() + } + } else { + "unknown".to_string() + } +} + +fn history_path(shell: &str) -> Option<PathBuf> { + let home = dirs::home_dir()?; + match shell { + "bash" => Some(home.join(".bash_history")), + "zsh" => Some(home.join(".zsh_history")), + "fish" => Some(home.join(".local/share/fish/fish_history")), + _ => None, + } +} + +#[derive(Debug)] +struct HistoryItem { + cmd: String, + path: Option<String>, +} + +fn load_history(shell: &str) -> Vec<HistoryItem> { + let path = match history_path(shell) { + Some(p) => p, + None => return vec![], + }; + let file = match File::open(&path) { + Ok(f) => f, + Err(_) => return vec![], + }; + let reader = BufReader::new(file); + + let mut result = Vec::new(); + if shell == "fish" { + let mut last_cmd: Option<String> = None; + let mut last_path: Option<String> = None; + for line in reader.lines().flatten() { + if let Some(cmd) = line.strip_prefix("- cmd: ") { + last_cmd = Some(cmd.trim().to_string()); + last_path = None; + } else if let Some(path) = line.strip_prefix(" path: ") { + last_path = Some(path.trim().to_string()); + } + + if let Some(cmd) = &last_cmd { + if line.starts_with("- cmd: ") || line.is_empty() { + result.push(HistoryItem { + cmd: cmd.clone(), + path: last_path.clone(), + }); + last_cmd = None; + last_path = None; + } + } + } + + if let Some(cmd) = last_cmd { + result.push(HistoryItem { + cmd, + path: last_path, + }); + } + } else { + for line in reader.lines().flatten() { + let line = if shell == "zsh" { + line.trim_start_matches(|c: char| c == ':' || c.is_digit(10) || c == ';').trim().to_string() + } else { + line.trim().to_string() + }; + if !line.is_empty() { + result.push(HistoryItem { cmd: line, path: None }); + } + } + } + result +} + +fn weighted_suggestions(history: &[HistoryItem], cwd: &str, shell: &str) -> Option<String> { + let dir_name = std::path::Path::new(cwd) + .file_name() + .and_then(|s| s.to_str()) + .unwrap_or(""); + let mut scores: HashMap<&str, f64> = HashMap::new(); + for (i, item) in history.iter().rev().enumerate() { + let mut score = 100.0 - i as f64 * 0.5; + + if let Some(ref p) = item.path { + if p == cwd { + score *= 2.0; + } + } + + if !cwd.is_empty() && item.cmd.contains(cwd) { + score *= 1.5; + } + + if !dir_name.is_empty() && item.cmd.contains(dir_name) { + score *= 1.2; + } + if !shell.is_empty() && item.cmd.contains(shell) { + score *= 1.1; + } + *scores.entry(item.cmd.as_str()).or_insert(0.0) += score; + } + scores.into_iter().max_by(|a, b| a.1.partial_cmp(&b.1).unwrap()).map(|(cmd, _)| cmd.to_string()) +} + +fn soon_now(shell: &str) { + let history = load_history(shell); + if history.is_empty() { + eprintln!("{}", format!("⚠️ Failed to load history for {shell}.").red()); + std::process::exit(1); + } + let cwd = env::current_dir().unwrap_or_default(); + let cwd = cwd.to_string_lossy(); + let suggestion = weighted_suggestions(&history, &cwd, shell); + println!("\n{}", "🔮 You might run next:".magenta().bold()); + if let Some(cmd) = suggestion { + println!("{} {}", "👉".green().bold(), cmd.green().bold()); + } else { + println!("{}", "No suggestion found.".yellow()); + } +} + +fn soon_stats(shell: &str) { + let history = load_history(shell); + if history.is_empty() { + eprintln!("{}", format!("⚠️ Failed to load history for {shell}.").red()); + std::process::exit(1); + } + let mut counter = Counter::<&String, i32>::new(); + for item in &history { + counter.update([&item.cmd]); + } + let mut most_common: Vec<_> = counter.most_common().into_iter().collect(); + most_common.truncate(10); + + println!("{}", "📊 Top 10 most used commands".bold().cyan()); + println!("{:<30}{}", "Command".cyan().bold(), "Usage Count".magenta().bold()); + for (cmd, freq) in most_common { + println!("{:<30}{}", cmd, freq); + } +} + +fn soon_learn(_shell: &str) { + println!("{}", "🧠 [soon learn] feature under development...".yellow()); +} + +fn soon_which(shell: &str) { + println!("{}", format!("🕵️ Current shell: {shell}").yellow().bold()); +} + +fn main() { + let cli = Cli::parse(); + let shell = cli.shell.clone().unwrap_or_else(detect_shell); + + if shell == "unknown" && !matches!(cli.command, Some(Commands::Which)) { + eprintln!("{}", "⚠️ Unknown shell. Please specify with --shell.".red()); + std::process::exit(1); + } + + match cli.command { + Some(Commands::Now) => soon_now(&shell), + Some(Commands::Stats) => soon_stats(&shell), + Some(Commands::Learn) => soon_learn(&shell), + Some(Commands::Which) => soon_which(&shell), + None => { + soon_now(&shell); + } + } +}
\ No newline at end of file @@ -0,0 +1,66 @@ +version = 1 +revision = 1 +requires-python = ">=3.10" + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, +] + +[[package]] +name = "pygments" +version = "2.19.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, +] + +[[package]] +name = "rich" +version = "14.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229 }, +] + +[[package]] +name = "soon" +source = { editable = "." } +dependencies = [ + { name = "rich" }, +] + +[package.metadata] +requires-dist = [{ name = "rich", specifier = ">=14.0.0" }] + +[[package]] +name = "typing-extensions" +version = "4.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806 }, +] |