mirror of https://github.com/deadc0de6/catcli
Compare commits
241 Commits
Author | SHA1 | Date |
---|---|---|
deadc0de6 | 3a082ee5ad | 7 months ago |
deadc0de6 | 1e12caa770 | 7 months ago |
deadc0de6 | c9b4043e5f | 7 months ago |
deadc0de6 | e6ca6e2fcc | 8 months ago |
deadc0de6 | 1f1ea8d37c | 8 months ago |
deadc0de6 | eb0a628ab6 | 8 months ago |
deadc0de | d8eae3024e | 8 months ago |
Michal Kielan | bbfb60fe2e | 8 months ago |
deadc0de6 | 8788ba8b86 | 8 months ago |
deadc0de | 06a36c42fd | 8 months ago |
deadc0de6 | 6b9e00f11b | 8 months ago |
deadc0de6 | e63f5b8d79 | 8 months ago |
deadc0de6 | 6164930088 | 8 months ago |
deadc0de6 | 638085ce98 | 8 months ago |
deadc0de6 | f3c4a86b1d | 9 months ago |
deadc0de6 | 963a8b0518 | 9 months ago |
deadc0de6 | ea0cb2f9da | 9 months ago |
deadc0de6 | 340ab62d77 | 9 months ago |
deadc0de6 | ff02f9bb97 | 9 months ago |
deadc0de6 | 4aaa073603 | 9 months ago |
deadc0de6 | 8111327b53 | 9 months ago |
deadc0de6 | bed81ffee4 | 9 months ago |
deadc0de6 | e58da8b6bf | 9 months ago |
deadc0de6 | 0dcbfa94bd | 9 months ago |
deadc0de6 | 691396c96a | 9 months ago |
deadc0de6 | b7d6f21cc2 | 9 months ago |
deadc0de6 | 9326911824 | 9 months ago |
deadc0de6 | b0876f3382 | 9 months ago |
deadc0de6 | 5c06e36cc6 | 9 months ago |
deadc0de6 | 5301126d90 | 9 months ago |
deadc0de6 | 9d6bba0127 | 9 months ago |
deadc0de6 | 982f69d410 | 9 months ago |
deadc0de6 | c61ce59b35 | 9 months ago |
deadc0de6 | 07d323f0e6 | 9 months ago |
deadc0de6 | ddefc662db | 9 months ago |
deadc0de6 | 3ccaf81abd | 9 months ago |
deadc0de6 | e610273dc3 | 9 months ago |
deadc0de6 | f918ea5ae4 | 9 months ago |
deadc0de6 | 9050c6bcf6 | 9 months ago |
deadc0de6 | 9dfc4da8bf | 9 months ago |
deadc0de6 | 35d1d0d9c4 | 9 months ago |
deadc0de6 | 1c74290fa1 | 9 months ago |
deadc0de6 | ac4ba145fe | 9 months ago |
deadc0de6 | 3cf3031af2 | 1 year ago |
deadc0de | 1c2d5f378e | 1 year ago |
deadc0de6 | 57d865bb71 | 1 year ago |
deadc0de6 | b7bd2ecc5d | 1 year ago |
deadc0de6 | e2be5136d4 | 1 year ago |
deadc0de6 | b0572bf119 | 1 year ago |
deadc0de6 | 001c03ca28 | 1 year ago |
deadc0de6 | b809850fa0 | 1 year ago |
deadc0de6 | b057bde35d | 1 year ago |
deadc0de6 | 055fe8a0ca | 1 year ago |
deadc0de | 9dbc8f7342 | 1 year ago |
deadc0de6 | 7e9a6806ee | 1 year ago |
deadc0de6 | 3dbec74e96 | 1 year ago |
deadc0de | 6321d04417 | 1 year ago |
deadc0de6 | a16838299f | 1 year ago |
deadc0de6 | d00964483e | 1 year ago |
deadc0de6 | 226448b334 | 1 year ago |
deadc0de6 | 205f4e2148 | 1 year ago |
deadc0de6 | b4723b6277 | 1 year ago |
deadc0de6 | f738644a52 | 1 year ago |
deadc0de6 | 59cb7bc953 | 1 year ago |
deadc0de6 | 59333441ee | 2 years ago |
deadc0de6 | 4147e35c28 | 2 years ago |
deadc0de6 | 8de3eec0fa | 2 years ago |
deadc0de6 | b9ca63f8bc | 2 years ago |
deadc0de6 | 7d374ca2fb | 2 years ago |
deadc0de | 511a32edfa | 2 years ago |
deadc0de6 | ee2cf80d9d | 2 years ago |
deadc0de6 | 49160f2b1d | 2 years ago |
deadc0de6 | 44a13b526f | 2 years ago |
deadc0de6 | e501668102 | 2 years ago |
deadc0de6 | 611ddd64a8 | 2 years ago |
deadc0de6 | ac79557d5d | 2 years ago |
deadc0de6 | 30c1af0d15 | 2 years ago |
deadc0de6 | 8fdaba5aea | 2 years ago |
deadc0de6 | 9deed263d3 | 2 years ago |
deadc0de6 | 5e27cc40ba | 2 years ago |
deadc0de6 | b76dff46d6 | 2 years ago |
deadc0de6 | 3ac55e7294 | 2 years ago |
deadc0de6 | 0da1adcca0 | 2 years ago |
deadc0de6 | b838166a37 | 2 years ago |
deadc0de6 | 0740626ef8 | 2 years ago |
deadc0de6 | 933f6efd2d | 2 years ago |
deadc0de6 | 8ed43d5262 | 2 years ago |
deadc0de6 | c318a62941 | 2 years ago |
deadc0de6 | a69a751544 | 2 years ago |
deadc0de6 | 3c6cce6b4c | 2 years ago |
deadc0de6 | 63a052fc00 | 2 years ago |
deadc0de6 | e4cda7948c | 2 years ago |
deadc0de6 | e39088f6cb | 2 years ago |
deadc0de6 | d294aa59e1 | 2 years ago |
deadc0de6 | a333f60fa7 | 2 years ago |
deadc0de6 | 45d3b096f3 | 2 years ago |
deadc0de6 | 45c2599a95 | 2 years ago |
deadc0de6 | 599852eb7a | 2 years ago |
deadc0de6 | bdf05e9322 | 2 years ago |
deadc0de6 | bb0835cd46 | 2 years ago |
deadc0de6 | 54eb365ee4 | 2 years ago |
deadc0de6 | 50eb5cf9fd | 2 years ago |
deadc0de6 | 4a9e565e74 | 2 years ago |
deadc0de6 | 047c9bf4ab | 2 years ago |
deadc0de6 | 8659bfb348 | 2 years ago |
deadc0de6 | 04b8d6e770 | 2 years ago |
deadc0de6 | 7590ad02c3 | 2 years ago |
deadc0de | 761ea54e1e | 2 years ago |
deadc0de6 | 37d2397139 | 2 years ago |
deadc0de6 | 3fd93fe23b | 2 years ago |
deadc0de6 | 6ec86cc143 | 2 years ago |
deadc0de6 | 517aed8b7a | 2 years ago |
deadc0de6 | 58e533f69e | 2 years ago |
deadc0de6 | 7775a941a7 | 2 years ago |
deadc0de6 | 173dca1d34 | 2 years ago |
deadc0de6 | 362d824787 | 2 years ago |
deadc0de6 | d9390a3288 | 2 years ago |
deadc0de6 | 6175230892 | 2 years ago |
deadc0de6 | eb9a2d3213 | 2 years ago |
deadc0de6 | 101f2d217b | 2 years ago |
deadc0de6 | 34fb8d4894 | 2 years ago |
deadc0de6 | b3101a1dfa | 2 years ago |
deadc0de6 | caebd209e0 | 2 years ago |
deadc0de6 | d8a360d3b5 | 2 years ago |
deadc0de6 | f782078c3d | 2 years ago |
deadc0de6 | 3d13218486 | 2 years ago |
deadc0de6 | 94f419b025 | 2 years ago |
deadc0de6 | 7ca20be2e5 | 2 years ago |
deadc0de6 | c0afb0feeb | 2 years ago |
deadc0de6 | cf3e465525 | 2 years ago |
deadc0de6 | 2176ac6127 | 2 years ago |
deadc0de6 | 0422ad9229 | 2 years ago |
deadc0de | 0fa48ade13 | 2 years ago |
Matthew Walker | 49dd45e84c | 2 years ago |
deadc0de6 | 53d463cab6 | 2 years ago |
deadc0de6 | d0bda4b679 | 2 years ago |
deadc0de | 34f6bc80e8 | 2 years ago |
Matthew Walker | b08548f276 | 2 years ago |
deadc0de6 | 582fa83c8c | 2 years ago |
deadc0de6 | 8385b0c0fd | 2 years ago |
deadc0de6 | 674880d807 | 2 years ago |
deadc0de6 | 7c06889aec | 2 years ago |
deadc0de6 | bcfd8a8374 | 2 years ago |
deadc0de6 | 4be5047d40 | 2 years ago |
deadc0de6 | 50fdcb3609 | 2 years ago |
deadc0de6 | 3ed5f05e31 | 2 years ago |
deadc0de6 | f49c5364cd | 2 years ago |
deadc0de6 | 797200f376 | 2 years ago |
deadc0de6 | 0979871ce8 | 2 years ago |
deadc0de6 | ba227e2318 | 2 years ago |
deadc0de6 | f52f196a1f | 2 years ago |
deadc0de | fd942d9d10 | 2 years ago |
deadc0de6 | 5d4f22595a | 2 years ago |
deadc0de6 | 695c3525fb | 3 years ago |
deadc0de6 | 8e918d34e2 | 3 years ago |
deadc0de | 8d7e4bb3a8 | 3 years ago |
deadc0de6 | 56d40c12cc | 3 years ago |
deadc0de6 | 93f9cf560d | 3 years ago |
deadc0de6 | ecd15e66bd | 3 years ago |
deadc0de6 | a3ebd2d5a4 | 3 years ago |
deadc0de6 | 1ac6f01b6c | 3 years ago |
deadc0de6 | ee8260b524 | 3 years ago |
deadc0de6 | 47602fe32d | 3 years ago |
deadc0de6 | 1b2c52fb3e | 3 years ago |
deadc0de6 | f5b20c023f | 3 years ago |
deadc0de6 | a7a7626b13 | 3 years ago |
deadc0de6 | cef572d7eb | 3 years ago |
deadc0de6 | f181f33753 | 3 years ago |
deadc0de6 | 38bc83d3b9 | 3 years ago |
deadc0de6 | 8453468459 | 3 years ago |
deadc0de6 | 9275050c62 | 3 years ago |
deadc0de6 | bb5ded2208 | 3 years ago |
deadc0de6 | 9b77b2a360 | 3 years ago |
deadc0de6 | bd5d504997 | 3 years ago |
deadc0de6 | 613a9092c1 | 3 years ago |
deadc0de6 | 5657a0eeb4 | 3 years ago |
deadc0de6 | 031cc1a331 | 3 years ago |
deadc0de6 | 9f6f1bb3a4 | 3 years ago |
deadc0de6 | 03f82f1514 | 3 years ago |
deadc0de6 | a596f39ac2 | 3 years ago |
deadc0de6 | 7e1336c57f | 3 years ago |
deadc0de6 | ff929302c8 | 3 years ago |
deadc0de6 | 37621f6ef6 | 3 years ago |
deadc0de6 | 73fd4df412 | 3 years ago |
deadc0de6 | 51b33e6e42 | 3 years ago |
deadc0de6 | 3284113c5b | 3 years ago |
deadc0de | b4ddb50242 | 3 years ago |
deadc0de6 | 430653c89f | 4 years ago |
deadc0de6 | b38f7e9f0e | 4 years ago |
deadc0de6 | 52a05bb748 | 4 years ago |
deadc0de6 | e989ab9f31 | 4 years ago |
deadc0de6 | 271ef75f74 | 4 years ago |
deadc0de6 | d019a2717c | 4 years ago |
deadc0de6 | ee1e3de79e | 5 years ago |
deadc0de6 | 76eab12499 | 5 years ago |
deadc0de6 | 09d86f0902 | 5 years ago |
deadc0de6 | c2510924b1 | 5 years ago |
deadc0de6 | 4af351d144 | 5 years ago |
deadc0de6 | bf0ecf83cc | 5 years ago |
deadc0de6 | e10a65f91c | 5 years ago |
deadc0de6 | 7d77061141 | 5 years ago |
deadc0de6 | 818e4fa5e0 | 5 years ago |
deadc0de6 | 6e4c3784fb | 5 years ago |
deadc0de6 | 99d29c272f | 5 years ago |
deadc0de6 | 7cb0e2050a | 5 years ago |
deadc0de6 | 74e74a6f9f | 5 years ago |
deadc0de6 | a8e3c3f77d | 5 years ago |
deadc0de6 | f23549786d | 5 years ago |
deadc0de6 | c4a1320961 | 5 years ago |
deadc0de6 | 16bf5f99d4 | 5 years ago |
deadc0de6 | f0495e6d00 | 5 years ago |
deadc0de6 | 6d7f95a6ae | 5 years ago |
deadc0de6 | aec8950380 | 5 years ago |
deadc0de | 17edfcd0d0 | 5 years ago |
deadc0de6 | 5d0114d0e9 | 5 years ago |
deadc0de6 | 40da582079 | 5 years ago |
deadc0de6 | a519d0a36c | 5 years ago |
deadc0de6 | 382062ea4e | 5 years ago |
deadc0de6 | c83c94bafb | 5 years ago |
deadc0de6 | b8bd90d7e3 | 5 years ago |
deadc0de6 | 5926239d89 | 5 years ago |
deadc0de6 | 99e49624c9 | 5 years ago |
deadc0de6 | 4faf1e6dec | 5 years ago |
deadc0de6 | a60f532ff7 | 6 years ago |
deadc0de6 | cdc20e3532 | 6 years ago |
deadc0de6 | a54716353d | 6 years ago |
deadc0de6 | 5c268d212c | 6 years ago |
deadc0de6 | f97e90bf22 | 6 years ago |
deadc0de6 | 256076fbec | 6 years ago |
deadc0de6 | a6716fce4a | 6 years ago |
deadc0de6 | a75390161f | 6 years ago |
deadc0de6 | b5f894a681 | 6 years ago |
deadc0de6 | a72a1b60fc | 6 years ago |
deadc0de6 | b0793dd97b | 6 years ago |
deadc0de6 | 413161b332 | 6 years ago |
deadc0de6 | 920ab5a7de | 6 years ago |
deadc0de6 | 4c4731e3a6 | 6 years ago |
deadc0de6 | 60993abf29 | 6 years ago |
deadc0de6 | 01729ec7db | 6 years ago |
deadc0de6 | fadc43cc73 | 6 years ago |
deadc0de6 | d3b5df8be5 | 6 years ago |
@ -0,0 +1 @@
|
||||
ko_fi: deadc0de6
|
@ -0,0 +1,7 @@
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
target: 90%
|
||||
threshold: 1%
|
||||
patch: off
|
@ -0,0 +1,26 @@
|
||||
name: Release to PyPI
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
jobs:
|
||||
pypi_publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python 3.8
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.8
|
||||
- name: Install Tools
|
||||
run: |
|
||||
sudo apt update
|
||||
python -m pip install --upgrade pip
|
||||
pip install setuptools wheel twine
|
||||
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
|
||||
- name: Build and Publish
|
||||
env:
|
||||
TWINE_USERNAME: __token__
|
||||
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
|
||||
run: |
|
||||
python setup.py sdist bdist_wheel
|
||||
twine upload dist/*
|
@ -0,0 +1,29 @@
|
||||
name: tests
|
||||
on: [push, pull_request, workflow_dispatch]
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r tests-requirements.txt
|
||||
pip install -r requirements.txt
|
||||
sudo apt-get -y install shellcheck jq
|
||||
- name: Run tests
|
||||
run: |
|
||||
./tests.sh
|
||||
- name: Upload coverage reports to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
files: coverage.xml
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
@ -1,2 +1,15 @@
|
||||
*.pyc
|
||||
.coverage
|
||||
.coverage*
|
||||
coverages/
|
||||
coverage.xml
|
||||
dist/
|
||||
build/
|
||||
*.egg-info/
|
||||
*.catalog
|
||||
.vscode/
|
||||
.mypy_cache
|
||||
.pytest_cache
|
||||
__pycache__
|
||||
.pyre
|
||||
.pytype
|
||||
|
@ -0,0 +1,5 @@
|
||||
[mypy]
|
||||
strict = true
|
||||
disable_error_code = import-untyped,import-not-found
|
||||
ignore_missing_imports = True
|
||||
warn_unused_ignores = False
|
@ -1,17 +0,0 @@
|
||||
language: python
|
||||
python:
|
||||
- "3.3"
|
||||
- "3.4"
|
||||
- "3.5"
|
||||
- "3.6"
|
||||
- "nightly"
|
||||
install:
|
||||
- "pip install pycodestyle"
|
||||
- "pip install nose"
|
||||
- "pip install coverage"
|
||||
- "pip install coveralls"
|
||||
- "pip install -r requirements.txt"
|
||||
script:
|
||||
./tests.sh
|
||||
after_success:
|
||||
coveralls
|
@ -0,0 +1,44 @@
|
||||
"""
|
||||
author: deadc0de6 (https://github.com/deadc0de6)
|
||||
Copyright (c) 2022, deadc0de6
|
||||
|
||||
shell colors
|
||||
"""
|
||||
|
||||
from typing import TypeVar, Type
|
||||
|
||||
|
||||
CLASSTYPE = TypeVar('CLASSTYPE', bound='Colors')
|
||||
|
||||
|
||||
class Colors:
|
||||
"""shell colors"""
|
||||
|
||||
RED = '\033[91m'
|
||||
GREEN = '\033[92m'
|
||||
YELLOW = '\033[93m'
|
||||
PURPLE = '\033[1;35m'
|
||||
BLUE = '\033[94m'
|
||||
GRAY = '\033[0;37m'
|
||||
CYAN = '\033[36m'
|
||||
MAGENTA = '\033[95m'
|
||||
WHITE = '\033[97m'
|
||||
RESET = '\033[0m'
|
||||
EMPH = '\033[33m'
|
||||
BOLD = '\033[1m'
|
||||
UND = '\033[4m'
|
||||
|
||||
@classmethod
|
||||
def no_color(cls: Type[CLASSTYPE]) -> None:
|
||||
"""disable colors"""
|
||||
Colors.RED = ''
|
||||
Colors.GREEN = ''
|
||||
Colors.YELLOW = ''
|
||||
Colors.PURPLE = ''
|
||||
Colors.BLUE = ''
|
||||
Colors.GRAY = ''
|
||||
Colors.MAGENTA = ''
|
||||
Colors.RESET = ''
|
||||
Colors.EMPH = ''
|
||||
Colors.BOLD = ''
|
||||
Colors.UND = ''
|
@ -0,0 +1,14 @@
|
||||
"""
|
||||
author: deadc0de6 (https://github.com/deadc0de6)
|
||||
Copyright (c) 2022, deadc0de6
|
||||
|
||||
Catcli exceptions
|
||||
"""
|
||||
|
||||
|
||||
class CatcliException(Exception):
|
||||
"""generic catcli exception"""
|
||||
|
||||
|
||||
class BadFormatException(CatcliException):
|
||||
"""use of bad format"""
|
@ -0,0 +1,133 @@
|
||||
"""
|
||||
author: deadc0de6 (https://github.com/deadc0de6)
|
||||
Copyright (c) 2023, deadc0de6
|
||||
|
||||
fuse for catcli
|
||||
"""
|
||||
|
||||
import os
|
||||
from time import time
|
||||
from stat import S_IFDIR, S_IFREG
|
||||
from typing import List, Dict, Any, Optional
|
||||
try:
|
||||
import fuse
|
||||
except ModuleNotFoundError:
|
||||
pass
|
||||
|
||||
# local imports
|
||||
from catcli.noder import Noder
|
||||
from catcli.nodes import NodeTop, NodeAny
|
||||
from catcli.nodes_utils import path_to_search_all, path_to_top
|
||||
from catcli import nodes
|
||||
|
||||
|
||||
class Fuser:
|
||||
"""fuse filesystem mounter"""
|
||||
|
||||
def __init__(self, mountpoint: str,
|
||||
top: NodeTop,
|
||||
noder: Noder,
|
||||
debug: bool = False):
|
||||
"""fuse filesystem"""
|
||||
filesystem = CatcliFilesystem(top, noder)
|
||||
fuse.FUSE(filesystem,
|
||||
mountpoint,
|
||||
foreground=debug,
|
||||
nothreads=True,
|
||||
debug=debug)
|
||||
|
||||
|
||||
class CatcliFilesystem(fuse.LoggingMixIn, fuse.Operations): # type: ignore
|
||||
"""in-memory filesystem for catcli catalog"""
|
||||
|
||||
def __init__(self, top: NodeTop,
|
||||
noder: Noder):
|
||||
"""init fuse filesystem"""
|
||||
self.top = top
|
||||
self.noder = noder
|
||||
|
||||
def _get_entry(self, path: str) -> Optional[NodeAny]:
|
||||
"""return the node pointed by path"""
|
||||
path = path_to_top(path)
|
||||
found = self.noder.list(self.top, path,
|
||||
rec=False,
|
||||
fmt='native',
|
||||
raw=True)
|
||||
if found:
|
||||
return found[0]
|
||||
return None
|
||||
|
||||
def _get_entries(self, path: str) -> List[NodeAny]:
|
||||
"""return nodes pointed by path"""
|
||||
path = path_to_search_all(path)
|
||||
found = self.noder.list(self.top, path,
|
||||
rec=False,
|
||||
fmt='native',
|
||||
raw=True)
|
||||
return found
|
||||
|
||||
def _getattr(self, path: str) -> Dict[str, Any]:
|
||||
entry = self._get_entry(path)
|
||||
if not entry:
|
||||
return {}
|
||||
|
||||
maccess = time()
|
||||
mode: Any = S_IFREG
|
||||
nodesize: int = 0
|
||||
if entry.type == nodes.TYPE_ARCHIVED:
|
||||
mode = S_IFREG
|
||||
nodesize = entry.nodesize
|
||||
elif entry.type == nodes.TYPE_DIR:
|
||||
mode = S_IFDIR
|
||||
nodesize = entry.nodesize
|
||||
maccess = entry.maccess
|
||||
elif entry.type == nodes.TYPE_FILE:
|
||||
mode = S_IFREG
|
||||
nodesize = entry.nodesize
|
||||
maccess = entry.maccess
|
||||
elif entry.type == nodes.TYPE_STORAGE:
|
||||
mode = S_IFDIR
|
||||
nodesize = entry.nodesize
|
||||
maccess = entry.ts
|
||||
elif entry.type == nodes.TYPE_META:
|
||||
mode = S_IFREG
|
||||
elif entry.type == nodes.TYPE_TOP:
|
||||
mode = S_IFREG
|
||||
mode = mode | 0o777
|
||||
return {
|
||||
'st_mode': (mode), # file type
|
||||
'st_nlink': 1, # count hard link
|
||||
'st_size': nodesize,
|
||||
'st_ctime': maccess, # attr last modified
|
||||
'st_mtime': maccess, # content last modified
|
||||
'st_atime': maccess, # access time
|
||||
'st_uid': os.getuid(),
|
||||
'st_gid': os.getgid(),
|
||||
}
|
||||
|
||||
def getattr(self, path: str, _fh: Any = None) -> Dict[str, Any]:
|
||||
"""return attr of file pointed by path"""
|
||||
if path == os.path.sep:
|
||||
# mountpoint
|
||||
curt = time()
|
||||
meta = {
|
||||
'st_mode': (S_IFDIR | 0o777),
|
||||
'st_nlink': 1,
|
||||
'st_size': 0,
|
||||
'st_ctime': curt,
|
||||
'st_mtime': curt,
|
||||
'st_atime': curt,
|
||||
'st_uid': os.getuid(),
|
||||
'st_gid': os.getgid(),
|
||||
}
|
||||
return meta
|
||||
meta = self._getattr(path)
|
||||
return meta
|
||||
|
||||
def readdir(self, path: str, _fh: Any) -> List[str]:
|
||||
"""read directory content"""
|
||||
content = ['.', '..']
|
||||
entries = self._get_entries(path)
|
||||
for entry in entries:
|
||||
content.append(entry.get_name())
|
||||
return content
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,339 @@
|
||||
"""
|
||||
author: deadc0de6 (https://github.com/deadc0de6)
|
||||
Copyright (c) 2023, deadc0de6
|
||||
|
||||
Class that represents a node in the catalog tree
|
||||
"""
|
||||
# pylint: disable=W0622
|
||||
|
||||
import os
|
||||
from typing import Dict, Any, cast
|
||||
from anytree import NodeMixin
|
||||
|
||||
from catcli.exceptions import CatcliException
|
||||
from catcli.utils import fix_badchars
|
||||
|
||||
|
||||
TYPE_TOP = 'top'
|
||||
TYPE_FILE = 'file'
|
||||
TYPE_DIR = 'dir'
|
||||
TYPE_ARCHIVED = 'arc'
|
||||
TYPE_STORAGE = 'storage'
|
||||
TYPE_META = 'meta'
|
||||
|
||||
NAME_TOP = 'top'
|
||||
NAME_META = 'meta'
|
||||
|
||||
|
||||
def typcast_node(node: Any) -> None:
|
||||
"""typecast node to its sub type"""
|
||||
if node.type == TYPE_TOP:
|
||||
node.__class__ = NodeTop
|
||||
elif node.type == TYPE_FILE:
|
||||
node.__class__ = NodeFile
|
||||
elif node.type == TYPE_DIR:
|
||||
node.__class__ = NodeDir
|
||||
elif node.type == TYPE_ARCHIVED:
|
||||
node.__class__ = NodeArchived
|
||||
elif node.type == TYPE_STORAGE:
|
||||
node.__class__ = NodeStorage
|
||||
elif node.type == TYPE_META:
|
||||
node.__class__ = NodeMeta
|
||||
else:
|
||||
raise CatcliException(f"bad node: {node}")
|
||||
|
||||
|
||||
class NodeAny(NodeMixin): # type: ignore
|
||||
"""generic node"""
|
||||
|
||||
def __init__(self, # type: ignore[no-untyped-def]
|
||||
name=None,
|
||||
size=0,
|
||||
parent=None,
|
||||
children=None):
|
||||
"""build generic node"""
|
||||
super().__init__()
|
||||
self.name = name
|
||||
self.nodesize = size
|
||||
self.parent = parent
|
||||
if children:
|
||||
self.children = children
|
||||
|
||||
def get_name(self) -> str:
|
||||
"""get node name"""
|
||||
return fix_badchars(self.name)
|
||||
|
||||
def set_name(self, name: str) -> None:
|
||||
"""set node name"""
|
||||
self.name = fix_badchars(name)
|
||||
|
||||
def has_attr(self, attr: str) -> bool:
|
||||
"""return True if node has attr as attribute"""
|
||||
return attr in self.__dict__
|
||||
|
||||
def may_have_children(self) -> bool:
|
||||
"""can node contains sub"""
|
||||
raise NotImplementedError
|
||||
|
||||
def _to_str(self) -> str:
|
||||
ret = str(self.__class__) + ": " + str(self.__dict__)
|
||||
if self.children:
|
||||
ret += '\n'
|
||||
for child in self.children:
|
||||
ret += f' child => {child}\n'
|
||||
return ret
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self._to_str()
|
||||
|
||||
def get_fullpath(self) -> str:
|
||||
"""return full path to this node"""
|
||||
path = self.get_name()
|
||||
if self.parent:
|
||||
typcast_node(self.parent)
|
||||
ppath = self.parent.get_fullpath()
|
||||
path = os.path.join(ppath, path)
|
||||
return fix_badchars(path)
|
||||
|
||||
def get_rec_size(self) -> int:
|
||||
"""recursively traverse tree and return size"""
|
||||
totsize: int = self.nodesize
|
||||
for node in self.children:
|
||||
typcast_node(node)
|
||||
totsize += node.get_rec_size()
|
||||
return totsize
|
||||
|
||||
def get_storage_node(self) -> NodeMixin:
|
||||
"""recursively traverse up to find storage"""
|
||||
return None
|
||||
|
||||
def flagged(self) -> bool:
|
||||
"""is flagged"""
|
||||
if not hasattr(self, '_flagged'):
|
||||
return False
|
||||
return self._flagged
|
||||
|
||||
def flag(self) -> None:
|
||||
"""flag a node"""
|
||||
self._flagged = True # pylint: disable=W0201
|
||||
|
||||
def unflag(self) -> None:
|
||||
"""unflag node"""
|
||||
self._flagged = False # pylint: disable=W0201
|
||||
delattr(self, '_flagged')
|
||||
|
||||
|
||||
class NodeTop(NodeAny):
|
||||
"""a top node"""
|
||||
|
||||
def __init__(self, # type: ignore[no-untyped-def]
|
||||
name: str,
|
||||
children=None):
|
||||
"""build a top node"""
|
||||
super().__init__() # type: ignore[no-untyped-call]
|
||||
self.name = name
|
||||
self.type = TYPE_TOP
|
||||
self.parent = None
|
||||
if children:
|
||||
self.children = children
|
||||
|
||||
def get_fullpath(self) -> str:
|
||||
"""return full path to this node"""
|
||||
return ''
|
||||
|
||||
def may_have_children(self) -> bool:
|
||||
"""can node contains sub"""
|
||||
return True
|
||||
|
||||
def get_rec_size(self) -> int:
|
||||
"""
|
||||
recursively traverse tree and return size
|
||||
also ensure to update the size on the way
|
||||
"""
|
||||
size = super().get_rec_size()
|
||||
self.nodesize = size
|
||||
return size
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self._to_str()
|
||||
|
||||
|
||||
class NodeFile(NodeAny):
|
||||
"""a file node"""
|
||||
|
||||
def __init__(self, # type: ignore[no-untyped-def]
|
||||
name: str,
|
||||
nodesize: int,
|
||||
md5: str,
|
||||
maccess: float,
|
||||
parent=None,
|
||||
children=None):
|
||||
"""build a file node"""
|
||||
super().__init__() # type: ignore[no-untyped-call]
|
||||
self.name = name
|
||||
self.type = TYPE_FILE
|
||||
self.nodesize = nodesize
|
||||
self.md5 = md5
|
||||
self.maccess = maccess
|
||||
self.parent = parent
|
||||
if children:
|
||||
self.children = children
|
||||
|
||||
def may_have_children(self) -> bool:
|
||||
"""can node contains sub"""
|
||||
return False
|
||||
|
||||
def get_storage_node(self) -> NodeAny:
|
||||
"""recursively traverse up to find storage"""
|
||||
return cast(NodeStorage, self.ancestors[1])
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self._to_str()
|
||||
|
||||
|
||||
class NodeDir(NodeAny):
|
||||
"""a directory node"""
|
||||
|
||||
def __init__(self, # type: ignore[no-untyped-def]
|
||||
name: str,
|
||||
nodesize: int,
|
||||
maccess: float,
|
||||
parent=None,
|
||||
children=None):
|
||||
"""build a directory node"""
|
||||
super().__init__() # type: ignore[no-untyped-call]
|
||||
self.name = name
|
||||
self.type = TYPE_DIR
|
||||
self.nodesize = nodesize
|
||||
self.maccess = maccess
|
||||
self.parent = parent
|
||||
if children:
|
||||
self.children = children
|
||||
|
||||
def may_have_children(self) -> bool:
|
||||
"""can node contains sub"""
|
||||
return True
|
||||
|
||||
def get_rec_size(self) -> int:
|
||||
"""
|
||||
recursively traverse tree and return size
|
||||
also ensure to update the size on the way
|
||||
"""
|
||||
size = super().get_rec_size()
|
||||
self.nodesize = size
|
||||
return size
|
||||
|
||||
def get_storage_node(self) -> NodeAny:
|
||||
"""recursively traverse up to find storage"""
|
||||
return cast(NodeStorage, self.ancestors[1])
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self._to_str()
|
||||
|
||||
|
||||
class NodeArchived(NodeAny):
|
||||
"""an archived node"""
|
||||
|
||||
def __init__(self, # type: ignore[no-untyped-def]
|
||||
name: str,
|
||||
nodesize: int,
|
||||
md5: str,
|
||||
archive: str,
|
||||
parent=None,
|
||||
children=None):
|
||||
"""build an archived node"""
|
||||
super().__init__() # type: ignore[no-untyped-call]
|
||||
self.name = name
|
||||
self.type = TYPE_ARCHIVED
|
||||
self.nodesize = nodesize
|
||||
self.md5 = md5
|
||||
self.archive = archive
|
||||
self.parent = parent
|
||||
if children:
|
||||
self.children = children
|
||||
|
||||
def may_have_children(self) -> bool:
|
||||
"""can node contains sub"""
|
||||
return False
|
||||
|
||||
def get_storage_node(self) -> NodeAny:
|
||||
"""recursively traverse up to find storage"""
|
||||
return cast(NodeStorage, self.ancestors[1])
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self._to_str()
|
||||
|
||||
|
||||
class NodeStorage(NodeAny):
|
||||
"""a storage node"""
|
||||
|
||||
def __init__(self, # type: ignore[no-untyped-def]
|
||||
name: str,
|
||||
free: int,
|
||||
total: int,
|
||||
nodesize: int,
|
||||
ts: float,
|
||||
attr: str,
|
||||
parent=None,
|
||||
children=None):
|
||||
"""build a storage node"""
|
||||
super().__init__() # type: ignore[no-untyped-call]
|
||||
self.name = name
|
||||
self.type = TYPE_STORAGE
|
||||
self.free = free
|
||||
self.total = total
|
||||
self.attr = attr
|
||||
self.nodesize = nodesize
|
||||
self.ts = ts # pylint: disable=C0103
|
||||
self.parent = parent
|
||||
if children:
|
||||
self.children = children
|
||||
|
||||
def may_have_children(self) -> bool:
|
||||
"""can node contains sub"""
|
||||
return True
|
||||
|
||||
def get_rec_size(self) -> int:
|
||||
"""
|
||||
recursively traverse tree and return size
|
||||
also ensure to update the size on the way
|
||||
"""
|
||||
size = super().get_rec_size()
|
||||
self.nodesize = size
|
||||
return size
|
||||
|
||||
def get_storage_node(self) -> NodeAny:
|
||||
"""recursively traverse up to find storage"""
|
||||
return self
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self._to_str()
|
||||
|
||||
|
||||
class NodeMeta(NodeAny):
|
||||
"""a meta node"""
|
||||
|
||||
def __init__(self, # type: ignore[no-untyped-def]
|
||||
name: str,
|
||||
attr: Dict[str, Any],
|
||||
parent=None,
|
||||
children=None):
|
||||
"""build a meta node"""
|
||||
super().__init__() # type: ignore[no-untyped-call]
|
||||
self.name = name
|
||||
self.type = TYPE_META
|
||||
self.attr = attr
|
||||
self.parent = parent
|
||||
if children:
|
||||
self.children = children
|
||||
|
||||
def may_have_children(self) -> bool:
|
||||
"""can node contains sub"""
|
||||
return False
|
||||
|
||||
def get_rec_size(self) -> int:
|
||||
"""recursively traverse tree and return size"""
|
||||
return 0
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self._to_str()
|
@ -0,0 +1,39 @@
|
||||
"""
|
||||
author: deadc0de6 (https://github.com/deadc0de6)
|
||||
Copyright (c) 2024, deadc0de6
|
||||
|
||||
nodes helpers
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
# local imports
|
||||
from catcli import nodes
|
||||
|
||||
|
||||
def path_to_top(path: str) -> str:
|
||||
"""path pivot under top"""
|
||||
pre = f"{os.path.sep}{nodes.NAME_TOP}"
|
||||
if not path.startswith(pre):
|
||||
# prepend with top node path
|
||||
path = pre + path
|
||||
return path
|
||||
|
||||
|
||||
def path_to_search_all(path: str) -> str:
|
||||
"""path to search for all subs"""
|
||||
if not path:
|
||||
path = os.path.sep
|
||||
if not path.startswith(os.path.sep):
|
||||
path = os.path.sep + path
|
||||
pre = f"{os.path.sep}{nodes.NAME_TOP}"
|
||||
if not path.startswith(pre):
|
||||
# prepend with top node path
|
||||
path = pre + path
|
||||
# if not path.endswith(os.path.sep):
|
||||
# # ensure ends with a separator
|
||||
# path += os.path.sep
|
||||
# if not path.endswith(WILD):
|
||||
# # add wild card
|
||||
# path += WILD
|
||||
return path
|
@ -0,0 +1,81 @@
|
||||
"""
|
||||
author: deadc0de6 (https://github.com/deadc0de6)
|
||||
Copyright (c) 2024, deadc0de6
|
||||
|
||||
Class for printing nodes in csv format
|
||||
"""
|
||||
|
||||
import sys
|
||||
from typing import List
|
||||
|
||||
from catcli.nodes import NodeAny, NodeStorage, TYPE_DIR
|
||||
from catcli.utils import size_to_str, epoch_to_str
|
||||
|
||||
|
||||
class CsvPrinter:
|
||||
"""a node printer class"""
|
||||
|
||||
DEFSEP = ','
|
||||
CSV_HEADER = ('name,type,path,size,indexed_at,'
|
||||
'maccess,md5,nbfiles,free_space,'
|
||||
'total_space,meta')
|
||||
|
||||
def _print_entries(self, entries: List[str], sep: str = DEFSEP) -> None:
|
||||
line = sep.join(['"' + o + '"' for o in entries])
|
||||
if len(line) > 0:
|
||||
sys.stdout.write(f'{line}\n')
|
||||
|
||||
def print_header(self) -> None:
|
||||
"""print csv header"""
|
||||
sys.stdout.write(f'{self.CSV_HEADER}\n')
|
||||
|
||||
def print_storage(self, node: NodeStorage,
|
||||
sep: str = DEFSEP,
|
||||
raw: bool = False) -> None:
|
||||
"""print a storage node"""
|
||||
out = []
|
||||
out.append(node.get_name()) # name
|
||||
out.append(node.type) # type
|
||||
out.append('') # fake full path
|
||||
size = node.get_rec_size()
|
||||
out.append(size_to_str(size, raw=raw)) # size
|
||||
out.append(epoch_to_str(node.ts)) # indexed_at
|
||||
out.append('') # fake maccess
|
||||
out.append('') # fake md5
|
||||
out.append(str(len(node.children))) # nbfiles
|
||||
# fake free_space
|
||||
out.append(size_to_str(node.free, raw=raw))
|
||||
# fake total_space
|
||||
out.append(size_to_str(node.total, raw=raw))
|
||||
out.append(node.attr) # meta
|
||||
self._print_entries(out, sep=sep)
|
||||
|
||||
def print_node(self, node: NodeAny,
|
||||
sep: str = DEFSEP,
|
||||
raw: bool = False) -> None:
|
||||
"""print other nodes"""
|
||||
out = []
|
||||
out.append(node.get_name().replace('"', '""')) # name
|
||||
out.append(node.type) # type
|
||||
fullpath = node.get_fullpath()
|
||||
out.append(fullpath.replace('"', '""')) # full path
|
||||
|
||||
out.append(size_to_str(node.nodesize, raw=raw)) # size
|
||||
storage = node.get_storage_node()
|
||||
out.append(epoch_to_str(storage.ts)) # indexed_at
|
||||
if node.has_attr('maccess'):
|
||||
out.append(epoch_to_str(node.maccess)) # maccess
|
||||
else:
|
||||
out.append('') # fake maccess
|
||||
if node.has_attr('md5'):
|
||||
out.append(node.md5) # md5
|
||||
else:
|
||||
out.append('') # fake md5
|
||||
if node.type == TYPE_DIR:
|
||||
out.append(str(len(node.children))) # nbfiles
|
||||
else:
|
||||
out.append('') # fake nbfiles
|
||||
out.append('') # fake free_space
|
||||
out.append('') # fake total_space
|
||||
out.append('') # fake meta
|
||||
self._print_entries(out, sep=sep)
|
@ -0,0 +1,165 @@
|
||||
"""
|
||||
author: deadc0de6 (https://github.com/deadc0de6)
|
||||
Copyright (c) 2022, deadc0de6
|
||||
|
||||
Class for printing nodes in native format
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
from catcli.nodes import NodeFile, NodeDir, \
|
||||
NodeStorage, NodeAny, typcast_node
|
||||
from catcli.colors import Colors
|
||||
from catcli.logger import Logger
|
||||
from catcli.utils import fix_badchars, size_to_str, \
|
||||
epoch_to_str
|
||||
|
||||
|
||||
COLOR_STORAGE = Colors.YELLOW
|
||||
COLOR_FILE = Colors.WHITE
|
||||
COLOR_DIRECTORY = Colors.BLUE
|
||||
COLOR_ARCHIVE = Colors.PURPLE
|
||||
COLOR_TS = Colors.CYAN
|
||||
COLOR_SIZE = Colors.GREEN
|
||||
|
||||
FULLPATH_IN_NAME = True
|
||||
|
||||
|
||||
class NativePrinter:
|
||||
"""a node printer class"""
|
||||
|
||||
STORAGE = 'storage'
|
||||
ARCHIVE = 'archive'
|
||||
NBFILES = 'nbfiles'
|
||||
|
||||
def print_du(self, node: NodeAny,
|
||||
raw: bool = False) -> None:
|
||||
"""print du style"""
|
||||
typcast_node(node)
|
||||
name = node.get_fullpath()
|
||||
size = node.nodesize
|
||||
|
||||
line = size_to_str(size, raw=raw).ljust(10, ' ')
|
||||
out = f'{COLOR_SIZE}{line}{Colors.RESET}'
|
||||
out += ' '
|
||||
out += f'{COLOR_FILE}{name}{Colors.RESET}'
|
||||
sys.stdout.write(f'{out}\n')
|
||||
|
||||
def print_top(self, pre: str, name: str) -> None:
|
||||
"""print top node"""
|
||||
sys.stdout.write(f'{pre}{name}\n')
|
||||
|
||||
def print_storage(self, pre: str,
|
||||
node: NodeStorage,
|
||||
raw: bool = False) -> None:
|
||||
"""print a storage node"""
|
||||
# construct name
|
||||
name = node.get_name()
|
||||
# construct attrs
|
||||
attrs = []
|
||||
# nb files
|
||||
attrs.append(f'nbfiles:{len(node.children)}')
|
||||
# the children size
|
||||
recsize = node.get_rec_size()
|
||||
sizestr = size_to_str(recsize, raw=raw)
|
||||
attrs.append(f'totsize:{sizestr}')
|
||||
# free
|
||||
pcent = 0.0
|
||||
if node.total > 0:
|
||||
pcent = node.free * 100 / node.total
|
||||
attrs.append(f'free:{pcent:.1f}%')
|
||||
# du
|
||||
sztotal = size_to_str(node.total, raw=raw)
|
||||
szused = size_to_str(node.total - node.free, raw=raw)
|
||||
attrs.append(f'du:{szused}/{sztotal}')
|
||||
# timestamp
|
||||
if node.has_attr('ts'):
|
||||
attrs.append(f'date:{epoch_to_str(node.ts)}')
|
||||
|
||||
# print
|
||||
out = f'{pre}{Colors.UND}{self.STORAGE}{Colors.RESET}: '
|
||||
out += f'{COLOR_STORAGE}{name}{Colors.RESET}'
|
||||
if attrs:
|
||||
out += f' [{Colors.WHITE}{"|".join(attrs)}{Colors.RESET}]'
|
||||
sys.stdout.write(f'{out}\n')
|
||||
|
||||
def print_file(self, pre: str,
|
||||
node: NodeFile,
|
||||
withpath: bool = False,
|
||||
withstorage: bool = False,
|
||||
raw: bool = False) -> None:
|
||||
"""print a file node"""
|
||||
# construct name
|
||||
name = node.get_name()
|
||||
storage = node.get_storage_node()
|
||||
if withpath:
|
||||
name = node.get_fullpath()
|
||||
# construct attributes
|
||||
attrs = []
|
||||
if node.md5:
|
||||
attrs.append(f'md5:{node.md5}')
|
||||
if withstorage:
|
||||
content = Logger.get_bold_text(storage.get_name())
|
||||
attrs.append(f'storage:{content}')
|
||||
# print
|
||||
out = []
|
||||
out.append(f'{pre}')
|
||||
out.append(f'{COLOR_FILE}{name}{Colors.RESET}')
|
||||
size = 0
|
||||
if node.nodesize:
|
||||
size = node.nodesize
|
||||
line = size_to_str(size, raw=raw)
|
||||
out.append(f'{COLOR_SIZE}{line}{Colors.RESET}')
|
||||
if node.has_attr('maccess'):
|
||||
line = epoch_to_str(node.maccess)
|
||||
out.append(f'{COLOR_TS}{line}{Colors.RESET}')
|
||||
if attrs:
|
||||
out.append(f'{Colors.GRAY}[{",".join(attrs)}]{Colors.RESET}')
|
||||
|
||||
out = [x for x in out if x]
|
||||
sys.stdout.write(f'{" ".join(out)}\n')
|
||||
|
||||
def print_dir(self, pre: str,
|
||||
node: NodeDir,
|
||||
withpath: bool = False,
|
||||
withstorage: bool = False,
|
||||
withnbchildren: bool = False,
|
||||
raw: bool = False) -> None:
|
||||
"""print a directory node"""
|
||||
# construct name
|
||||
name = node.get_name()
|
||||
storage = node.get_storage_node()
|
||||
if withpath:
|
||||
name = node.get_fullpath()
|
||||
# construct attrs
|
||||
attrs = []
|
||||
if withnbchildren:
|
||||
nbchildren = len(node.children)
|
||||
attrs.append(f'{self.NBFILES}:{nbchildren}')
|
||||
if withstorage:
|
||||
attrs.append(f"storage:{Logger.get_bold_text(storage.get_name())}")
|
||||
# print
|
||||
out = []
|
||||
out.append(f'{pre}')
|
||||
out.append(f'{COLOR_DIRECTORY}{name}{Colors.RESET}')
|
||||
size = 0
|
||||
if node.nodesize:
|
||||
size = node.nodesize
|
||||
line = size_to_str(size, raw=raw)
|
||||
out.append(f'{COLOR_SIZE}{line}{Colors.RESET}')
|
||||
if node.has_attr('maccess'):
|
||||
line = epoch_to_str(node.maccess)
|
||||
out.append(f'{COLOR_TS}{line}{Colors.RESET}')
|
||||
if attrs:
|
||||
out.append(f'{Colors.GRAY}[{",".join(attrs)}]{Colors.RESET}')
|
||||
|
||||
out = [x for x in out if x]
|
||||
sys.stdout.write(f'{" ".join(out)}\n')
|
||||
|
||||
def print_archive(self, pre: str,
|
||||
name: str, archive: str) -> None:
|
||||
"""print an archive"""
|
||||
name = fix_badchars(name)
|
||||
out = f'{pre}{COLOR_ARCHIVE}{name}{Colors.RESET} '
|
||||
out += f'{Colors.GRAY}[{self.ARCHIVE}:{archive}]{Colors.RESET}'
|
||||
sys.stdout.write(f'{out}\n')
|
@ -0,0 +1,6 @@
|
||||
"""
|
||||
author: deadc0de6 (https://github.com/deadc0de6)
|
||||
Copyright (c) 2022, deadc0de6
|
||||
"""
|
||||
|
||||
__version__ = '1.0'
|
@ -1,3 +1,8 @@
|
||||
docopt; python_version >= '3.0'
|
||||
types-docopt; python_version >= '3.0'
|
||||
anytree; python_version >= '3.0'
|
||||
psutil; python_version >= '3.0'
|
||||
pyfzf; python_version >= '3.0'
|
||||
fusepy; python_version >= '3.0'
|
||||
natsort; python_version >= '3.0'
|
||||
cmd2; python_version >= '3.0'
|
||||
gnureadline; python_version >= '3.0'
|
||||
|
@ -1,11 +0,0 @@
|
||||
[metadata]
|
||||
description-file = README.md
|
||||
license_file = LICENSE
|
||||
|
||||
[bdist_wheel]
|
||||
python-tag = py3
|
||||
|
||||
[files]
|
||||
extra_files =
|
||||
LICENSE
|
||||
README.md
|
@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env bash
|
||||
# run me from the root of the package
|
||||
|
||||
source tests-ng/helper
|
||||
|
||||
|
||||
rm -f tests-ng/assets/github.catalog.json
|
||||
python3 -m catcli.catcli index -c github .github --catalog=tests-ng/assets/github.catalog.json
|
||||
|
||||
# edit catalog
|
||||
clean_catalog "tests-ng/assets/github.catalog.json"
|
||||
|
||||
# native
|
||||
python3 -m catcli.catcli ls -r -s -B --catalog=tests-ng/assets/github.catalog.json | \
|
||||
sed -e 's/free:.*%/free:0.0%/g' \
|
||||
-e 's/....-..-.. ..:..:../2023-03-09 16:20:59/g' \
|
||||
-e 's#du:[^|]* |#du:0/0 |#g' > tests-ng/assets/github.catalog.native.txt
|
||||
|
||||
# csv
|
||||
python3 -m catcli.catcli ls -r -s -B --catalog=tests-ng/assets/github.catalog.json --format=csv | \
|
||||
sed -e 's/"3","[^"]*","[^"]*",""/"3","0","0",""/g' | \
|
||||
sed 's/20..-..-.. ..:..:..//g' > tests-ng/assets/github.catalog.csv.txt
|
@ -0,0 +1,6 @@
|
||||
"github","storage","","4865","","","","3","0","0",""
|
||||
"FUNDING.yml","file","github/FUNDING.yml","17","","","0c6407a84d412c514007313fb3bca4de","","","",""
|
||||
"codecov.yml","file","github/codecov.yml","104","","","4203204f75b43cd4bf032402beb3359d","","","",""
|
||||
"workflows","dir","github/workflows","3082","","","","2","","",""
|
||||
"pypi-release.yml","file","github/workflows/pypi-release.yml","691","","","57699a7a6a03e20e864f220e19f8e197","","","",""
|
||||
"testing.yml","file","github/workflows/testing.yml","850","","","691df1a4d2f254b5cd04c152e7c6ccaf","","","",""
|
@ -0,0 +1,65 @@
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"attr": "",
|
||||
"children": [
|
||||
{
|
||||
"maccess": 1666206037.0786593,
|
||||
"md5": "0c6407a84d412c514007313fb3bca4de",
|
||||
"name": "FUNDING.yml",
|
||||
"size": 17,
|
||||
"type": "file"
|
||||
},
|
||||
{
|
||||
"maccess": 1704320710.7056112,
|
||||
"md5": "4203204f75b43cd4bf032402beb3359d",
|
||||
"name": "codecov.yml",
|
||||
"size": 104,
|
||||
"type": "file"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"maccess": 1666206037.078865,
|
||||
"md5": "57699a7a6a03e20e864f220e19f8e197",
|
||||
"name": "pypi-release.yml",
|
||||
"size": 691,
|
||||
"type": "file"
|
||||
},
|
||||
{
|
||||
"maccess": 1704403569.24789,
|
||||
"md5": "691df1a4d2f254b5cd04c152e7c6ccaf",
|
||||
"name": "testing.yml",
|
||||
"size": 850,
|
||||
"type": "file"
|
||||
}
|
||||
],
|
||||
"maccess": 1704320727.2641916,
|
||||
"name": "workflows",
|
||||
"size": 1541,
|
||||
"type": "dir"
|
||||
}
|
||||
],
|
||||
"free": 0,
|
||||
"name": "github",
|
||||
"size": 1662,
|
||||
"total": 0,
|
||||
"ts": 1704923096,
|
||||
"type": "storage"
|
||||
},
|
||||
{
|
||||
"attr": {
|
||||
"access": 1704923096,
|
||||
"access_version": "0.9.6",
|
||||
"created": 1704923096,
|
||||
"created_version": "0.9.6"
|
||||
},
|
||||
"name": "meta",
|
||||
"size": null,
|
||||
"type": "meta"
|
||||
}
|
||||
],
|
||||
"name": "top",
|
||||
"size": null,
|
||||
"type": "top"
|
||||
}
|
@ -0,0 +1,136 @@
|
||||
#!/usr/bin/env bash
|
||||
# author: deadc0de6 (https://github.com/deadc0de6)
|
||||
# Copyright (c) 2023, deadc0de6
|
||||
|
||||
set -e
|
||||
cur=$(cd "$(dirname "${0}")" && pwd)
|
||||
prev="${cur}/.."
|
||||
cd "${prev}"
|
||||
|
||||
# coverage
|
||||
bin="python3 -m catcli.catcli"
|
||||
if command -v coverage 2>/dev/null; then
|
||||
mkdir -p coverages/
|
||||
bin="coverage run -p --data-file coverages/coverage --source=catcli -m catcli.catcli"
|
||||
fi
|
||||
|
||||
echo "current dir: $(pwd)"
|
||||
echo "pythonpath: ${PYTHONPATH}"
|
||||
echo "bin: ${bin}"
|
||||
${bin} --version
|
||||
|
||||
# get the helpers
|
||||
# shellcheck source=tests-ng/helper
|
||||
source "${cur}"/helper
|
||||
echo -e "$(tput setaf 6)==> RUNNING $(basename "${BASH_SOURCE[0]}") <==$(tput sgr0)"
|
||||
|
||||
##########################################################
|
||||
# the test
|
||||
##########################################################
|
||||
|
||||
# create temp dirs
|
||||
tmpd=$(mktemp -d)
|
||||
clear_on_exit "${tmpd}"
|
||||
|
||||
catalog="${tmpd}/catalog"
|
||||
|
||||
# index
|
||||
${bin} -B index -c --catalog="${catalog}" github .github
|
||||
clean_catalog "${catalog}"
|
||||
ls -laR .github
|
||||
cat "${catalog}"
|
||||
|
||||
#cat "${catalog}"
|
||||
echo ""
|
||||
|
||||
# compare keys
|
||||
echo "[+] compare keys"
|
||||
src="tests-ng/assets/github.catalog.json"
|
||||
src_keys="${tmpd}/src-keys"
|
||||
dst_keys="${tmpd}/dst-keys"
|
||||
cat "${src}" | jq '.. | keys?' | jq '.[]' | sort > "${src_keys}"
|
||||
cat "${catalog}" | jq '.. | keys?' | jq '.[]' | sort > "${dst_keys}"
|
||||
echo "src:"
|
||||
cat "${src_keys}"
|
||||
echo "dst:"
|
||||
cat "${dst_keys}"
|
||||
diff "${src_keys}" "${dst_keys}"
|
||||
echo "ok!"
|
||||
|
||||
# compare children 1
|
||||
echo "[+] compare children 1"
|
||||
src_keys="${tmpd}/src-child1"
|
||||
dst_keys="${tmpd}/dst-child1"
|
||||
cat "${src}" | jq '. | select(.type=="top") | .children | .[].name' | sort > "${src_keys}"
|
||||
cat "${catalog}" | jq '. | select(.type=="top") | .children | .[].name' | sort > "${dst_keys}"
|
||||
echo "src:"
|
||||
cat "${src_keys}"
|
||||
echo "dst:"
|
||||
cat "${dst_keys}"
|
||||
diff "${src_keys}" "${dst_keys}"
|
||||
echo "ok!"
|
||||
|
||||
# compare children 2
|
||||
echo "[+] compare children 2"
|
||||
src_keys="${tmpd}/src-child2"
|
||||
dst_keys="${tmpd}/dst-child2"
|
||||
cat "${src}" | jq '. | select(.type=="top") | .children | .[] | select(.name=="github") | .children | .[].name' | sort > "${src_keys}"
|
||||
cat "${catalog}" | jq '. | select(.type=="top") | .children | .[] | select(.name=="github") | .children | .[].name' | sort > "${dst_keys}"
|
||||
echo "src:"
|
||||
cat "${src_keys}"
|
||||
echo "dst:"
|
||||
cat "${dst_keys}"
|
||||
diff "${src_keys}" "${dst_keys}"
|
||||
echo "ok!"
|
||||
|
||||
# compare children 3
|
||||
echo "[+] compare children 3"
|
||||
src_keys="${tmpd}/src-child3"
|
||||
dst_keys="${tmpd}/dst-child3"
|
||||
cat "${src}" | jq '. | select(.type=="top") | .children | .[] | select(.name=="github") | .children | .[] | select(.name=="workflows") | .children | .[].name' | sort > "${src_keys}"
|
||||
cat "${catalog}" | jq '. | select(.type=="top") | .children | .[] | select(.name=="github") | .children | .[] | select(.name=="workflows") | .children | .[].name' | sort > "${dst_keys}"
|
||||
echo "src:"
|
||||
cat "${src_keys}"
|
||||
echo "dst:"
|
||||
cat "${dst_keys}"
|
||||
diff "${src_keys}" "${dst_keys}"
|
||||
echo "ok!"
|
||||
|
||||
# native
|
||||
echo "[+] compare native output"
|
||||
native="${tmpd}/native.txt"
|
||||
${bin} -B ls -s -r --format=native --catalog="${catalog}" > "${native}"
|
||||
mod="${tmpd}/native.mod.txt"
|
||||
cat "${native}" | sed -e 's/free:.*%/free:0.0%/g' \
|
||||
-e 's/....-..-.. ..:..:../2023-03-09 16:20:59/g' \
|
||||
-e 's#du:[^|]* |#du:0/0 |#g' > "${mod}"
|
||||
if command -v delta >/dev/null; then
|
||||
delta -s "tests-ng/assets/github.catalog.native.txt" "${mod}"
|
||||
fi
|
||||
diff --color=always "tests-ng/assets/github.catalog.native.txt" "${mod}"
|
||||
echo "ok!"
|
||||
|
||||
# csv
|
||||
echo "[+] compare csv output"
|
||||
csv="${tmpd}/csv.txt"
|
||||
${bin} -B ls -s -r --format=csv --catalog="${catalog}" > "${csv}"
|
||||
# modify created csv
|
||||
mod="${tmpd}/csv.mod.txt"
|
||||
cat "${csv}" | \
|
||||
sed -e 's/"3","[^"]*","[^"]*",""/"3","0","0",""/g' | \
|
||||
sed 's/20..-..-.. ..:..:..//g' > "${mod}"
|
||||
# modify original
|
||||
ori="${tmpd}/ori.mod.txt"
|
||||
cat "tests-ng/assets/github.catalog.csv.txt" | \
|
||||
sed 's/....-..-.. ..:..:..//g' | \
|
||||
sed 's/"3","[^"]*","[^"]*",""/"3","0","0",""/g' > "${ori}"
|
||||
if command -v delta >/dev/null; then
|
||||
delta -s "${ori}" "${mod}"
|
||||
fi
|
||||
diff "${ori}" "${mod}"
|
||||
echo "ok!"
|
||||
|
||||
# the end
|
||||
echo "test \"$(basename "$0")\" success"
|
||||
cd "${cur}"
|
||||
exit 0
|
@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env bash
|
||||
# author: deadc0de6 (https://github.com/deadc0de6)
|
||||
# Copyright (c) 2023, deadc0de6
|
||||
|
||||
set -e
|
||||
cur=$(cd "$(dirname "${0}")" && pwd)
|
||||
prev="${cur}/.."
|
||||
cd "${prev}"
|
||||
|
||||
# coverage
|
||||
bin="python3 -m catcli.catcli"
|
||||
if command -v coverage 2>/dev/null; then
|
||||
mkdir -p coverages/
|
||||
bin="coverage run -p --data-file coverages/coverage --source=catcli -m catcli.catcli"
|
||||
fi
|
||||
|
||||
echo "current dir: $(pwd)"
|
||||
echo "pythonpath: ${PYTHONPATH}"
|
||||
echo "bin: ${bin}"
|
||||
${bin} --version
|
||||
|
||||
# get the helpers
|
||||
# shellcheck source=tests-ng/helper
|
||||
source "${cur}"/helper
|
||||
echo -e "$(tput setaf 6)==> RUNNING $(basename "${BASH_SOURCE[0]}") <==$(tput sgr0)"
|
||||
|
||||
##########################################################
|
||||
# the test
|
||||
##########################################################
|
||||
|
||||
# create temp dirs
|
||||
tmpd=$(mktemp -d)
|
||||
clear_on_exit "${tmpd}"
|
||||
|
||||
catalog="${tmpd}/catalog"
|
||||
|
||||
# index
|
||||
${bin} -B index -c -f --catalog="${catalog}" github1 .github
|
||||
${bin} -B index -c -f --catalog="${catalog}" github2 .github
|
||||
clean_catalog "${catalog}"
|
||||
|
||||
#cat "${catalog}"
|
||||
echo ""
|
||||
|
||||
${bin} -B ls -r --catalog="${catalog}"
|
||||
|
||||
echo "finding \"testing.yml\""
|
||||
${bin} -B find --catalog="${catalog}" testing.yml
|
||||
cnt=$(${bin} -B find --catalog="${catalog}" testing.yml | wc -l)
|
||||
[ "${cnt}" != "2" ] && echo "should return 2 (not ${cnt})" && exit 1
|
||||
|
||||
echo "finding \"*.yml\""
|
||||
${bin} -B find --catalog="${catalog}" '*.yml'
|
||||
cnt=$(${bin} -B find --catalog="${catalog}" '*.yml' | wc -l)
|
||||
[ "${cnt}" != "8" ] && echo "should return 8 (not ${cnt})" && exit 1
|
||||
|
||||
# the end
|
||||
echo ""
|
||||
echo "test \"$(basename "$0")\" success"
|
||||
cd "${cur}"
|
||||
exit 0
|
@ -0,0 +1,76 @@
|
||||
# author: deadc0de6 (https://github.com/deadc0de6)
|
||||
# Copyright (c) 2023, deadc0de6
|
||||
#
|
||||
# file to be sourced from test scripts
|
||||
#
|
||||
|
||||
declare -a to_be_cleared
|
||||
|
||||
# add a file/directory to be cleared
|
||||
# on exit
|
||||
#
|
||||
# $1: file path to clear
|
||||
clear_on_exit()
|
||||
{
|
||||
local len="${#to_be_cleared[*]}"
|
||||
# shellcheck disable=SC2004
|
||||
to_be_cleared[${len}]="$1"
|
||||
if [ "${len}" = "0" ]; then
|
||||
# set trap
|
||||
trap on_exit EXIT
|
||||
fi
|
||||
}
|
||||
|
||||
# clear catalog stuff for testing
|
||||
# $1: catalog path
|
||||
clean_catalog()
|
||||
{
|
||||
sed -i 's/"free": .*,/"free": 0,/g' "${1}"
|
||||
sed -i 's/"total": .*,/"total": 0,/g' "${1}"
|
||||
}
|
||||
|
||||
# clear files
|
||||
on_exit()
|
||||
{
|
||||
for i in "${to_be_cleared[@]}"; do
|
||||
rm -rf "${i}"
|
||||
done
|
||||
}
|
||||
|
||||
# osx tricks
|
||||
# brew install coreutils gnu-sed
|
||||
if [[ $OSTYPE == 'darwin'* ]]; then
|
||||
mktemp() {
|
||||
gmktemp "$@"
|
||||
}
|
||||
stat() {
|
||||
gstat "$@"
|
||||
}
|
||||
sed() {
|
||||
gsed "$@"
|
||||
}
|
||||
wc() {
|
||||
gwc "$@"
|
||||
}
|
||||
date() {
|
||||
gdate "$@"
|
||||
}
|
||||
chmod() {
|
||||
gchmod "$@"
|
||||
}
|
||||
readlink() {
|
||||
greadlink "$@"
|
||||
}
|
||||
realpath() {
|
||||
grealpath "$@"
|
||||
}
|
||||
|
||||
export -f mktemp
|
||||
export -f stat
|
||||
export -f sed
|
||||
export -f wc
|
||||
export -f date
|
||||
export -f chmod
|
||||
export -f readlink
|
||||
export -f realpath
|
||||
fi
|
@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env bash
|
||||
# author: deadc0de6 (https://github.com/deadc0de6)
|
||||
# Copyright (c) 2024, deadc0de6
|
||||
|
||||
set -e
|
||||
cur=$(cd "$(dirname "${0}")" && pwd)
|
||||
prev="${cur}/.."
|
||||
cd "${prev}"
|
||||
|
||||
# coverage
|
||||
bin="python3 -m catcli.catcli"
|
||||
if command -v coverage 2>/dev/null; then
|
||||
mkdir -p coverages/
|
||||
bin="coverage run -p --data-file coverages/coverage --source=catcli -m catcli.catcli"
|
||||
fi
|
||||
|
||||
echo "current dir: $(pwd)"
|
||||
echo "pythonpath: ${PYTHONPATH}"
|
||||
echo "bin: ${bin}"
|
||||
${bin} --version
|
||||
|
||||
# get the helpers
|
||||
# shellcheck source=tests-ng/helper
|
||||
source "${cur}"/helper
|
||||
echo -e "$(tput setaf 6)==> RUNNING $(basename "${BASH_SOURCE[0]}") <==$(tput sgr0)"
|
||||
|
||||
##########################################################
|
||||
# the test
|
||||
##########################################################
|
||||
|
||||
# create temp dirs
|
||||
tmpd=$(mktemp -d)
|
||||
clear_on_exit "${tmpd}"
|
||||
|
||||
catalog="${tmpd}/catalog"
|
||||
|
||||
# index
|
||||
${bin} -B index -c -f --catalog="${catalog}" github1 .github
|
||||
${bin} -B index -c -f --catalog="${catalog}" github2 .github
|
||||
clean_catalog "${catalog}"
|
||||
|
||||
#cat "${catalog}"
|
||||
echo ""
|
||||
|
||||
${bin} -B ls -r --catalog="${catalog}"
|
||||
|
||||
${bin} -B ls --catalog="${catalog}" 'github1/*.yml'
|
||||
cnt=$(${bin} -B ls --catalog="${catalog}" 'github1/*.yml' | wc -l)
|
||||
[ "${cnt}" != "2" ] && echo "should return 2 (not ${cnt})" && exit 1
|
||||
|
||||
${bin} -B ls --catalog="${catalog}" 'github*/*.yml'
|
||||
cnt=$(${bin} -B ls --catalog="${catalog}" 'github*/*.yml' | wc -l)
|
||||
[ "${cnt}" != "4" ] && echo "should return 4 (not ${cnt})" && exit 1
|
||||
|
||||
# the end
|
||||
echo ""
|
||||
echo "test \"$(basename "$0")\" success"
|
||||
cd "${cur}"
|
||||
exit 0
|
@ -0,0 +1,80 @@
|
||||
#!/usr/bin/env bash
|
||||
# author: deadc0de6 (https://github.com/deadc0de6)
|
||||
# Copyright (c) 2021, deadc0de6
|
||||
|
||||
set -e
|
||||
cur=$(cd "$(dirname "${0}")" && pwd)
|
||||
prev="${cur}/.."
|
||||
cd "${prev}"
|
||||
|
||||
# coverage
|
||||
bin="python3 -m catcli.catcli"
|
||||
if command -v coverage 2>/dev/null; then
|
||||
mkdir -p coverages/
|
||||
bin="coverage run -p --data-file coverages/coverage --source=catcli -m catcli.catcli"
|
||||
fi
|
||||
|
||||
echo "current dir: $(pwd)"
|
||||
echo "pythonpath: ${PYTHONPATH}"
|
||||
echo "bin: ${bin}"
|
||||
${bin} --version
|
||||
|
||||
# get the helpers
|
||||
# shellcheck source=tests-ng/helper
|
||||
source "${cur}"/helper
|
||||
echo -e "$(tput setaf 6)==> RUNNING $(basename "${BASH_SOURCE[0]}") <==$(tput sgr0)"
|
||||
|
||||
##########################################################
|
||||
# the test
|
||||
##########################################################
|
||||
|
||||
# create temp dirs
|
||||
tmpd=$(mktemp -d)
|
||||
clear_on_exit "${tmpd}"
|
||||
tmpu="${tmpd}/dir2"
|
||||
mkdir -p "${tmpu}"
|
||||
|
||||
catalog="${tmpd}/catalog"
|
||||
|
||||
mkdir -p "${tmpd}/dir"
|
||||
echo "abc" > "${tmpd}/dir/a"
|
||||
|
||||
# index
|
||||
${bin} -B index --catalog="${catalog}" dir "${tmpd}/dir"
|
||||
${bin} -B ls --catalog="${catalog}"
|
||||
|
||||
# get attributes
|
||||
freeb=$(${bin} -B ls --catalog="${catalog}" | grep free: | sed 's/^.*,free:\([^ ]*\).*$/\1/g')
|
||||
dub=$(${bin} -B ls --catalog="${catalog}" | grep du: | sed 's/^.*,du:\([^ ]*\).*$/\1/g')
|
||||
dateb=$(${bin} -B ls --catalog="${catalog}" | grep date: | sed 's/^.*,date: \(.*\)$/\1/g')
|
||||
echo "before: free:${freeb} | du:${dub} | date:${dateb}"
|
||||
|
||||
# change content
|
||||
echo "abc" >> "${tmpd}/dir/a"
|
||||
echo "abc" > "${tmpd}/dir/b"
|
||||
|
||||
# move dir
|
||||
cp -r "${tmpd}/dir" "${tmpu}/"
|
||||
|
||||
# sleep to force date change
|
||||
sleep 1
|
||||
|
||||
# update
|
||||
${bin} -B update -f --catalog="${catalog}" dir "${tmpu}/dir"
|
||||
${bin} -B ls --catalog="${catalog}"
|
||||
|
||||
# get new attributes
|
||||
freea=$(${bin} -B ls --catalog="${catalog}" | grep free: | sed 's/^.*,free:\([^ ]*\).*$/\1/g')
|
||||
dua=$(${bin} -B ls --catalog="${catalog}" | grep du: | sed 's/^.*,du:\([^ ]*\).*$/\1/g')
|
||||
datea=$(${bin} -B ls --catalog="${catalog}" | grep date: | sed 's/^.*,date: \(.*\)$/\1/g')
|
||||
echo "after: free:${freea} | du:${dua} | date:${datea}"
|
||||
|
||||
# test they are all different
|
||||
[ "${freeb}" = "${freea}" ] && echo "WARNING free didn't change!"
|
||||
[ "${dub}" = "${dua}" ] && echo "WARNING du didn't change!"
|
||||
[ "${dateb}" = "${datea}" ] && echo "WARNING date didn't change!" && exit 1
|
||||
|
||||
# the end
|
||||
echo "test \"$(basename "$0")\" success"
|
||||
cd "${cur}"
|
||||
exit 0
|
@ -0,0 +1,8 @@
|
||||
pycodestyle; python_version >= '3.0'
|
||||
pyflakes; python_version >= '3.0'
|
||||
nose2; python_version >= '3.0'
|
||||
coverage; python_version >= '3.0'
|
||||
pylint; python_version > '3.0'
|
||||
mypy; python_version > '3.0'
|
||||
pytest; python_version > '3.0'
|
||||
pytype; python_version > '3.0'
|
@ -0,0 +1,29 @@
|
||||
"""
|
||||
author: deadc0de6 (https://github.com/deadc0de6)
|
||||
Copyright (c) 2017, deadc0de6
|
||||
|
||||
Basic unittest for ls
|
||||
"""
|
||||
|
||||
import unittest
|
||||
|
||||
from catcli.decomp import Decomp
|
||||
|
||||
|
||||
class TestDecomp(unittest.TestCase):
|
||||
"""test ls"""
|
||||
|
||||
def test_list(self):
|
||||
"""test decomp formats"""
|
||||
dec = Decomp()
|
||||
formats = dec.get_formats()
|
||||
self.assertTrue('zip' in formats)
|
||||
|
||||
|
||||
def main():
|
||||
"""entry point"""
|
||||
unittest.main()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Loading…
Reference in New Issue