diff --git a/.github/workflows/tests_multi_os.yml b/.github/workflows/tests_multi_os.yml index 3e37265..7713618 100644 --- a/.github/workflows/tests_multi_os.yml +++ b/.github/workflows/tests_multi_os.yml @@ -33,7 +33,7 @@ jobs: git submodule update --init --recursive pip install -r requirements.txt python -m pip install --upgrade pip - pip install . + pip install .[extras] - name: Install test requirements run: | diff --git a/.travis.yml b/.travis.yml index 05fa539..8d4e085 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ before_install: - python -V - pip -V - pip install pytest pytest-cov bandit sphinx recommonmark -- pip install -e . +- pip install -e .[extras] script: - pytest --disable-pytest-warnings --cov-report=xml --cov=chepy --cov-config=.coveragerc tests/ diff --git a/Dockerfile b/Dockerfile index f081707..d26ce84 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,7 @@ RUN pip install -r /chepy/requirements.txt \ && pip install python-magic virtualenv \ && virtualenv -p python3 /chepy/venv \ && pip install pytest pytest-cov bandit \ - && pip install scapy markdown pefile pyelftools pydriller + && pip install scapy markdown pefile pyelftools pydriller requests COPY . /chepy/ RUN cd /chepy \ @@ -36,6 +36,8 @@ FROM python:3.8.0-slim COPY --from=0 /chepy /chepy RUN apt update \ && apt install exiftool libmagic-dev -y \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* \ && /chepy/venv/bin/chepy -v \ && sed -i 's/enableplugins = false/enableplugins = true/' /root/.chepy/chepy.conf WORKDIR /data diff --git a/README.md b/README.md index 10373d9..7bcc8b8 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,8 @@ Chepy can be installed in a few ways. ### Pypi ```bash pip3 install chepy +# optionally with extra requirements +pip3 install chepy[extras] ``` ### Git diff --git a/TODO b/TODO new file mode 100644 index 0000000..1bf0e68 --- /dev/null +++ b/TODO @@ -0,0 +1,4 @@ + +Todo: + ✔ sort dict by key or value + ✔ Change sort list to be able to sort by any data type diff --git a/chepy/__version__.py b/chepy/__version__.py index defcbfa..d1fc948 100644 --- a/chepy/__version__.py +++ b/chepy/__version__.py @@ -1,2 +1,2 @@ -__version__ = "2.3.2" # pragma: no cover +__version__ = "2.4.0" # pragma: no cover __author__ = "Hapsida @securisec" # pragma: no cover diff --git a/chepy/chepy_plugins b/chepy/chepy_plugins index da182c0..558f245 160000 --- a/chepy/chepy_plugins +++ b/chepy/chepy_plugins @@ -1 +1 @@ -Subproject commit da182c0de7152048d0750d64c487aadb05494c6b +Subproject commit 558f245e55a2ea1fda324c996a36f5ed96de7bcc diff --git a/chepy/core.py b/chepy/core.py index e38b1e6..8965993 100644 --- a/chepy/core.py +++ b/chepy/core.py @@ -10,10 +10,10 @@ import subprocess from configparser import ConfigParser from urllib.parse import urljoin +from prettyprinter import pformat from typing import Any, Tuple, List, Union import pyperclip -from requests import request import ujson import jsonpickle import regex as re @@ -104,7 +104,7 @@ def state(self, val): def __str__(self): try: if isinstance(self.state, bytearray): - return re.sub(rb'[^\x00-\x7f]', b'.', self.state).decode() + return re.sub(rb"[^\x00-\x7f]", b".", self.state).decode() else: return self._convert_to_str() except UnicodeDecodeError: # pragma: no cover @@ -695,6 +695,12 @@ def json2str(obj): # pragma: no cover else: raise NotImplementedError + try: + from requests import request + except ImportError: # pragma: no cover + self._error_logger("Could not import requests. pip install requests") + return self + params = json2str(params) headers = json2str(headers) cookies = json2str(cookies) @@ -756,6 +762,12 @@ def json2str(obj): # pragma: no cover else: raise NotImplementedError + try: + from requests import request + except ImportError: # pragma: no cover + self._error_logger("Could not import requests. pip install requests") + return self + params = json2str(params) headers = json2str(headers) cookies = json2str(cookies) @@ -1060,6 +1072,19 @@ def shell_output(self): # pragma: no cover self.state = subprocess.getoutput(self.state) return self + @ChepyDecorators.call_stack + def pretty(self, indent: int = 2): + """Prettify the state. + + Args: + indent (int, optional): Indent level. Defaults to 2. + + Returns: + Chepy: The Chepy object. + """ + self.state = pformat(self.state, indent=int(indent)) + return self + def plugins(self, enable: str) -> None: # pragma: no cover """Use this method to enable or disable Chepy plugins. @@ -1093,3 +1118,27 @@ def plugins(self, enable: str) -> None: # pragma: no cover ) sys.exit() return None + + def set_plugin_path(self, path: str) -> None: # pragma: no cover + """Use this method to set the path for Chepy plugins. + + Args: + path (str): Path to plugins directory + + Returns: + None + """ + expand_path = self._abs_path(path) + if expand_path.exists(): + conf_path = pathlib.Path().home() / ".chepy" / "chepy.conf" + c = ConfigParser() + c.read(conf_path) + c.set("Plugins", "pluginpath", str(expand_path)) + with open(conf_path, "w") as f: + c.write(f) + self._info_logger(green("Plugin path has been set. Restart for changes.")) + sys.exit() + return None + else: + raise AttributeError("The path does not exist") + diff --git a/chepy/modules/utils.py b/chepy/modules/utils.py index beb81f2..e4999cd 100644 --- a/chepy/modules/utils.py +++ b/chepy/modules/utils.py @@ -1,5 +1,6 @@ import difflib import regex as re +from collections import OrderedDict from typing import Any, Union import pydash @@ -205,39 +206,79 @@ def unique(self): """Get an array of unique list items Raises: - TypeError: If state is not a list + StateNotList: If state is not a list Returns: Chepy: The Chepy object. """ - if isinstance(self.state, list): - self.state = pydash.uniq(self.state) - return self - else: # pragma: no cover - raise TypeError("State is not a list") + assert isinstance(self.state, list), StateNotList() + self.state = pydash.uniq(self.state) + return self @ChepyDecorators.call_stack - def sorted(self, reverse: bool = False): + def sort_list(self, reverse: bool = False): """Sort a list Args: reverse (bool, optional): In reverse order. Defaults to False. Raises: - TypeError: If state is not list + StateNotList: If state is not list Returns: Chepy: The Chepy object. Examples: - >>> Chepy(["a", "b", "1", "2"]).sorted().o + >>> Chepy(["a", "b", "1", "2"]).sort_list().o ["1", "2", "a", "b"] """ - if isinstance(self.state, (list)): - self.state = sorted(self.state) - return self - else: # pragma: no cover - raise TypeError("State is not a list") + assert isinstance(self.state, list), StateNotList() + self.state = sorted( + self.state, key=lambda v: (isinstance(v, str), v), reverse=reverse + ) + return self + + @ChepyDecorators.call_stack + def sort_dict_key(self, reverse: bool = False): + """Sort a dictionary by key + + Args: + reverse (bool, optional): Reverse sort order. Defaults to False. + + Returns: + Chepy: The Chepy object. + + Examples: + >>> c = Chepy({'z': 'string', 'a': True, 'zz': 1, 'aaa': {'bb': 'data'}, 'ccc': [1,'a']}) + >>> c.sort_dict_key(reverse=True) + {'zz': 1, 'z': 'string', 'ccc': [1, 'a'], 'aaa': {'bb': 'data'}, 'a': True} + """ + assert isinstance(self.state, dict), StateNotDict() + self.state = dict(OrderedDict(sorted(self.state.items(), reverse=reverse))) + return self + + @ChepyDecorators.call_stack + def sort_dict_value(self, reverse=False): + """Sort dictionary by value + + Args: + reverse (bool, optional): Reverse sort order. Defaults to False. + + Returns: + Chepy: The Chepy object. + + Examples: + >>> c = Chepy({'z': 'string', 'a': 'True', 'zz': '1', 'aaa': {'bb': 'data'}, 'ccc': [1,'a']}) + >>> c.sort_dict_value() + {'zz': '1', 'a': 'True', 'ccc': [1, 'a'], 'z': 'string', 'aaa': {'bb': 'data'}} + """ + assert isinstance(self.state, dict), StateNotDict() + self.state = dict( + OrderedDict( + sorted(self.state.items(), reverse=reverse, key=lambda x: str(x[1])) + ) + ) + return self @ChepyDecorators.call_stack def filter_list(self, by: Union[str, dict], regex: bool = True): @@ -248,7 +289,7 @@ def filter_list(self, by: Union[str, dict], regex: bool = True): regex (bool, optional): If pattern is a regex. Defaults to True Raises: - TypeError: If state is not a list + StateNotList: If state is not a list Returns: Chepy: The Chepy object. diff --git a/requirements.txt b/requirements.txt index 4be411b..e5b3e7a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,6 +13,7 @@ jsonpickle parsel phpserialize Pillow +prettyprinter prompt_toolkit>=2.0.8 pycipher pycryptodome @@ -23,6 +24,4 @@ pyperclip PyExifTool PyYAML regex -requests -requests-toolbelt ujson diff --git a/setup.py b/setup.py index dda3b4b..7c1ead7 100644 --- a/setup.py +++ b/setup.py @@ -35,6 +35,7 @@ def read_requirements(): "Documentation": "https://chepy.readthedocs.io/en/latest/", "Source Code": "https://github.com/securisec/chepy", }, + extras_require={"extras": ["requests"]}, packages=find_packages(exclude=(["tests", "docs"])), install_requires=requirements, classifiers=[ diff --git a/tests/test_core.py b/tests/test_core.py index da34a5f..85e2b19 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -191,3 +191,15 @@ def test_load_from_url(): ) == bytes ) + + +def test_pretty(): + assert ( + Chepy({"a": 1, "b": 2, "c": [1, 2, 3]}).pretty().o + == """{ + 'a': 1, + 'b': 2, + 'c': [1, 2, 3] +}""" + ) + diff --git a/tests/test_utils.py b/tests/test_utils.py index 5c0ff43..62f99e1 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -79,8 +79,42 @@ def test_unique(): assert len(Chepy('["a", "a", 1]').str_list_to_list().unique().o) == 2 -def test_sorted(): - assert Chepy(["a", "b", "1", "2"]).sorted().o == ["1", "2", "a", "b"] +def test_sorted_list(): + assert Chepy(["a", "b", "1", "2"]).sort_list().o == ["1", "2", "a", "b"] + assert Chepy(["a", "b", "1", "2"]).sort_list(reverse=True).o == ["b", "a", "2", "1"] + + +def test_sort_dict_key(): + assert Chepy( + {"z": "string", "a": True, "zz": 1, "aaa": {"bb": "data"}, "ccc": [1, "a"]} + ).sort_dict_key().o == { + "a": True, + "aaa": {"bb": "data"}, + "ccc": [1, "a"], + "z": "string", + "zz": 1, + } + assert Chepy( + {"z": "string", "a": True, "zz": 1, "aaa": {"bb": "data"}, "ccc": [1, "a"]} + ).sort_dict_key(reverse=True).o == { + "zz": 1, + "z": "string", + "ccc": [1, "a"], + "aaa": {"bb": "data"}, + "a": True, + } + + +def test_sort_dict_value(): + assert Chepy( + {"z": "string", "a": "True", "zz": "1", "aaa": {"bb": "data"}, "ccc": [1, "a"]} + ).sort_dict_value().o == { + "zz": "1", + "a": "True", + "ccc": [1, "a"], + "z": "string", + "aaa": {"bb": "data"}, + } def test_filter_list():