feat: start rewriting Nala in rust

This commit is contained in:
Blake Lee
2026-05-20 12:59:38 -05:00
committed by Blake
parent 7784856980
commit 817092feb3
19 changed files with 1146 additions and 1731 deletions
+11 -46
View File
@@ -1,49 +1,14 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# Sphinx documentation
docs/build/
# These are backup files generated by rustfmt
**/*.rs.bk
# pyenv
.python-version
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Volian
test*.py
.vscode/
# Nuitka Build
nala-cli.build/
nala-cli.bin
# Pyinstaller
/nala-cli.spec
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
+10 -15
View File
@@ -2,27 +2,22 @@ stages:
- test
variables:
DEBIAN_FRONTEND: noninteractive
PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/root/.cargo/bin"
test:
stage: test
image: debian:bookworm
image: debian:sid
environment:
script:
- apt-get update
- apt-get install -y eatmydata
- eatmydata apt install -y
git
build-essential
curl
devscripts
apt-utils
python3-cachecontrol
python3-pip
- eatmydata mk-build-deps
--tool 'apt-get --yes -o Debug::pkgProblemResolver=yes --no-install-recommends'
--install debian/control
- curl -sSL https://install.python-poetry.org | python3 -
- export PATH="/root/.local/bin:$PATH"
- poetry install
- poetry run pre-commit run --all-files
sudo
libapt-pkg-dev
codespell
- curl https://sh.rustup.rs -sSf | sh -- /dev/stdin -y
- cargo install just
- just setup-dev
- just check
-74
View File
@@ -1,74 +0,0 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.1.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
exclude: "^po/"
- id: mixed-line-ending
args: ["--fix=lf"]
- id: check-merge-conflict
- id: check-yaml
- id: check-ast
- id: check-added-large-files
- repo: local
hooks:
- id: pylint
name: check code for errors
language: system
entry: poetry run pylint
types: [python]
- id: codespell
name: check for misspellings
description: Check for common misspellings in text files
args: [
--check-hidden,
--check-filenames,
--skip=*.po,
--skip=poetry.lock,
--builtin,
clear,
rare,
informal,
usage,
code
]
language: system
entry: poetry run codespell --exclude-file ./.codespell-ignore-lines
types: [text]
- id: black
name: format python code
description: Automatically format code to follow the black code style
language: system
entry: poetry run black
types_or: [python, pyi]
require_serial: true
- id: isort
name: sort python imports
description: Automatically sort import statements
language: system
entry: poetry run isort
types_or: [cython, pyi, python]
require_serial: true
- id: pyupgrade
name: check for old python syntax
description: Automatically upgrade syntax for newer versions
args: [--py36-plus, --keep-runtime-typing]
language: system
entry: poetry run pyupgrade
types: [python]
- id: mypy
name: type check python code
description: Static type checker for Python
language: system
entry: poetry run mypy
types_or: [python, pyi]
require_serial: true
- id: pydocstyle
name: check docstrings
description: Check docstrings
language: system
entry: poetry run pydocstyle
types: [python]
-572
View File
@@ -1,572 +0,0 @@
[MASTER]
# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code.
extension-pkg-allow-list=apt_pkg,
# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code. (This is an alternative name to extension-pkg-allow-list
# for backward compatibility.)
extension-pkg-whitelist=
# Return non-zero exit code if any of these messages/categories are detected,
# even if score is above --fail-under value. Syntax same as enable. Messages
# specified are enabled, while categories only check already-enabled messages.
fail-on=
# Specify a score threshold to be exceeded before program exits with error.
fail-under=10.0
# Files or directories to be skipped. They should be base names, not paths.
ignore=CVS
# Add files or directories matching the regex patterns to the ignore-list. The
# regex matches against paths and can be in Posix or Windows format.
ignore-paths=
# Files or directories matching the regex patterns are skipped. The regex
# matches against base names, not paths.
ignore-patterns=
# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
init-hook="import sys; sys.path.append('/usr/lib/python3/dist-packages')"
# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
# number of processors available to use.
jobs=1
# Control the amount of potential inferred values when inferring a single
# object. This can help the performance when dealing with large functions or
# complex, nested conditions.
limit-inference-results=100
# List of plugins (as comma separated values of python module names) to load,
# usually to register additional checkers.
load-plugins=
# Pickle collected data for later comparisons.
persistent=yes
# Minimum Python version to use for version dependent checks. Will default to
# the version used to run pylint.
py-version=3.9
# When enabled, pylint would attempt to guess common misconfiguration and emit
# user-friendly hints instead of false-positive error messages.
suggestion-mode=yes
# Allow loading of arbitrary C extensions. Extensions are imported into the
# active Python interpreter and may run arbitrary code.
unsafe-load-any-extension=no
[MESSAGES CONTROL]
# Only show warnings with the listed confidence levels. Leave empty to show
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED.
confidence=
# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this
# option multiple times (only on the command line, not in the configuration
# file where it should appear only once). You can also use "--disable=all" to
# disable everything first and then re-enable specific checks. For example, if
# you want to run only the similarities checker, you can use "--disable=all
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use "--disable=all --enable=classes
# --disable=W".
disable=raw-checker-failed,
bad-inline-option,
locally-disabled,
file-ignored,
suppressed-message,
useless-suppression,
deprecated-pragma,
use-symbolic-message-instead,
logging-fstring-interpolation,
protected-access,
duplicate-code,
too-many-positional-arguments
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time (only on the command line, not in the configuration file where
# it should appear only once). See also the "--disable" option for examples.
enable=c-extension-no-member
[REPORTS]
# Python expression which should return a score less than or equal to 10. You
# have access to the variables 'error', 'warning', 'refactor', and 'convention'
# which contain the number of messages in each category, as well as 'statement'
# which is the total number of statements analyzed. This score is used by the
# global evaluation report (RP0004).
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
# Template used to display messages. This is a python new-style format string
# used to format the message information. See doc for all details.
#msg-template=
# Set the output format. Available formats are text, parseable, colorized, json
# and msvs (visual studio). You can also give a reporter class, e.g.
# mypackage.mymodule.MyReporterClass.
output-format=text
# Tells whether to display a full report or only the messages.
reports=no
# Activate the evaluation score.
score=yes
[REFACTORING]
# Maximum number of nested blocks for function / method body
max-nested-blocks=5
# Complete name of functions that never returns. When checking for
# inconsistent-return-statements if a never returning function is called then
# it will be considered as an explicit return statement and no message will be
# printed.
never-returning-functions=sys.exit,argparse.parse_error
[TYPECHECK]
# List of decorators that produce context managers, such as
# contextlib.contextmanager. Add to this list to register other decorators that
# produce valid context managers.
contextmanager-decorators=contextlib.contextmanager
# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E1101 when accessed. Python regular
# expressions are accepted.
generated-members=
# Tells whether missing members accessed in mixin class should be ignored. A
# class is considered mixin if its name matches the mixin-class-rgx option.
ignore-mixin-members=yes
# Tells whether to warn about missing members when the owner of the attribute
# is inferred to be None.
ignore-none=yes
# This flag controls whether pylint should warn about no-member and similar
# checks whenever an opaque object is returned when inferring. The inference
# can return multiple potential results while evaluating a Python object, but
# some branches might not be evaluated, which results in partial inference. In
# that case, it might be useful to still emit no-member and other checks for
# the rest of the inferred objects.
ignore-on-opaque-inference=yes
# List of class names for which member attributes should not be checked (useful
# for classes with dynamically set attributes). This supports the use of
# qualified names.
ignored-classes=optparse.Values,thread._local,_thread._local
# List of module names for which member attributes should not be checked
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis). It
# supports qualified module names, as well as Unix pattern matching.
ignored-modules=
# Show a hint with possible names when a member name was not found. The aspect
# of finding the hint is based on edit distance.
missing-member-hint=yes
# The minimum edit distance a name should have in order to be considered a
# similar match for a missing member name.
missing-member-hint-distance=1
# The total number of similar names that should be taken in consideration when
# showing a hint for a missing member.
missing-member-max-choices=1
# Regex pattern to define which classes are considered mixins ignore-mixin-
# members is set to 'yes'
mixin-class-rgx=.*[Mm]ixin
# List of decorators that change the signature of a decorated function.
signature-mutators=
[FORMAT]
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
expected-line-ending-format=
# Regexp for a line that is allowed to be longer than the limit.
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
# Number of spaces of indent required inside a hanging or continued line.
indent-after-paren=4
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
# tab).
indent-string="\t"
# Maximum number of characters on a single line.
max-line-length=100
# Maximum number of lines in a module.
max-module-lines=1000
# Allow the body of a class to be on the same line as the declaration if body
# contains single statement.
single-line-class-stmt=no
# Allow the body of an if to be on the same line as the test if there is no
# else.
single-line-if-stmt=no
[LOGGING]
# The type of string formatting that logging methods do. `old` means using %
# formatting, `new` is for `{}` formatting.
logging-format-style=new
# Logging modules to check that the string format arguments are in logging
# function parameter format.
logging-modules=logging
[SPELLING]
# Limits count of emitted suggestions for spelling mistakes.
max-spelling-suggestions=4
# Spelling dictionary name. Available dictionaries: none. To make it work,
# install the 'python-enchant' package.
spelling-dict=
# List of comma separated words that should be considered directives if they
# appear and the beginning of a comment and should not be checked.
spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy:
# List of comma separated words that should not be checked.
spelling-ignore-words=
# A path to a file that contains the private dictionary; one word per line.
spelling-private-dict-file=
# Tells whether to store unknown words to the private dictionary (see the
# --spelling-private-dict-file option) instead of raising a message.
spelling-store-unknown-words=no
[STRING]
# This flag controls whether inconsistent-quotes generates a warning when the
# character used as a quote delimiter is used inconsistently within a module.
check-quote-consistency=no
# This flag controls whether the implicit-str-concat should generate a warning
# on implicit string concatenation in sequences defined over several lines.
check-str-concat-over-line-jumps=no
[BASIC]
# Naming style matching correct argument names.
argument-naming-style=snake_case
# Regular expression matching correct argument names. Overrides argument-
# naming-style.
#argument-rgx=
# Naming style matching correct attribute names.
attr-naming-style=snake_case
# Regular expression matching correct attribute names. Overrides attr-naming-
# style.
#attr-rgx=
# Bad variable names which should always be refused, separated by a comma.
bad-names= foo,
bar,
baz,
toto,
tutu,
tata
# Bad variable names regexes, separated by a comma. If names match any regex,
# they will always be refused
bad-names-rgxs=
# Naming style matching correct class attribute names.
class-attribute-naming-style=any
# Regular expression matching correct class attribute names. Overrides class-
# attribute-naming-style.
#class-attribute-rgx=
# Naming style matching correct class constant names.
class-const-naming-style=UPPER_CASE
# Regular expression matching correct class constant names. Overrides class-
# const-naming-style.
#class-const-rgx=
# Naming style matching correct class names.
class-naming-style=PascalCase
# Regular expression matching correct class names. Overrides class-naming-
# style.
#class-rgx=
# Naming style matching correct constant names.
const-naming-style=UPPER_CASE
# Regular expression matching correct constant names. Overrides const-naming-
# style.
#const-rgx=
# Minimum line length for functions/classes that require docstrings, shorter
# ones are exempt.
docstring-min-length=-1
# Naming style matching correct function names.
function-naming-style=snake_case
# Regular expression matching correct function names. Overrides function-
# naming-style.
#function-rgx=
# Good variable names which should always be accepted, separated by a comma.
good-names= i,
j,
k,
ex,
Run,
_
# Good variable names regexes, separated by a comma. If names match any regex,
# they will always be accepted
good-names-rgxs=
# Include a hint for the correct naming format with invalid-name.
include-naming-hint=no
# Naming style matching correct inline iteration names.
inlinevar-naming-style=any
# Regular expression matching correct inline iteration names. Overrides
# inlinevar-naming-style.
#inlinevar-rgx=
# Naming style matching correct method names.
method-naming-style=snake_case
# Regular expression matching correct method names. Overrides method-naming-
# style.
#method-rgx=
# Naming style matching correct module names.
module-naming-style=snake_case
# Regular expression matching correct module names. Overrides module-naming-
# style.
#module-rgx=
# Colon-delimited sets of names that determine each other's naming style when
# the name regexes allow several styles.
name-group=
# Regular expression which should only match function or class names that do
# not require a docstring.
no-docstring-rgx=^_
# List of decorators that produce properties, such as abc.abstractproperty. Add
# to this list to register other decorators that produce valid properties.
# These decorators are taken in consideration only for invalid-name.
property-classes=abc.abstractproperty
# Naming style matching correct variable names.
variable-naming-style=snake_case
# Regular expression matching correct variable names. Overrides variable-
# naming-style.
#variable-rgx=
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes= FIXME,
XXX,
TODO
# Regular expression of note tags to take in consideration.
#notes-rgx=
[VARIABLES]
# List of additional names supposed to be defined in builtins. Remember that
# you should avoid defining new builtins when possible.
additional-builtins=
# Tells whether unused global variables should be treated as a violation.
allow-global-unused-variables=yes
# List of names allowed to shadow builtins
allowed-redefined-builtins=
# List of strings which can identify a callback function by name. A callback
# name must start or end with one of those strings.
callbacks= cb_,
_cb
# A regular expression matching the name of dummy variables (i.e. expected to
# not be used).
dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_
# Argument names that match this expression will be ignored. Default to name
# with leading underscore.
ignored-argument-names=_.*|^ignored_|^unused_
# Tells whether we should check for unused import in __init__ files.
init-import=no
# List of qualified module names which can have objects that can redefine
# builtins.
redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io
[SIMILARITIES]
# Comments are removed from the similarity computation
ignore-comments=yes
# Docstrings are removed from the similarity computation
ignore-docstrings=yes
# Imports are removed from the similarity computation
ignore-imports=no
# Signatures are removed from the similarity computation
ignore-signatures=no
# Minimum lines number of a similarity.
min-similarity-lines=4
[DESIGN]
# List of regular expressions of class ancestor names to ignore when counting
# public methods (see R0903)
exclude-too-few-public-methods=
# List of qualified class names to ignore when counting class parents (see
# R0901)
ignored-parents=
# Maximum number of arguments for function / method.
max-args=5
# Maximum number of attributes for a class (see R0902).
max-attributes=7
# Maximum number of boolean expressions in an if statement (see R0916).
max-bool-expr=5
# Maximum number of branch for function / method body.
max-branches=12
# Maximum number of locals for function / method body.
max-locals=15
# Maximum number of parents for a class (see R0901).
max-parents=7
# Maximum number of public methods for a class (see R0904).
max-public-methods=20
# Maximum number of return / yield for function / method body.
max-returns=6
# Maximum number of statements in function / method body.
max-statements=50
# Minimum number of public methods for a class (see R0903).
min-public-methods=2
[CLASSES]
# Warn about protected attribute access inside special methods
check-protected-access-in-special-methods=no
# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods= __init__,
__new__,
setUp,
__post_init__
# List of member names, which should be excluded from the protected access
# warning.
exclude-protected= _asdict,
_fields,
_replace,
_source,
_make
# List of valid names for the first argument in a class method.
valid-classmethod-first-arg=cls
# List of valid names for the first argument in a metaclass class method.
valid-metaclass-classmethod-first-arg=cls
[IMPORTS]
# List of modules that can be imported at any level, not just the top level
# one.
allow-any-import-level=
# Allow wildcard imports from modules that define __all__.
allow-wildcard-with-all=no
# Analyse import fallback blocks. This can be used to support both Python 2 and
# 3 compatible code, which means that the block might have code that exists
# only in one or another interpreter, leading to false positives when analysed.
analyse-fallback-blocks=no
# Deprecated modules which should not be used, separated by a comma.
deprecated-modules=
# Output a graph (.gv or any supported image format) of external dependencies
# to the given file (report RP0402 must not be disabled).
ext-import-graph=
# Output a graph (.gv or any supported image format) of all (i.e. internal and
# external) dependencies to the given file (report RP0402 must not be
# disabled).
import-graph=
# Output a graph (.gv or any supported image format) of internal dependencies
# to the given file (report RP0402 must not be disabled).
int-import-graph=
# Force import order to recognize a module as part of the standard
# compatibility libraries.
known-standard-library=
# Force import order to recognize a module as part of a third party library.
known-third-party=enchant
# Couples of modules and preferred modules, separated by a comma.
preferred-modules=
[EXCEPTIONS]
# Exceptions that will emit a warning when being caught. Defaults to
# "BaseException, Exception".
overgeneral-exceptions= builtins.Exception
+21
View File
@@ -0,0 +1,21 @@
[package]
name = "nala-rs"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rust-apt = { git = "https://gitlab.com/volian/rust-apt" }
clap = { version = "3.2.13", features = ["derive"] }
lazy_static = "1.4.0"
toml = "0.5"
anyhow = "1.0.59"
[build-dependencies]
clap = { version = "3.2.13", features = ["derive"] }
clap_complete = "*"
[profile.release-lto]
inherits = "release"
lto = true
-46
View File
@@ -1,46 +0,0 @@
enter:
echo "The Nala MakeFile"
install:
sudo apt install python3-debian
sudo python3 -m pip install .
# Install man pages
sudo ./nala_build.py man --install
# Install translations
sudo python3 -m pip install babel
sudo ./nala_build.py babel --compile --install
make completions
make config
completions:
# Create Shell Completion Directories
mkdir -p /usr/share/fish/vendor_completions.d/
mkdir -p /usr/share/bash-completion/completions/
mkdir -p /usr/share/zsh/vendor-completions/
# Install shell completions
sudo cp debian/nala.fish /usr/share/fish/vendor_completions.d/
sudo cp debian/bash-completion /usr/share/bash-completion/completions/nala
sudo cp debian/_nala /usr/share/zsh/vendor-completions/
config:
# Install the Nala Configuration file.
sudo mkdir -p /etc/nala
sudo cp debian/nala.conf /etc/nala/nala.conf
clean:
rm -f docs/nala*.8
rm -rf ./.venv
uninstall:
sudo rm -f /usr/share/man/man8/nala*8.gz
sudo rm -rf /etc/nala
sudo python3 -m pip uninstall nala
binary:
./nala-pyinstall.sh
+25
View File
@@ -0,0 +1,25 @@
use std::env;
use std::io::Error;
use clap_complete::generate_to;
use clap_complete::shells::Bash;
include!("src/cli.rs");
fn main() -> Result<(), Error> {
let outdir = match env::var_os("OUT_DIR") {
None => return Ok(()),
Some(outdir) => outdir,
};
let mut cmd = build();
let path = generate_to(
Bash, &mut cmd, // We need to specify what generator to use
"nala-rs", // We need to specify the bin name manually
outdir, // We need to specify where to write to
)?;
println!("cargo:warning=completion file is generated: {:?}", path);
Ok(())
}
+61
View File
@@ -0,0 +1,61 @@
#!/usr/bin/env just --justfile
# Setup the development environment
setup-dev:
@echo Installing required packages from apt
@sudo apt-get install libapt-pkg-dev codespell -y
@echo Setting up toolchains
@rustup toolchain install nightly
@rustup toolchain install stable
@echo Installing nightly \`rustfmt\`
@rustup toolchain install nightly --component rustfmt
@echo Nightly \`rustfmt\` successfully installed!
@echo Development environment installed successfully!
# Run checks
check: spellcheck clippy
@cargo +nightly fmt --check
@echo Checks were successful!
# Remove generated artifacts
clean:
@cargo clean
@echo Done!
# Build the project
build:
@cargo build
@echo Project successfully built!
# Run the tests
test +ARGS="":
@cargo test -- --test-threads 1 {{ARGS}}
# Run leak tests. Requires root
leak:
@cargo test --no-run
@sudo valgrind --leak-check=full -- $( \
find target/debug/deps/ \
-executable \
-type f \
-name "tests-*" \
-printf "%T@ %p\n" | sort -nr | awk '{print $2}' \
) --test-threads 1
# Lint the codebase
clippy +ARGS="":
@cargo clippy --all-targets --all-features --workspace -- --deny warnings {{ARGS}}
@echo Lint successful!
# Format the codebase
fmt +ARGS="":
@cargo +nightly fmt --all -- {{ARGS}}
@echo Codebase formatted successfully!
# Spellcheck the codebase
spellcheck +ARGS="--skip target*":
@codespell --skip="./.git" --builtin clear,rare,informal,code --ignore-words-list mut,crate {{ARGS}}
@echo Spellings look good!
-32
View File
@@ -1,32 +0,0 @@
#!/usr/bin/env python3
# __
# ____ _____ | | _____
# / \\__ \ | | \__ \
# | | \/ __ \| |__/ __ \_
# |___| (____ /____(____ /
# \/ \/ \/
#
# Copyright (C) 2021, 2022 Blake Lee
#
# This file is part of nala
#
# nala is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# nala is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with nala. If not, see <https://www.gnu.org/licenses/>.
# nala-cli.py doesn't conform to snake name
# This isn't really a module and is an entry script
# pylint: disable=invalid-name
"""Nala command-line entry point."""
from nala import __main__
__main__.main()
+69
View File
@@ -0,0 +1,69 @@
## Configuration file for Nala
[Nala]
# Set to false to disable scrolling text
scrolling_text = true
# Set to false to disable auto auto-removing
auto_remove = true
# Set to false to disable auto updating the package list
# when running the upgrade command
auto_update = true
# Set to true Nala will list the upgradable packages automatically after `update`
update_show_packages = false
# Set to true and Nala will always assume yes
assume_yes = false
# Set to true for `MiB` false for `MB`
filesize_binary = false
# Set to true for `MegaBit` false for `MegaByte`
transfer_speed_bit = false
[Theme]
error = { style = "bold", color = "green" }
red = { style = 0, color = "red" }
warning = { style = "bold", color = "yellow" }
package = { style = ["bold"], color = 201}
version = { style = "bold", color = [255, 250, 0] }
## Pre and Post install hooks allow you to run scripts or commands
## Before or After an install, upgrade and remove
[PreInstall]
## The `hook` key will run after every transaction
# hook = "/general/hook/pre-install.sh"
## You can use a package name to run a specific hook
## Only if that package has been altered
# nala = "apt show nala"
## Additionally you can use a wildcard*
# "linux-image-5*-amd64" = "/hook/for/pre-kernel.sh"
[PostInstall]
# hook = "/general/hook/post-install.sh"
# nala = "apt show nala"
# "linux-image-5*-amd64" = "/hook/for/post-kernel.sh"
## You can also setup Nala to pass arguments to your hook.
## It can be done inline style like so
# neofetch = { hook = "/home/username/neo-hook.sh", args = ["name", "version"] }
## You can also use the longer form
# [PostInstall.neofetch]
# hook = "/home/volitank/neo-hook.sh"
## Arguments are given to the hook in the order they exist in the array below
# args = [
# "name", # The name of the package. Arch is appended for non-native arches
# "fullname", # Full name of the package "neofetch:amd64"
# "architecture", # This will be the arch of the candidate version or "None"
# "version", # This will be the version string of the candidate version or "None"
## To pick information from a specific version you can use the following syntax
# "candidate.version", # Version string of the candidate version or "None"
# "installed.version", # Version string of the installed version or "None"
# "installed.architecture", # Arch of the installed version or "None"
# ]
Generated
-844
View File
@@ -1,844 +0,0 @@
# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand.
[[package]]
name = "anyio"
version = "4.9.0"
description = "High level compatibility layer for multiple asynchronous event loop implementations"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c"},
{file = "anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028"},
]
[package.dependencies]
exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""}
idna = ">=2.8"
sniffio = ">=1.1"
typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""}
[package.extras]
doc = ["Sphinx (>=8.2,<9.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"]
test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\" and python_version < \"3.14\""]
trio = ["trio (>=0.26.1)"]
[[package]]
name = "astroid"
version = "3.3.9"
description = "An abstract syntax tree for Python with inference support."
optional = false
python-versions = ">=3.9.0"
groups = ["dev"]
files = [
{file = "astroid-3.3.9-py3-none-any.whl", hash = "sha256:d05bfd0acba96a7bd43e222828b7d9bc1e138aaeb0649707908d3702a9831248"},
{file = "astroid-3.3.9.tar.gz", hash = "sha256:622cc8e3048684aa42c820d9d218978021c3c3d174fb03a9f0d615921744f550"},
]
[package.dependencies]
typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""}
[[package]]
name = "black"
version = "22.3.1.dev1+g48107e9"
description = "The uncompromising code formatter."
optional = false
python-versions = ">=3.6.2"
groups = ["dev"]
files = []
develop = false
[package.dependencies]
click = ">=8.0.0"
mypy_extensions = ">=0.4.3"
pathspec = ">=0.9.0"
platformdirs = ">=2"
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
typing_extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}
[package.extras]
colorama = ["colorama (>=0.4.3)"]
d = ["aiohttp (>=3.7.4)"]
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
uvloop = ["uvloop (>=0.15.2)"]
[package.source]
type = "git"
url = "https://github.com/volitank/black.git"
reference = "black-tabs"
resolved_reference = "48107e97788fa48c994497f698f02b69a44a7ee3"
[[package]]
name = "certifi"
version = "2025.1.31"
description = "Python package for providing Mozilla's CA Bundle."
optional = false
python-versions = ">=3.6"
groups = ["main"]
files = [
{file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"},
{file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"},
]
[[package]]
name = "cfgv"
version = "3.4.0"
description = "Validate configuration and produce human readable error messages."
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"},
{file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"},
]
[[package]]
name = "click"
version = "8.1.8"
description = "Composable command line interface toolkit"
optional = false
python-versions = ">=3.7"
groups = ["main", "dev"]
files = [
{file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"},
{file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"},
]
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
[[package]]
name = "codespell"
version = "2.4.1"
description = "Fix common misspellings in text files"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{file = "codespell-2.4.1-py3-none-any.whl", hash = "sha256:3dadafa67df7e4a3dbf51e0d7315061b80d265f9552ebd699b3dd6834b47e425"},
{file = "codespell-2.4.1.tar.gz", hash = "sha256:299fcdcb09d23e81e35a671bbe746d5ad7e8385972e65dbb833a2eaac33c01e5"},
]
[package.extras]
dev = ["Pygments", "build", "chardet", "pre-commit", "pytest", "pytest-cov", "pytest-dependency", "ruff", "tomli", "twine"]
hard-encoding-detection = ["chardet"]
toml = ["tomli ; python_version < \"3.11\""]
types = ["chardet (>=5.1.0)", "mypy", "pytest", "pytest-cov", "pytest-dependency"]
[[package]]
name = "colorama"
version = "0.4.6"
description = "Cross-platform colored terminal text."
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
groups = ["main", "dev"]
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
markers = {main = "platform_system == \"Windows\"", dev = "platform_system == \"Windows\" or sys_platform == \"win32\""}
[[package]]
name = "dill"
version = "0.3.9"
description = "serialize all of Python"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{file = "dill-0.3.9-py3-none-any.whl", hash = "sha256:468dff3b89520b474c0397703366b7b95eebe6303f108adf9b19da1f702be87a"},
{file = "dill-0.3.9.tar.gz", hash = "sha256:81aa267dddf68cbfe8029c42ca9ec6a4ab3b22371d1c450abc54422577b4512c"},
]
[package.extras]
graph = ["objgraph (>=1.7.2)"]
profile = ["gprof2dot (>=2022.7.29)"]
[[package]]
name = "distlib"
version = "0.3.9"
description = "Distribution utilities"
optional = false
python-versions = "*"
groups = ["dev"]
files = [
{file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"},
{file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"},
]
[[package]]
name = "exceptiongroup"
version = "1.2.2"
description = "Backport of PEP 654 (exception groups)"
optional = false
python-versions = ">=3.7"
groups = ["main"]
markers = "python_version < \"3.11\""
files = [
{file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
{file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
]
[package.extras]
test = ["pytest (>=6)"]
[[package]]
name = "filelock"
version = "3.18.0"
description = "A platform independent file lock."
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
{file = "filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de"},
{file = "filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2"},
]
[package.extras]
docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"]
testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"]
typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""]
[[package]]
name = "h11"
version = "0.14.0"
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
{file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
{file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
]
[[package]]
name = "httpcore"
version = "1.0.7"
description = "A minimal low-level HTTP client."
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"},
{file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"},
]
[package.dependencies]
certifi = "*"
h11 = ">=0.13,<0.15"
[package.extras]
asyncio = ["anyio (>=4.0,<5.0)"]
http2 = ["h2 (>=3,<5)"]
socks = ["socksio (==1.*)"]
trio = ["trio (>=0.22.0,<1.0)"]
[[package]]
name = "httpx"
version = "0.28.1"
description = "The next generation HTTP client."
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"},
{file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"},
]
[package.dependencies]
anyio = "*"
certifi = "*"
httpcore = "==1.*"
idna = "*"
[package.extras]
brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""]
cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"]
http2 = ["h2 (>=3,<5)"]
socks = ["socksio (==1.*)"]
zstd = ["zstandard (>=0.18.0)"]
[[package]]
name = "identify"
version = "2.6.9"
description = "File identification library for Python"
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
{file = "identify-2.6.9-py2.py3-none-any.whl", hash = "sha256:c98b4322da415a8e5a70ff6e51fbc2d2932c015532d77e9f8537b4ba7813b150"},
{file = "identify-2.6.9.tar.gz", hash = "sha256:d40dfe3142a1421d8518e3d3985ef5ac42890683e32306ad614a29490abeb6bf"},
]
[package.extras]
license = ["ukkonen"]
[[package]]
name = "idna"
version = "3.10"
description = "Internationalized Domain Names in Applications (IDNA)"
optional = false
python-versions = ">=3.6"
groups = ["main"]
files = [
{file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"},
{file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"},
]
[package.extras]
all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"]
[[package]]
name = "isort"
version = "6.0.1"
description = "A Python utility / library to sort Python imports."
optional = false
python-versions = ">=3.9.0"
groups = ["dev"]
files = [
{file = "isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615"},
{file = "isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450"},
]
[package.extras]
colors = ["colorama"]
plugins = ["setuptools"]
[[package]]
name = "markdown-it-py"
version = "3.0.0"
description = "Python port of markdown-it. Markdown parsing, done right!"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"},
{file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"},
]
[package.dependencies]
mdurl = ">=0.1,<1.0"
[package.extras]
benchmarking = ["psutil", "pytest", "pytest-benchmark"]
code-style = ["pre-commit (>=3.0,<4.0)"]
compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"]
linkify = ["linkify-it-py (>=1,<3)"]
plugins = ["mdit-py-plugins"]
profiling = ["gprof2dot"]
rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"]
testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"]
[[package]]
name = "mccabe"
version = "0.7.0"
description = "McCabe checker, plugin for flake8"
optional = false
python-versions = ">=3.6"
groups = ["dev"]
files = [
{file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
{file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
]
[[package]]
name = "mdurl"
version = "0.1.2"
description = "Markdown URL utilities"
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
{file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"},
{file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
]
[[package]]
name = "mypy"
version = "1.15.0"
description = "Optional static typing for Python"
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
{file = "mypy-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:979e4e1a006511dacf628e36fadfecbcc0160a8af6ca7dad2f5025529e082c13"},
{file = "mypy-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c4bb0e1bd29f7d34efcccd71cf733580191e9a264a2202b0239da95984c5b559"},
{file = "mypy-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be68172e9fd9ad8fb876c6389f16d1c1b5f100ffa779f77b1fb2176fcc9ab95b"},
{file = "mypy-1.15.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7be1e46525adfa0d97681432ee9fcd61a3964c2446795714699a998d193f1a3"},
{file = "mypy-1.15.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2e2c2e6d3593f6451b18588848e66260ff62ccca522dd231cd4dd59b0160668b"},
{file = "mypy-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:6983aae8b2f653e098edb77f893f7b6aca69f6cffb19b2cc7443f23cce5f4828"},
{file = "mypy-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2922d42e16d6de288022e5ca321cd0618b238cfc5570e0263e5ba0a77dbef56f"},
{file = "mypy-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2ee2d57e01a7c35de00f4634ba1bbf015185b219e4dc5909e281016df43f5ee5"},
{file = "mypy-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:973500e0774b85d9689715feeffcc980193086551110fd678ebe1f4342fb7c5e"},
{file = "mypy-1.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a95fb17c13e29d2d5195869262f8125dfdb5c134dc8d9a9d0aecf7525b10c2c"},
{file = "mypy-1.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1905f494bfd7d85a23a88c5d97840888a7bd516545fc5aaedff0267e0bb54e2f"},
{file = "mypy-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:c9817fa23833ff189db061e6d2eff49b2f3b6ed9856b4a0a73046e41932d744f"},
{file = "mypy-1.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:aea39e0583d05124836ea645f412e88a5c7d0fd77a6d694b60d9b6b2d9f184fd"},
{file = "mypy-1.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f2147ab812b75e5b5499b01ade1f4a81489a147c01585cda36019102538615f"},
{file = "mypy-1.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce436f4c6d218a070048ed6a44c0bbb10cd2cc5e272b29e7845f6a2f57ee4464"},
{file = "mypy-1.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8023ff13985661b50a5928fc7a5ca15f3d1affb41e5f0a9952cb68ef090b31ee"},
{file = "mypy-1.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1124a18bc11a6a62887e3e137f37f53fbae476dc36c185d549d4f837a2a6a14e"},
{file = "mypy-1.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:171a9ca9a40cd1843abeca0e405bc1940cd9b305eaeea2dda769ba096932bb22"},
{file = "mypy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445"},
{file = "mypy-1.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d"},
{file = "mypy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5"},
{file = "mypy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036"},
{file = "mypy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357"},
{file = "mypy-1.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf"},
{file = "mypy-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e601a7fa172c2131bff456bb3ee08a88360760d0d2f8cbd7a75a65497e2df078"},
{file = "mypy-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:712e962a6357634fef20412699a3655c610110e01cdaa6180acec7fc9f8513ba"},
{file = "mypy-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95579473af29ab73a10bada2f9722856792a36ec5af5399b653aa28360290a5"},
{file = "mypy-1.15.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f8722560a14cde92fdb1e31597760dc35f9f5524cce17836c0d22841830fd5b"},
{file = "mypy-1.15.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fbb8da62dc352133d7d7ca90ed2fb0e9d42bb1a32724c287d3c76c58cbaa9c2"},
{file = "mypy-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:d10d994b41fb3497719bbf866f227b3489048ea4bbbb5015357db306249f7980"},
{file = "mypy-1.15.0-py3-none-any.whl", hash = "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e"},
{file = "mypy-1.15.0.tar.gz", hash = "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43"},
]
[package.dependencies]
mypy_extensions = ">=1.0.0"
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
typing_extensions = ">=4.6.0"
[package.extras]
dmypy = ["psutil (>=4.0)"]
faster-cache = ["orjson"]
install-types = ["pip"]
mypyc = ["setuptools (>=50)"]
reports = ["lxml"]
[[package]]
name = "mypy-extensions"
version = "1.0.0"
description = "Type system extensions for programs checked with the mypy type checker."
optional = false
python-versions = ">=3.5"
groups = ["dev"]
files = [
{file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
]
[[package]]
name = "nodeenv"
version = "1.9.1"
description = "Node.js virtual environment builder"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
groups = ["dev"]
files = [
{file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"},
{file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"},
]
[[package]]
name = "pathspec"
version = "0.12.1"
description = "Utility library for gitignore style pattern matching of file paths."
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
{file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
]
[[package]]
name = "pexpect"
version = "4.9.0"
description = "Pexpect allows easy control of interactive console applications."
optional = false
python-versions = "*"
groups = ["main"]
files = [
{file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"},
{file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"},
]
[package.dependencies]
ptyprocess = ">=0.5"
[[package]]
name = "platformdirs"
version = "4.3.7"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
{file = "platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94"},
{file = "platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351"},
]
[package.extras]
docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"]
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"]
type = ["mypy (>=1.14.1)"]
[[package]]
name = "pre-commit"
version = "4.2.0"
description = "A framework for managing and maintaining multi-language pre-commit hooks."
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
{file = "pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd"},
{file = "pre_commit-4.2.0.tar.gz", hash = "sha256:601283b9757afd87d40c4c4a9b2b5de9637a8ea02eaff7adc2d0fb4e04841146"},
]
[package.dependencies]
cfgv = ">=2.0.0"
identify = ">=1.0.0"
nodeenv = ">=0.11.1"
pyyaml = ">=5.1"
virtualenv = ">=20.10.0"
[[package]]
name = "ptyprocess"
version = "0.7.0"
description = "Run a subprocess in a pseudo terminal"
optional = false
python-versions = "*"
groups = ["main"]
files = [
{file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"},
{file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"},
]
[[package]]
name = "pydocstyle"
version = "6.3.0"
description = "Python docstring style checker"
optional = false
python-versions = ">=3.6"
groups = ["dev"]
files = [
{file = "pydocstyle-6.3.0-py3-none-any.whl", hash = "sha256:118762d452a49d6b05e194ef344a55822987a462831ade91ec5c06fd2169d019"},
{file = "pydocstyle-6.3.0.tar.gz", hash = "sha256:7ce43f0c0ac87b07494eb9c0b462c0b73e6ff276807f204d6b53edc72b7e44e1"},
]
[package.dependencies]
snowballstemmer = ">=2.2.0"
[package.extras]
toml = ["tomli (>=1.2.3) ; python_version < \"3.11\""]
[[package]]
name = "pygments"
version = "2.19.1"
description = "Pygments is a syntax highlighting package written in Python."
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"},
{file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"},
]
[package.extras]
windows-terminal = ["colorama (>=0.4.6)"]
[[package]]
name = "pylint"
version = "3.3.5"
description = "python code static checker"
optional = false
python-versions = ">=3.9.0"
groups = ["dev"]
files = [
{file = "pylint-3.3.5-py3-none-any.whl", hash = "sha256:7cb170929a371238530b2eeea09f5f28236d106b70308c3d46a9c0cf11634633"},
{file = "pylint-3.3.5.tar.gz", hash = "sha256:38d0f784644ed493d91f76b5333a0e370a1c1bc97c22068a77523b4bf1e82c31"},
]
[package.dependencies]
astroid = ">=3.3.8,<=3.4.0-dev0"
colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""}
dill = [
{version = ">=0.2", markers = "python_version < \"3.11\""},
{version = ">=0.3.7", markers = "python_version >= \"3.12\""},
{version = ">=0.3.6", markers = "python_version == \"3.11\""},
]
isort = ">=4.2.5,<5.13.0 || >5.13.0,<7"
mccabe = ">=0.6,<0.8"
platformdirs = ">=2.2.0"
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
tomlkit = ">=0.10.1"
typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""}
[package.extras]
spelling = ["pyenchant (>=3.2,<4.0)"]
testutils = ["gitpython (>3)"]
[[package]]
name = "pyupgrade"
version = "3.19.1"
description = "A tool to automatically upgrade syntax for newer versions."
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
{file = "pyupgrade-3.19.1-py2.py3-none-any.whl", hash = "sha256:8c5b0bfacae5ff30fa136a53eb7f22c34ba007450d4099e9da8089dabb9e67c9"},
{file = "pyupgrade-3.19.1.tar.gz", hash = "sha256:d10e8c5f54b8327211828769e98d95d95e4715de632a3414f1eef3f51357b9e2"},
]
[package.dependencies]
tokenize-rt = ">=6.1.0"
[[package]]
name = "pyyaml"
version = "6.0.2"
description = "YAML parser and emitter for Python"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"},
{file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"},
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"},
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"},
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"},
{file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"},
{file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"},
{file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"},
{file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"},
{file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"},
{file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"},
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"},
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"},
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"},
{file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"},
{file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"},
{file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"},
{file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"},
{file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"},
{file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"},
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"},
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"},
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"},
{file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"},
{file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"},
{file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"},
{file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"},
{file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"},
{file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"},
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"},
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"},
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"},
{file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"},
{file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"},
{file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"},
{file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"},
{file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"},
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"},
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"},
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"},
{file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"},
{file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"},
{file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"},
{file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"},
{file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"},
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"},
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"},
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"},
{file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"},
{file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"},
{file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"},
{file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"},
{file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"},
]
[[package]]
name = "rich"
version = "13.9.4"
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
optional = false
python-versions = ">=3.8.0"
groups = ["main"]
files = [
{file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"},
{file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"},
]
[package.dependencies]
markdown-it-py = ">=2.2.0"
pygments = ">=2.13.0,<3.0.0"
typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.11\""}
[package.extras]
jupyter = ["ipywidgets (>=7.5.1,<9)"]
[[package]]
name = "shellingham"
version = "1.5.4"
description = "Tool to Detect Surrounding Shell"
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
{file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"},
{file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"},
]
[[package]]
name = "sniffio"
version = "1.3.1"
description = "Sniff out which async library your code is running under"
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
{file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"},
{file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
]
[[package]]
name = "snowballstemmer"
version = "2.2.0"
description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms."
optional = false
python-versions = "*"
groups = ["dev"]
files = [
{file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"},
{file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"},
]
[[package]]
name = "socksio"
version = "1.0.0"
description = "Sans-I/O implementation of SOCKS4, SOCKS4A, and SOCKS5."
optional = true
python-versions = ">=3.6"
groups = ["main"]
markers = "extra == \"socks\""
files = [
{file = "socksio-1.0.0-py3-none-any.whl", hash = "sha256:95dc1f15f9b34e8d7b16f06d74b8ccf48f609af32ab33c608d08761c5dcbb1f3"},
{file = "socksio-1.0.0.tar.gz", hash = "sha256:f88beb3da5b5c38b9890469de67d0cb0f9d494b78b106ca1845f96c10b91c4ac"},
]
[[package]]
name = "tokenize-rt"
version = "6.1.0"
description = "A wrapper around the stdlib `tokenize` which roundtrips."
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
{file = "tokenize_rt-6.1.0-py2.py3-none-any.whl", hash = "sha256:d706141cdec4aa5f358945abe36b911b8cbdc844545da99e811250c0cee9b6fc"},
{file = "tokenize_rt-6.1.0.tar.gz", hash = "sha256:e8ee836616c0877ab7c7b54776d2fefcc3bde714449a206762425ae114b53c86"},
]
[[package]]
name = "tomli"
version = "2.2.1"
description = "A lil' TOML parser"
optional = false
python-versions = ">=3.8"
groups = ["main", "dev"]
files = [
{file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"},
{file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"},
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"},
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"},
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"},
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"},
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"},
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"},
{file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"},
{file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"},
{file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"},
{file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"},
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"},
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"},
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"},
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"},
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"},
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"},
{file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"},
{file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"},
{file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"},
{file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"},
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"},
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"},
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"},
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"},
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"},
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"},
{file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"},
{file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"},
{file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"},
{file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"},
]
markers = {dev = "python_version < \"3.11\""}
[[package]]
name = "tomlkit"
version = "0.13.2"
description = "Style preserving TOML library"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{file = "tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde"},
{file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"},
]
[[package]]
name = "typer"
version = "0.15.2"
description = "Typer, build great CLIs. Easy to code. Based on Python type hints."
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
{file = "typer-0.15.2-py3-none-any.whl", hash = "sha256:46a499c6107d645a9c13f7ee46c5d5096cae6f5fc57dd11eccbbb9ae3e44ddfc"},
{file = "typer-0.15.2.tar.gz", hash = "sha256:ab2fab47533a813c49fe1f16b1a370fd5819099c00b119e0633df65f22144ba5"},
]
[package.dependencies]
click = ">=8.0.0"
rich = ">=10.11.0"
shellingham = ">=1.3.0"
typing-extensions = ">=3.7.4.3"
[[package]]
name = "typing-extensions"
version = "4.12.2"
description = "Backported and Experimental Type Hints for Python 3.8+"
optional = false
python-versions = ">=3.8"
groups = ["main", "dev"]
files = [
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
]
[[package]]
name = "virtualenv"
version = "20.29.3"
description = "Virtual Python Environment builder"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{file = "virtualenv-20.29.3-py3-none-any.whl", hash = "sha256:3e3d00f5807e83b234dfb6122bf37cfadf4be216c53a49ac059d02414f819170"},
{file = "virtualenv-20.29.3.tar.gz", hash = "sha256:95e39403fcf3940ac45bc717597dba16110b74506131845d9b687d5e73d947ac"},
]
[package.dependencies]
distlib = ">=0.3.7,<1"
filelock = ">=3.12.2,<4"
platformdirs = ">=3.9.1,<5"
[package.extras]
docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"]
test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""]
[extras]
socks = ["socksio"]
[metadata]
lock-version = "2.1"
python-versions = "^3.9"
content-hash = "29442adafeb06e182f9201ee34b1973ec482c9bb7cb999c87413fcaec3f4d158"
-102
View File
@@ -1,102 +0,0 @@
[tool.poetry]
name = "nala"
version = "0.16.0"
description = "Commandline frontend for the apt package manager"
authors = [
"Blake Lee <blake@volian.org>",
"Sourajyoti Basak <wiz28@protonmail.com>",
]
license = "GPL-3.0-or-later"
readme = "README.rst"
homepage = "https://gitlab.com/volian/nala"
repository = "https://gitlab.com/volian/nala"
documentation = "https://gitlab.com/volian/nala"
keywords = [
"nala",
"apt",
"cli",
"command line",
"console",
"debian",
"package manager",
"ubuntu"
]
classifiers = [
'Development Status :: 4 - Beta',
'Environment :: Console',
"Intended Audience :: Developers",
"Intended Audience :: End Users/Desktop",
"Intended Audience :: System Administrators",
'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)',
'Operating System :: POSIX :: Linux',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.9',
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
'Programming Language :: Python :: 3 :: Only',
"Topic :: System",
"Topic :: System :: Archiving :: Packaging",
"Topic :: System :: Installation/Setup",
"Topic :: System :: Systems Administration",
"Topic :: System :: Software Distribution",
"Topic :: Utilities",
"Typing :: Typed"
]
[tool.poetry.dependencies]
python = "^3.9"
httpx = "0.28.1"
rich = "^13.9.4"
pexpect = "^4.9.0"
anyio = "^4.9.0"
typer = "^0.15.2"
tomli = "^2.2.1"
typing-extensions = "^4.12.2"
socksio = {version = "1.0.0", optional = true}
[tool.poetry.group.dev.dependencies]
black = { git = "https://github.com/volitank/black.git", branch = "black-tabs" }
codespell = "2.4.1"
isort = "6.0.1"
mypy = "1.15.0"
pre-commit = "4.2.0"
pyupgrade = "3.19.1"
pydocstyle = "6.3.0"
pylint = "3.3.5"
[tool.poetry.extras]
socks = ["socksio"]
[tool.isort]
py_version = "auto"
balanced_wrapping = true
multi_line_output = 3
combine_as_imports = true
combine_star = true
indent = "\t"
group_by_package = true
known_first_party = ["nala"]
lexicographical = true
profile = "black"
[tool.mypy]
python_executable="/usr/bin/python3"
disallow_untyped_decorators = false
ignore_missing_imports = true
no_warn_unused_ignores = true
pretty = true
show_column_numbers = true
show_error_codes = true
show_error_context = true
strict = true
[tool.pydocstyle]
add_select = "D211, D212"
ignore = "D206, D203, D213, D417"
[tool.poetry.scripts]
nala = "nala.__main__:main"
[build-system]
requires = ["poetry_core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
+15
View File
@@ -0,0 +1,15 @@
hard_tabs = true
wrap_comments = true
format_strings = true
fn_single_line = true
empty_item_single_line = true
single_line_if_else_max_width = 80
normalize_doc_attributes = true
normalize_comments = true
reorder_impl_items = true
use_field_init_shorthand = true
group_imports = "StdExternalCrate"
imports_granularity = "Module"
condense_wildcard_suffixes = true
match_block_trailing_comma = true
format_code_in_doc_comments = false
+103
View File
@@ -0,0 +1,103 @@
use clap::{Arg, ArgAction, Command};
pub fn build() -> Command<'static> {
Command::new("nala-rs")
.about("Commandline front-end for libapt-pkg")
.version("0.1.0")
.author("Blake Lee <blake@volian.org>")
.subcommand_required(false)
.arg_required_else_help(true)
.disable_version_flag(true)
.arg(
Arg::new("license")
.long("license")
.help("Print license information")
.action(ArgAction::SetTrue)
.display_order(1),
)
.arg(
Arg::new("debug")
.long("debug")
.help("Print debug statements for solving issues")
.action(ArgAction::SetTrue)
.global(true)
.display_order(1),
)
.arg(
Arg::new("verbose")
.long("verbose")
.help("Disable scrolling text and print extra information")
.action(ArgAction::SetTrue)
.takes_value(true)
.global(true)
.display_order(1),
)
.arg(
Arg::new("config")
.long("config")
.help("Specify a different configuration file")
.action(ArgAction::Set)
.takes_value(true)
.global(true)
.display_order(1),
)
.subcommand(
Command::new("list")
.about("List packages based on package names.")
.arg(
Arg::new("pkg_names")
.help("Package names to list")
.action(ArgAction::Append)
.multiple_occurrences(true)
.takes_value(true)
.required(false),
)
.arg(
Arg::new("description")
.long("description")
.help("Print the full description of each package")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("summary")
.long("summary")
.help("Print the summary of each package")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("all_versions")
.short('a')
.long("all-versions")
.help("Show all versions of a package")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("installed")
.short('i')
.long("installed")
.help("List only packages that are installed")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("nala_installed")
.short('N')
.long("nala-installed")
.help("List only packages explicitly installed with Nala")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("upgradable")
.short('u')
.long("upgradable")
.help("List only packages that are upgradable")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("virtual")
.short('V')
.long("virtual")
.help("List only virtual packages")
.action(ArgAction::SetTrue),
),
)
}
+377
View File
@@ -0,0 +1,377 @@
use std::borrow::Cow;
use std::collections::HashMap;
use std::fmt;
use anyhow::{anyhow, Context, Result};
use lazy_static::lazy_static;
use toml::Value;
use crate::config::Config;
use crate::util::dprint;
static RESET: &str = "\x1b[0m";
lazy_static! {
static ref COLOR_MAP: HashMap<&'static str, u8> = HashMap::from([
("black", 0),
("red", 1),
("green", 2),
("yellow", 3),
("blue", 4),
("magenta", 5),
("cyan", 6),
("white", 7),
("bright_black", 8),
("bright_red", 9),
("bright_green", 10),
("bright_yellow", 11),
("bright_blue", 12),
("bright_magenta", 13),
("bright_cyan", 14),
("bright_white", 15),
("warning", 11),
("error", 9),
("package", 10),
("version", 12),
]);
}
#[repr(u8)]
#[derive(Clone, Debug)]
/// Ansi Color Styles
pub enum Style {
/// Text is Normal
Normal,
/// Text is Bold
Bold,
/// Text is Faint
Faint,
/// Text is Italic
Italic,
/// Underlines the text
Underline,
/// Text will slowly blink
SlowBlink,
/// Rapidly blinks the text
RapidBlink,
/// Inverts the Foreground and Background Colors
InvertColors,
/// Not widely supported
Hide,
/// Strike through the text
StrikeThrough,
/// Multiple Styles as a String
Multiple(String),
}
impl Style {
/// Return the Enum as a str ansi code
///
/// If called on `Style::Multiple` this will panic
pub fn as_str(&self) -> &'static str {
match self {
Style::Normal => "0",
Style::Bold => "1",
Style::Faint => "2",
Style::Italic => "3",
Style::Underline => "4",
Style::SlowBlink => "5",
Style::RapidBlink => "6",
Style::InvertColors => "7",
Style::Hide => "8",
Style::StrikeThrough => "9",
_ => panic!("as_str is not supported for the multiple variant"),
}
}
/// Load a Style from an int such as `1` for Bold
pub fn from_i64(value: &i64) -> Result<Style> {
match value {
0 => Ok(Style::Normal),
1 => Ok(Style::Bold),
2 => Ok(Style::Faint),
3 => Ok(Style::Italic),
4 => Ok(Style::Underline),
5 => Ok(Style::SlowBlink),
6 => Ok(Style::RapidBlink),
7 => Ok(Style::InvertColors),
8 => Ok(Style::Hide),
9 => Ok(Style::StrikeThrough),
_ => Err(anyhow!("Value '{value}' is not a valid Int Style")),
}
}
/// Load a Style from a str such as `"underline"`
pub fn from_str(value: &str) -> Result<Style> {
match value {
"default" => Ok(Style::Normal),
"bold" => Ok(Style::Bold),
"faint" => Ok(Style::Faint),
"italic" => Ok(Style::Italic),
"underline" => Ok(Style::Underline),
"slow_blink" => Ok(Style::SlowBlink),
"rapid_blink" => Ok(Style::RapidBlink),
"invert_colors" => Ok(Style::InvertColors),
"hide" => Ok(Style::Hide),
"strike_through" => Ok(Style::StrikeThrough),
_ => Err(anyhow!("Value '{value}' is not a valid String Style")),
}
}
/// Load a style from a toml array
///
/// Return `Style::Multiple(String)`
pub fn from_toml_array(vector: &Vec<Value>) -> Result<Style> {
let last = vector.len() - 1;
let mut string = String::new();
for (i, value) in vector.iter().enumerate() {
let style = match value {
Value::Integer(int) => Style::from_i64(int)?,
Value::String(string) => Style::from_str(string)?,
_ => return Err(anyhow!("RGB Value should be 'int' not '{value:?}'")),
};
string += style.as_str();
if i != last {
string += ";"
}
}
Ok(Style::Multiple(string))
}
}
impl fmt::Display for Style {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
// Multiple cannot use as_str. It will panic
Style::Multiple(string) => write!(f, "{string}")?,
_ => write!(f, "{}", self.as_str())?,
}
Ok(())
}
}
#[derive(Debug)]
pub enum ColorType {
Standard(u8),
Rgb(String),
}
impl ColorType {
pub fn from_i64(value: &i64) -> Result<ColorType> {
let num = u8::try_from(*value).with_context(|| "Int must be in range 0...255")?;
Ok(ColorType::Standard(num))
}
pub fn from_str(value: &str) -> Result<ColorType> {
match value {
"black" => Ok(ColorType::Standard(0)),
"red" => Ok(ColorType::Standard(1)),
"green" => Ok(ColorType::Standard(2)),
"yellow" => Ok(ColorType::Standard(3)),
"blue" => Ok(ColorType::Standard(4)),
"magenta" => Ok(ColorType::Standard(5)),
"cyan" => Ok(ColorType::Standard(6)),
"white" => Ok(ColorType::Standard(7)),
"bright_black" => Ok(ColorType::Standard(8)),
"bright_red" => Ok(ColorType::Standard(9)),
"bright_green" => Ok(ColorType::Standard(10)),
"bright_yellow" => Ok(ColorType::Standard(11)),
"bright_blue" => Ok(ColorType::Standard(12)),
"bright_magenta" => Ok(ColorType::Standard(13)),
"bright_cyan" => Ok(ColorType::Standard(14)),
"bright_white" => Ok(ColorType::Standard(15)),
_ => Err(anyhow!("Value '{value}' is not a valid Color")),
}
}
pub fn from_toml_array(vector: &Vec<Value>) -> Result<ColorType> {
let total = vector.len();
if total != 3 {
return Err(anyhow!("RGB Value should contain 3 integers not {total}"));
}
let mut string = String::new();
for (num, value) in vector.iter().enumerate() {
match value {
Value::Integer(int) => {
let rgb = u8::try_from(*int).with_context(|| "Int must be in range 0...255")?;
string += &rgb.to_string();
if num != 2 {
string += ";";
}
},
_ => {
return Err(anyhow!("RGB Value should be 'int' not '{value:?}'"));
},
}
}
Ok(ColorType::Rgb(string))
}
}
impl fmt::Display for ColorType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ColorType::Standard(int) => write!(f, "{int}"),
ColorType::Rgb(string) => write!(f, "{string}"),
}
}
}
#[derive(Debug)]
pub struct Theme {
#[allow(dead_code)]
/// Name is used for debug purposes
name: &'static str,
style: Style,
color: ColorType,
}
impl Theme {
pub fn new(name: &'static str, style: Style, color: ColorType) -> Self {
Self { name, style, color }
}
pub fn default(name: &'static str, color: ColorType) -> Self {
Self {
name,
style: Style::Bold,
color,
}
}
}
/// Color text based on Style and ColorCodes
pub struct Color {
can_color: bool,
color_map: HashMap<&'static str, Theme>,
}
impl Color {
pub fn default() -> Color {
let mut color_map: HashMap<&'static str, Theme> = HashMap::new();
for (color, code) in &*COLOR_MAP {
color_map.insert(color, Theme::default(color, ColorType::Standard(*code)));
}
Color {
can_color: true,
color_map,
}
}
pub fn update_from_config(&mut self, config: &Config) -> Result<()> {
let config_map = match config.color_map.as_ref() {
Ok(map) => map,
Err(err) => {
self.warn(&format!("{err:?}"));
return Ok(());
},
};
// Iterate the key, value of the [Theme] Configuration
for (string, value) in config_map.iter() {
let conf_key = string.as_str();
// If the key is not in the defaults, ignore it
if !self.color_map.contains_key(conf_key) {
continue;
}
match value {
// Each Theme should be a table
Value::Table(table) => {
dprint!(config, "Loading '{conf_key}' from config");
let (key, _unused) = self.color_map.get_key_value(conf_key).unwrap();
let theme = Theme::new(
key,
config
.get_style(table, "style")
.with_context(|| format!("Invalid value for '{key}.style'"))?,
config
.get_color(table, "color", *COLOR_MAP.get(key).unwrap())
.with_context(|| format!("Invalid value for '{key}.color'"))?,
);
dprint!(config, "Loaded {theme:?}");
self.color_map.insert(key, theme);
},
_ => {
return Err(anyhow!(
"Invalid value for '{conf_key}': Unsupported Type '{}'",
value.type_str()
))
},
}
}
Ok(())
}
/// Color a string based on a Theme
pub fn color<'a>(&self, theme: &Theme, string: &'a str) -> Cow<'a, str> {
if self.can_color {
let style = &theme.style;
match &theme.color {
ColorType::Standard(color) => {
return Cow::Owned(format!("\x1b[{style};38;5;{color}m{string}{RESET}"));
},
ColorType::Rgb(color) => {
return Cow::Owned(format!("\x1b[{style};38;2;{color}m{string}{RESET}"));
},
}
}
Cow::Borrowed(string)
}
pub fn style<'a>(&self, style: Style, string: &'a str) -> Cow<'a, str> {
if self.can_color {
return Cow::Owned(format!("\x1b[{style}m{string}{RESET}"));
}
Cow::Borrowed(string)
}
// /// Color the text red with configured settings
// pub fn red<'a>(&self, string: &'a str) -> Cow<'a, str> {
// self.color(self.color_map.get("bright_red").unwrap(), string)
// }
/// Color the text red with configured settings
pub fn yellow<'a>(&self, string: &'a str) -> Cow<'a, str> {
self.color(self.color_map.get("bright_yellow").unwrap(), string)
}
// pub fn blue<'a>(&self, string: &'a str) -> Cow<'a, str> {
// self.color(self.color_map.get("bright_blue").unwrap(), string)
// }
/// Styles the text in bold only
pub fn bold<'a>(&self, string: &'a str) -> Cow<'a, str> { self.style(Style::Bold, string) }
/// Color the package name according to configuration
pub fn package<'a>(&self, string: &'a str) -> Cow<'a, str> {
self.color(self.color_map.get("package").unwrap(), string)
}
/// Color the version according to configuration
pub fn version<'a>(&self, string: &'a str) -> Cow<'a, str> {
let open = self.bold("(");
let close = self.bold(")");
let version = self.color(self.color_map.get("version").unwrap(), string);
return Cow::Owned(format!("{open}{version}{close}"));
}
/// Print a warning to stderr
pub fn warn(&self, string: &str) {
eprintln!(
"{} {string}",
self.color(self.color_map.get("warning").unwrap(), "Warning:",)
);
}
/// Print an error to stderr
pub fn error(&self, string: &str) {
eprintln!(
"{} {string}",
self.color(self.color_map.get("error").unwrap(), "Error:",)
);
}
}
+183
View File
@@ -0,0 +1,183 @@
use std::fs;
use anyhow::{anyhow, Context, Result};
use clap::ArgMatches;
use toml::map::Map;
use toml::Value;
use crate::colors::{Color, ColorType, Style};
use crate::util::dprint;
#[derive(Debug)]
/// Configuration struct
pub struct Config {
nala_map: Map<String, Value>,
pkg_names: Option<Vec<String>>,
pub color_map: Result<Map<String, Value>>,
}
impl Config {
pub fn error(err: anyhow::Error) -> Self {
Config {
nala_map: Map::new(),
pkg_names: None,
color_map: Err(err),
}
}
pub fn new(color: &Color, conf_file: &str) -> Config {
// Try to read the entire config file and map it.
// Return an empty config and print a warning on failure.
let config = match Self::read_config(conf_file) {
Ok(map) => map,
Err(err) => {
color.warn(&format!("{err:?}"));
return Config::error(err);
},
};
// Reads the [Nala] section of the config
let nala_map = Self::read_section(&config, conf_file, "Nala").unwrap_or_else(|err| {
color.warn(&format!("{err:?}"));
Map::new()
});
// Result is unused.
// It will be handled when the Color struct is loaded from the config.
let color_map = Self::read_section(&config, conf_file, "Theme");
// Eventually this needs to include preinstall and postinstall sections.
Config {
nala_map,
pkg_names: None,
color_map,
}
}
/// Read and Return the entire toml configuration file
fn read_config(conf_file: &str) -> Result<Map<String, Value>> {
let conf = fs::read_to_string(conf_file)
.with_context(|| format!("Failed to read {conf_file}, using defaults"))?
// Parse the Toml string
.parse::<Value>()
.with_context(|| format!("Failed to parse {conf_file}, using defaults"))?
// Serialize Toml into a Mapping
.try_into::<Map<String, Value>>()
.with_context(|| format!("Unable to serialize {conf_file}, using defaults"))?;
Ok(conf)
}
/// Read and Return a specific section of the configuration file
fn read_section(
config_map: &Map<String, Value>,
conf_file: &str,
section: &str,
) -> Result<Map<String, Value>> {
let section_map = config_map
.get(section)
.with_context(|| {
format!("Section '[{section}]' was not found in {conf_file}, using defaults")
})?
// Clone and Serialize the Nala section into its own Map
.clone()
.try_into::<Map<String, Value>>()
.with_context(|| {
format!("Unable to map '[{section}]' from {conf_file}, using defaults")
})?;
Ok(section_map)
}
/// Load configuration with the command line arguments
pub fn load_args(&mut self, args: &ArgMatches) {
let bool_opts = [
"debug",
"verbose",
"description",
"summary",
"all_versions",
"installed",
"nala_installed",
"upgradable",
"virtual",
];
for opt in bool_opts {
if *args.get_one(opt).unwrap_or(&false) {
self.nala_map.insert(opt.to_string(), Value::Boolean(true));
} else {
self.nala_map.insert(opt.to_string(), Value::Boolean(false));
}
}
if let Some(pkg_names) = args.get_many::<String>("pkg_names") {
let pkgs: Vec<String> = pkg_names.cloned().collect();
self.pkg_names = if pkgs.is_empty() { None } else { Some(pkgs) };
dprint!(self, "Package Names = {:?}", self.pkg_names);
}
// If Debug is there we can print the whole thing.
dprint!(self, "Config Map = {:#?}", self.nala_map);
}
/// Get a bool from the configuration by &str
pub fn get_bool(&self, key: &str, default: bool) -> bool {
match self.nala_map.get(key) {
Some(Value::Boolean(ret)) => *ret,
_ => default,
}
}
/// Get the package names that were passed as arguments
pub fn pkg_names(&self) -> Option<&Vec<String>> { self.pkg_names.as_ref() }
/// Return true if debug is enabled
pub fn debug(&self) -> bool { self.get_bool("debug", false) }
/// Get the color information from the configuration
pub fn get_color(
&self,
theme_map: &Map<String, Value>,
key: &str,
default: u8,
) -> Result<ColorType> {
if let Some(value) = theme_map.get(key) {
let color = match value {
Value::Integer(int) => ColorType::from_i64(int)?,
Value::String(string) => ColorType::from_str(string)?,
Value::Array(array) => ColorType::from_toml_array(array)?,
_ => {
return Err(anyhow!("Unsupported Type '{}'", value.type_str()));
},
};
dprint!(self, "Loading '{key}' from config {value:?} as {color:?}");
return Ok(color);
}
// Return the default
let color = ColorType::Standard(default);
dprint!(self, "Key: '{key}' not found, using default '{color:?}'");
Ok(color)
}
/// Get the style information from the configuration
pub fn get_style(&self, theme_map: &Map<String, Value>, key: &str) -> Result<Style> {
if let Some(value) = theme_map.get(key) {
let style = match value {
Value::Integer(int) => Style::from_i64(int)?,
Value::String(string) => Style::from_str(string)?,
Value::Array(array) => Style::from_toml_array(array)?,
_ => {
return Err(anyhow!("Unsupported Type '{}'", value.type_str()));
},
};
dprint!(self, "Loading '{key}' from config {value:?} as {style:?}");
return Ok(style);
}
// Return the default
let style = Style::Bold;
dprint!(self, "Key: '{key}' not found, using default '{style:?}'");
Ok(style)
}
}
+214
View File
@@ -0,0 +1,214 @@
use std::io::Write;
use anyhow::{anyhow, Result};
use rust_apt::cache::{Cache, PackageSort};
use rust_apt::package::{Package, Version};
use crate::colors::Color;
use crate::config::Config;
/// The list command
pub fn list(config: &Config, color: &Color) -> Result<()> {
let stdout = std::io::stdout();
let mut out = stdout.lock();
let cache = Cache::new();
let mut sort = PackageSort::default().names();
// set up our sorting parameters
if config.get_bool("installed", false) {
sort = sort.installed();
}
if config.get_bool("upgradable", false) {
sort = sort.upgradable();
}
if config.get_bool("virtual", false) {
sort = sort.only_virtual();
}
// Filter the packages by names if they were provided
let mut packages: Vec<Package> = Vec::new();
let mut not_found: Vec<&String> = Vec::new();
match config.pkg_names() {
Some(pkg_names) => {
// Get only the packages of the name
for name in pkg_names {
if let Some(pkg) = cache.get(name) {
packages.push(pkg);
continue;
}
// Keep track of names that were not found
not_found.push(name)
}
// Sort the packages by name so it's pretty
packages.sort_by_cached_key(|pkg| pkg.fullname(true));
},
None => {
// No package were specified by the user so get them all.
packages = cache.packages(&sort).collect::<Vec<Package>>();
},
}
// If packages are empty then there is nothing to list.
if packages.is_empty() {
return Err(anyhow!("Nothing was found to list"));
}
// We at least have one package so we can begin listing.
for pkg in packages {
if config.get_bool("all_versions", false) && pkg.has_versions() {
for version in pkg.versions() {
write!(out, "{} ", color.package(&pkg.fullname(true)))?;
list_version(&mut out, color, &pkg, &version)?;
list_description(&mut out, config, &version)?;
}
// The new line is a little weird if we print descriptions
if !config.get_bool("description", false) && !config.get_bool("summary", false) {
// New line to separate package groups
writeln!(out)?;
}
continue;
}
// Write the package name
write!(out, "{} ", color.package(&pkg.fullname(true)))?;
// The first version in the list should be the latest
if let Some(version) = pkg.versions().next() {
// There is a version! Let's format it
list_version(&mut out, color, &pkg, &version)?;
list_description(&mut out, config, &version)?;
continue;
}
// There are no versions so it must be a virtual package
list_virtual(&mut out, color, &cache, &pkg)?;
}
// Alert the user of any patterns that were not found
for name in not_found {
color.warn(&format!("'{name}' was not found"));
}
Ok(())
}
/// List a single version of a package
fn list_version(
out: &mut impl std::io::Write,
color: &Color,
pkg: &Package,
version: &Version,
) -> std::io::Result<()> {
// Add the version to the string
write!(out, "{}", color.version(&version.version()))?;
if let Some(pkg_file) = version.package_files().next() {
let archive = pkg_file
.archive()
.unwrap_or_else(|| String::from("Unknown"));
if archive != "now" {
let origin = pkg_file.origin().unwrap_or_else(|| String::from("Unknown"));
let component = pkg_file
.component()
.unwrap_or_else(|| String::from("Unknown"));
write!(out, " [{origin}/{archive} {component}]")?;
// write!(out, " [local]")?;
// Do we want to show something for this? Kind of handled elsewhere
}
}
// There is both an installed and candidate version
if let (Some(installed), Some(candidate)) = (pkg.installed(), pkg.candidate()) {
// Version is installed, check if it's upgradable
if version == &installed && version < &candidate {
return writeln!(
out,
" [Installed, Upgradable to: {}]",
color.version(&candidate.version()),
);
}
// Version isn't installed, see if it's the candidate
if version == &candidate && version > &installed {
return writeln!(
out,
" [Upgradable from: {}]",
color.version(&installed.version()),
);
}
}
// The version will not have an upgradable string, but is installed
if version.is_installed() {
// Version isn't downloadable, consider it locally installed
if !version.downloadable() {
return writeln!(out, " [Installed, Local]");
}
if pkg.is_auto_removable() {
return writeln!(out, " [Installed, Auto-Removable]");
}
if pkg.is_auto_installed() {
return writeln!(out, " [Installed, Automatic]");
}
// None of the installed conditions were met
return writeln!(out, " [Installed]");
}
// Conditions aren't met, return the package name and version
out.write_all(b"\n")
}
/// List the description or summary if requested
fn list_description(
out: &mut impl std::io::Write,
config: &Config,
version: &Version,
) -> Result<()> {
if config.get_bool("description", false) {
writeln!(out, " {}\n", version.description())?;
}
if config.get_bool("summary", false) {
writeln!(out, " {}\n", version.summary())?;
}
Ok(())
}
fn list_virtual(
out: &mut impl std::io::Write,
color: &Color,
cache: &Cache,
pkg: &Package,
) -> Result<()> {
// There are no versions so it must be a virtual package
write!(
out,
"{}{}{} ",
color.bold("("),
color.yellow("Virtual Package"),
color.bold(")")
)?;
let provides = cache
.provides(pkg, true)
.map(|p| p.fullname(true))
.collect::<Vec<String>>();
// If the virtual package provides anything we can show it
if !provides.is_empty() {
writeln!(
out,
"\n {} {}",
color.bold("Provides:"),
&provides.join(", "),
)?;
} else {
writeln!(out, "\n Nothing provides this package.")?;
}
Ok(())
}
+46
View File
@@ -0,0 +1,46 @@
use std::process::ExitCode;
use anyhow::{anyhow, Result};
mod cli;
mod colors;
mod config;
mod list;
mod util;
use crate::colors::Color;
use crate::config::Config;
use crate::list::list;
fn main() -> ExitCode {
let mut color = Color::default();
if let Err(err) = main_nala(&mut color) {
color.error(&format!("{err:?}"));
return ExitCode::FAILURE;
}
ExitCode::SUCCESS
}
fn main_nala(color: &mut Color) -> Result<()> {
let args = cli::build().get_matches();
let conf_file = match args.get_one::<String>("config") {
Some(string) => string,
None => "/etc/nala/nala.conf",
};
let mut config = Config::new(color, conf_file);
color.update_from_config(&config)?;
if *args.get_one::<bool>("license").unwrap_or(&false) {
println!("Not Yet Implemented.");
return Ok(());
}
match args.subcommand() {
Some(("list", cmd)) => {
config.load_args(cmd);
list(&config, color)?;
},
// Match other subcommands here...
_ => return Err(anyhow!("Unknown error in the argument parser")),
}
Ok(())
}
+11
View File
@@ -0,0 +1,11 @@
#[macro_export]
/// Print Debug information if the option is set
macro_rules! dprint {
($config:expr $(,)?, $($arg: tt)*) => {
if $config.debug() {
let string = std::fmt::format(std::format_args!($($arg)*));
eprintln!("DEBUG: {string}");
}
};
}
pub use dprint;