├── .vim
└── coc-settings.json
├── tests
├── __access.py
├── test_battery.py
├── test_decorateddata.py
├── test_cli.py
├── test_disk.py
├── test_charts.py
├── test_files.py
└── test_tools.py
├── requirements.txt
├── LICENSE
├── .github
└── workflows
│ └── python-app.yml
├── setup.py
├── vizex
├── vizexdu
│ ├── battery.py
│ ├── cpu.py
│ ├── charts.py
│ └── disks.py
├── vizextree
│ └── viztree.py
├── tools.py
├── vizexdf
│ └── files.py
└── cli.py
├── .gitignore
├── README.md
└── README.html
/.vim/coc-settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "python.linting.pylintEnabled": false,
3 | "python.linting.flake8Enabled": true,
4 | "python.linting.enabled": true
5 | }
--------------------------------------------------------------------------------
/tests/__access.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | import inspect
4 |
5 | # Creates path to the main module files
6 | def ADD_PATH():
7 | currentdir = os.path.dirname(
8 | os.path.abspath(
9 | inspect.getfile(inspect.currentframe())
10 | )
11 | )
12 | parentdir = os.path.dirname(currentdir)
13 | sys.path.insert(0, parentdir)
14 |
15 | if __name__ == '__main__':
16 | ADD_PATH()
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | attrs>=20.2.0
2 | build>=0.3.1.post1
3 | click>=7.1.2
4 | colored>=1.4.2
5 | iniconfig>=1.0.1
6 | isort>=5.8.0
7 | lazy-object-proxy>=1.6.0
8 | mccabe>=0.6.1
9 | numpy>=1.19.2
10 | packaging>=20.4
11 | pandas>=1.1.3
12 | pluggy>=0.13.1
13 | psutil>=5.7.2
14 | py>=1.9.0
15 | pytest>=6.1.1
16 | python-dateutil>=2.8.1
17 | python-magic>=0.4.18
18 | pytz>=2020.1
19 | six>=1.15.0
20 | tabulate>=0.8.7
21 | toml>=0.10.1
22 |
--------------------------------------------------------------------------------
/tests/test_battery.py:
--------------------------------------------------------------------------------
1 | # add path to the main package and test battery.py
2 | if __name__ == '__main__':
3 | from __access import ADD_PATH
4 | ADD_PATH()
5 |
6 | import unittest
7 | import psutil
8 |
9 | from vizexdu.battery import Battery
10 |
11 |
12 | class TestBattery(unittest.TestCase):
13 | """ Test battery module """
14 |
15 | def test_Battery_constructor(self):
16 | if not (has_battery := psutil.sensors_battery()):
17 | with self.assertRaises(Exception):
18 | Battery()
19 | else:
20 | self.assertTrue(has_battery.percent > 0)
21 |
22 | def test_create_details_text(self):
23 | if not psutil.sensors_battery():
24 | pass
25 | else:
26 | self.assertTrue(isinstance(Battery().create_details_text(), str))
27 |
28 |
29 | if __name__ == '__main__':
30 | unittest.main()
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Beka Modebadze
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.github/workflows/python-app.yml:
--------------------------------------------------------------------------------
1 | # This workflow will install Python dependencies, run tests and lint with a single version of Python
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
3 |
4 | name: Python application
5 |
6 | on:
7 | push:
8 | branches: [ master ]
9 | pull_request:
10 | branches: [ master ]
11 |
12 | permissions:
13 | contents: read
14 |
15 | jobs:
16 | build:
17 |
18 | runs-on: ubuntu-latest
19 |
20 | steps:
21 | - uses: actions/checkout@v3
22 | - name: Set up Python 3.8
23 | uses: actions/setup-python@v3
24 | with:
25 | python-version: "3.8"
26 | - name: Install dependencies
27 | run: |
28 | python -m pip install --upgrade pip
29 | pip install flake8 pytest
30 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
31 | - name: Lint with flake8
32 | run: |
33 | # stop the build if there are Python syntax errors or undefined names
34 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
35 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
36 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
37 | - name: Test with pytest
38 | run: |
39 | pytest
40 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup, find_packages
2 |
3 | with open('README.md', 'r') as rm:
4 | long_description = rm.read()
5 |
6 | with open('requirements.txt') as r:
7 | requirements = r.read().splitlines()
8 |
9 | setup(
10 | name='vizex',
11 | version='2.1.1',
12 | author='Beka Modebadze',
13 | author_email='bexx.modd@gmail.com',
14 | description='UNIX/Linux Terminal program to graphically display the disk space usage and/or directory data',
15 | long_description=long_description,
16 | long_description_content_type ='text/markdown',
17 | url='https://github.com/bexxmodd/vizex',
18 | package_dir = {'': 'vizex'},
19 | py_modules=[
20 | 'cli', 'vizexdu/disks', 'tools',
21 | 'vizexdu/charts', 'vizexdu/battery', 'vizexdu/cpu',
22 | 'vizexdf/files', 'vizextree/viztree'
23 | ],
24 | packages = find_packages(where='vizex'),
25 | classifiers=[
26 | "Programming Language :: Python :: 3.8",
27 | "Programming Language :: Python :: 3.9",
28 | "License :: OSI Approved :: MIT License",
29 | "Operating System :: POSIX",
30 | ],
31 | install_requires=requirements,
32 | python_requires='>=3.8',
33 | entry_points='''
34 | [console_scripts]
35 | vizex=cli:disk_usage
36 | vizexdf=cli:dirs_files
37 | vizextree=cli:print_tree
38 | '''
39 | )
40 |
--------------------------------------------------------------------------------
/tests/test_decorateddata.py:
--------------------------------------------------------------------------------
1 | # add path to the main package and test decorateddata.py
2 | if __name__ == '__main__':
3 | from __access import ADD_PATH
4 | ADD_PATH()
5 |
6 |
7 | import unittest
8 | import unittest.mock
9 |
10 | from tools import DecoratedData
11 |
12 |
13 | class TestTools(unittest.TestCase):
14 |
15 | def test_decorated_data_constructor(self):
16 | testing = DecoratedData(33, 'Thirteen Three')
17 | self.assertEqual(33, testing.size,
18 | msg='int value was not initialized properly')
19 | self.assertEqual('Thirteen Three', testing.to_string,
20 | msg='to_string of a object was not initialized properly')
21 |
22 | def test_decorated_data_str_printing(self):
23 | testing = DecoratedData(8, 'E1gh7@')
24 | self.assertEqual('E1gh7@', str(testing))
25 |
26 | def test_decorated_data_equal(self):
27 | test_a = DecoratedData(5, 'five')
28 | test_b = DecoratedData(5, 'five')
29 | self.assertEqual(test_a, test_b)
30 |
31 | def test_decorated_notequal(self):
32 | test_a = DecoratedData(7, 'five')
33 | test_b = DecoratedData(5, 'seven')
34 | self.assertNotEqual(test_a, test_b)
35 |
36 | def test_decorated_greater(self):
37 | test_a = DecoratedData(7, 'five')
38 | test_b = DecoratedData(5, 'seven')
39 | self.assertGreater(test_a, test_b)
40 |
41 | def test_decorated_greater_equal(self):
42 | test_a = DecoratedData(7, 'five')
43 | test_b = DecoratedData(5, 'seven')
44 | self.assertGreaterEqual(test_a, test_b)
45 |
46 | def test_decorated_less(self):
47 | test_a = DecoratedData(3, 'three')
48 | test_b = DecoratedData(5, 'five and three')
49 | self.assertLess(test_a, test_b)
50 |
51 | def test_decorated_less_equal(self):
52 | test_a = DecoratedData(6, 'three')
53 | test_b = DecoratedData(6, 'x and three')
54 | self.assertLessEqual(test_a, test_b)
55 |
56 |
57 | if __name__ == '__main__':
58 | unittest.main()
59 |
--------------------------------------------------------------------------------
/vizex/vizexdu/battery.py:
--------------------------------------------------------------------------------
1 | '''
2 | Battery module for vizex
3 | '''
4 |
5 | import datetime
6 | import psutil
7 |
8 | from math import ceil
9 | from .charts import Chart, HorizontalBarChart, Options
10 |
11 |
12 | class Battery:
13 | """Personalize and visualize the Battery usage in the terminal"""
14 |
15 | def __init__(self) -> None:
16 | """ Create a new Battery Object """
17 | self._battery = psutil.sensors_battery()
18 | if not self._battery:
19 | raise Exception("Battery information currently unavailable")
20 |
21 | def print_charts(self, options: Options = None) -> None:
22 | """ Prints battery information """
23 | if not options:
24 | options = Options()
25 | chart = HorizontalBarChart(options)
26 | self.print_battery_chart(chart)
27 |
28 | def print_battery_chart(self, chart: Chart) -> None:
29 | """ Prints battery information chart """
30 | post_graph_text = str(
31 | ceil(100 * self._battery.percent) / 100) + "%"
32 | footer = self.create_details_text()
33 |
34 | chart.chart(
35 | post_graph_text=post_graph_text,
36 | pre_graph_text=None,
37 | title="Battery",
38 | footer=footer,
39 | maximum=100,
40 | current=self._battery.percent,
41 | )
42 | print()
43 |
44 | def create_details_text(self) -> str:
45 | """ Format more information about the battery """
46 | time_left = datetime.timedelta(seconds=self._battery.secsleft)
47 |
48 | if plugged := self._battery.power_plugged:
49 | return format("Charging")
50 | return f"Plugged in: {plugged}\tDischarging: {time_left}\t"
51 |
52 |
53 | if __name__ == "__main__":
54 | battery = Battery()
55 | battery.print_charts()
56 | # Example output:
57 |
58 | # Battery
59 | # █████████████▒░░░░░░░░░░░░░░░░░░░░░░░░░ 34.23%
60 | # Charging
61 |
62 | # Battery
63 | # █████████████▒░░░░░░░░░░░░░░░░░░░░░░░░░ 34.51%
64 | # Plugged in: False Discharging: 0:48:24
65 |
--------------------------------------------------------------------------------
/tests/test_cli.py:
--------------------------------------------------------------------------------
1 | # add path to the main package and test cli.py
2 | if __name__ == '__main__':
3 | from __access import ADD_PATH
4 | ADD_PATH()
5 |
6 |
7 | import io
8 | import random
9 | import psutil
10 | import unittest
11 | import unittest.mock
12 |
13 | from click.testing import CliRunner
14 | from cli import dirs_files, disk_usage
15 |
16 |
17 | class TestCli(unittest.TestCase):
18 |
19 | def test_dirs_files(self):
20 | try:
21 | runner = CliRunner()
22 | result = runner.invoke(dirs_files, ['-ds', 'name'])
23 | self.assertTrue(result.exit_code == 0)
24 | self.assertTrue('name' in result.output)
25 | self.assertTrue('last modified' in result.output)
26 | self.assertTrue('size' in result.output)
27 | self.assertTrue('type' in result.output)
28 | except Exception as e:
29 | self.fail(f'Exception occured when calling vizexdf with desc and sort name options {e}')
30 |
31 | def test_dirs_files_help(self):
32 | try:
33 | runner = CliRunner()
34 | result = runner.invoke(dirs_files, ['--help'])
35 | self.assertTrue(result.exit_code == 0)
36 | self.assertTrue('Made by: Beka Modebadze' in result.output)
37 | except Exception as e:
38 | self.fail(f'Exception occured when calling vizexdf\'s --help {e}')
39 |
40 | def test_disk_usage(self):
41 | try:
42 | runner = CliRunner()
43 | result = runner.invoke(disk_usage, ['--mark', '@'])
44 | self.assertTrue(result.exit_code == 0)
45 | self.assertTrue('root' in result.output)
46 | self.assertTrue('Total' in result.output)
47 | self.assertTrue('Used' in result.output)
48 | self.assertTrue('Free' in result.output)
49 | self.assertTrue('@' in result.output)
50 | except Exception as e:
51 | self.fail(f'Exception occured when calling vizex {e}')
52 |
53 | def test_disk_usage_help(self):
54 | try:
55 | runner = CliRunner()
56 | result = runner.invoke(disk_usage, ['--help'])
57 | self.assertTrue(result.exit_code == 0)
58 | self.assertTrue('Made by: Beka Modebadze' in result.output)
59 | except Exception as e:
60 | self.fail(f'Exception occured when calling vizex --help {e}')
61 |
62 |
63 | if __name__ == '__main__':
64 | unittest.main()
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | pip-wheel-metadata/
24 | share/python-wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 | MANIFEST
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .nox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *.cover
50 | *.py,cover
51 | .hypothesis/
52 | .pytest_cache/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | target/
76 |
77 | # Jupyter Notebook
78 | .ipynb_checkpoints
79 |
80 | # IPython
81 | profile_default/
82 | ipython_config.py
83 |
84 | # pyenv
85 | .python-version
86 |
87 | # pipenv
88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
91 | # install all needed dependencies.
92 | #Pipfile.lock
93 |
94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
95 | __pypackages__/
96 |
97 | # Celery stuff
98 | celerybeat-schedule
99 | celerybeat.pid
100 |
101 | # SageMath parsed files
102 | *.sage.py
103 |
104 | # Environments
105 | .env
106 | .venv
107 | env/
108 | venv/
109 | ENV/
110 | env.bak/
111 | venv.bak/
112 |
113 | # Spyder project settings
114 | .spyderproject
115 | .spyproject
116 |
117 | # Rope project settings
118 | .ropeproject
119 |
120 | # mkdocs documentation
121 | /site
122 |
123 | # mypy
124 | .mypy_cache/
125 | .dmypy.json
126 | dmypy.json
127 |
128 | # Pyre type checker
129 | .pyre/
130 |
131 | # Manually excluded
132 | beta.py
133 | timetest.py
134 | .vscode
135 | .idea
136 | profile_vixez1.txt
137 |
--------------------------------------------------------------------------------
/vizex/vizextree/viztree.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | from itertools import islice
3 | from tools import find_word
4 |
5 | from colored import fg, attr, stylize
6 |
7 |
8 | SPACE = ' '
9 | BRANCH = '│ '
10 | TEE = '├── '
11 | LEAF = '└── '
12 |
13 | FILES_COUNT = 0
14 | DIRS_COUNT = 0
15 |
16 |
17 | def construct_tree(dir_path: str, level: int, only_dirs: bool = False,
18 | max_length: int = 1000) -> None:
19 | dir_path = Path(dir_path)
20 | print_colored(str(dir_path), 'red', 'bold')
21 | iterator = generate_iterable(
22 | dir_path, level=level, only_dirs=only_dirs)
23 | for line in islice(iterator, max_length):
24 | filter_project_dirs(line)
25 | if next(iterator, None):
26 | print(f'... length limit of {max_length} is reached! counted:')
27 | print(f'\n{stylize(DIRS_COUNT, fg(172))} directories'
28 | + (f', {stylize(FILES_COUNT, fg(12))} files' if FILES_COUNT else ''))
29 |
30 |
31 | def generate_iterable(dir_path: Path, prefix: str = '',
32 | level=-1, only_dirs: bool = False) -> str:
33 | global FILES_COUNT, DIRS_COUNT
34 | if not level:
35 | return # stop iterating
36 |
37 | if only_dirs:
38 | contents = [d for d in dir_path.iterdir() if d.is_dir()]
39 | else:
40 | contents = list(dir_path.iterdir())
41 |
42 | pointers = [TEE] * (len(contents) - 1) + [LEAF]
43 | for pointer, path in zip(pointers, contents):
44 | if path.is_dir():
45 | yield prefix + pointer + path.name
46 | DIRS_COUNT += 1
47 | extension = BRANCH if pointer == TEE else SPACE
48 | yield from generate_iterable(
49 | path, prefix=prefix + extension, level=level - 1)
50 | elif not only_dirs:
51 | yield prefix + pointer + path.name
52 | FILES_COUNT += 1
53 |
54 |
55 | def filter_project_dirs(line: str) -> None:
56 | if find_word('test', line) or find_word('tests', line):
57 | print_colored(line, 'sky_blue_2', 'bold')
58 | elif find_word('src', line) or find_word('main', line):
59 | print_colored(line, 'chartreuse_2b', 'bold')
60 | elif find_word('venv', line):
61 | print_colored(line, 'purple_1a', 'dim')
62 | elif is_hidden(line):
63 | print_colored(line, 'dark_gray', 'dim')
64 | else:
65 | print(line)
66 |
67 |
68 | def print_colored(line: str, color: str = None,
69 | style: str = None) -> None:
70 | for c in line:
71 | if c not in ('├', '─', '│', '└'):
72 | print(stylize(c, attr(style) + fg(color)), end='')
73 | else:
74 | print(c, end='')
75 | print()
76 |
77 |
78 | def is_hidden(line: str) -> bool:
79 | for c in line:
80 | if c in ('├', '─', '│', '└', ' '):
81 | continue
82 | return c == '.'
83 |
84 |
85 | if __name__ == '__main__':
86 | construct_tree("../", level=2)
87 |
--------------------------------------------------------------------------------
/tests/test_disk.py:
--------------------------------------------------------------------------------
1 | # add path to the main package and test disks.py
2 | if __name__ == '__main__':
3 | from __access import ADD_PATH
4 | ADD_PATH()
5 |
6 | import io
7 | import random
8 | import psutil
9 | import unittest
10 |
11 | from unittest.mock import MagicMock, call
12 | from colored import fg, attr, stylize
13 | from vizexdu.charts import Options
14 |
15 |
16 | @unittest.skip("Tests need to be updated to suit the changes in the disks.py module")
17 | class TestDiskUsage(unittest.TestCase):
18 | """Test DiskUsage class"""
19 |
20 | def test_grab_partitions(self):
21 | disks = self.du.grab_partitions(self.du.exclude)
22 | self.assertIsInstance(disks, dict,
23 | msg='Function should return dict')
24 | # Test that proper keys are present
25 | compare_keys = ['total',
26 | 'used',
27 | 'free',
28 | 'percent',
29 | 'fstype',
30 | 'mountpoint']
31 | for disk in disks:
32 | self.assertIsNotNone(disk)
33 | keys = [key for key in disks[disk].keys()]
34 | self.assertListEqual(keys, compare_keys,
35 | msg=f'{keys} are not present')
36 |
37 | # Test that they have positive integer values
38 | for disk in disks:
39 | self.assertGreaterEqual(disks[disk]['total'], 1)
40 | self.assertGreaterEqual(disks[disk]['used'], 1)
41 | self.assertGreaterEqual(disks[disk]['free'], 0)
42 | self.assertGreaterEqual(disks[disk]['percent'], 0)
43 | self.assertIsInstance(disks[disk]['fstype'], str)
44 | self.assertIsInstance(disks[disk]['mountpoint'], str)
45 |
46 | def test_grab_specific_disk(self):
47 | disks = self.du.grab_specific_disk('/home/')
48 | self.assertIsInstance(disks, dict,
49 | msg='Function should return dict')
50 | # Test that proper keys are present
51 | compare_keys = ['total',
52 | 'used',
53 | 'free',
54 | 'percent',
55 | 'fstype',
56 | 'mountpoint']
57 | for disk in disks:
58 | self.assertIsNotNone(disks)
59 | keys = [key for key in disks[disk].keys()]
60 | self.assertListEqual(keys, compare_keys,
61 | msg=f'{keys} are not present')
62 |
63 | # Test that they have positive integer values
64 | for disk in disks:
65 | self.assertGreaterEqual(disks[disk]['total'], 1)
66 | self.assertGreaterEqual(disks[disk]['used'], 1)
67 | self.assertGreaterEqual(disks[disk]['free'], 0)
68 | self.assertGreaterEqual(disks[disk]['percent'], 0)
69 | self.assertIsInstance(disks[disk]['fstype'], str)
70 | self.assertIsInstance(disks[disk]['mountpoint'], str)
71 |
72 |
73 | if __name__ == '__main__':
74 | unittest.main()
75 |
76 |
--------------------------------------------------------------------------------
/vizex/vizexdu/cpu.py:
--------------------------------------------------------------------------------
1 | """
2 | -[ ] Option to record the all the cpu freqs
3 | -[ ] Do some analysis on them based on time and maybe what was running from processes
4 | """
5 |
6 | import os
7 | import time
8 | import psutil
9 | import getpass
10 | import tools
11 |
12 | from .charts import HorizontalBarChart
13 |
14 |
15 | class CPUFreq:
16 | _max = psutil.cpu_freq().max
17 | _min = psutil.cpu_freq().min
18 |
19 | @property
20 | def max_freq(self) -> int:
21 | return self._max
22 |
23 | @max_freq.setter
24 | def max_freq(self, maximum: int) -> None:
25 | self._max = maximum
26 |
27 | @property
28 | def min_freq(self) -> int:
29 | return self._min
30 |
31 | @min_freq.setter
32 | def min_freq(self, minimum: int) -> None:
33 | self._min = minimum
34 |
35 | def __init__(self) -> None:
36 | return
37 |
38 | def display_separately(self, filename="") -> None:
39 | os.system('cls' if os.name == 'nt' else 'clear')
40 | ch = HorizontalBarChart()
41 | ch.options.graph_color = 'white'
42 |
43 | while True:
44 | cpu = psutil.cpu_freq(percpu=True)
45 |
46 | for i, core in enumerate(cpu, start=1):
47 | min_freq, max_freq, current_freq = core.min, core.max, core.current
48 | percent = round((current_freq - min_freq) / (max_freq - min_freq) * 100, 2)
49 | ch.chart(
50 | title=f'CPU #{i}',
51 | pre_graph_text=f'Current: {round(current_freq, 1)}MHz || Min: {min_freq}MHz || Max: {max_freq}MHz',
52 | post_graph_text=tools.create_usage_warning(percent, 30, 15),
53 | footer=None,
54 | maximum=max_freq - min_freq,
55 | current=current_freq - min_freq,
56 | )
57 |
58 | if filename:
59 | cpu = {
60 | 'user': [getpass.getuser()],
61 | 'cpu': [i],
62 | 'time': [time.time()],
63 | 'current': [current_freq],
64 | 'usage': [percent],
65 | }
66 |
67 | if (filename.split(".")[-1].lower()) == 'csv':
68 | tools.save_to_csv(cpu, filename)
69 | else:
70 | raise NameError("Not supported file type, please indicate "
71 | + ".CSV at the end of the filename")
72 |
73 | print()
74 |
75 | time.sleep(0.8)
76 | os.system('cls' if os.name == 'nt' else 'clear')
77 |
78 | def display_combined(self) -> None:
79 | os.system('cls' if os.name == 'nt' else 'clear')
80 | ch = HorizontalBarChart()
81 | ch.options.graph_color = 'white'
82 | while True:
83 | cpu = psutil.cpu_freq(percpu=False)
84 | ch.chart(
85 | title='CPU (ALL)',
86 | pre_graph_text=f'Current: {round(cpu.current, 1)}MHz || Min: {self._min}MHz || Max: {self._max}MHz',
87 | post_graph_text=tools.create_usage_warning(
88 | round((cpu.current - self._min) / (self._max - self._min) * 100, 2),
89 | 30, 15),
90 | footer=None,
91 | maximum=self._max - self._min,
92 | current=cpu.current - self._min
93 | )
94 | print()
95 |
96 | time.sleep(0.8)
97 | os.system('cls' if os.name == 'nt' else 'clear')
98 |
99 |
100 | if __name__ == '__main__':
101 | cpu_freq = CPUFreq()
102 | cpu_freq.display_separately("~/cpu.csv")
103 |
--------------------------------------------------------------------------------
/tests/test_charts.py:
--------------------------------------------------------------------------------
1 | # add path to the main package and test charts.py
2 | if __name__ == '__main__':
3 | from __access import ADD_PATH
4 | ADD_PATH()
5 |
6 |
7 | import io
8 | import unittest
9 | import unittest.mock
10 |
11 | from colored import fg, attr, stylize
12 | from vizexdu.charts import Options, Chart, HorizontalBarChart
13 |
14 |
15 | class TestOptions(unittest.TestCase):
16 |
17 | @classmethod
18 | def setUpClass(cls):
19 | cls.opts = Options()
20 |
21 | def test_symbol_setter(self):
22 | self.opts.symbol = '@'
23 | self.assertEqual(self.opts.fsymbol, '@')
24 | self.assertEqual(self.opts.msymbol, '>')
25 | self.assertEqual(self.opts.esymbol, '-')
26 |
27 | def test_check_color(self):
28 | self.opts.graph_color = 'cyan'
29 | self.assertEqual(
30 | fg('cyan'), self.opts.graph_color)
31 |
32 | self.assertEqual(
33 | 'white', self.opts._check_color('notcolor'))
34 | self.assertEqual(
35 | fg('magenta'), self.opts._check_color('magenta'))
36 |
37 | def test_check_color_key_error(self):
38 | color = self.opts._check_color('notcolor')
39 | self.assertEqual('white', color,
40 | msg='When unavailable color is given this function should return "white"')
41 |
42 | def test_check_attr(self):
43 | self.opts.header_style = 'underlined'
44 | self.assertEqual(
45 | attr('underlined'), self.opts.header_style)
46 |
47 | self.assertEqual(
48 | 'bold', self.opts._check_attr('notattr'))
49 | self.assertEqual(
50 | attr('blink'), self.opts._check_attr('blink'))
51 |
52 | def test_check_attr_key_error(self):
53 | color = self.opts._check_attr('notarr')
54 | self.assertEqual('bold', color,
55 | msg='When unavailable attribute is given this function should return "bold"')
56 |
57 |
58 | class TestChart(unittest.TestCase):
59 |
60 | @classmethod
61 | def setUpClass(cls):
62 | cls.chart = Chart()
63 |
64 | def test_chart_init(self):
65 | # Test that if no Options arg given new is created
66 | self.assertIsInstance(self.chart.options, Options)
67 |
68 |
69 | class TestHorizontalBarChart(unittest.TestCase):
70 | """Test Horizontal Bar Chart printing"""
71 |
72 | @classmethod
73 | def setUpClass(cls):
74 | cls.horizontal_chart = HorizontalBarChart()
75 |
76 | @unittest.mock.patch('sys.stdout', new_callable=io.StringIO)
77 | def assert_stdout(self, expected_output, mock_stdout):
78 | self.horizontal_chart.chart(
79 | title='Test Title',
80 | pre_graph_text='This looks sweet',
81 | post_graph_text=None,
82 | footer=None,
83 | maximum=10,
84 | current=5
85 | )
86 |
87 | self.assertEqual(mock_stdout.getvalue(), expected_output)
88 |
89 | def test_chart(self):
90 | try:
91 | compare = f"{stylize('Test Title', fg('red') + attr('bold'))}\n" \
92 | + f"{stylize('This looks sweet', fg('white'))}\n" \
93 | + f"{stylize('███████████████████▒░░░░░░░░░░░░░░░░░░░', fg('white'))} \n"
94 | self.assert_stdout(compare)
95 | except Exception as e:
96 | self.fail(f"Exception occured when trying to print horizontal chart {e}")
97 |
98 | def test_draw_horizontal_bar_half_full(self):
99 | try:
100 | compare = '███████████████████▒░░░░░░░░░░░░░░░░░░░'
101 | self.assertEqual(
102 | compare, self.horizontal_chart.draw_horizontal_bar(10, 5))
103 | except Exception as e:
104 | self.fail(f'Exception occured when trying to draw half full bar chart {e}')
105 |
106 | def test_draw_horizontal_bar_empty(self):
107 | try:
108 | compare_02 = '▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░'
109 | self.assertEqual(
110 | compare_02, self.horizontal_chart.draw_horizontal_bar(10, 0))
111 | except Exception as e:
112 | self.fail(f'Exception occured when trying to draw an empty bar chart {e}')
113 |
114 | def test_draw_horizontal_bar_full(self):
115 | try:
116 | compare_02 = '██████████████████████████████████████▒'
117 | self.assertEqual(
118 | compare_02, self.horizontal_chart.draw_horizontal_bar(10, 10))
119 | except Exception as e:
120 | self.fail(f'Exception occured when trying to draw a full bar chart {e}')
121 |
122 |
123 | if __name__ == '__main__':
124 | unittest.main()
125 |
--------------------------------------------------------------------------------
/tests/test_files.py:
--------------------------------------------------------------------------------
1 | # add path to the main package and test files.py
2 | if __name__ == '__main__':
3 | from __access import ADD_PATH
4 | ADD_PATH()
5 |
6 | import unittest
7 | import tempfile
8 | import os
9 | import warnings
10 |
11 | from vizexdf.files import DirectoryFiles
12 |
13 |
14 | class TestDirectoryFiles(unittest.TestCase):
15 |
16 | @classmethod
17 | def setUp(cls):
18 | cls.tmpd = tempfile.TemporaryDirectory()
19 |
20 | @classmethod
21 | def tearDown(cls):
22 | cls.tmpd.cleanup()
23 |
24 | def test_get_usage_files(self):
25 | try:
26 | for i in range(5):
27 | tempfile.NamedTemporaryFile(prefix='TEST_', delete=False, dir=self.tmpd.name)
28 | df = DirectoryFiles(path=self.tmpd.name)
29 | usage = df.get_usage()
30 | self.assertEqual(5, len(usage),
31 | msg='There should have been 5 items in the list')
32 | for i in usage:
33 | self.assertTrue('TEST' in i[0])
34 | except Exception as e:
35 | self.fail(f'Exception occured when trying to get_usage for tmp files {e}')
36 |
37 | def test_get_usage_files_empty_dir(self):
38 | try:
39 | df = DirectoryFiles(path=self.tmpd.name)
40 | usage = df.get_usage()
41 | self.assertListEqual([], usage,
42 | msg='For empty folder method should return an empty list')
43 | except Exception as e:
44 | self.fail(f'Exception occured when tried to get_usage of an empty directory {e}')
45 |
46 | def test_get_dir_size(self):
47 | try:
48 | warnings.filterwarnings('ignore') # suppress tempfile warnings
49 | # Nest folders three times
50 | tmp = tempfile.TemporaryDirectory(dir=self.tmpd.name)
51 | nested_tmp = tempfile.TemporaryDirectory(dir=tmp.name)
52 | nested_nested_tmp = tempfile.TemporaryDirectory(dir=nested_tmp.name)
53 | # Create 10 files zie of 1024 * 1024 bytes each
54 | for i in range(10):
55 | f = tempfile.NamedTemporaryFile(mode='wb',
56 | dir=nested_nested_tmp.name,
57 | delete=False)
58 | f.write(b'0' * 1024 * 1024)
59 | size = DirectoryFiles().get_dir_size(self.tmpd.name)
60 | self.assertEqual(10485760, size,
61 | msg='Total size of directory should be 10485760')
62 | except Exception as e:
63 | self.fail(f'Exception occured when tried to get_size nested files {e}')
64 |
65 | def test_get_dir_size_empty_dir(self):
66 | try:
67 | size = DirectoryFiles().get_dir_size(self.tmpd.name)
68 | self.assertEqual(0, size, msg='Size 0 was expected')
69 | except Exception as e:
70 | self.fail(f'Exception occured when tried to get_size for an empty folder {e}')
71 |
72 | def test_get_dir_size_broken_symlink(self):
73 | os.symlink(
74 | '/this/path/could/not/possibly/exist/like/evar',
75 | os.path.join(self.tmpd.name, 'broken-link'),
76 | )
77 | size = DirectoryFiles().get_dir_size(self.tmpd.name)
78 | self.assertEqual(0, size)
79 |
80 | def test_sort_data(self):
81 | try:
82 | data = [
83 | ['folder1', 1927317893, 333, 'dir'],
84 | ['file1', 3419273173817333, 9081231, 'file'],
85 | ['folder2', 921231938192, 12313744908, 'dir'],
86 | ['file2', 1238193123, 22, 'file'],
87 | ['x-file', 34192773817333, 445522, 'x-files']
88 | ]
89 |
90 | DirectoryFiles().sort_data(data, 'type', True)
91 | self.assertEqual('x-file', data[0][0])
92 |
93 | DirectoryFiles().sort_data(data, 'name', True)
94 | self.assertListEqual(['x-file', 34192773817333, 445522, 'x-files'], data[0])
95 |
96 | DirectoryFiles().sort_data(data, 'size', False)
97 | self.assertEqual(22, data[0][2])
98 |
99 | DirectoryFiles().sort_data(data, 'dt', True)
100 | self.assertListEqual(['file1', 3419273173817333, 9081231, 'file'], data[0])
101 | except Exception as e:
102 | self.fail(f'Exception occured when trying to sort data {e}')
103 |
104 | @unittest.skip("UNDER CONSTRUCTION!")
105 | def test_decorate_dir(self):
106 | # TODO
107 | self.fail("TODO")
108 |
109 | @unittest.skip("UNDER CONSTRUCTION!")
110 | def test_decorate_file(self):
111 | # TODO
112 | self.fail("TODO")
113 |
114 |
115 | if __name__ == '__main__':
116 | unittest.main()
117 |
--------------------------------------------------------------------------------
/vizex/vizexdu/charts.py:
--------------------------------------------------------------------------------
1 | '''
2 | Text Chart Coloring and Printing Functions
3 | '''
4 |
5 | from math import ceil
6 | from colored import fg, attr, stylize
7 |
8 |
9 | class Options:
10 | """Options"""
11 |
12 | _graph_color = fg("white")
13 | _header_color = fg("red")
14 | _header_style = attr('bold')
15 | _text_color = fg("white")
16 | _fsymbol = "█"
17 | _msymbol = "▒"
18 | _esymbol = "░"
19 |
20 | def __init__(self):
21 | return
22 |
23 | @property
24 | def graph_color(self):
25 | """ Graph Color """
26 | return self._graph_color
27 |
28 | @graph_color.setter
29 | def graph_color(self, color: str):
30 | self._graph_color = Options._check_color(color, self._graph_color)
31 |
32 | @property
33 | def header_color(self):
34 | """ Header Color """
35 | return self._header_color
36 |
37 | @header_color.setter
38 | def header_color(self, color: str):
39 | self._header_color = Options._check_color(color, self._header_color)
40 |
41 | @property
42 | def header_style(self):
43 | """ header style """
44 | return self._header_style
45 |
46 | @header_style.setter
47 | def header_style(self, style: str):
48 | self._header_style = Options._check_attr(style, self._header_style)
49 |
50 | @property
51 | def text_color(self):
52 | """ text color """
53 | return self._text_color
54 |
55 | @text_color.setter
56 | def text_color(self, color: str):
57 | self._text_color = Options._check_color(color, self._text_color)
58 |
59 | @property
60 | def symbol(self):
61 | """ graph symbols to use """
62 | return self._fsymbol, self._msymbol, self.esymbol
63 |
64 | @symbol.setter
65 | def symbol(self, symbol: str):
66 | if symbol:
67 | self._fsymbol = symbol
68 | self._msymbol = ">"
69 | self._esymbol = "-"
70 | else:
71 | self._fsymbol = "█"
72 | self._msymbol = "▒"
73 | self._esymbol = "░"
74 |
75 | @property
76 | def fsymbol(self):
77 | """ The full symbol """
78 | return self._fsymbol
79 |
80 | @property
81 | def esymbol(self):
82 | """ The empty symbol """
83 | return self._esymbol
84 |
85 | @property
86 | def msymbol(self):
87 | """ The middle symbol """
88 | return self._msymbol
89 |
90 | @staticmethod
91 | def _check_color(color: str, default_color: str = "white"):
92 | """Checks if set color is valid"""
93 | try:
94 | return fg(color)
95 | except KeyError:
96 | return default_color
97 |
98 | @staticmethod
99 | def _check_attr(style: str, default_attr: str = "bold"):
100 | """Checks if set attribute is valid"""
101 | try:
102 | return attr(style)
103 | except KeyError:
104 | return default_attr
105 |
106 |
107 | class Chart:
108 | """Abstract base object for charts"""
109 |
110 | def __init__(self, options: Options = None):
111 | if options:
112 | self.options = options
113 | else:
114 | self.options = Options()
115 |
116 |
117 | class HorizontalBarChart(Chart):
118 | """
119 | Create horizontal chart with user selected color and symbol
120 | """
121 |
122 | def chart(self,
123 | title: str,
124 | pre_graph_text: str,
125 | post_graph_text: str,
126 | footer: str,
127 | maximum: int,
128 | current: int) -> None:
129 | """prints assembled chart to the terminal"""
130 | print(
131 | stylize(title, self.options.header_color +
132 | self.options.header_style)
133 | )
134 |
135 | if pre_graph_text:
136 | print(stylize(pre_graph_text, self.options.text_color))
137 |
138 | print(
139 | "%s"
140 | % stylize(
141 | self.draw_horizontal_bar(maximum, current),
142 | self.options.graph_color
143 | ),
144 | end=" ",
145 | )
146 | if post_graph_text:
147 | print(stylize(post_graph_text, self.options.text_color))
148 | else:
149 | print()
150 |
151 | if footer:
152 | print(stylize(footer, self.options.text_color))
153 |
154 | def draw_horizontal_bar(self, maximum: int, current: int) -> str:
155 | """Draw a horizontal bar chart"""
156 |
157 | # Sanity check that numbers add up
158 | if current > maximum:
159 | current = maximum
160 |
161 | if current < 0:
162 | current = 0
163 |
164 | if maximum < 0:
165 | maximum = 0
166 |
167 | text_bar = ""
168 | usage = int((current / maximum) * 38)
169 |
170 | for _ in range(1, usage + 1):
171 | text_bar += self.options.fsymbol
172 | text_bar += self.options.msymbol
173 |
174 | for _ in range(1, 39 - usage):
175 | text_bar += self.options.esymbol
176 |
177 | # Check if the user set up graph color
178 | if "█" not in self.options.fsymbol:
179 | return f"[{text_bar}]"
180 |
181 | return text_bar
182 |
183 |
184 | if __name__ == "__main__":
185 | ch = HorizontalBarChart()
186 | ch.options.graph_color = 'cyan'
187 | ch.options.text_color = 'yellow'
188 | ch.options.header_color = 'red'
189 | ch.options.header_style = 'underlined'
190 |
191 | ch.chart(
192 | title="Test Content",
193 | maximum=100,
194 | current=32,
195 | pre_graph_text="Lorem: Sit ea dolore ad accusantium",
196 | post_graph_text="Good job!",
197 | footer="This concludes our test",
198 | )
199 |
--------------------------------------------------------------------------------
/vizex/tools.py:
--------------------------------------------------------------------------------
1 | '''
2 | Utility functions for vizex/vizexdf
3 | '''
4 |
5 | import os
6 | import re
7 | import json
8 | import time
9 | import pandas as pd
10 |
11 | from typing import Optional, Match
12 | from math import ceil
13 | from colored import fg, attr, stylize
14 | from dataclasses import dataclass
15 |
16 |
17 | @dataclass(order=True)
18 | class DecoratedData:
19 | """
20 | Custom class to compare numerical data for sorting
21 | which appears in the stylized representation of a string.
22 | """
23 | size: int
24 | to_string: str
25 |
26 | def __str__(self):
27 | """String representation of the class"""
28 | return self.to_string
29 |
30 |
31 | def bytes_to_human_readable(bytes_in: int, suffix='B') -> str:
32 | """
33 | Converts bytes into the appropriate human
34 | readable unit with a relevant suffix.
35 |
36 | Args:
37 | bytes_in (int): to convert
38 | suffix (str, optional): suffix of a size string.
39 | Defaults to 'b'.
40 |
41 | Returns:
42 | str: size in a human readable
43 | """
44 | for unit in ['', 'k', 'M', 'G', 'T', 'P', 'E', 'Z']:
45 | if abs(bytes_in) < 1024.0:
46 | return f'{bytes_in:3.1f} {unit}{suffix}'
47 | bytes_in /= 1024.0
48 | return f'{bytes_in:.1f} {"Y"}{suffix}'
49 |
50 |
51 | def ints_to_human_readable(disk: dict) -> dict:
52 | """Converts the dictionary of integers
53 | into the human readable size formats.
54 |
55 | Args:
56 | disk [dict]: of large byte numbers
57 |
58 | Returns:
59 | dict: same dict but in human readable format
60 | """
61 | result = {}
62 | for key in disk:
63 | try:
64 | result[key] = bytes_to_human_readable(disk[key])
65 | except Exception:
66 | result[key] = disk[key] # If not able to convert return input back
67 | return result
68 |
69 |
70 | def printml(folder: list, cols: int = 1) -> None:
71 | """Prints multiline strings side by side.
72 |
73 | Args:
74 | folder (list): list of folders to print
75 | cols (int, optional): On how many lines should it be printed.
76 | Defaults to 1.
77 | """
78 | size = len(folder)
79 | incr = ceil(size / cols)
80 | end, start = 0, 0
81 | while True:
82 | if end >= size:
83 | break
84 | end += incr
85 | # Check if the end index exceeds the last index
86 | if end > size:
87 | end = size
88 | lines = [folder[i].splitlines() for i in range(start, end)]
89 | for line in zip(*lines):
90 | print(*line, sep=' ')
91 | print()
92 | start = end
93 |
94 |
95 | def create_usage_warning(usage_pct: float,
96 | red_flag: int,
97 | yellow_flag: int) -> str:
98 | """Create disk usage percent with warning color
99 |
100 | Args:
101 | usage_pct (float): of a given space
102 | red_flag (int): threshold that decides if print should be red
103 | yellow_flag (int): threshold that decides if print is yellow
104 |
105 | Returns:
106 | str: stylized warning string
107 | """
108 | if usage_pct < 0:
109 | usage_pct = 0
110 |
111 | if usage_pct > 100:
112 | usage_pct = 100
113 |
114 | use = str(usage_pct) + '% used'
115 |
116 | if usage_pct >= red_flag:
117 | return f"{stylize(use, attr('blink') + fg(9))}"
118 | if usage_pct >= yellow_flag:
119 | return f"{stylize(use, fg(214))}"
120 | return f"{stylize(use, fg(82))}"
121 |
122 |
123 | def save_to_csv(data: dict,
124 | filename: str,
125 | orient: str = 'index') -> None:
126 | """Outputs disks/partitions data as a CSV file
127 |
128 | Args:
129 | data (dict): to be saved to a CSV file
130 | filename (str): to name a saved file
131 | orient (str, optional): how lines are saved.
132 | Defaults to 'index'.
133 |
134 | Raises:
135 | NameError: if filename doesn't contain .csv at the end
136 | """
137 | if filename.split(".")[-1].lower() == 'csv':
138 | df = pd.DataFrame.from_dict(data, orient=orient)
139 | df.to_csv(filename, mode='a')
140 | else:
141 | raise NameError('Please include ".csv" in the filename')
142 |
143 |
144 | def save_to_json(data: dict,
145 | filename: str,
146 | indent: int = 4) -> None:
147 | """Saves disk/partitions data as a JSON file
148 |
149 | Args:
150 | data (dict): to be saved to a JSON file
151 | filename (str): to name a saved file
152 | indent (int, optional): of each new line.
153 | Defaults to 4.
154 |
155 | Raises:
156 | NameError: if filename doesn't contain .json at the end
157 | """
158 | if filename.split(".")[-1].lower() == 'json':
159 | with open(filename, "w") as file:
160 | json.dump(data, file, indent=indent)
161 | else:
162 | raise NameError('Please include ".json" in the filename')
163 |
164 |
165 | def append_to_bash(alias: str, line: str) -> None:
166 | """
167 | Appends terminal command line as an alias in .bashrc for reuse
168 |
169 | Args:
170 | alias[str]: To set up in the bash
171 | line[str]: line which will be assigned to an alias
172 | """
173 | bash = os.path.expanduser("~") + '/.bash_aliases'
174 | print(remove_if_exists(alias, bash))
175 | with open(bash, 'a+') as file:
176 | file.write('alias ' + alias + f"='{line}'")
177 |
178 |
179 | def remove_if_exists(alias: str, path: str) -> None:
180 | """
181 | Removes if the given line/alias exists in a given file
182 |
183 | Args:
184 | alias (str): to check if exists in bash
185 | path (str): path to the file to read
186 | """
187 | if not os.path.exists(path):
188 | return
189 | with open(path, "r") as file:
190 | lines = file.readlines()
191 |
192 | with open(path, "w") as file:
193 | for line in lines:
194 | # We only write back lines which is not alias
195 | if f'alias {alias}' not in line.strip("\n"):
196 | file.write(line)
197 |
198 |
199 | def normalize_date(formatting: str, date: int) -> str:
200 | """
201 | Converts date from nanoseconds to the human readable form
202 |
203 | Args:
204 | format (str): example %h-%d-%Y for mm-dd-yyyy
205 | date (int): date in nanoseconds
206 |
207 | Returns:
208 | str: Human readable format of a date
209 | """
210 | return time.strftime(formatting, time.localtime(date))
211 |
212 |
213 | def find_word(word, src) -> Optional[Match[str]]:
214 | """Find word in a src using regex"""
215 | return re.compile(r'\b({0})\b'.format(word),
216 | flags=re.IGNORECASE).search(src)
217 |
218 |
219 | if __name__ == '__main__':
220 | file1 = DecoratedData(55456, '54.2 kB')
221 | file2 = DecoratedData(123233419, '117.5 MB')
222 | print(f'{file1} is less than {file2} : {file1 < file2}')
223 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
181 |
182 |
183 |
184 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
40 |
41 |
42 |
43 |
44 |
vizex is the terminal program for the UNIX/Linux systems which helps the user to visualize the disk space usage for every partition and media on the user's machine. vizex is highly customizable and can fit any user's taste and preferences.
51 |vizexdf is a new feature that allows to organize and print directory data in the terminal. With a recent release 2.0.4 vizexdf does directory parsing using Asynchronous execution, which improved runtime performance by over 400%.
You can check full release history here.
53 |vizex can be installed through your terminal and requires Python >= 3.9 and the pip package manager. Here's how to set up Python on your machine.
If you don't have PyPackage Index (PyPI or just pip) installed, Here's the guide on how to install it. Install vizex with the following command:
pip install vizex
60 |
61 | If you already have vizex install you'll need to upgrade it:
62 |pip install vizex --upgrade
63 |
64 | If you encounter any problems during installation, know that some vizex dependencies require a Python 3 development package on Linux and you need to set up that manually.
For Debian and other derived systems (Ubuntu, Mint, Kali, etc.) you can install this with the following command:
66 |sudo apt-get install python3-dev
67 |
68 | For Red Hat derived systems (Fedora, RHEL, CentOS, etc.) you can install this with the following command:
69 |sudo yum install python3-devel
70 |
71 | vizex is available as a package on the AUR (Arch user repository), distributions with AUR support may install directly from the command line using their favorite pacman helper.
Example using yay:
yay -S vizex
75 |
76 | After installing you can use two terminal commands: vizex to display the disk usage/space and vizexdf, which will print the data of a current working directory with sizes, file types and last modified date.
This will graphically display disk space and usage:
79 |vizex
80 |
81 | 
vizexdf
84 |
85 | 
new feature:
89 |you can now print tree of directory structure with the level you want. For example tree with level 1 only
91 |By default level is set to 3 and path is a current path. But you can manually supply path, by just typing path you want to generate tree for, and using -l option to instruct how many levels of directories you want to print.
For example:
93 |vizextree . -level 1
94 |
95 | 
The best part is that you can modify the colors and style of the display to your preferences with the following commands. For the example above command has excluded two partitions. You can also do give the following options:
99 |-d --header <color>
100 | -s --style <attribute>
101 | -t --text <color>
102 | -g --graph <color>
103 |
104 | Display additional details, like fstype and mount point, for each partition:
vizex --details
106 |
107 | 
If you are interested in visualizing a specific path run with the following command:
109 |vizex --path </full/path>
110 |
111 | You can also exclude any combination of partitions/disks with multiple -X or for verbose --exclude option:
vizex -X <PartitionName1> -X <PartitionName2> ...
113 |
114 | You can also save the partitions full information in csv or in json file, just by calling --save option with the full path where you want your output to be saved:
vizex --save "/home/user/disk_info.json"
116 |
117 | And if you are on laptop you can even call your battery information with simple argument:
118 |vizex battery
119 |
120 | For a full list of the available options please check:
121 |vizex --help
122 |
123 | You can include hidden files and folders by calling --all or -a for short and sort the output with --sort or -s for short based on name, file type, size, or date. Put it in descending order with the --desc option.
You can chain multiple options but make sure to put the -s at the end as it requires a text argument. Example:
vizexdf -ads name
128 |
129 | This will print current directory data sorted by name and in descending order and will include hidden files.
130 |Lastly, you save all the modifications by adding -l at the end of the command:
131 |vizex -d red -t blue --details -l
132 |
133 | The next time you call vizex / vizexdf it will include all the options listed in the above command. If you decided to change the default calling command for vizex/vizexdf just include -l and it will be overwritten
If you want to contribute to the project you are more than welcome! But first, make sure all the tests run after you fork the project and before the pull request. First, run the access.py, that way tests folder will obtain a path to the main folder and you can run all the tests.
You can get the full set of features by calling --help option with command.
140 |
141 |
142 |
143 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
Repo is distributed under the MIT license. Please see the LICENSE for more information.