[
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nindent_style = space\nindent_size = 4\nend_of_line = LF\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[*.{yml,yaml,md}]\nindent_size = 2\n\n[{Makefile,.SRCINFO}]\nindent_style = tab\n\n[*.json]\nindent_style = space\nindent_size = 2\n\n[*.rb]\nindent_style = space\nindent_size = 2\n"
  },
  {
    "path": ".gitattributes",
    "content": "docs/web/** linguist-documentation\n**.snap linguist-language=Plain-Text\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "name: Bug report 🐛\ndescription: Create a bug report to help us improve\nlabels: [\"bug\"]\nbody:\n  - type: markdown\n    attributes:\n      value: Thanks for contributing by creating an issue! ❤️\n  - type: checkboxes\n    attributes:\n      label: Duplicates\n      description: |\n        Please [search the history](https://github.com/o2sh/onefetch/issues) to see if an issue already exists for the same problem.\n\n        📌 If your issue refers to an incorrect language detection, please have a look [here](https://github.com/o2sh/onefetch/issues/26).\n      options:\n        - label: I have searched the existing issues\n          required: true\n  - type: textarea\n    attributes:\n      label: Current behavior 😯\n      description: Describe what happens instead of the expected behavior.\n  - type: textarea\n    attributes:\n      label: Expected behavior 🤔\n      description: Describe what should happen.\n  - type: textarea\n    attributes:\n      label: Steps to reproduce 🕹\n      description: Describe how we can reproduce this bug.\n      placeholder: |\n        1.\n        2.\n        3.\n  - type: textarea\n    attributes:\n      label: Additional context/Screenshots 🔦\n      description: Add any other context about the problem here. If applicable, add screenshots to help explain.\n  - type: textarea\n    attributes:\n      label: Possible Solution 💡\n      description: Only if you have suggestions on a fix for the bug.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "content": "name: Feature request 💄\ndescription: Suggest an improvement\nlabels: [\"enhancement\"]\nbody:\n  - type: markdown\n    attributes:\n      value: Thanks for contributing by creating an issue! ❤️\n  - type: textarea\n    attributes:\n      label: Summary 💡\n      description: Describe how it should work.\n  - type: textarea\n    attributes:\n      label: Motivation 🔦\n      description: >\n        What are you trying to accomplish?\n        How has the lack of this feature affected you?\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/new_language.yml",
    "content": "name: New Language Request 📢\ndescription: Request for a new language to be supported\nlabels: [\"enhancement, good first issue\"]\n\nbody:\n  - type: markdown\n    attributes:\n      value: Thanks for contributing by creating an issue! ❤️\n  - type: textarea\n    attributes:\n      label: Language Name 🖊\n      description: Provide the name of the language and any additional details that we should know.\n  - type: textarea\n    attributes:\n      label: Logo 📷\n      description: Is there a logo that can be used as a source of inspiration for the ASCII art?\n  - type: checkboxes\n    attributes:\n      label: Upstream support ✅\n      description: |\n        Onefetch relies on [tokei](https://github.com/XAMPPRocky/tokei) for language detection.\n      options:\n        - label: Does tokei already support the language in question?\n          required: true\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: cargo\n    directory: \"/\"\n    groups:\n      clap:\n        patterns:\n          - \"clap\"\n          - \"clap_*\"\n      gix:\n        patterns:\n          - \"gix\"\n          - \"gix-*\"\n    schedule:\n      interval: \"weekly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n  - package-ecosystem: npm\n    directory: /docs/web\n    groups:\n      site:\n        patterns:\n          - \"*\"\n    schedule:\n      interval: monthly\n"
  },
  {
    "path": ".github/release.yml",
    "content": "changelog:\n  exclude:\n    labels:\n      - ignore-for-release\n  categories:\n    - title: New Features\n      labels:\n        - feat\n    - title: Bug Fixes\n      labels:\n        - fix\n    - title: Chores\n      labels:\n        - chore\n        - refactor\n        - doc\n    - title: Dependencies\n      labels:\n        - dependencies\n    - title: Other Changes\n      labels:\n        - \"*\"\n"
  },
  {
    "path": ".github/workflows/cd.yml",
    "content": "name: CD\non:\n  release:\n    types: [published]\nenv:\n  CARGO_TERM_COLOR: always\n\njobs:\n  deploy:\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [ubuntu-latest, macos-latest, windows-latest]\n    runs-on: ${{ matrix.os }}\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n\n      - name: Setup Rust\n        uses: actions-rust-lang/setup-rust-toolchain@v1\n\n      - name: Build Release\n        run: make build\n\n      - name: Package release for Mac\n        if: matrix.os == 'macos-latest'\n        run: make release-mac\n\n      - name: Package release for Linux\n        if: matrix.os == 'ubuntu-latest'\n        run: make release-linux\n\n      - name: Package release for Windows\n        if: matrix.os == 'windows-latest'\n        run: make release-win\n\n      - name: Publish to GitHub\n        uses: softprops/action-gh-release@v2\n        with:\n          tag_name: ${{ github.event.release.tag_name }}\n          files: |\n            ./release/*.tar.gz\n            ./release/*.zip\n            ./onefetch-setup.exe\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Publish to Homebrew\n        uses: mislav/bump-homebrew-formula-action@v3\n        if: matrix.os == 'macos-latest'\n        env:\n          COMMITTER_TOKEN: ${{ secrets.BREW_TOKEN }}\n        with:\n          formula-name: onefetch\n\n      - name: Publish to WinGet\n        uses: vedantmgoyal9/winget-releaser@main\n        if: matrix.os == 'windows-latest'\n        with:\n          identifier: o2sh.onefetch\n          installers-regex: '\\.exe$'\n          version: ${{ github.event.release.tag_name }}\n          token: ${{ secrets.WINGET_TOKEN }}\n\n  crates:\n    name: Publish to crates.io\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n\n      - name: Setup Rust\n        uses: actions-rust-lang/setup-rust-toolchain@v1\n\n      - name: Publish to crates.io (ascii)\n        env:\n          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}\n        run: cargo publish -p onefetch-ascii\n\n      - name: Publish to crates.io (image)\n        env:\n          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}\n        run: cargo publish -p onefetch-image\n\n      - name: Publish to crates.io (manifest)\n        env:\n          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}\n        run: cargo publish -p onefetch-manifest\n\n      - name: Publish to crates.io\n        env:\n          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}\n        run: cargo publish\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\non:\n  push:\n    branches: [\"*\"]\n    paths-ignore:\n      - \"docs/**\"\n      - \"**.md\"\n      - \"docs/web/**\"\n  pull_request:\n    branches: [main]\n    paths-ignore:\n      - \"docs/**\"\n      - \"**.md\"\n      - \"docs/web/**\"\nenv:\n  CARGO_TERM_COLOR: always\n\njobs:\n  CI:\n    strategy:\n      matrix:\n        os: [ubuntu-latest, windows-latest, macOS-latest]\n    runs-on: ${{ matrix.os }}\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n\n      - name: Setup Rust\n        uses: actions-rust-lang/setup-rust-toolchain@v1\n\n      - name: Build\n        run: cargo build --features=fail-on-deprecated\n\n      - name: Run tests\n        run: cargo test\n"
  },
  {
    "path": ".github/workflows/msrv-badge.yml",
    "content": "name: MSRV Badge\non:\n  schedule:\n    # Once a week at midnight UTC\n    - cron: \"0 0 * * 0\"\n  workflow_dispatch:\n\njobs:\n  make-badge:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n        with:\n          ref: ${{ github.event.pull_request.head.ref }}\n          token: ${{ secrets.BADGE_TOKEN }}\n\n      - name: Get Minimum Supported Rust Version\n        uses: spenserblack/actions-msrv@v0.4\n        id: get-msrv\n        timeout-minutes: 60\n\n      - name: Create Badge\n        run: curl https://img.shields.io/badge/rustc-${{ steps.get-msrv.outputs.msrv }}%2B-blue > ./assets/msrv-badge.svg\n\n      - name: Commit Badge\n        continue-on-error: true\n        run: |\n          git status\n          git pull --ff-only\n          git add ./assets/msrv-badge.svg\n          git config user.name \"github-actions[bot]\"\n          git config user.email \"github-actions[bot]@users.noreply.github.com\"\n          git commit -m \"Update msrv badge [Skip CI]\"\n          git push\n"
  },
  {
    "path": ".github/workflows/web-ci.yml",
    "content": "name: Web UI CI\non:\n  push:\n    branches: [\"main\"]\n    paths:\n      - \"docs/web/**\"\n  pull_request:\n    branches: [main]\n    paths:\n      - \"docs/web/**\"\n\njobs:\n  check:\n    name: Check Code Quality\n    runs-on: ubuntu-latest\n    defaults:\n      run:\n        working-directory: docs/web\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n\n      - name: Setup Node\n        uses: actions/setup-node@v6\n        with:\n          node-version: 20\n          cache: npm\n          cache-dependency-path: docs/web/package-lock.json\n\n      - name: Install Dependencies\n        run: npm ci\n\n      - name: Svelte Check\n        run: npm run check:svelte -- --fail-on-warnings\n\n      - name: ESLint\n        run: npm run check:lint\n\n      - name: Prettier\n        run: npm run check:prettier\n"
  },
  {
    "path": ".github/workflows/wiki.yml",
    "content": "name: Publish Wiki\non:\n  push:\n    branches: [main]\n    paths:\n      - \"docs/wiki/**\"\n\n  workflow_dispatch:\n\nconcurrency:\n  group: wiki\n  cancel-in-progress: true\npermissions:\n  contents: write\njobs:\n  wiki:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - uses: spenserblack/actions-wiki@v0.3.0\n        with:\n          path: docs/wiki\n"
  },
  {
    "path": ".github/workflows/windows-installer.iss",
    "content": "; Script generated by the Inno Setup Script Wizard.\n; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!\n\n#define MyAppName \"onefetch\"\n;#define MyAppVersion \"1.0\"\n#define MyAppPublisher \"Ossama Hjaji\"\n#define MyAppURL \"https://github.com/o2sh/onefetch\"\n#define MyAppExeName \"onefetch.exe\"\n\n[Setup]\n; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.\n; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)\nAppId={{BB44DE71-B34D-4707-AE9D-5FF3FA632283}\nAppName={#MyAppName}\nAppVersion={#MyAppVersion}\n;AppVerName={#MyAppName} {#MyAppVersion}\nAppPublisher={#MyAppPublisher}\nAppPublisherURL={#MyAppURL}\nAppSupportURL={#MyAppURL}\nAppUpdatesURL={#MyAppURL}\nDefaultDirName={autopf}\\{#MyAppName}\nDisableDirPage=yes\nDisableProgramGroupPage=yes\n; Uncomment the following line to run in non administrative install mode (install for current user only.)\n;PrivilegesRequired=lowest\nOutputDir=..\\..\nOutputBaseFilename=onefetch-setup\nSetupIconFile=..\\..\\assets\\onefetch.ico\r\nUninstallDisplayIcon={app}\\{#MyAppExeName}\nCompression=lzma\nSolidCompression=yes\nWizardStyle=modern\nChangesEnvironment=true\n\n[Languages]\nName: \"english\"; MessagesFile: \"compiler:Default.isl\"\n\n[Files]\nSource: \"..\\..\\target\\release\\{#MyAppExeName}\"; DestDir: \"{app}\"; Flags: ignoreversion\n; NOTE: Don't use \"Flags: ignoreversion\" on any shared system files\n\n[Icons]\nName: \"{autoprograms}\\{#MyAppName}\"; Filename: \"{app}\\{#MyAppExeName}\"\n\n[Code]\n{ https://stackoverflow.com/a/46609047/149111 }\nconst EnvironmentKey = 'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment';\nprocedure EnvAddPath(instlPath: string);\nvar\n  Paths: string;\nbegin\n  { Retrieve current path (use empty string if entry not exists) }\n  if not RegQueryStringValue(HKEY_LOCAL_MACHINE, EnvironmentKey, 'Path', Paths) then\n    Paths := '';\n  if Paths = '' then\n    Paths := instlPath + ';'\n  else\n  begin\n    { Skip if string already found in path }\n    if Pos(';' + Uppercase(instlPath) + ';',  ';' + Uppercase(Paths) + ';') > 0 then exit;\n    if Pos(';' + Uppercase(instlPath) + '\\;', ';' + Uppercase(Paths) + ';') > 0 then exit;\n    { Append App Install Path to the end of the path variable }\n    if Paths[length(Paths)] <> ';' then\n      Paths := Paths + ';';\n    Paths := Paths + instlPath + ';';\n  end;\n  { Overwrite (or create if missing) path environment variable }\n  if RegWriteStringValue(HKEY_LOCAL_MACHINE, EnvironmentKey, 'Path', Paths)\n  then Log(Format('The [%s] added to PATH: [%s]', [instlPath, Paths]))\n  else Log(Format('Error while adding the [%s] to PATH: [%s]', [instlPath, Paths]));\nend;\nprocedure EnvRemovePath(instlPath: string);\nvar\n  Paths: string;\n  P, Offset, DelimLen: Integer;\nbegin\n  { Skip if registry entry not exists }\n  if not RegQueryStringValue(HKEY_LOCAL_MACHINE, EnvironmentKey, 'Path', Paths) then\n    exit;\n  { Skip if string not found in path }\n  DelimLen := 1;     { Length(';') }\n  P := Pos(';' + Uppercase(instlPath) + ';', ';' + Uppercase(Paths) + ';');\n  if P = 0 then\n  begin\n    { perhaps instlPath lives in Paths, but terminated by '\\;' }\n    DelimLen := 2; { Length('\\;') }\n    P := Pos(';' + Uppercase(instlPath) + '\\;', ';' + Uppercase(Paths) + ';');\n    if P = 0 then exit;\n  end;\n  { Decide where to start string subset in Delete() operation. }\n  if P = 1 then\n    Offset := 0\n  else\n    Offset := 1;\n  { Update path variable }\n  Delete(Paths, P - Offset, Length(instlPath) + DelimLen);\n  { Overwrite path environment variable }\n  if RegWriteStringValue(HKEY_LOCAL_MACHINE, EnvironmentKey, 'Path', Paths)\n  then Log(Format('The [%s] removed from PATH: [%s]', [instlPath, Paths]))\n  else Log(Format('Error while removing the [%s] from PATH: [%s]', [instlPath, Paths]));\nend;\n\nprocedure CurStepChanged(CurStep: TSetupStep);\nbegin\n  if CurStep = ssPostInstall then\n    EnvAddPath(ExpandConstant('{app}'));\nend;\nprocedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);\nbegin\n  if CurUninstallStep = usPostUninstall then\n    EnvRemovePath(ExpandConstant('{app}'));\nend;\n"
  },
  {
    "path": ".gitignore",
    "content": "/target\n**/*.rs.bk\n/stage\n/parts\n/prime\n.gitignore.swp\n.DS_Store\nresult\n**/generated-*\n**/.idea\n"
  },
  {
    "path": ".mailmap",
    "content": "Ossama Hjaji <ossama-hjaji@live.fr>\nKomeil Parseh <ahmdparsh129@gmail.com>\nEduardo Broto <ebroto@tutanota.com>\n"
  },
  {
    "path": ".rustfmt.toml",
    "content": "# See also https://rust-lang.github.io/rustfmt for more settings.\nedition = \"2024\"\nnewline_style = \"Unix\"\n"
  },
  {
    "path": ".tokeignore",
    "content": "docs/web\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"search.exclude\": {\n    \"**/node_modules\": false,\n    \"**/target\": true\n  }\n}\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n## 2.27.1 (2025-3-19)\n\n### Bug Fixes\n\n- Fix CD pipeline by @o2sh in 497d4c011ede6acba4b3ca4c1e62f9aecaf20528\n\n## 2.27.0 (2025-3-19)\n\n### New Features\n\n- add language support for Nushell by @o2sh in https://github.com/o2sh/onefetch/pull/1687\n- add language support for Slint by @opmr0 in https://github.com/o2sh/onefetch/pull/1710\n\n### Bug Fixes\n\n- fix npm manifest parsing by @o2sh in https://github.com/o2sh/onefetch/pull/1693\n- always show ascii-language when provided by CLI by @o2sh in https://github.com/o2sh/onefetch/pull/1713\n- ascii_colors should match ascii-language colors when provided by CLI when no language detected bcfca6ba6f4fb05b723b64c5a2d6b315d7c74e71\n\n### Chores\n\n- simplify logic for commit graph traversal, reduce parallelization by @o2sh in https://github.com/o2sh/onefetch/pull/1682\n\n### onefetch.dev\n\n- Add Copy button with toaster to Ascii preview by @o2sh in https://github.com/o2sh/onefetch/pull/1679\n\n## 2.26.1 (2025-12-21)\n\n### Bug Fixes\n\n- Fix CD pipeline by @o2sh in becddb963ce78f25f2a1f88921b34f9a61e08cc9\n\n## 2.26.0 (2025-12-21)\n\n### New Features\n\n- Add colors to --help by @starsep in https://github.com/o2sh/onefetch/pull/1633\n\n### Chores\n\n- No unsafe by @Sk7Str1p3 in https://github.com/o2sh/onefetch/pull/1646\n- bump cargo edition to 2024 by @o2sh in a3062f48db4eb2ff43cf8965f33f60b9454b4908\n- increase default color resolution for sixel by @o2sh in 73300b60e38920aced8cfee376ad1051a1706f76\n\n### Bug Fixes\n\n- skip get_main_language call if no language detected by @o2sh in 10603ae2e6259c93595f5e5e7ff35b1d312541af\n\n## 2.25.0 (2025-07-06)\n\n### New Features\n\n- add language support for Text by @Kajiih in https://github.com/o2sh/onefetch/pull/1579\n- support repos \"without source code\" by @o2sh in https://github.com/o2sh/onefetch/pull/1580\n\n### Chores\n\n- improve error handling by @Mahdiglm in https://github.com/o2sh/onefetch/pull/1560\n\n### Bug Fixes\n\n- remove extra line break by @o2sh in 886d942c510864612c1ad58a64a9c91b1193b9e9\n\n## 2.24.0 (2025-04-12)\n\n### New Features\n\n- add language support for Lean by @foxyseta in https://github.com/o2sh/onefetch/pull/1509\n- add language support for Typst by @foxyseta in https://github.com/o2sh/onefetch/pull/1508\n- add language support for Razor by @SrS2225a in https://github.com/o2sh/onefetch/pull/1521\n\n### Chores\n\n- more idiomatic way to fetch HEAD refs by @o2sh in https://github.com/o2sh/onefetch/pull/1515\n- more idiomatic way to fetch repository remote URL by @o2sh in https://github.com/o2sh/onefetch/pull/1516\n- update holyc language logo by @o2sh in https://github.com/o2sh/onefetch/pull/1543\n- update wiki powershell-snippet by @FallenDeity in https://github.com/o2sh/onefetch/pull/1542\n- add nix local setup @Sk7Str1p3 in https://github.com/o2sh/onefetch/pull/1549\n\n## 2.23.1 (2025-01-01)\n\n### Bug Fixes\n\n- Fix version in man page\n\n## 2.23.0 (2025-01-01)\n\n### New Features\n\n- add language support for OpenSCAD by @kenchou in https://github.com/o2sh/onefetch/pull/1502\n- add language support for Modelica by @dietmarw in https://github.com/o2sh/onefetch/pull/1262\n- add language support for ATS by @pykenny in https://github.com/o2sh/onefetch/pull/523\n- add language support for CUDA by @jtmr05 in https://github.com/o2sh/onefetch/pull/940\n- add missing nerd fonts icons for some languages by @ankddev in https://github.com/o2sh/onefetch/pull/1491\n\n### Chores\n\n- add Italian translation of README by @tlazzarin in https://github.com/o2sh/onefetch/pull/1435\n- add Polish translation of README by @adamperkowski in https://github.com/o2sh/onefetch/pull/1444\n- add Czech translation of READEME by @Amereyeu in https://github.com/o2sh/onefetch/pull/1439\n- update russian README by @ankddev in https://github.com/o2sh/onefetch/pull/1478\n- [onefetch.dev] migrate to Svelte v5 by @o2sh in https://github.com/o2sh/onefetch/pull/1455\n- add script to preview/validate Nerd Fonts by @spenserblack in https://github.com/o2sh/onefetch/pull/1492\n- add Powershell snippet to run onefetch automatically by @kiapanahi in https://github.com/o2sh/onefetch/pull/1453\n\n## 2.22.0 (2024-09-20)\n\n### New Features\n\n- Add support for nerd font glyphs in languages info by @Localghost385 in https://github.com/o2sh/onefetch/pull/1395\n- [onefetch.dev] Add nerdfont iconts to the preview by @Localghost385 in https://github.com/o2sh/onefetch/pull/1411\n- Automate publishing crates to crates.io by @musicinmybrain in https://github.com/o2sh/onefetch/pull/1364\n\n### Bug Fixes\n\n- Show future commit dates without panicking by @MalteT in https://github.com/o2sh/onefetch/pull/1389\n\n### Chores\n\n- Re-generate the man page with --no-info by @musicinmybrain in https://github.com/o2sh/onefetch/pull/1376\n- Drop unused shebangs from repo test fixture scripts by @musicinmybrain in https://github.com/o2sh/onefetch/pull/1375\n\n## 2.21.0 (2024-05-08)\n\n### New Features\n\n- Add CLI option to force URL format to HTTP instead of SSH by @0spotter0 in https://github.com/o2sh/onefetch/pull/1314\n- Add CLI flag to hide token from repository URL by @o2sh in https://github.com/o2sh/onefetch/pull/1319\n- Make Lua logo more readable on dark terminal by @o2sh in https://github.com/o2sh/onefetch/pull/1337\n\n### Bug Fixes\n\n- Fix deadlock in Churn computation by @Nettifani in https://github.com/o2sh/onefetch/pull/1316\n- Exclude bot commits from churn when `--no-bots` option is used by @o2sh in https://github.com/o2sh/onefetch/pull/1335\n\n### Chores\n\n- [onefetch.dev] switch to dark theme by @o2sh in https://github.com/o2sh/onefetch/pull/1297\n- RUSTSEC-2024-0320: remove yaml-rust dependency by @Suyun114 in https://github.com/o2sh/onefetch/pull/1309\n- Refactor `--no-bots` CLI option by @o2sh in https://github.com/o2sh/onefetch/pull/1340\n\n## 2.20.0 (2024-03-17)\n\nThis version marks the completion of the transition from [`git2`](https://crates.io/crates/git2) to [`gitoxide`](https://crates.io/crates/gix). No more dependency to git2, onefetch is now fully oxidized!\n\n### New Features\n\n- Add svg language support by @Localghost385 in https://github.com/o2sh/onefetch/pull/1266\n- lang: Adding Oz programming language by @luxluth in https://github.com/o2sh/onefetch/pull/1280\n\n### Chores\n\n- website: Filter entries by language type in onefetch.dev by @o2sh in https://github.com/o2sh/onefetch/pull/1227\n- Use GitHub's alert syntax by @spenserblack in https://github.com/o2sh/onefetch/pull/1234\n- Add german translation of `README.md` by @rdwz in https://github.com/o2sh/onefetch/pull/1236\n- Use `gitoxide` to get pending changes by @Byron in https://github.com/o2sh/onefetch/pull/1285\n\n## 2.19.0 (2023-11-29)\n\n### New Features\n\n- exclude files from churn by @o2sh in https://github.com/o2sh/onefetch/pull/1120\n- add odin support by @spsandwichman in https://github.com/o2sh/onefetch/pull/1064\n- New language: Arduino by @Sh4rk-Byte in https://github.com/o2sh/onefetch/pull/1176\n- Right align authorship percentages by @lukehsiao in https://github.com/o2sh/onefetch/pull/1207\n- Add Agda to languages.yaml by @Zoltan-Balazs in https://github.com/o2sh/onefetch/pull/1216\n\n### Bug Fixes\n\n- add a test for negative dates and see how onefetch handles it by @Byron in https://github.com/o2sh/onefetch/pull/1100\n\n### Chores\n\n- Group clap dependency updates by @spenserblack in https://github.com/o2sh/onefetch/pull/1101\n- Group all NPM dependency updates by @spenserblack in https://github.com/o2sh/onefetch/pull/1110\n- Added Turkish Translations by @4Chaffenel in https://github.com/o2sh/onefetch/pull/1135\n- use workspace inheritance by @o2sh in https://github.com/o2sh/onefetch/pull/1142\n- docs(contributing): Add syntax highlighting to YAML block by @spenserblack in https://github.com/o2sh/onefetch/pull/1172\n- add release.yml file by @o2sh in https://github.com/o2sh/onefetch/pull/1177\n- replace action-rs by @o2sh in https://github.com/o2sh/onefetch/pull/1191\n- Resolve clippy warnings by @spenserblack in https://github.com/o2sh/onefetch/pull/1201\n- Refactor and test info field styling by @spenserblack in https://github.com/o2sh/onefetch/pull/1214\n- Refactoring git metrics module by @o2sh in https://github.com/o2sh/onefetch/pull/1217\n\n### Dependencies\n\n- upgrade to `gix` 0.53.1 by @Byron in https://github.com/o2sh/onefetch/pull/1166\n\n## 2.18.1 (2023-06-25)\n\n### Bug Fixes\n\n- don't fail when computing diff on partial clones (#1093) @Byron @o2sh\n\n### Features\n\n- fetch banner info from github (#1094) @spenserblack @o2sh\n\n## 2.18.0 (2023-06-20)\n\n### Features\n\n- add new info line called \"Churn\" which displays the files with the most modifications (commits) (#1071) @o2sh @Byron\n- add Hlsl support (#1082) @progDes007\n\n### Other\n\n- add info builder pattern\n\n### Chore\n\n- performance: optimize case where repo has a commit-graph for massive performance gains (#1081) @Byron\n- docs: add a cmd.exe version of the cd snippet (#1048) @mataha\n- refacto: use the builder pattern to instantiate the `Info` struct (#1047) @o2sh @spenserblack\n- improve bot regex (#1086) @o2sh @spenserblack\n\n## 2.17.1 (2023-04-28)\n\n### Other\n\n- Improve code coverage of src/info/mod.rs (#1011) @changhc\n- Improve code coverage of src/ui/mod.rs (#1012) @changhc\n- Added fish git repository greeter script to wiki (#1021) @TheSast\n- upgrade gitoxide to v0.44 (and incorporate #1023):x (#1024) @Byron @spenserblack\n\n## 2.17.0 (2023-04-09)\n\n### Other\n\n- Disable line wrap (#983) @o2sh\n- Add Pascal support (#989) @rchastain\n- Add Coldfusion support (#971) @theemanofsteele\n- Remove github token from url field (#996) @jim4067\n- Changed Hashbang (#979) @gautamprikshit1\n- Prevent conflicts in wiki action 39fe441 @spenserblack\n- Fix typos (#992) @hezhizhen\n- Group CLI options in sections (#995) @o2sh\n- replace --show-logo with --no-art (#1002) @o2sh\n- Set snapshot language to plain text (#1003) @spenserblack\n- Better error message when human_time panics (#1010) @o2sh\n\n### New Features\n\n- remove github token from url field\n\n## 2.16.0 (2023-02-24)\n\n### Other\n\n- Add GLSL language support #490 (#824) @sangsatori\n- Fix Markdown / Jupyter markup not getting counted (#937) @spenserblack\n- upgrade gix to 0.36.1 to avoid breakage. (#965) @Byron\n- Fix path to language template (#939) @spenserblack\n- Create the Arabic README file (Arabic translation) (#950) @anas-elgarhy\n- Refactoring of info/langs/mod.rs (#948) @o2sh\n- Remove country flags #928 @o2sh\n- Upgrade git-repository 0.30 to gix 0.36 (#963) @Byron\n\n## 2.15.1 (2023-01-19)\n\n### Other\n\n- Fix CD Github action @o2sh\n\n## 2.15.0 (2023-01-19)\n\n### Other\n\n- Add --number-separator CLI flag #892 @o2sh\n- Add Makefile language support #867 @ozwaldorf\n- Vercel: add section links #922 @ozwaldorf\n- Add gitpod.io configuration #881 @spenserblack\n- Use human_panic #887 @o2sh\n- Read license from manifest first #769 @o2sh\n- Install cargo-insta in dev containers #909 @spenserblack\n- Info struct to holds a Vec #911 @o2sh\n- Add benchmark #912 @o2sh\n- GH action to synchronize wiki with .github/wiki #926 @spenserblack @o2sh\n- Clean up greeter and fix repository detection mechanism in wiki #927 @quazar-omega\n- Turn AsciiArt.rs into its own crate #934 @o2sh\n- Use ISO time for snapshot tests #908 @spenserblack\n- Parse multi-byte unicode chars correctly + docs #936 @ozwaldorf\n\n## 2.14.2 (2022-11-27)\n\n### Other\n\n- Include assets in crate a2f508a @o2sh\n- Fix clap deps for onefetch-image crate 8cca7af\n- Add description field to onefetch-image and onefetch-manifest crate 2888186 @o2sh\n\n## 2.14.1 (2022-11-27)\n\n### Other\n\n- Fix CD 5085c5b @o2sh\n\n## 2.14.0 (2022-11-27)\n\n### Other\n\n- Add description info line #851 @o2sh\n- Add CLI flag to set the maximum number of languages to be shown 8159b34 @o2sh\n- Add VisualBasic language support #867 @antonizyla\n- Add manifest crate #851 @o2sh @spenserblack\n- Move image_backends into its own crate 9ce17c1 @o2sh\n- Add devcontainer/codespace config #857 @spenserblack\n- Switch to Swatinem/rust-cache for caching 7592eb2 @o2sh\n- Add README translation for Korean #869 @abiriadev\n- add icon to windows exe 584574f @o2sh\n- Fix typo in help message for -e (--exclude) #861 @skogseth\n\n## 2.13.2 (2022-10-30)\n\n### Other\n\n- [fix] Repo without remote should not fail #841 @o2sh\n- [chore] Add integration tests with snapshot testing for Info struct #827 @atluft\n- [chore] Refactor test expressions #831 @saguywalker\n\n## 2.13.1 (2022-10-22)\n\n### Other\n\n- [ci/cd] fix Snapcraft release\n- [misc] fix Cargo.lock\n\n## 2.13.0 (2022-10-21)\n\n`onefetch` is now typically more than twice as fast when executing. This was achieved by reducing\nallocations and switching to `gitoxide` for the most taxing tasks.\n\nA new web interface [onefetch.dev](https://onefetch.dev) was developed where users can visualize an ASCII preview for all the programming languages currently supported by onefetch. Like the binary, the data is parsed from the `Languages.yaml` file.\n\n### Other\n\n- [chore] reducing allocations and switching to gitoxide from libgit2 #635 @Byron\n- [docs] add README translation for Spanish #631 @JakeRoggenbuck @practicatto\n- [docs] add Changelog generated using cargo-smart-release #637 @Byron\n- [cli] add --completion option #657 @spenserblack\n- [language] update PHP colors #664 @DenverCoder1\n- [misc] switch to actions/stale #666 @spenserblack @o2sh\n- [misc] add github issue forms #667 @spenserblack @o2sh\n- [ci/cd] generate Windows installer from CD #668 @o2sh\n- [ci/cd] create WinGet workflow for auto publishing #673 @russellbanks\n- [language] update logo: shell #677 @fux0c1ety\n- [docs] adding french documentation support #693 @Kaderovski\n- [chore] extract language definitions into data file #699 @spenserblack\n- [ci/cd] add codecov + tarpaulin in ci @o2sh\n- [misc] create Vercel app for onefetch with ASCII preview #701 @spenserblack\n- [docs] update the README in Russian #736 @AndreyKozhev\n- [chore] turn InfoField into a trait (big refactoring) #755 @o2sh\n- [language] Improve JSX ASCII logo #784 @alessandroasm\n- [language] Improve TSX ASCII logo #785 @alessandroasm\n- [language] added support for verilog #789 @atluft\n- [language] improve ruby logo #786 @atluft\n- [language] added support for xsl #798 @atluft\n- [language] added support for systemverilog #797 @atluft\n- [test] add unit tests to src/info/info_field.rs #810 @alessandroasm\n- [ci/cd] automate publish to crates.io #800 @spenserblack\n- [language] added support for ABNF #811 @atluft\n- [test] add unit tests src/info/repo/commits.rs #813 @alessandroasm\n- [test] add unit tests src/info/repo/contributors.rs #814 @alessandroasm\n- [language] added support for ABAP #821@atluft\n- [test] testing get_git_username using git-testtools for #812 @atluft\n- [language] improve bash logo @o2sh\n- [language] improve assembly logo @o2sh\n- [test] add unit tests for author.rs #829 @gallottino @Oniryu95\n\n### Fixes\n\n- Commits replaced with `git replace` are now followed. This can be turned off by setting the\n  `GIT_NO_REPLACE_OBJECTS` environment variable.\n- Shallow clones are now detected and displayed as such. Previously it might have appeared that\n  the commit count is the real even though it was truncated due to the shallow-ness of the\n  repository.\n  If a repository is shallow, `(shallow)` will appear after the commit count.\n\n### Refactor\n\n- git2 repository can now be owned by the `Repo` type\n  Previously this wasn't possible as commits would be kept in `Repo`\n  which would cause self-referential borrow check issues unless\n  the git2 repository was kept outside.\n- completely separate `Commits` and `Repo` structure\n- put all commit-traversal related initialization into own struct\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "### Getting started\n\nFollow the instructions for [installing onefetch from source](https://github.com/o2sh/onefetch/wiki/Installation#build-from-source).\n\n### Adding support for a new language\n\nAdding support for a new Language consists in adding a new entry to [language.yaml](./languages.yaml) and filling it in with the right data.\n\n**Example**:\n\n```yaml\nCSharp: # required, this will be the name of the enum variant for the language as specified by tokei (link 1)\n  type: programming # required, can be programming, data, markup, or prose as specified by linguist (link 2)\n  # required, this is the logo. If it's not within 25x40 bounds, you will get a compiler error. Use `{i}` to color the ascii with `i` the color index.\n  ascii: |\n    {0}                 ++++++\n    {0}              ++++++++++++\n    {0}          ++++++++++++++++++++\n    {0}       ++++++++++++++++++++++++++\n    {0}    ++++++++++++++++++++++++++++++++\n    {0} +++++++++++++{3}************{0}+++++++++++++\n    {0}+++++++++++{3}******************{0}++++++++{2};;;\n    {0}+++++++++{3}**********************{0}++{2};;;;;;;\n    {0}++++++++{3}*********{0}++++++{3}******{2};;;;;;;;;;;\n    {0}+++++++{3}********{0}++++++++++{3}**{2};;;{3}**{2};;;{3}**{2};;;\n    {0}+++++++{3}*******{0}+++++++++{2};;;;;;{3}*********{2}::\n    {0}+++++++{3}******{0}+++++++{2};;;;;;;;;;{3}**{2};;;{3}**{2};;;\n    {0}+++++++{3}*******{0}+++{1}:::::{2};;;;;;;{3}*********{2};;\n    {0}+++++++{3}********{1}::::::::::{3}**{2};;;{3}**{2};;;{3}**{2};;;\n    {0}++++++++{3}*********{1}::::::{3}******{2};;;;;;;;;;;\n    {0}++++++{1}:::{3}**********************{1}::{2};;;;;;;\n    {0}+++{1}::::::::{3}******************{1}::::::::{2};;;\n    {1} :::::::::::::{3}************{1}:::::::::::::\n    {1}    ::::::::::::::::::::::::::::::::\n    {1}       ::::::::::::::::::::::::::\n    {1}          ::::::::::::::::::::\n    {1}              ::::::::::::\n    {1}                 ::::::\n  colors:\n    ansi: # required, a list of the ANSI colors used to colorize the logo\n      - blue\n      - magenta\n      - magenta\n      - white\n    hex: # optional, alternative to basic colors for terminals that support true colour.\n      - \"#9B4F97\"\n      - \"#67217A\"\n      - \"#803788\"\n      - \"#FFFFFF\"\n    chip: \"#178600\" # required, this is used for the language breakdown bar, its value can be found in linguist (link 2).\n  icon: '\\u{E648}' # optional, the UTF-16 code point of the nerd font icon if supported (link 3).\n  serialization: c# # required only if the Enum name `CSharp` doesn't match the display name `C#`\n```\n\n- link 1: https://github.com/XAMPPRocky/tokei#supported-languages\n- link 2: https://github.com/github/linguist/blob/master/lib/linguist/languages.yml\n- link 3: https://www.nerdfonts.com/cheat-sheet\n\n<h2 align=\"center\">Special Thanks to</h2>\n\n- Every onefetch user, who contributed to the project by writing issues or PRs.\n- [@spenserblack](https://github.com/spenserblack) and [@Byron](https://github.com/Byron) for maintaining this project.\n- Everyone I forgot to mention here, but also influenced onefetch and helped it grow.\n\n<p align=\"center\">:heart::heart:</p>\n\n<p align=\"center\">\n  <img src=\"https://contrib.rocks/image?repo=o2sh/onefetch\" />\n</p>\n\nMade with [contributors-img](https://contrib.rocks).\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[workspace.package]\nauthors = [\"o2sh <ossama-hjaji@live.fr>\"]\nedition = \"2024\"\nlicense = \"MIT\"\nversion = \"2.27.1\"\nrepository = \"https://github.com/o2sh/onefetch\"\n\n[workspace]\nmembers = [\"ascii\", \"image\", \"manifest\"]\n\n[package]\nauthors.workspace = true\nedition.workspace = true\nversion.workspace = true\nlicense.workspace = true\nrepository.workspace = true\ncategories = [\"command-line-utilities\"]\ndescription = \"Command-line Git information tool\"\nexclude = [\"docs/web/*\"]\nkeywords = [\"git\", \"cli\", \"terminal\"]\nname = \"onefetch\"\nhomepage = \"https://onefetch.dev\"\nrust-version = \"1.88.0\"\n\n[dependencies]\nanyhow = \"1.0.101\"\naskalono = \"0.5.0\"\nbyte-unit = \"5.2.0\"\nclap = { version = \"4.5.57\", features = [\"derive\"] }\nclap_complete = \"4.5.65\"\ngix = { version = \"0.80.0\", default-features = false, features = [\n    \"max-performance-safe\",\n    \"blob-diff\",\n    \"mailmap\",\n    \"index\",\n    \"status\",\n] }\ngix-features = \"0.46.0\"\nglobset = \"0.4.18\"\nhuman-panic = \"2.0.6\"\nimage = { version = \"0.25.10\", default-features = false, features = [\n    \"color_quant\",\n    \"jpeg\",\n    \"png\",\n    \"webp\",\n] }\nnum-format = \"0.4.4\"\nonefetch-ascii = { path = \"ascii\", version = \"2.27.1\" }\nonefetch-image = { path = \"image\", version = \"2.27.1\" }\nonefetch-manifest = { path = \"manifest\", version = \"2.27.1\" }\nowo-colors = \"4.3.0\"\nregex = \"1.12.2\"\nserde = \"1.0.228\"\nserde_json = \"1.0.149\"\nserde_yaml = \"0.9.34\"\nstrum = { version = \"0.28.0\", features = [\"derive\"] }\ntime = { version = \"0.3.47\", features = [\"formatting\"] }\ntime-humanize = { version = \"0.1.3\", features = [\"time\"] }\ntokei = \"14.0.0\"\ntypetag = \"0.2.21\"\n\n[dev-dependencies]\ncriterion = \"0.8.2\"\ngix-testtools = \"0.18.0\"\ninsta = { version = \"1.46.3\", features = [\"json\", \"redactions\"] }\nrstest = \"0.26.1\"\n\n[[bench]]\nname = \"repo\"\nharness = false\n\n[build-dependencies]\nlazy_static = \"1.5.0\"\nregex = \"1.12.2\"\nserde_json = \"1.0.149\"\nserde_yaml = \"0.9.34\"\ntera = { version = \"1.20.1\", default-features = false }\n\n[target.'cfg(windows)'.build-dependencies]\nwinres = \"0.1.12\"\n\n[target.'cfg(windows)'.dependencies]\nenable-ansi-support = \"0.3.1\"\n\n[features]\nfail-on-deprecated = []\n"
  },
  {
    "path": "LICENSE.md",
    "content": "MIT License\n\nCopyright (c) 2018 Ossama Hjaji\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "install:\n\tcargo install --path \".\" --features=fail-on-deprecated\n\nbuild:\n\tcargo build --release --features=fail-on-deprecated\n\nuninstall:\n\tcargo uninstall onefetch\n\nclean:\n\tcargo clean\n\nrelease-mac:\n\tstrip target/release/onefetch\n\tmkdir -p release\n\ttar -C ./target/release/ -czvf ./release/onefetch-mac.tar.gz ./onefetch\n\nrelease-win: TAG_NAME = $$(git describe --abbrev=0 --tags)\nrelease-win:\n\tmkdir -p release\n\ttar -C ./target/release/ -czvf ./release/onefetch-win.tar.gz ./onefetch.exe\n\tiscc.exe -DMyAppVersion=${TAG_NAME} ./.github/workflows/windows-installer.iss\n\nrelease-linux:\n\tstrip target/release/onefetch\n\tmkdir -p release\n\ttar -C ./target/release/ -czvf ./release/onefetch-linux.tar.gz ./onefetch\n"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\">\n\n# Onefetch - Command-line Git information tool\n\n<p><img src=\"assets/onefetch.svg\" height=\"100\" alt=\"Onefetch logo\"></p>\n\n[![Crates.io Version](https://img.shields.io/crates/v/onefetch)](https://crates.io/crates/onefetch)\n[![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/o2sh/onefetch/ci.yml)](https://github.com/o2sh/onefetch/actions/workflows/ci.yml)\n[![help wanted](https://img.shields.io/github/issues/o2sh/onefetch/help%20wanted?color=green)](https://github.com/o2sh/onefetch/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22)\n![MSRV](assets/msrv-badge.svg)\n\n<h3>\n<a href=\"https://onefetch.dev/\">Homepage</a> |\n<a href=\"https://github.com/o2sh/onefetch/wiki/Installation\">Installation</a> |\n<a href=\"https://github.com/o2sh/onefetch/wiki/\">Documentation</a>\n</h3>\n\n</div>\n\n---\n\nOnefetch is a command-line Git information tool that displays project information and code statistics for a local Git repository directly in your terminal. The tool works completely offline with a focus on performance and customizability.\n\n|||\n|---|---|\n| ![Screenshot 1](assets/screenshot-1.png) | ![Screenshot 2](assets/screenshot-2.png) |\n\n## Installation\n\nOnefetch is available on Linux, macOS, and Windows platforms. Binaries for Linux, Windows, and macOS are available on the [release page](https://github.com/o2sh/onefetch/releases).\n\n### Linux\n\n- Ubuntu\n  \n  ```\n  wget https://github.com/o2sh/onefetch/releases/latest/download/onefetch_amd64.deb && sudo dpkg -i ./onefetch_amd64.deb && rm onefetch_amd64.deb\n  ```\n\n- Arch Linux\n  \n  ```\n  pacman -S onefetch\n  ```\n\n- openSUSE\n  \n  ```\n  zypper install onefetch\n  ```\n\n### macOS\n  \n```\nbrew install onefetch\n```\n\n### Windows\n  \n```\nwinget install onefetch\n```\n\n## Usage\n\n```\nonefetch /path/of/your/repo\n```\n\nOr\n\n```\ncd /path/of/your/repo\nonefetch\n```\n\n## Customization\n\nOnefetch can be customized via [command-line arguments](https://github.com/o2sh/onefetch/wiki/command-line-options) to display exactly what you want, the way you want it: adjust the text styling, disable info lines, ignore files and directories, output in multiple formats (JSON, YAML), etc.\n\n## Contributing\n\nCurrently, onefetch supports more than [100 different programming languages](https://onefetch.dev); if your language of choice isn't supported, open an issue and support will be added.\n\nContributions are very welcome! See [CONTRIBUTING](CONTRIBUTING.md) for more info.\n"
  },
  {
    "path": "ascii/Cargo.toml",
    "content": "[package]\nauthors.workspace = true\nedition.workspace = true\nversion.workspace = true\nlicense.workspace = true\nrepository.workspace = true\nname = \"onefetch-ascii\"\ndescription = \"Display colorized ascii art to the terminal\"\n\n[dependencies]\nowo-colors = \"4.3.0\"\n"
  },
  {
    "path": "ascii/README.md",
    "content": "# ascii\n\n[![crates.io](https://img.shields.io/crates/v/onefetch-ascii)](https://crates.io/crates/onefetch-ascii)\n[![docs.rs](https://img.shields.io/docsrs/onefetch-ascii)](https://docs.rs/onefetch-ascii)\n\nProvides the primary interface to display ascii art to the terminal.\n\nMore info [here](https://github.com/o2sh/onefetch/wiki/ascii-art).\n\n_This crate is designed as part of the [onefetch](https://github.com/o2sh/onefetch) project._\n"
  },
  {
    "path": "ascii/src/lib.rs",
    "content": "//! # onefetch-ascii\n//!\n//! Provides the ascii template interface for [onefetch](https://github.com/o2sh/onefetch).\n//!\n//! ```rust,no_run\n//! use onefetch_ascii::AsciiArt;\n//! use owo_colors::{DynColors, AnsiColors};\n//!\n//! const ASCII: &str = r#\"\n//! {2}            .:--::////::--.`\n//! {1}        `/yNMMNho{2}////////////:.\n//! {1}      `+NMMMMMMMMmy{2}/////////////:`\n//! {0}    `-:::{1}ohNMMMMMMMNy{2}/////////////:`\n//! {0}   .::::::::{1}odMMMMMMMNy{2}/////////////-\n//! {0}  -:::::::::::{1}/hMMMMMMMmo{2}////////////-\n//! {0} .::::::::::::::{1}oMMMMMMMMh{2}////////////-\n//! {0}`:::::::::::::{1}/dMMMMMMMMMMNo{2}///////////`\n//! {0}-::::::::::::{1}sMMMMMMmMMMMMMMy{2}//////////-\n//! {0}-::::::::::{1}/dMMMMMMs{0}:{1}+NMMMMMMd{2}/////////:\n//! {0}-:::::::::{1}+NMMMMMm/{0}:::{1}/dMMMMMMm+{2}///////:\n//! {0}-::::::::{1}sMMMMMMh{0}:::::::{1}dMMMMMMm+{2}//////-\n//! {0}`:::::::{1}sMMMMMMy{0}:::::::::{1}dMMMMMMm+{2}/////`\n//! {0} .:::::{1}sMMMMMMs{0}:::::::::::{1}mMMMMMMd{2}////-\n//! {0}  -:::{1}sMMMMMMy{0}::::::::::::{1}/NMMMMMMh{2}//-\n//! {0}   .:{1}+MMMMMMd{0}::::::::::::::{1}oMMMMMMMo{2}-\n//! {1}    `yMMMMMN/{0}:::::::::::::::{1}hMMMMMh.\n//! {1}      -yMMMo{0}::::::::::::::::{1}/MMMy-\n//! {1}        `/s{0}::::::::::::::::::{1}o/`\n//! {0}            ``.---::::---..`\n//! \"#;\n//!\n//! let colors = vec![\n//!     DynColors::Ansi(AnsiColors::Blue),\n//!     DynColors::Ansi(AnsiColors::Default),\n//!     DynColors::Ansi(AnsiColors::BrightBlue)\n//! ];\n//!\n//! let art = AsciiArt::new(ASCII, colors.as_slice(), true);\n//!\n//! for line in art {\n//!     println!(\"{line}\")\n//! }\n//! ```\n//!\n\nuse owo_colors::{AnsiColors, DynColors, OwoColorize, Style};\nuse std::fmt::Write;\n\n/// Renders an ascii template with the given colors truncated to the correct width.\npub struct AsciiArt<'a> {\n    content: Box<dyn 'a + Iterator<Item = &'a str>>,\n    colors: &'a [DynColors],\n    bold: bool,\n    start: usize,\n    end: usize,\n}\nimpl<'a> AsciiArt<'a> {\n    pub fn new(input: &'a str, colors: &'a [DynColors], bold: bool) -> AsciiArt<'a> {\n        let mut lines: Vec<_> = input.lines().skip_while(|line| line.is_empty()).collect();\n        while let Some(line) = lines.last() {\n            if Tokens(line).is_empty() {\n                lines.pop();\n            } else {\n                break;\n            }\n        }\n\n        let (start, end) = get_min_start_max_end(&lines);\n\n        AsciiArt {\n            content: Box::new(lines.into_iter()),\n            colors,\n            bold,\n            start,\n            end,\n        }\n    }\n\n    pub fn width(&self) -> usize {\n        assert!(self.end >= self.start);\n        self.end - self.start\n    }\n}\n\nfn get_min_start_max_end(lines: &[&str]) -> (usize, usize) {\n    lines\n        .iter()\n        .map(|line| {\n            let line_start = Tokens(line).leading_spaces();\n            let line_end = Tokens(line).true_length();\n            (line_start, line_end)\n        })\n        .fold((usize::MAX, 0), |(acc_s, acc_e), (line_s, line_e)| {\n            (acc_s.min(line_s), acc_e.max(line_e))\n        })\n}\n\n/// Produces a series of lines which have been automatically truncated to the\n/// correct width\nimpl Iterator for AsciiArt<'_> {\n    type Item = String;\n    fn next(&mut self) -> Option<String> {\n        self.content\n            .next()\n            .map(|line| Tokens(line).render(self.colors, self.start, self.end, self.bold))\n    }\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\nenum Token {\n    Color(u32),\n    Char(char),\n    Space,\n}\nimpl std::fmt::Display for Token {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match *self {\n            Token::Color(c) => write!(f, \"{{{c}}}\"),\n            Token::Char(c) => write!(f, \"{c}\"),\n            Token::Space => write!(f, \" \"),\n        }\n    }\n}\nimpl Token {\n    fn is_solid(&self) -> bool {\n        matches!(*self, Token::Char(_))\n    }\n    fn is_space(&self) -> bool {\n        matches!(*self, Token::Space)\n    }\n    fn has_zero_width(&self) -> bool {\n        matches!(*self, Token::Color(_))\n    }\n}\n\n/// An iterator over tokens found within the *.ascii format.\n#[derive(Clone, Debug)]\nstruct Tokens<'a>(&'a str);\nimpl Iterator for Tokens<'_> {\n    type Item = Token;\n    fn next(&mut self) -> Option<Token> {\n        let (s, tok) = color_token(self.0)\n            .or_else(|| space_token(self.0))\n            .or_else(|| char_token(self.0))?;\n\n        self.0 = s;\n        Some(tok)\n    }\n}\n\nimpl<'a> Tokens<'a> {\n    fn is_empty(&mut self) -> bool {\n        for token in self {\n            if token.is_solid() {\n                return false;\n            }\n        }\n        true\n    }\n    fn true_length(&mut self) -> usize {\n        let mut last_non_space = 0;\n        let mut last = 0;\n        for token in self {\n            if token.has_zero_width() {\n                continue;\n            }\n            last += 1;\n            if !token.is_space() {\n                last_non_space = last;\n            }\n        }\n        last_non_space\n    }\n    fn leading_spaces(&mut self) -> usize {\n        self.take_while(|token| !token.is_solid())\n            .filter(Token::is_space)\n            .count()\n    }\n    fn truncate(self, mut start: usize, end: usize) -> impl 'a + Iterator<Item = Token> {\n        assert!(start <= end);\n        let mut width = end - start;\n\n        self.filter(move |token| {\n            if start > 0 && !token.has_zero_width() {\n                start -= 1;\n                return false;\n            }\n            true\n        })\n        .take_while(move |token| {\n            if width == 0 {\n                return false;\n            }\n            if !token.has_zero_width() {\n                width -= 1;\n            }\n            true\n        })\n    }\n    /// render a truncated line of tokens.\n    fn render(self, colors: &[DynColors], start: usize, end: usize, bold: bool) -> String {\n        assert!(start <= end);\n        let mut width = end - start;\n        let mut colored_segment = String::new();\n        let mut whole_string = String::new();\n        let mut color = &DynColors::Ansi(AnsiColors::Default);\n\n        self.truncate(start, end).for_each(|token| match token {\n            Token::Char(chr) => {\n                width = width.saturating_sub(1);\n                colored_segment.push(chr);\n            }\n            Token::Color(col) => {\n                add_styled_segment(&mut whole_string, &colored_segment, *color, bold);\n                colored_segment = String::new();\n                color = colors\n                    .get(col as usize)\n                    .unwrap_or(&DynColors::Ansi(AnsiColors::Default));\n            }\n            Token::Space => {\n                width = width.saturating_sub(1);\n                colored_segment.push(' ');\n            }\n        });\n\n        add_styled_segment(&mut whole_string, &colored_segment, *color, bold);\n        (0..width).for_each(|_| whole_string.push(' '));\n        whole_string\n    }\n}\n\n// Utility functions\n\nfn succeed_when<I>(predicate: impl FnOnce(I) -> bool) -> impl FnOnce(I) -> Option<()> {\n    |input| {\n        if predicate(input) { Some(()) } else { None }\n    }\n}\n\nfn add_styled_segment(base: &mut String, segment: &str, color: DynColors, bold: bool) {\n    let mut style = Style::new().color(color);\n    if bold {\n        style = style.bold();\n    }\n    let formatted_segment = segment.style(style);\n    let _ = write!(base, \"{formatted_segment}\");\n}\n\n// Basic combinators\n\ntype ParseResult<'a, R> = Option<(&'a str, R)>;\n\nfn token<'a, R>(s: &'a str, predicate: impl FnOnce(char) -> Option<R>) -> ParseResult<'a, R> {\n    let mut chars = s.chars();\n    let token = chars.next()?;\n    let result = predicate(token)?;\n    Some((chars.as_str(), result))\n}\n\n// Parsers\n\n/// Parses a color indicator of the format `{n}` where `n` is a digit.\nfn color_token<'a>(s: &'a str) -> ParseResult<'a, Token> {\n    let (s, ()) = token(s, succeed_when(|c| c == '{'))?;\n    let (s, color_index) = token(s, |c| c.to_digit(10))?;\n    let (s, ()) = token(s, succeed_when(|c| c == '}'))?;\n    Some((s, Token::Color(color_index)))\n}\n\n/// Parses a space.\nfn space_token<'a>(s: &'a str) -> ParseResult<'a, Token> {\n    token(s, succeed_when(|c| c == ' ')).map(|(s, ())| (s, Token::Space))\n}\n\n/// Parses any arbitrary character. This cannot fail.\nfn char_token<'a>(s: &'a str) -> ParseResult<'a, Token> {\n    token(s, |c| Some(Token::Char(c)))\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    #[test]\n    fn test_get_min_start_max_end() {\n        let lines = [\n            \"                     xxx\",\n            \"   xxx\",\n            \"         oo\",\n            \"     o\",\n            \"                           xx\",\n        ];\n        assert_eq!(get_min_start_max_end(&lines), (3, 29));\n    }\n\n    #[test]\n    fn space_parses() {\n        assert_eq!(space_token(\" \"), Some((\"\", Token::Space)));\n        assert_eq!(space_token(\" hello\"), Some((\"hello\", Token::Space)));\n        assert_eq!(space_token(\"      \"), Some((\"     \", Token::Space)));\n        assert_eq!(space_token(\" {1}{2}\"), Some((\"{1}{2}\", Token::Space)));\n    }\n\n    #[test]\n    fn color_indicator_parses() {\n        assert_eq!(color_token(\"{1}\"), Some((\"\", Token::Color(1))));\n        assert_eq!(color_token(\"{9} \"), Some((\" \", Token::Color(9))));\n    }\n\n    #[test]\n    fn leading_spaces_counts_correctly() {\n        assert_eq!(Tokens(\"\").leading_spaces(), 0);\n        assert_eq!(Tokens(\"     \").leading_spaces(), 5);\n        assert_eq!(Tokens(\"     a;lksjf;a\").leading_spaces(), 5);\n        assert_eq!(Tokens(\"  {1} {5}  {9} a\").leading_spaces(), 6);\n    }\n\n    #[test]\n    fn render() {\n        let colors_shim = Vec::new();\n\n        assert_eq!(\n            Tokens(\"\").render(&colors_shim, 0, 0, true),\n            \"\\u{1b}[39;1m\\u{1b}[0m\"\n        );\n\n        assert_eq!(\n            Tokens(\"     \").render(&colors_shim, 0, 0, true),\n            \"\\u{1b}[39;1m\\u{1b}[0m\"\n        );\n\n        assert_eq!(\n            Tokens(\"     \").render(&colors_shim, 0, 5, true),\n            \"\\u{1b}[39;1m     \\u{1b}[0m\"\n        );\n\n        assert_eq!(\n            Tokens(\"     \").render(&colors_shim, 1, 5, true),\n            \"\\u{1b}[39;1m    \\u{1b}[0m\"\n        );\n\n        assert_eq!(\n            Tokens(\"     \").render(&colors_shim, 3, 5, true),\n            \"\\u{1b}[39;1m  \\u{1b}[0m\"\n        );\n\n        assert_eq!(\n            Tokens(\"     \").render(&colors_shim, 0, 4, true),\n            \"\\u{1b}[39;1m    \\u{1b}[0m\"\n        );\n\n        assert_eq!(\n            Tokens(\"     \").render(&colors_shim, 0, 3, true),\n            \"\\u{1b}[39;1m   \\u{1b}[0m\"\n        );\n\n        // https://github.com/o2sh/onefetch/issues/935\n        assert_eq!(\n            Tokens(\"███\").render(Vec::new().as_slice(), 0, 3, true),\n            \"\\u{1b}[39;1m███\\u{1b}[0m\"\n        );\n\n        assert_eq!(\n            Tokens(\"  {1} {5}  {9} a\").render(&colors_shim, 4, 10, true),\n            \"\\u{1b}[39;1m\\u{1b}[0m\\u{1b}[39;1m\\u{1b}[0m\\u{1b}[39;1m \\u{1b}[0m\\u{1b}[39;1m a\\u{1b}[0m   \"\n        );\n\n        // Tests for bold disabled\n        assert_eq!(\n            Tokens(\"     \").render(&colors_shim, 0, 0, false),\n            \"\\u{1b}[39m\\u{1b}[0m\"\n        );\n        assert_eq!(\n            Tokens(\"     \").render(&colors_shim, 0, 5, false),\n            \"\\u{1b}[39m     \\u{1b}[0m\"\n        );\n    }\n\n    #[test]\n    fn truncate() {\n        assert_eq!(\n            Tokens(\"\").truncate(0, 0).collect::<Vec<_>>(),\n            Tokens(\"\").collect::<Vec<_>>()\n        );\n\n        assert_eq!(\n            Tokens(\"     \").truncate(0, 0).collect::<Vec<_>>(),\n            Tokens(\"\").collect::<Vec<_>>()\n        );\n        assert_eq!(\n            Tokens(\"     \").truncate(0, 5).collect::<Vec<_>>(),\n            Tokens(\"     \").collect::<Vec<_>>()\n        );\n        assert_eq!(\n            Tokens(\"     \").truncate(1, 5).collect::<Vec<_>>(),\n            Tokens(\"    \").collect::<Vec<_>>()\n        );\n        assert_eq!(\n            Tokens(\"     \").truncate(3, 5).collect::<Vec<_>>(),\n            Tokens(\"  \").collect::<Vec<_>>()\n        );\n        assert_eq!(\n            Tokens(\"     \").truncate(0, 4).collect::<Vec<_>>(),\n            Tokens(\"    \").collect::<Vec<_>>()\n        );\n        assert_eq!(\n            Tokens(\"     \").truncate(0, 3).collect::<Vec<_>>(),\n            Tokens(\"   \").collect::<Vec<_>>()\n        );\n\n        assert_eq!(\n            Tokens(\"  {1} {5}  {9} a\")\n                .truncate(4, 10)\n                .collect::<Vec<_>>(),\n            Tokens(\"{1}{5} {9} a\").collect::<Vec<_>>()\n        );\n    }\n}\n"
  },
  {
    "path": "benches/repo.rs",
    "content": "use criterion::{Criterion, criterion_group, criterion_main};\nuse gix::{ThreadSafeRepository, open};\nuse onefetch::{cli::CliOptions, info::build_info};\nuse std::hint::black_box;\n\nfn bench_repo_info(c: &mut Criterion) {\n    let name = \"make_repo.sh\".to_string();\n    let repo_path = gix_testtools::scripted_fixture_read_only(name).unwrap();\n    let repo = ThreadSafeRepository::open_opts(repo_path, open::Options::isolated()).unwrap();\n    let config: CliOptions = CliOptions {\n        input: repo.path().to_path_buf(),\n        ..Default::default()\n    };\n\n    c.bench_function(\"get repo information\", |b| {\n        b.iter(|| {\n            let result = black_box(build_info(&config));\n            assert!(result.is_ok());\n        });\n    });\n}\n\ncriterion_group!(benches, bench_repo_info);\ncriterion_main!(benches);\n"
  },
  {
    "path": "build.rs",
    "content": "use regex::Regex;\nuse std::collections::HashMap;\nuse std::env;\nuse std::error::Error;\nuse std::fs::{self, File};\nuse std::path::Path;\nuse std::sync::LazyLock;\nuse tera::{Context, Tera};\n\nfn main() -> Result<(), Box<dyn Error>> {\n    #[cfg(windows)]\n    {\n        let mut res = winres::WindowsResource::new();\n        res.set_icon(\"assets/onefetch.ico\");\n        res.compile()?;\n    }\n    let out_dir = env::var(\"OUT_DIR\").expect(\"No OUT_DIR variable.\");\n    let mut tera = Tera::default();\n    tera.register_filter(\"strip_color_tokens\", strip_color_tokens_filter);\n    tera.register_filter(\"hex_to_rgb\", hex_to_rgb_filter);\n\n    let lang_data: serde_json::Value = serde_yaml::from_reader(File::open(\"languages.yaml\")?)?;\n\n    let output_path = Path::new(&out_dir).join(\"language.rs\");\n\n    let rust_code = tera.render_str(\n        &std::fs::read_to_string(\"src/info/langs/language.tera\")?,\n        &Context::from_value(serde_json::json!({ \"languages\": lang_data, }))?,\n    )?;\n    fs::write(output_path, rust_code)?;\n\n    Ok(())\n}\n\n/// Strips out `{n}` from the given string.\nfn strip_color_tokens_filter(\n    value: &tera::Value,\n    _args: &HashMap<String, tera::Value>,\n) -> tera::Result<tera::Value> {\n    static COLOR_INDEX_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r\"\\{\\d+\\}\").unwrap());\n    let tera::Value::String(s) = value else {\n        return Err(tera::Error::msg(\"expected string\"));\n    };\n    Ok(tera::Value::String(\n        COLOR_INDEX_REGEX.replace_all(s, \"\").to_string(),\n    ))\n}\n\nfn hex_to_rgb_filter(\n    value: &tera::Value,\n    _args: &HashMap<String, tera::Value>,\n) -> tera::Result<tera::Value> {\n    let tera::Value::String(hex_string) = value else {\n        return Err(tera::Error::msg(\"expected string\"));\n    };\n    let Some(hex_string) = hex_string.strip_prefix('#') else {\n        return Err(tera::Error::msg(\"expected hex string starting with `#`\"));\n    };\n    if hex_string.len() != 6 {\n        return Err(tera::Error::msg(\"expected a 6 digit hex string\"));\n    }\n    let Ok(channel_bytes) = u32::from_str_radix(hex_string, 16) else {\n        return Err(tera::Error::msg(\"expected a valid hex string\"));\n    };\n    let r = (channel_bytes >> 16) & 0xFF;\n    let g = (channel_bytes >> 8) & 0xFF;\n    let b = channel_bytes & 0xFF;\n\n    Ok(serde_json::json!({\n        \"r\": r,\n        \"g\": g,\n        \"b\": b,\n    }))\n}\n"
  },
  {
    "path": "docs/onefetch.1",
    "content": ".\\\" DO NOT MODIFY THIS FILE!  It was generated by help2man 1.49.3.\n.TH ONEFETCH \"1\" \"March 2026\" \"onefetch 2.27.1\" \"User Commands\"\n.SH NAME\nonefetch \\- Command-line Git information tool\n.SH SYNOPSIS\n.B onefetch\n[\\fI\\,OPTIONS\\/\\fR] [\\fI\\,INPUT\\/\\fR]\n.SH DESCRIPTION\nCommand\\-line Git information tool\n.SS \"Arguments:\"\n.IP\n[INPUT]\n.IP\nRun as if onefetch was started in <input> instead of the current working directory\n.SH OPTIONS\n.HP\n\\fB\\-h\\fR, \\fB\\-\\-help\\fR\n.IP\nPrint help (see a summary with '\\-h')\n.HP\n\\fB\\-V\\fR, \\fB\\-\\-version\\fR\n.IP\nPrint version\n.SS \"INFO:\"\n.HP\n\\fB\\-d\\fR, \\fB\\-\\-disabled\\-fields\\fR <FIELD>...\n.IP\nAllows you to disable FIELD(s) from appearing in the output\n.HP\n\\fB\\-\\-no\\-title\\fR\n.IP\nHides the title\n.HP\n\\fB\\-\\-number\\-of\\-authors\\fR <NUM>\n.IP\nMaximum NUM of authors to be shown\n.IP\n[default: 3]\n.HP\n\\fB\\-\\-number\\-of\\-languages\\fR <NUM>\n.IP\nMaximum NUM of languages to be shown\n.IP\n[default: 6]\n.HP\n\\fB\\-\\-number\\-of\\-file\\-churns\\fR <NUM>\n.IP\nMaximum NUM of file churns to be shown\n.IP\n[default: 3]\n.HP\n\\fB\\-\\-churn\\-pool\\-size\\fR <NUM>\n.IP\nMinimum NUM of commits from HEAD used to compute the churn summary\n.IP\nBy default, the actual value is non\\-deterministic due to time\\-based computation and will be displayed under the info title \"Churn (NUM)\"\n.HP\n\\fB\\-e\\fR, \\fB\\-\\-exclude\\fR <EXCLUDE>...\n.IP\nIgnore all files & directories matching EXCLUDE\n.HP\n\\fB\\-\\-no\\-bots[=\\fR<REGEX>]\n.IP\nExclude [bot] commits. Use <REGEX> to override the default pattern\n.HP\n\\fB\\-\\-no\\-merges\\fR\n.IP\nIgnores merge commits\n.HP\n\\fB\\-E\\fR, \\fB\\-\\-email\\fR\n.IP\nShow the email address of each author\n.HP\n\\fB\\-\\-http\\-url\\fR\n.IP\nDisplay repository URL as HTTP\n.HP\n\\fB\\-\\-hide\\-token\\fR\n.IP\nHide token in repository URL\n.HP\n\\fB\\-\\-include\\-hidden\\fR\n.IP\nCount hidden files and directories\n.HP\n\\fB\\-T\\fR, \\fB\\-\\-type\\fR <TYPE>...\n.IP\nFilters output by language type\n.IP\n[default: programming markup]\n[possible values: programming, markup, prose, data]\n.SS \"TEXT FORMATTING:\"\n.HP\n\\fB\\-t\\fR, \\fB\\-\\-text\\-colors\\fR <X>...\n.IP\nChanges the text colors (X X X...)\n.IP\nGoes in order of title, ~, underline, subtitle, colon, and info\n.IP\nFor example:\n.IP\n\\&'\\-\\-text\\-colors 9 10 11 12 13 14'\n.HP\n\\fB\\-z\\fR, \\fB\\-\\-iso\\-time\\fR\n.IP\nUse ISO 8601 formatted timestamps\n.HP\n\\fB\\-\\-number\\-separator\\fR <SEPARATOR>\n.IP\nWhich thousands SEPARATOR to use\n.IP\n[default: plain]\n[possible values: plain, comma, space, underscore]\n.HP\n\\fB\\-\\-no\\-bold\\fR\n.IP\nTurns off bold formatting\n.SS \"ASCII:\"\n.HP\n\\fB\\-\\-ascii\\-input\\fR <STRING>\n.IP\nTakes a non\\-empty STRING as input to replace the ASCII logo\n.IP\nIt is possible to pass a generated STRING by command substitution\n.IP\nFor example:\n.IP\n\\&'\\-\\-ascii\\-input \"$(fortune | cowsay \\fB\\-W\\fR 25)\"'\n.HP\n\\fB\\-c\\fR, \\fB\\-\\-ascii\\-colors\\fR <X>...\n.IP\nColors (X X X...) to print the ascii art\n.HP\n\\fB\\-a\\fR, \\fB\\-\\-ascii\\-language\\fR <LANGUAGE>\n.IP\nWhich LANGUAGE's ascii art to print\n.HP\n\\fB\\-\\-true\\-color\\fR <WHEN>\n.IP\nSpecify when to use true color\n.IP\nIf set to auto: true color will be enabled if supported by the terminal\n.IP\n[default: auto]\n[possible values: auto, never, always]\n.SS \"IMAGE:\"\n.HP\n\\fB\\-i\\fR, \\fB\\-\\-image\\fR <IMAGE>\n.IP\nPath to the IMAGE file\n.HP\n\\fB\\-\\-image\\-protocol\\fR <PROTOCOL>\n.IP\nWhich image PROTOCOL to use\n.IP\n[possible values: kitty, sixel, iterm]\n.HP\n\\fB\\-\\-color\\-resolution\\fR <VALUE>\n.IP\nVALUE of color resolution to use with SIXEL backend\n.IP\n[default: 64]\n[possible values: 16, 32, 64, 128, 256]\n.SS \"VISUALS:\"\n.HP\n\\fB\\-\\-no\\-color\\-palette\\fR\n.IP\nHides the color palette\n.HP\n\\fB\\-\\-no\\-art\\fR\n.IP\nHides the ascii art or image if provided\n.HP\n\\fB\\-\\-nerd\\-fonts\\fR\n.IP\nUse Nerd Font icons\n.IP\nReplaces language chips with Nerd Font icons\n.SS \"DEVELOPER:\"\n.HP\n\\fB\\-o\\fR, \\fB\\-\\-output\\fR <FORMAT>\n.IP\nOutputs Onefetch in a specific format\n.IP\n[possible values: json, yaml]\n.HP\n\\fB\\-\\-generate\\fR <SHELL>\n.IP\nIf provided, outputs the completion file for given SHELL\n.IP\n[possible values: bash, elvish, fish, powershell, zsh]\n.SS \"OTHER:\"\n.HP\n\\fB\\-l\\fR, \\fB\\-\\-languages\\fR\n.IP\nPrints out supported languages\n.HP\n\\fB\\-p\\fR, \\fB\\-\\-package\\-managers\\fR\n.IP\nPrints out supported package managers\n"
  },
  {
    "path": "docs/web/.gitignore",
    "content": "node_modules\n\n# Output\n.output\n.vercel\n/.svelte-kit\n/build\n\n# OS\n.DS_Store\nThumbs.db\n\n# Env\n.env\n.env.*\n!.env.example\n!.env.test\n\n# Vite\nvite.config.js.timestamp-*\nvite.config.ts.timestamp-*\n"
  },
  {
    "path": "docs/web/.npmrc",
    "content": "engine-strict=true\n"
  },
  {
    "path": "docs/web/.prettierignore",
    "content": "# Package Managers\npackage-lock.json\npnpm-lock.yaml\nyarn.lock\n"
  },
  {
    "path": "docs/web/.prettierrc",
    "content": "{\n  \"useTabs\": false,\n  \"tabWidth\": 2,\n  \"semi\": true,\n  \"singleQuote\": true,\n  \"trailingComma\": \"none\",\n  \"bracketSameLine\": true,\n  \"singleAttributePerLine\": false,\n  \"quoteProps\": \"consistent\",\n  \"plugins\": [\"prettier-plugin-svelte\"],\n  \"overrides\": [\n    {\n      \"files\": \"*.svelte\",\n      \"options\": {\n        \"parser\": \"svelte\"\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "docs/web/README.md",
    "content": "# Onefetch Web\n\n## Development\n\n```bash\n# setup\n## install dependencies\nnpm i\n\n# run server with hot reloading\nnpm start\n```\n"
  },
  {
    "path": "docs/web/eslint.config.js",
    "content": "import prettier from 'eslint-config-prettier';\nimport svelte from 'eslint-plugin-svelte';\nimport globals from 'globals';\nimport ts from 'typescript-eslint';\n\nexport default ts.config(\n  ...ts.configs.recommended,\n  ...svelte.configs['flat/recommended'],\n  prettier,\n  ...svelte.configs['flat/prettier'],\n  {\n    languageOptions: {\n      globals: {\n        ...globals.browser,\n        ...globals.node\n      }\n    }\n  },\n  {\n    files: ['**/*.svelte'],\n\n    languageOptions: {\n      parserOptions: {\n        parser: ts.parser\n      }\n    }\n  },\n  {\n    ignores: ['build/', '.svelte-kit/', 'dist/']\n  }\n);\n"
  },
  {
    "path": "docs/web/package.json",
    "content": "{\n  \"name\": \"onefetch-web\",\n  \"version\": \"0.0.1\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"start\": \"vite\",\n    \"prebuild\": \"svelte-kit sync\",\n    \"build\": \"vite build\",\n    \"format\": \"prettier -w .\",\n    \"preview\": \"vite preview\",\n    \"check\": \"npm run check:svelte && npm run check:prettier && npm run check:lint\",\n    \"check:svelte\": \"svelte-kit sync && svelte-check --tsconfig ./tsconfig.json\",\n    \"check:prettier\": \"prettier --check **/*.{ts,js,svelte,css,html,json}\",\n    \"check:lint\": \"eslint\"\n  },\n  \"devDependencies\": {\n    \"@rollup/plugin-yaml\": \"^4.1.2\",\n    \"@sveltejs/adapter-auto\": \"^7.0.1\",\n    \"@sveltejs/kit\": \"^2.55.0\",\n    \"@sveltejs/vite-plugin-svelte\": \"^7.0.0\",\n    \"@types/eslint\": \"^9.6.0\",\n    \"eslint\": \"^10.0.3\",\n    \"eslint-config-prettier\": \"^10.1.8\",\n    \"eslint-plugin-svelte\": \"^3.15.2\",\n    \"globals\": \"^17.4.0\",\n    \"prettier\": \"^3.8.1\",\n    \"prettier-plugin-svelte\": \"^3.5.1\",\n    \"svelte\": \"^5.53.12\",\n    \"svelte-check\": \"^4.4.5\",\n    \"typescript\": \"^5.9.3\",\n    \"typescript-eslint\": \"^8.57.1\",\n    \"vite\": \"^8.0.0\"\n  },\n  \"dependencies\": {\n    \"svelte-hot-french-toast\": \"^4.0.0\"\n  }\n}\n"
  },
  {
    "path": "docs/web/src/app.css",
    "content": "/* $color-text: #dedce5; */\n/* Sakura.css v1.5.1\n * ================\n * Minimal css theme.\n * Project: https://github.com/oxalorg/sakura/\n */\n/* Body */\nhtml {\n  font-size: 62.5%;\n  font-family:\n    -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue',\n    Arial, 'Noto Sans', sans-serif;\n}\n\nbody {\n  font-size: 1.8rem;\n  line-height: 1.618;\n  max-width: 38em;\n  margin: auto;\n  color: #c9c9c9;\n  background-color: #222222;\n  padding: 30px 13px 13px;\n}\n\n@media (max-width: 684px) {\n  body {\n    font-size: 1.53rem;\n  }\n}\n\n@media (max-width: 382px) {\n  body {\n    font-size: 1.35rem;\n  }\n}\n\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n  line-height: 1.1;\n  font-family:\n    -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue',\n    Arial, 'Noto Sans', sans-serif;\n  font-weight: 700;\n  margin-top: 3rem;\n  margin-bottom: 1.5rem;\n  overflow-wrap: break-word;\n  word-wrap: break-word;\n  -ms-word-break: break-all;\n  word-break: break-word;\n}\n\nh1 {\n  font-size: 2.35em;\n}\n\nh2 {\n  font-size: 2em;\n}\n\nh3 {\n  font-size: 1.75em;\n}\n\nh4 {\n  font-size: 1.5em;\n}\n\nh5 {\n  font-size: 1.25em;\n}\n\nh6 {\n  font-size: 1em;\n}\n\np {\n  margin-top: 0px;\n  margin-bottom: 2.5rem;\n}\n\nsmall,\nsub,\nsup {\n  font-size: 75%;\n}\n\nhr {\n  border-color: #ffffff;\n}\n\na {\n  text-decoration: none;\n  color: #ffffff;\n}\n\na:visited {\n  color: #e6e6e6;\n}\n\na:hover {\n  color: #c9c9c9;\n  border-bottom: 2px solid #c9c9c9;\n}\n\nul {\n  padding-left: 1.4em;\n  margin-top: 0px;\n  margin-bottom: 2.5rem;\n}\n\nli {\n  margin-bottom: 0.4em;\n}\n\nblockquote {\n  margin-left: 0px;\n  margin-right: 0px;\n  padding-left: 1em;\n  padding-top: 0.8em;\n  padding-bottom: 0.8em;\n  padding-right: 0.8em;\n  border-left: 5px solid #ffffff;\n  margin-bottom: 2.5rem;\n  background-color: #4a4a4a;\n}\n\nblockquote p {\n  margin-bottom: 0;\n}\n\nimg,\nvideo {\n  height: auto;\n  max-width: 100%;\n  margin-top: 0px;\n  margin-bottom: 2.5rem;\n}\n\n/* Pre and Code */\npre {\n  background-color: #4a4a4a;\n  display: block;\n  padding: 1em;\n  overflow-x: auto;\n  margin-top: 0px;\n  margin-bottom: 2.5rem;\n  font-size: 0.9em;\n}\n\ncode,\nkbd,\nsamp {\n  font-size: 0.9em;\n  padding: 0 0.5em;\n  background-color: #4a4a4a;\n  white-space: pre-wrap;\n}\n\npre > code {\n  padding: 0;\n  background-color: transparent;\n  white-space: pre;\n  font-size: 1em;\n}\n\n/* Tables */\ntable {\n  text-align: justify;\n  width: 100%;\n  border-collapse: collapse;\n  margin-bottom: 2rem;\n}\n\ntd,\nth {\n  padding: 0.5em;\n  border-bottom: 1px solid #4a4a4a;\n}\n\n/* Buttons, forms and input */\ninput,\ntextarea {\n  border: 1px solid #c9c9c9;\n}\n\ninput:focus,\ntextarea:focus {\n  border: 1px solid #ffffff;\n}\n\ntextarea {\n  width: 100%;\n}\n\n.button,\nbutton,\ninput[type='submit'],\ninput[type='reset'],\ninput[type='button'],\ninput[type='file']::file-selector-button {\n  display: inline-block;\n  padding: 5px 10px;\n  text-align: center;\n  text-decoration: none;\n  white-space: nowrap;\n  background-color: #ffffff;\n  color: #222222;\n  border-radius: 1px;\n  border: 1px solid #ffffff;\n  cursor: pointer;\n  box-sizing: border-box;\n}\n\n.button:hover,\nbutton:hover,\ninput[type='submit']:hover,\ninput[type='reset']:hover,\ninput[type='button']:hover,\ninput[type='file']::file-selector-button:hover {\n  background-color: #c9c9c9;\n  color: #222222;\n  outline: 0;\n}\n\n.button[disabled],\nbutton[disabled],\ninput[type='submit'][disabled],\ninput[type='reset'][disabled],\ninput[type='button'][disabled],\ninput[type='file'][disabled] {\n  cursor: default;\n  opacity: 0.5;\n}\n\n.button:focus-visible,\nbutton:focus-visible,\ninput[type='submit']:focus-visible,\ninput[type='reset']:focus-visible,\ninput[type='button']:focus-visible,\ninput[type='file']:focus-visible {\n  outline-style: solid;\n  outline-width: 2px;\n}\n\ntextarea,\nselect,\ninput {\n  color: #c9c9c9;\n  padding: 6px 10px;\n  /* The 6px vertically centers text on FF, ignored by Webkit */\n  margin-bottom: 10px;\n  background-color: #4a4a4a;\n  border: 1px solid #4a4a4a;\n  border-radius: 4px;\n  box-shadow: none;\n  box-sizing: border-box;\n}\n\ntextarea:focus,\nselect:focus,\ninput:focus {\n  border: 1px solid #ffffff;\n  outline: 0;\n}\n\ninput[type='checkbox']:focus {\n  outline: 1px dotted #ffffff;\n}\n\nlabel,\nlegend,\nfieldset {\n  display: block;\n  margin-bottom: 0.5rem;\n  font-weight: 600;\n}\n"
  },
  {
    "path": "docs/web/src/app.d.ts",
    "content": "declare module '*/languages.yaml' {\n  export interface LanguageColors {\n    ansi: string[];\n    hex?: string[];\n    chip: string;\n  }\n\n  export interface Language {\n    name: string;\n    type: string;\n    ascii: string;\n    colors: LanguageColors;\n    icon: string;\n  }\n\n  export type Languages = Record<string, Language>;\n  export type Language = Language;\n}\n"
  },
  {
    "path": "docs/web/src/app.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <link rel=\"icon\" href=\"%sveltekit.assets%/onefetch.ico\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    %sveltekit.head%\n  </head>\n  <body data-sveltekit-preload-data=\"hover\">\n    <div style=\"display: contents\">%sveltekit.body%</div>\n  </body>\n</html>\n"
  },
  {
    "path": "docs/web/src/lib/utils.ts",
    "content": "export function mapToDefaultTerminalFgColor(\n  color: string,\n  dark: boolean\n): string {\n  return color === 'white' && !dark ? 'black' : color;\n}\n"
  },
  {
    "path": "docs/web/src/routes/+page.svelte",
    "content": "<script lang=\"ts\">\n  import AsciiPreview from './AsciiPreview.svelte';\n  import data from '../../../../languages.yaml';\n  import type { Languages, Language } from '../../../languages.yaml';\n  import { onMount } from 'svelte';\n  import { writable, derived } from 'svelte/store';\n  import { Toaster } from 'svelte-hot-french-toast';\n  import '../app.css';\n\n  let tagName: string;\n  let htmlUrl: string;\n  let showMenu: boolean;\n\n  const languages: Language[] = Object.entries(data as Languages).map(\n    ([name, { type, ascii, colors, icon }]) => ({\n      name,\n      type,\n      ascii,\n      colors,\n      icon\n    })\n  );\n\n  const languageTypes: string[] = Array.from(\n    new Set<string>(Object.values(data as Languages).map(({ type }) => type))\n  );\n\n  const filter = writable({\n    checkboxes: languageTypes\n  });\n\n  const filteredLanguages = derived(filter, ($filter) => {\n    return languages.filter(({ type }) => $filter.checkboxes.includes(type));\n  });\n\n  function escapeToUnicode(unicodeEscape: string): string {\n    if (unicodeEscape) {\n      let codePoint = parseInt(unicodeEscape.slice(3, -1), 16); // extract the relevent portion of the escape\n      return String.fromCodePoint(codePoint);\n    } else {\n      return '\\u{25CF}';\n    }\n  }\n\n  onMount(async () => {\n    const response = await fetch(\n      'https://api.github.com/repos/o2sh/onefetch/releases/latest'\n    );\n    const data = await response.json();\n\n    tagName = data.tag_name;\n    htmlUrl = data.html_url;\n  });\n</script>\n\n<Toaster position=\"bottom-center\" />\n\n<header>\n  {#if tagName && htmlUrl}\n    <div class=\"banner\">\n      Onefetch {tagName} is here 🎉\n      <!-- eslint-disable-next-line svelte/no-navigation-without-resolve -->\n      <a href={htmlUrl}>Learn More</a>📝\n    </div>\n  {/if}\n  <h1>Onefetch</h1>\n  <p>\n    <small>\n      <a href=\"https://github.com/o2sh/onefetch\">github</a> |\n      <a href=\"https://github.com/o2sh/onefetch/wiki\">wiki</a>\n      | built with ❤ by\n      <a href=\"https://github.com/spenserblack\">@spenserblack</a> and\n      <a href=\"https://github.com/o2sh\">@o2sh</a></small>\n  </p>\n</header>\n<main>\n  <p>\n    This page displays an ASCII preview for all the programming languages\n    supported by onefetch. Like the binary, the data is sourced from the <a\n      href=\"https://raw.githubusercontent.com/o2sh/onefetch/main/languages.yaml\"\n      ><code>Languages.yaml</code></a> file, and the layout aims to replicate how\n    the logo would appear inside a terminal.\n  </p>\n  <p>\n    Suggestions and PRs are welcome at <a\n      href=\"https://github.com/o2sh/onefetch\">github.com/o2sh/onefetch</a>\n  </p>\n  <div class=\"title\">\n    <h3>Languages <small>({$filteredLanguages.length})</small></h3>\n    <button on:click={() => (showMenu = !showMenu)}>Filter by type</button>\n  </div>\n\n  <div class:hide={!showMenu}>\n    <div class=\"checkbox-group\">\n      {#each languageTypes as type (type)}\n        <label for={type}>\n          <input\n            id={type}\n            type=\"checkbox\"\n            value={type}\n            bind:group={$filter.checkboxes} />\n          {type}\n        </label>\n      {/each}\n    </div>\n    <small\n      >Note: By default, onefetch will only recognize <strong\n        >programming</strong>\n      and <strong>markup</strong> types. Use the\n      <code>--type</code> flag to configure.</small>\n  </div>\n\n  {#each $filteredLanguages as language (language.name)}\n    <AsciiPreview\n      name={language.name}\n      ansi={language.colors.ansi}\n      hex={language.colors.hex}\n      ascii={language.ascii}\n      chipColor={language.colors.chip}\n      chipIcon={escapeToUnicode(language.icon)} />\n  {/each}\n</main>\n\n<style>\n  .banner {\n    background-color: black;\n    position: absolute;\n    top: 0;\n    left: 0;\n    width: 100%;\n    text-align: center;\n    padding: 1rem 0;\n    color: white;\n  }\n\n  .title {\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n    margin-top: 3rem;\n    margin-bottom: 1.5rem;\n  }\n\n  .title h3 {\n    margin-bottom: 0;\n    margin-top: 0;\n  }\n\n  .checkbox-group {\n    margin-top: 1.5rem;\n  }\n\n  .checkbox-group label {\n    width: fit-content;\n    text-transform: capitalize;\n  }\n\n  .hide {\n    display: none;\n  }\n</style>\n"
  },
  {
    "path": "docs/web/src/routes/+page.ts",
    "content": "// since there's no dynamic data here, we can prerender\n// it so that it gets served as a static asset in production\nexport const prerender = true;\n"
  },
  {
    "path": "docs/web/src/routes/AsciiPreview.svelte",
    "content": "<script lang=\"ts\">\n  import { mapToDefaultTerminalFgColor } from '../lib/utils';\n  import toast from 'svelte-hot-french-toast';\n\n  export let name: string;\n  export let ansi: string[];\n  export let hex: string[] | null = null;\n  export let chipColor: string;\n  export let ascii: string = '';\n  export let chipIcon: string;\n\n  let dark = true;\n  let trueColor = hex != null;\n\n  const copyAscii = async () => {\n    const plainAscii = ascii.replace(/\\{\\d+\\}/g, '');\n\n    try {\n      await navigator.clipboard.writeText(plainAscii);\n      toast.success('Copied to clipboard');\n    } catch {\n      toast.error('Could not copy to clipboard');\n    }\n  };\n\n  $: html = ascii\n    .split('\\n')\n    .map((line) => {\n      let spanCount = 0;\n      const htmlLine = line.replace(/\\{(\\d+)\\}/g, (_match, index) => {\n        const i = Number.parseInt(index, 10);\n        const spanText = `<span style=\"font-weight: bold; color: ${\n          trueColor && hex ? hex[i] : mapToDefaultTerminalFgColor(ansi[i], dark)\n        }\">`;\n        spanCount++;\n        return spanText;\n      });\n      return `${htmlLine}${'</span>'.repeat(spanCount)}`;\n    })\n    .join('\\n');\n</script>\n\n<div class=\"language-name\">\n  <h3 class=\"nerd-font\" style=\"color: {chipColor}\">{chipIcon}</h3>\n  <h3 id={name}><a href=\"#{name}\">{name}</a></h3>\n</div>\n\n<div class=\"logo-container\" class:dark>\n  <!-- eslint-disable-next-line svelte/no-at-html-tags -->\n  <pre class:dark>{@html html}</pre>\n</div>\n\n<div class=\"controls\">\n  <div class=\"checkboxes\">\n    <div class=\"checkbox\">\n      <input id=\"dark-checkbox-{name}\" type=\"checkbox\" bind:checked={dark} />\n      <label for=\"dark-checkbox-{name}\">Dark</label>\n    </div>\n    <div class=\"checkbox\">\n      <input\n        id=\"hex-checkbox-{name}\"\n        type=\"checkbox\"\n        disabled={hex == null}\n        bind:checked={trueColor} />\n      <label for=\"hex-checkbox-{name}\">True Color</label>\n    </div>\n  </div>\n  <button\n    class=\"copy-button\"\n    type=\"button\"\n    on:click={copyAscii}\n    aria-label=\"Copy ASCII to clipboard\">\n    Copy\n  </button>\n</div>\n\n<style>\n  @import url('https://www.nerdfonts.com/assets/css/webfont.css');\n\n  .logo-container {\n    display: flex;\n    justify-content: center;\n    background-color: white;\n  }\n\n  .logo-container.dark {\n    background-color: black;\n  }\n\n  pre {\n    line-height: 1.1;\n    background-color: white;\n    display: inline-block;\n    margin-bottom: 0px;\n  }\n\n  pre.dark {\n    background-color: black;\n  }\n\n  .language-name {\n    display: flex;\n    flex-direction: row;\n    align-items: baseline;\n    gap: 0.5rem;\n  }\n\n  .checkbox {\n    display: flex;\n    align-items: baseline;\n  }\n\n  .checkboxes {\n    display: flex;\n    gap: 1rem;\n  }\n\n  .controls {\n    margin-top: 0.5rem;\n    display: flex;\n    justify-content: space-between;\n    align-items: baseline;\n  }\n</style>\n"
  },
  {
    "path": "docs/web/static/robots.txt",
    "content": "# https://www.robotstxt.org/robotstxt.html\nUser-agent: *\nDisallow:\n"
  },
  {
    "path": "docs/web/svelte.config.js",
    "content": "import adapter from '@sveltejs/adapter-auto';\nimport { vitePreprocess } from '@sveltejs/vite-plugin-svelte';\n\n/** @type {import('@sveltejs/kit').Config} */\nconst config = {\n  // Consult https://svelte.dev/docs/kit/integrations\n  // for more information about preprocessors\n  preprocess: vitePreprocess(),\n\n  kit: {\n    // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.\n    // If your environment is not supported, or you settled on a specific environment, switch out the adapter.\n    // See https://svelte.dev/docs/kit/adapters for more information about adapters.\n    adapter: adapter()\n  }\n};\n\nexport default config;\n"
  },
  {
    "path": "docs/web/tsconfig.json",
    "content": "{\n  \"extends\": \"./.svelte-kit/tsconfig.json\",\n  \"compilerOptions\": {\n    \"allowJs\": true,\n    \"checkJs\": true,\n    \"esModuleInterop\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"resolveJsonModule\": true,\n    \"skipLibCheck\": true,\n    \"sourceMap\": true,\n    \"strict\": true,\n    \"moduleResolution\": \"bundler\"\n  }\n  // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias\n  // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files\n  //\n  // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes\n  // from the referenced tsconfig.json - TypeScript does not merge them in\n}\n"
  },
  {
    "path": "docs/web/vercel.json",
    "content": "{\n  \"github\": {\n    \"silent\": true\n  }\n}\n"
  },
  {
    "path": "docs/web/vite.config.ts",
    "content": "import { sveltekit } from '@sveltejs/kit/vite';\nimport { defineConfig } from 'vite';\nimport yaml from '@rollup/plugin-yaml';\n\nexport default defineConfig({\n  plugins: [sveltekit(), yaml()],\n  server: {\n    fs: {\n      // Allow serving files from one level up to the project root\n      allow: ['..']\n    }\n  }\n});\n"
  },
  {
    "path": "docs/wiki/_Footer.md",
    "content": "- 📰 [Wiki Home](https://github.com/o2sh/onefetch/wiki) \n- 🛖 [Project Home](https://github.com/o2sh/onefetch) "
  },
  {
    "path": "docs/wiki/_Sidebar.md",
    "content": "- **[Home](https://github.com/o2sh/onefetch/wiki)**\n- **General**\n  - [Installation](https://github.com/o2sh/onefetch/wiki/installation)\n  - [Getting started](https://github.com/o2sh/onefetch/wiki/getting-started)\n- **Options**\n  - [Command-line options](https://github.com/o2sh/onefetch/wiki/command-line-options)\n- **Images**\n  - [Images in the terminal](https://github.com/o2sh/onefetch/wiki/images-in-the-terminal)\n- **Ascii**\n  - [Ascii art](https://github.com/o2sh/onefetch/wiki/ascii-art)\n"
  },
  {
    "path": "docs/wiki/ascii-art.md",
    "content": "This is the format onefetch uses for its ASCII art files.\n\n#### Here's an example:\n\n```\n{2}            .:--::////::--.`\n{1}        `/yNMMNho{2}////////////:.\n{1}      `+NMMMMMMMMmy{2}/////////////:`\n{0}    `-:::{1}ohNMMMMMMMNy{2}/////////////:`\n{0}   .::::::::{1}odMMMMMMMNy{2}/////////////-\n{0}  -:::::::::::{1}/hMMMMMMMmo{2}////////////-\n{0} .::::::::::::::{1}oMMMMMMMMh{2}////////////-\n{0}`:::::::::::::{1}/dMMMMMMMMMMNo{2}///////////`\n{0}-::::::::::::{1}sMMMMMMmMMMMMMMy{2}//////////-\n{0}-::::::::::{1}/dMMMMMMs{0}:{1}+NMMMMMMd{2}/////////:\n{0}-:::::::::{1}+NMMMMMm/{0}:::{1}/dMMMMMMm+{2}///////:\n{0}-::::::::{1}sMMMMMMh{0}:::::::{1}dMMMMMMm+{2}//////-\n{0}`:::::::{1}sMMMMMMy{0}:::::::::{1}dMMMMMMm+{2}/////`\n{0} .:::::{1}sMMMMMMs{0}:::::::::::{1}mMMMMMMd{2}////-\n{0}  -:::{1}sMMMMMMy{0}::::::::::::{1}/NMMMMMMh{2}//-\n{0}   .:{1}+MMMMMMd{0}::::::::::::::{1}oMMMMMMMo{2}-\n{1}    `yMMMMMN/{0}:::::::::::::::{1}hMMMMMh.\n{1}      -yMMMo{0}::::::::::::::::{1}/MMMy-\n{1}        `/s{0}::::::::::::::::::{1}o/`\n{0}            ``.---::::---..`\n```\n\n#### Features:\n\n- You can use `{0}` to `{X}`to color the ascii, with X > 0.\n- You can pass the flag `-c, --ascii-colors 2 5 ...` to set your own colors.\n    - Look at the color palette to know the color order\n\n![](https://i.imgur.com/NFT4WL4.png)\n"
  },
  {
    "path": "docs/wiki/command-line-options.md",
    "content": "```man\nUsage: onefetch.exe [OPTIONS] [INPUT]\n\nArguments:\n  [INPUT]\n          Run as if onefetch was started in <input> instead of the current working directory\n\nOptions:\n  -h, --help\n          Print help (see a summary with '-h')\n\n  -V, --version\n          Print version\n\nINFO:\n  -d, --disabled-fields <FIELD>...\n          Allows you to disable FIELD(s) from appearing in the output\n\n      --no-title\n          Hides the title\n\n      --number-of-authors <NUM>\n          Maximum NUM of authors to be shown\n\n          [default: 3]\n\n      --number-of-languages <NUM>\n          Maximum NUM of languages to be shown\n\n          [default: 6]\n\n      --number-of-file-churns <NUM>\n          Maximum NUM of file churns to be shown\n\n          [default: 3]\n\n      --churn-pool-size <NUM>\n          Minimum NUM of commits from HEAD used to compute the churn summary\n\n          By default, the actual value is non-deterministic due to time-based computation and will be displayed under the info title \"Churn (NUM)\"\n\n  -e, --exclude <EXCLUDE>...\n          Ignore all files & directories matching EXCLUDE\n\n      --no-bots[=<REGEX>]\n          Exclude [bot] commits. Use <REGEX> to override the default pattern\n\n      --no-merges\n          Ignores merge commits\n\n  -E, --email\n          Show the email address of each author\n\n      --http-url\n          Display repository URL as HTTP\n\n      --hide-token\n          Hide token in repository URL\n\n      --include-hidden\n          Count hidden files and directories\n\n  -T, --type <TYPE>...\n          Filters output by language type\n\n          [default: programming markup]\n          [possible values: programming, markup, prose, data]\n\nTEXT FORMATTING:\n  -t, --text-colors <X>...\n          Changes the text colors (X X X...)\n\n          Goes in order of title, ~, underline, subtitle, colon, and info\n\n          For example:\n\n          '--text-colors 9 10 11 12 13 14'\n\n  -z, --iso-time\n          Use ISO 8601 formatted timestamps\n\n      --number-separator <SEPARATOR>\n          Which thousands SEPARATOR to use\n\n          [default: plain]\n          [possible values: plain, comma, space, underscore]\n\n      --no-bold\n          Turns off bold formatting\n\nASCII:\n      --ascii-input <STRING>\n          Takes a non-empty STRING as input to replace the ASCII logo\n\n          It is possible to pass a generated STRING by command substitution\n\n          For example:\n\n          '--ascii-input \"$(fortune | cowsay -W 25)\"'\n\n  -c, --ascii-colors <X>...\n          Colors (X X X...) to print the ascii art\n\n  -a, --ascii-language <LANGUAGE>\n          Which LANGUAGE's ascii art to print\n\n      --true-color <WHEN>\n          Specify when to use true color\n\n          If set to auto: true color will be enabled if supported by the terminal\n\n          [default: auto]\n          [possible values: auto, never, always]\n\nIMAGE:\n  -i, --image <IMAGE>\n          Path to the IMAGE file\n\n      --image-protocol <PROTOCOL>\n          Which image PROTOCOL to use\n\n          [possible values: kitty, sixel, iterm]\n\n      --color-resolution <VALUE>\n          VALUE of color resolution to use with SIXEL backend\n\n          [default: 16]\n          [possible values: 16, 32, 64, 128, 256]\n\nVISUALS:\n      --no-color-palette\n          Hides the color palette\n\n      --no-art\n          Hides the ascii art or image if provided\n\n      --nerd-fonts\n          Use Nerd Font icons\n\n          Replaces language chips with Nerd Font icons\n\nDEVELOPER:\n  -o, --output <FORMAT>\n          Outputs Onefetch in a specific format\n\n          [possible values: json, yaml]\n\n      --generate <SHELL>\n          If provided, outputs the completion file for given SHELL\n\n          [possible values: bash, elvish, fish, powershell, zsh]\n\nOTHER:\n  -l, --languages\n          Prints out supported languages\n\n  -p, --package-managers\n          Prints out supported package managers\n```\n"
  },
  {
    "path": "docs/wiki/getting-started.md",
    "content": "Onefetch is installed, then what?\n\n### Usage\n\n```sh\n> onefetch /path/of/your/repo\n ```\n\n Or\n\n```sh\n> cd /path/of/your/repo\n> onefetch\n```\n\n### Misc\n\nBy [**@spenserblack**](https://github.com/spenserblack)\n\n```sh\n# Runs `onefetch -a Assembly`, `onefetch -a C`, etc.\nonefetch -l | tr \"[:upper:] \" \"[:lower:]-\" | while read line; do echo \"$line\"; onefetch -a $line; done;\n```\n\n### Automatic repo detection and running\n\nIf you want to automate the detection and running of `onefetch` every time you `cd` into a repository you can leverage one of the methods below:\n\n#### 1. Bash / Zsh\n\nBy [**@quazar-omega**](https://github.com/quazar-omega)\n\nA script to put in your `.bashrc` - or `.zshrc` - to run onefetch whenever you open a shell into a repository or `cd` into a repository, making sure that it's different from the last one you were in:\n\n```sh\n# git repository greeter\nlast_repository=\ncheck_directory_for_new_repository() {\n current_repository=$(git rev-parse --show-toplevel 2> /dev/null)\n \n if [ \"$current_repository\" ] && \\\n    [ \"$current_repository\" != \"$last_repository\" ]; then\n  onefetch\n fi\n last_repository=$current_repository\n}\ncd() {\n builtin cd \"$@\"\n check_directory_for_new_repository\n}\n\n# optional, greet also when opening shell directly in repository directory\n# adds time to startup\ncheck_directory_for_new_repository\n```\n\n#### 2. Fish\n\nBy [**@TheSast**](https://github.com/TheSast)\n\nA fish adaptation of the previous script, run it once in your shell to save it:\n\n```fish\nfunction cd -w='cd'\n  builtin cd $argv || return\n  check_directory_for_new_repository\nend\n\nfunction check_directory_for_new_repository\n  set current_repository (git rev-parse --show-toplevel 2> /dev/null)\n  if [ \"$current_repository\" ] && \\\n    [ \"$current_repository\" != \"$last_repository\" ]\n    onefetch\n  end\n  set -gx last_repository $current_repository\nend\n\nfuncsave cd\nfuncsave check_directory_for_new_repository\n```\n\n#### 3. CMD\n\nBy [**@mataha**](https://github.com/mataha)\n\nAn adaptation of the above snippet suited for Windows's `cmd.exe`,\nspecifically for inclusion in AutoRun scripts or DOSKEY macrofiles:\n\n```bat\n@set LAST_REPOSITORY=\n\n@doskey cd = ( ^\n    for %%^^^^ in (\"\") do @for /f \"delims=\" %%i in (^^\"\"$*%%~^^\"^\") do @( ^\n        if \"%%~i\"==\"\" ( ^\n            if defined USERPROFILE ( ^\n                if /i not \"%%CD%%\"==\"%%USERPROFILE%%\" ( ^\n                    chdir /d \"%%USERPROFILE%%\" ^&^& set \"OLDPWD=%%CD%%\" ^\n                ) ^\n            ) else (call) ^\n        ) else if \"%%~i\"==\"-\" ( ^\n            if defined OLDPWD ( ^\n                if /i not \"%%CD%%\"==\"%%OLDPWD%%\" ( ^\n                    chdir /d \"%%OLDPWD%%\"      ^&^& set \"OLDPWD=%%CD%%\" ^\n                ) ^\n            ) else (call) ^\n        ) else ( ^\n            if defined CD ( ^\n                if /i not \"%%CD%%\"==\"%%~fi\" ( ^\n                    chdir /d %%~fi             ^&^& set \"OLDPWD=%%CD%%\" ^\n                ) ^\n            ) else (call) ^\n        ) ^\n    ) ^\n) ^&^& for /f \"delims=\" %%r in ('git rev-parse --show-toplevel 2^^^>nul') do @( ^\n    if not \"%%~r\"==\"%%LAST_REPOSITORY%%\" ( ^\n        onefetch ^\n    ) ^& set \"LAST_REPOSITORY=%%~r\" ^\n)\n```\n\n#### 4. Powershell\n\nBy [**@kiapanahi**](https://github.com/kiapanahi)\n\nAn adaptation of the above snippet suited for `Powershell`. Put this script in the `$PROFILE`.\n\n```pwsh\n# git repository greeter\n\n# Set the console output encoding to UTF-8, so that special characters are displayed correctly when piping to Write-Host\n[Console]::OutputEncoding = [System.Text.Encoding]::UTF8\n$global:lastRepository = $null\n\nfunction Check-DirectoryForNewRepository {\n    $currentRepository = git rev-parse --show-toplevel 2>$null\n    if ($currentRepository -and ($currentRepository -ne $global:lastRepository)) {\n        onefetch | Write-Host\n    }\n    $global:lastRepository = $currentRepository\n}\n\nfunction Set-Location {\n    Microsoft.PowerShell.Management\\Set-Location @args  \n    Check-DirectoryForNewRepository\n}\n\n# Optional: Check the repository also when opening a shell directly in a repository directory\n# Uncomment the following line if desired\n#Check-DirectoryForNewRepository\n```\n\n### Git alias\n\nBy [**@mbrehin**](https://github.com/mbrehin)\n\nYou can also add git alias to run onefetch during your git workflows\n\n```sh\n# Add Git alias for onefetch.\ngit config --global alias.project-summary '!which onefetch && onefetch'\n```\n"
  },
  {
    "path": "docs/wiki/home.md",
    "content": "Welcome to the onefetch's wiki!\n\n- **[Home](https://github.com/o2sh/onefetch/wiki)**\n- **General**\n  - [Installation](https://github.com/o2sh/onefetch/wiki/installation)\n  - [Getting started](https://github.com/o2sh/onefetch/wiki/getting-started)\n- **Options**\n  - [Command-line options](https://github.com/o2sh/onefetch/wiki/command-line-options)\n- **Images**\n  - [Images in the terminal](https://github.com/o2sh/onefetch/wiki/images-in-the-terminal)\n- **Ascii**\n  - [Ascii art](https://github.com/o2sh/onefetch/wiki/ascii-art)\n"
  },
  {
    "path": "docs/wiki/images-in-the-terminal.md",
    "content": "Onefetch supports displaying images using [`kitty`](https://sw.kovidgoyal.net/kitty/graphics-protocol.html), [`Sixel`](https://en.wikipedia.org/wiki/Sixel) and [`iTerm`](https://www.iterm2.com/documentation-images.html) protocols.\n\n<p align=\"center\">\n<img src=\"https://raw.githubusercontent.com/o2sh/onefetch/main/assets/screenshot-1.png\" height=\"250px\">\n</p>\n\nWhen running `onefetch --image ./My-picture.jpg`, the program looks for the first `Image Protocol` supported by the terminal and use it to display the requested image instead of the Ascii logo.\n\nIf you decide to go manual, and want to force the use of a specific image protocol: `onefetch --image ./My-picture.jpg --image-protocol sixel|kitty|iterm`\n\n### Sixel\n\nThe Sixel protocol is handled by multiple terminal emulators such as iterm2, [`xterm`](https://invisible-island.net/xterm/) (enabled via flag `-ti 340`), [`mlterm`](https://wiki.ubuntu.com/Mlterm) and [`WezTerm`](https://github.com/wez/wezterm) which are actively maintained.\n\nYou can increase the color resolution using the `--color-resolution` flag.\n\n### Kitty\n\nThe kitty terminal graphics protocol used on the terminal of the same name allows the program running in the terminal, to render graphics to the screen of the terminal emulator.\n\n### ITerm\n\nThe iTerm inline image protocol supported by iTerm2 (also WezTerm) allows to display images within the terminal.\n"
  },
  {
    "path": "docs/wiki/installation.md",
    "content": "This wiki page will guide you through getting onefetch working on your system.\n\n[![Packaging status](https://repology.org/badge/vertical-allrepos/onefetch.svg)](https://repology.org/project/onefetch/versions)\n\n# Table of Contents\n\n- [Universal Install](#universal-install)\n  - [Build from source](#build-from-source)\n  - [Cargo](#cargo)\n- [OS/Distro Packages](#osdistro-packages)\n  - [Alpine Linux](#alpine-linux)\n  - [Arch](#arch)\n  - [Fedora](#fedora)\n  - [FreeBSD](#freebsd)\n  - [Funtoo](#funtoo)\n  - [macOS](#macos)\n    - [Homebrew](#homebrew)\n    - [MacPorts](#macports)\n  - [NetBSD](#netbsd)\n  - [NixOS](#nixos)\n  - [openSUSE](#openSUSE-Leap-or-Tumbleweed)\n  - [Ubuntu](#ubuntu)\n  - [Void Linux](#void-linux)\n  - [Windows](#windows)\n    - [Winget](#Winget)\n    - [Scoop](#scoop)\n    - [Chocolatey](#chocolatey)\n\n# Universal Install\n\nIf your architecture is supported by the pre-built binaries, you can download them from the [releases page](https://github.com/o2sh/onefetch/releases/latest).\n\n## Build from source\n\nFirst, install rust toolchain with [rustup](https://rustup.rs/):\n\n```shell\ncurl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh\n```\n\n> [!IMPORTANT]\n> Ensure [CMake](https://cmake.org/download/) is installed.\n\n```\ngit clone https://github.com/o2sh/onefetch\ncd onefetch\nmake install\n```\n\n## Cargo\n\nFirst, install rust toolchain with [rustup](https://rustup.rs/):\n\n```shell\ncurl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh\n```\n\n> [!IMPORTANT]\n> Ensure [CMake](https://cmake.org/download/) is installed.\n\n```\ncargo install onefetch\n```\n\nThis method will build the binary from source.\n\nTo update, run\n\n```\ncargo install onefetch --force\n```\n\n# OS/Distro Packages\n\n## Alpine Linux\n\n1. Update repositories\n   - `apk update`\n2. Install the package\n   - `apk add onefetch`\n\n## Arch\n\nOnefetch is available in the official repos.\n\n- Install the package\n  - `pacman -S onefetch`\n\n## Fedora\n\nInstall it from the COPR\n\n```\nsudo dnf copr enable varlad/onefetch\nsudo dnf install onefetch\n```\n\n## FreeBSD\n\nInstall it from the official repositories\n\n- `pkg install onefetch`\n\n## Funtoo\n\nFuntoo has an autogenerated onefetch package in the official kits:\n\n- `emerge app-misc/onefetch`\n\n## MacOS\n\n### HomeBrew\n\nInstall `onefetch` with Homebrew\n\n```\nbrew install onefetch\n```\n\nTo update, run\n\n```\nbrew upgrade onefetch\n```\n\n### MacPorts\n\nInstall `onefetch` with MacPorts\n\n```\nsudo port selfupdate\nsudo port install onefetch\n```\n\nTo update run,\n\n```\nsudo port upgrade onefetch\n```\n\n## NetBSD\n\nInstall it from the official repositories.\n\n- `pkg_add onefetch`\n\n## NixOS\n\nInstall it from the official repositories\n\n- `nix-env -i onefetch`\n\n## openSUSE Leap or Tumbleweed\n\nInstall it from the official repositories.\n\n- `zypper install onefetch`\n\n## Ubuntu\n\nUse debian package from release\n\n```\nwget https://github.com/o2sh/onefetch/releases/latest/download/onefetch_amd64.deb && sudo dpkg -i ./onefetch_amd64.deb\n```\n\nUse Snap\n\n```\nsnap install onefetch\n```\n\nor\n\n```\nsudo add-apt-repository ppa:o2sh/onefetch\nsudo apt-get update\nsudo apt-get install onefetch\n```\n\n## Void Linux\n\nInstall it from the official repositories\n\n- `sudo xbps-install -S onefetch`\n\n## Windows\n\n### Winget\n\nYou can install onefetch using [winget](https://docs.microsoft.com/en-us/windows/package-manager/winget/)\n\n```\nwinget install onefetch\n```\n\n### Scoop\n\nFor [Scoop](https://scoop.sh/) users, onefetch is available from the \"Extras\" bucket\n\n```\nscoop bucket add extras\nscoop install onefetch\n```\n\n### Chocolatey\n\nIf you prefer to use [Chocolatey](https://chocolatey.org/) to manage software, it can be installed like so\n\n```\nchoco install onefetch\n```\n"
  },
  {
    "path": "image/Cargo.toml",
    "content": "[package]\nauthors.workspace = true\nedition.workspace = true\nversion.workspace = true\nlicense.workspace = true\nrepository.workspace = true\nname = \"onefetch-image\"\ndescription = \"Display images in the terminal\"\n\n[dependencies]\nanyhow = \"1.0.101\"\nclap = { version = \"4.5.57\", features = [\"derive\"] }\nimage = { version = \"0.25.10\", default-features = false, features = [\n    \"color_quant\",\n    \"jpeg\",\n    \"png\",\n    \"webp\",\n] }\n\n[target.'cfg(not(windows))'.dependencies]\ncolor_quant = \"1.1.0\"\nbase64 = \"0.22.1\"\nrustix = { version = \"1.1.4\", features = [\"termios\", \"event\"] }\n"
  },
  {
    "path": "image/README.md",
    "content": "# image\n\n[![crates.io](https://img.shields.io/crates/v/onefetch-image)](https://crates.io/crates/onefetch-image)\n[![docs.rs](https://img.shields.io/docsrs/onefetch-image)](https://docs.rs/onefetch-image)\n\nProvides the primary interface to display images to the terminal.\n\nProtocols supported:\n\n- Sixel\n- Kitty\n- Iterm\n\n_This crate is designed as part of the [onefetch](https://github.com/o2sh/onefetch) project._\n"
  },
  {
    "path": "image/src/iterm.rs",
    "content": "use anyhow::Result;\nuse base64::{Engine, engine};\nuse image::{DynamicImage, imageops::FilterType};\nuse rustix::termios::tcgetwinsize;\nuse std::env;\nuse std::io::Cursor;\n\npub struct ITermBackend;\n\nimpl ITermBackend {\n    pub fn supported() -> bool {\n        let term_program = env::var(\"TERM_PROGRAM\").unwrap_or_else(|_| \"\".to_string());\n        term_program == \"iTerm.app\"\n    }\n}\n\nimpl super::ImageBackend for ITermBackend {\n    fn add_image(\n        &self,\n        lines: Vec<String>,\n        image: &DynamicImage,\n        _colors: usize,\n    ) -> Result<String> {\n        let tty_size = tcgetwinsize(std::io::stdin())?;\n        let width_ratio = f64::from(tty_size.ws_col) / f64::from(tty_size.ws_xpixel);\n        let height_ratio = f64::from(tty_size.ws_row) / f64::from(tty_size.ws_ypixel);\n\n        // resize image to fit the text height with the Lanczos3 algorithm\n        let image = image.resize(\n            u32::MAX,\n            (lines.len() as f64 / height_ratio) as u32,\n            FilterType::Lanczos3,\n        );\n        let _image_columns = width_ratio * f64::from(image.width());\n        let image_rows = height_ratio * f64::from(image.height());\n\n        let mut bytes: Vec<u8> = Vec::new();\n        image.write_to(&mut Cursor::new(&mut bytes), image::ImageFormat::Png)?;\n        let encoded_image = engine::general_purpose::STANDARD.encode(bytes);\n        let mut image_data = Vec::<u8>::new();\n\n        image_data.extend(b\"\\x1B]1337;File=inline=1:\");\n        image_data.extend(encoded_image.bytes());\n        image_data.extend(b\"\\x07\");\n\n        image_data.extend(format!(\"\\x1B[{}A\", image_rows as u32 - 1).as_bytes()); // move cursor to start of image\n        let mut i = 0;\n        for line in &lines {\n            image_data.extend(format!(\"\\x1B[s{line}\\x1B[u\\x1B[1B\").as_bytes());\n            i += 1;\n        }\n        image_data\n            .extend(format!(\"\\n\\x1B[{}B\", lines.len().max(image_rows as usize) - i).as_bytes()); // move cursor to end of image\n\n        Ok(String::from_utf8(image_data)?)\n    }\n}\n"
  },
  {
    "path": "image/src/kitty.rs",
    "content": "use anyhow::{Context as _, Result};\nuse base64::{Engine, engine};\nuse image::{DynamicImage, imageops::FilterType};\n\nuse rustix::event::{PollFd, PollFlags, Timespec, poll};\nuse rustix::io::read;\nuse rustix::termios::{LocalModes, OptionalActions, tcgetattr, tcgetwinsize, tcsetattr};\n\nuse std::io::{Write, stdout};\nuse std::os::fd::AsFd as _;\nuse std::time::Instant;\n\npub struct KittyBackend;\n\nimpl KittyBackend {\n    pub fn supported() -> Result<bool> {\n        let stdin = std::io::stdin();\n        // save terminal attributes and disable canonical input processing mode\n        let old_attributes = {\n            let old = tcgetattr(&stdin).context(\"Failed to recieve terminal attibutes\")?;\n\n            let mut new = old.clone();\n            new.local_modes &= !LocalModes::ICANON;\n            new.local_modes &= !LocalModes::ECHO;\n            tcsetattr(&stdin, OptionalActions::Now, &new)\n                .context(\"Failed to update terminal attributes\")?;\n            old\n        };\n\n        // generate red rgba test image\n        let mut test_image = Vec::<u8>::with_capacity(32 * 32 * 4);\n        test_image.extend(std::iter::repeat_n([255, 0, 0, 255].iter(), 32 * 32).flatten());\n\n        // print the test image with the action set to query\n        print!(\n            \"\\x1B_Gi=1,f=32,s=32,v=32,a=q;{}\\x1B\\\\\",\n            engine::general_purpose::STANDARD.encode(&test_image)\n        );\n        stdout().flush()?;\n\n        let start_time = Instant::now();\n        let stdin_fd = stdin.as_fd();\n        let mut stdin_pollfd = [PollFd::new(&stdin_fd, PollFlags::IN)];\n        let allowed_bytes = [0x1B, b'_', b'G', b'\\\\'];\n        let mut buf = Vec::<u8>::new();\n        loop {\n            // check for timeout while polling to avoid blocking the main thread\n            while poll(&mut stdin_pollfd, Some(&Timespec::default()))? < 1 {\n                if start_time.elapsed().as_millis() > 50 {\n                    tcsetattr(&stdin, OptionalActions::Now, &old_attributes)\n                        .context(\"Failed to update terminal attributes\")?;\n                    return Ok(false);\n                }\n            }\n            let mut byte = [0];\n            read(&stdin, &mut byte)?;\n            if allowed_bytes.contains(&byte[0]) {\n                buf.push(byte[0]);\n            }\n            if buf.starts_with(&[0x1B, b'_', b'G']) && buf.ends_with(&[0x1B, b'\\\\']) {\n                tcsetattr(&stdin, OptionalActions::Now, &old_attributes)\n                    .context(\"Failed to update terminal attributes\")?;\n                return Ok(true);\n            }\n        }\n    }\n}\n\nimpl super::ImageBackend for KittyBackend {\n    fn add_image(\n        &self,\n        lines: Vec<String>,\n        image: &DynamicImage,\n        _colors: usize,\n    ) -> Result<String> {\n        let tty_size = tcgetwinsize(std::io::stdin())?;\n        let width_ratio = f64::from(tty_size.ws_col) / f64::from(tty_size.ws_xpixel);\n        let height_ratio = f64::from(tty_size.ws_row) / f64::from(tty_size.ws_ypixel);\n\n        // resize image to fit the text height with the Lanczos3 algorithm\n        let image = image.resize(\n            u32::MAX,\n            (lines.len() as f64 / height_ratio) as u32,\n            FilterType::Lanczos3,\n        );\n        let _image_columns = width_ratio * f64::from(image.width());\n        let image_rows = height_ratio * f64::from(image.height());\n\n        // convert the image to rgba samples\n        let rgba_image = image.to_rgba8();\n        let flat_samples = rgba_image.as_flat_samples();\n        let raw_image = flat_samples\n            .image_slice()\n            .expect(\"Conversion from image to rgba samples failed\");\n        assert_eq!(\n            image.width() as usize * image.height() as usize * 4,\n            raw_image.len()\n        );\n\n        let encoded_image = engine::general_purpose::STANDARD.encode(raw_image); // image data is base64 encoded\n        let mut image_data = Vec::<u8>::new();\n        for chunk in encoded_image.as_bytes().chunks(4096) {\n            // send a 4096 byte chunk of base64 encoded rgba image data\n            image_data.extend(\n                format!(\n                    \"\\x1B_Gf=32,s={},v={},m=1,a=T;\",\n                    image.width(),\n                    image.height()\n                )\n                .as_bytes(),\n            );\n            image_data.extend(chunk);\n            image_data.extend(b\"\\x1B\\\\\");\n        }\n        image_data.extend(b\"\\x1B_Gm=0;\\x1B\\\\\"); // write empty last chunk\n        image_data.extend(format!(\"\\x1B[{}A\", image_rows as u32 - 1).as_bytes()); // move cursor to start of image\n        let mut i = 0;\n        for line in &lines {\n            image_data.extend(format!(\"\\x1B[s{line}\\x1B[u\\x1B[1B\").as_bytes());\n            i += 1;\n        }\n        image_data\n            .extend(format!(\"\\n\\x1B[{}B\", lines.len().max(image_rows as usize) - i).as_bytes()); // move cursor to end of image\n\n        Ok(String::from_utf8(image_data)?)\n    }\n}\n"
  },
  {
    "path": "image/src/lib.rs",
    "content": "use anyhow::Result;\nuse image::DynamicImage;\n\n#[derive(clap::ValueEnum, Clone, PartialEq, Eq, Debug)]\npub enum ImageProtocol {\n    Kitty,\n    Sixel,\n    Iterm,\n}\n\n#[cfg(not(windows))]\npub mod iterm;\n#[cfg(not(windows))]\npub mod kitty;\n#[cfg(not(windows))]\npub mod sixel;\n\npub trait ImageBackend {\n    fn add_image(&self, lines: Vec<String>, image: &DynamicImage, colors: usize) -> Result<String>;\n}\n\npub fn get_best_backend() -> Result<Option<Box<dyn ImageBackend>>> {\n    #[cfg(not(windows))]\n    if sixel::SixelBackend::supported()? {\n        Ok(Some(Box::new(sixel::SixelBackend)))\n    } else if kitty::KittyBackend::supported()? {\n        Ok(Some(Box::new(kitty::KittyBackend)))\n    } else if iterm::ITermBackend::supported() {\n        Ok(Some(Box::new(iterm::ITermBackend)))\n    } else {\n        Ok(None)\n    }\n\n    #[cfg(windows)]\n    Ok(None)\n}\n\n#[allow(unused_variables)]\npub fn get_image_backend(image_protocol: ImageProtocol) -> Option<Box<dyn ImageBackend>> {\n    #[cfg(not(windows))]\n    let backend = Some(match image_protocol {\n        ImageProtocol::Kitty => Box::new(kitty::KittyBackend) as Box<dyn ImageBackend>,\n        ImageProtocol::Iterm => Box::new(iterm::ITermBackend) as Box<dyn ImageBackend>,\n        ImageProtocol::Sixel => Box::new(sixel::SixelBackend) as Box<dyn ImageBackend>,\n    });\n\n    #[cfg(windows)]\n    let backend = None;\n    backend\n}\n"
  },
  {
    "path": "image/src/sixel.rs",
    "content": "use anyhow::{Context as _, Result};\nuse color_quant::NeuQuant;\nuse image::{\n    DynamicImage, GenericImageView, ImageBuffer, Pixel, Rgb,\n    imageops::{FilterType, colorops},\n};\n\nuse rustix::event::{PollFd, PollFlags, Timespec, poll};\nuse rustix::io::read;\nuse rustix::termios::{LocalModes, OptionalActions, tcgetattr, tcgetwinsize, tcsetattr};\n\nuse std::io::{Write, stdout};\nuse std::os::fd::AsFd;\nuse std::time::Instant;\n\npub struct SixelBackend;\n\nimpl SixelBackend {\n    pub fn supported() -> Result<bool> {\n        let stdin = std::io::stdin();\n        // save terminal attributes and disable canonical input processing mode\n        let old_attributes = {\n            let old = tcgetattr(&stdin).context(\"Failed to recieve terminal attibutes\")?;\n\n            let mut new = old.clone();\n            new.local_modes &= !LocalModes::ICANON;\n            new.local_modes &= !LocalModes::ECHO;\n            tcsetattr(&stdin, OptionalActions::Now, &new)\n                .context(\"Failed to update terminal attributes\")?;\n            old\n        };\n\n        // ask for the primary device attribute string\n        print!(\"\\x1B[c\");\n        stdout().flush()?;\n\n        let start_time = Instant::now();\n        let stdin_fd = stdin.as_fd();\n        let mut stdin_pollfd = [PollFd::new(&stdin_fd, PollFlags::IN)];\n        let mut buf = Vec::<u8>::new();\n        loop {\n            // check for timeout while polling to avoid blocking the main thread\n            while poll(&mut stdin_pollfd, Some(&Timespec::default()))? < 1 {\n                if start_time.elapsed().as_millis() > 50 {\n                    tcsetattr(stdin, OptionalActions::Now, &old_attributes)\n                        .context(\"Failed to update terminal attributes\")?;\n                    return Ok(false);\n                }\n            }\n            let mut byte = [0];\n            read(&stdin, &mut byte)?;\n            buf.push(byte[0]);\n            if buf.starts_with(&[0x1B, b'[', b'?']) && buf.ends_with(b\"c\") {\n                for attribute in buf[3..(buf.len() - 1)].split(|x| *x == b';') {\n                    if attribute == [b'4'] {\n                        tcsetattr(stdin, OptionalActions::Now, &old_attributes)\n                            .context(\"Failed to update terminal attributes\")?;\n                        return Ok(true);\n                    }\n                }\n            }\n        }\n    }\n}\n\nimpl super::ImageBackend for SixelBackend {\n    #[allow(clippy::map_entry)]\n    fn add_image(&self, lines: Vec<String>, image: &DynamicImage, colors: usize) -> Result<String> {\n        let tty_size = tcgetwinsize(std::io::stdin())?;\n        let cw = tty_size.ws_xpixel / tty_size.ws_col;\n        let lh = tty_size.ws_ypixel / tty_size.ws_row;\n        let width_ratio = 1.0 / cw as f64;\n        let height_ratio = 1.0 / lh as f64;\n\n        // resize image to fit the text height with the Lanczos3 algorithm\n        let image = image.resize(\n            u32::MAX,\n            (lines.len() as f64 / height_ratio) as u32,\n            FilterType::Lanczos3,\n        );\n        let image_columns = width_ratio * image.width() as f64;\n        let image_rows = height_ratio * image.height() as f64;\n\n        let rgba_image = image.to_rgba8(); // convert the image to rgba samples\n        let flat_samples = rgba_image.as_flat_samples();\n        let mut rgba_image = rgba_image.clone();\n        // reduce the amount of colors using dithering\n        let pixels = flat_samples\n            .image_slice()\n            .context(\"Error while slicing the image\")?;\n        colorops::dither(&mut rgba_image, &NeuQuant::new(10, colors, pixels));\n\n        let rgb_image = ImageBuffer::from_fn(rgba_image.width(), rgba_image.height(), |x, y| {\n            let rgba_pixel = rgba_image.get_pixel(x, y);\n            let mut rgb_pixel = rgba_pixel.to_rgb();\n            for subpixel in &mut rgb_pixel.0 {\n                *subpixel = (*subpixel as f32 / 255.0 * rgba_pixel[3] as f32) as u8;\n            }\n            rgb_pixel\n        });\n\n        let mut image_data = Vec::<u8>::new();\n        image_data.extend(b\"\\x1BPq\"); // start sixel data\n        image_data.extend(format!(\"\\\"1;1;{};{}\", image.width(), image.height()).as_bytes());\n\n        let mut colors = std::collections::HashMap::<Rgb<u8>, u8>::new();\n        // subtract 1 -> divide -> add 1 to round up the integer division\n        for i in 0..((rgb_image.height() - 1) / 6 + 1) {\n            let sixel_row = rgb_image.view(\n                0,\n                i * 6,\n                rgb_image.width(),\n                std::cmp::min(6, rgb_image.height() - i * 6),\n            );\n            for (_, _, pixel) in sixel_row.pixels() {\n                if !colors.contains_key(&pixel) {\n                    // sixel uses percentages for rgb values\n                    let color_multiplier = 100.0 / 255.0;\n                    image_data.extend(\n                        format!(\n                            \"#{};2;{};{};{}\",\n                            colors.len(),\n                            (pixel[0] as f32 * color_multiplier) as u32,\n                            (pixel[1] as f32 * color_multiplier) as u32,\n                            (pixel[2] as f32 * color_multiplier) as u32\n                        )\n                        .as_bytes(),\n                    );\n                    colors.insert(pixel, colors.len() as u8);\n                }\n            }\n            for (color, color_index) in &colors {\n                let mut sixel_samples = vec![0; sixel_row.width() as usize];\n                sixel_samples.resize(sixel_row.width() as usize, 0);\n                for (x, y, pixel) in sixel_row.pixels() {\n                    if color == &pixel {\n                        sixel_samples[x as usize] |= 1 << y;\n                    }\n                }\n                image_data.extend(format!(\"#{color_index}\").bytes());\n                image_data.extend(sixel_samples.iter().map(|x| x + 0x3F));\n                image_data.push(b'$');\n            }\n            image_data.push(b'-');\n        }\n        image_data.extend(b\"\\x1B\\\\\");\n\n        image_data.extend(format!(\"\\x1B[{}A\", image_rows as u32 - 1).as_bytes()); // move cursor to top-left corner\n        image_data.extend(format!(\"\\x1B[{}C\", image_columns as u32 + 1).as_bytes()); // move cursor to top-right corner of image\n        let mut i = 0;\n        for line in &lines {\n            image_data.extend(format!(\"\\x1B[s{line}\\x1B[u\\x1B[1B\").as_bytes());\n            i += 1;\n        }\n        image_data\n            .extend(format!(\"\\n\\x1B[{}B\", lines.len().max(image_rows as usize) - i).as_bytes()); // move cursor to end of image\n\n        Ok(String::from_utf8(image_data)?)\n    }\n}\n"
  },
  {
    "path": "languages.yaml",
    "content": "Abap:\n  type: programming\n  ascii: |\n    {0}xxxxxxxxxxxxxxxxxxxx\n    {0}xx{1}###{0}xxx{1}##{0}xx{1}####{0}xxx{1} ##  ####   ##  ####\n    {0}x{1}#{0}xxxxx{1}#{0}xx{1}#{0}x{1}#{0}xxx{1}#{0}x{1} #  # #   # #  # #   #\n    {0}xx{1}###{0}xx{1}####{0}x{1}####{0}x{1}  #### ####  #### ####\n    {0}xxxxx{1}#{0}x{1}#{0}xx{1}#{0}x{1}#{0}xxx{1}   #  # #   # #  # #\n    {0}x{1}####{0}xx{1}#{0}xx{1}#{0}x{1}#{0}xx{1}    #  # ####  #  # #\n    {0}xxxxxxxxxxxxxx\n  colors:\n    ansi:\n      - blue\n      - white\n    hex:\n      - \"#1B387D\"\n      - \"#EEEEEE\"\n    chip: \"#E8274B\"\nABNF:\n  type: data\n  ascii: |\n    {0}          ______\n    {0}         |      |\n    {0}         |  /\\  |\n    {0}>>---+-->| /--\\ |-->+--->>\n    {0}     |   |______|   |\n    {0}     |    ______    |\n    {0}     |   |  __  |   |\n    {0}     |   | |__) |   |\n    {0}     +-->| |__) |-->+\n    {0}     |   |______|   |\n    {0}     |    ______    |\n    {0}     |   |      |   |\n    {0}     |   | |\\ | |   |\n    {0}     +-->| | \\| |-->+\n    {0}     |   |______|   |\n    {0}     |    ______    |\n    {0}     |   |  ___ |   |\n    {0}     |   | |___ |   |\n    {0}     +-->| |    |-->+\n    {0}         |______|\n  colors:\n    ansi:\n      - white\n    hex:\n      - \"#888888\"\n    chip: \"#555e25\"\nAda:\n  type: programming\n  ascii: |\n    {0}                              *\n    {0}                              *\n    {0}                            * **\n    {0}   *                        *****\n    {0}   **                       ******\n    {0}    **                   ###********\n    {0}    ***          -******#'###*********\n    {0}     *****               #### *********\n    {0}      ****************************{1}XXX{0}**\n    {0}     ** ******************{1}XXXX{2}o{1}X{0}*{1}X{2}o{1}XXX{0}*\n    {0}      **** ***************{1}XXXX{2}ooooo{1}XX{0}*\n    {0}        *******************{1}XXXX{2}ooooo{1}X\n    {0}          *******************{1}XXX{2}ooo{1}X\n    {0}          ************************{1}XX{0}*\n    {0}             *************    ******\n    {0}  /| |                           ****\n    {0} /-|(|(|                     ********\n    {0}                        ************\n    {0}Time-tested, safe     *************\n    {0}and secure            ***********\n    {0}                         *****\n  colors:\n    ansi:\n      - white\n      - cyan\n      - blue\n    hex:\n      - \"#FFFFFF\"\n      - \"#0018C9\"\n      - \"#0C0A7C\"\n    chip: \"#02F88C\"\n  icon: '\\u{E6B5}'\nAgda:\n  type: programming\n  ascii: |\n    {0}   / / /           /  /\n    {0}  / / /           /  / /\n    {0} / o o           /    / /\n    {0}/____           /      /\n    {0}     |                 |\n    {0}     |                 |\n    {0}     ;                 ;\n    {0}      \\               /\n    {0}       '.           .'\n    {0}         '-._____.-'\n  colors:\n    ansi:\n      - white\n    chip: \"#315665\"\nArduino:\n  type: programming\n  ascii: |\n    {0}   ,=======.    ,=======.\n    {0}  //       \\\\  //       \\\\\n    {0} //         \\\\//    #    \\\\\n    {0} ||   ###    //    ###   ||\n    {0} \\\\         //\\\\    #    //\n    {0}  \\\\       //  \\\\       //\n    {0}   `======='    `======='\n    {0}      _   _              _\n    {0} /\\  |_) | \\ | | | |\\ | / \\\n    {0}/--\\ | \\ |_/ |_| | | \\| \\_/\n  colors:\n    ansi:\n      - cyan\n    chip: \"#F34B7D\"\n  icon: '\\u{F34B}'\nAssembly:\n  type: programming\n  ascii: |\n    {0}     __________________________\n    {0}    /                          \\\n    {1}==={0}|  {1}.-.                       {0}|{1}===\n    {0}   | {1}(   )                      {0}|\n    {1}==={0}|  {1}'-'                       {0}|{1}===\n    {0}   |                            |\n    {1}==={0}|     {2} _____  ___ ____       {0}|{1}===\n    {0}   |     {2}(____ |/___)    \\      {0}|\n    {1}==={0}|     {2}/ ___ |___ | | | |     {0}|{1}===\n    {0}   |     {2}\\_____(___/|_|_|_|     {0}|\n    {1}==={0}|                            |{1}===\n    {0}   |                            |\n    {1}==={0}|                       {1}.-.  {0}|{1}===\n    {0}   |                      {1}(   ) {0}|\n    {1}==={0}|                       {1}'-'  {0}|{1}===\n    {0}    \\__________________________/\n  colors:\n    ansi:\n      - white\n      - yellow\n      - green\n    chip: \"#6E4C13\"\n  icon: '\\u{E6AB}'\nAts:\n  type: programming\n  ascii: |\n    {0}              ############\n    {0}          ####################\n    {0}       ##########################\n    {0}     ##############################\n    {0}   #####{1}LLLL{0}#####{2}atsatsatsatsatsatsa{0}#\n    {0}  ####{1}LLLLLLL{0}####{2}TSATSATSATSATSATSAT{0}##\n    {0} ####{1}LLL{0}##{1}LLL{0}############{2}SAT{0}###########\n    {0} ###{1}LLL{0}####{1}LLL{0}###{2}satsatsaTSAtsatsats{0}###\n    {0}###########{1}LLLL{0}##{2}ATSATSATSATSATSATSA{0}####\n    {0}##########{1}LLLLL{0}##{2}TS{0}######{2}ATS{0}######{2}AT{0}####\n    {0}#########{1}LLLLLL{0}##{2}SA{0}######{2}TSA{0}######{2}TS{0}####\n    {0} #######{1}LLL{0}##{1}LLL{0}#{2}ATsatsatSATsatsatSA{0}###\n    {0} ######{1}LLL{0}###{1}LLL{0}#{2}TSATSATSATSATSATSAT{0}###\n    {0}  ####{1}LLL{0}####{1}LLL{0}######################\n    {0}   ##{1}LLL{0}######{1}LLLLLLLLLLLLLLLLLLL{0}####\n    {0}     {1}LLL{0}#######{1}LLLLLLLLLLLLLLLLLL{0}##\n    {0}       ##########################\n    {0}          ####################\n    {0}              ############\n  colors:\n    ansi:\n      - yellow\n      - red\n      - blue\n    hex:\n      - \"#FFFF00\"\n      - \"#FF0000\"\n      - \"#0000FF\"\n    chip: \"#1AC620\"\nAutoHotKey:\n  type: programming\n  ascii: |\n    {1} .----------------.\n    {1}| .--------------. |\n    {1}| | {0} ____  ____ {1} | |\n    {1}| | {0}|_   ||   _|{1} | |\n    {1}| | {0}  | |__| |  {1} | |\n    {1}| | {0}  |  __  |  {1} | |\n    {1}| | {0} _| |  | |_ {1} | |\n    {1}| | {0}|____||____|{1} | |\n    {1}| |              | |\n    {1}| '--------------' |\n    {1} '----------------'\n  colors:\n    ansi:\n      - white\n      - green\n    hex:\n      - \"#FFFFFF\"\n      - \"#119810\"\n    chip: \"#6594B9\"\nBash:\n  type: programming\n  ascii: |\n    {0}             _._\n    {0}         _.-'   '-._\n    {0}     _.-'           '-._\n    {0} _.-'                   '-._\n    {0}|                        _,-|\n    {0}|                    _,-'+++|\n    {0}|                _,-'+++++++|\n    {0}|             ,-'+++++++++++|\n    {0}|             |++++ ++++++++|\n    {0}|             |+++   +++++++|\n    {0}|             |++  +++++++++|\n    {0}|             |++++  +++{1}**{0}++|\n    {0}|             |++   ++{1}**{0}++++|\n    {0}'-,_          |+++ ++++++_,-'\n    {0}    '-,_      |++++++_,-'\n    {0}        '-,_  |++_,-'\n    {0}            '-|-'\n  colors:\n    ansi:\n      - white\n      - green\n    chip: \"#89E051\"\n  icon: '\\u{EBCA}'\nC:\n  type: programming\n  ascii: |\n    {0}                 ++++++\n    {0}              ++++++++++++\n    {0}          ++++++++++++++++++++\n    {0}       ++++++++++++++++++++++++++\n    {0}    ++++++++++++++++++++++++++++++++\n    {0} +++++++++++++{3}************{0}+++++++++++++\n    {0}+++++++++++{3}******************{0}++++++++{2};;;\n    {0}+++++++++{3}**********************{0}++{2};;;;;;;\n    {0}++++++++{3}*********{0}++++++{3}******{2};;;;;;;;;;;\n    {0}+++++++{3}********{0}++++++++++{3}**{2};;;;;;;;;;;;;\n    {0}+++++++{3}*******{0}+++++++++{2};;;;;;;;;;;;;;;;;\n    {0}+++++++{3}******{0}+++++++{2};;;;;;;;;;;;;;;;;;;;\n    {0}+++++++{3}*******{0}+++{1}:::::{2};;;;;;;;;;;;;;;;;;\n    {0}+++++++{3}********{1}::::::::::{3}**{2};;;;;;;;;;;;;\n    {0}++++++++{3}*********{1}::::::{3}******{2};;;;;;;;;;;\n    {0}++++++{1}:::{3}**********************{1}::{2};;;;;;;\n    {0}+++{1}::::::::{3}******************{1}::::::::{2};;;\n    {1} :::::::::::::{3}************{1}:::::::::::::\n    {1}    ::::::::::::::::::::::::::::::::\n    {1}       ::::::::::::::::::::::::::\n    {1}          ::::::::::::::::::::\n    {1}              ::::::::::::\n    {1}                 ::::::\n  colors:\n    ansi:\n      - cyan\n      - blue\n      - blue\n      - white\n    hex:\n      - \"#649AD2\"\n      - \"#004283\"\n      - \"#00599D\"\n      - \"#FFFFFF\"\n    chip: \"#555555\"\n  icon: '\\u{E61E}'\nCeylon:\n  type: programming\n  ascii: |\n    {1}                                 @@\n    {1}                                 @@\n    {3}          @@@@@@@{1}@@@@@@@@@@@@@@   @@\n    {0}    @@@{3}@@@@@@@@@@{1}@@@@@@ /@@@@@@@@  @@@\n    {0}  @@@@@@{3}@@@@@@@@@@{1}@@@@ (@@@@ @@@@@  @@{2}@@\n    {0} @@@@@@@@{3}@@@@@@@@@@@{1}@@@ \\@@@@@@@@@@@@{2}@@@\n    {0}@@@@@@@@@@@{3}@@@@@@@@@@@{1}@@@@@@@@@@@@@{2}@@@@\n    {0}|/ @@@@@@@@@@{3}@@@@@@@@@@@@{1}@\n    {0}   @@@@@@@@@@@@{3}@@@@@@@@@@\n    {0}   @@@@@@@@@@@@@@{3}@@@@@@@ {4}@@\n    {0}    @@@@@@@     @@@@@@@ {4}@@@\n    {0}    @@@@@@ {4}@@    {0}@@@@@ {4}@@@@\n    {0}     @@@@ {4}@@@    {0}@@@@@ {4}@@@@@\n  colors:\n    ansi:\n      - yellow\n      - yellow\n      - yellow\n      - yellow\n      - yellow\n    hex:\n      - \"#C27E10\"\n      - \"#DDA12E\"\n      - \"#D1911F\"\n      - \"#CC8B18\"\n      - \"#AB7008\"\n    chip: \"#DFA535\"\n  icon: '\\u{E78B}'\nClojure:\n  type: programming\n  ascii: |\n    {0}                 ,,,,,\n    {0}           .-/+ooooooooo+/:-`\n    {0}        ./ooooooooooooooooooo+:.\n    {0}      -+oooooooooooooooooooooooo+-\n    {0}    ,+ooooooooo+/:---::/+ooooooooo+.\n    {0}                        `-/oosoooooo:\n    {1}   .,,oooo/'      {0}:\\\\\\\\\\,  `\\oooooooo:\n    {1}  :,ooooo-  :///:  {0}:\\\\\\\\\\\\,  -oooooooo-\n    {1} :oooooo:  ://///:  {0}:\\\\\\\\\\\\\\,  :oooooo+\n    {1}.ooooooo  :///////:  {0}:\\\\\\\\\\\\\\,  ooooooo.\n    {1}:oooooo+  ://////:    {0}:\\\\\\\\\\\\,  +oooooo:\n    {1}-ooooooo`  :////:  ::  {0}:\\\\\\\\\\, 'ooooooo-\n    {1}`ooooooo:   ://:  ://:  {0}:\\\\\\,  /oooooo:\n    {1} -ooooooo:   ::  :////:  {0}:\\:  :ooooo,:\n    {1}  :ooooooo+.    ://////:    {0}.+oooo,,.\n    {1}   :oooooooo+-`\n    {1}    .+ooooooooo+/::::://oooooooooo+'\n    {1}      -+oooooooooooooooooooooooo+-\n    {1}        .:ooooooooooooooooooo+:.\n    {1}           `-:/ooooooooo+/:.`\n    {1}                 ``````\n  colors:\n    ansi:\n      - cyan\n      - green\n    hex:\n      - \"#8BADF6\"\n      - \"#8DD545\"\n    chip: \"#DB5855\"\n  icon: '\\u{E768}'\nCMake:\n  type: programming\n  ascii: |\n    {0}            ;e{2}`\n    {0}           ;QD{2}?`\n    {0}          ;B#R{2}1r`\n    {0}         ;WRMK{2}S|r`\n    {0}        :O9KOK{2}S\\\\*`\n    {0}       :keXPk6{2}Zc7v|`\n    {0}      :ajyoaZe{3}m{2}JJ{]\\`\n    {0}     :]z1x}f9{3}@@{2}yujSoc`\n    {0}    \"7\\/LvU{3}Q@@@{2}XoZemXv`\n    {0}   ,\\**v{1}oo{3}qMBBB{2}kmXEkU9z`\n    {0}  ,\\*}{1}Sx\\||?|cFf{2}okqOdHDx`\n    {0} ,L]{1}jc\\\\\\||?*>rr^^|zo{2}$MN]`\n    {0}-v{1}7????*>>rrr^^^;;;;;;^\\{2}F^`\n  colors:\n    ansi:\n      - blue\n      - green\n      - red\n      - black\n    chip: \"#DA3434\"\n  icon: '\\u{E794}'\nCoffeeScript:\n  type: programming\n  ascii: |\n    {0}           #####    ######\n    {0}          ###    ###    ###\n    {0}  /A\\      ######    #####      /A\\\n    {0}  \\AAAAA\\                   /AAAAA/\n    {0}       \\AAAAAAAAAAAAAAAAAAAAA/\n    {0}  \\AA\\                         /AA/\n    {0}   |\\AAAAAAAAAAAAAAAAAAAAAAAAAAA/|\n    {0}  /\\\\llAAAAAAAAAAAAAAAAAAAAAAAll//\n    {0} /#A\\\\llAAAAAAAAAAAAAAAAAAAAAll//\n    {0}|#A   \\\\llAAAAAAAAAAAAAAAAAAll//\n    {0} \\#A    \\\\llAAAAAAAAAAAAAAll//\n    {0}   \\#A   \\\\llAAAAAAAAAAAAll//\n    {0}     \\#A>  ||lAAAAAAAAAAl||\n    {0}            \\;AAAAAAAAAA;/\n  colors:\n    ansi:\n      - red\n    chip: \"#244776\"\n  icon: '\\u{E751}'\nColdFusion:\n  type: programming\n  ascii: |\n    {0}CfCfCfCfCfCfCfCfCfCfCfCfCfCfCfCfCfCf\n    {0}Cf{1}@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@{0}Cf\n    {0}Cf{1}@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@{0}Cf\n    {0}Cf{1}@@@@@@@@@@@@@@@@@@@@@@{0}CfCfCfCf{1}@@{0}Cf\n    {0}Cf{1}@@@@@{0}CfCfCfCfCf{1}@@@@{0}CfCfCfCfCf{1}@@@{0}Cf\n    {0}Cf{1}@@@@{0}CfCfCfCfCf{1}@@@@{0}CfCf{1}@@@@@@@@@@{0}Cf\n    {0}Cf{1}@@@{0}CfCf{1}@@@@@@@@@@{0}CfCf{1}@@@@@@@@@@@{0}Cf\n    {0}Cf{1}@@{0}CfCf{1}@@@@@@@@{0}CfCfCfCfCfCf{1}@@@@@@{0}Cf\n    {0}Cf{1}@@{0}CfCf{1}@@@@@@@@@@@{0}CfCf{1}@@@@@@@@@@@{0}Cf\n    {0}Cf{1}@@{0}CfCf{1}@@@@@@@@@@@{0}CfCf{1}@@@@@@@@@@@{0}Cf\n    {0}Cf{1}@@@{0}CfCf{1}@@@@@@@@@@{0}CfCf{1}@@@@@@@@@@@{0}Cf\n    {0}Cf{1}@@@@{0}CfCfCfCfCf{1}@@@{0}CfCf{1}@@@@@@@@@@@{0}Cf\n    {0}Cf{1}@@@@@{0}CfCfCfCfCf{1}@@{0}CfCf{1}@@@@@@@@@@@{0}Cf\n    {0}Cf{1}@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@{0}Cf\n    {0}Cf{1}@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@{0}Cf\n    {0}CfCfCfCfCfCfCfCfCfCfCfCfCfCfCfCfCfCf\n  colors:\n    ansi:\n      - white\n      - blue\n    hex:\n      - \"#E5F3FC\"\n      - \"#274550\"\n    chip: \"#ed2cd6\"\n  icon: '\\u{E645}'\nCoq:\n  type: programming\n  ascii: |\n    {0}   ::::::::\n    {0} :::::::::::\n    {0} :::::::::::\n    {0} :::::{1}___{0}:::\n    {1}   ______ {0}:\n    {1}     ____   _________\n    {1}      ___  ___________\n    {1}      ___ ____________\n    {1}     _________________\n    {1}   ___________________\n    {1}______________ ______\n    {1}   ___________ ___\n    {1}      ______\n    {1}       ____\n    {1}     ______\n    {1}    ________\n    {1}    ________\n  colors:\n    ansi:\n      - yellow\n      - white\n    hex:\n      - \"#BF8C5E\"\n      - \"#D5BE99\"\n    chip: \"#D0B68C\"\nCpp:\n  type: programming\n  ascii: |\n    {0}                 ++++++\n    {0}              ++++++++++++\n    {0}          ++++++++++++++++++++\n    {0}       ++++++++++++++++++++++++++\n    {0}    ++++++++++++++++++++++++++++++++\n    {0} +++++++++++++{3}************{0}+++++++++++++\n    {0}+++++++++++{3}******************{0}++++++++{2};;;\n    {0}+++++++++{3}**********************{0}++{2};;;;;;;\n    {0}++++++++{3}*********{0}++++++{3}******{2};;;;;;;;;;;\n    {0}+++++++{3}********{0}++++++++++{3}**{2};;;;;;;;;;;;;\n    {0}+++++++{3}*******{0}+++++++++{2};;;;;;{3}**{2};;;;{3}**{2};;;\n    {0}+++++++{3}******{0}+++++++{2};;;;;;;;{3}****{2};;{3}****{2};;\n    {0}+++++++{3}*******{0}+++{1}:::::{2};;;;;;;{3}**{2};;;;{3}**{2};;;\n    {0}+++++++{3}********{1}::::::::::{3}**{2};;;;;;;;;;;;;\n    {0}++++++++{3}*********{1}::::::{3}******{2};;;;;;;;;;;\n    {0}++++++{1}:::{3}**********************{1}::{2};;;;;;;\n    {0}+++{1}::::::::{3}******************{1}::::::::{2};;;\n    {1} :::::::::::::{3}************{1}:::::::::::::\n    {1}    ::::::::::::::::::::::::::::::::\n    {1}       ::::::::::::::::::::::::::\n    {1}          ::::::::::::::::::::\n    {1}              ::::::::::::\n    {1}                 ::::::\n  colors:\n    ansi:\n      - cyan\n      - blue\n      - blue\n      - white\n    hex:\n      - \"#649AD2\"\n      - \"#004283\"\n      - \"#00599D\"\n      - \"#FFFFFF\"\n    chip: \"#F34B7D\"\n  icon: '\\u{E61D}'\n  serialization: c++\nCrystal:\n  type: programming\n  ascii: |\n    {0}            ,loc;'..\n    {0}          ,xNMMMWNXK0kdl,..\n    {0}        ,xNMMMMMMMMMMMMMKOxoc;.\n    {0}      ,xNMMMMMMMMMMMMMMMMMMMMWX:\n    {0}    ,xNMMMMMMMMMMWNNWMMMMMMMMMMk.\n    {0}  ,xNMMMMMWX0kdlc;:lOMMMMMMMMMMNc\n    {0},xXK{1}..mmmMMMMMMMM'{0}0MMMMMMMMMMMMMO.\n    {0}0o;{1}MMMMMMMMMMMMMm{0}.MMMMMMMMMMMMMMWl\n    {0}0O:.{1}MMMMMMMMMMMM'{0}cMMMMMMMMMMMMMMM0'\n    {0}oWWO:.{1}MMMMMMMMMm{0}.OMMMMMMMMMMMMMMMWo\n    {0}'0MMWO:.{1}MMMMMMM'{0}lWMMMMMMMMMMMMMMMMK,\n    {0} lWMMMWO:.{1}MMMMm{0}'0MMMMMMMMMMMMMMMMMWd\n    {0} .OMMMMMW0c.{1}MM'{0}oWMMMMMMMMMMMMMMMMMWk.\n    {0}  cNMMMMMMW0c{1}'{0},KMMMMMMMMMMMMMMMMW0c.\n    {0}  .kMMMMMMMMW00WMMMMMMMMMMMMMMW0c.\n    {0}   cNMMMMMMMMMMMMMMMMMMMMMMMW0c.\n    {0}   .xWMMMMMMMMMMMMMMMMMMMMW0:.\n    {0}    .';coxOKNWMMMMMMMMMMWO:.\n    {0}           ..,:ldk0KXWMM:.\n    {0}               ...';c:\n  colors:\n    ansi:\n      - white\n      - black\n    chip: \"#000100\"\n  icon: '\\u{E62F}'\nCSharp:\n  type: programming\n  ascii: |\n    {0}                 ++++++\n    {0}              ++++++++++++\n    {0}          ++++++++++++++++++++\n    {0}       ++++++++++++++++++++++++++\n    {0}    ++++++++++++++++++++++++++++++++\n    {0} +++++++++++++{3}************{0}+++++++++++++\n    {0}+++++++++++{3}******************{0}++++++++{2};;;\n    {0}+++++++++{3}**********************{0}++{2};;;;;;;\n    {0}++++++++{3}*********{0}++++++{3}******{2};;;;;;;;;;;\n    {0}+++++++{3}********{0}++++++++++{3}**{2};;;{3}**{2};;;{3}**{2};;;\n    {0}+++++++{3}*******{0}+++++++++{2};;;;;;{3}*********{2};;\n    {0}+++++++{3}******{0}+++++++{2};;;;;;;;;;{3}**{2};;;{3}**{2};;;\n    {0}+++++++{3}*******{0}+++{1}:::::{2};;;;;;;{3}*********{2};;\n    {0}+++++++{3}********{1}::::::::::{3}**{2};;;{3}**{2};;;{3}**{2};;;\n    {0}++++++++{3}*********{1}::::::{3}******{2};;;;;;;;;;;\n    {0}++++++{1}:::{3}**********************{1}::{2};;;;;;;\n    {0}+++{1}::::::::{3}******************{1}::::::::{2};;;\n    {1} :::::::::::::{3}************{1}:::::::::::::\n    {1}    ::::::::::::::::::::::::::::::::\n    {1}       ::::::::::::::::::::::::::\n    {1}          ::::::::::::::::::::\n    {1}              ::::::::::::\n    {1}                 ::::::\n  colors:\n    ansi:\n      - blue\n      - magenta\n      - magenta\n      - white\n    hex:\n      - \"#9B4F97\"\n      - \"#67217A\"\n      - \"#803788\"\n      - \"#FFFFFF\"\n    chip: \"#178600\"\n  icon: '\\u{E648}'\n  serialization: c#\nCss:\n  type: markup\n  ascii: |\n    {1}        ####    ####    ####\n    {1}       ##  ##  ##      ##\n    {1}       ##       ####    ####\n    {1}       ##  ##      ##      ##\n    {1}        ####    ####    ####\n    {0}(((((((((((((((((((((((((((((((((((\n    {0}(((((((((((((((((/////////////(((((\n    {0}(((((((((((((((((/////////////(((((\n    {0}(((((((                    ///(((((\n    {0} ((((((                    ///((((\n    {0} ((((((((((((((((        /////((((\n    {0} (((((((((((        //////////((((\n    {0} (((((((                   ///((((\n    {0}  ((((((                   ///(((\n    {0}  (((((((((((((((//////    ///(((\n    {0}  (((((((    ((((//////    ///(((\n    {0}  (((((((                  ///(((\n    {0}   (((((((               /////((\n    {0}   ((((((((((((((/////////////((\n    {0}   ((((((((((((((//////(((((((((\n    {0}          (((((((((((((((\n  colors:\n    ansi:\n      - blue\n      - white\n    chip: \"#563D7C\"\n  icon: '\\u{E749}'\nCuda:\n  type: programming\n  ascii: |\n    {0}               88888888888888888888888\n    {0}           8888       8888888888888888\n    {0}       888888  8888       888888888888\n    {0}    88888      88888888     8888888888\n    {0}  8888     8888      8888     88888888\n    {0}8888    8888   888     8888    8888888\n    {0}8888   8888    8888   8888    88888888\n    {0} 8888   8888   888888888    88888  888\n    {0}  88888   888  8888888    88888      8\n    {0}    8888   8888         88888        8\n    {0}     88888         8888888        8888\n    {0}        888888 888888         88888888\n    {0}           8888          8888888888888\n    {0}               88888888888888888888888\n  colors:\n    ansi:\n      - green\n    hex:\n      - \"#76B900\"\n    chip: \"#73B303\"\nD:\n  type: programming\n  ascii: |\n    {0}                                    DDD\n    {0}DDDDDDDDDDDDDDDDDDDDDD             DDDDD\n    {0}DDDDDDDDDDDDDDDDDDDDDDDD            DDD\n    {0}DDDDDDDDDDDDDDDDDDDDDDDDDD     DDDDD\n    {0}DDDDDD               DDDDDDD DDDDDDDDD\n    {0}DDDDDD                DDDDDDDDDDDDDDDDD\n    {0}DDDDDD                 DDDDDDDDDDDDDDDD\n    {0}DDDDDD                 DDDDDDDDDDDDDD\n    {0}DDDDDD                 DDDDDDDDDDD\n    {0}DDDDDD                 DDDDDDD\n    {0}DDDDDD                DDDDDDD\n    {0}DDDDDD               DDDDDDD\n    {0}DDDDDDDDDDDDDDDDDDDDDDDDDDD\n    {0}DDDDDDDDDDDDDDDDDDDDDDDDD\n    {0}DDDDDDDDDDDDDDDDDDDDDD\n  colors:\n    ansi:\n      - red\n    chip: \"#BA595E\"\n  icon: '\\u{E7AF}'\nDart:\n  type: programming\n  ascii: |\n    {0}#\n    {0} ##\n    {0}  ###\n    {0}   ######              ###\n    {0}    #########        #######\n    {0}      ###########  ######{2}O{0}##{2}========-\n    {0}       #####################\n    {0}         ##################\n    {0}      ###############{1}+++++\n    {0}###################{1}+++++++\n    {0}        ##########{1}+++++++\n    {0}               ##{1}+++++++\n    {0}               ###{1}+++\n    {0}               #####\n    {0}               #######\n    {0}               #########\n    {0}                #######\n    {0}                 #####\n  colors:\n    ansi:\n      - blue\n      - cyan\n      - blue\n    hex:\n      - \"#00A3E7\"\n      - \"#42DFCD\"\n      - \"#01597D\"\n    chip: \"#00B4AB\"\n  icon: '\\u{E64C}'\nDockerfile:\n  type: programming\n  ascii: |\n    {2}                  ##   {0}      .\n    {2}            ## ## ##   {0}     ==\n    {2}         ## ## ## ## ##{0}    ===\n    {0}     /\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\\___/ ===\n    {1}~~~ {0}{{1}~~ ~~~~ ~~~ ~~~~ ~~~ ~ {0}/  ===-{1} ~~~\n    {0}     \\{1}______ o{0}           __/\n    {1}       \\    \\ {0}        __/\n    {1}        \\____\\{0}_______/\n  colors:\n    ansi:\n      - cyan\n      - white\n      - cyan\n    chip: \"#384D54\"\n  icon: '\\u{F308}'\nElisp:\n  type: programming\n  ascii: |\n    {0}         ':r\\iv7i|r:'\n    {0}      :LFaZZZaaaoooo2t\\:\n    {0}    ^]aZZZZaaw9DN{1}Q@Q{0}gojjv;\n    {0}  ,vaZZZaX69KOHRW#{1}@@@{0}Ouuuu/,\n    {0} _[ZZaaa{1}Q@@@@QBNMRD{0}dEuuufFFl,\n    {0}.7aaaooo{1}wB@@#{0}h2jjuuuufFF]]]]|-\n    {0}:yooooSSS2S${1}gQ@Q{0}8hjfFF]]]][tz\"\n    {0};oooSS2ed#{1}Q@@@@@Q{0}N{0}Do]]][[ttt[:\n    {0}:uSS2mQ{1}@@@@Q{0}deuF]]]][[ttt[[[z~\n    {0}-/yjjO{1}@@@@Q{0}uFF]]][[tttt[[]]]?`\n    {0} ,vuuue&{1}Q@@@QQ#NNggg{0}&D9u[]i'\n    {0}  '/ffFF]]jek99OR{1}#Q@@Q{0}Hj]]|'\n    {0}   `;c]]][uaXUKO$wo]]]]L:`\n    {0}     `,>i1tt[[]]]]ti>,`\n    {0}        `.~;^>??>^;,-`\n  colors:\n    ansi:\n      - magenta\n      - white\n    chip: \"#C065DB\"\n  icon: '\\u{E632}'\n  serialization: emacs-lisp\nElixir:\n  type: programming\n  ascii: |\n    {0}            x\n    {0}           WNX\n    {0}          Odc:x\n    {0}        0ddko,oX\n    {0}       kokNWOllOW\n    {0}     KdoKWMMNKxl0W\n    {0}    0odXMMMMMMNxoON\n    {0}   0lxNMMMMMMMMW0dd0N\n    {0}  0oxNMMMMMMMMMMMNOodKW\n    {0} xodXMMMMMMMMMMMMMMXxokN\n    {0}xol0MMMMMMMMMMMMMMMMW0odX\n    {0}xoxWMMMMMMMMMMMMMMMMMMKodN\n    {0}0lOMMMMMMMMMMMMMMMMMMMWOlO\n    {0}OlOMWKXMMMMMMMMMMMMMMMMKlxW\n    {0}KlxWXodNMMMMMMMMMMMMMMM0lkW\n    {0}xxoKWOlkNMMMMMMMMMMMMMWkl0\n    {0} XooKN0ddkKNWWWMMMMMMWOlkW\n    {0}  XxokXN0kxxkkKMMMMN0doON\n    {0}   WKxdxk0KKKKXK0OxddkXW\n    {0}     WNKOxxxxxxxxkOXW\n    {0}         WWWWWWW\n  colors:\n    ansi:\n      - magenta\n    chip: \"#6E4A7E\"\n  icon: '\\u{E62D}'\nElm:\n  type: programming\n  ascii: |\n    {0}   {1}ElmElmElmElm   {3}ElmElmElmElmElm\n    {0}El   {1}mElmElmElmEl   {3}mElmElmElmElm\n    {0}Elm   {1}ElmElmElmElmE   {3}lmElmElmElm\n    {0}ElmEl   {1}mElmElmElmElm   {3}ElmElmElm\n    {0}ElmElmE                   {3}mElmElm\n    {0}ElmElmElm   {2}ElmElmElmEl     {3}lmElm\n    {0}ElmElmElmEl   {2}mElmElm   {1}Elm   {3}Elm\n    {0}ElmElmElmElmE   {2}lmE   {1}lmElmEl   {3}m\n    {0}ElmElmElmElmElm     {1}mElmElmElmE\n    {0}ElmElmElmElmElmE    {1}mElmElmElm\n    {0}ElmElmElmElmEl   {3}mE   {1}lmElmE   {2}lm\n    {0}ElmElmElmElm   {3}ElmElm   {1}El   {2}mElm\n    {0}ElmElmElmE   {3}lmElmElmEl    {2}ElmElm\n    {0}ElmElmEl   {3}mElmElmElmElmE   {2}lmElm\n    {0}ElmElm   {3}ElmElmElmElmElmElm   {2}Elm\n    {0}ElmE   {3}lmElmElmElmElmElmElmEl   {2}m\n    {0}El   {3}mElmElmElmElmElmElmElmElmE\n    {0}   {3}ElmElmElmElmElmElmElmElmElmElm\n  colors:\n    ansi:\n      - blue\n      - green\n      - yellow\n      - cyan\n    chip: \"#60B5CC\"\n  icon: '\\u{E62C}'\nEmojicode:\n  type: programming\n  ascii: |\n    {0}~~\n    {0} ~~\n    {0}  ~~\n    {0}   ~~{2}           ''''''''\n    {0}    ~~~{2}       ''''''''''''\n    {0}     {1}````````{2}''''''''''''''\n    {1}    `````````{2}''''''''''''''\n    {1}   `````````{2}'''''''''''''{3}~~~~~~~-\n    {1}   `````````{2}'''''''''''{3}~~~~~~~~~~~\n    {1}   ``````````{2}''{1}````````{3}~~~~~~~~~~~~\n    {1}   ``````````````````````{3}~~~~~~~~~~~\n    {1}   ```````````````````````{3}~~~~~~~~~~\n    {1}    ``{3}~~~~~{1}```````````````{3}~~~~~~~~~~\n    {3}   -~~~~~~~{1}```````````````{3}~~~~~~~~~\n    {3}  ~~~~~~~~~{1}```````````````````````\n    {3}  ~~~~~~~~~~{1}````````````````````````\n    {3} ~~~~~~~~~~~~{1}`````````````````````````\n    {3}  ~~~~~~~~~~~~~~{1}``````{2}````````````````\n    {3}  ~~~~~~~~~~~~~~{2}'''''''{1}```````````````\n    {3}   ~~~~~~~~~~~~{2}''''''''{1}```````````````\n    {3}     ~~~~~~~~{2}'''''''''''{1}``````````````\n    {2}              {2}'''''''''''{1}````````````\n    {2}                {2}''''''''   {1}````````\n  colors:\n    ansi:\n      - green\n      - magenta\n      - magenta\n      - magenta\n    hex:\n      - \"#77B255\"\n      - \"#9266CC\"\n      - \"#AA8DD8\"\n      - \"#744EAA\"\n    chip: \"#60B5CC\"\n  icon: '\\u{f1044}'\nErlang:\n  type: programming\n  ascii: |\n    {0}   EEEEEEEEEEEEE      EEEEEEEEEEEE\n    {0}  EEEEEEEEEEEE         EEEEEEEEEEEE\n    {0} EEEEEEEEEEEE           EEEEEEEEEEE\n    {0} EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n    {0}EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n    {0}EEEEEEEEEEEEE\n    {0}EEEEEEEEEEEEE\n    {0}EEEEEEEEEEEEE\n    {0} EEEEEEEEEEEEE                 E\n    {0} EEEEEEEEEEEEE                EEEEE\n    {0}  EEEEEEEEEEEEE             EEEEEEEEE\n    {0}   EEEEEEEEEEEEEE         EEEEEEEEEE\n  colors:\n    ansi:\n      - red\n    chip: \"#B83998\"\n  icon: '\\u{E7B1}'\nFish:\n  type: programming\n  ascii: |\n    {0}                 ___\n    {0}  ___======____=-{1}-{0}-=)\n    {0}/T            \\_{1}--={0}==)\n    {0}[ \\ ({1}0{0})   \\~    \\_{1}-={0}=)\n    {0} \\      / )J~~    \\{1}-={0})\n    {0}  \\\\___/  )JJ~{1}~~{0}   \\)\n    {0}   \\_____/JJJ~~{1}~~{0}    \\\n    {0}   / \\  {1}, \\{0}J~~~{1}~~{0}     \\\n    {0}  (-{1}\\){0}\\=|{1}\\\\\\{0}~~{1}~~{0}       L_{1}_\n    {0}  (\\\\)  ({1}\\{0}\\\\)_           {1}\\==__\n    {0}   \\V    \\\\\\) ===_____   {1}\\\\\\\\{0}\\\\\n    {0}          \\V)     \\_) \\\\{1}\\\\JJ\\{0}J\\)\n    {0}                      /J{1}\\J{0}T\\JJJJ)\n    {0}                      (JJJ| \\UUU)\n    {0}                       (UU)\n  colors:\n    ansi:\n      - red\n      - yellow\n    chip: \"#4AAE47\"\n  icon: '\\u{EE41}'\nForth:\n  type: programming\n  ascii: |\n    {0}::::::::::::::::::::::::::::::::::::::::\n    {0}::::::::::::::::::::::::::::::::::::::::\n    {0}::::::::::::::::::::::::::::::::::::::::\n    {0}::::::::::::::::::::::::::::::::::::::::\n    {0}::::::          ::::::::          ::::::\n    {0}::::::          ::::::::          ::::::\n    {0}::::::          ::::::::          ::::::\n    {0}::::::          ::::::::          ::::::\n    {0}::::::::::::::::::::::::::::::::::::::::\n    {0}::::::::::::::::::::::::::::::::::::::::\n    {0}::::::::::::::::::::::::::::::::::::::::\n    {0}::::::          ::::::::          ::::::\n    {0}::::::          ::::::::          ::::::\n    {0}::::::          ::::::::          ::::::\n    {0}::::::          ::::::::         :::::::\n    {0}::::::::::::::::::::::::::     :::::::::\n    {0}::::::::::::::::::::::::     :::::::::::\n    {0}::::::::::::::::::::::::::::::::::::::::\n    {0}::::::::::::::::::::::::::::::::::::::::\n    {0}::::::::::::::::::::::::::::::::::::::::\n  colors:\n    ansi:\n      - red\n    chip: \"#341708\"\n  icon: '\\u{229E}'\nFortranLegacy:\n  type: programming\n  ascii: |\n    {4}    _ {1}__ __\n    {4}  _|_ {1} /  /\n    {0}  o{4}|{1}  /  /\n    {0}      /\\\n    {0}     /  \\\n    {0}    |    |\n    {0}    |{2}NASA{0}|\n    {0}    |    |\n    {0}    |    |\n    {0}    |    |\n    {0}   '      '\n    {0}   |      |\n    {0}   |      |\n    {0}   |______|\n    {3}   /-`'-`.\\\n    {3}  ; / . \\'\\.\n    {3} '/''( .'\\.''\n    {3}'.'.;.;' ;'.;'\n  colors:\n    ansi:\n      - white\n      - green\n      - cyan\n      - yellow\n      - red\n    chip: \"#4D41B1\"\n  icon: '\\u{f121a}'\n  serialization: fortran\nFortranModern:\n  type: programming\n  ascii: |\n    {4}    _{1} _  _\n    {4}  _|_{1}(_|/ \\\n    {0}  o{4}| {1} _|\\_/\n    {0}      /\\\n    {0}     /  \\\n    {0}    |    |\n    {0}    |{2}NASA{0}|\n    {0}    |    |\n    {0}    |    |\n    {0}    |    |\n    {0}   '      '\n    {0}   |      |\n    {0}   |      |\n    {0}   |______|\n    {3}   /-`'-`.\\\n    {3}  ; / . \\'\\.\n    {3} '/''( .'\\.''\n    {3}'.'.;.;' ;'.;'\n  colors:\n    ansi:\n      - white\n      - green\n      - cyan\n      - yellow\n      - red\n    chip: \"#4D41B1\"\n  icon: '\\u{f121a}'\nFSharp:\n  type: programming\n  ascii: |\n    {0}                 /  {1}((\n    {0}               ///  {1}((((\n    {0}             /////  {1}((((((\n    {0}           ///////  {1}((((((((\n    {0}         /////////  {1}((((((((((\n    {0}       //////////   {1}  ((((((((((\n    {0}     //////////  /  {1}    ((((((((((\n    {0}   //////////  ///  {1}      ((((((((((\n    {0} //////////  /////  {1}        ((((((((((\n    {0}/////////   //////  {1}         ((((((((((\n    {0}  /////////   ////  {1}       ((((((((((\n    {0}    /////////   //  {1}     ((((((((((\n    {0}      /////////     {1}   ((((((((((\n    {0}        /////////   {1} ((((((((((\n    {0}          ////////  {1}(((((((((\n    {0}            //////  {1}(((((((\n    {0}              ////  {1}(((((\n    {0}                //  {1}(((\n  colors:\n    ansi:\n      - cyan\n      - cyan\n    chip: \"#B845FC\"\n  icon: '\\u{E7A7}'\n  serialization: f#\nGdScript:\n  type: programming\n  ascii: |\n    {0}         _.aMb      dMe._\n    {0}        'H8888b,  ,d8888H'\n    {0}   .   .:88888d8888888888:.   .\n    {0} .d8b.dM888888888888888888Mb.d8b.\n    {0}d88888888888888888888888888888888b\n    {0}'V888888888888888888888888888888V'\n    {0} 88888888888888888888888888888888\n    {0} 8888P' {1}__{0} \"V88888888V\" {1}__{0} 'V8888\n    {0} 8888\" {1}dMMb {0}'888{1}''{0}888' {1}d88b {0}\"8888\n    {0} 8888b {1}:HH: {0}/888{1}  {0}888\\ {1}:HH: {0}d8888\n    {0} 8888be._.ad8888{1}..{0}8888be._.ad8888\n    {0} WW8888888888888888888888888888WW\n    {0} {1}#######{0}YW88/{1}########{0}\\88WY{1}#######\n    {0} MWbzxe{1}##{0}8MW;{1}##{0}8888{1}##{0};8MW{1}##{0}aezdWM\n    {0} 'Y8888b.{1}#####{0}/8888\\{1}#####{0}.d8888Y'\n    {0}  \"V8888888888888888888888888V\"\n    {0}   '^YV8888888888888888888VP^'\n    {0}      '\"^^VY888888888VY^^'\n  colors:\n    ansi:\n      - cyan\n      - white\n    hex:\n      - \"#458DC0\"\n      - \"#FFFFFF\"\n    chip: \"#355570\"\n  icon: '\\u{E65F}'\nGlsl:\n  type: programming\n  ascii: |\n    {0}         ,,@@@@@@@@@@@@@@@@@..\n    {0}       ,@@@@@@@@@@@@@@@@@@@@@@@.\n    {0}     ,@@@@@@@@@@@@@@@@@@@@@@@@@@@.\n    {0}   ,@@@@@@@@'              `@@@@@@@.\n    {0} ,@@@@@@@@'                    `@@@@.\n    {0},@@@@@@@'                         `@@.\n    {0}@@@@@@@' {1}_____  _        _____  _   {0}`@\n    {0}@@@@@@  {1}/:::::||:|      /:::::||:|\n    {0}@@@@@@ {1}|:|  __ |:|     |:(___  |:|\n    {0}@@@@@@ {1}|:| |::||:|      \\::::\\ |:|\n    {0}@@@@@@ {1}|:|__|:||:|____  ____):||:|____\n    {0}@@@@@@  {1}\\:::::||::::::||:::::/ |::::::|\n    {0}@@@@@@@.                            ,@\n    {0}`@@@@@@@.                         ,@@'\n    {0} `@@@@@@@@.                    ,@@@@'\n    {0}   `@@@@@@@@.              ,@@@@@@@'\n    {0}     `@@@@@@@@@@@@@@@@@@@@@@@@@@@'\n    {0}       `@@@@@@@@@@@@@@@@@@@@@@@'\n    {0}         ``@@@@@@@@@@@@@@@@@''\n  colors:\n    ansi:\n      - blue\n      - magenta\n    hex:\n      - \"#5487a6\"\n      - \"#bc258e\"\n    chip: \"#5686a5\"\nGo:\n  type: programming\n  ascii: |\n    {0}           --==============--\n    {0}  .-==-.===oooo=oooooo=ooooo===--===-\n    {0} .==  =o={1}oGGGGGG{0}o=oo=o{1}GGGGGGG{0}G=o=  oo-\n    {0} -o= oo={1}G .=GGGGG{0}o=o={1}= .=GGGGG{0}=ooo o=-\n    {0}  .-=oo={1}o==oGGGGG{0}=oo={1}oooGGGGGo{0}=oooo.\n    {0}   -ooooo{1}=oooooo{0}={2}.   .{0}={1}=ooo=={0}oooooo-\n    {0}   -ooooooooooo{2}====_===={0}ooooooooooo=\n    {0}   -oooooooooooo{2}=={1}#{0}.{1}#{2}=={0}ooooooooooooo\n    {0}   -ooooooooooooo={1}#{0}.{1}#{0}=oooooooooooooo\n    {0}   .oooooooooooooooooooooooooooooooo.\n    {0}    oooooooooooooooooooooooooooooooo.\n    {2}  ..{0}oooooooooooooooooooooooooooooooo{2}..\n    {2}-=o-{0}=ooooooooooooooooooooooooooooooo{2}-oo.\n    {2}.=- {0}oooooooooooooooooooooooooooooooo{2}-.-\n    {0}   .oooooooooooooooooooooooooooooooo-\n    {0}   -oooooooooooooooooooooooooooooooo-\n    {0}   -oooooooooooooooooooooooooooooooo-\n    {0}   -oooooooooooooooooooooooooooooooo-\n    {0}   .oooooooooooooooooooooooooooooooo\n    {0}    =oooooooooooooooooooooooooooooo-\n    {0}    .=oooooooooooooooooooooooooooo-\n    {0}      -=oooooooooooooooooooooooo=.\n    {2}     =oo{0}====oooooooooooooooo==-{2}oo=-\n    {2}    .-==-    {0}.--=======---     {2}.==-\n  colors:\n    ansi:\n      - cyan\n      - white\n      - yellow\n    hex:\n      - \"#74CDDD\"\n      - \"#FFFFFF\"\n      - \"#F6D2A2\"\n    chip: \"#00ADD8\"\n  icon: '\\u{E627}'\nGraphql:\n  type: data\n  ascii: |\n    {0}                 {}{}{}\n    {0}                {}{}{}{}\n    {0}                {}{}{}{}\n    {0}            {}   {}{}{}   {}\n    {0} {}{}{}  {}    {}      {}    {}  {}{}{}\n    {0}{}{}{}{}      {}        {}      {}{}{}{}\n    {0}{}{}{}{}     {}          {}     {}{}{}{}\n    {0} {}{}{}     {}            {}     {}{}{}\n    {0}   {}      {}              {}      {}\n    {0}   {}     {}                {}     {}\n    {0}   {}    {}                  {}    {}\n    {0}   {}   {}                    {}   {}\n    {0}   {}  {}                      {}  {}\n    {0} {}{}{}                          {}{}{}\n    {0}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}\n    {0}{}{}{}{}                        {}{}{}{}\n    {0} {}{}{}  {}                  {}  {}{}{}\n    {0}            {}   {}{}{}   {}\n    {0}                {}{}{}{}\n    {0}                {}{}{}{}\n    {0}                 {}{}{}\n  colors:\n    ansi:\n      - magenta\n    chip: \"#E10098\"\n  icon: '\\u{e662}'\nGroovy:\n  type: programming\n  ascii: |\n    {0}                   *\n    {0}                  ***\n    {0}                 *****\n    {0}                *******\n    {0}               *********\n    {0}              ***********\n    {0}             *************\n    {0}*****************{1}@@@@@{0}*****************\n    {0}  *************{1}@{0}******{1}@{0}**************\n    {0}   ***********{1}@{0}**{1}@@{0}***{1}@{0}*************\n    {0}    *********{1}@{0}***{1}@@{0}**{1}@{0}**{1}@{0}**********\n    {0}      *******{1}@{0}****{1}@@{0}***{1}@@@{0}*******\n    {0}       *******{1}@{0}******{1}@@{0}**{1}@@{0}*****\n    {0}        *******{1}@@@@@@{0}**{1}@@@@{0}****\n    {0}         ************{1}@@@@@@{0}***\n    {0}         ******{1}@@@@@@@@@@@{0}****\n    {0}        ********{1}@@@@@@@@{0}*******\n    {0}       **********{1}@@{0}*************\n    {0}      ***************************\n    {0}     ***********       ***********\n    {0}    *********             *********\n    {0}   *****                       *****\n    {0}  *                                 *\n  colors:\n    ansi:\n      - cyan\n      - white\n    chip: \"#4298B8\"\n  icon: '\\u{E775}'\nHaskell:\n  type: programming\n  ascii: |\n    {0}yyyyyy{1} xxxxxx\n    {0} yyyyyy{1} xxxxxx\n    {0}  yyyyyy{1} xxxxxx\n    {0}   yyyyyy{1} xxxxxx\n    {0}    yyyyyy{1} xxxxxx{2} yyyyyyyyyy\n    {0}     yyyyyy{1} xxxxxx{2} yyyyyyyyy\n    {0}      yyyyyy{1} xxxxxx\n    {0}     yyyyyy{1} xxxxxxxx{2} yyyyyyy\n    {0}    yyyyyy{1} xxxxxxxxxx{2} yyyyyy\n    {0}   yyyyyy{1} xxxxxxxxxxxx\n    {0}  yyyyyy{1} xxxxxx  xxxxxx\n    {0} yyyyyy{1} xxxxxx    xxxxxx\n    {0}yyyyyy{1} xxxxxx      xxxxxx\n  colors:\n    ansi:\n      - cyan\n      - magenta\n      - blue\n    hex:\n      - \"#453A62\"\n      - \"#5E5086\"\n      - \"#8F4E8B\"\n    chip: \"#5E5086\"\n  icon: '\\u{E777}'\nHaxe:\n  type: programming\n  ascii: |\n    {0}#############              {2}@@@@@@@@@@@@@\n    {0}#################      {2}@@@@@@@@@@@@@@@@@\n    {0}###################{1}XX{2}@@@@@@@@@@@@@@@@@@@\n    {0}##################{1}XXXX{2}@@@@@@@@@@@@@@@@@@\n    {0}################{1}XXXXXXXX{2}@@@@@@@@@@@@@@@@\n    {0}##############{1}XXXXXXXXXXXX{2}@@@@@@@@@@@@@@\n    {0} ###########{1}XXXXXXXXXXXXXXXX{2}@@@@@@@@@@@\n    {0}  ########{1}XXXXXXXXXXXXXXXXXXXX{2}@@@@@@@@\n    {0}   #####{1}XXXXXXXXXXXXXXXXXXXXXXXX{2}@@@@@\n    {0}    ##{1}XXXXXXXXXXXXXXXXXXXXXXXXXXXX{2}@@\n    {0}    ##{1}XXXXXXXXXXXXXXXXXXXXXXXXXXXX{2}@@\n    {0}   #####{1}XXXXXXXXXXXXXXXXXXXXXXXX{2}@@@@@\n    {0}  ########{1}XXXXXXXXXXXXXXXXXXXX{2}@@@@@@@@\n    {0} ###########{1}XXXXXXXXXXXXXXXX{2}@@@@@@@@@@@\n    {0}##############{1}XXXXXXXXXXXX{2}@@@@@@@@@@@@@@\n    {0}################{1}XXXXXXXX{2}@@@@@@@@@@@@@@@@\n    {0}##################{1}XXXX{2}@@@@@@@@@@@@@@@@@@\n    {0}###################{1}XX{2}@@@@@@@@@@@@@@@@@@@\n    {0}#################      {2}@@@@@@@@@@@@@@@@@\n    {0}#############              {2}@@@@@@@@@@@@@\n  colors:\n    ansi:\n      - yellow\n      - yellow\n      - yellow\n    hex:\n      - \"#FAB20B\"\n      - \"#F69912\"\n      - \"#F47216\"\n    chip: \"#DF7900\"\n  icon: '\\u{E666}'\nHcl:\n  type: programming\n  ascii: |\n    {0}::\n    {0}::::\n    {0}::::::\n    {0}::::::::\n    {0}::::::::::\n    {0}:::::::::: ::        {1}         ..\n    {0}  :::::::: ::::      {1}       ....\n    {0}    :::::: ::::::    {1}     ......\n    {0}      :::: ::::::::  {1}   ........\n    {0}        :: ::::::::::{1} ..........\n    {0}           ::::::::::{1} ..........\n    {0}             ::::::::{1} ........\n    {0}           ::  ::::::{1} ......\n    {0}           ::::  ::::{1} ....\n    {0}           ::::::  ::{1} ..\n    {0}           ::::::::\n    {0}           ::::::::::\n    {0}           ::::::::::\n    {0}             ::::::::\n    {0}               ::::::\n    {0}                 ::::\n    {0}                   ::\n  colors:\n    ansi:\n      - magenta\n      - magenta\n    hex:\n      - \"#5F43E9\"\n      - \"#4040B2\"\n    chip: \"#AACE60\"\nHlsl:\n  type: programming\n  ascii: |\n    {0}████████████████  {1}████████████████\n    {0}█████  ██  █████  {1}█████  █████████\n    {0}█████  ██  █████  {1}█████  █████████\n    {0}█████      █████  {1}█████  █████████\n    {0}█████  ██  █████  {1}█████  █████████\n    {0}█████  ██  █████  {1}█████      █████\n    {0}████████████████  {1}████████████████\n\n    {2}████████████████  {3}████████████████\n    {2}█████      █████  {3}█████  █████████\n    {2}█████  █████████  {3}█████  █████████\n    {2}█████      █████  {3}█████  █████████\n    {2}█████████  █████  {3}█████  █████████\n    {2}█████      █████  {3}█████      █████\n    {2}████████████████  {3}████████████████\n  colors:\n    ansi:\n      - red\n      - green\n      - blue\n      - yellow\n    hex:\n      - \"#F65314\"\n      - \"#7CBB00\"\n      - \"#00A1F1\"\n      - \"#FFBB00\"\n    chip: \"#AACE60\"\n  icon: '\\u{229E}'\nHolyC:\n  type: programming\n  ascii: |\n    {0}                 ++++++\n    {0}              ++++++++++++\n    {0}          ++++++++++++++++++++\n    {0}       ++++++++++++++++++++++++++\n    {0}    ++++++++++++++++++++++++++++++++\n    {0} +++++++++++++{3}************{0}+++++++++++++\n    {0}+++++++++++{3}******************{0}++++++++{2};;;\n    {0}+++++++++{3}**********************{0}++{2};;;;;;;\n    {0}++++++++{3}*********{0}++++++{3}******{2};;;;;;;;;;;\n    {0}+++++++{3}********{0}++++++++++{3}**{2};;;;{3}**{2};;;;;;;\n    {0}+++++++{3}*******{0}+++++++++{2};;;;;;{3}******{2};;;;;\n    {0}+++++++{3}******{0}+++++++{2};;;;;;;;;;;{3}**{2};;;;;;;\n    {0}+++++++{3}*******{0}+++{1}:::::{2};;;;;;;;;{3}**{2};;;;;;;\n    {0}+++++++{3}********{1}::::::::::{3}**{2};;;;{3}**{2};;;;;;;\n    {0}++++++++{3}*********{1}::::::{3}******{2};;;;;;;;;;;\n    {0}++++++{1}:::{3}**********************{1}::{2};;;;;;;\n    {0}+++{1}::::::::{3}******************{1}::::::::{2};;;\n    {1} :::::::::::::{3}************{1}:::::::::::::\n    {1}    ::::::::::::::::::::::::::::::::\n    {1}       ::::::::::::::::::::::::::\n    {1}          ::::::::::::::::::::\n    {1}              ::::::::::::\n    {1}                 ::::::\n  colors:\n    ansi:\n      - yellow\n      - yellow\n      - yellow\n      - white\n    hex:\n      - \"#D6AE46\"\n      - \"#AB5921\"\n      - \"#C37C2E\"\n      - \"#FFFFFF\"\n    chip: \"#FFEFAF\"\n  icon: '\\u{E61E}'\nHtml:\n  type: markup\n  ascii: |\n    {1}  ##  ##  ######  ##   ##  ##\n    {1}  ##  ##    ##    ### ###  ##\n    {1}  ######    ##    ## # ##  ##\n    {1}  ##  ##    ##    ##   ##  ##\n    {1}  ##  ##    ##    ##   ##  ######\n    {0}(((((((((((((((((((((((((((((((((((\n    {0}(((((((((((((((((/////////////(((((\n    {0}(((((((((((((((((/////////////(((((\n    {0}(((((((                     //(((((\n    {0} ((((((                     //((((\n    {0} ((((((    ((((((/////////////((((\n    {0} ((((((     (((((/////////////((((\n    {0} ((((((                    ///((((\n    {0}  (((((                    ///(((\n    {0}  (((((((((((((((//////    ///(((\n    {0}  ((((((    (((((/////     ///(((\n    {0}  ((((((                   ///(((\n    {0}   (((((((               /////((\n    {0}   ((((((((((((((/////////////((\n    {0}   ((((((((((((((//////(((((((((\n    {0}          (((((((((((((((\n  colors:\n    ansi:\n      - red\n      - white\n    chip: \"#E34C26\"\n  icon: '\\u{E736}'\nIdris:\n  type: programming\n  ascii: |\n    {0}       %\n    {0}          %\n    {0}   %&&&     %%\n    {0}        %%    %&\n    {0}&%%%&     %    %%\n    {0}     %%        %%\n    {0}       %       %%\n    {0} %%%          %%&\n    {0}   %%       %%%\n    {0}    %     &%%%\n    {0}         %%%&\n    {0}       &%%%\n    {0}      %%%%\n    {0}      %%%\n    {0}     %%%\n    {0}     %%%\n    {0}     &%%\n    {0}      %%%\n  colors:\n    ansi:\n      - red\n    chip: \"#B30000\"\nJava:\n  type: programming\n  ascii: |\n    {0}                  |\n    {0}                 ||\n    {0}               |||\n    {0}             ||||    ||\n    {0}           ||||| ||||\n    {0}          ||||  |||\n    {0}         ||||  |||\n    {0}         |||    |||\n    {0}          |||    |||\n    {0}            ||    ||\n    {0}              |   |\n    {1}   ####               #    ##\n    {1}    ################       ##\n    {1}       #                   ##\n    {1}      ################   ###\n    {1}\n    {1}       ##############\n    {1}####      #######          #\n    {1}#####                   ####\n    {1}   #####################      #\n    {1}                          ###\n    {1}          ###############\n  colors:\n    ansi:\n      - red\n      - blue\n    hex:\n      - \"#F44336\"\n      - \"#1665C0\"\n    chip: \"#B07219\"\n  icon: '\\u{E738}'\nJavaScript:\n  type: programming\n  ascii: |\n    {0}JSJSJSJSJSJSJSJSJSJSJSJSJSJSJSJSJSJSJS\n    {0}JSJSJSJSJSJSJSJSJSJSJSJSJSJSJSJSJSJSJS\n    {0}JSJSJSJSJSJSJSJSJSJSJSJSJSJSJSJSJSJSJS\n    {0}JSJSJSJSJSJSJSJSJSJSJSJSJSJSJSJSJSJSJS\n    {0}JSJSJSJSJSJSJSJSJSJSJSJSJSJSJSJSJSJSJS\n    {0}JSJSJSJSJSJSJSJSJSJSJSJSJSJSJSJSJSJSJS\n    {0}JSJSJSJSJSJSJSJSJSJSJSJSJSJSJSJSJSJSJS\n    {0}JSJSJSJSJSJSJSJSJ    SJSJS      JSJSJS\n    {0}JSJSJSJSJSJSJSJSJ    SJS          JSJS\n    {0}JSJSJSJSJSJSJSJSJ    SJS     JSJSJSJSJ\n    {0}JSJSJSJSJSJSJSJSJ    SJSJ     SJSJSJSJ\n    {0}JSJSJSJSJSJSJSJSJ    SJSJSJ     SJSJSJ\n    {0}JSJSJSJSJSJSJSJSJ    SJSJSJSJ     JSJS\n    {0}JSJSJSJSJSJSJSJSJ    SJSJSJSJS     JSJ\n    {0}JSJSJSJSJS     JS    JSJS          JSJ\n    {0}JSJSJSJSJSJ          SJSJSJ      SJSJS\n    {0}JSJSJSJSJSJSJSJSJSJSJSJSJSJSJSJSJSJSJS\n    {0}JSJSJSJSJSJSJSJSJSJSJSJSJSJSJSJSJSJSJS\n  colors:\n    ansi:\n      - yellow\n    hex:\n      - \"#ECE653\"\n    chip: \"#F1E05A\"\n  icon: '\\u{F2EE}'\nJson:\n  type: data\n  ascii: |\n    {0}           `:+osyyyso+/:`\n    {0}        :smNNNmmmddddhhhmds:\n    {0}     .oNNNNNmmmddddhhhyyyym{1}MNs.\n    {0}    oNNNNNmmmddddhhhyyyysssh{1}MMMs`\n    {0}  .dNNNNmmmddmmmdyyyyysssoooh{1}MMMm.\n    {0} `mNNNmmmmm{1}NMMMy-{0}  .+ssoooo++N{1}MMMN.\n    {0} yNNmmmdm{1}MMMMN-  {0}    .ooo+++/d{1}MMMMd\n    {0}-Nmmmddm{1}MMMMM:   {0}     .+++///y{1}MMMMM-\n    {0}+mmdddd{1}MMMMMm    {0}      /////:y{1}MMMMM+\n    {0}+ddddhd{1}MMMMMm    {0}      ///:::m{1}MMMMM+\n    {0}-ddhhhd{1}MMMMMM-   {0}     `/::::y{1}MMMMMM-\n    {0} shhyyh{1}MMMMMMm-  {0}    `:::::h{1}MMMMMMh\n    {0} .yyyyyN{1}MMMMMMMs.{0}  `-:::/y{1}NMMMMMMm`\n    {0}  .osssh{1}MMMMMMMMMmhyyydNMMMMMMMMd.\n    {0}    :oood{1}MMMMMMMMMMMMMMMMMMMMMNo\n    {0}     `:++yN{1}MMMMMMMMMMMMMMMMMNs.\n    {0}        .-/ym{1}MMMMMMMMMMMMmy:\n    {0}            `-/oyhhhys+:`\n  colors:\n    ansi:\n      - white\n      - black\n    chip: \"#292929\"\n  icon: '\\u{eb0f}'\nJsonnet:\n  type: programming\n  ascii: |\n    {0}        .       .\n    {0}       /{1}:{0}\\     /{1}:{0}\\\n    {0}      /{1}:::{0}\\   /{1}:::{0}\\\n    {0}     /{1}:::::{0}\\ /{1}:::::{0}\\\n    {0}   /|\\{1}:::::{0}/|\\{1}:::::{0}/|\n    {0}  /{1}:{0}|#\\{1}:::{0}/{1}.{0}|#\\{1}:::{0}/{1}.{0}|\n    {0} /{1}::{0}|##\\{1}:{0}/{1}..{0}|##\\{1}:{0}/{1}..{0}|\n    {0}|\\{1}::{0}|###|{1}...{0}|###|{1}...{0}|\n    {0}|#\\{1}:{0}|###|{1}...{0}|###|{1}...{0}|\n    {0}|##\\|###|{1}...{0}|###|{1}...{0}|\n    {0}|#######|{1}..{0}/{1}:{0}\\##|{1}..{0}/\n    {0}|#######|{1}.{0}/{1}:::{0}\\#|{1}.{0}/\n    {0}|#######|/{1}:::::{0}\\|/\n    {0} \\#######\\{1}:::::{0}/|\\\n    {0}  \\#######\\{1}:::{0}/{1}.{0}|{1}:{0}\\\n    {0}   \\#######\\{1}:{0}/{1}..{0}|{1}::{0}\\\n    {0}    |#######|{1}...{0}|{1}::{0}/|\n    {0}    |#######|{1}...{0}|{1}:{0}/{1}.{0}|\n    {0}    |#######|{1}...{0}|/{1}..{0}|\n    {0}    |###|\\##|{1}..{0}/|{1}...{0}|\n    {0}    |###|{1}.{0}\\#|{1}.{0}/#|{1}...{0}|\n    {0}    |###|{1}..{0}\\|/##|{1}...{0}|\n    {0}     \\##|{1}..{0}/ \\##|{1}..{0}/\n    {0}      \\#|{1}.{0}/   \\#|{1}.{0}/\n    {0}       \\|/     \\|/\n  colors:\n    ansi:\n      - white\n      - black\n    chip: \"#0064BD\"\nJsx:\n  type: programming\n  ascii: |\n    {0}JSXJSXJSXJSXJSXJSXJSXJSXJSXJSXJSXJSX{1}JSX\n    {0}JSXJSXJSXJSXJSXJSXJSXJSXJSXJSXJSXJS{1}XJSX\n    {0}JSXJSXJSXJSXJSXJSXJSXJSXJSXJSXJSXJ{1}SXJSX\n    {0}JSXJSXJSXJSXJSXJSXJSXJSXJSXJSXJSX{1}JSXJSX\n    {0}JSXJSXJSXJSXJSXJSXJSXJSXJSXJSXJS{1}XJSXJSX\n    {0}JSXJSXJSXJSXJSXJSXJSXJSXJSXJSXJ{1}SXJSXJSX\n    {0}JSXJSXJSXJSXJSXJSXJSXJSXJSXJSX{1}JSXJSXJSX\n    {0}JSXJSXJSXJSXJSXJSXJSXJSXJSXJS{1}XJSXJSXJSX\n    {0}JSXJSXJSXJSXJSXJSXJSXJSXJSXJ{1}SXJSXJSXJSX\n    {0}JSXJSXJSXJSXJSXJSXJSXJSXJSX{1}JSXJSXJSXJSX\n    {0}JSXJSXJ   SXJS      XJSXJS{1}X{2}JSX{1}JSX{2}JSX{1}JSX\n    {0}JSXJSXJ   SXJ   SXJSXJSXJ{1}SXJ{2}SXJ{1}S{2}XJS{1}XJSX\n    {0}JSXJSXJ   SXJS   XJSXJSX{1}JSXJSX{2}JSX{1}JSXJSX\n    {0}JSXJSXJ   SXJSX   JSXJS{1}XJSXJS{2}XJSXJ{1}SXJSX\n    {0}JS   XJ   SXJSXJ   SXJ{1}SXJSXJ{2}SXJ{1}S{2}XJS{1}XJSX\n    {0}JSX       JS      XJS{1}XJSXJS{2}XJS{1}XJS{2}XJS{1}XJS\n    {0}JSXJSXJSXJSXJSXJSXJS{1}XJSXJSXJSXJSXJSXJSX\n    {0}JSXJSXJSXJSXJSXJSXJ{1}SXJSXJSXJSXJSXJSXJSX\n  colors:\n    ansi:\n      - yellow\n      - magenta\n      - white\n    hex:\n      - \"#ECE653\"\n      - \"#B684D3\"\n      - \"#FFFFFF\"\n    chip: \"#F1E05A\"\n  icon: '\\u{F2EE}'\nJulia:\n  type: programming\n  ascii: |\n    {0}               {2}_\n    {0}   {1}_       {0}_ {3}_{2}(_){4}_\n    {0}  {1}(_)     {0}| {3}(_) {4}(_)\n    {0}   _ _   _| |_  __ _\n    {0}  | | | | | | |/ _` |\n    {0}  | | |_| | | | (_| |\n    {0} _/ |\\__'_|_|_|\\__'_|\n    {0}|__/\n  colors:\n    ansi:\n      - white\n      - blue\n      - green\n      - red\n      - magenta\n    chip: \"#A270BA\"\n  icon: '\\u{E624}'\nJupyter:\n  type: programming\n  ascii: |\n    {0}                                 +%%%+\n    {0}                                $$$$$$$\n    {0}  +%+                           $$$$$$$\n    {0} $$$$$           {1}******          {0}*%%%*\n    {0}  *%*       {1}****************\n    {1}        ************************\n    {1}     ********              ********\n    {1}   ***                            ***\n    {1}  *                       {0}_          {1}*\n    {0}  _  _   _  _ __   _   _ | |_  ___  _ _\n    {0} | || | | || '_ \\ | | | || __|/ _ \\| '_|\n    {0} | || |_| || |_) || |_| || |_ | __/| |\n    {0} | | \\__,_|| .__/  \\__, | \\__|\\___||_|\n    {0}/_/        |_|     |___/\n    {1}  *                                  *\n    {1}   ***                            ***\n    {1}     ********              ********\n    {1}        ************************\n    {1}            ****************\n    {0}    +%%%+        {1}******\n    {0}   $$$$$$$\n    {0}   $$$$$$$\n    {0}    *%%%*\n  colors:\n    ansi:\n      - white\n      - yellow\n      - white\n    hex:\n      - \"#FFFFFF\"\n      - \"#FF700F\"\n      - \"#9E9E9E\"\n    chip: \"#DA5B0B\"\n  icon: '\\u{E80F}'\n  serialization: jupyter-notebooks\nKotlin:\n  type: programming\n  ascii: |\n    {0}KOTLIN{2}KOTLINKOTLINKO{1}TLINKOTLINKOTLINKOTL\n    {0}KOTLINKO{2}TLINKOTLIN{1}KOTLINKOTLINKOTLINKO\n    {0}KOTLINKOTL{2}INKOTL{1}INKOTLINKOTLINKOTLIN\n    {0}KOTLINKOTLIN{2}KO{1}TLINKOTLINKOTLINKOTL\n    {0}KOTLINKOTLIN{1}KOTLINKOTLINKOTLINKO\n    {0}KOTLINKOTL{1}INKOTLINKOTLINKOTLIN\n    {0}KOTLINKO{1}TLINKOTLINKOTLINKOTL\n    {0}KOTLIN{1}KOTLINKOTLINKOTLINKO\n    {0}KOTL{1}INKOTLINKOTLINKOTLIN\n    {0}KO{1}TLINKOTLINKOTLINKOTL\n    {1}KOTLINKOTLINKOTLINKO{2}TL\n    {2}KO{1}TLINKOTLINKOTLIN{2}KOTLIN\n    {2}KOTL{1}INKOTLINKOTL{2}INKOTLINKO\n    {2}KOTLIN{1}KOTLINKO{2}TLINKOTLINKOTL\n    {2}KOTLINKO{1}TLIN{0}K{2}OTLINKOTLINKOTLIN\n    {2}KOTLINKOTL{0}INKOT{2}LINKOTLINKOTLINKO\n    {2}KOTLINKO{0}TLINKOTLI{2}NKOTLINKOTLINKOTL\n    {2}KOTLIN{0}KOTLINKOTLINK{2}OTLINKOTLINKOTLIN\n    {2}KOTL{0}INKOTLINKOTLINKOT{2}LINKOTLINKOTLINKO\n    {2}KO{0}TLINKOTLINKOTLINKOTLI{2}NKOTLINKOTLINKOTL\n  colors:\n    ansi:\n      - blue\n      - yellow\n      - magenta\n    chip: \"#A97BFF\"\n  icon: '\\u{E634}'\nLean:\n  type: programming\n  ascii: |\n    {0}       ______\n    {0}|            |\\              /|\\       |\n    {0}|            | \\            / | \\      |\n    {0}|            |  \\          /  |  \\     |\n    {0}|      ______|   \\________/   |   \\    |\n    {0}|            |    \\      /    |    \\   |\n    {0}|            |     \\    /     |     \\  |\n    {0}|            |      \\  /      |      \\ |\n    {0}|____________|       \\/       |       \\|\n  colors:\n    ansi:\n      - white\n    chip: \"#FFFFFF\"\nLisp:\n  type: programming\n  ascii: |\n    {0}              ............\n    {0}         ********..............\n    {0}       *************.............\n    {0}     ****************..............\n    {0}   *******************...***.........\n    {0}  **...********...*****...***.........\n    {0} ***...*******...******....***.........\n    {0}****...******...*******....****.........\n    {0}*****...****...*******.....*****........\n    {0}******...**...******.......******.......\n    {0}*******......******.......***..***......\n    {0}********.....*****.......***....***.....\n    {0}*********....****.......***......***....\n    {0} *********...****......***.......***...\n    {0}  *********...***.....***........***..\n    {0}   *********...***...................\n    {0}     **************................\n    {0}       *************.............\n    {0}         **************........\n    {0}              ************\n  colors:\n    ansi:\n      - white\n    chip: \"#3FB68B\"\n  icon: '\\u{E6B0}'\nLLVM:\n  type: programming\n  ascii: |\n    {0}KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK\n    {0}KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK\n    {0}KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK\n    {0}KKKKKKKKK\"              \"KKKKKKKK\n    {0}KKKKKKK     KKKKKKKKKK     \"KKKKK\n    {0}KKKKK     KKKKKKKKKKKKKKK    \"KKK\n    {0}KKKK     KKKKKKKKKKKKKKKKK\n    {0}KKKK      KKKKKKKKKKKKKKKKK\n    {0}KKKK        KKKKKKKKKKKKKKKK\n    {0}KKKK           KKKKKKKKKKKKKK\n    {0}KKKK            KKKKKKKKKKKKK\n    {0}KKKK            ,KKKKKKKKKKKK\n    {0}KKKKKK,    ,     KKKKKKKKKKKK\n    {0}KKKKKKKK   KK,    `KKKKKKKKKK\n    {0}KKKKKKKKK  KKKKKKKKKKKKKKKKK\n    {0}KKKKKKKK,  ,KKKKKKKKKKKKKKK\n    {0} `KKKKKKKKKKKKKKKKKKKKKKK`\n    {0}   `KKKKKKKKKKKKKKKKKKK`\n    {0}      `KKKKKKKKKKKKKK`\n  colors:\n    ansi:\n      - red\n    hex:\n      - \"#98012E\"\n    chip: \"#185619\"\n  icon: '\\u{E823}'\nLua:\n  type: programming\n  ascii: |\n    {1}                 -- --\n    {1}         --                 --{0} @@@@\n    {1}      --      {0}@@@@@@@@@@@     @@@@@@\n    {0}           @@@@@@@@@@@@@@@@@   @@@@\n    {1}  --     {0}@@@@@@@@@@@@@@{1}@@@@{0}@@@     {1}--\n    {1} --    {0}@@@@@@@@@@@@@@@{1}@@@@@@{0}@@@@    {1}--\n    {0}      @@@@@@@@@@@@@@@@@{1}@@@@{0}@@@@@@\n    {1}--   {0}@@@{1}@@{0}@@@@@@@@@@@@@@@@@@@@@@@@   {1}--\n    {1}--   {0}@@@{1}@@{0}@@@@@@{1}@@{0}@@{1}@@{0}@@{1}@@@@@@{0}@@@@   {1}--\n    {0}     @@@{1}@@{0}@@@@@@{1}@@{0}@@{1}@@{0}@{1}@@{0}@@@{1}@@{0}@@@@\n    {1}--   {0}@@@{1}@@{0}@@@@@@{1}@@{0}@@{1}@@{0}@@@{1}@@@@@{0}@@@@   {1}--\n    {1}--   {0}@@@{1}@@{0}@@@@@@{1}@@{0}@@{1}@@{0}@{1}@@@{0}@@{1}@@{0}@@@@   {1}--\n    {0}      @@{1}@@@@@@@{0}@{1}@@@@@@{0}@{1}@@@@@@@@{0}@@\n    {1} --    {0}@@@@@@@@@@@@@@@@@@@@@@@@@    {1}--\n    {1}  --     {0}@@@@@@@@@@@@@@@@@@@@@     {1}--\n    {0}           @@@@@@@@@@@@@@@@@\n    {1}      --      {0}@@@@@@@@@@@      {1}--\n    {1}         --                 --\n    {1}                 -- --\n  colors:\n    ansi:\n      - blue\n      - white\n    hex:\n      - \"#134074\"\n      - \"#FFFFFF\"\n    chip: \"#000080\"\n  icon: '\\u{E620}'\nMakefile:\n  type: programming\n  ascii: |\n    {0}    _-`````-,           ,- '- .\n    {0}  .'   .- - |          | - -.  `.\n    {0} /.'  /                     `.   \\\n    {0}:/   :      {1}_...   ..._      {0}``   :\n    {0}::   :     {1}/._ .`:'_.._\\.    {0}||   :\n    {0}::    `._ {1}./  ,`  :    \\{0} . _.''   .\n    {0}`:.      {1}/   |  -.  \\-. \\\\_{0}      /\n    {0}  \\:._ {1}_/  .'   .{2}@{1})  \\{2}@{1}) ` `\\{0} ,.'\n    {1}    {0}'{1}_/,--'       .- .\\,-.`--`.\n    {1}       ,'/''     (( \\ `  )\n    {1}        /'/'  \\    `-'  (\n    {1}         '/''  `._,-----'\n    {1}          ''/'    .,---'\n    {1}           ''/'      ;:\n    {1}             ''/''  ''/\n    {1}               ''/''/''\n    {1}                 '/'/'\n    {1}                  `;\n  colors:\n    ansi:\n      - white\n      - yellow\n      - red\n    hex:\n      - \"#FFFFFF\"\n      - \"#FAEC9A\"\n      - \"#610000\"\n    chip: \"#427819\"\n  icon: '\\u{E673}'\nMarkdown:\n  type: prose\n  ascii: |\n    {0}#######  {1} ,#####. .#####.\n    {0}  ###    {1}########.########\n    {0}  ###    {1}#################\n    {0}  ###    {1}`###############'\n    {0}  ###    {1} `#############'\n    {0}  ###    {1}   `#########'\n    {0}  ###    {1}     `#####'\n    {0}#######  {1}       `#'\n    {0}\n    {0}####     ####     ###\n    {0}#####   #####     ###\n    {0}######.######     ###\n    {0}### ##### ###     ###\n    {0}###  ###  ###   #######\n    {0}###   #   ###    #####\n    {0}###       ###     ###\n    {0}###       ###      #\n  colors:\n    ansi:\n      - white\n      - red\n    hex:\n      - \"#FFFFFF\"\n      - \"#FF0000\"\n    chip: \"#083FA1\"\n  icon: '\\u{E73E}'\nModelica:\n  type: programming\n  ascii: |\n    {0}       #########   ########   #########\n    {0}     ############ ########## ###########\n    {0}    ######   ########   #######   ######\n    {0}   ####     ########   #######   ######\n    {0}  ###      #######    ######    #####\n    {0} ##       ######     #####     ####\n    {0}         ######     #####     ###\n    {0}        #####      ####      ##\n    {0}       #####      ####\n    {0}      ####       ###       {1}***\n    {0}     ####       ##       {1}*******\n    {0}    ####                {1}*********\n    {0}   ###                  {1}*********\n    {0}  ##                     {1}*******\n    {1}                           ***\n  colors:\n    ansi:\n      - white\n      - red\n    hex:\n      - \"#FFFFFF\"\n      - \"#FF0000\"\n    chip: \"#de1d31\"\nNim:\n  type: programming\n  ascii: |\n    {0}                   ++\n    {0}       ++        ++++++        ++\n    {0}      ++++++++++++++++++++++++++++\n    {0}     ++++++++++++++++++++++++++++++\n    {0}++  ++++++++++++++++++++++++++++++++  ++\n    {0} ++++++++++++++++++++++++++++++++++++++\n    {0}  +++++++++++              +++++++++++\n    {0}   ++++++++                  ++++++++\n    {0}    +++++                      +++++\n    {1} ?   {0}++                          ++   {1}?\n    {1}  ??             ??????             ??\n    {1}   ???         ??????????         ???\n    {1}    ????     ??????????????     ????\n    {1}     ??????????????????????????????\n    {1}      ????????????????????????????\n    {1}       ??????????????????????????\n    {1}         ??????????????????????\n    {1}           ??????????????????\n  colors:\n    ansi:\n      - yellow\n      - white\n    chip: \"#FFC200\"\n  icon: '\\u{E677}'\nNix:\n  type: programming\n  ascii: |\n    {1}         :::.    {0}':::::     ::::'\n    {1}         '::::    {0}':::::.  ::::'\n    {1}           ::::     {0}'::::.:::::\n    {1}     ......:::::..... {0}::::::::\n    {1}    :::::::::::::::::. {0}::::::    {1}::::.\n    {1}   :::::::::::::::::::: {0}:::::.  {1}.::::'\n    {0}         .....           {0}::::' {1}:::::'\n    {0}        :::::            {0}'::' {1}:::::'\n    {0} ......:::::               {0}' {1}::::::::::.\n    {0}:::::::::::                 {1}::::::::::::\n    {0} ::::::::: {1}..              {1}:::::\n    {0}    .:::: {1}.:::            {1}:::::\n    {0}   .::::  {1}:::::          {1}'''''    {0}.....\n    {0}   ::::   {1}':::::.  {0}......:::::::::::::'\n    {0}    ::     {1}::::::. {0}':::::::::::::::::'\n    {1}           {1}.:::::::: {0}'::::::::::\n    {1}          {1}.::::''::::.     {0}'::::.\n    {1}         {1}.::::'   ::::.     {0}'::::.\n    {1}        {1}.::::      ::::      {0}'::::.\n  colors:\n    ansi:\n      - cyan\n      - blue\n    chip: \"#7E7EFF\"\n  icon: '\\u{f313}'\nNushell:\n  type: programming\n  ascii: |\n    {0} _ __  \n    {0}| '_ \\      {1}__\n    {0}| | | |     {1}\\ \\\n    {0}| | |_|  _  {1} \\ \\\n    {0}|_|  _  | | {1} / /\n    {0}    | | | | {1}/_/\n    {0}    | |_| |\n    {0}     \\__,_|\n  colors:\n    ansi:\n      - green\n      - white\n    chip: \"#4E9906\"\nObjectiveC:\n  type: programming\n  ascii: |\n    {0}888                                888\n    {0}8    8888   8       8        8888    8\n    {0}8   8    8  8               8    88  8\n    {0}8  8      8 8       8      8         8\n    {0}8  8      8 8 888   8      8         8\n    {0}8  8      8 88   8  8 ==== 8         8\n    {0}8  8      8 8    8  8      8      8  8\n    {0}8   8    8  88   8  8       8    88  8\n    {0}8    8888   8 888   8        88888   8\n    {0}888                 8              888\n    {0}                    8\n    {0}                  88Y\n  colors:\n    ansi:\n      - cyan\n      - blue\n    chip: \"#438EFF\"\n  icon: '\\u{E84D}'\n  serialization: objective-c\nOCaml:\n  type: programming\n  ascii: |\n    {0}///////////////////////////////////////\n    {0}///////////////////////////////////////\n    {0}///////////////////////////////////////\n    {0}///////////////////////////////////////\n    {0}///////////////////////////////////////\n    {0}///   \\////    \\///////////////////////\n    {0}//      //      /////////     .////////\n    {0}/                ///////         \\/////\n    {0}                  /////      //////////\n    {0}                            ///////////\n    {0}                           ////////////\n    {0}  //                    ///////////////\n    {0} /////////   ///   ////////////////////\n    {0}/////////  //////  ////////////////////\n    {0}////////  ///////  ////////////////////\n    {0}///////  ////////  ////////////////////\n    {0}//////  /////////  ////////////////////\n  colors:\n    ansi:\n      - yellow\n    chip: \"#3BE133\"\n  icon: '\\u{E67A}'\nOdin:\n  type: programming\n  ascii: |\n    {0}          @@@@@@@@@\n    {0}      @@@@     @@@   @@@\n    {0}    @@@@      @@@   @@@@@@\n    {1}  @@@@       @@@   @@@  @@@@\n    {1} @@@        @@@   @@@     @@@\n    {1}@@@        @@@   @@@       @@@\n    {2}@@@       @@@   @@@        @@@\n    {2}@@@      @@@   @@@         @@@\n    {2}@@@     @@@   @@@          @@@\n    {3}@@@    @@@   @@@           @@@\n    {3} @@@  @@@   @@@           @@@\n    {3}  @@@@@@   @@@          @@@@\n    {4}    @@@   @@@         @@@@\n    {4}         @@@        @@@@\n    {4}          @@@@@@@@@@\n  colors:\n    ansi:\n      - blue\n      - blue\n      - blue\n      - blue\n      - blue\n    hex:\n      - \"#265A99\"\n      - \"#3473BE\"\n      - \"#3F88DD\"\n      - \"#4797F3\"\n      - \"#499AF7\"\n    chip: \"#60AFFE\"\nOpenScad:\n  type: programming\n  ascii: |\n    {0}                 ________\n    {0}             .:------------:   {1}...\n    {0}          .-+=             =+=-    {1}:.\n    {0}        :+***               +**=:   {1}.:.\n    {0}       =*****+             +*****=    {1}-.\n    {0}      +**********+++++++**********+    {1}-\n    {0}     =*****************************=   {1}-\n    {0}    .*+==+********************+++***. {1}.:\n    {1} .::-:.   {0}.-+***************-     =*-{1}..\n    {1}-.    .:.    {0}-************=        *.\n    {1}=       .:.   {0}:**********-        :+\n    {1}=         :.   {0}=********=        .+.\n    {1}.-         -   {0}.********-       :=.\n    {1} .-.       :   {0}=********+.   .:=-\n    {1}   ::     .-{0}:=************++==:\n    {1}     ·...·   {0}.-=++*****++=-.\n    {0}                ---------\n  colors:\n    ansi:\n      - yellow\n      - white\n    hex:\n      - \"#FFFA56\"\n      - \"#F6C59A\"\n    chip: \"#E5CD45\"\n  icon: '\\u{F34E}'\nOrg:\n  type: prose\n  ascii: |\n    {2}                    j\n    {2}                   eL\n    {0}                Q {2}kD\n    {0}            Nt{1}yew{2}kQ{0}y\n    {1}       :r/2K{0}@Q@@@#N@@Qmir\n    {1}  -cDBQB6XXe{0}Q@@@@@@@@@@@@@Q@@g\n    {1} =NQQQR6XwPee{0}6@@@@@@@@@@@QdRm\n    {1}?#QB#HKXej{0}D@QQRNkPD9|\n    {1}SQNHDXwmXq{0}@@@QDR\n    {1}=D6XwmSm{0}D@Q@@QDH\n    {1} tXaZe{0}H@QQ@@@@QRdz\n    {1}   u{0}QRQ@@@@@@@@@QDDd\n    {0}     B@@@@@@@@@@@QDHDd\n    {0}         Syz*:'--'~;\\oM\n  colors:\n    ansi:\n      - green\n      - red\n      - white\n    chip: \"#77AA99\"\n  icon: '\\u{E633}'\nOz:\n  type: programming\n  ascii: |\n    {0}                   ooooooooo\n    {0}               oooooo{1}zzz{0}oooooo\n    {0}            oooooo{1}zzzzzz{0}ooooooo\n    {0}         oooooo{1}zzz{0}ooo{1}zzz{0}oooooooo\n    {0}      ooooooooooooooo{1}zzz{0}ooooooooo\n    {0}   oooo    oooooooooo{1}zzz{0}ooo{1}zzz{0}oooo\n    {0} ooo   oooo   ooooooo{1}zzzzzz{0}oooooooo\n    {0}ooo   oooooo    ooooo{1}zzz{0}oooooooooooo\n    {0}ooo    oooooo    ooooooooooo   oooooo\n    {0}oooo    oooooo   oooooooo      ooooooo\n    {0} ooooo   oooo   oooooo   ooo   oooooooo\n    {0}  ooooooo    ooooooooooooooo   oooooooo\n    {0}   ooooooooooo{1}zzzz{0}oooooooooo   ooo   oo\n    {0}    ooooooo{1}zzz{0}oooo{1}zzz{0}ooooooo      oooo\n    {0}     ooooo{1}zzz{0}oooooo{1}zzzz{0}ooooo   ooooo\n    {0}      oooo{1}zzzz{0}oooooo{1}zzzz{0}ooooooooo\n    {0}       oooo{1}zzzz{0}oooooo{1}zzz{0}oooooo\n    {0}        ooooo{1}zzz{0}oooo{1}zzz{0}ooo\n    {0}         ooooooo{1}zzzz{0}oooo\n    {0}           ooooooooo\n  colors:\n    ansi:\n      - yellow\n      - white\n    hex:\n      - \"#FCAF3E\"\n      - \"#FFFFFF\"\n    chip: \"#FAB738\"\nPascal:\n  type: programming\n  ascii: |\n    {0}█████{1}╗  {0}████{1}╗ {0}█████{1}╗ {0}████{1}╗ {0}████{1}╗ {0}██{1}╗\n    {0}██{1}╔═{0}██{1}╗{0}██{1}╔═{0}██{1}╗{0}██{1}╔══╝{0}██{1}╔══╝{0}██{1}╔═{0}██{1}╗{0}██{1}║\n    {0}█████{1}╔╝{0}██████{1}║{0}█████{1}╗{0}██{1}║   {0}██████{1}║{0}██{1}║\n    {0}██{1}╔══╝ {0}██{1}╔═{0}██{1}║╚══{0}██{1}║{0}██{1}║   {0}██{1}╔═{0}██{1}║{0}██{1}║\n    {0}██{1}║    {0}██{1}║ {0}██{1}║{0}█████{1}║╚{0}████{1}╗{0}██{1}║ {0}██{1}║{0}█████{1}╗\n    {1}╚═╝    ╚═╝ ╚═╝╚════╝ ╚═══╝╚═╝ ╚═╝╚════╝\n  colors:\n    ansi:\n      - blue\n      - white\n    chip: \"#E3F171\"\nPerl:\n  type: programming\n  ascii: |\n    {0}                  ######\n    {0}    ###         #########\n    {0} ########      ##########\n    {0}#########     ############\n    {0}   ######   ###############\n    {0}  ####### ##################\n    {0}  ####### ###################\n    {0}  ############################\n    {0}  #############################\n    {0}  ########################### ##\n    {0}    ######################### ##\n    {0}     ###################  ### #\n    {0}          ##### #### ###  ### #\n    {0}          ####  #### ###   ##\n    {0}          ####  ###  ###    #\n    {0}           ##  ###   ###    #\n    {0}           ##   ##   ##     #\n    {0}           ##    #   #      #\n    {0}           #       ##       #\n    {0}           #       # #      #\n    {0}           #     ### ##     ##\n    {0}          ##\n  colors:\n    ansi:\n      - cyan\n    chip: \"#0298C3\"\n  icon: '\\u{E67E}'\nPhp:\n  type: programming\n  ascii: |\n    {0}            ################\n    {0}      ##########{1}/  |{0}##############\n    {0}   #############{1}|  |{0}#################\n    {0} #####{1}/   __   \\|   __   \\/   __   \\{0}###\n    {0}######{1}|  |{0}##{1}|  ||  |{0}##{1}|  ||  |{0}##{1}|  |{0}####\n    {0}######{1}|  |{0}##{1}/  ||  |{0}##{1}|  ||  |{0}##{1}/  |{0}####\n    {0} #####{1}|   ____ /|__|{0}##{1}|__||   ____ /{0}###\n    {0}   ###{1}|  |{0}################{1}|  |{0}#######\n    {1}      |_ /{0}################{1}|_ /{0}####\n    {0}            ################\n  colors:\n    ansi:\n      - blue\n      - white\n    hex:\n      - \"#777BB3\"\n      - \"#FFFFFF\"\n    chip: \"#4F5D95\"\n  icon: '\\u{f031f}'\nPowerShell:\n  type: programming\n  ascii: |\n    {0}         #########################\n    {0}        ####{1}####{0}#################\n    {0}       ######{1}####{0}###############\n    {0}      ########{1}####{0}#############\n    {0}     ##########{1}####{0}###########\n    {0}    #########{1}####{0}############\n    {0}   ########{1}####{0}#############\n    {0}  #######{1}####{0}##############\n    {0} ######{1}####{0}#####{1}######{0}####\n    {0}#########################\n  colors:\n    ansi:\n      - blue\n      - white\n    hex:\n      - \"#316CB9\"\n      - \"#FFFFFF\"\n    chip: \"#012456\"\n  icon: '\\u{f0a0a}'\nProcessing:\n  type: programming\n  ascii: |\n    {0}          PPPPPPPPPPPP\n    {0}      PPPPPPPPPPPPPPPPPPPP\n    {0}    PPPPPPPPPPPP{1}PPPPPP{0}PPPPPP\n    {0}   PPPPPPPPPPPPP{1}P{0}PPPP{1}PP{0}PPPPPP\n    {0}  PPPPPPPPPPPPPPPPPPP{1}PP{0}PPPPPPP\n    {0} PPPPPPPPPPPPPPPPPPP{1}PP{0}PPPPPPPPP\n    {0}PPPPPPPPPPP{1}PPPP{0}PP{1}PPPPP{0}PPPPPPPPPP\n    {0}PPPPPPPPPPPP{1}PPP{0}PPPPPP{1}PP{0}PPPPPPPPP\n    {0}PPPPPPPPPPPP{1}PPP{0}PPPPPP{1}PP{0}PPPPPPPPP\n    {0}PPPPPPPPPPPP{1}PPP{0}PP{1}PPPPP{0}PPPPPPPPPP\n    {0}PPPPPPPPPPPP{1}PPP{0}PPPPPPPPPPPPPPPPP\n    {0} PPPPPPPPPPP{1}PPP{0}PPPPPPPPPPPPPPPP\n    {0}  PPPPPPPPPP{1}PPP{0}PPPPPPPPPPPPPPP\n    {0}   PPPPPPP{1}PPPPPPP{0}PPPPPPPPPPPP\n    {0}    PPPPPPPPPPPPPPPPPPPPPPPP\n    {0}      PPPPPPPPPPPPPPPPPPPP\n    {0}          PPPPPPPPPPPP\n  colors:\n    ansi:\n      - blue\n      - white\n    hex:\n      - \"#505050\"\n      - \"#FFFFFF\"\n    chip: \"#0096D8\"\nProlog:\n  type: programming\n  ascii: |\n    {0}            ############   #\n    {0}       #################   ####\n    {0}     ###################   ######\n    {0}   #####################   #######\n    {0}  ######################   ########\n    {0} #######         #######   #########\n    {0}######  ###        #####   ##########\n    {0}#####  ####      #  ####   ##########\n    {0}#####  #####    ##  ####   ##########\n    {0}#####   #########   ####   ##########\n    {0}#####     #####   #######    ########\n    {0} ####           ###########       ##\n    {0}  ###   ###########################\n    {0}    #   ##########################\n    {0}        ########################\n    {0}        ######################\n    {0}           ################\n  colors:\n    ansi:\n      - white\n    chip: \"#74283C\"\n  icon: '\\u{E7A1}'\nProtobuf:\n  type: programming\n  ascii: |\n    {0}      ;;;;;;;;     {2}::::::::\n    {0}     ;;;;;;;;       {2}::::::::\n    {0}    ;;;;;;;;         {2}::::::::\n    {1}   :{0};;;;;;;           {2}::::::::\n    {1}  :::{0};;;;;             {2}::::::::\n    {1} :::::{0};;;               {2}::::::::\n    {1}:::::::{0};                {3};{2}:::::::\n    {1}::::::::               {3};;;{2}:::::\n    {1} ::::::::             {3};;;;;{2}:::\n    {1}  ::::::::           {3};;;;;;;{2}:\n    {1}   ::::::::         {3};;;;;;;;\n    {1}    ::::::::       {3};;;;;;;;\n    {1}     ::::::::     {3};;;;;;;;\n  colors:\n    ansi:\n      - red\n      - blue\n      - green\n      - yellow\n    chip: \"#74283C\"\n  serialization: protocol-buffers\nPureScript:\n  type: programming\n  ascii: |\n    {0}           \\\\\\\\\\\\\\\\\\\\\\\\\\\\    \\\\\\\\\\\n    {0}            \\\\\\\\\\\\\\\\\\\\\\\\\\\\     \\\\\\\\\\\n    {0}    /////                        \\\\\\\\\\\n    {0}  /////      //////////////      /////\n    {0}/////       //////////////     /////\n    {0}\\\\\\\\\\                        /////\n    {0}  \\\\\\\\\\     \\\\\\\\\\\\\\\\\\\\\\\\\\\\\n    {0}    \\\\\\\\\\    \\\\\\\\\\\\\\\\\\\\\\\\\\\\\n  colors:\n    ansi:\n      - white\n    chip: \"#1D222D\"\n  icon: '\\u{E630}'\nPython:\n  type: programming\n  ascii: |\n    {0}               =========\n    {0}            ===============\n    {0}           =================\n    {0}          ===  ==============\n    {0}          ===================\n    {0}                   ==========\n    {0}   ========================== {1}=======\n    {0} ============================ {1}========\n    {0}============================= {1}=========\n    {0}============================ {1}==========\n    {0}========================== {1}============\n    {0}============ {1}==========================\n    {0}========== {1}============================\n    {0}========= {1}=============================\n    {0} ======== {1}============================\n    {0}  ======= {1}==========================\n    {1}          ==========\n    {1}          ===================\n    {1}          ==============  ===\n    {1}           =================\n    {1}            ===============\n    {1}               =========\n  colors:\n    ansi:\n      - blue\n      - yellow\n    hex:\n      - \"#2F69A2\"\n      - \"#FFD940\"\n    chip: \"#3572A5\"\n  icon: '\\u{E73C}'\nQml:\n  type: programming\n  ascii: |\n    {0}****************************************\n    {0}****************************************\n    {0}****************************************\n    {0}****************************************\n    {0}****************************************\n    {0}*******{1}####{0}*****{1}###{0}*****{1}###{0}**{1}###{0}********\n    {0}*****{1}########{0}***{1}####{2},{0}**{1}####{2},{0}*{1}###{2},,{0}******\n    {0}****{1}###{2},,,,{1}###{2},{0}*{1}#####{2},{1}#####{2},,{1}###{2},,,,{0}****\n    {0}***{1}###{2},,,,,,{1}###{2},{1}###########{2},,{1}###{2},,,,,,{0}**\n    {0}***{1}###{2},,,,,,{1}###{2},{1}###{2},{1}###{2},{1}###{2},,{1}###{2},,,,,,,{0}*\n    {0}***{1}###{2},,,,,,{1}###{2},{1}###{2},,{1}#{2},,{1}###{2},,{1}###{2},,,,,,,,\n    {0}****{1}###{2},,,,{1}###{2},,{1}###{2},,,,,{1}###{2},,{1}###{2},,,,,,,,\n    {0}*****{1}########{2},,,{1}###{2},,,,,{1}###{2},,{1}########{2},,,\n    {0}*******{1}####{2},,,,,{1}###{2},,,,,{1}###{2},,{1}########{2},,,\n    {0}********{2},{1}#####{2},,,,,,,,,,,,,,,,,,,,,,,,,,\n    {0}*********{2},,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\n    {0}***********{2},,,,,,,,,,,,,,,,,,,,,,,,,,,,,\n    {0}*************{2},,,,,,,,,,,,,,,,,,,,,,,,,,,\n    {0}***************{2},,,,,,,,,,,,,,,,,,,,,,,,,\n    {0}*****************{2},,,,,,,,,,,,,,,,,,,,,,,\n  colors:\n    ansi:\n      - green\n      - white\n      - green\n    hex:\n      - \"#80C342\"\n      - \"#FFFFFF\"\n      - \"#4D7528\"\n    chip: \"#44A51C\"\nR:\n  type: programming\n  ascii: |\n    {0}             .,,,,,,,,,,,,,\n    {0}       ,,,,,,,,,,,,,,,,,********\n    {0}    ,,,,,,,,,,,,,,,,,**************\n    {0}  ,,,,,,,,,,,,                  *****\n    {0} ,,,,,,,,,      {1}RRRRRRRRRRRRRRRR   {0}***\n    {0},,,,,,,,,       {1}RRRRRRRRRRRRRRRRRRR {0}***\n    {0},,,,,,,,        {1}RRRRRRRRRRRRRRRRRRRR {0}//\n    {0},,,,,,*         {1}RRRRRRR      RRRRRRR {0}//\n    {0},,,*****        {1}RRRRRRR     RRRRRRR  {0}//\n    {0} ********       {1}RRRRRRRRRRRRRRRRRR  {0}//\n    {0}   *********    {1}RRRRRRRRRRRRRR    {0}//\n    {0}      **********{1}RRRRRRR   RRRRRRR\n    {0}         *******{1}RRRRRRR    RRRRRRR\n    {1}                RRRRRRR     RRRRRRRR\n  colors:\n    ansi:\n      - white\n      - blue\n    chip: \"#198CE7\"\n  icon: '\\u{E68A}'\nRacket:\n  type: programming\n  ascii: |\n    {0}            {2}.:--::////::--.`\n    {0}        {1}`/yNMMNho{2}////////////:.\n    {0}      {1}`+NMMMMMMMMmy{2}/////////////:`\n    {0}    `-:::{1}ohNMMMMMMMNy{2}/////////////:`\n    {0}   .::::::::{1}odMMMMMMMNy{2}/////////////-\n    {0}  -:::::::::::{1}/hMMMMMMMmo{2}////////////-\n    {0} .::::::::::::::{1}oMMMMMMMMh{2}////////////-\n    {0}`:::::::::::::{1}/dMMMMMMMMMMNo{2}///////////`\n    {0}-::::::::::::{1}sMMMMMMmMMMMMMMy{2}//////////-\n    {0}-::::::::::{1}/dMMMMMMs{0}:{1}+NMMMMMMd{2}/////////:\n    {0}-:::::::::{1}+NMMMMMm/{0}:::{1}/dMMMMMMm+{2}///////:\n    {0}-::::::::{1}sMMMMMMh{0}:::::::{1}dMMMMMMm+{2}//////-\n    {0}`:::::::{1}sMMMMMMy{0}:::::::::{1}dMMMMMMm+{2}/////`\n    {0} .:::::{1}sMMMMMMs{0}:::::::::::{1}mMMMMMMd{2}////-\n    {0}  -:::{1}sMMMMMMy{0}::::::::::::{1}/NMMMMMMh{2}//-\n    {0}   .:{1}+MMMMMMd{0}::::::::::::::{1}oMMMMMMMo{2}-\n    {0}    {1}`yMMMMMN/{0}:::::::::::::::{1}hMMMMMh.\n    {0}      {1}-yMMMo{0}::::::::::::::::{1}/MMMy-\n    {0}        {1}`/s{0}::::::::::::::::::{1}o/`\n    {0}            ``.---::::---..`\n  colors:\n    ansi:\n      - red\n      - white\n      - blue\n    chip: \"#3C5CAA\"\nRaku:\n  type: programming\n  ascii: |\n    {0} +@8DM#8W,\n    {0}#DM\"{1},ypy,{0}\"8#\n    {0}DDU {1}8M]N8u{0} DM\n    {0}8DD {1}TMD8M,{0}8M  {4}8\n    {0} 8D#=e@8MM^   {4}8\n    {0}  *MDw  {4},.,+#M`\n    {0}    \"8#                   {0},e88DDDD8m,\n    {0}      \"8 {3}x33#,           {0}z8D#M`9Dw \"9DW\n    {3}        JE   E {2}BBW.     {0}DM {1}p#Kw {0}D8   {0}JDM\n    {1} #pp#pr {3} JRFMy{2}#EEF{3}y#Rr, {0}DM{1} MD]8 {0}9DM {4}`N\n    {1}Z#{0}8#D]{1}D# {2}EE#EEEEE{3}`EW  E {0}8M,{1}\"RM`{0} 8DM  {4}D\n    {1}88{0}8]D]{1}D8 {2}EEM{1}#{2}EEE{1}E{2}Bp{3}TFF^  {0}*M888#`  {4}#M\n    {1}*#88EE8  {2} RRk{1}8BMM{2}#EEE,        {4}'^'\n    {1}   ``      {2}'\"F*FFF*  {1},yw,\n    {1}                  ,#]{0}[D8{1}8W\n    {1}                  k8{0}]DDN8{1}8L\n    {1}                  '8E{0}$DE8{1}8M\n    {1}                    R#E#R^\n  colors:\n    ansi:\n      - blue\n      - red\n      - yellow\n      - white\n      - green\n    hex:\n      - \"#5B00FD\"\n      - \"#FF005E\"\n      - \"#F3FF27\"\n      - \"#FFFFFF\"\n      - \"#00FF39\"\n    chip: \"#0000FB\"\nRazor:\n  type: markup\n  ascii: |\n    {0}                                     @\n    {0}                               @    @@\n    {0}                            @@@@   @@@@\n    {0}           @@@@@@@@@@@@@@@@@@@@  @@@@@@\n    {0}       @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n    {0}     @@@@              @@@@@@@@@@@@@@@@\n    {0}   @@@     @@@@@@@@@     @@@@@@@@@@@@@@\n    {0}  @@@   @@@@@@@@@@@@@@@   @@@@@@@@@@@@\n    {0} @@@   @@@@         @@@@   @@@@@@@@@@\n    {0}@@@   @@@   @@@@@@   @@@@   @@@@@@@@\n    {0}@@@   @@   @@@@@@@   @@@@   @@@@@@@\n    {0}@@@   @@@   @@@@@@   @@@@   @@@@@@\n    {0} @@@   @@@           @@    @@@@@\n    {0}  @@@   @@@@@    @@      @@@@@\n    {0}   @@@   @@@@@@@@@@@@@@@@@@@    @\n    {0}    @@@      @@@@@@@@@       @@\n    {0}      @@@@@             @@@@@\n    {0}         @@@@@@@@@@@@@@@@\n  colors:\n    ansi:\n      - magenta\n    hex:\n      - \"#5c2994\"\n    chip: \"#512be4\"\nRenpy:\n  type: programming\n  ascii: |\n    {0}       ++++++\n    {0}     ++++++++++\n    {0}    ++++++++++++\n    {0}   ++++++++++++++\n    {0}   ++++++++++++++++\n    {0}  +++++++++++++{1}***{0}+++\n    {0}  +++++++{1}***********\n    {0} +++++{1}**************\n    {0}+++{1}*****************\n    {0}++{1}********{2}#{1}**********\n    {1} *****{2}#{1}*{2}#{1}*{2}#{1}****{2}#{1}*{2}#{1}***\n    {1} *****{2}#{1}*{2}###{1}*{2}#{1}**{2}#{1}*{2}#{1}***\n    {1} *****{2}##########{1}*{2}#{1}***\n    {1} *****{2}##{3}**{2}####{3}**{2}##{1}***\n    {1} *****{2}##{3}**{2}####{3}**{2}##{1}***\n    {1} *****{2}############{1}***\n    {1} *****{2}#####{5}--{2}####{1}***\n    {1}  **{4}#####{2}#######{1}**\n    {4}   #{5}**{4}#{5}**{4}#{2}###{4}####\n    {4}   #{5}**{4}#{5}**{4}#{2}###{4}######\n    {4}   ##{5}*{4}#{5}*{4}##{6}++++{4}######\n    {4}    #####{6}++++++{4}#####\n    {4}     ###{6}++++++++{4}####\n    {6}     +++++++++++{4}####\n    {6}     ++++++++++{4}####\n  colors:\n    ansi:\n      - white\n      - red\n      - white\n      - blue\n      - yellow\n      - white\n      - magenta\n    hex:\n      - \"#EADBCC\"\n      - \"#FF7F7F\"\n      - \"#FBEEE2\"\n      - \"#495F8E\"\n      - \"#FAE45A\"\n      - \"#FFFFFF\"\n      - \"#B5A396\"\n    chip: \"#FF7F7F\"\n  icon: '\\u{E88D}'\nRuby:\n  type: programming\n  ascii: |\n    {0}                -+sdmhmMMhyhNMMddm`\n    {0}            .smdy+:`  `-smmNy/    :h\\\n    {0}          -yNs.          mmhmo.    Md\n    {0}        -hNo`           oM- .oNh:  :s\n    {0}      :dm+`            .Ns    `/dmo.d\n    {0}     +Mo`              hN        yMMM\n    {0}   `sN/              /dMmsssoo++///NM\n    {0}  `hN-             +md/sM.        hNM\n    {0} .dm.            +mh:  .Ns      .dm+M\n    {0} +My          .oNy-     sM.    :Nd.+M\n    {0} +MM+     .:/sNd-       .Ns   sNo  sM\n    {0}`+MyMyhdddysmMoydmhs+:smdsM.+Nh-   hN\n    {0}`+M.MM:`   -Mo           hMNm/     md\n    {0}`+M  hm.   dm`         `+Nmdm+     My\n    {0}`/M:  md` /M/        -sNy:  -yNs. .Mo\n    {0} `Nh   Nh`Nh      :smd+.      .sNyoM/\n    {0}   `h:mh:MmNss:+sdNds:-::///++oosyN-\n  colors:\n    ansi:\n      - red\n    hex:\n      - \"#F30301\"\n    chip: \"#701516\"\n  icon: '\\u{E739}'\nRust:\n  type: programming\n  ascii: |\n    {0}                 R RR RR\n    {0}              R RRRRRRRR R          R\n    {0} R RR       R RRRRRRRRRRRRR R      RR\n    {0}rR RRR    R RRRRRRRRRRRRRRRRR R   RRR R\n    {0}RRR RR   RRRRRRRRRRRRRRRRRRRRRRR  RRRRR\n    {0} RRRRR  RRRRRRRRRRRRRRRRRRRRRRRR  RRRR\n    {0}  RRR RRRRRRRRRRRRRRRRRRRRRRRRRRRR RR\n    {0}    R  RRRRRRRRRR{1}=  {0}RR{1} = {0}RRRRRRRRRRR\n    {0}     RRRRRRRRRRRR{1}=  {0}RR{1} = {0}RRRRRRRRRR\n    {0}      RRRRRRRRRRR   RR   RRRRRRRRRR\n    {0}     RR==RRRRRRRRRRRRRRRRRRRRRR===RR\n    {0}     RR =  ==RRRRRRR  RRRRRR==  = RR\n    {0}      RR =     ===========     = RR\n    {0}       RR                        R\n    {0}        R                       R\n    {0}         R\n  colors:\n    ansi:\n      - red\n      - white\n    hex:\n      - \"#E43717\"\n      - \"#FFFFFF\"\n    chip: \"#DEA584\"\n  icon: '\\u{E7A8}'\nSass:\n  type: markup\n  ascii: |\n    {0}            ,wppbbbbbp,\n    {0}       ,wpb@KP\"``` ``\"T@b\n    {0}    ,pb@P\"`            @@\n    {0}  ,b@P`               /@P\n    {0} p@b`               ,bK`\n    {0}{@@      'w,  ,,wpbP*`\n    {0} 0@b        ````      ,pp  ;@@\n    {0}  \"0bw        ,bPK   ,K@L /PT@\n    {0}     \"0b, ,,pbP  @b .b Tb{\"  $bP\"\"\"*Tb,\n    {0}      ,/b@P &@  0@M.b  ,@K ,P @L      `b\n    {0}   ,pb\"  Ib @@ &h@bP ,pCpP bb*`       /`\n    {0}  p@`   ,@` `bb` T\"  \"\"`\n    {0}  @@w,pbK`\n    {0}  `***^\n  colors:\n    ansi:\n      - magenta\n    hex:\n      - \"#CD6799\"\n    chip: \"#A53B70\"\n  icon: '\\u{E74B}'\nScala:\n  type: programming\n  ascii: |\n    {0}                        +\n    {0}                      +++\n    {0}          +++++++++++++++\n    {0}+++++++++++++++++++++++++\n    {0}+++++++++++++++++++++++++\n    {0}+++++++++++++++++++++++++\n    {0}+++++++++++++++++{1}-------\n    {0}+++{1}-------------------{0}+++\n    {1}        ---{0}++++++++++++++\n    {0}+++++++++++++++++++++++++\n    {0}+++++++++++++++++++++++++\n    {0}+++++++++++++++++++++++++\n    {0}+++++++++++++++++{1}-------\n    {0}+++{1}-------------------{0}+++\n    {1}        ---{0}++++++++++++++\n    {0}+++++++++++++++++++++++++\n    {0}+++++++++++++++++++++++++\n    {0}+++++++++++++++++++++++++\n    {0}+++++++++++++++\n    {0}+++\n  colors:\n    ansi:\n      - red\n      - red\n    hex:\n      - \"#DF3F3D\"\n      - \"#7F0C1D\"\n    chip: \"#C22D40\"\n  icon: '\\u{E737}'\nScheme:\n  type: programming\n  ascii: |\n    {0}  ////\n    {0} //  //\n    {0} /    //\n    {0}       //\n    {0}        //\n    {0}         //\n    {0}        ////\n    {0}       /// //\n    {0}      ///   //\n    {0}     ///     //\n    {0}    ///       //    /\n    {0}   ///         //  //\n    {0}  ///           ////\n  colors:\n    ansi:\n      - white\n    hex:\n      - \"#555555\"\n    chip: \"#1E4AEC\"\n  icon: '\\u{E6B1}'\nSh:\n  type: programming\n  ascii: |\n    {0}              ___       ___        ___\n    {0}             ####      ####       ####\n    {0}            ####      ####       ####\n    {0}      _____####______####___    ####\n    {0}     #######################   ####\n    {0}    #######################   ####\n    {0}        ####      ####       ####\n    {0}       ####      ####       ####\n    {0}  ____####______####____   ####\n    {0} #######################  ####\n    {0}#######################  ___\n    {0}   ####      ####       ####\n    {0}  ####      ####       ####\n    {0} ####      ####       ####\n  colors:\n    ansi:\n      - green\n    chip: \"#89E051\"\n  icon: '\\u{f1183}'\n  serialization: shell\nSlint:\n  type: markup\n  ascii: |\n    {0}                        s\n    {0}                     ss\n    {0}                  sss\n    {0}               ssss\n    {0}            ssss\n    {0}         sssss\n    {0}      ssssss\n    {0}   ssssssss\n    {0}sssssssss    s\n    {0}    sssssss    sss\n    {0}        sssss    sssss\n    {0}            sss    sssssss\n    {0}                s    sssssssss\n    {0}                   ssssssss\n    {0}                 ssssss\n    {0}               sssss\n    {0}             ssss\n    {0}           ssss\n    {0}         sss\n    {0}       ss\n    {0}     s\n  colors:\n    ansi:\n      - blue\n    hex:\n      - \"#2379F4\"\n    chip: \"#2379F4\"\nSolidity:\n  type: programming\n  ascii: |\n    {0}MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\n    {0}MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\n    {0}MMMMMMMMMMMM{2}SS{3}SSSSSSSSSS{4}SS{0}MMMMMMMMMMMM\n    {0}MMMMMMMMMMM{2}SSSS{3}SSSSSSSS{4}SSSS{0}MMMMMMMMMMM\n    {0}MMMMMMMMMM{2}SSSSSS{3}SSSSSS{4}SSSSSS{0}MMMMMMMMMM\n    {0}MMMMMMMMM{2}SSSSSSSS{3}SSSS{4}SSSSSSSS{0}MMMMMMMMM\n    {0}MMMMMMMM{2}SSSSSSSSSS{3}SS{4}SSSSSSSSSS{0}MMMMMMMM\n    {0}MMMMMMMM{1}SSSSSSSSSS{0}MMMMMMMMMMMMMMMMMMMM\n    {0}MMMMMMMMM{1}SSSSSSSS{0}MMMMMMMMMMMMMMMMMMMMM\n    {0}MMMMMMMMMM{1}SSSSSS{0}MMMMMMMM{1}SS{0}MMMMMMMMMMMM\n    {0}MMMMMMMMMMM{1}SSSS{0}MMMMMMMM{1}SSSS{0}MMMMMMMMMMM\n    {0}MMMMMMMMMMMM{1}SS{0}MMMMMMMM{1}SSSSSS{0}MMMMMMMMMM\n    {0}MMMMMMMMMMMMMMMMMMMMM{1}SSSSSSSS{0}MMMMMMMMM\n    {0}MMMMMMMMMMMMMMMMMMMM{1}SSSSSSSSSS{0}MMMMMMMM\n    {0}MMMMMMMM{4}SSSSSSSSSS{3}SS{2}SSSSSSSSSS{0}MMMMMMMM\n    {0}MMMMMMMMM{4}SSSSSSSS{3}SSSS{2}SSSSSSSS{0}MMMMMMMMM\n    {0}MMMMMMMMMM{4}SSSSSS{3}SSSSSS{2}SSSSSS{0}MMMMMMMMMM\n    {0}MMMMMMMMMMM{4}SSSS{3}SSSSSSSS{2}SSSS{0}MMMMMMMMMMM\n    {0}MMMMMMMMMMMM{4}SS{3}SSSSSSSSSS{2}SS{0}MMMMMMMMMMMM\n    {0}MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\n    {0}MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\n  colors:\n    ansi:\n      - white\n      - black\n      - black\n      - black\n      - black\n    hex:\n      - \"#FFFFFF\"\n      - \"#2E2E2E\"\n      - \"#1A1A1A\"\n      - \"#333333\"\n      - \"#515151\"\n    chip: \"#AA6746\"\n  icon: '\\u{E8A6}'\nSql:\n  type: data\n  ascii: |\n    {0}   _..------.._\n    {0}.-~            ~-.\n    {0}|                |\n    {0}|\"-..________..-\"|\n    {0}|                | {1} ____   ___  _\n    {0}|                | {1}/ ___| / _ \\| |\n    {0}|\"-..________..-\"| {1}\\___ \\| | | | |\n    {0}|                | {1} ___) | |_| | |___\n    {0}|                | {1}|____/ \\__\\_|_____|\n    {0}|\"-..________..-\"|\n    {0}|                |\n    {0}|                |\n    {0} \"-..________..-\"\n  colors:\n    ansi:\n      - cyan\n      - yellow\n    chip: \"#E38C00\"\n  icon: '\\u{E737}'\nSvelte:\n  type: markup\n  ascii: |\n    {0}SSSSSSSSSSSSSSSSSS{1}sssssssssss{0}SSSSSSSS\n    {0}SSSSSSSSSSSSSSS{1}sssssssssssssssss{0}SSSSS\n    {0}SSSSSSSSSSS{1}sssssssssss{0}SSSS{1}ssssssss{0}SSS\n    {0}SSSSSSSS{1}ssssssssss{0}SSSSSSSSSSS{1}sssssss{0}S\n    {0}SSSSS{1}sssssssss{0}SSSSSSSSSSSSSSSSS{1}sssss{0}S\n    {0}SSS{1}ssssssss{0}SSSSSSSSSS{1}sssss{0}SSSSSS{1}ssss{0}S\n    {0}S{1}sssssss{0}SSSSSSSSSS{1}sssssssss{0}SSSSS{1}ssss{0}S\n    {0}S{1}sssss{0}SSSSSSSSS{1}sssssssssssssssssssss{0}S\n    {0}S{1}sssss{0}SSSSSS{1}ssssssss{0}SSSSSS{1}ssssssssss{0}S\n    {0}S{1}sssss{0}SSSSS{1}ssssss{0}SSSSSSSSSSSS{1}ssssss{0}SS\n    {0}S{1}sssss{0}SSSSSSSSSSSSSSSSSSSSSSSSS{1}sssss{0}S\n    {0}SS{1}ssssss{0}SSSSSSSSSSSS{1}ssssss{0}SSSSS{1}sssss{0}S\n    {0}S{1}ssssssssss{0}SSSSSS{1}ssssssss{0}SSSSSS{1}sssss{0}S\n    {0}S{1}sssssssssssssssssssss{0}SSSSSSSSS{1}sssss{0}S\n    {0}S{1}ssss{0}SSSSS{1}sssssssss{0}SSSSSSSSSS{1}ssssss{0}SS\n    {0}S{1}ssss{0}SSSSSS{1}sssss{0}SSSSSSSSSS{1}ssssssss{0}SSS\n    {0}S{1}sssss{0}SSSSSSSSSSSSSSSSS{1}sssssssss{0}SSSSS\n    {0}S{1}sssssss{0}SSSSSSSSSSS{1}ssssssssss{0}SSSSSSSS\n    {0}SSS{1}ssssssss{0}SSSS{1}sssssssssss{0}SSSSSSSSSSS\n    {0}SSSSS{1}sssssssssssssssss{0}SSSSSSSSSSSSSSS\n    {0}SSSSSSSS{1}sssssssssss{0}SSSSSSSSSSSSSSSSSS\n  colors:\n    ansi:\n      - red\n      - white\n    hex:\n      - \"#FF3C00\"\n      - \"#FFFFFF\"\n    chip: \"#FF3E00\"\n  icon: '\\u{E697}'\nSvg:\n  type: data\n  ascii: |\n    {0}                 ......\n    {0}               ....{1}--{0}....\n    {0}        ....  {0}...{1}------{0}...  {0}....\n    {0}     ............{1}------{0}............\n    {0}    ...{1}-----{0}......{1}----{0}......{1}-----{0}...\n    {0}    ...{1}------{0}.....{1}----{0}.....{1}------{0}...\n    {0}    ...{1}--------{0}...{1}----{0}...{1}--------{0}...\n    {0}     .....{1}-------{0}.{1}----{0}.{1}-------{0}.....\n    {0}  ..........{1}----------------{0}..........\n    {0} ...{1}---{0}.......{1}------------{0}.......{1}---{0}...\n    {0}...{1}----------------------------------{0}...\n    {0}...{1}----------------------------------{0}...\n    {0} ...{1}---{0}.......{1}------------{0}.......{1}---{0}...\n    {0}  ..........{1}----------------{0}..........\n    {0}     .....{1}-------{0}.{1}----{0}.{1}-------{0}.....\n    {0}    ...{1}--------{0}...{1}----{0}...{1}--------{0}...\n    {0}    ...{1}------{0}.....{1}----{0}.....{1}------{0}...\n    {0}    ...{1}-----{0}......{1}----{0}......{1}-----{0}...\n    {0}     ............{1}------{0}............\n    {0}        ....  {0}...{1}------{0}...  {0}....\n    {0}               ....{1}--{0}....\n    {0}                 ......\n  colors:\n    ansi:\n      - white\n      - yellow\n    hex:\n      - \"#FFFFFF\"\n      - \"#EBA71F\"\n    chip: \"#FF9900\"\n  icon: '\\u{f0721}'\nSwift:\n  type: programming\n  ascii: |\n    {0}                         :\n    {0}                          ::\n    {1}                           :::\n    {1}          :                ::::\n    {2}     :     :                ::::\n    {2}      :     ::              :::::\n    {3}       ::    :::             :::::\n    {3}        :::    :::           ::::::\n    {4}          :::   :::          :::::::\n    {4}           ::::  ::::        :::::::\n    {5}            :::::::::::      ::::::::\n    {5}              :::::::::::   :::::::::\n    {5}               ::::::::::::::::::::::\n    {6}                :::::::::::::::::::::\n    {6}                  :::::::::::::::::::\n    {6}:                   :::::::::::::::::\n    {7} ::                   ::::::::::::::\n    {7}   ::::              ::::::::::::::::\n    {7}    ::::::::::::::::::::::::::::::::::\n    {8}      :::::::::::::::::::::::::::::::::\n    {8}        :::::::::::::::::::::::::::::::\n    {8}          ::::::::::::::::::::::   :::::\n    {9}             .::::::::::::::.         ::\n    {9}\n  colors:\n    ansi:\n      - red\n      - red\n      - red\n      - red\n      - red\n      - red\n      - red\n      - red\n      - red\n      - red\n    hex:\n      - \"#F88134\"\n      - \"#F97732\"\n      - \"#F96D30\"\n      - \"#FA632E\"\n      - \"#FA592C\"\n      - \"#FB502A\"\n      - \"#FB4628\"\n      - \"#FC3C26\"\n      - \"#FC3224\"\n      - \"#FD2822\"\n    chip: \"#F05138\"\n  icon: '\\u{E755}'\nSystemVerilog:\n  type: programming\n  ascii: |\n    {0}     _.._   _.._   _.._   _.._\n    {0}     _.._   _.._   _.._   _.._\n    {0} .................................\n    {0}.               {1}----              {0}.\n    {0}.          {1}--------------         {0}.\n    {0}.        {1}----       ---------     {0}.\n    {0}.      {1}---                -----   {0}.\n    {0}.     {1}-    #####  #     #  -----  {0}.\n    {0}.         {1}#     # #     #         {0}.\n    {0}.         {1}#       #     #         {0}.\n    {0}.          {1}#####  #     #         {0}.\n    {0}.               {1}#  #   #          {0}.\n    {0}.         {1}#     #   # #           {0}.\n    {0}.  {1}-----   #####     #      -     {0}.\n    {0}.   {1}-----                ---      {0}.\n    {0}.     {1}---------       ----        {0}.\n    {0}.         {1}--------------          {0}.\n    {0}.              {1}----               {0}.\n    {0} .................................\n    {0}     _.._   _.._   _.._   _.._\n    {0}     _.._   _.._   _.._   _.._\n  colors:\n    ansi:\n      - blue\n      - white\n    chip: \"#DAE1C2\"\n  icon: '\\u{F4BC}'\nTcl:\n  type: programming\n  ascii: |\n    {0}          //\n    {2}      . /{0}////\n    {2}     /{0}/////// .\n    {2}     //{0}//{1}/{0}/////\n    {2}    //{0}//{1}/{0}/////\n    {2}  . //{0}/{1}//{0}////\n    {2}  //{0}//{1}//{0}////\n    {2}  //{0}/{1}//{0}/////\n    {2}  /{0}//{1}//{0}////\n    {2}  /{0}/{1}//{0}////  .\n    {2}. /{0}/{1}//{0}/////\n    {2} /{0}/{1}//{0}////\n    {2}  /{1}//{0}//\n    {0}   {1}//\n    {0}   {1}//\n    {0}    {1}/\n    {0}     {1}/\n  colors:\n    ansi:\n      - blue\n      - white\n      - cyan\n    chip: \"#E4CC98\"\n  icon: '\\u{e7c4}'\nTex:\n  type: markup\n  ascii: |\n    {0}$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$\n    {0}$$$            $$$$$$$      $$$$$     $$\n    {0}$$  $$$$  $$$$  $$$$$$$$$  $$$$$$$ $$$$$\n    {0}$$ $$$$$  $$$$$ $$$$$$$$$$  $$$$$ $$$$$$\n    {0}$$ $$$$$  $$$          $$$$  $$$ $$$$$$$\n    {0}$$ $$$$$  $$$$$  $$$$$  $$$$  $ $$$$$$$$\n    {0}$$$$$$$$  $$$$$  $$$$$$ $$$$$  $$$$$$$$$\n    {0}$$$$$$$$  $$$$$  $$$$$$ $$$$$  $$$$$$$$$\n    {0}$$$$$$$$  $$$$$  $$$$$$$$$$$$   $$$$$$$$\n    {0}$$$$$$$$  $$$$$  $$$ $$$$$$$ $$  $$$$$$$\n    {0}$$$$$$$$  $$$$$      $$$$$$ $$$$  $$$$$$\n    {0}$$$$$$$$  $$$$$  $$$ $$$$$ $$$$$$  $$$$$\n    {0}$$$$$$$$  $$$$$  $$$$$$$$ $$$$$$$$  $$$$\n    {0}$$$$$        $$  $$$$$     $$$$$$     $$\n    {0}$$$$$$$$$$$$$$$  $$$$$$$ $$$$$$$$$$$$$$$\n    {0}$$$$$$$$$$$$$$$  $$$$$$ $$$$$$$$$$$$$$$$\n    {0}$$$$$$$$$$$$$           $$$$$$$$$$$$$$$$\n    {0}$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$\n  colors:\n    ansi:\n      - white\n    chip: \"#3D6117\"\n  icon: '\\u{E69B}'\nText:\n  type: prose\n  ascii: |\n    {0} -----------------\n    {0}| .txt ========== |\n    {0}| =============== |\n    {0}| =============== |\n    {0}| =============== |\n    {0}| =============== |\n    {0}| ============    |\n    {0}|                 |\n    {0}| =============== |\n    {0}| =============== |\n    {0}| =============== |\n    {0}| =============== |\n    {0}| =============== |\n    {0} -----------------\n  colors:\n    ansi:\n      - white\n    chip: \"#ffffff\"\n  icon: '\\u{f09a8}'\nToml:\n  type: data\n  ascii: |\n    {0}[[[[[[[[                    ]]]]]]]]\n    {0}[[[[[[[[                    ]]]]]]]]\n    {0}[[[[                            ]]]]\n    {0}[[[[      {1}TTTTTTTTTTTTTTTT{0}      ]]]]\n    {0}[[[[      {1}TTTTTTTTTTTTTTTT{0}      ]]]]\n    {0}[[[[            {1}TTTT{0}            ]]]]\n    {0}[[[[            {1}TTTT{0}            ]]]]\n    {0}[[[[            {1}TTTT{0}            ]]]]\n    {0}[[[[            {1}TTTT{0}            ]]]]\n    {0}[[[[            {1}TTTT{0}            ]]]]\n    {0}[[[[            {1}TTTT{0}            ]]]]\n    {0}[[[[            {1}TTTT{0}            ]]]]\n    {0}[[[[            {1}TTTT{0}            ]]]]\n    {0}[[[[            {1}TTTT{0}            ]]]]\n    {0}[[[[            {1}TTTT{0}            ]]]]\n    {0}[[[[            {1}TTTT{0}            ]]]]\n    {0}[[[[[[[[                    ]]]]]]]]\n    {0}[[[[[[[[                    ]]]]]]]]\n  colors:\n    ansi:\n      - red\n      - white\n    hex:\n      - \"#9C4221\"\n      - \"#FFFFFF\"\n    chip: \"#9C4221\"\n  icon: '\\u{E6B2}'\nTsx:\n  type: programming\n  ascii: |\n    {0}TSXTSXTSXTSXTSXTSXTSXTSXTSXTSXTSXTSX{1}TSX\n    {0}TSXTSXTSXTSXTSXTSXTSXTSXTSXTSXTSXTS{1}XTSX\n    {0}TSXTSXTSXTSXTSXTSXTSXTSXTSXTSXTSXT{1}SXTSX\n    {0}TSXTSXTSXTSXTSXTSXTSXTSXTSXTSXTSX{1}TSXTSX\n    {0}TSXTSXTSXTSXTSXTSXTSXTSXTSXTSXTS{1}XTSXTSX\n    {0}TSXTSXTSXTSXTSXTSXTSXTSXTSXTSXT{1}SXTSXTSX\n    {0}TSXTSXTSXTSXTSXTSXTSXTSXTSXTSX{1}TSXTSXTSX\n    {0}TSXTSXTSXTSXTSXTSXTSXTSXTSXTS{1}XTSXTSXTSX\n    {0}TSXTSXTSXTSXTSXTSXTSXTSXTSXT{1}SXTSXTSXTSX\n    {0}TSXTSXTSXTSXTSXTSXTSXTSXTSX{1}TSXTSXTSXTSX\n    {0}TS{2}XTSXTSXTS{0}XTSX{2}TSXTSX{0}TSXTS{1}X{2}TSX{1}TSX{2}TSX{1}TSX\n    {0}TSXTS{2}XTS{0}XTSXTS{2}XTS{0}XTSXTSXT{1}SXT{2}SXT{1}S{2}XTS{1}XTSX\n    {0}TSXTS{2}XTS{0}XTSXTSX{2}TSX{0}TSXTSX{1}TSXTSX{2}TSX{1}TSXTSX\n    {0}TSXTS{2}XTS{0}XTSXTSXT{2}SXT{0}SXTS{1}XTSXTS{2}XTSXT{1}TSTSX\n    {0}TSXTS{2}XTS{0}XTSXTSXTS{2}XTS{0}XT{1}SXTSXT{2}SXT{1}S{2}XTS{1}XTSX\n    {0}TSXTS{2}XTS{0}XTSXT{2}SXTSXT{0}SX{1}TSXTSX{2}TSX{1}TSX{2}TSX{1}TSX\n    {0}TSXTSXTSXTSXTSXTSXTS{1}XTSXTSXTSXTSXTSXTSX\n    {0}TSXTSXTSXTSXTSXTSXT{1}SXTSXTSXTSXTSXTSXTSX\n  colors:\n    ansi:\n      - cyan\n      - magenta\n      - white\n    hex:\n      - \"#007ACC\"\n      - \"#8A53A6\"\n      - \"#FFFFFF\"\n    chip: \"#2B7489\"\n  icon: '\\u{E69D}'\nTypeScript:\n  type: programming\n  ascii: |\n    {0}TSTSTSTSTSTSTSTSTSTSTSTSTSTSTSTSTSTSTS\n    {0}TSTSTSTSTSTSTSTSTSTSTSTSTSTSTSTSTSTSTS\n    {0}TSTSTSTSTSTSTSTSTSTSTSTSTSTSTSTSTSTSTS\n    {0}TSTSTSTSTSTSTSTSTSTSTSTSTSTSTSTSTSTSTS\n    {0}TSTSTSTSTSTSTSTSTSTSTSTSTSTSTSTSTSTSTS\n    {0}TSTSTSTSTSTSTSTSTSTSTSTSTSTSTSTSTSTSTS\n    {0}TSTSTSTSTSTSTSTSTSTSTSTSTSTSTSTSTSTSTS\n    {0}TSTSTSTS{1}TSTSTSTSTSTSTS{0}TSTS{1}TSTSTS{0}TSTSTS\n    {0}TSTSTSTS{1}TSTSTSTSTSTSTS{0}TS{1}TSTSTSTSTS{0}TSTS\n    {0}TSTSTSTSTSTST{1}STST{0}STSTSTS{1}TSTST{0}TSTSTSTST\n    {0}TSTSTSTSTSTST{1}STST{0}STSTSTST{1}STSTS{0}TSTSTSTS\n    {0}TSTSTSTSTSTST{1}STST{0}STSTSTSTST{1}STSTS{0}TSTSTS\n    {0}TSTSTSTSTSTST{1}STST{0}STSTSTSTSTST{1}STSTS{0}TSTS\n    {0}TSTSTSTSTSTST{1}STST{0}STSTSTSTSTSTS{1}TSTST{0}TST\n    {0}TSTSTSTSTSTST{1}STST{0}STSTSTST{1}STSTSTSTST{0}STS\n    {0}TSTSTSTSTSTST{1}STST{0}STSTSTSSTS{1}TSTSTS{0}TSTST\n    {0}TSTSTSTSTSTSTSTSTSTSTSTSTSTSTSTSTSTSTS\n    {0}TSTSTSTSTSTSTSTSTSTSTSTSTSTSTSTSTSTSTS\n  colors:\n    ansi:\n      - cyan\n      - white\n    hex:\n      - \"#007ACC\"\n      - \"#FFFFFF\"\n    chip: \"#2B7489\"\n  icon: '\\u{E69D}'\nTypst:\n  type: markup\n  ascii: |\n    {0}ttttttttttttttttttttttttttttttttttttttt\n    {0}ttttttttttttttttttttttttttttttttttttttt\n    {0}ttttttttttttttttttttttttttttttttttttttt\n    {0}ttttttttttttttttttttttttttttttttttttttt\n    {0}ttttttttttttttt{1}tttt{0}tttttttttttttttttttt\n    {0}ttttttttttttt{1}tttttt{0}tttttttttttttttttttt\n    {0}tttttttttt{1}ttttttttttttttt{0}tttttttttttttt\n    {0}tttttttttt{1}tttttttttttttt{0}ttttttttttttttt\n    {0}ttttttttttttt{1}tttttt{0}tttttttttttttttttttt\n    {0}ttttttttttttt{1}tttttt{0}tttttttttttttttttttt\n    {0}ttttttttttttt{1}tttttt{0}tttttttttttttttttttt\n    {0}ttttttttttttt{1}tttttt{0}tttttttttttttttttttt\n    {0}ttttttttttttt{1}tttttt{0}tttttttttttttttttttt\n    {0}ttttttttttttt{1}tttttt{0}tttttttttttttttttttt\n    {0}ttttttttttttt{1}tttttt{0}tttttttttttttttttttt\n    {0}ttttttttttttt{1}ttttttt{0}tttttttt{1}t{0}tttttttttt\n    {0}tttttttttttttt{1}tttttttttttttt{0}ttttttttttt\n    {0}tttttttttttttttt{1}tttttttttt{0}ttttttttttttt\n    {0}ttttttttttttttttttttttttttttttttttttttt\n    {0}ttttttttttttttttttttttttttttttttttttttt\n    {0}ttttttttttttttttttttttttttttttttttttttt\n    {0}ttttttttttttttttttttttttttttttttttttttt\n  colors:\n    ansi:\n      - cyan\n      - white\n    hex:\n      - \"#239DAD\"\n      - \"#FFFFFF\"\n    chip: \"#239DAD\"\n  icon: '\\u{F37F}'\nVala:\n  type: programming\n  ascii: |\n    {0} ###################################\n    {0}#####################################\n    {0}#####################################\n    {0}############{1}######{0}##########{1}##{0}#######\n    {0}##########{1}###{0}#{1}####{0}#########{1}##{0}########\n    {0}########{1}###{0}###{1}####{0}########{1}##{0}#########\n    {0}#######{1}###{0}####{1}####{0}#######{1}##{0}##########\n    {0}######{1}###{0}#####{1}####{0}######{1}##{0}###########\n    {0}######{1}###{0}#####{1}####{0}#####{1}##{0}############\n    {0}########{1}#{0}#####{1}####{0}####{1}##{0}#############\n    {0}##############{1}####{0}###{1}##{0}##############\n    {0}##############{1}####{0}##{1}##{0}###############\n    {0}##############{1}####{0}#{1}##{0}################\n    {0}##############{1}######{0}#################\n    {0}##############{1}#####{0}##################\n    {0}##############{1}####{0}###################\n    {0}#####################################\n    {0}#####################################\n    {0} ###################################\n  colors:\n    ansi:\n      - magenta\n      - white\n    chip: \"#A56DE2\"\n  icon: '\\u{E8D1}'\nVerilog:\n  type: programming\n  ascii: |\n    {0}        _.._    _.._    _.._\n    {0}        _.._    _.._    _.._\n    {0}        _.._    _.._    _.._\n    {0}     _......................._\n    {0}    _.{1}-----             -----{0}._\n    {0}_..._.{1} ---               --- {0}._..._\n    {0}_..._.{1}  ---             ---  {0}._..._\n    {0}    _.{1}   ---           ---   {0}._\n    {0}    _.{1}    ---         ---    {0}._\n    {0}_..._.{1}     ---       ---     {0}._..._\n    {0}_..._.{1}      ---     ---      {0}._..._\n    {0}    _.{1}       ---   ---       {0}._\n    {0}    _.{1}        --- ---        {0}._\n    {0}_..._.{1}         -----         {0}._..._\n    {0}_..._.{1}          ---          {0}._..._\n    {0}    _.{1}           -           {0}._\n    {0}     _......................._\n    {0}        _.._    _.._    _.._\n    {0}        _.._    _.._    _.._\n    {0}        _.._    _.._    _.._\n  colors:\n    ansi:\n      - white\n      - magenta\n    chip: \"#b2b7f8\"\n  icon: '\\u{F4BC}'\nVhdl:\n  type: programming\n  ascii: |\n    {0}        |  |  |  |\n    {0}       {1}------------\n    {0}    --{1}|    {2}----    {1}|{0}--\n    {0}    --{1}|   {2}|    |   {1}|{0}--\n    {0}    --{1}|   {2}|    |   {1}|{0}--\n    {0}    --{1}|    {2}----    {1}|{0}--\n    {0}       {1}------------\n    {0}        |  |  |  |\n    {2}__     ___   _ ____  _\n    {2}\\ \\   / / | | |  _ \\| |\n    {2} \\ \\ / /| |_| | | | | |\n    {2}  \\ V / |  _  | |_| | |___\n    {2}   \\_/  |_| |_|____/|_____|\n  colors:\n    ansi:\n      - yellow\n      - green\n      - white\n    chip: \"#ADB2CB\"\n  icon: '\\u{F4BC}'\nVimScript:\n  type: programming\n  ascii: |\n    {1}     ________{0}  ++    {1}________\n    {1}    /{2}VVVVVVVV{1}\\{0}++++  {1}/{2}VVVVVVVV{1}\\\n    {1}    \\{2}VVVVVVVV{1}/{0}++++++{1}\\{2}VVVVVVVV{1}/\n    {1}     |{2}VVVVVV{1}|{0}++++++++{1}/{2}VVVVV{1}/'\n    {1}     |{2}VVVVVV{1}|{0}++++++{1}/{2}VVVVV{1}/'\n    {0}    +{1}|{2}VVVVVV{1}|{0}++++{1}/{2}VVVVV{1}/'{0}+\n    {0}  +++{1}|{2}VVVVVV{1}|{0}++{1}/{2}VVVVV{1}/'{0}+++++\n    {0}+++++{1}|{2}VVVVVV{1}|/{2}VVV{1}___{0}++++++++++\n    {0}  +++{1}|{2}VVVVVVVVVV{1}/{2}##{1}/ {0}+{1}_{0}+{1}_{0}+{1}_{0}+{1}_\n    {0}    +{1}|{2}VVVVVVVVV{1}___ {0}+{1}/{2}#{1}_{2}#{1},{2}#{1}_{2}#{1},\\\n    {1}     |{2}VVVVVVV{1}//{2}##{1}/{0}+{1}/{2}#{1}/{0}+{1}/{2}#{1}/'/{2}#{1}/\n    {1}     |{2}VVVVV{1}/'{0}+{1}/{2}#{1}/{0}+{1}/{2}#{1}/{0}+{1}/{2}#{1}/ /{2}#{1}/\n    {1}     |{2}VVV{1}/'{0}++{1}/{2}#{1}/{0}+{1}/{2}#{1}/ /{2}#{1}/ /{2}#{1}/\n    {1}     '{2}V{1}/'  /{2}##{1}//{2}##{1}//{2}##{1}//{2}###{1}/\n    {0}              ++\n  colors:\n    ansi:\n      - green\n      - black\n      - white\n    chip: \"#199F4B\"\n  icon: '\\u{E7C5}'\nVisualBasic:\n  type: programming\n  ascii: |\n    {1}             ::::::::::\n    {1}         ::::::::::::::::::\n    {1}      ::::::::::::::::::::::::\n    {0}    &&&&{1}::::::::::::::::::::::::\n    {0}  &&&&&&&&{1}::::::::::::::::::::::::\n    {0} &&&&{2}##{0}&&&&&{1}::::::{2}##{1}:::{2}######{1}::::::\n    {0}&&&&&&{2}##{0}&&&&&&{1}:::{2}##{1}::::{2}#{1}:::::{2}##{1}:::::\n    {0}&&&&&&&{2}##{0}&&&&&&&{2}##{1}:::::{2}#{1}:::::{2}##{1}:::::\n    {0}&&&&&&&&{2}##{0}&&&&&{2}##{0}&{1}:::::{2}#######{1}::::::\n    {0}&&&&&&&&&{2}##{0}&&&{2}##{0}&&&&{1}:::{2}#{1}::::::{2}##{1}::::\n    {0}&&&&&&&&&&{2}##{0}&{2}##{0}&&&&&&&{1}:{2}#{1}::::::{2}##{1}::::\n    {0} &&&&&&&&&&{2}###{0}&&&&&&&&&{2}######{1}::::::\n    {0}  &&&&&&&&&&&&&&&&&&&&&&&&{1}::::::::\n    {0}    &&&&&&&&&&&&&&&&&&&&&&&&{1}::::\n    {0}      &&&&&&&&&&&&&&&&&&&&&&&&\n    {0}         &&&&&&&&&&&&&&&&&&\n    {0}             &&&&&&&&&&\n  colors:\n    ansi:\n      - blue\n      - blue\n      - white\n    hex:\n      - \"#195F97\"\n      - \"#004E8C\"\n      - \"#FFFFFF\"\n    chip: \"#945db7\"\n  icon: '\\u{E8D5}'\nVue:\n  type: markup\n  ascii: |\n    {0}VUE{1}\\\\\\\\\\                /////{0}VUE\n    {0} VUE{1}\\\\\\\\\\              /////{0}VUE\n    {0}  VUE{1}\\\\\\\\\\            /////{0}VUE\n    {0}   VUE{1}\\\\\\\\\\          /////{0}VUE\n    {0}    VUE{1}\\\\\\\\\\        /////{0}VUE\n    {0}     VUE{1}\\\\\\\\\\      /////{0}VUE\n    {0}      VUE{1}\\\\\\\\\\    /////{0}VUE\n    {0}       VUE{1}\\\\\\\\\\  /////{0}VUE\n    {0}        VUE{1}\\\\\\\\\\/////{0}VUE\n    {0}         VUE{1}\\\\\\\\////{0}VUE\n    {0}          VUE{1}\\\\\\///{0}VUE\n    {0}           VUE{1}\\\\//{0}VUE\n    {0}            VUE{1}||{0}VUE\n    {0}             VUEVUE\n    {0}              VUEV\n  colors:\n    ansi:\n      - green\n      - blue\n    chip: \"#199F4B\"\n  icon: '\\u{E6A0}'\nWebAssembly:\n  type: programming\n  ascii: |\n    {0}:::::::::::             ::::::::::::\n    {0}::::::::::::           :::::::::::::\n    {0}:::::::::::::::     ::::::::::::::::\n    {0}::::::::::::::::::::::::::::::::::::\n    {0}::::::::::::::::::::::::::::::::::::\n    {0}::::::::::::::::::::::::::::::::::::\n    {0}::::::::::::::::::::::::::::::::::::\n    {0}::::::::::::::::::::::::::::::::::::\n    {0}::::::::::::::::::::::::::::::::::::\n    {0}::::::::::::::::::::::::::::::::::::\n    {0}::::::::::::{1}WW{0}:::{1}WW{0}:::{1}WW{0}::::{1}AA{0}::::::\n    {0}::::::::::::{1}WW{0}:::{1}WW{0}:::{1}WW{0}:::{1}AAAA{0}:::::\n    {0}::::::::::::{1}WW{0}:::{1}WW{0}:::{1}WW{0}::{1}AA{0}::{1}AA{0}::::\n    {0}:::::::::::::{1}WW{0}:{1}WWWW{0}:{1}WW{0}::{1}AAAAAAAA{0}:::\n    {0}::::::::::::::{1}WWW{0}::{1}WWW{0}::{1}AA{0}::::::{1}AA{0}::\n    {0}::::::::::::::::::::::::::::::::::::\n  colors:\n    ansi:\n      - magenta\n      - white\n    hex:\n      - \"#654FF0\"\n      - \"#FFFFFF\"\n    chip: \"#04133B\"\n  icon: '\\u{E6A1}'\nWolfram:\n  type: programming\n  ascii: |\n    {0}             OOOOOOOOOOOOOO\n    {0}         OOOOOO  OOOOOOOOOOOOOO\n    {0}       OOOOOO  {1}WW{0} OOOO OOOOOOOOOOO\n    {0}     OOOOOOO  {1}WWW {0}OOO {1}W{0} OOOOOOOOOOOO\n    {0}   OOOOOOOO  {1}WWWWW {0}O {1}WW{0} OOOOOOOOOOOOO\n    {0}  OOOOOOOO  {1}WWWWWW  WWW{0} OOOOOOOOOOOOOO\n    {0} OOOOOOOOO  {1}WWWWWWW WWW{0} OOOOOOOOOOOOOOO\n    {0} OOOOOOO  {1}WWWWWWWWWW WW{0} OOOOOOOOOOOOOOO\n    {0}OOOOOOO  {1}WWWWWWWWWWWWWW{0}  OOOOOOOOOOOOOOO\n    {0}OOOOOO  {1}WWWWWWWWWWWWWWWWW{0}   OOOOOOOOOOOO\n    {0}OOOOO  {1}WWW WWWWWWWWW   WWWWW{0}   OOOOOOOOO\n    {0}OOO   {1}WW  WWWWWWWWWWWWWWWWWWWWW{0}  OOOOOOO\n    {0} O   {1}WWWW  WWWWWWWWWWWWWWWWWWWWW{2}D{0}  OOOO\n    {0}    {1}WWWW  WWWWWWWWWWWWWWWWWWWWWW{0}  OOOOO\n    {1}   WWWWW..............  {0}.........OOOOO\n    {1}    WWWWWWWWWWWWWWWWWW{0}   OOOOOOOOOOOO\n    {1}     WWWWWWWWWWWWWWWWWW{0}    OOOOOOOO\n    {1}       WWWWWWWWWWWWWWWWW{0}    OOOOOO\n    {1}         WWWWWWWWWWWWWWWW{0}     OO\n    {1}             WWWWWWWWWWWWW{0}\n  colors:\n    ansi:\n      - red\n      - white\n      - black\n    hex:\n      - \"#FF0A01\"\n      - \"#FFFFFF\"\n      - \"#000000\"\n    chip: \"#DD1100\"\nXaml:\n  type: data\n  ascii: |\n    {0}        :::::::::::::::::::::..\n    {0}       :::{1}------{0}::::::::::::....\n    {0}      :::{1}------{0}::::::::::::..{1}-{0}...\n    {0}     :::{1}------{0}::::::::::::..{1}---{0}...\n    {0}    :::{1}------{0}::::::::::::..{1}-----{0}...\n    {0}   :::{1}------{0}::::::::::::...{1}------{0}...\n    {0}  :::{1}------{0}::::::::::::.....{1}------{0}...\n    {0} :::{1}------{0}::::::::::::.......{1}------{0}...\n    {0}:::{1}------{0}::::::::::::.........{1}------{0}...\n    {0} :::{1}------{0}::::::::::::.......{1}------{0}...\n    {0}  :::{1}------{0}::::::::::::.....{1}------{0}...\n    {0}   :::{1}------{0}::::::::::::...{1}------{0}...\n    {0}    :::{1}------{0}::::::::::::..{1}-----{0}...\n    {0}     :::{1}------{0}::::::::::::..{1}---{0}...\n    {0}      :::{1}------{0}::::::::::::..{1}-{0}...\n    {0}       :::{1}------{0}::::::::::::....\n    {0}        :::::::::::::::::::::..\n  colors:\n    ansi:\n      - blue\n      - white\n    hex:\n      - \"#3378CE\"\n      - \"#FFFFFF\"\n    chip: \"#0060AC\"\n  icon: '\\u{f0673}'\nXml:\n  type: data\n  ascii: |\n    {0}  __{1} __  _  __ __  _    {2}   __{0}__\n    {0} / /{1} \\ \\/  |  \\  \\| |   {2}  / /{0}\\ \\\n    {0}< < {1}  \\ \\  |     || |_  {2} / / {0} > >\n    {0} \\_\\{1} _/\\_\\ |_|_|_||___| {2}/_/  {0}/_/\n  colors:\n    ansi:\n      - yellow\n      - white\n      - green\n    chip: \"#0060AC\"\n  icon: '\\u{f05c0}'\nXSL:\n  type: programming\n  ascii: |\n    {0}                       :::\n    {0}       :::::          :::  :::::\n    {0}     :::::           :::     :::::\n    {0}   :::::            :::        :::::\n    {0} :::::             :::           :::::\n    {0}:::::             :::             :::::\n    {0} :::::           :::             :::::\n    {0}   :::::        :::            :::::\n    {0}     :::::     :::           :::::\n    {0}       :::::  :::          :::::\n    {0}             :::\n  colors:\n    ansi:\n      - cyan\n    chip: \"#EB8CEB\"\n  icon: '\\u{f05c0}'\nYaml:\n  type: data\n  ascii: |\n    {0}__    __  __   __    __   _\n    {0}\\ \\  / / /  | |  \\  /  | | |\n    {0} \\ \\/ / / | | | \\ \\/ / | | |.\n    {0}  \\  / / /| | | |\\__/| | | | .\n    {0}  / / / / | | | |    | | | |  .\n    {0} / / / /  | | | |    | | | |___.\n    {0}/_/ /_/   |_| |_|    |_| |______\\\n  colors:\n    ansi:\n      - white\n    chip: \"#CB171E\"\n  icon: '\\u{E6A8}'\nZig:\n  type: programming\n  ascii: |\n    {0}                                     z\n    {0}                                  zzz\n    {0}                             zzzzzz\n    {0}zzzzzzzzzzz  zzzzzzzzzzzzzzzzzzzz  zzz\n    {0}zzzzzzzzz  zzzzzzzzzzzzzzzzzzzz  zzzzz\n    {0}zzzzzzz  zzzzzzzzzzzzzzzzzzzz  zzzzzzz\n    {0}zzzzz                zzzzzz      zzzzz\n    {0}zzzzz              zzzzzz        zzzzz\n    {0}zzzzz            zzzzzz          zzzzz\n    {0}zzzzz          zzzzzz            zzzzz\n    {0}zzzzz        zzzzzz              zzzzz\n    {0}zzzzz      zzzzzz                zzzzz\n    {0}zzzzzzz  zzzzzzzzzzzzzzzzzzzz  zzzzzzz\n    {0}zzzzz  zzzzzzzzzzzzzzzzzzzz  zzzzzzzzz\n    {0}zzz  zzzzzzzzzzzzzzzzzzzz  zzzzzzzzzzz\n    {0}   zzzzzz\n    {0} zzz\n    {0}z\n  colors:\n    ansi:\n      - yellow\n    chip: \"#EC915C\"\n  icon: '\\u{E6A9}'\nZsh:\n  type: programming\n  ascii: |\n    {0}ZSHZSHZSHZSHZSHZSHZSHZSHZSHZSHZS\n    {0}ZSHZSHZSHZSHZSHZSHZSHZSHZSHZSHZS\n    {0}ZSHZ     ZSHZSHZSHZSHZSHZSHZSHZS\n    {0}ZSHZSH     SHZSHZSHZSHZSHZSHZSHZ\n    {0}ZSHZSHZS     SHZSHZSHZSHZSHZSHZS\n    {0}ZSHZSHZSHZ     HZSHZSHZSHZSHZSHZ\n    {0}ZSHZSHZSHZSH     ZSHZSHZSHZSHZSH\n    {0}ZSHZSHZSHZ     SHZSHZSHZSHZSHZSH\n    {0}ZSHZSHZS     ZSHZSHZSHZSHZSHZSHZ\n    {0}ZSHZSH     SHZSHZSHZSHZSHZSHZSHZ\n    {0}ZSHZ     ZSHZSH             SHZS\n    {0}ZSHZSHZSHZSHZSHZSHZSHZSHZSHZSHZS\n    {0}ZSHZSHZSHZSHZSHZSHZSHZSHZSHZSHZS\n  colors:\n    ansi:\n      - white\n    chip: \"#89E051\"\n  icon: '\\u{f1183}'\n"
  },
  {
    "path": "manifest/Cargo.toml",
    "content": "[package]\nauthors.workspace = true\nedition.workspace = true\nversion.workspace = true\nlicense.workspace = true\nrepository.workspace = true\nname = \"onefetch-manifest\"\ndescription = \"Detect and parse manifest files\"\n\n[dependencies]\nanyhow = \"1.0.101\"\ncargo_toml = \"0.22.3\"\nserde = { version = \"1.0.228\", features = [\"derive\"] }\nserde_json = \"1.0.149\"\nstrum = { version = \"0.28.0\", features = [\"derive\"] }\n"
  },
  {
    "path": "manifest/README.md",
    "content": "# manifest\n\n[![crates.io](https://img.shields.io/crates/v/onefetch-manifest)](https://crates.io/crates/onefetch-manifest)\n[![docs.rs](https://img.shields.io/docsrs/onefetch-manifest)](https://docs.rs/onefetch-manifest)\n\nProvides the primary interface to detect and parse the repository's manifest(s).\n\n_This crate is designed as part of the [onefetch](https://github.com/o2sh/onefetch) project._\n"
  },
  {
    "path": "manifest/src/lib.rs",
    "content": "use anyhow::{Context, Result};\nuse serde::Deserialize;\nuse std::{\n    collections::HashMap,\n    fs,\n    path::{Path, PathBuf},\n};\nuse strum::{Display, EnumIter};\n\n#[derive(Clone, PartialEq, Eq, Debug)]\npub struct Manifest {\n    pub manifest_type: ManifestType,\n    pub number_of_dependencies: usize,\n    pub name: Option<String>,\n    pub description: Option<String>,\n    pub version: Option<String>,\n    pub license: Option<String>,\n}\n\n#[derive(Display, Clone, Copy, PartialEq, Eq, Debug, EnumIter)]\npub enum ManifestType {\n    Npm,\n    Cargo,\n}\n\npub fn get_manifests<P: AsRef<Path>>(path: P) -> Result<Vec<Manifest>> {\n    let manifests = fs::read_dir(path)?\n        .filter_map(|entry| entry.ok())\n        .map(|entry| entry.path())\n        .filter(|p| p.is_file())\n        .filter_map(|file_path: PathBuf| {\n            let file_name = file_path.file_name()?.to_str()?;\n            let manifest_type = file_name_to_manifest_type(file_name)?;\n            Some((file_path, manifest_type))\n        })\n        .filter_map(|(file_path, manifest_type)| match manifest_type {\n            ManifestType::Cargo => parse_cargo_manifest(&file_path).ok(),\n            ManifestType::Npm => parse_npm_manifest(&file_path).ok(),\n        })\n        .collect::<Vec<_>>();\n\n    Ok(manifests)\n}\n\nfn parse_cargo_manifest(path: &Path) -> Result<Manifest> {\n    let m = cargo_toml::Manifest::from_path(path)\n        .with_context(|| format!(\"Failed to parse Cargo.toml at '{}'\", path.display()))?;\n    let package = m.package.context(\"Not a package (only a workspace)\")?;\n    let description = package.description().map(Into::into);\n\n    Ok(Manifest {\n        manifest_type: ManifestType::Cargo,\n        number_of_dependencies: m.dependencies.len(),\n        name: Some(package.name.clone()),\n        description,\n        version: Some(package.version().into()),\n        license: package.license().map(Into::into),\n    })\n}\n\n#[derive(Deserialize)]\nstruct PackageJson {\n    name: Option<String>,\n    description: Option<String>,\n    version: Option<String>,\n    license: Option<String>,\n    #[serde(default)]\n    dependencies: HashMap<String, serde_json::Value>,\n}\n\nfn parse_npm_manifest(path: &Path) -> Result<Manifest> {\n    let content = fs::read_to_string(path)\n        .with_context(|| format!(\"Failed to read package.json at '{}'\", path.display()))?;\n\n    let pkg: PackageJson = serde_json::from_str(&content)\n        .with_context(|| format!(\"Failed to parse package.json at '{}'\", path.display()))?;\n\n    Ok(Manifest {\n        manifest_type: ManifestType::Npm,\n        number_of_dependencies: pkg.dependencies.len(),\n        name: pkg.name,\n        description: pkg.description,\n        version: pkg.version,\n        license: pkg.license,\n    })\n}\n\nfn file_name_to_manifest_type(filename: &str) -> Option<ManifestType> {\n    match filename {\n        \"Cargo.toml\" => Some(ManifestType::Cargo),\n        \"package.json\" => Some(ManifestType::Npm),\n        _ => None,\n    }\n}\n"
  },
  {
    "path": "manifest/tests/cargo.rs",
    "content": "use anyhow::Result;\nuse onefetch_manifest::{ManifestType, get_manifests};\n\n#[test]\nfn should_detect_and_parse_cargo_manifest() -> Result<()> {\n    let manifests = get_manifests(\"tests/fixtures/cargo\")?;\n    assert_eq!(manifests.len(), 1);\n    let cargo_manifest = manifests.first().unwrap();\n    assert_eq!(cargo_manifest.manifest_type, ManifestType::Cargo);\n    assert_eq!(cargo_manifest.number_of_dependencies, 5);\n    assert_eq!(cargo_manifest.name, Some(String::from(\"project\")));\n    assert_eq!(\n        cargo_manifest.description,\n        Some(\"this is a description\".into())\n    );\n    assert_eq!(cargo_manifest.version, Some(String::from(\"0.1.0\")));\n    assert_eq!(cargo_manifest.license, Some(\"MIT\".into()));\n\n    Ok(())\n}\n"
  },
  {
    "path": "manifest/tests/fixtures/cargo/Cargo.toml",
    "content": "[package]\nname = \"project\"\nversion = \"0.1.0\"\nedition = \"2024\"\ndescription = \"this is a description\"\nlicense = \"MIT\"\n\n[dependencies]\ncortex-m-rtic = \"0.6.0-alpha.1\"\nanyhow = \"1.0\"\n\n[dependencies.cortex-m]\nversion = \"0.7.1\"\n\n[dependencies.cortex-m-rt]\nversion = \"0.6.11\"\n\n[dependencies.stm32f4xx-hal]\nversion = \"0.8.3\"\nfeatures = [\"rt\", \"stm32f401\"]\n"
  },
  {
    "path": "manifest/tests/fixtures/npm/package.json",
    "content": "{\n    \"name\": \"my_package\",\n    \"description\": \"description for my_package\",\n    \"version\": \"1.0.0\",\n    \"main\": \"index.js\",\n    \"dependencies\": {\n        \"@rollup/plugin-yaml\": \"^4.0.1\",\n        \"@sveltejs/vite-plugin-svelte\": \"^1.0.8\",\n        \"@tsconfig/svelte\": \"^3.0.0\"\n    },\n    \"scripts\": {\n        \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n    },\n    \"repository\": {\n        \"type\": \"git\",\n        \"url\": \"https://github.com/<user>/my_package.git\"\n    },\n    \"keywords\": [],\n    \"author\": \"\",\n    \"license\": \"ISC\",\n    \"bugs\": {\n        \"url\": \"https://github.com/<user>/my_package/issues\"\n    },\n    \"homepage\": \"https://github.com/<user>/my_package\"\n}\n"
  },
  {
    "path": "manifest/tests/npm.rs",
    "content": "use anyhow::Result;\nuse onefetch_manifest::{ManifestType, get_manifests};\n\n#[test]\nfn should_detect_and_parse_npm_manifest() -> Result<()> {\n    let manifests = get_manifests(\"tests/fixtures/npm\")?;\n    assert_eq!(manifests.len(), 1);\n    let npm_manifest = manifests.first().unwrap();\n    assert_eq!(npm_manifest.manifest_type, ManifestType::Npm);\n    assert_eq!(npm_manifest.number_of_dependencies, 3);\n    assert_eq!(npm_manifest.name, Some(String::from(\"my_package\")));\n    assert_eq!(\n        npm_manifest.description,\n        Some(\"description for my_package\".into())\n    );\n    assert_eq!(npm_manifest.version, Some(String::from(\"1.0.0\")));\n    assert_eq!(npm_manifest.license, Some(\"ISC\".into()));\n    Ok(())\n}\n"
  },
  {
    "path": "scripts/nf-preview.rb",
    "content": "#!/usr/bin/env ruby\nrequire \"yaml\"\n\nLANGUAGES_FILE = File.expand_path(\"../../languages.yaml\", __FILE__)\n\nlanguages = YAML.safe_load_file(ARGV[0] || LANGUAGES_FILE, symbolize_names: true)\n\nlanguages.each do |language, attributes|\n  icon = attributes[:icon]\n  next if icon.nil?\n  match = /\\A\\\\u\\{([A-F0-9]{4,})\\}\\z/i.match(icon)\n  raise \"Icon for #{language} is not in the correct format: `#{icon}`\" unless match\n  glyph = match.captures[0].hex.chr(\"UTF-8\")\n  puts \"#{language}: #{glyph}\"\nend\n"
  },
  {
    "path": "snap/snapcraft.yaml",
    "content": "name: onefetch\nbase: core22\nadopt-info: onefetch\nsummary: Command-line Git information tool\ndescription: |\n  Onefetch is a command-line Git information tool that displays project information\n  and code statistics for a local Git repository directly to your terminal.\n\ngrade: stable\nconfinement: strict\n\nparts:\n  onefetch:\n    plugin: rust\n    source: .\n    build-packages:\n      - cargo\n      - rustc\n      - cmake\n    stage-packages:\n      - git\n    override-build: |\n      snapcraftctl build\n      snapcraftctl set-version $(git describe --abbrev=0 --tags)\n\nplugs:\n  etc-gitconfig:\n    interface: system-files\n    read:\n      - /etc/gitconfig\n  gitconfig:\n    interface: personal-files\n    read:\n      - $HOME/.gitconfig\n      - $HOME/.config/git/config\n\napps:\n  onefetch:\n    environment:\n      HOME: $SNAP_REAL_HOME\n    command: bin/onefetch\n    plugs:\n      - home\n      - removable-media\n      - etc-gitconfig\n      - gitconfig\n"
  },
  {
    "path": "src/cli.rs",
    "content": "use crate::info::langs::language::{Language, LanguageType};\nuse crate::info::utils::info_field::InfoType;\nuse crate::ui::printer::SerializationFormat;\nuse anyhow::Result;\nuse clap::builder::PossibleValuesParser;\nuse clap::builder::Styles;\nuse clap::builder::TypedValueParser as _;\nuse clap::builder::styling::AnsiColor;\nuse clap::{Args, Command, Parser, ValueHint, value_parser};\nuse clap_complete::{Generator, Shell, generate};\nuse num_format::CustomFormat;\nuse onefetch_image::ImageProtocol;\nuse onefetch_manifest::ManifestType;\nuse regex::Regex;\nuse serde::Serialize;\nuse std::env;\nuse std::io;\nuse std::path::PathBuf;\nuse std::str::FromStr;\nuse strum::IntoEnumIterator;\n\nconst COLOR_RESOLUTIONS: [&str; 5] = [\"16\", \"32\", \"64\", \"128\", \"256\"];\npub const NO_BOTS_DEFAULT_REGEX_PATTERN: &str = r\"(?:-|\\s)[Bb]ot$|\\[[Bb]ot\\]\";\n\nconst STYLES: Styles = Styles::styled()\n    .header(AnsiColor::Yellow.on_default())\n    .usage(AnsiColor::Green.on_default())\n    .literal(AnsiColor::Green.on_default())\n    .placeholder(AnsiColor::Green.on_default());\n\n#[derive(Clone, Debug, Parser, PartialEq, Eq)]\n#[command(version, about)]\n#[command(styles = STYLES)]\npub struct CliOptions {\n    /// Run as if onefetch was started in <input> instead of the current working directory\n    #[arg(default_value = \".\", hide_default_value = true, value_hint = ValueHint::DirPath)]\n    pub input: PathBuf,\n    #[command(flatten)]\n    pub info: InfoCliOptions,\n    #[command(flatten)]\n    pub text_formatting: TextForamttingCliOptions,\n    #[command(flatten)]\n    pub ascii: AsciiCliOptions,\n    #[command(flatten)]\n    pub image: ImageCliOptions,\n    #[command(flatten)]\n    pub visuals: VisualsCliOptions,\n    #[command(flatten)]\n    pub developer: DeveloperCliOptions,\n    #[command(flatten)]\n    pub other: OtherCliOptions,\n}\n\n#[derive(Clone, Debug, Args, PartialEq, Eq)]\n#[command(next_help_heading = \"INFO\")]\npub struct InfoCliOptions {\n    /// Allows you to disable FIELD(s) from appearing in the output\n    #[arg(\n        long,\n        short,\n        num_args = 1..,\n        hide_possible_values = true,\n        value_enum,\n        value_name = \"FIELD\"\n    )]\n    pub disabled_fields: Vec<InfoType>,\n    /// Hides the title\n    #[arg(long)]\n    pub no_title: bool,\n    /// Maximum NUM of authors to be shown\n    #[arg(long, default_value_t = 3usize, value_name = \"NUM\")]\n    pub number_of_authors: usize,\n    /// Maximum NUM of languages to be shown\n    #[arg(long, default_value_t = 6usize, value_name = \"NUM\")]\n    pub number_of_languages: usize,\n    /// Maximum NUM of file churns to be shown\n    #[arg(long, default_value_t = 3usize, value_name = \"NUM\")]\n    pub number_of_file_churns: usize,\n    /// Minimum NUM of commits from HEAD used to compute the churn summary\n    ///\n    /// By default, the actual value is non-deterministic due to time-based computation\n    /// and will be displayed under the info title \"Churn (NUM)\"\n    #[arg(long, value_name = \"NUM\")]\n    pub churn_pool_size: Option<usize>,\n    /// Ignore all files & directories matching EXCLUDE\n    #[arg(long, short, num_args = 1..)]\n    pub exclude: Vec<String>,\n    /// Exclude [bot] commits. Use <REGEX> to override the default pattern\n    #[arg(\n        long,\n        num_args = 0..=1,\n        require_equals = true,\n        default_missing_value = NO_BOTS_DEFAULT_REGEX_PATTERN,\n        value_name = \"REGEX\"\n    )]\n    pub no_bots: Option<MyRegex>,\n    /// Ignores merge commits\n    #[arg(long)]\n    pub no_merges: bool,\n    /// Show the email address of each author\n    #[arg(long, short = 'E')]\n    pub email: bool,\n    /// Display repository URL as HTTP\n    #[arg(long)]\n    pub http_url: bool,\n    /// Hide token in repository URL\n    #[arg(long)]\n    pub hide_token: bool,\n    /// Count hidden files and directories\n    #[arg(long)]\n    pub include_hidden: bool,\n    /// Filters output by language type\n    #[arg(\n        long,\n        num_args = 1..,\n        default_values = &[\"programming\", \"markup\"],\n        short = 'T',\n        value_enum,\n    )]\n    pub r#type: Vec<LanguageType>,\n}\n\n#[derive(Clone, Debug, Args, PartialEq, Eq)]\n#[command(next_help_heading = \"ASCII\")]\npub struct AsciiCliOptions {\n    /// Takes a non-empty STRING as input to replace the ASCII logo\n    ///\n    /// It is possible to pass a generated STRING by command substitution\n    ///\n    /// For example:\n    ///\n    /// '--ascii-input \"$(fortune | cowsay -W 25)\"'\n    #[arg(long, value_name = \"STRING\", value_hint = ValueHint::CommandString)]\n    pub ascii_input: Option<String>,\n    /// Colors (X X X...) to print the ascii art\n    #[arg(\n        long,\n        num_args = 1..,\n        value_name = \"X\",\n        short = 'c',\n        value_parser = value_parser!(u8).range(..16),\n    )]\n    pub ascii_colors: Vec<u8>,\n    /// Which LANGUAGE's ascii art to print\n    #[arg(\n        long,\n        short,\n        value_name = \"LANGUAGE\",\n        value_enum,\n        hide_possible_values = true\n    )]\n    pub ascii_language: Option<Language>,\n    /// Specify when to use true color\n    ///\n    /// If set to auto: true color will be enabled if supported by the terminal\n    #[arg(long, default_value = \"auto\", value_name = \"WHEN\", value_enum)]\n    pub true_color: When,\n}\n\n#[derive(Clone, Debug, Args, PartialEq, Eq)]\n#[command(next_help_heading = \"IMAGE\")]\npub struct ImageCliOptions {\n    /// Path to the IMAGE file\n    #[arg(long, short, value_hint = ValueHint::FilePath)]\n    pub image: Option<PathBuf>,\n    /// Which image PROTOCOL to use\n    #[arg(long, value_enum, requires = \"image\", value_name = \"PROTOCOL\")]\n    pub image_protocol: Option<ImageProtocol>,\n    /// VALUE of color resolution to use with SIXEL backend\n    #[arg(\n        long,\n        value_name = \"VALUE\",\n        requires = \"image\",\n        default_value_t = 64usize,\n        value_parser = PossibleValuesParser::new(COLOR_RESOLUTIONS)\n            .map(|s| s.parse::<usize>().unwrap())\n    )]\n    pub color_resolution: usize,\n}\n\n#[derive(Clone, Debug, Args, PartialEq, Eq)]\n#[command(next_help_heading = \"TEXT FORMATTING\")]\npub struct TextForamttingCliOptions {\n    /// Changes the text colors (X X X...)\n    ///\n    /// Goes in order of title, ~, underline, subtitle, colon, and info\n    ///\n    /// For example:\n    ///\n    /// '--text-colors 9 10 11 12 13 14'\n    #[arg(\n        long,\n        short,\n        value_name = \"X\",\n        value_parser = value_parser!(u8).range(..16),\n        num_args = 1..=6\n    )]\n    pub text_colors: Vec<u8>,\n    /// Use ISO 8601 formatted timestamps\n    #[arg(long, short = 'z')]\n    pub iso_time: bool,\n    /// Which thousands SEPARATOR to use\n    #[arg(long, value_name = \"SEPARATOR\", default_value = \"plain\", value_enum)]\n    pub number_separator: NumberSeparator,\n    /// Turns off bold formatting\n    #[arg(long)]\n    pub no_bold: bool,\n}\n#[derive(Clone, Debug, Args, PartialEq, Eq, Default)]\n#[command(next_help_heading = \"VISUALS\")]\npub struct VisualsCliOptions {\n    /// Hides the color palette\n    #[arg(long)]\n    pub no_color_palette: bool,\n    /// Hides the ascii art or image if provided\n    #[arg(long)]\n    pub no_art: bool,\n    /// Use Nerd Font icons\n    ///\n    /// Replaces language chips with Nerd Font icons\n    #[arg(long)]\n    pub nerd_fonts: bool,\n}\n\n#[derive(Clone, Debug, Args, PartialEq, Eq, Default)]\n#[command(next_help_heading = \"DEVELOPER\")]\npub struct DeveloperCliOptions {\n    /// Outputs Onefetch in a specific format\n    #[arg(long, short, value_name = \"FORMAT\", value_enum)]\n    pub output: Option<SerializationFormat>,\n    /// If provided, outputs the completion file for given SHELL\n    #[arg(long = \"generate\", value_name = \"SHELL\", value_enum)]\n    pub completion: Option<Shell>,\n}\n\n#[derive(Clone, Debug, Args, PartialEq, Eq, Default)]\n#[command(next_help_heading = \"OTHER\")]\npub struct OtherCliOptions {\n    /// Prints out supported languages\n    #[arg(long, short)]\n    pub languages: bool,\n    /// Prints out supported package managers\n    #[arg(long, short)]\n    pub package_managers: bool,\n}\n\nimpl Default for CliOptions {\n    fn default() -> CliOptions {\n        CliOptions {\n            input: PathBuf::from(\".\"),\n            info: InfoCliOptions::default(),\n            text_formatting: TextForamttingCliOptions::default(),\n            visuals: VisualsCliOptions::default(),\n            ascii: AsciiCliOptions::default(),\n            image: ImageCliOptions::default(),\n            developer: DeveloperCliOptions::default(),\n            other: OtherCliOptions::default(),\n        }\n    }\n}\n\nimpl Default for InfoCliOptions {\n    fn default() -> Self {\n        InfoCliOptions {\n            number_of_authors: 3,\n            number_of_languages: 6,\n            number_of_file_churns: 3,\n            churn_pool_size: Option::default(),\n            exclude: Vec::default(),\n            no_bots: Option::default(),\n            no_merges: Default::default(),\n            email: Default::default(),\n            http_url: Default::default(),\n            hide_token: Default::default(),\n            include_hidden: Default::default(),\n            r#type: vec![LanguageType::Programming, LanguageType::Markup],\n            disabled_fields: Vec::default(),\n            no_title: Default::default(),\n        }\n    }\n}\n\nimpl Default for TextForamttingCliOptions {\n    fn default() -> Self {\n        TextForamttingCliOptions {\n            text_colors: Vec::default(),\n            iso_time: Default::default(),\n            number_separator: NumberSeparator::Plain,\n            no_bold: Default::default(),\n        }\n    }\n}\n\nimpl Default for AsciiCliOptions {\n    fn default() -> Self {\n        AsciiCliOptions {\n            ascii_input: Option::default(),\n            ascii_colors: Vec::default(),\n            ascii_language: Option::default(),\n            true_color: When::Auto,\n        }\n    }\n}\nimpl Default for ImageCliOptions {\n    fn default() -> Self {\n        ImageCliOptions {\n            image: Option::default(),\n            image_protocol: Option::default(),\n            color_resolution: 64,\n        }\n    }\n}\n\npub fn print_supported_languages() -> Result<()> {\n    for l in Language::iter() {\n        println!(\"{l}\");\n    }\n\n    Ok(())\n}\n\npub fn print_supported_package_managers() -> Result<()> {\n    for p in ManifestType::iter() {\n        println!(\"{p}\");\n    }\n\n    Ok(())\n}\n\npub fn is_truecolor_terminal() -> bool {\n    env::var(\"COLORTERM\")\n        .map(|colorterm| colorterm == \"truecolor\" || colorterm == \"24bit\")\n        .unwrap_or(false)\n}\n\npub fn get_git_version() -> String {\n    let version = std::process::Command::new(\"git\").arg(\"--version\").output();\n\n    match version {\n        Ok(v) => String::from_utf8_lossy(&v.stdout).replace('\\n', \"\"),\n        Err(_) => String::new(),\n    }\n}\n\npub fn print_completions<G: Generator>(generator: G, cmd: &mut Command) {\n    generate(\n        generator,\n        cmd,\n        cmd.get_name().to_string(),\n        &mut io::stdout(),\n    );\n}\n\n#[derive(clap::ValueEnum, Clone, PartialEq, Eq, Debug)]\npub enum When {\n    Auto,\n    Never,\n    Always,\n}\n\n#[derive(clap::ValueEnum, Clone, PartialEq, Eq, Debug, Serialize, Copy)]\npub enum NumberSeparator {\n    Plain,\n    Comma,\n    Space,\n    Underscore,\n}\n\nimpl NumberSeparator {\n    fn separator(self) -> &'static str {\n        match self {\n            Self::Plain => \"\",\n            Self::Comma => \",\",\n            Self::Space => \"\\u{202f}\",\n            Self::Underscore => \"_\",\n        }\n    }\n\n    pub fn get_format(&self) -> CustomFormat {\n        num_format::CustomFormat::builder()\n            .grouping(num_format::Grouping::Standard)\n            .separator(self.separator())\n            .build()\n            .unwrap()\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    #[test]\n    fn test_default_config() {\n        let config: CliOptions = CliOptions::default();\n        assert_eq!(config, CliOptions::parse_from([\"onefetch\"]));\n    }\n\n    #[test]\n    fn test_custom_config() {\n        let config: CliOptions = CliOptions {\n            input: PathBuf::from(\"/tmp/folder\"),\n            info: InfoCliOptions {\n                number_of_authors: 4,\n                no_merges: true,\n                disabled_fields: vec![InfoType::Version, InfoType::URL],\n                ..Default::default()\n            },\n            ascii: AsciiCliOptions {\n                ascii_colors: vec![5, 0],\n                ascii_language: Some(Language::Lisp),\n                ..Default::default()\n            },\n            visuals: VisualsCliOptions {\n                no_art: true,\n                ..Default::default()\n            },\n            ..Default::default()\n        };\n\n        assert_eq!(\n            config,\n            CliOptions::parse_from([\n                \"onefetch\",\n                \"/tmp/folder\",\n                \"--number-of-authors\",\n                \"4\",\n                \"--no-merges\",\n                \"--ascii-colors\",\n                \"5\",\n                \"0\",\n                \"--disabled-fields\",\n                \"version\",\n                \"url\",\n                \"--no-art\",\n                \"--ascii-language\",\n                \"lisp\"\n            ])\n        );\n    }\n\n    #[test]\n    fn test_config_with_image_protocol_but_no_image() {\n        assert!(CliOptions::try_parse_from([\"onefetch\", \"--image-protocol\", \"sixel\"]).is_err())\n    }\n\n    #[test]\n    fn test_config_with_color_resolution_but_no_image() {\n        assert!(CliOptions::try_parse_from([\"onefetch\", \"--color-resolution\", \"32\"]).is_err())\n    }\n\n    #[test]\n    fn test_config_with_ascii_colors_but_out_of_bounds() {\n        assert!(CliOptions::try_parse_from([\"onefetch\", \"--ascii-colors\", \"17\"]).is_err())\n    }\n\n    #[test]\n    fn test_config_with_text_colors_but_out_of_bounds() {\n        assert!(CliOptions::try_parse_from([\"onefetch\", \"--text-colors\", \"17\"]).is_err())\n    }\n}\n\n#[derive(Clone, Debug)]\npub struct MyRegex(pub Regex);\n\nimpl Eq for MyRegex {}\n\nimpl PartialEq for MyRegex {\n    fn eq(&self, other: &MyRegex) -> bool {\n        self.0.as_str() == other.0.as_str()\n    }\n}\n\nimpl FromStr for MyRegex {\n    type Err = anyhow::Error;\n\n    fn from_str(s: &str) -> Result<Self> {\n        Ok(MyRegex(Regex::new(s)?))\n    }\n}\n"
  },
  {
    "path": "src/info/authors.rs",
    "content": "use super::git::sig::Sig;\nuse crate::{\n    cli::NumberSeparator,\n    info::utils::{format_number, info_field::InfoField},\n};\nuse serde::Serialize;\nuse std::{collections::HashMap, fmt::Write};\n\n#[derive(Serialize, Clone, Debug, PartialEq)]\n#[serde(rename_all = \"camelCase\")]\npub struct Author {\n    pub name: String,\n    email: Option<String>,\n    nbr_of_commits: usize,\n    contribution: usize,\n    #[serde(skip_serializing)]\n    number_separator: NumberSeparator,\n}\n\nimpl Author {\n    pub fn new(\n        name: String,\n        email: Option<String>,\n        nbr_of_commits: usize,\n        total_nbr_of_commits: usize,\n        number_separator: NumberSeparator,\n    ) -> Self {\n        let contribution =\n            (nbr_of_commits as f32 * 100. / total_nbr_of_commits as f32).round() as usize;\n        Self {\n            name,\n            email,\n            nbr_of_commits,\n            contribution,\n            number_separator,\n        }\n    }\n}\n\nimpl std::fmt::Display for Author {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        if let Some(email) = &self.email {\n            write!(\n                f,\n                \"{}% {} <{}> {}\",\n                self.contribution,\n                self.name,\n                email,\n                format_number(&self.nbr_of_commits, self.number_separator)\n            )\n        } else {\n            write!(\n                f,\n                \"{}% {} {}\",\n                self.contribution,\n                self.name,\n                format_number(&self.nbr_of_commits, self.number_separator)\n            )\n        }\n    }\n}\n\n#[derive(Serialize)]\npub struct AuthorsInfo {\n    pub authors: Vec<Author>,\n}\n\nimpl AuthorsInfo {\n    pub fn new(\n        number_of_commits_by_signature: &HashMap<Sig, usize>,\n        total_number_of_commits: usize,\n        number_of_authors_to_display: usize,\n        show_email: bool,\n        number_separator: NumberSeparator,\n    ) -> Self {\n        let authors = compute_authors(\n            number_of_commits_by_signature,\n            total_number_of_commits,\n            number_of_authors_to_display,\n            show_email,\n            number_separator,\n        );\n        Self { authors }\n    }\n\n    fn top_contribution(&self) -> usize {\n        if let Some(top_contributor) = self.authors.first() {\n            return top_contributor.contribution;\n        }\n        0\n    }\n}\n\nfn compute_authors(\n    number_of_commits_by_signature: &HashMap<Sig, usize>,\n    total_number_of_commits: usize,\n    number_of_authors_to_display: usize,\n    show_email: bool,\n    number_separator: NumberSeparator,\n) -> Vec<Author> {\n    let mut signature_with_number_of_commits_sorted: Vec<(&Sig, &usize)> =\n        Vec::from_iter(number_of_commits_by_signature);\n\n    signature_with_number_of_commits_sorted.sort_by(|(sa, a_count), (sb, b_count)| {\n        b_count.cmp(a_count).then_with(|| sa.name.cmp(&sb.name))\n    });\n\n    let authors: Vec<Author> = signature_with_number_of_commits_sorted\n        .into_iter()\n        .map(|(author, author_nbr_of_commits)| {\n            Author::new(\n                author.name.to_string(),\n                if show_email {\n                    Some(author.email.to_string())\n                } else {\n                    None\n                },\n                *author_nbr_of_commits,\n                total_number_of_commits,\n                number_separator,\n            )\n        })\n        .take(number_of_authors_to_display)\n        .collect();\n    authors\n}\n\nfn digit_difference(num1: usize, num2: usize) -> usize {\n    let count_digits = |num: usize| (num.checked_ilog10().unwrap_or(0) + 1) as usize;\n    count_digits(num1).abs_diff(count_digits(num2))\n}\n\nimpl std::fmt::Display for AuthorsInfo {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        let mut authors_info = String::new();\n\n        let pad = self.title().len() + 2;\n        for (i, author) in self.authors.iter().enumerate() {\n            if i == 0 {\n                write!(authors_info, \"{author}\")?;\n            } else {\n                write!(\n                    authors_info,\n                    \"\\n{:<width$}{}\",\n                    \"\",\n                    author,\n                    width = pad + digit_difference(self.top_contribution(), author.contribution)\n                )?;\n            }\n        }\n\n        write!(f, \"{authors_info}\")\n    }\n}\n\n#[typetag::serialize]\nimpl InfoField for AuthorsInfo {\n    fn value(&self) -> String {\n        self.to_string()\n    }\n\n    fn title(&self) -> String {\n        let mut title: String = \"Author\".into();\n        if self.authors.len() > 1 {\n            title.push('s');\n        }\n        title\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n    use crate::ui::text_colors::TextColors;\n    use insta::assert_snapshot;\n    use owo_colors::DynColors;\n    use rstest::rstest;\n\n    #[test]\n    fn test_display_author() {\n        let author = Author::new(\n            \"John Doe\".into(),\n            Some(\"john.doe@email.com\".into()),\n            1500,\n            2000,\n            NumberSeparator::Plain,\n        );\n\n        assert_eq!(author.to_string(), \"75% John Doe <john.doe@email.com> 1500\");\n    }\n\n    #[test]\n    fn test_display_author_with_no_email() {\n        let author = Author::new(\"John Doe\".into(), None, 1500, 2000, NumberSeparator::Plain);\n\n        assert_eq!(author.to_string(), \"75% John Doe 1500\");\n    }\n\n    #[test]\n    fn test_authors_info_title_with_one_author() {\n        let author = Author::new(\n            \"John Doe\".into(),\n            Some(\"john.doe@email.com\".into()),\n            1500,\n            2000,\n            NumberSeparator::Plain,\n        );\n\n        let authors_info = AuthorsInfo {\n            authors: vec![author],\n        };\n\n        assert_eq!(authors_info.title(), \"Author\");\n    }\n\n    #[test]\n    fn test_authors_info_title_with_two_authors() {\n        let author = Author::new(\n            \"John Doe\".into(),\n            Some(\"john.doe@email.com\".into()),\n            1500,\n            2000,\n            NumberSeparator::Plain,\n        );\n\n        let author_2 = Author::new(\n            \"Roberto Berto\".into(),\n            None,\n            240,\n            300,\n            NumberSeparator::Plain,\n        );\n\n        let authors_info = AuthorsInfo {\n            authors: vec![author, author_2],\n        };\n\n        assert_eq!(authors_info.title(), \"Authors\");\n    }\n\n    #[test]\n    fn test_author_info_with_one_author() {\n        let author = Author::new(\n            \"John Doe\".into(),\n            Some(\"john.doe@email.com\".into()),\n            1500,\n            2000,\n            NumberSeparator::Plain,\n        );\n\n        let authors_info = AuthorsInfo {\n            authors: vec![author],\n        };\n        let colors = TextColors::new(&[], DynColors::Rgb(0xFF, 0xFF, 0xFF));\n        let mut buffer = String::new();\n        authors_info\n            .write_styled(&mut buffer, false, &colors)\n            .unwrap();\n\n        assert_snapshot!(buffer);\n    }\n\n    #[test]\n    fn test_author_info_with_two_authors() {\n        let author = Author::new(\n            \"John Doe\".into(),\n            Some(\"john.doe@email.com\".into()),\n            1500,\n            2000,\n            NumberSeparator::Plain,\n        );\n\n        let author_2 = Author::new(\n            \"Roberto Berto\".into(),\n            None,\n            240,\n            300,\n            NumberSeparator::Plain,\n        );\n\n        let authors_info = AuthorsInfo {\n            authors: vec![author, author_2],\n        };\n\n        let colors = TextColors::new(&[], DynColors::Rgb(0xFF, 0xFF, 0xFF));\n        let mut buffer = String::new();\n        authors_info\n            .write_styled(&mut buffer, false, &colors)\n            .unwrap();\n\n        assert_snapshot!(buffer);\n    }\n    #[test]\n    fn test_author_info_alignment_with_three_authors() {\n        let author = Author::new(\n            \"John Doe\".into(),\n            Some(\"john.doe@email.com\".into()),\n            1500,\n            2000,\n            NumberSeparator::Plain,\n        );\n\n        let author_2 = Author::new(\n            \"Roberto Berto\".into(),\n            None,\n            240,\n            300,\n            NumberSeparator::Plain,\n        );\n\n        let author_3 = Author::new(\"Jane Doe\".into(), None, 1, 100, NumberSeparator::Plain);\n\n        let authors_info = AuthorsInfo {\n            authors: vec![author, author_2, author_3],\n        };\n\n        let colors = TextColors::new(&[], DynColors::Rgb(0xFF, 0xFF, 0xFF));\n        let mut buffer = String::new();\n        authors_info\n            .write_styled(&mut buffer, false, &colors)\n            .unwrap();\n\n        assert_snapshot!(buffer);\n    }\n\n    #[rstest]\n    #[case(456, 123, 0)]\n    #[case(456789, 123, 3)]\n    #[case(1, 12, 1)]\n    fn test_digit_difference(#[case] num1: usize, #[case] num2: usize, #[case] expected: usize) {\n        let result = digit_difference(num1, num2);\n        assert_eq!(result, expected);\n    }\n\n    #[test]\n    fn test_compute_authors() {\n        let mut number_of_commits_by_signature: HashMap<Sig, usize> = HashMap::new();\n        number_of_commits_by_signature.insert(\n            Sig {\n                name: \"John Doe\".into(),\n                email: \"johndoe@example.com\".into(),\n            },\n            30,\n        );\n        number_of_commits_by_signature.insert(\n            Sig {\n                name: \"Jane Doe\".into(),\n                email: \"janedoe@example.com\".into(),\n            },\n            20,\n        );\n        number_of_commits_by_signature.insert(\n            Sig {\n                name: \"Ellen Smith\".into(),\n                email: \"ellensmith@example.com\".into(),\n            },\n            50,\n        );\n        let total_number_of_commits = 100;\n        let number_of_authors_to_display = 2;\n        let show_email = false;\n        let number_separator = NumberSeparator::Comma;\n\n        let actual = compute_authors(\n            &number_of_commits_by_signature,\n            total_number_of_commits,\n            number_of_authors_to_display,\n            show_email,\n            number_separator,\n        );\n\n        let expected = vec![\n            Author::new(String::from(\"Ellen Smith\"), None, 50, 100, number_separator),\n            Author::new(String::from(\"John Doe\"), None, 30, 100, number_separator),\n        ];\n        assert_eq!(actual, expected);\n    }\n}\n"
  },
  {
    "path": "src/info/churn.rs",
    "content": "use super::utils::info_field::InfoField;\nuse crate::{cli::NumberSeparator, info::utils::format_number};\nuse anyhow::Result;\nuse gix::bstr::BString;\nuse globset::{Glob, GlobSetBuilder};\nuse serde::Serialize;\nuse std::{collections::HashMap, fmt::Write};\n\n#[derive(Serialize, Clone, Debug, PartialEq)]\n#[serde(rename_all = \"camelCase\")]\npub struct FileChurn {\n    pub file_path: String,\n    pub nbr_of_commits: usize,\n    #[serde(skip_serializing)]\n    number_separator: NumberSeparator,\n}\n\nimpl FileChurn {\n    pub fn new(\n        file_path: String,\n        nbr_of_commits: usize,\n        number_separator: NumberSeparator,\n    ) -> Self {\n        Self {\n            file_path,\n            nbr_of_commits,\n            number_separator,\n        }\n    }\n}\n\nimpl std::fmt::Display for FileChurn {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        write!(\n            f,\n            \"{} {}\",\n            shorten_file_path(&self.file_path, 2),\n            format_number(&self.nbr_of_commits, self.number_separator)\n        )\n    }\n}\n\n#[derive(Serialize)]\npub struct ChurnInfo {\n    pub file_churns: Vec<FileChurn>,\n    pub churn_pool_size: usize,\n}\n\nimpl ChurnInfo {\n    pub fn new(\n        number_of_commits_by_file_path: &HashMap<BString, usize>,\n        churn_pool_size: usize,\n        number_of_file_churns_to_display: usize,\n        globs_to_exclude: &[String],\n        number_separator: NumberSeparator,\n    ) -> Result<Self> {\n        let file_churns = compute_file_churns(\n            number_of_commits_by_file_path,\n            number_of_file_churns_to_display,\n            globs_to_exclude,\n            number_separator,\n        )?;\n\n        Ok(Self {\n            file_churns,\n            churn_pool_size,\n        })\n    }\n}\n\nfn compute_file_churns(\n    number_of_commits_by_file_path: &HashMap<BString, usize>,\n    number_of_file_churns_to_display: usize,\n    globs_to_exclude: &[String],\n    number_separator: NumberSeparator,\n) -> Result<Vec<FileChurn>> {\n    let mut builder = GlobSetBuilder::new();\n    for glob in globs_to_exclude {\n        builder.add(Glob::new(glob)?);\n    }\n    let glob_set = builder.build()?;\n    let mut number_of_commits_by_file_path_sorted = Vec::from_iter(number_of_commits_by_file_path);\n\n    number_of_commits_by_file_path_sorted\n        .sort_by(|(_, a_count), (_, b_count)| b_count.cmp(a_count));\n\n    Ok(number_of_commits_by_file_path_sorted\n        .into_iter()\n        .filter_map(|(file_path, nbr_of_commits)| {\n            if glob_set.is_match(file_path.to_string()) {\n                None\n            } else {\n                Some(FileChurn::new(\n                    file_path.to_string(),\n                    *nbr_of_commits,\n                    number_separator,\n                ))\n            }\n        })\n        .take(number_of_file_churns_to_display)\n        .collect())\n}\n\nimpl std::fmt::Display for ChurnInfo {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        let mut churn_info = String::new();\n\n        let pad = self.title().len() + 2;\n\n        for (i, file_churn) in self.file_churns.iter().enumerate() {\n            if i == 0 {\n                write!(churn_info, \"{file_churn}\")?;\n            } else {\n                write!(churn_info, \"\\n{:<width$}{}\", \"\", file_churn, width = pad)?;\n            }\n        }\n\n        write!(f, \"{churn_info}\")\n    }\n}\n\n#[typetag::serialize]\nimpl InfoField for ChurnInfo {\n    fn value(&self) -> String {\n        self.to_string()\n    }\n\n    fn title(&self) -> String {\n        format!(\"Churn ({})\", self.churn_pool_size)\n    }\n}\n\nfn shorten_file_path(file_path: &str, depth: usize) -> String {\n    let components: Vec<&str> = file_path.split('/').collect();\n\n    if depth == 0 || components.len() <= depth {\n        return file_path.to_string();\n    }\n\n    let truncated_components: Vec<&str> = components\n        .iter()\n        .skip(components.len() - depth)\n        .copied()\n        .collect();\n\n    format!(\"\\u{2026}/{}\", truncated_components.join(\"/\"))\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_display_file_churn() {\n        let file_churn = FileChurn::new(\"path/to/file.txt\".into(), 50, NumberSeparator::Plain);\n\n        assert_eq!(file_churn.to_string(), \"\\u{2026}/to/file.txt 50\");\n    }\n\n    #[test]\n    fn test_churn_info_value_with_two_file_churns() {\n        let file_churn_1 = FileChurn::new(\"path/to/file.txt\".into(), 50, NumberSeparator::Plain);\n        let file_churn_2 = FileChurn::new(\"file_2.txt\".into(), 30, NumberSeparator::Plain);\n\n        let churn_info = ChurnInfo {\n            file_churns: vec![file_churn_1, file_churn_2],\n            churn_pool_size: 5,\n        };\n\n        assert!(\n            churn_info\n                .value()\n                .contains(&\"\\u{2026}/to/file.txt 50\".to_string())\n        );\n\n        assert!(churn_info.value().contains(&\"file_2.txt 30\".to_string()));\n    }\n\n    #[test]\n    fn test_truncate_file_path() {\n        assert_eq!(shorten_file_path(\"path/to/file.txt\", 3), \"path/to/file.txt\");\n        assert_eq!(shorten_file_path(\"another/file.txt\", 2), \"another/file.txt\");\n        assert_eq!(shorten_file_path(\"file.txt\", 1), \"file.txt\");\n        assert_eq!(\n            shorten_file_path(\"path/to/file.txt\", 2),\n            \"\\u{2026}/to/file.txt\"\n        );\n        assert_eq!(\n            shorten_file_path(\"another/file.txt\", 1),\n            \"\\u{2026}/file.txt\"\n        );\n        assert_eq!(shorten_file_path(\"file.txt\", 0), \"file.txt\");\n    }\n\n    #[test]\n    fn test_compute_file_churns() -> Result<()> {\n        let mut number_of_commits_by_file_path = HashMap::new();\n        number_of_commits_by_file_path.insert(\"path/to/file1.txt\".into(), 2);\n        number_of_commits_by_file_path.insert(\"path/to/file2.txt\".into(), 5);\n        number_of_commits_by_file_path.insert(\"path/to/file3.txt\".into(), 3);\n        number_of_commits_by_file_path.insert(\"path/to/file4.txt\".into(), 7);\n        number_of_commits_by_file_path.insert(\"foo/x/y/file.txt\".into(), 70);\n        number_of_commits_by_file_path.insert(\"foo/x/file.txt\".into(), 10);\n\n        let number_of_file_churns_to_display = 3;\n        let number_separator = NumberSeparator::Comma;\n        let globs_to_exclude = vec![\n            \"foo/**/file.txt\".to_string(),\n            \"path/to/file2.txt\".to_string(),\n        ];\n        let actual = compute_file_churns(\n            &number_of_commits_by_file_path,\n            number_of_file_churns_to_display,\n            &globs_to_exclude,\n            number_separator,\n        )?;\n        let expected = vec![\n            FileChurn::new(String::from(\"path/to/file4.txt\"), 7, number_separator),\n            FileChurn::new(String::from(\"path/to/file3.txt\"), 3, number_separator),\n            FileChurn::new(String::from(\"path/to/file1.txt\"), 2, number_separator),\n        ];\n        assert_eq!(actual, expected);\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/info/commits.rs",
    "content": "use super::git::metrics::GitMetrics;\nuse crate::{\n    cli::NumberSeparator,\n    info::utils::{format_number, info_field::InfoField},\n};\nuse serde::Serialize;\n\n#[derive(Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct CommitsInfo {\n    pub number_of_commits: usize,\n    is_shallow: bool,\n    #[serde(skip_serializing)]\n    number_separator: NumberSeparator,\n}\n\nimpl CommitsInfo {\n    pub fn new(\n        git_metrics: &GitMetrics,\n        is_shallow: bool,\n        number_separator: NumberSeparator,\n    ) -> Self {\n        Self {\n            number_of_commits: git_metrics.total_number_of_commits,\n            is_shallow,\n            number_separator,\n        }\n    }\n}\n\n#[typetag::serialize]\nimpl InfoField for CommitsInfo {\n    fn value(&self) -> String {\n        format!(\n            \"{}{}\",\n            format_number(&self.number_of_commits, self.number_separator),\n            if self.is_shallow {\n                \" (shallow)\"\n            } else {\n                Default::default()\n            }\n        )\n    }\n\n    fn title(&self) -> String {\n        \"Commits\".into()\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    #[test]\n    fn test_display_commits_info() {\n        let commits_info = CommitsInfo {\n            number_of_commits: 3,\n            is_shallow: false,\n            number_separator: NumberSeparator::Plain,\n        };\n\n        assert_eq!(commits_info.value(), \"3\".to_string());\n    }\n\n    #[test]\n    fn test_display_commits_info_shallow() {\n        let commits_info = CommitsInfo {\n            number_of_commits: 2,\n            is_shallow: true,\n            number_separator: NumberSeparator::Plain,\n        };\n\n        assert_eq!(commits_info.value(), \"2 (shallow)\".to_string());\n    }\n}\n"
  },
  {
    "path": "src/info/contributors.rs",
    "content": "use super::utils::format_number;\nuse crate::{cli::NumberSeparator, info::utils::info_field::InfoField};\nuse serde::Serialize;\n\n#[derive(Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct ContributorsInfo {\n    pub total_number_of_authors: usize,\n    #[serde(skip_serializing)]\n    pub number_of_authors_to_display: usize,\n    #[serde(skip_serializing)]\n    number_separator: NumberSeparator,\n}\n\nimpl ContributorsInfo {\n    pub fn new(\n        total_number_of_authors: usize,\n        number_of_authors_to_display: usize,\n        number_separator: NumberSeparator,\n    ) -> Self {\n        Self {\n            total_number_of_authors,\n            number_of_authors_to_display,\n            number_separator,\n        }\n    }\n}\n\n#[typetag::serialize]\nimpl InfoField for ContributorsInfo {\n    fn value(&self) -> String {\n        if self.total_number_of_authors > self.number_of_authors_to_display {\n            format_number(&self.total_number_of_authors, self.number_separator)\n        } else {\n            String::new()\n        }\n    }\n\n    fn title(&self) -> String {\n        \"Contributors\".into()\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    #[test]\n    fn test_display_contributors_info() {\n        let contributors_info = ContributorsInfo::new(12, 2, NumberSeparator::Plain);\n        assert_eq!(contributors_info.value(), \"12\".to_string());\n        assert_eq!(contributors_info.title(), \"Contributors\".to_string());\n    }\n\n    #[test]\n    fn test_display_contributors_less_than_authors_to_display() {\n        let contributors_info = ContributorsInfo {\n            total_number_of_authors: 1,\n            number_of_authors_to_display: 3,\n            number_separator: NumberSeparator::Plain,\n        };\n\n        assert!(contributors_info.value().is_empty());\n    }\n}\n"
  },
  {
    "path": "src/info/created.rs",
    "content": "use super::{git::metrics::GitMetrics, utils::format_time};\nuse crate::info::utils::info_field::InfoField;\nuse serde::Serialize;\n\n#[derive(Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct CreatedInfo {\n    pub creation_date: String,\n}\n\nimpl CreatedInfo {\n    pub fn new(iso_time: bool, git_metrics: &GitMetrics) -> Self {\n        let creation_date = get_creation_date(git_metrics, iso_time);\n        Self { creation_date }\n    }\n}\n\nfn get_creation_date(git_metrics: &GitMetrics, iso_time: bool) -> String {\n    format_time(git_metrics.time_of_first_commit, iso_time)\n}\n\n#[typetag::serialize]\nimpl InfoField for CreatedInfo {\n    fn value(&self) -> String {\n        self.creation_date.to_string()\n    }\n\n    fn title(&self) -> String {\n        \"Created\".into()\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    #[test]\n    fn test_display_created_info() {\n        let created_info = CreatedInfo {\n            creation_date: \"2 years ago\".to_string(),\n        };\n\n        assert_eq!(created_info.value(), \"2 years ago\".to_string());\n    }\n}\n"
  },
  {
    "path": "src/info/dependencies.rs",
    "content": "use crate::{\n    cli::NumberSeparator,\n    info::utils::{format_number, info_field::InfoField},\n};\nuse onefetch_manifest::Manifest;\nuse serde::Serialize;\n\n#[derive(Serialize)]\npub struct DependenciesInfo {\n    pub dependencies: String,\n}\n\nimpl DependenciesInfo {\n    pub fn new(manifest: Option<&Manifest>, number_separator: NumberSeparator) -> Self {\n        let dependencies = manifest\n            .and_then(|m| {\n                (m.number_of_dependencies != 0).then(|| {\n                    format!(\n                        \"{} ({})\",\n                        format_number(&m.number_of_dependencies, number_separator),\n                        m.manifest_type\n                    )\n                })\n            })\n            .unwrap_or_default();\n\n        Self { dependencies }\n    }\n}\n\n#[typetag::serialize]\nimpl InfoField for DependenciesInfo {\n    fn value(&self) -> String {\n        self.dependencies.clone()\n    }\n\n    fn title(&self) -> String {\n        \"Dependencies\".into()\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n    use onefetch_manifest::ManifestType;\n\n    #[test]\n    fn should_display_license() {\n        let dependencies_info = DependenciesInfo::new(\n            Some(&Manifest {\n                manifest_type: ManifestType::Cargo,\n                name: None,\n                description: None,\n                number_of_dependencies: 21,\n                version: None,\n                license: None,\n            }),\n            NumberSeparator::Plain,\n        );\n\n        assert_eq!(dependencies_info.value(), \"21 (Cargo)\".to_string());\n    }\n}\n"
  },
  {
    "path": "src/info/description.rs",
    "content": "use crate::info::utils::info_field::InfoField;\nuse onefetch_manifest::Manifest;\nuse serde::Serialize;\n\nconst NUMBER_OF_WORDS_PER_LINE: usize = 5;\n\n#[derive(Serialize)]\npub struct DescriptionInfo {\n    pub description: Option<String>,\n}\n\nimpl DescriptionInfo {\n    pub fn new(manifest: Option<&Manifest>) -> Self {\n        let description = match manifest {\n            Some(m) => m.description.clone(),\n            None => None,\n        };\n\n        Self { description }\n    }\n}\n\n#[typetag::serialize]\nimpl InfoField for DescriptionInfo {\n    fn value(&self) -> String {\n        match &self.description {\n            Some(description) => {\n                let left_pad = self.title().len() + 2;\n                break_sentence_into_lines(description, left_pad)\n            }\n            None => String::new(),\n        }\n    }\n\n    fn title(&self) -> String {\n        \"Description\".into()\n    }\n}\n\nfn break_sentence_into_lines(sentence: &str, left_pad: usize) -> String {\n    let words: Vec<&str> = sentence.split_whitespace().collect();\n    let mut lines = Vec::new();\n\n    for (i, chunk) in words.chunks(NUMBER_OF_WORDS_PER_LINE).enumerate() {\n        let line = if i == 0 {\n            chunk.join(\" \")\n        } else {\n            format!(\"{:>width$}{}\", \"\", chunk.join(\" \"), width = left_pad)\n        };\n        lines.push(line);\n    }\n\n    lines.join(\"\\n\")\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n    use onefetch_manifest::ManifestType;\n    use rstest::rstest;\n\n    #[test]\n    fn should_display_description() {\n        let description_info = DescriptionInfo::new(Some(&Manifest {\n            manifest_type: ManifestType::Cargo,\n            name: None,\n            description: Some(\"test\".into()),\n            number_of_dependencies: 0,\n            version: Some(\"0.1.0\".into()),\n            license: None,\n        }));\n\n        assert_eq!(description_info.value(), \"test\".to_string());\n    }\n\n    #[rstest]\n    #[case(\"Hello\", \"Hello\")]\n    #[case(\n        \"Hello world, how are you doing?\",\n        \"Hello world, how are you\\n    doing?\"\n    )]\n    #[case(\n        \"This is a long sentence that needs to be broken into multiple lines.\",\n        \"This is a long sentence\\n    that needs to be broken\\n    into multiple lines.\"\n    )]\n    fn test_break_sentence_into_lines(#[case] sentence: &str, #[case] expected_result: &str) {\n        assert_eq!(break_sentence_into_lines(sentence, 4), expected_result);\n    }\n}\n"
  },
  {
    "path": "src/info/git/metrics.rs",
    "content": "use super::sig::Sig;\nuse gix::bstr::BString;\nuse gix::date::Time;\nuse std::collections::HashMap;\n\npub struct GitMetrics {\n    pub number_of_commits_by_signature: HashMap<Sig, usize>,\n    pub number_of_commits_by_file_path: HashMap<BString, usize>,\n    pub total_number_of_authors: usize,\n    pub total_number_of_commits: usize,\n    pub churn_pool_size: usize,\n    pub time_of_most_recent_commit: gix::date::Time,\n    pub time_of_first_commit: gix::date::Time,\n}\n\nimpl GitMetrics {\n    pub fn new(\n        number_of_commits_by_signature: HashMap<Sig, usize>,\n        number_of_commits_by_file_path: HashMap<BString, usize>,\n        churn_pool_size: usize,\n        time_of_first_commit: Option<Time>,\n        time_of_most_recent_commit: Option<Time>,\n    ) -> Self {\n        let total_number_of_commits = number_of_commits_by_signature.values().sum();\n        let total_number_of_authors = number_of_commits_by_signature.len();\n\n        // This could happen if a branch pointed to non-commit object, so no traversal actually happens.\n        let (time_of_first_commit, time_of_most_recent_commit) = time_of_first_commit\n            .and_then(|a| time_of_most_recent_commit.map(|b| (a, b)))\n            .unwrap_or_default();\n\n        Self {\n            number_of_commits_by_signature,\n            number_of_commits_by_file_path,\n            total_number_of_authors,\n            total_number_of_commits,\n            churn_pool_size,\n            time_of_most_recent_commit,\n            time_of_first_commit,\n        }\n    }\n}\n"
  },
  {
    "path": "src/info/git/mod.rs",
    "content": "use self::metrics::GitMetrics;\nuse self::sig::Sig;\nuse crate::cli::MyRegex;\nuse anyhow::Result;\nuse gix::bstr::BString;\nuse gix::bstr::ByteSlice;\nuse gix::diff::Options;\nuse gix::diff::tree_with_rewrites::Change;\nuse gix::prelude::ObjectIdExt;\nuse gix::revision::walk::Sorting;\nuse gix::traverse::commit::simple::CommitTimeOrder;\nuse gix::{Commit, ObjectId};\nuse std::collections::HashMap;\nuse std::sync::Arc;\nuse std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};\nuse std::sync::mpsc::{Sender, channel};\nuse std::thread::JoinHandle;\n\npub mod metrics;\npub mod sig;\n\npub fn traverse_commit_graph(\n    repo: &gix::Repository,\n    no_bots: Option<MyRegex>,\n    churn_pool_size: Option<usize>,\n    no_merges: bool,\n) -> Result<GitMetrics> {\n    let mut time_of_most_recent_commit = None;\n    let mut time_of_first_commit = None;\n    let mut number_of_commits_by_signature: HashMap<Sig, usize> = HashMap::new();\n    let mailmap = repo.open_mailmap();\n    let is_traversal_complete = Arc::new(AtomicBool::default());\n    let total_number_of_commits = Arc::new(AtomicUsize::default());\n\n    let commit_graph = repo.commit_graph().ok();\n    let can_use_commit_graph = commit_graph.is_some();\n\n    let commit_iter = repo\n        .head_commit()?\n        .id()\n        .ancestors()\n        .sorting(Sorting::ByCommitTime(CommitTimeOrder::NewestFirst))\n        .use_commit_graph(can_use_commit_graph)\n        .with_commit_graph(commit_graph)\n        .all()?;\n\n    // Best-effort strategy for Churn computation: keep computing churn while traversal runs;\n    // it stops once traversal is done and churn_pool_size is reached (if provided).\n    let (churn_thread, churn_tx) = get_churn_channel(\n        repo,\n        &mailmap,\n        no_bots.clone(),\n        &is_traversal_complete,\n        &total_number_of_commits,\n        churn_pool_size,\n    );\n\n    let mut count = 0;\n    for commit in commit_iter {\n        let commit = commit?;\n        {\n            if no_merges && commit.parent_ids.len() > 1 {\n                continue;\n            }\n\n            update_signature_counts(\n                &commit.object()?,\n                &mailmap,\n                no_bots.as_ref(),\n                &mut number_of_commits_by_signature,\n            )?;\n\n            churn_tx.send(commit.id)?;\n\n            let commit_time = gix::date::Time::new(\n                commit\n                    .commit_time\n                    .expect(\"sorting by time yields this field as part of traversal\"),\n                0,\n            );\n            time_of_most_recent_commit.get_or_insert(commit_time);\n            time_of_first_commit = commit_time.into();\n\n            count += 1;\n        }\n    }\n\n    total_number_of_commits.store(count, Ordering::SeqCst);\n    is_traversal_complete.store(true, Ordering::SeqCst);\n\n    drop(churn_tx);\n\n    let (number_of_commits_by_file_path, churn_pool_size) =\n        churn_thread.join().expect(\"never panics\")?;\n\n    let git_metrics = GitMetrics::new(\n        number_of_commits_by_signature,\n        number_of_commits_by_file_path,\n        churn_pool_size,\n        time_of_first_commit,\n        time_of_most_recent_commit,\n    );\n\n    Ok(git_metrics)\n}\n\ntype NumberOfCommitsByFilepath = HashMap<BString, usize>;\ntype ChurnPair = (NumberOfCommitsByFilepath, usize);\n\nfn get_churn_channel(\n    repo: &gix::Repository,\n    mailmap: &gix::mailmap::Snapshot,\n    bot_regex_pattern: Option<MyRegex>,\n    is_traversal_complete: &Arc<AtomicBool>,\n    total_number_of_commits: &Arc<AtomicUsize>,\n    churn_pool_size: Option<usize>,\n) -> (JoinHandle<Result<ChurnPair>>, Sender<ObjectId>) {\n    let (tx, rx) = channel::<gix::hash::ObjectId>();\n    let thread = std::thread::spawn({\n        let repo = repo.clone();\n        let mailmap = mailmap.clone();\n        let bot_regex_pattern = bot_regex_pattern.clone();\n        let is_traversal_complete = is_traversal_complete.clone();\n        let total_number_of_commits = total_number_of_commits.clone();\n        move || -> Result<_> {\n            let mut number_of_commits_by_file_path = NumberOfCommitsByFilepath::new();\n            let mut diffs_computed = 0;\n            while let Ok(commit_id) = rx.recv() {\n                let commit = repo.find_object(commit_id)?.into_commit();\n                if is_bot_commit(&commit, &mailmap, bot_regex_pattern.as_ref())? {\n                    continue;\n                }\n                compute_diff_with_parent(&mut number_of_commits_by_file_path, &commit, &repo)?;\n                diffs_computed += 1;\n                if should_break(\n                    is_traversal_complete.load(Ordering::Relaxed),\n                    total_number_of_commits.load(Ordering::Relaxed),\n                    churn_pool_size,\n                    diffs_computed,\n                ) {\n                    break;\n                }\n            }\n\n            Ok((number_of_commits_by_file_path, diffs_computed))\n        }\n    });\n\n    (thread, tx)\n}\n\nfn should_break(\n    is_traversal_complete: bool,\n    total_number_of_commits: usize,\n    churn_pool_size_opt: Option<usize>,\n    diffs_computed: usize,\n) -> bool {\n    if !is_traversal_complete {\n        return false;\n    }\n\n    churn_pool_size_opt.is_none_or(|churn_pool_size| {\n        diffs_computed >= churn_pool_size.min(total_number_of_commits)\n    })\n}\n\nfn update_signature_counts(\n    commit: &gix::Commit,\n    mailmap: &gix::mailmap::Snapshot,\n    bot_regex_pattern: Option<&MyRegex>,\n    number_of_commits_by_signature: &mut HashMap<Sig, usize>,\n) -> Result<()> {\n    let sig = mailmap.resolve(commit.author()?);\n    if !is_bot(&sig.name, bot_regex_pattern) {\n        *number_of_commits_by_signature\n            .entry(sig.into())\n            .or_insert(0) += 1;\n    }\n    Ok(())\n}\n\nfn compute_diff_with_parent(\n    change_map: &mut HashMap<BString, usize>,\n    commit: &Commit,\n    repo: &gix::Repository,\n) -> Result<()> {\n    let mut parents = commit.parent_ids();\n    let parents = (\n        parents\n            .next()\n            .and_then(|parent_id| parent_id.object().ok()?.into_commit().tree_id().ok())\n            .unwrap_or_else(|| gix::hash::ObjectId::empty_tree(repo.object_hash()).attach(repo)),\n        parents.next(),\n    );\n\n    if let (parent_tree_id, None) = parents {\n        let old_tree = parent_tree_id.object()?.into_tree();\n        let new_tree = commit.tree()?;\n        let changes =\n            repo.diff_tree_to_tree(&old_tree, &new_tree, Options::default().with_rewrites(None))?;\n        for change in &changes {\n            let is_file_change = match change {\n                Change::Addition { entry_mode, .. } | Change::Modification { entry_mode, .. } => {\n                    entry_mode.is_blob()\n                }\n                Change::Deletion { .. } | Change::Rewrite { .. } => false,\n            };\n            if is_file_change {\n                let path = change.location();\n                *change_map.entry(path.to_owned()).or_insert(0) += 1;\n            }\n        }\n    }\n\n    Ok(())\n}\n\nfn is_bot_commit(\n    commit: &Commit,\n    mailmap: &gix::mailmap::Snapshot,\n    bot_regex_pattern: Option<&MyRegex>,\n) -> Result<bool> {\n    if bot_regex_pattern.is_some() {\n        let sig = mailmap.resolve(commit.author()?);\n        Ok(is_bot(&sig.name, bot_regex_pattern))\n    } else {\n        Ok(false)\n    }\n}\n\nfn is_bot(author_name: &BString, bot_regex_pattern: Option<&MyRegex>) -> bool {\n    bot_regex_pattern.is_some_and(|regex| regex.0.is_match(author_name.to_str_lossy().as_ref()))\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::cli::NO_BOTS_DEFAULT_REGEX_PATTERN;\n    use rstest::rstest;\n    use std::str::FromStr;\n\n    #[rstest]\n    #[case(\"John Doe\", false)]\n    #[case(\"dependabot[bot]\", true)]\n    #[case(\"foo bot\", true)]\n    #[case(\"foo-bot\", true)]\n    #[case(\"bot\", false)]\n    fn test_is_bot(#[case] author_name: &str, #[case] expected: bool) -> Result<()> {\n        let from_str = MyRegex::from_str(NO_BOTS_DEFAULT_REGEX_PATTERN);\n        let no_bots: Option<MyRegex> = Some(from_str?);\n        assert_eq!(is_bot(&author_name.into(), no_bots.as_ref()), expected);\n        Ok(())\n    }\n\n    #[rstest]\n    #[case(false, 10, Some(5), 5, false)]\n    #[case(true, 10, Some(5), 5, true)]\n    #[case(true, 10, Some(8), 5, false)]\n    #[case(true, 10, Some(20), 10, true)]\n    #[case(true, 10, None, 5, true)]\n    fn test_should_break(\n        #[case] has_commit_graph_traversal_ended: bool,\n        #[case] total_number_of_commits: usize,\n        #[case] churn_pool_size_opt: Option<usize>,\n        #[case] number_of_diffs_computed: usize,\n        #[case] expected: bool,\n    ) {\n        let result = should_break(\n            has_commit_graph_traversal_ended,\n            total_number_of_commits,\n            churn_pool_size_opt,\n            number_of_diffs_computed,\n        );\n\n        assert_eq!(result, expected);\n    }\n}\n"
  },
  {
    "path": "src/info/git/sig.rs",
    "content": "#[derive(Hash, PartialOrd, Ord, Eq, PartialEq)]\npub struct Sig {\n    pub name: gix::bstr::BString,\n    pub email: gix::bstr::BString,\n}\n\nimpl From<gix::actor::Signature> for Sig {\n    fn from(gix::actor::Signature { name, email, .. }: gix::actor::Signature) -> Self {\n        Self { name, email }\n    }\n}\n"
  },
  {
    "path": "src/info/head.rs",
    "content": "use crate::info::utils::info_field::InfoField;\nuse anyhow::{Context, Result};\nuse gix::Repository;\nuse serde::Serialize;\n\n#[derive(Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct HeadRefs {\n    short_commit_id: String,\n    refs: Vec<String>,\n}\n\nimpl HeadRefs {\n    pub fn new(short_commit_id: String, refs: Vec<String>) -> HeadRefs {\n        HeadRefs {\n            short_commit_id,\n            refs,\n        }\n    }\n}\n\nimpl std::fmt::Display for HeadRefs {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        if self.refs.is_empty() {\n            write!(f, \"{}\", self.short_commit_id)\n        } else {\n            let refs_str = self\n                .refs\n                .iter()\n                .map(String::as_str)\n                .collect::<Vec<&str>>()\n                .join(\", \");\n            write!(f, \"{} ({})\", self.short_commit_id, refs_str)\n        }\n    }\n}\n\n#[derive(Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct HeadInfo {\n    pub head_refs: HeadRefs,\n}\n\nimpl HeadInfo {\n    pub fn new(repo: &Repository) -> Result<Self> {\n        let head_refs = get_head_refs(repo)?;\n        Ok(Self { head_refs })\n    }\n}\n\nfn get_head_refs(repo: &Repository) -> Result<HeadRefs> {\n    let head_id = repo.head_id().context(\"Failed to retrieve HEAD ID\")?;\n\n    let mut ref_names = Vec::new();\n\n    if let Some(head_ref) = repo.head_ref()? {\n        let head_ref_name = head_ref.name().shorten().to_string();\n        ref_names.push(head_ref_name);\n\n        if let Some(Ok(remote_tracking_ref)) =\n            repo.branch_remote_tracking_ref_name(head_ref.name(), gix::remote::Direction::Push)\n        {\n            let remote_tracking_ref_name = remote_tracking_ref.shorten().to_string();\n            ref_names.push(remote_tracking_ref_name);\n        }\n    }\n\n    Ok(HeadRefs::new(head_id.shorten()?.to_string(), ref_names))\n}\n\n#[typetag::serialize]\nimpl InfoField for HeadInfo {\n    fn value(&self) -> String {\n        self.head_refs.to_string()\n    }\n\n    fn title(&self) -> String {\n        \"HEAD\".into()\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    #[test]\n    fn test_display_head_refs() {\n        let head = HeadRefs::new(\"be561d5\".into(), vec![\"main\".into(), \"origin/main\".into()]);\n        assert_eq!(head.to_string(), \"be561d5 (main, origin/main)\");\n    }\n\n    #[test]\n    fn test_display_head_refs_with_no_refs() {\n        let head = HeadRefs::new(\"be561d5\".into(), vec![]);\n        assert_eq!(head.to_string(), \"be561d5\");\n    }\n}\n"
  },
  {
    "path": "src/info/langs/language.rs",
    "content": "use crate::info::utils::info_field::InfoField;\nuse owo_colors::OwoColorize;\nuse serde::Serialize;\nuse tokei;\n\ninclude!(concat!(env!(\"OUT_DIR\"), \"/language.rs\"));\n\nconst LANGUAGES_BAR_LENGTH: usize = 26;\n\n#[derive(Serialize)]\npub struct LanguageWithPercentage {\n    pub language: Language,\n    pub percentage: f64,\n}\n\n#[derive(Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct LanguagesInfo {\n    pub languages_with_percentage: Vec<LanguageWithPercentage>,\n    #[serde(skip_serializing)]\n    true_color: bool,\n    #[serde(skip_serializing)]\n    number_of_languages_to_display: usize,\n    #[serde(skip_serializing)]\n    info_color: DynColors,\n    #[serde(skip_serializing)]\n    nerd_fonts: bool,\n}\n\nimpl LanguagesInfo {\n    pub fn new(\n        loc_by_language: &[(Language, usize)],\n        true_color: bool,\n        number_of_languages_to_display: usize,\n        info_color: DynColors,\n        nerd_fonts: bool,\n    ) -> Self {\n        let total: usize = loc_by_language.iter().map(|(_, v)| v).sum();\n\n        let weight_by_language: Vec<(Language, f64)> = loc_by_language\n            .iter()\n            .map(|(k, v)| {\n                let mut val = *v as f64;\n                val /= total as f64;\n                val *= 100_f64;\n                (*k, val)\n            })\n            .collect();\n\n        let languages_with_percentage = weight_by_language\n            .into_iter()\n            .map(|(language, percentage)| LanguageWithPercentage {\n                language,\n                percentage,\n            })\n            .collect();\n        Self {\n            languages_with_percentage,\n            true_color,\n            number_of_languages_to_display,\n            info_color,\n            nerd_fonts,\n        }\n    }\n}\n\nimpl std::fmt::Display for LanguagesInfo {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        let color_palette = [\n            DynColors::Ansi(AnsiColors::Red),\n            DynColors::Ansi(AnsiColors::Green),\n            DynColors::Ansi(AnsiColors::Yellow),\n            DynColors::Ansi(AnsiColors::Blue),\n            DynColors::Ansi(AnsiColors::Magenta),\n            DynColors::Ansi(AnsiColors::Cyan),\n        ];\n\n        let languages: Vec<LanguageDisplayData> = prepare_languages(self, &color_palette);\n\n        let mut languages_info = build_language_bar(&languages);\n\n        for (i, language_display_data) in languages.iter().enumerate() {\n            let formatted_number = format!(\"{:.*}\", 1, language_display_data.percentage);\n            let chip = language_display_data\n                .chip_icon\n                .color(language_display_data.chip_color);\n            let language_str = format!(\n                \"{} {} \",\n                chip,\n                format!(\"{0} ({formatted_number} %)\", language_display_data.language)\n                    .color(self.info_color)\n            );\n            if i % 2 == 0 {\n                write!(\n                    languages_info,\n                    \"\\n{:<width$}{}\",\n                    \"\",\n                    language_str,\n                    width = self.title().len() + 2\n                )\n                .unwrap();\n            } else {\n                languages_info.push_str(language_str.trim_end());\n            }\n        }\n\n        write!(f, \"{languages_info}\")\n    }\n}\n\n#[derive(Debug, PartialEq)]\nstruct LanguageDisplayData {\n    language: String,\n    percentage: f64,\n    chip_color: DynColors,\n    chip_icon: char,\n}\n\nfn prepare_languages(\n    languages_info: &LanguagesInfo,\n    color_palette: &[DynColors],\n) -> Vec<LanguageDisplayData> {\n    let mut iter = languages_info\n        .languages_with_percentage\n        .iter()\n        .enumerate()\n        .map(\n            |(\n                i,\n                &LanguageWithPercentage {\n                    language,\n                    percentage,\n                },\n            )| {\n                let chip_color = if languages_info.true_color {\n                    language.get_chip_color()\n                } else {\n                    color_palette[i % color_palette.len()]\n                };\n\n                let chip_icon = language.get_chip_icon(languages_info.nerd_fonts);\n\n                LanguageDisplayData {\n                    language: language.to_string(),\n                    percentage,\n                    chip_color,\n                    chip_icon,\n                }\n            },\n        );\n    if languages_info.languages_with_percentage.len()\n        > languages_info.number_of_languages_to_display\n    {\n        let mut languages = iter\n            .by_ref()\n            .take(languages_info.number_of_languages_to_display)\n            .collect::<Vec<_>>();\n        let other_perc = iter.fold(0.0, |acc, x| acc + x.percentage);\n        languages.push(LanguageDisplayData {\n            language: \"Other\".to_string(),\n            percentage: other_perc,\n            chip_color: DynColors::Ansi(AnsiColors::White),\n            chip_icon: DEFAULT_CHIP_ICON,\n        });\n        languages\n    } else {\n        iter.collect()\n    }\n}\n\nfn build_language_bar(languages: &[LanguageDisplayData]) -> String {\n    languages\n        .iter()\n        .fold(String::new(), |mut output, language_display_data| {\n            let bar_width = std::cmp::max(\n                (language_display_data.percentage / 100. * LANGUAGES_BAR_LENGTH as f64).round()\n                    as usize,\n                1,\n            );\n            let _ = write!(\n                output,\n                \"{:<width$}\",\n                \"\".on_color(language_display_data.chip_color),\n                width = bar_width\n            );\n            output\n        })\n}\n\n#[typetag::serialize]\nimpl InfoField for LanguagesInfo {\n    fn value(&self) -> String {\n        self.to_string()\n    }\n\n    fn title(&self) -> String {\n        let mut title: String = \"Language\".into();\n        if self.languages_with_percentage.len() > 1 {\n            title.push('s');\n        }\n        title\n    }\n}\n\npub fn loc(language_type: &tokei::LanguageType, language: &tokei::Language) -> usize {\n    __loc(language_type, language.code, language.comments)\n        + language\n            .children\n            .iter()\n            .fold(0, |sum, (lang_type, reports)| {\n                sum + reports.iter().fold(0, |sum, report| {\n                    let stats = report.stats.summarise();\n                    sum + __loc(lang_type, stats.code, stats.comments)\n                })\n            })\n}\n\nfn __loc(language_type: &tokei::LanguageType, code: usize, comments: usize) -> usize {\n    match language_type {\n        tokei::LanguageType::Markdown => code + comments,\n        _ => code,\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n    use rstest::rstest;\n\n    #[test]\n    fn test_display_languages_info() {\n        let languages_info = LanguagesInfo {\n            languages_with_percentage: vec![LanguageWithPercentage {\n                language: Language::Go,\n                percentage: 100_f64,\n            }],\n            true_color: false,\n            number_of_languages_to_display: 6,\n            info_color: DynColors::Ansi(AnsiColors::White),\n            nerd_fonts: false,\n        };\n        let expected_languages_info = format!(\n            \"{:<width$}\\n{:<pad$}{} {} \",\n            \"\".on_color(DynColors::Ansi(AnsiColors::Red)),\n            \"\",\n            DEFAULT_CHIP_ICON.color(DynColors::Ansi(AnsiColors::Red)),\n            \"Go (100.0 %)\".color(DynColors::Ansi(AnsiColors::White)),\n            width = LANGUAGES_BAR_LENGTH,\n            pad = \"Language\".len() + 2\n        );\n\n        assert_eq!(languages_info.value(), expected_languages_info);\n    }\n\n    #[test]\n    fn should_display_correct_number_of_languages() {\n        let languages_info = LanguagesInfo {\n            languages_with_percentage: vec![\n                LanguageWithPercentage {\n                    language: Language::Go,\n                    percentage: 30_f64,\n                },\n                LanguageWithPercentage {\n                    language: Language::Erlang,\n                    percentage: 40_f64,\n                },\n                LanguageWithPercentage {\n                    language: Language::Java,\n                    percentage: 20_f64,\n                },\n                LanguageWithPercentage {\n                    language: Language::Rust,\n                    percentage: 10_f64,\n                },\n            ],\n            true_color: false,\n            number_of_languages_to_display: 2,\n            info_color: DynColors::Ansi(AnsiColors::White),\n            nerd_fonts: false,\n        };\n\n        assert!(\n            languages_info.value().contains(\n                &\"Go (30.0 %)\"\n                    .color(DynColors::Ansi(AnsiColors::White))\n                    .to_string()\n            )\n        );\n        assert!(\n            languages_info.value().contains(\n                &\"Erlang (40.0 %)\"\n                    .color(DynColors::Ansi(AnsiColors::White))\n                    .to_string()\n            )\n        );\n        assert!(\n            languages_info.value().contains(\n                &\"Other (30.0 %)\"\n                    .color(DynColors::Ansi(AnsiColors::White))\n                    .to_string()\n            )\n        );\n    }\n\n    #[test]\n    fn test_build_language_bar_multiple_languages() {\n        let languages: Vec<LanguageDisplayData> = vec![\n            LanguageDisplayData {\n                language: \"Rust\".to_string(),\n                percentage: 60.0,\n                chip_color: DynColors::Ansi(AnsiColors::Red),\n                chip_icon: DEFAULT_CHIP_ICON,\n            },\n            LanguageDisplayData {\n                language: \"Python\".to_string(),\n                percentage: 40.0,\n                chip_color: DynColors::Ansi(AnsiColors::Yellow),\n                chip_icon: DEFAULT_CHIP_ICON,\n            },\n        ];\n        let result = build_language_bar(&languages);\n\n        let rust_bar_width = (0.6 * LANGUAGES_BAR_LENGTH as f64).round() as usize;\n        let python_bar_width = (0.4 * LANGUAGES_BAR_LENGTH as f64).round() as usize;\n\n        let rust_bar = \" \".repeat(rust_bar_width);\n        let python_bar = \" \".repeat(python_bar_width);\n\n        let expected_result = format!(\n            \"{}{}\",\n            rust_bar.on_color(DynColors::Ansi(AnsiColors::Red)),\n            python_bar.on_color(DynColors::Ansi(AnsiColors::Yellow))\n        );\n\n        assert_eq!(result, expected_result);\n    }\n\n    #[test]\n    fn test_prepare_languages() {\n        let languages_info = LanguagesInfo {\n            languages_with_percentage: vec![\n                LanguageWithPercentage {\n                    language: Language::Go,\n                    percentage: 40_f64,\n                },\n                LanguageWithPercentage {\n                    language: Language::Erlang,\n                    percentage: 30_f64,\n                },\n                LanguageWithPercentage {\n                    language: Language::Java,\n                    percentage: 20_f64,\n                },\n                LanguageWithPercentage {\n                    language: Language::Rust,\n                    percentage: 10_f64,\n                },\n            ],\n            true_color: false,\n            number_of_languages_to_display: 2,\n            info_color: DynColors::Ansi(AnsiColors::White),\n            nerd_fonts: false,\n        };\n\n        let color_palette = [\n            DynColors::Ansi(AnsiColors::Red),\n            DynColors::Ansi(AnsiColors::Green),\n        ];\n\n        let result = prepare_languages(&languages_info, &color_palette);\n\n        let expected_result = vec![\n            LanguageDisplayData {\n                language: Language::Go.to_string(),\n                percentage: 40_f64,\n                chip_color: DynColors::Ansi(AnsiColors::Red),\n                chip_icon: DEFAULT_CHIP_ICON,\n            },\n            LanguageDisplayData {\n                language: Language::Erlang.to_string(),\n                percentage: 30_f64,\n                chip_color: DynColors::Ansi(AnsiColors::Green),\n                chip_icon: DEFAULT_CHIP_ICON,\n            },\n            LanguageDisplayData {\n                language: \"Other\".to_string(),\n                percentage: 30_f64,\n                chip_color: DynColors::Ansi(AnsiColors::White),\n                chip_icon: DEFAULT_CHIP_ICON,\n            },\n        ];\n\n        assert_eq!(result, expected_result);\n    }\n    #[rstest]\n    #[case(Language::Go, true, '\\u{e627}')]\n    #[case(Language::Abap, true, DEFAULT_CHIP_ICON)] // No Nerd Font icon for this language\n    #[case(Language::Rust, false, DEFAULT_CHIP_ICON)]\n    fn test_language_get_chip_icon(\n        #[case] language: Language,\n        #[case] use_nerd_fonts: bool,\n        #[case] expected_chip_icon: char,\n    ) {\n        let result = language.get_chip_icon(use_nerd_fonts);\n        assert_eq!(result, expected_chip_icon);\n    }\n}\n"
  },
  {
    "path": "src/info/langs/language.tera",
    "content": "use owo_colors::{\n    AnsiColors,\n    DynColors::{self, Ansi, Rgb},\n};\nuse std::fmt;\nuse std::fmt::Write;\nuse strum::EnumIter;\n\npub struct Colors {\n    basic_colors: Vec<DynColors>,\n    true_colors: Option<Vec<DynColors>>,\n}\n\nconst DEFAULT_CHIP_ICON: char = '\\u{25CF}';\n\n#[derive(Clone, PartialEq, Eq, Debug, clap::ValueEnum)]\npub enum LanguageType {\n    Programming,\n    Markup,\n    Prose,\n    Data,\n}\n\n#[derive(Clone, Copy, PartialEq, Eq, Hash, EnumIter, clap::ValueEnum, Debug, Serialize)]\n#[allow(clippy::upper_case_acronyms)]\n#[clap(rename_all = \"lowercase\")]\npub enum Language {\n    {% for language, attrs in languages -%}\n        {% if attrs.serialization %}#[clap(name=\"{{ attrs.serialization }}\")]{% endif -%}\n        {{ language }},\n    {% endfor %}\n}\n\nimpl fmt::Display for Language {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        match self {\n            {% for language, _ in languages -%}\n                Self::{{ language }} => write!(f, \"{}\", tokei::LanguageType::{{ language }}.name()),\n            {% endfor %}\n        }\n    }\n}\n\nimpl From<tokei::LanguageType> for Language {\n    fn from(language: tokei::LanguageType) -> Self {\n        match language {\n            {% for language, _ in languages -%}\n                tokei::LanguageType::{{ language }} => Self::{{ language }},\n            {% endfor %}\n            _ => unimplemented!(\"Language {:?}\", language),\n        }\n    }\n}\n\nimpl From<Language> for tokei::LanguageType {\n    fn from(language: Language) -> Self {\n        match language {\n            {% for language, _ in languages -%}\n                Language::{{ language }} => tokei::LanguageType::{{ language }},\n            {% endfor %}\n        }\n    }\n}\n\nimpl Language {\n    pub fn get_ascii_art(&self) -> &'static str {\n        match self {\n            {% for language, attrs in languages -%}\n                Language::{{ language }} => \"{{ attrs.ascii | addslashes }}\",\n            {% endfor %}\n        }\n    }\n\n    pub fn get_colors(&self, true_color: bool) -> Vec<DynColors> {\n        let colors = match self {\n            {% for language, attrs in languages -%}\n                Language::{{ language }} => Colors {\n                    basic_colors: vec![{%- for color in attrs.colors.ansi -%}Ansi(AnsiColors::{{ color | capitalize | replace(from=\"White\", to=\"Default\") }}),{% endfor %}],\n                    true_colors: {% if attrs.colors.hex -%}\n                        Some(vec![\n                            {%- for hex in attrs.colors.hex -%}\n                                {% set rgb = hex | hex_to_rgb -%}\n                                Rgb({{ rgb.r }}, {{ rgb.g }}, {{ rgb.b }}),\n                            {% endfor %}])\n                    {% else -%}None\n                    {% endif %},\n                },\n            {% endfor %}\n        };\n        match colors.true_colors {\n            Some(true_colors) if true_color => true_colors,\n            _ => colors.basic_colors,\n        }\n    }\n\n    pub fn get_type(&self) -> LanguageType {\n        match self {\n            {% for language, attrs in languages -%}\n                Language::{{ language }} => LanguageType::{{ attrs.type | title }},\n            {% endfor %}\n        }\n    }\n\n    pub fn get_chip_color(&self) -> DynColors {\n        match self {\n            {% for language, attrs in languages -%}\n                {% set rgb = attrs.colors.chip | hex_to_rgb -%}\n                Language::{{ language }} => Rgb({{ rgb.r }}, {{ rgb.g }}, {{ rgb.b }}),\n            {% endfor %}\n        }\n    }\n\n    pub fn get_chip_icon(&self, use_nerd_fonts: bool) -> char {\n        if use_nerd_fonts {\n            match self {\n                {% for language, attrs in languages -%}\n                    {% if attrs.icon is defined %}Language::{{ language }} => '{{ attrs.icon }}',{% else %}Language::{{ language }} => DEFAULT_CHIP_ICON,{% endif %}\n                {% endfor %}\n            }\n        } else {\n            DEFAULT_CHIP_ICON\n        }\n    }\n}\n\n{% for language, attrs in languages -%}\n    {% if attrs.colors.rgb %}\n        {% set ansi_length = attrs.colors.ansi | length -%}\n        {% set rgb_length = attrs.colors.rgb | length %}\n        {% if ansi_length != rgb_length %}\n            compile_error!(\"{{ language }}: ansi and rgb colors must be the same length\");\n        {% endif %}\n    {% endif -%}\n{% endfor -%}\n\n{% set max_width = 40 -%}\n{# NOTE Permitting trailing newline #}\n{% set max_height = 26 -%}\n\n\n{% for language, attrs in languages -%}\n    {% set lines = attrs.ascii | split(pat=\"\\n\") -%}\n    {% set height = lines | length -%}\n    {% if height > max_height %}\n        compile_error!(\"{{ language }}: ascii art must have {{ max_height - 1 }} or less lines, has {{ height }}\");\n    {% endif -%}\n\n    {% for line in lines -%}\n        {% set cleaned_line = line | strip_color_tokens -%}\n        {% set width = cleaned_line | length -%}\n        {% if width > max_width %}\n            compile_error!(\"{{ language }}: ascii art line {{ loop.index }} must be {{ max_width }} or less characters wide\");\n        {% endif -%}\n    {% endfor -%}\n{% endfor -%}\n"
  },
  {
    "path": "src/info/langs/mod.rs",
    "content": "use language::{Language, LanguageType};\nuse std::collections::HashMap;\nuse std::path::Path;\nuse strum::IntoEnumIterator;\n\npub mod language;\n\npub fn get_main_language(loc_by_language: &[(Language, usize)]) -> Language {\n    loc_by_language[0].0\n}\n\n/// Returns a vector of tuples containing all the languages detected inside the repository.\n/// Each tuple is composed of the language and its corresponding loc (lines of code).\n/// The vector is sorted by loc in descending order.\npub fn get_loc_by_language_sorted(\n    dir: &Path,\n    globs_to_exclude: &[String],\n    language_types: &[LanguageType],\n    include_hidden: bool,\n) -> Option<Vec<(Language, usize)>> {\n    let locs = get_locs(dir, globs_to_exclude, language_types, include_hidden);\n    let loc_by_language_opt = get_loc_by_language(&locs);\n    loc_by_language_opt.map(sort_by_loc)\n}\n\nfn sort_by_loc(map: HashMap<Language, usize>) -> Vec<(Language, usize)> {\n    let mut vec: Vec<(Language, usize)> = map.into_iter().collect();\n    vec.sort_by(|a, b| b.1.cmp(&a.1));\n    vec\n}\n\nfn get_loc_by_language(languages: &tokei::Languages) -> Option<HashMap<Language, usize>> {\n    let mut loc_by_language = HashMap::new();\n\n    for (language_name, language) in languages {\n        let loc = language::loc(language_name, language);\n\n        if loc > 0 {\n            loc_by_language.insert(Language::from(*language_name), loc);\n        }\n    }\n\n    if loc_by_language.is_empty() {\n        None\n    } else {\n        Some(loc_by_language)\n    }\n}\n\npub fn get_total_loc(loc_by_language: &[(Language, usize)]) -> usize {\n    let total_loc: usize = loc_by_language.iter().map(|(_, v)| v).sum();\n    total_loc\n}\n\nfn get_locs(\n    dir: &Path,\n    globs_to_exclude: &[String],\n    language_types: &[LanguageType],\n    include_hidden: bool,\n) -> tokei::Languages {\n    let mut languages = tokei::Languages::new();\n    let filtered_languages = filter_languages_on_type(language_types);\n\n    let tokei_config = tokei::Config {\n        types: Some(filtered_languages),\n        hidden: Some(include_hidden),\n        ..tokei::Config::default()\n    };\n    let ignored: Vec<&str> = globs_to_exclude.iter().map(AsRef::as_ref).collect();\n    languages.get_statistics(&[&dir], &ignored, &tokei_config);\n    languages\n}\n\nfn filter_languages_on_type(types: &[LanguageType]) -> Vec<tokei::LanguageType> {\n    Language::iter()\n        .filter(|language| types.contains(&language.get_type()))\n        .map(std::convert::Into::into)\n        .collect()\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n    use tokei;\n\n    #[test]\n    fn get_loc_by_language_counts_md_comments() {\n        let js = tokei::Language {\n            blanks: 25,\n            comments: 50,\n            code: 100,\n            ..Default::default()\n        };\n        let js_type = tokei::LanguageType::JavaScript;\n\n        let md = tokei::Language {\n            blanks: 50,\n            comments: 200,\n            code: 100,\n            ..Default::default()\n        };\n        let md_type = tokei::LanguageType::Markdown;\n\n        let mut languages = tokei::Languages::new();\n        languages.insert(js_type, js);\n        languages.insert(md_type, md);\n\n        let loc_by_language = get_loc_by_language(&languages).unwrap();\n\n        // NOTE: JS  with 100 lines of code, MD with 300 lines of code + comments\n        assert_eq!(loc_by_language[&Language::JavaScript], 100);\n        assert_eq!(loc_by_language[&Language::Markdown], 300);\n    }\n\n    #[test]\n    fn deeply_nested_total_loc() {\n        let mut bash_code_stats = tokei::CodeStats::new();\n        // NOTE: When inside Markdown, comments should be counted as code\n        bash_code_stats.code = 5;\n        bash_code_stats.blanks = 1;\n        bash_code_stats.comments = 2;\n\n        let mut md_code_stats = tokei::CodeStats::new();\n        md_code_stats.code = 10;\n        md_code_stats.blanks = 2;\n        md_code_stats.comments = 4;\n        md_code_stats\n            .blobs\n            .insert(tokei::LanguageType::Bash, bash_code_stats);\n        // NOTE: This may break if tokei ever does more than just assign `name` to a field\n        let mut md_report = tokei::Report::new(\"/tmp/file.ipynb\".into());\n        md_report.stats = md_code_stats;\n\n        let mut jupyter_notebook = tokei::Language::default();\n        jupyter_notebook\n            .children\n            .insert(tokei::LanguageType::Markdown, vec![md_report]);\n\n        let mut languages = tokei::Languages::new();\n        languages.insert(tokei::LanguageType::Jupyter, jupyter_notebook);\n\n        let loc_by_language = get_loc_by_language(&languages).unwrap();\n\n        assert_eq!(loc_by_language[&Language::Jupyter], 21);\n    }\n\n    // https://github.com/o2sh/onefetch/issues/966\n    #[test]\n    fn get_loc_by_language_should_not_panic_when_children_language_is_not_supported() {\n        let mut stylus_code_stats = tokei::CodeStats::new();\n        stylus_code_stats.code = 10;\n        stylus_code_stats.blanks = 2;\n        stylus_code_stats.comments = 4;\n\n        let mut stylus_report = tokei::Report::new(\"/tmp/file.vue\".into());\n        stylus_report.stats = stylus_code_stats;\n\n        let mut vue = tokei::Language {\n            blanks: 50,\n            comments: 200,\n            code: 100,\n            ..Default::default()\n        };\n\n        vue.children\n            .insert(tokei::LanguageType::Stylus, vec![stylus_report]);\n\n        let mut languages = tokei::Languages::new();\n        languages.insert(tokei::LanguageType::Vue, vue);\n\n        let loc_by_language = get_loc_by_language(&languages).unwrap();\n\n        assert_eq!(loc_by_language[&Language::Vue], 110);\n    }\n\n    #[test]\n    fn test_get_loc_by_language_sorted() {\n        let mut map = HashMap::new();\n        map.insert(Language::Ada, 300);\n        map.insert(Language::Java, 40);\n        map.insert(Language::Rust, 1200);\n        map.insert(Language::Go, 8);\n\n        let sorted_map = sort_by_loc(map);\n\n        let expected_order = vec![\n            (Language::Rust, 1200),\n            (Language::Ada, 300),\n            (Language::Java, 40),\n            (Language::Go, 8),\n        ];\n        let actual_order: Vec<_> = sorted_map.into_iter().collect();\n\n        assert_eq!(expected_order, actual_order);\n    }\n\n    #[test]\n    fn test_get_total_loc() {\n        let loc_by_language = [(Language::JavaScript, 100), (Language::Markdown, 300)];\n        assert_eq!(get_total_loc(&loc_by_language), 400);\n    }\n}\n"
  },
  {
    "path": "src/info/last_change.rs",
    "content": "use super::{git::metrics::GitMetrics, utils::format_time};\nuse crate::info::utils::info_field::InfoField;\nuse serde::Serialize;\n\n#[derive(Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct LastChangeInfo {\n    pub last_change: String,\n}\n\nimpl LastChangeInfo {\n    pub fn new(iso_time: bool, git_metrics: &GitMetrics) -> Self {\n        let last_change = get_date_of_last_commit(git_metrics, iso_time);\n\n        Self { last_change }\n    }\n}\n\nfn get_date_of_last_commit(git_metrics: &GitMetrics, iso_time: bool) -> String {\n    format_time(git_metrics.time_of_most_recent_commit, iso_time)\n}\n\n#[typetag::serialize]\nimpl InfoField for LastChangeInfo {\n    fn value(&self) -> String {\n        self.last_change.to_string()\n    }\n\n    fn title(&self) -> String {\n        \"Last change\".into()\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    #[test]\n    fn test_display_last_change_info() {\n        let last_change_info = LastChangeInfo {\n            last_change: \"34 minutes ago\".to_string(),\n        };\n\n        assert_eq!(last_change_info.value(), \"34 minutes ago\".to_string());\n    }\n}\n"
  },
  {
    "path": "src/info/license.rs",
    "content": "use crate::info::utils::info_field::InfoField;\nuse anyhow::{Result, bail};\nuse askalono::{Store, TextData};\nuse onefetch_manifest::Manifest;\nuse serde::Serialize;\nuse std::path::Path;\nuse std::{ffi::OsStr, fs};\n\nconst LICENSE_FILES: [&str; 3] = [\"LICENSE\", \"LICENCE\", \"COPYING\"];\n\nstatic CACHE_DATA: &[u8] = include_bytes!(concat!(\n    env!(\"CARGO_MANIFEST_DIR\"),\n    \"/resources/license.cache.zstd\"\n));\nconst MIN_THRESHOLD: f32 = 0.8;\n\npub struct Detector {\n    store: Store,\n}\n\nimpl Detector {\n    pub fn new() -> Result<Self> {\n        match Store::from_cache(CACHE_DATA) {\n            Ok(store) => Ok(Self { store }),\n            Err(e) => {\n                bail!(\"Could not initialize the license detector: {}\", e)\n            }\n        }\n    }\n\n    fn get_license(&self, dir: &Path, manifest: Option<&Manifest>) -> Result<String> {\n        let license_from_manifest = manifest.and_then(|m| m.license.clone()).unwrap_or_default();\n        if license_from_manifest.is_empty() {\n            let mut output = fs::read_dir(dir)?\n                .filter_map(std::result::Result::ok)\n                .map(|entry| entry.path())\n                .filter(|entry| {\n                    entry.is_file()\n                        && entry\n                            .file_name()\n                            .map(OsStr::to_string_lossy)\n                            .is_some_and(is_license_file)\n                })\n                .filter_map(|entry| {\n                    let contents = fs::read_to_string(entry).unwrap_or_default();\n                    self.analyze(&contents)\n                })\n                .collect::<Vec<_>>();\n\n            output.sort();\n            output.dedup();\n            let license = output.join(\", \");\n            Ok(license)\n        } else {\n            Ok(license_from_manifest)\n        }\n    }\n\n    fn analyze(&self, text: &str) -> Option<String> {\n        let matched = self.store.analyze(&TextData::from(text));\n\n        if matched.score >= MIN_THRESHOLD {\n            Some(matched.name.into())\n        } else {\n            None\n        }\n    }\n}\n\nfn is_license_file<S: AsRef<str>>(file_name: S) -> bool {\n    LICENSE_FILES\n        .iter()\n        .any(|&name| file_name.as_ref().starts_with(name))\n}\n\n#[derive(Serialize)]\npub struct LicenseInfo {\n    pub license: String,\n}\n\nimpl LicenseInfo {\n    pub fn new(repo_path: &Path, manifest: Option<&Manifest>) -> Result<Self> {\n        let license = Detector::new()?.get_license(repo_path, manifest)?;\n        Ok(Self { license })\n    }\n}\n\n#[typetag::serialize]\nimpl InfoField for LicenseInfo {\n    fn value(&self) -> String {\n        self.license.to_string()\n    }\n\n    fn title(&self) -> String {\n        \"License\".into()\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n    use onefetch_manifest::ManifestType;\n\n    #[test]\n    fn test_get_license() -> Result<()> {\n        let detector = Detector::new()?;\n        let license = detector.get_license(Path::new(\".\"), None)?;\n        assert_eq!(license, \"MIT\");\n        Ok(())\n    }\n\n    #[test]\n    fn test_is_license_file() {\n        for file_name in &LICENSE_FILES {\n            assert!(is_license_file(file_name));\n        }\n        assert!(!is_license_file(\"NOT_LICENSE\"));\n    }\n\n    #[test]\n    fn test_analyze() -> Result<()> {\n        let detector = Detector::new()?;\n        let license_text = fs::read_to_string(Path::new(\"LICENSE.md\"))?;\n        let license = detector.analyze(&license_text);\n        assert_eq!(license, Some(\"MIT\".into()));\n        Ok(())\n    }\n\n    #[test]\n    fn should_read_from_manifest_first() -> Result<()> {\n        let license_info = LicenseInfo::new(\n            Path::new(\".\"),\n            Some(&Manifest {\n                manifest_type: ManifestType::Cargo,\n                name: None,\n                description: None,\n                number_of_dependencies: 0,\n                version: None,\n                license: Some(\"LICENSE\".into()),\n            }),\n        )?;\n        assert_eq!(license_info.value(), \"LICENSE\");\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/info/loc.rs",
    "content": "use super::utils::format_number;\nuse crate::info::langs::get_total_loc;\nuse crate::info::langs::language::Language;\nuse crate::{cli::NumberSeparator, info::utils::info_field::InfoField};\nuse serde::Serialize;\n\n#[derive(Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct LocInfo {\n    pub lines_of_code: usize,\n    #[serde(skip_serializing)]\n    number_separator: NumberSeparator,\n}\n\nimpl LocInfo {\n    pub fn new(loc_by_language: &[(Language, usize)], number_separator: NumberSeparator) -> Self {\n        let lines_of_code = get_total_loc(loc_by_language);\n        Self {\n            lines_of_code,\n            number_separator,\n        }\n    }\n}\n\n#[typetag::serialize]\nimpl InfoField for LocInfo {\n    fn value(&self) -> String {\n        format_number(&self.lines_of_code, self.number_separator)\n    }\n\n    fn title(&self) -> String {\n        \"Lines of code\".into()\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    #[test]\n    fn test_display_loc_info() {\n        let loc_info = LocInfo {\n            lines_of_code: 1235,\n            number_separator: NumberSeparator::Plain,\n        };\n\n        assert_eq!(loc_info.value(), \"1235\".to_string());\n    }\n}\n"
  },
  {
    "path": "src/info/mod.rs",
    "content": "use self::authors::AuthorsInfo;\nuse self::churn::ChurnInfo;\nuse self::commits::CommitsInfo;\nuse self::contributors::ContributorsInfo;\nuse self::created::CreatedInfo;\nuse self::dependencies::DependenciesInfo;\nuse self::description::DescriptionInfo;\nuse self::git::metrics::GitMetrics;\nuse self::git::traverse_commit_graph;\nuse self::head::HeadInfo;\nuse self::langs::language::Language;\nuse self::langs::language::LanguagesInfo;\nuse self::last_change::LastChangeInfo;\nuse self::license::LicenseInfo;\nuse self::loc::LocInfo;\nuse self::pending::PendingInfo;\nuse self::project::ProjectInfo;\nuse self::size::SizeInfo;\nuse self::title::Title;\nuse self::url::UrlInfo;\nuse self::url::get_repo_url;\nuse self::utils::info_field::{InfoField, InfoType};\nuse self::version::VersionInfo;\nuse crate::cli::{CliOptions, NumberSeparator, When, is_truecolor_terminal};\nuse crate::ui::get_ascii_colors;\nuse crate::ui::text_colors::TextColors;\nuse anyhow::{Context, Result};\nuse gix::Repository;\nuse onefetch_manifest::Manifest;\nuse owo_colors::{DynColors, OwoColorize};\nuse serde::Serialize;\nuse std::path::Path;\n\nmod authors;\nmod churn;\nmod commits;\nmod contributors;\nmod created;\nmod dependencies;\nmod description;\nmod git;\nmod head;\npub mod langs;\nmod last_change;\nmod license;\nmod loc;\nmod pending;\nmod project;\nmod size;\nmod title;\nmod url;\npub mod utils;\nmod version;\n\n#[derive(Serialize, Default)]\n#[serde(rename_all = \"camelCase\")]\npub struct Info {\n    title: Option<Title>,\n    info_fields: Vec<Box<dyn InfoField>>,\n    #[serde(skip_serializing)]\n    text_colors: TextColors,\n    #[serde(skip_serializing)]\n    no_color_palette: bool,\n    #[serde(skip_serializing)]\n    no_bold: bool,\n    #[serde(skip_serializing)]\n    pub dominant_language: Option<Language>,\n    #[serde(skip_serializing)]\n    pub ascii_colors: Vec<DynColors>,\n}\n\nstruct InfoBuilder {\n    title: Option<Title>,\n    info_fields: Vec<Box<dyn InfoField>>,\n    disabled_fields: Vec<InfoType>,\n    no_title: bool,\n}\n\nimpl std::fmt::Display for Info {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        // Title\n        if let Some(title) = &self.title {\n            write!(f, \"{title}\")?;\n        }\n\n        // Info lines\n        for info_field in self.info_fields.iter() {\n            info_field.write_styled(f, self.no_bold, &self.text_colors)?;\n        }\n\n        // Palette\n        if !self.no_color_palette {\n            writeln!(\n                f,\n                \"\\n{0}{1}{2}{3}{4}{5}{6}{7}\",\n                \"   \".on_black(),\n                \"   \".on_red(),\n                \"   \".on_green(),\n                \"   \".on_yellow(),\n                \"   \".on_blue(),\n                \"   \".on_magenta(),\n                \"   \".on_cyan(),\n                \"   \".on_white()\n            )?;\n        }\n\n        Ok(())\n    }\n}\n\npub fn build_info(cli_options: &CliOptions) -> Result<Info> {\n    let repo = gix::discover(&cli_options.input)?;\n    let repo_path = get_work_dir(&repo)?;\n    // Compute LOC in a separate thread so it runs in parallel with commit-graph traversal.\n    let loc_by_language_sorted_handle = std::thread::spawn({\n        let globs_to_exclude = cli_options.info.exclude.clone();\n        let language_types = cli_options.info.r#type.clone();\n        let include_hidden = cli_options.info.include_hidden;\n        let workdir = repo_path.clone();\n        move || {\n            langs::get_loc_by_language_sorted(\n                &workdir,\n                &globs_to_exclude,\n                &language_types,\n                include_hidden,\n            )\n        }\n    });\n    let git_metrics = traverse_commit_graph(\n        &repo,\n        cli_options.info.no_bots.clone(),\n        cli_options.info.churn_pool_size,\n        cli_options.info.no_merges,\n    )\n    .context(\"Failed to traverse Git commit history\")?;\n    let manifest = get_manifest(&repo_path)?;\n    let repo_url = get_repo_url(\n        &repo,\n        cli_options.info.hide_token,\n        cli_options.info.http_url,\n    )\n    .context(\"Failed to determine repository URL\")?;\n    let true_color = match cli_options.ascii.true_color {\n        When::Always => true,\n        When::Never => false,\n        When::Auto => is_truecolor_terminal(),\n    };\n    let loc_by_language = loc_by_language_sorted_handle\n        .join()\n        .ok()\n        .context(\"BUG: panic in language statistics thread\")?;\n    let dominant_language = loc_by_language\n        .as_ref()\n        .map(|v| langs::get_main_language(v));\n    let ascii_colors = get_ascii_colors(\n        dominant_language.as_ref(),\n        cli_options.ascii.ascii_language.as_ref(),\n        &cli_options.ascii.ascii_colors,\n        true_color,\n    );\n    let text_colors = TextColors::new(&cli_options.text_formatting.text_colors, ascii_colors[0]);\n    let no_bold = cli_options.text_formatting.no_bold;\n    let number_separator = cli_options.text_formatting.number_separator;\n    let iso_time = cli_options.text_formatting.iso_time;\n    let number_of_languages_to_display = cli_options.info.number_of_languages;\n    let number_of_authors_to_display = cli_options.info.number_of_authors;\n    let number_of_file_churns_to_display = cli_options.info.number_of_file_churns;\n    let globs_to_exclude = &cli_options.info.exclude;\n    let show_email = cli_options.info.email;\n\n    Ok(InfoBuilder::new(cli_options)\n        .title(&repo, no_bold, &text_colors)\n        .project(&repo, &repo_url, manifest.as_ref(), number_separator)?\n        .description(manifest.as_ref())\n        .head(&repo)?\n        .pending(&repo)?\n        .version(&repo, manifest.as_ref())?\n        .created(&git_metrics, iso_time)\n        .languages(\n            loc_by_language.as_ref(),\n            true_color,\n            number_of_languages_to_display,\n            &text_colors,\n            cli_options,\n        )\n        .dependencies(manifest.as_ref(), number_separator)\n        .authors(\n            &git_metrics,\n            number_of_authors_to_display,\n            show_email,\n            number_separator,\n        )\n        .last_change(&git_metrics, iso_time)\n        .contributors(&git_metrics, number_of_authors_to_display, number_separator)\n        .url(&repo_url)\n        .commits(&git_metrics, repo.is_shallow(), number_separator)\n        .churn(\n            &git_metrics,\n            number_of_file_churns_to_display,\n            globs_to_exclude,\n            number_separator,\n        )?\n        .loc(loc_by_language.as_ref(), number_separator)\n        .size(&repo, number_separator)\n        .license(&repo_path, manifest.as_ref())?\n        .build(cli_options, text_colors, dominant_language, ascii_colors))\n}\n\nimpl InfoBuilder {\n    fn new(cli_options: &CliOptions) -> Self {\n        Self {\n            title: None,\n            info_fields: Vec::new(),\n            disabled_fields: cli_options.info.disabled_fields.clone(),\n            no_title: cli_options.info.no_title,\n        }\n    }\n\n    fn title(mut self, repo: &Repository, no_bold: bool, text_colors: &TextColors) -> Self {\n        if !self.no_title {\n            let title = Title::new(\n                repo,\n                text_colors.title,\n                text_colors.tilde,\n                text_colors.underline,\n                !no_bold,\n            );\n            self.title = Some(title);\n        }\n        self\n    }\n\n    fn description(mut self, manifest: Option<&Manifest>) -> Self {\n        if !self.disabled_fields.contains(&InfoType::Description) {\n            let description = DescriptionInfo::new(manifest);\n            self.info_fields.push(Box::new(description));\n        }\n        self\n    }\n\n    fn pending(mut self, repo: &Repository) -> Result<Self> {\n        if !self.disabled_fields.contains(&InfoType::Pending) {\n            let pending = PendingInfo::new(repo)?;\n            self.info_fields.push(Box::new(pending));\n        }\n        Ok(self)\n    }\n\n    fn url(mut self, repo_url: &str) -> Self {\n        if !self.disabled_fields.contains(&InfoType::URL) {\n            let repo_url = UrlInfo::new(repo_url);\n            self.info_fields.push(Box::new(repo_url));\n        }\n        self\n    }\n\n    fn project(\n        mut self,\n        repo: &Repository,\n        repo_url: &str,\n        manifest: Option<&Manifest>,\n        number_separator: NumberSeparator,\n    ) -> Result<Self> {\n        if !self.disabled_fields.contains(&InfoType::Project) {\n            let project = ProjectInfo::new(repo, repo_url, manifest, number_separator)?;\n            self.info_fields.push(Box::new(project));\n        }\n        Ok(self)\n    }\n\n    fn head(mut self, repo: &Repository) -> Result<Self> {\n        if !self.disabled_fields.contains(&InfoType::Head) {\n            let head = HeadInfo::new(repo)?;\n            self.info_fields.push(Box::new(head));\n        }\n        Ok(self)\n    }\n\n    fn version(mut self, repo: &Repository, manifest: Option<&Manifest>) -> Result<Self> {\n        if !self.disabled_fields.contains(&InfoType::Version) {\n            let version = VersionInfo::new(repo, manifest)?;\n            self.info_fields.push(Box::new(version));\n        }\n        Ok(self)\n    }\n\n    fn size(mut self, repo: &Repository, number_separator: NumberSeparator) -> Self {\n        if !self.disabled_fields.contains(&InfoType::Size) {\n            let size = SizeInfo::new(repo, number_separator);\n            self.info_fields.push(Box::new(size));\n        }\n        self\n    }\n\n    fn license(mut self, repo_path: &Path, manifest: Option<&Manifest>) -> Result<Self> {\n        if !self.disabled_fields.contains(&InfoType::License) {\n            let license = LicenseInfo::new(repo_path, manifest)?;\n            self.info_fields.push(Box::new(license));\n        }\n        Ok(self)\n    }\n\n    fn created(mut self, git_metrics: &GitMetrics, iso_time: bool) -> Self {\n        if !self.disabled_fields.contains(&InfoType::Created) {\n            let created = CreatedInfo::new(iso_time, git_metrics);\n            self.info_fields.push(Box::new(created));\n        }\n        self\n    }\n\n    fn languages(\n        mut self,\n        loc_by_language_opt: Option<&Vec<(Language, usize)>>,\n        true_color: bool,\n        number_of_languages: usize,\n        text_colors: &TextColors,\n        cli_options: &CliOptions,\n    ) -> Self {\n        if !self.disabled_fields.contains(&InfoType::Languages)\n            && let Some(loc_by_language) = loc_by_language_opt\n        {\n            let languages = LanguagesInfo::new(\n                loc_by_language,\n                true_color,\n                number_of_languages,\n                text_colors.info,\n                cli_options.visuals.nerd_fonts,\n            );\n            self.info_fields.push(Box::new(languages));\n        }\n        self\n    }\n\n    fn dependencies(\n        mut self,\n        manifest: Option<&Manifest>,\n        number_separator: NumberSeparator,\n    ) -> Self {\n        if !self.disabled_fields.contains(&InfoType::Dependencies) {\n            let dependencies = DependenciesInfo::new(manifest, number_separator);\n            self.info_fields.push(Box::new(dependencies));\n        }\n        self\n    }\n\n    fn authors(\n        mut self,\n        git_metrics: &GitMetrics,\n        number_of_authors_to_display: usize,\n        show_email: bool,\n        number_separator: NumberSeparator,\n    ) -> Self {\n        if !self.disabled_fields.contains(&InfoType::Authors) {\n            let authors = AuthorsInfo::new(\n                &git_metrics.number_of_commits_by_signature,\n                git_metrics.total_number_of_commits,\n                number_of_authors_to_display,\n                show_email,\n                number_separator,\n            );\n            self.info_fields.push(Box::new(authors));\n        }\n        self\n    }\n\n    fn last_change(mut self, git_metrics: &GitMetrics, iso_time: bool) -> Self {\n        if !self.disabled_fields.contains(&InfoType::LastChange) {\n            let last_change = LastChangeInfo::new(iso_time, git_metrics);\n            self.info_fields.push(Box::new(last_change));\n        }\n        self\n    }\n\n    fn contributors(\n        mut self,\n        git_metrics: &GitMetrics,\n        number_of_authors_to_display: usize,\n        number_separator: NumberSeparator,\n    ) -> Self {\n        if !self.disabled_fields.contains(&InfoType::Contributors) {\n            let contributors = ContributorsInfo::new(\n                git_metrics.total_number_of_authors,\n                number_of_authors_to_display,\n                number_separator,\n            );\n            self.info_fields.push(Box::new(contributors));\n        }\n        self\n    }\n\n    fn commits(\n        mut self,\n        git_metrics: &GitMetrics,\n        is_shallow: bool,\n        number_separator: NumberSeparator,\n    ) -> Self {\n        if !self.disabled_fields.contains(&InfoType::Commits) {\n            let commits = CommitsInfo::new(git_metrics, is_shallow, number_separator);\n            self.info_fields.push(Box::new(commits));\n        }\n        self\n    }\n\n    fn churn(\n        mut self,\n        git_metrics: &GitMetrics,\n        number_of_file_churns_to_display: usize,\n        globs_to_exclude: &[String],\n        number_separator: NumberSeparator,\n    ) -> Result<Self> {\n        if !self.disabled_fields.contains(&InfoType::Churn) {\n            let churn = ChurnInfo::new(\n                &git_metrics.number_of_commits_by_file_path,\n                git_metrics.churn_pool_size,\n                number_of_file_churns_to_display,\n                globs_to_exclude,\n                number_separator,\n            )?;\n            self.info_fields.push(Box::new(churn));\n        }\n        Ok(self)\n    }\n\n    fn loc(\n        mut self,\n        loc_by_language_opt: Option<&Vec<(Language, usize)>>,\n        number_separator: NumberSeparator,\n    ) -> Self {\n        if !self.disabled_fields.contains(&InfoType::LinesOfCode)\n            && let Some(loc_by_language) = loc_by_language_opt\n        {\n            let lines_of_code = LocInfo::new(loc_by_language, number_separator);\n            self.info_fields.push(Box::new(lines_of_code));\n        }\n        self\n    }\n\n    fn build(\n        self,\n        cli_options: &CliOptions,\n        text_colors: TextColors,\n        dominant_language: Option<Language>,\n        ascii_colors: Vec<DynColors>,\n    ) -> Info {\n        Info {\n            title: self.title,\n            info_fields: self.info_fields,\n            text_colors,\n            dominant_language,\n            ascii_colors,\n            no_color_palette: cli_options.visuals.no_color_palette,\n            no_bold: cli_options.text_formatting.no_bold,\n        }\n    }\n}\n\nfn get_manifest(repo_path: &Path) -> Result<Option<Manifest>> {\n    let manifests = onefetch_manifest::get_manifests(repo_path)?;\n\n    if manifests.is_empty() {\n        Ok(None)\n    } else {\n        Ok(manifests.first().cloned())\n    }\n}\n\npub fn get_work_dir(repo: &gix::Repository) -> Result<std::path::PathBuf> {\n    Ok(repo\n        .workdir()\n        .context(\"please run onefetch inside of a non-bare git repository\")?\n        .to_owned())\n}\n"
  },
  {
    "path": "src/info/pending.rs",
    "content": "use crate::info::utils::info_field::InfoField;\nuse anyhow::Result;\nuse gix::Repository;\nuse serde::Serialize;\n\n#[derive(Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct PendingInfo {\n    added: usize,\n    deleted: usize,\n    modified: usize,\n}\n\nimpl PendingInfo {\n    pub fn new(repo: &Repository) -> Result<Self> {\n        let statuses = repo\n            .status(gix::progress::Discard)?\n            .dirwalk_options(|options| {\n                options.emit_untracked(gix::dir::walk::EmissionMode::Matching)\n            })\n            .into_index_worktree_iter(Vec::new())?;\n\n        let (added, deleted, modified) = statuses\n            .take_while(Result::is_ok)\n            .filter_map(Result::ok)\n            .filter_map(|item| item.summary())\n            .fold((0, 0, 0), |(added, deleted, modified), status| {\n                use gix::status::index_worktree::iter::Summary;\n                match status {\n                    Summary::Removed => (added, deleted + 1, modified),\n                    Summary::Added | Summary::Copied => (added + 1, deleted, modified),\n                    Summary::Modified | Summary::TypeChange => (added, deleted, modified + 1),\n                    Summary::Renamed => (added + 1, deleted + 1, modified),\n                    Summary::IntentToAdd | Summary::Conflict => (added, deleted, modified),\n                }\n            });\n        Ok(Self {\n            added,\n            deleted,\n            modified,\n        })\n    }\n}\n\nimpl std::fmt::Display for PendingInfo {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        let mut result = String::new();\n        if self.modified > 0 {\n            result = format!(\"{}+-\", self.modified);\n        }\n\n        if self.added > 0 {\n            result = format!(\"{result} {}+\", self.added);\n        }\n\n        if self.deleted > 0 {\n            result = format!(\"{result} {}-\", self.deleted);\n        }\n\n        write!(f, \"{}\", result.trim())\n    }\n}\n\n#[typetag::serialize]\nimpl InfoField for PendingInfo {\n    fn value(&self) -> String {\n        self.to_string()\n    }\n\n    fn title(&self) -> String {\n        \"Pending\".into()\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    #[test]\n    fn test_display_pending_info() {\n        let pending_info = PendingInfo {\n            added: 0,\n            deleted: 0,\n            modified: 4,\n        };\n\n        assert_eq!(pending_info.value(), \"4+-\".to_string());\n    }\n}\n"
  },
  {
    "path": "src/info/project.rs",
    "content": "use crate::{cli::NumberSeparator, info::utils::info_field::InfoField};\nuse anyhow::Result;\nuse gix::{Repository, bstr::ByteSlice};\nuse onefetch_manifest::Manifest;\nuse serde::Serialize;\nuse std::ffi::OsStr;\n\nuse super::utils::format_number;\n\n#[derive(Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct ProjectInfo {\n    pub repo_name: String,\n    pub number_of_branches: usize,\n    pub number_of_tags: usize,\n    #[serde(skip_serializing)]\n    number_separator: NumberSeparator,\n}\n\nimpl ProjectInfo {\n    pub fn new(\n        repo: &Repository,\n        repo_url: &str,\n        manifest: Option<&Manifest>,\n        number_separator: NumberSeparator,\n    ) -> Result<Self> {\n        let repo_name = get_repo_name(repo_url, manifest)?;\n        let number_of_branches = get_number_of_branches(repo)?;\n        let number_of_tags = get_number_of_tags(repo)?;\n        Ok(Self {\n            repo_name,\n            number_of_branches,\n            number_of_tags,\n            number_separator,\n        })\n    }\n}\n\nfn get_repo_name(repo_url: &str, manifest: Option<&Manifest>) -> Result<String> {\n    if repo_url.is_empty() {\n        return Ok(String::default());\n    }\n    let url = gix::url::parse(repo_url.into())?;\n    let path = gix::path::from_bstr(url.path.as_bstr());\n    let repo_name = path\n        .with_extension(\"\")\n        .file_name()\n        .map(OsStr::to_string_lossy)\n        .map(std::borrow::Cow::into_owned)\n        .unwrap_or_default();\n\n    if repo_name.is_empty() {\n        let repo_name_from_manifest = manifest.and_then(|m| m.name.clone()).unwrap_or_default();\n        Ok(repo_name_from_manifest)\n    } else {\n        Ok(repo_name)\n    }\n}\n\n// This collects the repo size excluding .git\nfn get_number_of_tags(repo: &Repository) -> Result<usize> {\n    Ok(repo.references()?.tags()?.count())\n}\n\nfn get_number_of_branches(repo: &Repository) -> Result<usize> {\n    let mut number_of_branches = repo.references()?.remote_branches()?.count();\n    number_of_branches = number_of_branches.saturating_sub(1); //Exclude origin/HEAD -> origin/main\n    Ok(number_of_branches)\n}\n\nimpl std::fmt::Display for ProjectInfo {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        if self.repo_name.is_empty() {\n            Ok(())\n        } else {\n            let branches_str = match self.number_of_branches {\n                0 => String::new(),\n                1 => \"1 branch\".into(),\n                _ => format!(\n                    \"{} branches\",\n                    format_number(&self.number_of_branches, self.number_separator)\n                ),\n            };\n\n            let tags_str = match self.number_of_tags {\n                0 => String::new(),\n                1 => \"1 tag\".into(),\n                _ => format!(\n                    \"{} tags\",\n                    format_number(&self.number_of_tags, self.number_separator)\n                ),\n            };\n\n            if tags_str.is_empty() && branches_str.is_empty() {\n                write!(f, \"{}\", self.repo_name)\n            } else if branches_str.is_empty() || tags_str.is_empty() {\n                write!(f, \"{} ({}{})\", self.repo_name, tags_str, branches_str)\n            } else {\n                write!(f, \"{} ({}, {})\", self.repo_name, branches_str, tags_str)\n            }\n        }\n    }\n}\n\n#[typetag::serialize]\nimpl InfoField for ProjectInfo {\n    fn value(&self) -> String {\n        self.to_string()\n    }\n\n    fn title(&self) -> String {\n        \"Project\".into()\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    #[test]\n    fn test_display_project_info() {\n        let project_info = ProjectInfo {\n            repo_name: \"onefetch\".to_string(),\n            number_of_branches: 3,\n            number_of_tags: 2,\n            number_separator: NumberSeparator::Plain,\n        };\n\n        assert_eq!(\n            project_info.value(),\n            \"onefetch (3 branches, 2 tags)\".to_string()\n        );\n    }\n\n    #[test]\n    fn test_display_project_info_when_no_branches_no_tags() {\n        let project_info = ProjectInfo {\n            repo_name: \"onefetch\".to_string(),\n            number_of_branches: 0,\n            number_of_tags: 0,\n            number_separator: NumberSeparator::Plain,\n        };\n\n        assert_eq!(project_info.value(), \"onefetch\".to_string());\n    }\n\n    #[test]\n    fn test_display_project_info_when_no_tags() {\n        let project_info = ProjectInfo {\n            repo_name: \"onefetch\".to_string(),\n            number_of_branches: 3,\n            number_of_tags: 0,\n            number_separator: NumberSeparator::Plain,\n        };\n\n        assert_eq!(project_info.value(), \"onefetch (3 branches)\".to_string());\n    }\n\n    #[test]\n    fn test_display_project_info_when_no_branches() {\n        let project_info = ProjectInfo {\n            repo_name: \"onefetch\".to_string(),\n            number_of_branches: 0,\n            number_of_tags: 2,\n            number_separator: NumberSeparator::Plain,\n        };\n\n        assert_eq!(project_info.value(), \"onefetch (2 tags)\".to_string());\n    }\n\n    #[test]\n    fn test_display_project_info_when_one_branch_one_tag() {\n        let project_info = ProjectInfo {\n            repo_name: \"onefetch\".to_string(),\n            number_of_branches: 1,\n            number_of_tags: 1,\n            number_separator: NumberSeparator::Plain,\n        };\n\n        assert_eq!(\n            project_info.value(),\n            \"onefetch (1 branch, 1 tag)\".to_string()\n        );\n    }\n\n    #[test]\n    fn test_get_repo_name_when_no_remote() -> Result<()> {\n        let repo_name = get_repo_name(\"\", None)?;\n        assert!(repo_name.is_empty());\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_display_project_info_when_no_repo_name() {\n        let project_info = ProjectInfo {\n            repo_name: String::new(),\n            number_of_branches: 0,\n            number_of_tags: 0,\n            number_separator: NumberSeparator::Plain,\n        };\n\n        assert!(project_info.value().is_empty());\n    }\n}\n"
  },
  {
    "path": "src/info/size.rs",
    "content": "use crate::{\n    cli::NumberSeparator,\n    info::utils::{format_number, info_field::InfoField},\n};\nuse byte_unit::{Byte, UnitType};\nuse gix::Repository;\nuse serde::Serialize;\n\n#[derive(Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct SizeInfo {\n    pub repo_size: String,\n    pub file_count: u64,\n    #[serde(skip_serializing)]\n    number_separator: NumberSeparator,\n}\n\nimpl SizeInfo {\n    pub fn new(repo: &Repository, number_separator: NumberSeparator) -> Self {\n        let (repo_size, file_count) = get_repo_size(repo);\n        Self {\n            repo_size,\n            file_count,\n            number_separator,\n        }\n    }\n}\n\nfn get_repo_size(repo: &Repository) -> (String, u64) {\n    let (repo_size, file_count) = match repo.index() {\n        Ok(index) => {\n            let repo_size = index.entries().iter().map(|e| e.stat.size as u64).sum();\n            (repo_size, index.entries().len() as u64)\n        }\n        _ => (0, 0),\n    };\n\n    (bytes_to_human_readable(repo_size), file_count)\n}\n\nfn bytes_to_human_readable(bytes: u64) -> String {\n    let byte = Byte::from_u64(bytes);\n    let adjusted_byte_based = byte.get_appropriate_unit(UnitType::Binary);\n    format!(\"{adjusted_byte_based:#.2}\")\n}\n\nimpl std::fmt::Display for SizeInfo {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        match self.file_count {\n            0 => write!(f, \"{}\", &self.repo_size),\n            1 => write!(f, \"{} (1 file)\", self.repo_size),\n            _ => {\n                write!(\n                    f,\n                    \"{} ({} files)\",\n                    self.repo_size,\n                    format_number(&self.file_count, self.number_separator)\n                )\n            }\n        }\n    }\n}\n\n#[typetag::serialize]\nimpl InfoField for SizeInfo {\n    fn value(&self) -> String {\n        self.to_string()\n    }\n    fn title(&self) -> String {\n        \"Size\".into()\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use rstest::rstest;\n\n    use super::*;\n\n    #[test]\n    fn test_display_size_info() {\n        let size_info = SizeInfo {\n            repo_size: \"2.40 MiB\".to_string(),\n            file_count: 123,\n            number_separator: NumberSeparator::Plain,\n        };\n\n        assert_eq!(size_info.value(), \"2.40 MiB (123 files)\".to_string());\n    }\n\n    #[test]\n    fn test_display_size_info_no_files() {\n        let size_info = SizeInfo {\n            repo_size: \"2.40 MiB\".to_string(),\n            file_count: 0,\n            number_separator: NumberSeparator::Plain,\n        };\n\n        assert_eq!(size_info.value(), \"2.40 MiB\".to_string());\n    }\n\n    #[test]\n    fn test_display_size_info_one_files() {\n        let size_info = SizeInfo {\n            repo_size: \"2.40 MiB\".to_string(),\n            file_count: 1,\n            number_separator: NumberSeparator::Plain,\n        };\n\n        assert_eq!(size_info.value(), \"2.40 MiB (1 file)\".to_string());\n    }\n\n    #[rstest(\n        case(0, \"0 B\"),\n        case(1023, \"1023 B\"),\n        case(1024, \"1 KiB\"),\n        case(2048, \"2 KiB\"),\n        case(1048576, \"1 MiB\"),\n        case(1099511627776, \"1 TiB\"),\n        case(2577152, \"2.46 MiB\")\n    )]\n    fn test_bytes_to_human_readable(#[case] input: u64, #[case] expected: &str) {\n        assert_eq!(bytes_to_human_readable(input), expected);\n    }\n}\n"
  },
  {
    "path": "src/info/snapshots/onefetch__info__authors__test__author_info_alignment_with_three_authors.snap",
    "content": "---\nsource: src/info/authors.rs\nexpression: buffer\n---\n\u001b[38;2;255;255;255;1mAuthors\u001b[0m\u001b[39;1m:\u001b[0m \u001b[39m75% John Doe <john.doe@email.com> 1500\u001b[0m\n\u001b[39m         80% Roberto Berto 240\u001b[0m\n\u001b[39m          1% Jane Doe 1\u001b[0m\n\n"
  },
  {
    "path": "src/info/snapshots/onefetch__info__authors__test__author_info_with_one_author.snap",
    "content": "---\nsource: src/info/authors.rs\nexpression: buffer\n---\n\u001b[38;2;255;255;255;1mAuthor\u001b[0m\u001b[39;1m:\u001b[0m \u001b[39m75% John Doe <john.doe@email.com> 1500\u001b[0m\n\n"
  },
  {
    "path": "src/info/snapshots/onefetch__info__authors__test__author_info_with_two_authors.snap",
    "content": "---\nsource: src/info/authors.rs\nexpression: buffer\n---\n\u001b[38;2;255;255;255;1mAuthors\u001b[0m\u001b[39;1m:\u001b[0m \u001b[39m75% John Doe <john.doe@email.com> 1500\u001b[0m\n\u001b[39m         80% Roberto Berto 240\u001b[0m\n\n"
  },
  {
    "path": "src/info/title.rs",
    "content": "use crate::{cli, info::utils::get_style};\nuse gix::Repository;\nuse owo_colors::{DynColors, OwoColorize};\nuse serde::Serialize;\n\n#[derive(Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct Title {\n    pub git_username: String,\n    pub git_version: String,\n    #[serde(skip_serializing)]\n    pub title_color: DynColors,\n    #[serde(skip_serializing)]\n    pub tilde_color: DynColors,\n    #[serde(skip_serializing)]\n    pub underline_color: DynColors,\n    #[serde(skip_serializing)]\n    pub is_bold: bool,\n}\n\nimpl Title {\n    pub fn new(\n        repo: &Repository,\n        title_color: DynColors,\n        tilde_color: DynColors,\n        underline_color: DynColors,\n        is_bold: bool,\n    ) -> Self {\n        let git_username = get_git_username(repo);\n        let git_version = cli::get_git_version();\n        Self {\n            git_username,\n            git_version,\n            title_color,\n            tilde_color,\n            underline_color,\n            is_bold,\n        }\n    }\n}\npub fn get_git_username(repo: &Repository) -> String {\n    repo.committer()\n        .and_then(Result::ok)\n        .map(|c| c.name.to_string())\n        .unwrap_or_default()\n}\n\nimpl std::fmt::Display for Title {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        if !&self.git_username.is_empty() || !&self.git_version.is_empty() {\n            let git_info_length = self.git_username.len() + self.git_version.len();\n            let title_style = get_style(self.is_bold, self.title_color);\n\n            let (git_info_field_str, git_info_field_len) =\n                if !&self.git_username.is_empty() && !&self.git_version.is_empty() {\n                    let tilde_style = get_style(self.is_bold, self.tilde_color);\n                    (\n                        format!(\n                            \"{} {} {}\",\n                            &self.git_username.style(title_style),\n                            \"~\".style(tilde_style),\n                            &self.git_version.style(title_style)\n                        ),\n                        git_info_length + 3,\n                    )\n                } else {\n                    (\n                        format!(\n                            \"{}{}\",\n                            &self.git_username.style(title_style),\n                            &self.git_version.style(title_style)\n                        ),\n                        git_info_length,\n                    )\n                };\n\n            writeln!(f, \"{git_info_field_str}\")?;\n            let separator = \"-\".repeat(git_info_field_len);\n            writeln!(f, \"{}\", separator.color(self.underline_color))\n        } else {\n            Ok(())\n        }\n    }\n}\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use anyhow::Result;\n    use owo_colors::AnsiColors;\n\n    #[test]\n    fn test_title_format() -> Result<()> {\n        let mut title = Title {\n            git_username: \"onefetch-committer-name\".to_string(),\n            git_version: \"git version 2.37.2\".to_string(),\n            title_color: DynColors::Ansi(AnsiColors::Red),\n            tilde_color: DynColors::Ansi(AnsiColors::White),\n            underline_color: DynColors::Ansi(AnsiColors::Blue),\n            is_bold: true,\n        };\n\n        title.git_version = \"git version 2.37.2\".to_string();\n        assert!(title.to_string().contains(\"onefetch-committer-name\"));\n        assert!(title.to_string().contains('~'));\n        assert!(title.to_string().contains(\"git version 2.37.2\"));\n\n        title.git_version = String::new();\n        assert!(title.to_string().contains(\"onefetch-committer-name\"));\n        assert!(!title.to_string().contains('~'));\n        assert!(!title.to_string().contains(\"git version 2.37.2\"));\n\n        title.git_username = String::new();\n        let expected_title = String::new();\n        assert_eq!(format!(\"{title}\"), expected_title);\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/info/url.rs",
    "content": "use crate::info::utils::info_field::InfoField;\nuse anyhow::Result;\nuse gix::Repository;\nuse regex::Regex;\nuse serde::Serialize;\n\n#[derive(Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct UrlInfo {\n    pub repo_url: String,\n}\nimpl UrlInfo {\n    pub fn new(repo_url: &str) -> Self {\n        Self {\n            repo_url: repo_url.into(),\n        }\n    }\n}\n\npub fn get_repo_url(repo: &Repository, hide_token: bool, http_url: bool) -> Result<String> {\n    let remote = match repo.try_find_remote(\"origin\") {\n        Some(remote) => remote?,\n        None => return Ok(String::new()),\n    };\n\n    Ok(remote\n        .url(gix::remote::Direction::Push)\n        .map(|url| format_url(&url.to_string(), hide_token, http_url))\n        .unwrap_or_default())\n}\n\nfn format_url(url: &str, hide_token: bool, http_url: bool) -> String {\n    let formatted_url = if hide_token {\n        remove_token_from_url(url)\n    } else {\n        String::from(url)\n    };\n\n    if http_url && !formatted_url.starts_with(\"http\") {\n        create_http_url_from_ssh(&formatted_url)\n    } else {\n        formatted_url\n    }\n}\n\nfn remove_token_from_url(url: &str) -> String {\n    let pattern = Regex::new(r\"(https?://)([^@]+@)\").unwrap();\n    pattern.replace(url, \"$1\").to_string()\n}\n\nfn create_http_url_from_ssh(url: &str) -> String {\n    let pattern = Regex::new(r\"([^@]+)@([^:]+):(.*)\").unwrap();\n    pattern.replace(url, \"https://${2}/${3}\").to_string()\n}\n\n#[typetag::serialize]\nimpl InfoField for UrlInfo {\n    fn value(&self) -> String {\n        self.repo_url.to_string()\n    }\n\n    fn title(&self) -> String {\n        \"URL\".into()\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n    use rstest::rstest;\n\n    #[test]\n    fn test_display_url_info() {\n        let url_info = UrlInfo {\n            repo_url: \"git@github.com:o2sh/onefetch.git\".to_string(),\n        };\n\n        assert_eq!(\n            url_info.value(),\n            \"git@github.com:o2sh/onefetch.git\".to_string()\n        );\n    }\n\n    #[rstest]\n    #[case(\n        \"https://username:token@github.com/user/repo\",\n        true,\n        false,\n        \"https://github.com/user/repo\"\n    )]\n    #[case(\n        \"https://user:token@gitlab.com/user/repo\",\n        true,\n        false,\n        \"https://gitlab.com/user/repo\"\n    )]\n    #[case(\n        \"git@github.com:user/repo.git\",\n        false,\n        true,\n        \"https://github.com/user/repo.git\"\n    )]\n    #[case(\n        \"git@gitlab.com:user/repo\",\n        false,\n        true,\n        \"https://gitlab.com/user/repo\"\n    )]\n    #[case(\n        \"https://github.com/user/repo\",\n        true,\n        true,\n        \"https://github.com/user/repo\"\n    )]\n    #[case(\n        \"https://username:token@github.com/user/repo\",\n        false,\n        false,\n        \"https://username:token@github.com/user/repo\"\n    )]\n    fn test_format_url(\n        #[case] url: &str,\n        #[case] hide_token: bool,\n        #[case] http_url: bool,\n        #[case] expected: &str,\n    ) {\n        assert_eq!(format_url(url, hide_token, http_url), expected);\n    }\n\n    #[test]\n    fn test_remove_token_from_url() {\n        assert_eq!(\n            remove_token_from_url(\"https://username:token@github.com/user/repo\"),\n            \"https://github.com/user/repo\"\n        );\n    }\n\n    #[rstest]\n    #[case(\"git@github.com:user/repo.git\", \"https://github.com/user/repo.git\")]\n    #[case(\"git@gitlab.com:user/repo\", \"https://gitlab.com/user/repo\")]\n    fn test_create_http_url_from_ssh(#[case] url: &str, #[case] expected: &str) {\n        assert_eq!(create_http_url_from_ssh(url), expected);\n    }\n}\n"
  },
  {
    "path": "src/info/utils/info_field.rs",
    "content": "use crate::{info::utils::get_style, ui::text_colors::TextColors};\nuse owo_colors::OwoColorize;\nuse std::fmt;\n\n#[typetag::serialize]\npub trait InfoField {\n    fn value(&self) -> String;\n    fn title(&self) -> String;\n\n    /// Writes the styled info field. If the info doesn't have a value, nothing is\n    /// written.\n    fn write_styled(\n        &self,\n        w: &mut dyn fmt::Write,\n        no_bold: bool,\n        text_colors: &TextColors,\n    ) -> fmt::Result {\n        if let Some(styled_value) = self.style_value(text_colors) {\n            writeln!(\n                w,\n                \"{} {}\",\n                self.style_title(text_colors, no_bold),\n                styled_value\n            )\n        } else {\n            Ok(())\n        }\n    }\n\n    /// Returns a styled version of the info field's title.\n    fn style_title(&self, text_colors: &TextColors, no_bold: bool) -> String {\n        let subtitle_style = get_style(!no_bold, text_colors.subtitle);\n        let colon_style = get_style(!no_bold, text_colors.colon);\n        format!(\n            \"{}{}\",\n            self.title().style(subtitle_style),\n            \":\".style(colon_style)\n        )\n    }\n\n    /// Returns a styled version of the info field's value. This can be `None` if the\n    /// value is empty.\n    fn style_value(&self, text_colors: &TextColors) -> Option<String> {\n        let value = self.value();\n        if value.is_empty() {\n            return None;\n        }\n        let style = get_style(false, text_colors.info);\n        let styled_lines: Vec<String> = self\n            .value()\n            .lines()\n            .map(|line| format!(\"{}\", line.style(style)))\n            .collect();\n        Some(styled_lines.join(\"\\n\"))\n    }\n}\n\n#[derive(Clone, clap::ValueEnum, Debug, Eq, PartialEq)]\npub enum InfoType {\n    Project,\n    Description,\n    Head,\n    Pending,\n    Version,\n    Created,\n    Languages,\n    Dependencies,\n    Authors,\n    LastChange,\n    Contributors,\n    URL,\n    Commits,\n    Churn,\n    LinesOfCode,\n    Size,\n    License,\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n    use owo_colors::DynColors;\n    use serde::Serialize;\n\n    #[derive(Serialize)]\n    struct InfoFieldImpl(&'static str);\n\n    #[typetag::serialize]\n    impl InfoField for InfoFieldImpl {\n        fn value(&self) -> String {\n            self.0.into()\n        }\n\n        fn title(&self) -> String {\n            \"title\".into()\n        }\n    }\n\n    #[test]\n    fn test_info_field_with_value() {\n        let info = InfoFieldImpl(\"test\");\n        assert_eq!(info.title(), \"title\".to_string());\n        assert_eq!(info.value(), \"test\".to_string());\n    }\n\n    #[test]\n    fn test_write_styled() {\n        let colors = TextColors::new(&[], DynColors::Rgb(0xFF, 0xFF, 0xFF));\n        let info = InfoFieldImpl(\"test\");\n        let mut buffer = String::new();\n        info.write_styled(&mut buffer, false, &colors).unwrap();\n        insta::assert_snapshot!(buffer);\n    }\n\n    #[test]\n    fn test_write_styled_no_value() {\n        let colors = TextColors::new(&[], DynColors::Rgb(0xFF, 0xFF, 0xFF));\n        let info = InfoFieldImpl(\"\");\n        let mut buffer = String::new();\n        info.write_styled(&mut buffer, false, &colors).unwrap();\n        assert_eq!(buffer, \"\", \"It should not write anything\");\n    }\n}\n"
  },
  {
    "path": "src/info/utils/mod.rs",
    "content": "use crate::cli::NumberSeparator;\nuse gix::date::Time;\nuse num_format::ToFormattedString;\nuse owo_colors::{DynColors, Style};\nuse std::time::SystemTime;\nuse time::{OffsetDateTime, format_description::well_known::Rfc3339};\nuse time_humanize::HumanTime;\n\npub mod info_field;\n\npub fn format_time(time: Time, iso_time: bool) -> String {\n    if iso_time {\n        to_rfc3339(HumanTime::from(time.seconds))\n    } else {\n        to_human_time(time)\n    }\n}\n\nfn to_rfc3339<T>(dt: T) -> String\nwhere\n    T: Into<OffsetDateTime>,\n{\n    dt.into().format(&Rfc3339).unwrap()\n}\n\nfn to_human_time(time: Time) -> String {\n    let since_epoch_duration = SystemTime::now()\n        .duration_since(SystemTime::UNIX_EPOCH)\n        .expect(\"System time is before the Unix epoch\");\n    // Calculate the distance from the current time. This handles\n    // future dates gracefully and will simply return something like `in 5 minutes`\n    let delta_in_seconds = time\n        .seconds\n        .saturating_sub(since_epoch_duration.as_secs() as i64);\n    let ht = HumanTime::from(delta_in_seconds);\n    ht.to_string()\n}\n\npub fn format_number<T: ToFormattedString + std::fmt::Display>(\n    number: &T,\n    number_separator: NumberSeparator,\n) -> String {\n    number.to_formatted_string(&number_separator.get_format())\n}\n\npub fn get_style(is_bold: bool, color: DynColors) -> Style {\n    let mut style = Style::new().color(color);\n    if is_bold {\n        style = style.bold();\n    }\n    style\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use owo_colors::AnsiColors;\n    use rstest::rstest;\n    use std::time::{Duration, SystemTime};\n\n    #[test]\n    fn display_time_as_human_time_current_time_now() {\n        let current_time = SystemTime::now()\n            .duration_since(SystemTime::UNIX_EPOCH)\n            .unwrap();\n\n        let time = Time::new(\n            current_time.as_secs() as gix::date::SecondsSinceUnixEpoch,\n            0,\n        );\n        let result = format_time(time, false);\n        assert_eq!(result, \"now\");\n    }\n\n    #[test]\n    fn display_time_as_human_time_current_time_arbitrary() {\n        let day = Duration::from_secs(60 * 60 * 24);\n        let current_time = SystemTime::now()\n            .duration_since(SystemTime::UNIX_EPOCH)\n            .unwrap();\n        // NOTE 366 so that it's a year ago even with leap years.\n        let year_ago = current_time - (day * 366);\n        let time = Time::new(year_ago.as_secs() as gix::date::SecondsSinceUnixEpoch, 0);\n        let result = format_time(time, false);\n        assert_eq!(result, \"a year ago\");\n    }\n\n    #[test]\n    fn display_time_as_iso_time_some_time() {\n        // Set \"current\" time to 11/18/2021 11:02:22\n        let time_sample = 1_637_233_282;\n        let time = Time::new(time_sample, 0);\n        let result = format_time(time, true);\n        assert_eq!(result, \"2021-11-18T11:01:22Z\");\n    }\n\n    #[test]\n    fn display_time_as_iso_time_current_epoch() {\n        let time_sample = 0;\n        let time = Time::new(time_sample, 0);\n        let result = format_time(time, true);\n        assert_eq!(result, \"1970-01-01T00:00:00Z\");\n    }\n\n    #[test]\n    fn handle_display_human_time_and_commit_date_in_the_future() {\n        let day = Duration::from_secs(60 * 60 * 24);\n        let current_time = SystemTime::now()\n            .duration_since(SystemTime::UNIX_EPOCH)\n            .unwrap();\n        let tomorrow = current_time + day;\n        let time = Time::new(tomorrow.as_secs() as gix::date::SecondsSinceUnixEpoch, 0);\n        let result = format_time(time, false);\n        assert_eq!(result, \"in a day\");\n    }\n\n    #[test]\n    fn display_time_before_epoch() {\n        let time = Time::new(gix::date::SecondsSinceUnixEpoch::MIN, 0);\n        let result = to_human_time(time);\n        assert!(result.ends_with(\" years ago\"));\n    }\n\n    #[rstest]\n    #[case(1_000_000, NumberSeparator::Comma, \"1,000,000\")]\n    #[case(1_000_000, NumberSeparator::Space, \"1\\u{202f}000\\u{202f}000\")]\n    #[case(1_000_000, NumberSeparator::Underscore, \"1_000_000\")]\n    #[case(1_000_000, NumberSeparator::Plain, \"1000000\")]\n    fn test_format_number(\n        #[case] number: usize,\n        #[case] number_separator: NumberSeparator,\n        #[case] expected: &str,\n    ) {\n        assert_eq!(&format_number(&number, number_separator), expected);\n    }\n\n    #[test]\n    fn test_get_style() {\n        let style = get_style(true, DynColors::Ansi(AnsiColors::Cyan));\n        assert_eq!(\n            style,\n            Style::new().color(DynColors::Ansi(AnsiColors::Cyan)).bold()\n        );\n    }\n\n    #[test]\n    fn test_get_style_no_bold() {\n        let style = get_style(false, DynColors::Ansi(AnsiColors::Cyan));\n        assert_eq!(style, Style::new().color(DynColors::Ansi(AnsiColors::Cyan)));\n    }\n}\n"
  },
  {
    "path": "src/info/utils/snapshots/onefetch__info__utils__info_field__test__write_styled.snap",
    "content": "---\nsource: src/info/utils/info_field.rs\nexpression: buffer\n---\n\u001b[38;2;255;255;255;1mtitle\u001b[0m\u001b[39;1m:\u001b[0m \u001b[39mtest\u001b[0m\n\n"
  },
  {
    "path": "src/info/version.rs",
    "content": "use crate::info::utils::info_field::InfoField;\nuse anyhow::Result;\nuse gix::Repository;\nuse onefetch_manifest::Manifest;\nuse serde::Serialize;\n\n#[derive(Serialize)]\npub struct VersionInfo {\n    pub version: String,\n}\n\nimpl VersionInfo {\n    pub fn new(repo: &Repository, manifest: Option<&Manifest>) -> Result<Self> {\n        let version = get_version(repo, manifest)?;\n        Ok(Self { version })\n    }\n}\n\nfn get_version(repo: &Repository, manifest: Option<&Manifest>) -> Result<String> {\n    let mut version = String::new();\n    let mut most_recent = 0;\n\n    for tag in repo.references()?.tags()?.peeled()?.filter_map(Result::ok) {\n        if let Ok(commit) = tag.id().object()?.try_into_commit() {\n            let current_time = commit.time()?.seconds;\n            if current_time > most_recent {\n                most_recent = current_time;\n                version = tag.name().shorten().to_string();\n            }\n        }\n    }\n\n    if version.is_empty() {\n        let version_from_manifest = manifest.and_then(|m| m.version.clone()).unwrap_or_default();\n        Ok(version_from_manifest)\n    } else {\n        Ok(version)\n    }\n}\n\n#[typetag::serialize]\nimpl InfoField for VersionInfo {\n    fn value(&self) -> String {\n        self.version.to_string()\n    }\n\n    fn title(&self) -> String {\n        \"Version\".into()\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    #[test]\n    fn test_display_version_info() {\n        let version_info = VersionInfo {\n            version: \"v.1.50.0\".to_string(),\n        };\n\n        assert_eq!(version_info.value(), \"v.1.50.0\".to_string(),);\n    }\n}\n"
  },
  {
    "path": "src/lib.rs",
    "content": "// Lib is present to allow for benchmarks and integration tests\npub mod cli;\npub mod info;\npub mod ui;\n"
  },
  {
    "path": "src/main.rs",
    "content": "#![cfg_attr(feature = \"fail-on-deprecated\", deny(deprecated))]\n\nuse anyhow::Result;\nuse clap::{CommandFactory, Parser};\nuse human_panic::setup_panic;\nuse onefetch::cli::{self, CliOptions};\nuse onefetch::info::build_info;\nuse onefetch::ui::printer::factory::PrinterFactory;\nuse std::io;\n\nfn main() -> Result<()> {\n    setup_panic!();\n\n    #[cfg(windows)]\n    enable_ansi_support::enable_ansi_support()?;\n\n    let cli_options = cli::CliOptions::parse();\n\n    if cli_options.other.languages {\n        return cli::print_supported_languages();\n    }\n\n    if cli_options.other.package_managers {\n        return cli::print_supported_package_managers();\n    }\n\n    if let Some(generator) = cli_options.developer.completion {\n        let mut cmd = CliOptions::command();\n        cli::print_completions(generator, &mut cmd);\n        return Ok(());\n    }\n\n    let info = build_info(&cli_options)?;\n\n    let printer = PrinterFactory::new(info, cli_options)?.create()?;\n\n    let mut writer = io::BufWriter::new(io::stdout());\n\n    printer.print(&mut writer)?;\n\n    Ok(())\n}\n"
  },
  {
    "path": "src/ui/mod.rs",
    "content": "use crate::info::langs::language::Language;\nuse owo_colors::{AnsiColors, DynColors};\n\npub mod printer;\npub mod text_colors;\n\npub fn get_ascii_colors(\n    language_opt: Option<&Language>,\n    override_language_opt: Option<&Language>,\n    ascii_colors: &[u8],\n    true_color: bool,\n) -> Vec<DynColors> {\n    let language_colors = match override_language_opt.or(language_opt) {\n        Some(lang) => lang.get_colors(true_color),\n        None => vec![DynColors::Ansi(AnsiColors::White)],\n    };\n    if ascii_colors.is_empty() {\n        return language_colors;\n    }\n\n    let mut colors: Vec<DynColors> = ascii_colors.iter().map(num_to_color).collect();\n\n    if language_colors.len() > colors.len() {\n        colors.extend(language_colors.into_iter().skip(colors.len()));\n    }\n\n    colors\n}\n\npub fn num_to_color(num: &u8) -> DynColors {\n    match num {\n        0 => DynColors::Ansi(AnsiColors::Black),\n        1 => DynColors::Ansi(AnsiColors::Red),\n        2 => DynColors::Ansi(AnsiColors::Green),\n        3 => DynColors::Ansi(AnsiColors::Yellow),\n        4 => DynColors::Ansi(AnsiColors::Blue),\n        5 => DynColors::Ansi(AnsiColors::Magenta),\n        6 => DynColors::Ansi(AnsiColors::Cyan),\n        7 => DynColors::Ansi(AnsiColors::White),\n        8 => DynColors::Ansi(AnsiColors::BrightBlack),\n        9 => DynColors::Ansi(AnsiColors::BrightRed),\n        10 => DynColors::Ansi(AnsiColors::BrightGreen),\n        11 => DynColors::Ansi(AnsiColors::BrightYellow),\n        12 => DynColors::Ansi(AnsiColors::BrightBlue),\n        13 => DynColors::Ansi(AnsiColors::BrightMagenta),\n        14 => DynColors::Ansi(AnsiColors::BrightCyan),\n        15 => DynColors::Ansi(AnsiColors::BrightWhite),\n        _ => DynColors::Ansi(AnsiColors::Default),\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    #[test]\n    fn test_num_to_color() {\n        assert_eq!(num_to_color(&2), DynColors::Ansi(AnsiColors::Green));\n        assert_eq!(num_to_color(&u8::MAX), DynColors::Ansi(AnsiColors::Default));\n    }\n\n    #[test]\n    fn get_ascii_colors_no_language_no_custom_language_custom_colors() {\n        let colors = get_ascii_colors(None, None, &[3, 5, 8], false);\n        assert_eq!(colors.len(), 3);\n        assert_eq!(\n            colors,\n            vec![num_to_color(&3), num_to_color(&5), num_to_color(&8)]\n        );\n    }\n\n    #[test]\n    fn get_ascii_colors_no_language_no_custom_language() {\n        let colors = get_ascii_colors(None, None, &[], false);\n        assert_eq!(colors.len(), 1);\n        assert_eq!(colors, vec![DynColors::Ansi(AnsiColors::White)]);\n    }\n\n    #[test]\n    fn get_ascii_colors_no_language_with_custom_language() {\n        let colors = get_ascii_colors(None, Some(&Language::Python), &[], false);\n        assert_eq!(colors.len(), 2);\n        assert_eq!(\n            colors,\n            vec![\n                DynColors::Ansi(AnsiColors::Blue),\n                DynColors::Ansi(AnsiColors::Yellow)\n            ]\n        );\n    }\n\n    #[test]\n    fn get_ascii_colors_no_custom_language_no_custom_colors_no_true_color() {\n        let colors = get_ascii_colors(Some(&Language::Rust), None, &[], false);\n        assert_eq!(colors.len(), 2);\n        assert_eq!(\n            colors,\n            vec![\n                DynColors::Ansi(AnsiColors::Red),\n                DynColors::Ansi(AnsiColors::Default)\n            ]\n        );\n    }\n\n    #[test]\n    fn get_ascii_colors_no_custom_language_no_custom_colors_true_color() {\n        let colors = get_ascii_colors(Some(&Language::Rust), None, &[], true);\n        assert_eq!(colors.len(), 2);\n        assert_eq!(\n            colors,\n            vec![DynColors::Rgb(228, 55, 23), DynColors::Rgb(255, 255, 255)]\n        );\n    }\n\n    #[test]\n    fn get_ascii_colors_custom_language_no_custom_colors_no_true_color() {\n        let colors = get_ascii_colors(Some(&Language::Rust), Some(&Language::Sh), &[], false);\n        assert_eq!(colors.len(), 1);\n        assert_eq!(colors, vec![DynColors::Ansi(AnsiColors::Green)]);\n    }\n\n    #[test]\n    fn get_ascii_colors_no_custom_language_custom_colors_no_true_color() {\n        let colors = get_ascii_colors(Some(&Language::Rust), None, &[2, 3], false);\n        assert_eq!(colors.len(), 2);\n        assert_eq!(colors, vec![num_to_color(&2), num_to_color(&3)]);\n    }\n\n    #[test]\n    fn get_ascii_colors_fill_custom_colors_with_language_colors() {\n        // When custom ascii colors are not enough for the given language,\n        // language colors should be used as default\n        let colors = get_ascii_colors(Some(&Language::Go), None, &[0], false);\n        assert_eq!(colors.len(), 3);\n        assert_eq!(\n            colors,\n            vec![\n                num_to_color(&0),\n                DynColors::Ansi(AnsiColors::Default),\n                DynColors::Ansi(AnsiColors::Yellow)\n            ]\n        );\n    }\n}\n"
  },
  {
    "path": "src/ui/printer/factory.rs",
    "content": "use super::Printer;\nuse crate::cli::CliOptions;\nuse crate::info::Info;\nuse crate::info::langs::language::Language;\nuse crate::ui::printer::{PrinterType, SerializationFormat};\nuse anyhow::{Context, Result};\nuse image::DynamicImage;\nuse onefetch_image::ImageBackend;\n\npub struct PrinterFactory {\n    pub output: Option<SerializationFormat>,\n    pub info: Info,\n    image: Option<DynamicImage>,\n    pub no_bold: bool,\n    pub art_off: bool,\n    image_backend: Option<Box<dyn ImageBackend>>,\n    color_resolution: usize,\n    ascii_input: Option<String>,\n    ascii_language: Option<Language>,\n}\n\nimpl PrinterFactory {\n    pub fn new(info: Info, cli_options: CliOptions) -> Result<Self> {\n        let image =\n            match cli_options.image.image {\n                Some(p) => Some(image::open(&p).with_context(|| {\n                    format!(\"Could not load the image file at '{}'\", p.display())\n                })?),\n                None => None,\n            };\n\n        let image_backend = if image.is_some() {\n            cli_options\n                .image\n                .image_protocol\n                .map_or_else(onefetch_image::get_best_backend, |s| {\n                    Ok(onefetch_image::get_image_backend(s))\n                })?\n        } else {\n            None\n        };\n\n        Ok(Self {\n            output: cli_options.developer.output,\n            info,\n            image,\n            no_bold: cli_options.text_formatting.no_bold,\n            art_off: cli_options.visuals.no_art,\n            image_backend,\n            color_resolution: cli_options.image.color_resolution,\n            ascii_input: cli_options.ascii.ascii_input,\n            ascii_language: cli_options.ascii.ascii_language,\n        })\n    }\n\n    pub fn create(self) -> Result<Printer> {\n        let PrinterFactory {\n            output,\n            info,\n            image,\n            no_bold,\n            art_off,\n            image_backend,\n            color_resolution,\n            ascii_input,\n            ascii_language,\n        } = self;\n\n        match output {\n            Some(SerializationFormat::Json) => Ok(Printer {\n                r#type: PrinterType::Json,\n                info,\n            }),\n            Some(SerializationFormat::Yaml) => Ok(Printer {\n                r#type: PrinterType::Yaml,\n                info,\n            }),\n            None => {\n                if art_off {\n                    Ok(Printer {\n                        r#type: PrinterType::Plain,\n                        info,\n                    })\n                } else if let Some(image) = image {\n                    Ok(Printer {\n                        r#type: PrinterType::Image {\n                            image,\n                            backend: image_backend.context(\"No supported image backend\")?,\n                            resolution: color_resolution,\n                        },\n                        info,\n                    })\n                } else {\n                    let ascii_art = ascii_input\n                        .or_else(|| {\n                            ascii_language.map(|language| language.get_ascii_art().to_string())\n                        })\n                        .or_else(|| {\n                            info.dominant_language\n                                .as_ref()\n                                .map(|language| language.get_ascii_art().to_string())\n                        });\n\n                    if let Some(art) = ascii_art {\n                        Ok(Printer {\n                            r#type: PrinterType::Ascii { art, no_bold },\n                            info,\n                        })\n                    } else {\n                        Ok(Printer {\n                            r#type: PrinterType::Plain,\n                            info,\n                        })\n                    }\n                }\n            }\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::{\n        cli::CliOptions,\n        info::{Info, langs::language::Language},\n        ui::printer::{PrinterType, SerializationFormat, factory::PrinterFactory},\n    };\n    use image::DynamicImage;\n\n    #[test]\n    fn test_create_json_printer() {\n        let info = Info::default();\n        let mut options = CliOptions::default();\n        options.developer.output = Some(SerializationFormat::Json);\n\n        let factory = PrinterFactory::new(info, options).unwrap();\n        let printer = factory.create().unwrap();\n\n        assert_eq!(printer.r#type, PrinterType::Json);\n    }\n\n    #[test]\n    fn test_create_yaml_printer() {\n        let info = Info::default();\n        let mut options = CliOptions::default();\n        options.developer.output = Some(SerializationFormat::Yaml);\n\n        let factory = PrinterFactory::new(info, options).unwrap();\n        let printer = factory.create().unwrap();\n\n        assert_eq!(printer.r#type, PrinterType::Yaml);\n    }\n\n    #[test]\n    fn test_create_plain_printer_when_no_art() {\n        let mut info = Info::default();\n        info.dominant_language = Some(Language::Rust);\n        let mut options = CliOptions::default();\n        options.visuals.no_art = true;\n\n        let factory = PrinterFactory::new(info, options).unwrap();\n        let printer = factory.create().unwrap();\n\n        assert_eq!(printer.r#type, PrinterType::Plain);\n    }\n\n    #[test]\n    fn test_create_plain_printer_when_no_dominant_language_no_ascii_input() {\n        let info = Info::default();\n        let options = CliOptions::default();\n\n        let factory = PrinterFactory::new(info, options).unwrap();\n        let printer = factory.create().unwrap();\n\n        assert_eq!(printer.r#type, PrinterType::Plain);\n    }\n\n    #[test]\n    fn test_create_ascii_printer_when_dominant_language() {\n        let mut info = Info::default();\n        info.dominant_language = Some(Language::Rust);\n        let options = CliOptions::default();\n\n        let factory = PrinterFactory::new(info, options).unwrap();\n        let printer = factory.create().unwrap();\n\n        assert!(matches!(printer.r#type, PrinterType::Ascii { .. }));\n    }\n\n    #[test]\n    fn test_create_ascii_printer_when_ascii_language_without_dominant_language() {\n        let info = Info::default();\n        let mut options = CliOptions::default();\n        options.ascii.ascii_language = Some(Language::Rust);\n\n        let factory = PrinterFactory::new(info, options).unwrap();\n        let printer = factory.create().unwrap();\n\n        assert!(matches!(printer.r#type, PrinterType::Ascii { .. }));\n    }\n\n    pub struct DummyBackend {}\n    impl DummyBackend {\n        pub fn new() -> Self {\n            Self {}\n        }\n    }\n    impl super::ImageBackend for DummyBackend {\n        fn add_image(\n            &self,\n            _lines: Vec<String>,\n            _image: &DynamicImage,\n            _colors: usize,\n        ) -> anyhow::Result<String> {\n            Ok(\"foo\".to_string())\n        }\n    }\n\n    #[test]\n    fn test_create_image_printer() {\n        let factory = PrinterFactory {\n            output: None,\n            info: Info::default(),\n            image: Some(DynamicImage::default()),\n            no_bold: false,\n            art_off: false,\n            image_backend: Some(Box::new(DummyBackend::new())),\n            color_resolution: 8,\n            ascii_input: None,\n            ascii_language: None,\n        };\n\n        let printer = factory.create().unwrap();\n\n        assert!(matches!(printer.r#type, PrinterType::Image { .. }));\n    }\n}\n"
  },
  {
    "path": "src/ui/printer/mod.rs",
    "content": "use crate::info::Info;\nuse ::image::DynamicImage;\nuse anyhow::{Context, Result};\nuse onefetch_ascii::AsciiArt;\nuse onefetch_image::ImageBackend;\nuse std::fmt::Write as _;\n\npub mod factory;\n\nconst CENTER_PAD_LENGTH: usize = 3;\n\n#[derive(Clone, clap::ValueEnum, PartialEq, Eq, Debug)]\npub enum SerializationFormat {\n    Json,\n    Yaml,\n}\n\npub struct Printer {\n    info: Info,\n    r#type: PrinterType,\n}\n\nenum PrinterType {\n    Plain,\n    Json,\n    Yaml,\n    Ascii {\n        art: String,\n        no_bold: bool,\n    },\n    Image {\n        image: DynamicImage,\n        backend: Box<dyn ImageBackend>,\n        resolution: usize,\n    },\n}\n\nimpl Printer {\n    pub fn print(&self, writer: &mut dyn std::io::Write) -> Result<()> {\n        match &self.r#type {\n            PrinterType::Json => {\n                write!(writer, \"{}\", serde_json::to_string_pretty(&self.info)?)?;\n                Ok(())\n            }\n            PrinterType::Yaml => {\n                write!(writer, \"{}\", serde_yaml::to_string(&self.info)?)?;\n                Ok(())\n            }\n            PrinterType::Plain => {\n                write_with_line_wrapping(writer, &self.info.to_string())?;\n                Ok(())\n            }\n            PrinterType::Image {\n                image,\n                backend,\n                resolution,\n            } => {\n                let center_pad = \" \".repeat(CENTER_PAD_LENGTH);\n                let info_str = self.info.to_string();\n                let info_lines = info_str\n                    .lines()\n                    .map(|s| format!(\"{center_pad}{s}\"))\n                    .collect();\n\n                let rendered = backend\n                    .add_image(info_lines, image, *resolution)\n                    .context(\"Failed to render image\")?;\n\n                write_with_line_wrapping(writer, &rendered)?;\n                Ok(())\n            }\n            PrinterType::Ascii { art, no_bold } => {\n                let mut buf = String::new();\n                let center_pad = \" \".repeat(CENTER_PAD_LENGTH);\n                let info_str = self.info.to_string();\n                let mut info_lines = info_str.lines();\n                let mut logo_lines = AsciiArt::new(art, &self.info.ascii_colors, !no_bold);\n\n                loop {\n                    match (logo_lines.next(), info_lines.next()) {\n                        (Some(logo), Some(info)) => writeln!(buf, \"{logo}{center_pad}{info:^}\")?,\n                        (Some(logo), None) => writeln!(buf, \"{logo}\")?,\n                        (None, Some(info)) => writeln!(\n                            buf,\n                            \"{:<width$}{center_pad}{info:^}\",\n                            \"\",\n                            width = logo_lines.width()\n                        )?,\n                        (None, None) => break,\n                    }\n                }\n\n                write_with_line_wrapping(writer, &buf)?;\n                Ok(())\n            }\n        }\n    }\n}\n\nfn write_with_line_wrapping(writer: &mut dyn std::io::Write, content: &str) -> Result<()> {\n    // \\x1B[?7l turns off line wrapping and \\x1B[?7h turns it on\n    write!(writer, \"\\x1B[?7l{content}\\x1B[?7h\")?;\n    Ok(())\n}\n\nimpl PartialEq for PrinterType {\n    fn eq(&self, other: &Self) -> bool {\n        matches!(\n            (self, other),\n            (PrinterType::Plain, PrinterType::Plain)\n                | (PrinterType::Json, PrinterType::Json)\n                | (PrinterType::Yaml, PrinterType::Yaml)\n                | (PrinterType::Ascii { .. }, PrinterType::Ascii { .. })\n                | (PrinterType::Image { .. }, PrinterType::Image { .. })\n        )\n    }\n}\n\nimpl std::fmt::Debug for PrinterType {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let name = match self {\n            PrinterType::Plain => \"Plain\",\n            PrinterType::Json => \"Json\",\n            PrinterType::Yaml => \"Yaml\",\n            PrinterType::Ascii { .. } => \"Ascii\",\n            PrinterType::Image { .. } => \"Image\",\n        };\n        write!(f, \"PrinterType::{name}\")\n    }\n}\n"
  },
  {
    "path": "src/ui/text_colors.rs",
    "content": "use crate::ui::num_to_color;\nuse owo_colors::{AnsiColors, DynColors};\n\n#[derive(Clone)]\npub struct TextColors {\n    pub title: DynColors,\n    pub tilde: DynColors,\n    pub underline: DynColors,\n    pub subtitle: DynColors,\n    pub colon: DynColors,\n    pub info: DynColors,\n}\n\nimpl TextColors {\n    pub fn new(colors: &[u8], primary_color: DynColors) -> Self {\n        let mut text_colors = Self {\n            title: primary_color,\n            tilde: DynColors::Ansi(AnsiColors::Default),\n            underline: DynColors::Ansi(AnsiColors::Default),\n            subtitle: primary_color,\n            colon: DynColors::Ansi(AnsiColors::Default),\n            info: DynColors::Ansi(AnsiColors::Default),\n        };\n\n        if !colors.is_empty() {\n            let custom_color = colors.iter().map(num_to_color).collect::<Vec<DynColors>>();\n\n            text_colors.title = *custom_color.first().unwrap_or(&primary_color);\n            text_colors.tilde = *custom_color\n                .get(1)\n                .unwrap_or(&DynColors::Ansi(AnsiColors::Default));\n            text_colors.underline = *custom_color\n                .get(2)\n                .unwrap_or(&DynColors::Ansi(AnsiColors::Default));\n            text_colors.subtitle = *custom_color.get(3).unwrap_or(&primary_color);\n            text_colors.colon = *custom_color\n                .get(4)\n                .unwrap_or(&DynColors::Ansi(AnsiColors::Default));\n            text_colors.info = *custom_color\n                .get(5)\n                .unwrap_or(&DynColors::Ansi(AnsiColors::Default));\n        }\n        text_colors\n    }\n}\n\nimpl Default for TextColors {\n    fn default() -> Self {\n        Self {\n            title: DynColors::Ansi(AnsiColors::Default),\n            tilde: DynColors::Ansi(AnsiColors::Default),\n            underline: DynColors::Ansi(AnsiColors::Default),\n            subtitle: DynColors::Ansi(AnsiColors::Default),\n            colon: DynColors::Ansi(AnsiColors::Default),\n            info: DynColors::Ansi(AnsiColors::Default),\n        }\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    #[test]\n    fn no_custom_colors() {\n        let primary_color = DynColors::Ansi(AnsiColors::Blue);\n        let text_colors = TextColors::new(&[], primary_color);\n        assert_eq!(text_colors.title, primary_color);\n        assert_eq!(text_colors.tilde, DynColors::Ansi(AnsiColors::Default));\n        assert_eq!(text_colors.underline, DynColors::Ansi(AnsiColors::Default));\n        assert_eq!(text_colors.subtitle, primary_color);\n        assert_eq!(text_colors.colon, DynColors::Ansi(AnsiColors::Default));\n        assert_eq!(text_colors.info, DynColors::Ansi(AnsiColors::Default));\n    }\n\n    #[test]\n    fn with_custom_colors() {\n        let custom_colors = vec![0, 1, 2, 3, 4, 5];\n        let text_colors = TextColors::new(&custom_colors, DynColors::Ansi(AnsiColors::Blue));\n        assert_eq!(text_colors.title, num_to_color(&custom_colors[0]));\n        assert_eq!(text_colors.tilde, num_to_color(&custom_colors[1]));\n        assert_eq!(text_colors.underline, num_to_color(&custom_colors[2]));\n        assert_eq!(text_colors.subtitle, num_to_color(&custom_colors[3]));\n        assert_eq!(text_colors.colon, num_to_color(&custom_colors[4]));\n        assert_eq!(text_colors.info, num_to_color(&custom_colors[5]));\n    }\n\n    #[test]\n    fn with_some_custom_colors() {\n        let custom_colors = vec![0, 1, 2];\n        let primary_color = DynColors::Ansi(AnsiColors::Blue);\n        let text_colors = TextColors::new(&custom_colors, primary_color);\n        assert_eq!(text_colors.title, num_to_color(&custom_colors[0]));\n        assert_eq!(text_colors.tilde, num_to_color(&custom_colors[1]));\n        assert_eq!(text_colors.underline, num_to_color(&custom_colors[2]));\n        assert_eq!(text_colors.subtitle, primary_color);\n        assert_eq!(text_colors.colon, DynColors::Ansi(AnsiColors::Default));\n        assert_eq!(text_colors.info, DynColors::Ansi(AnsiColors::Default));\n    }\n}\n"
  },
  {
    "path": "tests/fixtures/make_bare_repo.sh",
    "content": "set -eu -o pipefail\n\ngit init -q --bare\n"
  },
  {
    "path": "tests/fixtures/make_partial_repo.sh",
    "content": "set -eu -o pipefail\n\nmkdir base\n(cd base\n    git init -q\n    git checkout -b main\n    touch code.rs\n    git add code.rs\n    git commit -q -m c1\n    echo hello >> code.rs\n    git add code.rs\n    git commit -q -m c2\n    echo world >> code.rs\n    git add code.rs\n    git commit -q -m c3\n    echo something >> code.rs\n    git add code.rs\n    git commit -q -m c4\n    echo more >> code.rs\n    git mv code.rs renamed.rs\n    echo change >> renamed.rs\n    git commit -q -am c5\n\n    git config uploadpack.allowfilter true\n)\n\ngit clone --filter=blob:none file://$PWD/base partial\n(cd partial\n    git config diff.renames true\n)\n"
  },
  {
    "path": "tests/fixtures/make_pre_epoch_repo.sh",
    "content": "set -eu -o pipefail\n\ngit init -q\ngit checkout -b main\n\necho \"hello\\nworld\" >> code.rs\ngit add code.rs\nGIT_AUTHOR_DATE=\"@0 +0000\" GIT_COMMITTER_DATE=\"@0 +0000\" git commit -q -m c1\ngit cat-file -p @ > to-be-patched.txt\n\npatch -p1 <<EOF\ndiff --git a/to-be-patched.txt b/to-be-patched.txt\nindex 95ad1b1..3ea89af 100644\n--- a/to-be-patched.txt\n+++ b/to-be-patched.txt\n@@ -1,5 +1,5 @@\n tree 00d3a67028ba1004a04bd720eee966811102f0c3\n-author author <author@example.com> 0 +0000\n-committer committer <committer@example.com> 0 +0000\n+author author <author@example.com> -5263747740 +0009\n+committer committer <committer@example.com> -5263747740 +0009\n\n c1\nEOF\n\nnew_commit=$(git hash-object -w -t commit to-be-patched.txt || git hash-object --literally -w -t commit to-be-patched.txt)\ngit update-ref refs/heads/main $new_commit\n\n"
  },
  {
    "path": "tests/fixtures/make_repo.sh",
    "content": "set -eu -o pipefail\n\ngit init -q\n\n# BOTH NAME AND EMAIL ARE NEEDED FOR RECOGNITION\ngit config --local --add \"committer.name\" \"onefetch-committer-name\"\ngit config --local --add \"committer.email\" \"onefetch-committer-email@onefetch.com\"\n\ngit remote add origin https://github.com/user/repo.git\n\ngit checkout -b main\ntouch code.rs\ngit add code.rs\ngit commit -q -m c1 --author=\"Author One <author1@example.org>\"\ngit tag tag1\necho hello >> code.rs\ngit add code.rs\ngit commit -q -m c2 --author=\"Author Two <author2@example.org>\"\necho world >> code.rs\ngit add code.rs\ngit commit -q -m c3 --author=\"Author Three <author3@example.org>\"\necho something >> code.rs\ngit add code.rs\ngit commit -q -m c4 --author=\"Author Four <author4@example.org>\"\necho more >> code.rs\n\ncat > Cargo.toml << EOF\n[package]\nname = \"repo\"\nversion = \"0.1.0\"\ndescription = \"Amazing tool\"\n\n[dependencies]\nanyhow = \"1.0.65\"\nEOF\n\ncat > LICENSE << '__LICENSE__'\nMIT License\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n__LICENSE__\n\n\n"
  },
  {
    "path": "tests/fixtures/make_repo_without_code.sh",
    "content": "set -eu -o pipefail\n\ngit init -q\n\n# BOTH NAME AND EMAIL ARE NEEDED FOR RECOGNITION\ngit config --local --add \"committer.name\" \"onefetch-committer-name\"\ngit config --local --add \"committer.email\" \"onefetch-committer-email@onefetch.com\"\n\ngit remote add origin https://github.com/user/repo.git\n\ngit checkout -b main\n\n# Markdown is exclude by default because it's a prose language (cf. --type option)\ntouch README.md\ngit add README.md\ngit commit -q -m c1\n"
  },
  {
    "path": "tests/fixtures/make_repo_without_remote.sh",
    "content": "set -eu -o pipefail\n\ngit init -q\n\ngit checkout -b main\ntouch this.rs\ngit add this.rs\ngit commit -q -m c1\necho hello >> this.rs\ngit commit -q -am c2\n"
  },
  {
    "path": "tests/repo.rs",
    "content": "use anyhow::Result;\nuse gix::{Repository, ThreadSafeRepository, open};\nuse onefetch::cli::{CliOptions, InfoCliOptions, TextForamttingCliOptions};\nuse onefetch::info::{build_info, get_work_dir};\n\nfn repo(name: &str) -> Result<Repository> {\n    let repo_path = gix_testtools::scripted_fixture_read_only(name).unwrap();\n    let safe_repo = ThreadSafeRepository::open_opts(repo_path, open::Options::isolated())?;\n    Ok(safe_repo.to_thread_local())\n}\n\npub fn named_repo(fixture: &str, name: &str) -> Result<Repository> {\n    let repo_path = gix_testtools::scripted_fixture_read_only(fixture)\n        .unwrap()\n        .join(name);\n    let safe_repo = ThreadSafeRepository::open_opts(repo_path, open::Options::isolated())?;\n    Ok(safe_repo.to_thread_local())\n}\n\n#[test]\nfn test_bare_repo() -> Result<()> {\n    let repo = repo(\"make_bare_repo.sh\")?;\n    let work_dir = get_work_dir(&repo);\n    assert!(\n        work_dir.is_err(),\n        \"oops, info was returned on a bare git repo\"\n    );\n    assert_eq!(\n        work_dir.unwrap_err().to_string(),\n        \"please run onefetch inside of a non-bare git repository\"\n    );\n    Ok(())\n}\n\n#[test]\nfn test_repo() -> Result<()> {\n    let repo = repo(\"make_repo.sh\")?;\n    let config: CliOptions = CliOptions {\n        input: repo.path().to_path_buf(),\n        info: InfoCliOptions {\n            email: true,\n            churn_pool_size: Some(10),\n            ..Default::default()\n        },\n        text_formatting: TextForamttingCliOptions {\n            iso_time: true,\n            ..Default::default()\n        },\n        ..Default::default()\n    };\n    let info = build_info(&config)?;\n    insta::assert_json_snapshot!(\n        info,\n        {\n            \".title.gitVersion\" => \"git version\",\n            \".infoFields[].HeadInfo.headRefs.shortCommitId\" => \"short commit\",\n        }\n    );\n\n    Ok(())\n}\n\n#[test]\nfn test_repo_without_remote() -> Result<()> {\n    let repo = repo(\"make_repo_without_remote.sh\")?;\n    let config: CliOptions = CliOptions {\n        input: repo.path().to_path_buf(),\n        ..Default::default()\n    };\n    let info = build_info(&config);\n    assert!(info.is_ok());\n\n    Ok(())\n}\n\n#[test]\nfn test_partial_repo() -> Result<()> {\n    let repo = named_repo(\"make_partial_repo.sh\", \"partial\")?;\n    let config: CliOptions = CliOptions {\n        input: repo.path().to_path_buf(),\n        ..Default::default()\n    };\n    let _info = build_info(&config).expect(\"no error\");\n    Ok(())\n}\n\n#[test]\nfn test_repo_with_pre_epoch_dates() -> Result<()> {\n    let repo = repo(\"make_pre_epoch_repo.sh\")?;\n    let config: CliOptions = CliOptions {\n        input: repo.path().to_path_buf(),\n        ..Default::default()\n    };\n    let _info = build_info(&config).expect(\"no error\");\n    Ok(())\n}\n\n#[test]\nfn test_repo_without_code() -> Result<()> {\n    let repo = repo(\"make_repo_without_code.sh\")?;\n    let config: CliOptions = CliOptions {\n        input: repo.path().to_path_buf(),\n        ..Default::default()\n    };\n    let _info = build_info(&config).expect(\"no error\");\n    Ok(())\n}\n"
  },
  {
    "path": "tests/snapshots/repo__repo.snap",
    "content": "---\nsource: tests/repo.rs\nexpression: info\n---\n{\n  \"title\": {\n    \"gitUsername\": \"onefetch-committer-name\",\n    \"gitVersion\": \"git version\"\n  },\n  \"infoFields\": [\n    {\n      \"ProjectInfo\": {\n        \"repoName\": \"repo\",\n        \"numberOfBranches\": 0,\n        \"numberOfTags\": 1\n      }\n    },\n    {\n      \"DescriptionInfo\": {\n        \"description\": \"Amazing tool\"\n      }\n    },\n    {\n      \"HeadInfo\": {\n        \"headRefs\": {\n          \"shortCommitId\": \"short commit\",\n          \"refs\": [\n            \"main\"\n          ]\n        }\n      }\n    },\n    {\n      \"PendingInfo\": {\n        \"added\": 2,\n        \"deleted\": 0,\n        \"modified\": 1\n      }\n    },\n    {\n      \"VersionInfo\": {\n        \"version\": \"tag1\"\n      }\n    },\n    {\n      \"CreatedInfo\": {\n        \"creationDate\": \"2000-01-02T00:00:00Z\"\n      }\n    },\n    {\n      \"LanguagesInfo\": {\n        \"languagesWithPercentage\": [\n          {\n            \"language\": \"Rust\",\n            \"percentage\": 100.0\n          }\n        ]\n      }\n    },\n    {\n      \"DependenciesInfo\": {\n        \"dependencies\": \"1 (Cargo)\"\n      }\n    },\n    {\n      \"AuthorsInfo\": {\n        \"authors\": [\n          {\n            \"name\": \"Author Four\",\n            \"email\": \"author4@example.org\",\n            \"nbrOfCommits\": 1,\n            \"contribution\": 25\n          },\n          {\n            \"name\": \"Author One\",\n            \"email\": \"author1@example.org\",\n            \"nbrOfCommits\": 1,\n            \"contribution\": 25\n          },\n          {\n            \"name\": \"Author Three\",\n            \"email\": \"author3@example.org\",\n            \"nbrOfCommits\": 1,\n            \"contribution\": 25\n          }\n        ]\n      }\n    },\n    {\n      \"LastChangeInfo\": {\n        \"lastChange\": \"2000-01-02T00:00:00Z\"\n      }\n    },\n    {\n      \"ContributorsInfo\": {\n        \"totalNumberOfAuthors\": 4\n      }\n    },\n    {\n      \"UrlInfo\": {\n        \"repoUrl\": \"https://github.com/user/repo.git\"\n      }\n    },\n    {\n      \"CommitsInfo\": {\n        \"numberOfCommits\": 4,\n        \"isShallow\": false\n      }\n    },\n    {\n      \"ChurnInfo\": {\n        \"file_churns\": [\n          {\n            \"filePath\": \"code.rs\",\n            \"nbrOfCommits\": 4\n          }\n        ],\n        \"churn_pool_size\": 4\n      }\n    },\n    {\n      \"LocInfo\": {\n        \"linesOfCode\": 4\n      }\n    },\n    {\n      \"SizeInfo\": {\n        \"repoSize\": \"22 B\",\n        \"fileCount\": 1\n      }\n    },\n    {\n      \"LicenseInfo\": {\n        \"license\": \"MIT\"\n      }\n    }\n  ]\n}\n"
  }
]