From e53f801a2f1bb7097b7e9ca4c8a8c4777fe04e5a Mon Sep 17 00:00:00 2001 From: hminaee-tc Date: Wed, 19 Jun 2024 17:54:41 -0300 Subject: [PATCH] feat/initial-v initial v (#1) feat/initial-v initial v --- .github/CODEOWNERS | 1 + .github/workflows/pre-commit.yml | 24 +++ .github/workflows/publish-to-pypi.yml | 35 +++ .github/workflows/tests.yml | 74 +++++++ .gitignore | 8 +- .pre-commit-config.yaml | 92 ++++++++ .pre-commit-hooks.yaml | 6 + .yamllint.yaml | 35 +++ README.md | 202 +++++++++++++++++- assets/publish.sh | 18 ++ assets/pypi-package.png | Bin 0 -> 70767 bytes find_and_replace_strings/__init__.py | 0 find_and_replace_strings/__main__.py | 5 + find_and_replace_strings/main.py | 98 +++++++++ pypi_bumpversion_check/check_version.py | 31 +++ pypi_bumpversion_check/requirements.txt | 5 + pyproject.toml | 43 ++++ setup.py | 4 + tests-package-e2e/.find-and-replace.json | 30 +++ tests-package-e2e/README_TEST_PACKAGE.md | 9 + .../README_TEST_PACKAGE.md.expected | 9 + .../README_TEST_PACKAGE.md.template | 9 + tests-package-e2e/check-commit-hook.sh | 28 +++ tests-package-e2e/test-package-e2e.sh | 32 +++ tests-pre-commit-hook/.find-and-replace.json | 30 +++ tests-pre-commit-hook/.pre-commit-config.yaml | 14 ++ .../README_TEST_PRE_COMMIT.md | 9 + .../README_TEST_PRE_COMMIT.md.expected | 9 + .../README_TEST_PRE_COMMIT.md.template | 9 + tests-pre-commit-hook/test-pre-commit-hook.sh | 32 +++ tests/__init__.py | 0 tests/test_main.py | 41 ++++ 32 files changed, 939 insertions(+), 3 deletions(-) create mode 100644 .github/CODEOWNERS create mode 100644 .github/workflows/pre-commit.yml create mode 100644 .github/workflows/publish-to-pypi.yml create mode 100644 .github/workflows/tests.yml create mode 100644 .pre-commit-config.yaml create mode 100644 .pre-commit-hooks.yaml create mode 100644 .yamllint.yaml create mode 100644 assets/publish.sh create mode 100644 assets/pypi-package.png create mode 100644 find_and_replace_strings/__init__.py create mode 100644 find_and_replace_strings/__main__.py create mode 100755 find_and_replace_strings/main.py create mode 100644 pypi_bumpversion_check/check_version.py create mode 100644 pypi_bumpversion_check/requirements.txt create mode 100644 pyproject.toml create mode 100644 setup.py create mode 100644 tests-package-e2e/.find-and-replace.json create mode 100644 tests-package-e2e/README_TEST_PACKAGE.md create mode 100644 tests-package-e2e/README_TEST_PACKAGE.md.expected create mode 100644 tests-package-e2e/README_TEST_PACKAGE.md.template create mode 100755 tests-package-e2e/check-commit-hook.sh create mode 100755 tests-package-e2e/test-package-e2e.sh create mode 100644 tests-pre-commit-hook/.find-and-replace.json create mode 100644 tests-pre-commit-hook/.pre-commit-config.yaml create mode 100644 tests-pre-commit-hook/README_TEST_PRE_COMMIT.md create mode 100644 tests-pre-commit-hook/README_TEST_PRE_COMMIT.md.expected create mode 100644 tests-pre-commit-hook/README_TEST_PRE_COMMIT.md.template create mode 100755 tests-pre-commit-hook/test-pre-commit-hook.sh create mode 100644 tests/__init__.py create mode 100644 tests/test_main.py diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..e6cefce --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @opencepk/opencepk-admins diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 0000000..aa35841 --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -0,0 +1,24 @@ +--- +name: pre-commit + +on: + pull_request: + push: + branches: [main] + +permissions: read-all + +jobs: + pre-commit: + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v3 + - name: setup-python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + - name: install-dependencies + run: pip install -r pypi_bumpversion_check/requirements.txt + - name: pre-commit-run + uses: pre-commit/action@v3.0.0 diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml new file mode 100644 index 0000000..bf35e65 --- /dev/null +++ b/.github/workflows/publish-to-pypi.yml @@ -0,0 +1,35 @@ +--- +name: Publish Python 🐍 distributions 📦 to PyPI + +on: + pull_request: + branches: + - main + types: [closed] + +jobs: + build-n-publish: + if: ${{ github.event.pull_request.merged }} + name: Build and publish Python 🐍 distributions 📦 to PyPI + runs-on: ubuntu-latest + permissions: + id-token: write + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v5.1.0 + with: + python-version: 3.11 + + - name: Install pip packages + run: pip install twine build setuptools + + - name: Build the package + run: python -m build + + - name: Publish release distributions to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + skip-existing: true diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..dac0169 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,74 @@ +--- +name: tests + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + # ---------------------- + # JOB 1: Run unit tests + # ---------------------- + tests-unit: + name: tests-unit + runs-on: ubuntu-latest + permissions: + id-token: write + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v5.1.0 + with: + python-version: 3.11 + + - name: Run tests + run: python3 -m unittest tests/test_main.py + # ---------------------- + # JOB 2: Run python package end to end test + # ---------------------- + test-package-e2e: + name: test-package-e2e + runs-on: ubuntu-latest + permissions: + id-token: write + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v5.1.0 + with: + python-version: 3.11 + + - name: Run test-package-e2e.sh + run: | + cd tests-package-e2e + ./test-package-e2e.sh + # ---------------------- + # JOB 3: Run pre-commit hook test + # ---------------------- + test-pre-commit-hook: + name: test-pre-commit-hook + runs-on: ubuntu-latest + permissions: + id-token: write + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v5.1.0 + with: + python-version: 3.11 + + - name: Run test-pre-commit-hook.sh + run: | + pip install pre-commit + cd tests-pre-commit-hook + ./test-pre-commit-hook.sh diff --git a/.gitignore b/.gitignore index 82f9275..b2ce492 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,14 @@ # Byte-compiled / optimized / DLL files __pycache__/ +**/__pycache__/ *.py[cod] *$py.class +.DS_Store +**/.DS_Store + +**/precommit-e2e.test + # C extensions *.so @@ -85,7 +91,7 @@ ipython_config.py # pyenv # For a library or package, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: -# .python-version +.python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..701ec9c --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,92 @@ +--- +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: check-merge-conflict + - id: check-added-large-files + args: [--maxkb=500] + - id: trailing-whitespace + - id: detect-private-key + - id: end-of-file-fixer + - id: fix-encoding-pragma + - id: file-contents-sorter + - id: check-case-conflict + - id: mixed-line-ending + args: [--fix=lf] + # ----------------------------- + # Checkov is a static code analysis tool for scanning infrastructure as code (IaC) files for misconfigurations + # that may lead to security or compliance problems. + # ----------------------------- + # Checkov includes more than 750 predefined policies to check for common misconfiguration issues. + # Checkov also supports the creation and contribution of custom policies. + # https://www.checkov.io/4.Integrations/pre-commit.html + # ----------------------------- + - repo: https://github.com/bridgecrewio/checkov.git + rev: 3.2.141 + hooks: + - id: checkov + # ----------------------------- + # Gitleaks SAST tool for detecting and preventing hardcoded secrets like passwords, api keys, and tokens in git repos + # ----------------------------- + # If you are knowingly committing something that is not a secret and gitleaks is catching it, + # you can add an inline comment of '# gitleaks:allow' to the end of that line in your file. + # This will instructs gitleaks to ignore that secret - example: + # some_non_secret_value = a1b2c3d4e5f6g7h8i9j0 # gitleaks:allow + # ----------------------------- + - repo: https://github.com/gitleaks/gitleaks + rev: v8.18.4 + hooks: + - id: gitleaks + # ----------------------------- + # Generates Table of Contents in Markdown files + # ----------------------------- + - repo: https://github.com/frnmst/md-toc + rev: 9.0.0 + hooks: + - id: md-toc + args: [-p, github] # CLI options + # ----------------------------- + # YAML Linting on yaml files for pre-commit and github actions + # ----------------------------- + - repo: https://github.com/adrienverge/yamllint + rev: v1.35.1 + hooks: + - id: yamllint + name: Check YAML syntax with yamllint + args: [--strict, -c=.yamllint.yaml, '.'] + always_run: true + pass_filenames: true + # ----------------------------- + # Install PYPI bumpversion check requirements + # ----------------------------- + - repo: local + hooks: + - id: install-pypi_bumpversion_check-requirements + name: Install PYPI bumpversion check requirements + entry: pip3 install -r pypi_bumpversion_check/requirements.txt + language: system + files: pyproject.toml + # ----------------------------- + # PYPI bumpversion check + # ----------------------------- + - repo: local + hooks: + - id: pypi_bumpversion_check + name: Check version + entry: python3 pypi_bumpversion_check/check_version.py + language: system + files: pyproject.toml + # ----------------------------- + # Unit Tests + # ----------------------------- + - repo: local + hooks: + - id: unittest + name: Run unit tests + entry: python3 -m unittest tests.test_main + language: system + pass_filenames: false + always_run: true diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml new file mode 100644 index 0000000..08472bf --- /dev/null +++ b/.pre-commit-hooks.yaml @@ -0,0 +1,6 @@ +--- +- id: find-and-replace-strings + name: find-and-replace-strings + description: Finds strings in files and replaces them with other strings. + entry: find-and-replace-strings + language: python diff --git a/.yamllint.yaml b/.yamllint.yaml new file mode 100644 index 0000000..bb6d180 --- /dev/null +++ b/.yamllint.yaml @@ -0,0 +1,35 @@ +--- +yaml-files: + - '*.yaml' + - '*.yml' + - '.yamllint' + +rules: + anchors: enable + braces: enable + brackets: enable + colons: enable + commas: enable + comments: + level: warning + comments-indentation: + level: warning + document-end: disable + document-start: + level: warning + empty-lines: enable + empty-values: disable + float-values: disable + hyphens: enable + indentation: enable + key-duplicates: enable + key-ordering: disable + line-length: + max: 120 + level: warning + new-line-at-end-of-file: enable + new-lines: enable + octal-values: disable + quoted-strings: disable + trailing-spaces: enable + truthy: disable diff --git a/README.md b/README.md index 41e1a5f..faa9a47 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,200 @@ -# find-and-replace-strings -Python package and pre-commit-hook for finding and replacing string(s) in file(s) +# find-and-replace + +Python package and pre-commit-hook for finding and replacing string(s) in file(s). + +## Prerequisite + +pre-commit install +pre-commit install -t pre-push + +The above will make sure precommit will be run automatically on push + +## Installation as a pip package + +This is an easy to use package which is already available here https://pypi.org/project/find-and-replace-template-commit-check/: + +![package to use](./assets/pypi-package.png "Title") + +You can install the package via pip: + +```bash +pip install find-and-replace-strings +``` +In case if you want to use it from the root folder in source: + +``` + python -m find_and_replace_strings -h +``` + +## Usage as a pre commit hook + +To use this package, you need to add it to your pre-commit configuration file (.pre-commit-config.yaml). Here's an example: + +For config mod + +``` +repos: + - repo: https://github.com/opencepk/find-and-replace + rev: v0.0.1 + hooks: + - id: find-and-replace-strings + name: find-and-replace-strings + description: Find and replace strings + entry: find-and-replace-strings + language: python + pass_filenames: true + exclude_types: + - binary + files: README.md + verbose: true + +``` + +and for direct mode + +``` +repos: + - repo: https://github.com/opencepk/find-and-replace + rev: v0.0.1 + hooks: + - id: find-and-replace-strings + name: find-and-replace-strings + description: Find and replace strings + entry: find-and-replace-strings + language: python + pass_filenames: true + exclude_types: + - binary + args: ["--find", "search_string", "--replacement", "replacement_string"] + files: README.md + verbose: true +``` + +Please note you also have a choice of +files: '.*\.md$' +or +files: . + +In this configuration, the find-and-replace hook is set to read search and replacement strings from a file (.project-properties.json by default which should be defined in the root of the project you want to use this package). You can also specify the search and replacement strings directly in the args field (which is not a suggested way). + +## Usage as a python package +python -m find_and_replace_strings --usage +or +find-and-replace-strings --usage + +shows some usage examples. +``` + python -m find_and_replace_strings --usage +Example usages: +python -m find_and_replace_strings --config e2e/.find-and-replace.json e2e/precommit-e2e.test --dry-run --verbose +python -m find_and_replace_strings --config e2e/.find-and-replace.json e2e/precommit-e2e.test --dry-run --log-level=DEBUG +python -m find_and_replace_strings --find 'old_string' --replacement 'new_string' example.txt --verbose +python -m find_and_replace_strings --find 'old_string' --replacement 'new_string' example1.txt example2.txt --verbose +python -m find_and_replace_strings --config my_config.json example.txt --dry-run --verbose +python -m find_and_replace_strings --config e2e/.find-and-replace.json e2e/precommit-e2e.test --dry-run --log-level=INFO +``` + +## Run tests + +``` +python -m unittest tests.test_main + +``` + +## How to run it using installed python package + +``` + pip install find-and-replace-strings + find-and-replace --config .find-and-replace.json README1.md README2.md +``` + +also if you prefer to use a direct mod + +``` +find-and-replace-strings --find "old_string" --replacement "new_string" README1.md README2.md +``` + +## Dry run + +Inside the project + +python -m find_and_replace_strings --config ./.find-and-replace.json ./README.md READMEtest.md --dry-run + +or using the deployed package + +find-and-replace-strings --config ./.find-and-replace.json ./README.md READ +MEtest.md --dry-run + +More example: + + +python -m find_and_replace_strings --config e2e/.find-and-replace.json e2e/precommit-e2e.test --dry-run --verbose + +python -m find_and_replace_strings --config e2e/.find-and-replace.json e2e/precommit-e2e.test --dry-run --log-level=DEBUG + +``` +python -m find_and_replace_strings --config e2e/.find-and-replace.json e2e/precommit-e2e.test --dry-run --verbose +INFO:root:Running in default config file mode +INFO:root:Replacing {{BUSINESS_UNIT}} with examp"lebu in e2e/precommit-e2e.test +INFO:root:Replacing {{PROJECT_NAME}} with exampleproject in e2e/precommit-e2e.test +INFO:root:{{PROJECT_NAME}} would be replaced with exampleproject in e2e/precommit-e2e.test +INFO:root:Replacing {{GITHUB_REPO_URL}} with https://github.com/examplebu/exampleproject in e2e/precommit-e2e.test +INFO:root:Replacing {{PROJECT_DESCRIPTION_SLUG}} with Example project used to demonstrate all aspects of a project development and deployment in e2e/precommit-e2e.test +INFO:root:Replacing tucowsinc/iaascloudenablement with tucowsinc/example-github-team-name in e2e/precommit-e2e.test +INFO:root:Replacing {{PROJECT_CONTRIBUTORS}} with * [Andre Ouellet](mailto:aouellet@tucowsinc.com) in e2e/precommit-e2e.test +``` + +## If you need more help with the available flags + +``` +python -m find_and_replace_strings -h +usage: __main__.py [-h] [--config CONFIG] [--find] [--replacement] [--dry-run] + [--log-level LOG_LEVEL] [--verbose] [--usage] + [files ...] + +Perform find and replace operations on one or more target files. By default, the script reads the +search and replacement entries (strings) from a JSON file. You can also specify the search and +replacement strings directly as command line args by setting the --find "search_string" and +--replacement "replacement_string" argument options. + +positional arguments: + files File(s) on which to perform search and replace + +options: + -h, --help show this help message and exit + --config CONFIG PATH to JSON config file containing find and replacement entries + --find String to find in files + --replacement String to replace with in files + --dry-run Perform a dry run without making any changes + --log-level LOG_LEVEL + Set the logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL) + --verbose Print debug and info messages + --usage Print example usages + +``` + +## Building and Publishing + +To build and publish it to pypi run with proper token + +``` +bash assets/publish.sh +``` +or create a PR and after merge the changes will be published to the artifactory. + +## Contributing + +Contributions are welcome! Please feel free to submit a Pull Request. + +## License + +This project is licensed under the terms of the MIT license. + + +## Reference Info + +- https://www.gnu.org/prep/standards/html_node/Option-Table.html#Option-Table +- https://setuptools.pypa.io/en/latest/userguide/declarative_config.html +- https://packaging.python.org/guides/distributing-packages-using-setuptools/ +- https://autopilot-docs.readthedocs.io/en/latest/license_list.html +- https://pypi.org/classifiers/ diff --git a/assets/publish.sh b/assets/publish.sh new file mode 100644 index 0000000..5133bd2 --- /dev/null +++ b/assets/publish.sh @@ -0,0 +1,18 @@ +#!/bin/bash +rm -r dist +# Check if twine and setuptools are installed +if ! python3 -c "import twine" &> /dev/null; then + echo "twine not found, installing..." + pip install twine +fi + +if ! python3 -c "import setuptools" &> /dev/null; then + echo "setuptools not found, installing..." + pip install setuptools +fi + +# Build the package +python3 setup.py sdist + +# Upload the package to PyPI +twine upload --repository-url https://upload.pypi.org/legacy/ -u __token__ -p $PYPI_TOKEN dist/* diff --git a/assets/pypi-package.png b/assets/pypi-package.png new file mode 100644 index 0000000000000000000000000000000000000000..7e97d251156825fa007b960f124ad68d4afa6ca2 GIT binary patch literal 70767 zcmeFZWmKHYwl0jj2MO-MT@u_Kf;O&!#@$^LB)A86C%8L-;O_43?s7Y8?X}lBXXo5s z-?-!ZaTudV7w=nDvu4$-Y4ucxD14MaM!-V=0|P^rk`z?}1A|Tm0|Ot0g8@BRNVUlW z14Ae<6A@965)mO$u(vTbvjl>HNruEH!m7mfWB2YpPT({6IttrNXo1mW!78|8#YaKG zQFaR|pyO9HKzz+6gBXBd3?^xy6`S!7{v=jupcf@3=*Ea`nc0H+;q$WBaf`=@k@xP- zNSep)Q0nC?SZ0(4gb*t|v><|j&fBeR>CoV?>~(Q47)fy2G4K!AJaQdyaA%Og!=pa6 zk*HwoY^xmU{nzEMuWGS7wv%&Ug5Yqy_7ih7a2diqa9Sj?uwa0QyV{vtMB@fX0}WCo zk{`FmAG{+Zj6XO>6c`%{`!9hBlIyJZ(;@U32H< z=GvoK3#t=`pp)>%W)M9V@Sgkh;ggvf-`JK>G!O4jAjM~Bd46O_SawH8jdq*dPDbKay4 z-u4#^g^)Uj}uQTp{CZP~dBl>&Z)n7gC0aPS$sNV;=XZXab3Zghg} z-aLNECb4_gZc9A;(U!ikqCD|3#S}@So05*U!ElmRt00g#s@|7`Ofp?ms=H-B9to>$&|+3w*JPdv!FYgMaz|Qc zKgmp%zb+AfkwD}>Rsy4_1~=sz#fE3w@!U%yMk0ZbB0LLGjN1UwgvR2{Oa13L=wqLZ-#HO?PF0Xf3ga1F{7+N1;87FQd#zSHXr>MDTr z3@n%0Wh6ouo;sV09h?oM63jDNC zyI?^U;uxS1#~lh;FlUTsk1ou=zk5w3W0R+LhyhHR!*@(c%ie(`vb;eVvfKE z|DNn3Q@n+@$G$h>LD}bV1T#y;y*ofB3?c0n-N60?Um5Khau;`(E=pz=b0q0UL1e~O^w}}h z@e8})gKz>Ve#~;;xDmP=Rb#@EkhiF}e3pVE4PzX&G&hhcFTy_hBK1PUpVok^Gj2`N zGKMer zZm}l8hr%nsK00n{SXo#QlEgcSe9fKgArdUH>Fg%O`Kj+yT~jhsq#C8A>GaB+ikx~k z-z+1K8dAej2RXYu^L0X%-GQ4xC!i+E(-3De>u|gO3Ni*R2P;(|Y4u zrbwEs-#^cY;m<0JpUt3|shD4x%9Sk`}QZ!ovZV444BkZPc9!>dnbvtbfrHDP|y zqq4|8;8<@~zwg3kQEgP+Wh_4g9P%2f9$p+?)p)B}!7xC7qM=03$M9aGPNS`swQ56? zu*%(3u2$E)b+~ErAtn5K_*nk9@s#Q!XHVTyT_mFnY9`KD`JBO#_A%#tTIJ&>8^a81 zpC2!%u!cbtK{+VoGFJ(h?=UR>oMl7fs8Q$$omCeBwU+CX>ZA z?2GIx-^(b(6@)766J9Vf5*$6EC$14u9M%+}EV3?^C;OdYu!W>SU#!Bjf}*L(HzRdN zb)C5a+uO73vlqC@U}W@J@^~`0Xe-5r!ivJ=!jNsE!CMC6uXPb4;ocR(Ha{1z)YNFd z%j&v0$8XWj6vwiB`*!v%hees*M-|J0ECE-Gh-bvv@YJMzr@{8dUc514iR{ES)lge; zNwUT8DgAC7YWMTTXO?vQ^0-^hah^)DbLAKxC94TXdslm91b)hQ+w6LQ8QrJS02!T> z1v+MoErbWP#qyPUckMm8QmKFm)!f>=L9*<Y(r>>;*B!O~?a+C7Yp^3hQ7>)|R$}D^G8Wv~6-FM&RxKaW+J*@bvzBEvp4GvpY zA7vk*YIW7u?H2A>j^I9Z9tJ&VhiDsEuW|-iVF_gWY(39aFWW!*9j{(~aGSjtz6iZ6 zK$wG`b^dlrJ-fBXToo^a@4JQJDM{pWlC+WPm@9;ojgI>C>*MVs#|KF&mp&y zUDD+P3AD?_8|Icy;@5*+20_g}`LEh)IV!Pwm){fg7JVr8B-PTabe0w4bG}^LwibHV zkT$K~u-qD2h4+NV%U#Ib_jcNipR2WZKV7VSJ`EX(y^0MlaQqnmUODAbGc&=7Ve>ou z_h+kUtJVUqk1lmQHSb@|FD#C6!`Xl)+auWN{{%1il3(^XZL0`%NMn68V$U1?(?sQcZgEb6SV5; z7pxbTtxt6(JWe7m(-2*jPj%aRTU?%BsvcWWg^78OLQf8T`rJNTP~VR)_TolTWmtND zbxXdLJ4TazAe03|yu!IUa)AsKd?nz92j_z85k6(1P=P(fj(ehkTHQ^u%K@Y&_^^A;;*;R$(fLUmBB}URTNSd zk&*&^D;wGafz}SDHjXc5HpHN=X3bPUnm|sL+t9{}(ZI;&6Ohr>%Jx?iFkV+~P|*tL zXh7m>Wohle?aD{?M-6UJ`PXA6GLk>4I9l+Lsmm#lh}hTzNjMpq8JWrW5lBc#c$=cyxY=IbL`t^p1 zg^`)*-+hCc^8R|ttzhN~wA2tavjR;Ihz379CmZh{_5b6|-xUARQvL6iEbMIT|7`k? zSO2{!zyWA4Vq*oO>B#^0()_FOKVSZ4(uyyU- zU&H&a151Tx9y^i^oTL%-%V8X&nbT78`btsBs)t~YxMizRYsv6^>~XvHy@W7?Cs0W# z>@$^kUowP^MxruJ(oZCA;_LSjJilQ=u=&kdOmn_keRIwZxUw$a? zpw(z+6A@NH-a%j91bV(=&Q}S@)a7nVt9mC*hKoTx%Ir44N$q3OX^6#F?dZ259bx1r zxG*UkJN`@+`mN-wC4fN=TeH}aA}C~5LTv=J=BCJ3Qm7Clv>cK3mlwti2^=!wFA6$e zoLV@CwwKq72151i>C#{j9nqDznCxXbgwPhCW9-q{83;`@%Vfu?x(z-&yH$Bv)Tuma ze9B1<8G@lA{++Z5#v(G0Ep2r#fdn4R&wE|hUN{J1zN9LaEp*VRq&QL$Ai>@s3!R0! zgEi5TkQIo-&DO)H%|nRGeU#^V?nX!5v~n!e(-b6`Fc1 z9ZKm*8Yu4`0p$*m{uEs9h=hu%XLH-;UvX4%A{CQNz!Z9$AZ_GYIJ6Q&xgR8x3X{Zh zr#c|UM@PvxSHQ<|ss1}AIuz0M#V-{NcExGNUZ5@jjiD|6tTZY>R*=wA4voq@^uS0< z<@J%qXL(Y$ccW0UPU$J}h)aFat}((?>FXJSpphQLu(4snjmHDlmrjQY0@62s>b}On zJ|HJTb3twwP3stGl`RCZ&P-KYY_R`EEl{t8fEF-)uaXJGBndcF3gYw!FW69X{}DxR zvhzwIrR`HHe($!JiOL+zIFJ6G8s7O9N-f&g(q`@Ua6o9}mSmMD-LCix{p7puzL5d= z3oUnIH#?n{qrq|&A#0elLz98L-*N6+3nDwim?{m9z@F{f9$7C6?JARCmIk2pP1Vyb z^A>&bq|H;#t?!l)9b7tYRN5hfJKEj`YQ`QWt|-xhPe10>-}>Z92v=w)%>g*$37Oa? z9tT#jADT~=KJMyrktyn?&Zr4Ps=Mc=r9<#q{^I-^gPQt#oL2ih$tmNqUb(mSx4+e8 z<}qA@8cFRj0aN5>2uxFPzitKMMv`g0m6W%nYRD1JjoTLkv|QRM?|yDiGX@N)OYv! zDuf&v+M6u~NHWi@C2K!cRt`;fCmA$7Jz|X-dyCe(_f0M>Vf!g0gb>eL1z63%_52$5 z>w29A+3dWj`pCG)YYd-LZTO-E#n zAbE;~ab~2mifT|FCVD;2@Lr9xPl3WFtKM>yP7o z!F=|YD21M$JP(N-Rik&RzDmzmyw`eoC(#ef_I}_gd}Pr0&fP;QxV`==5;d{AkAy2q zje78Qr_ic5xIH(dzL10s|F`B;Fctx%d~#c~xu6#!ed`hK0>6CO%P20Um692Z>Y{za zMc)!YLD4`2H&$9>td`L7{ODe!Dzppz(VQ&@#pw2%!afl`*%x4ZmAPKo&}x#EE|wn1 z*8FB7*#kh^WY~8|P9d`uKGRXWAVTq>psGsR`FM#crLI6K7M;q23b5qpqa zrS2_{`T7-MzKKr6Tdzq@^@N*Zp7!tqH@iYUlI(OI%gpIuj3ZztTV2j&8bW1xdk@VA zHwul>z^|=}kwIcAz_X#-jSIPSbTPEkM6E1U;&+wG&y`)aQO~;nr~8M<+A2?vz#|6gSpS%>3t^i}KkVndne~Jr zJt;NfTR%9Ny7c!mB>+5BlEOEtH0cD0nc#13qlIW2mS}htoK0~0HkN}Z>I;QERYD@r zi)uqJ;Qv}!YlN+>t-hD%$L+Pw03`(llFG`;iAqBWsgEDAeSCc0&;I!NPX|Pwp+m5` z7O_Cd7lISQkGA9kOSeou<>TPzDQ4XH_fo>b5LPe`mocZtWzo?1D19>`VoI6PhH(Iu z`O?l8njM>^j1r%09w^DtJ2d2Z-&Fca% zqQ#|m`alP+?Bw+=J`};@Y3;WRE;ovIzjmqY&g$$A``{=QK;_nG|b{Y7{A#=5V z`a4X&vR+Der!Ep4ozk%wfKSxR#HhI54=jLu4G)iqyAl%TOdnZ7A!BqVrg9iU+juBR zDL~DZ3QCOhDKq@pN1HMCxuR>hmiwI)3_?N%lQbvQ(1rb)8g{}f+z+<;p$kkWNFcUu zpo~Y;b?}Xdc$`a-@HvN0Ze0}>qdjkzUF_F7z%?3dvlw%tzkQp#A#oK2xxcY+NGd}0 zr{zTNB43*1R&=XIt?^S{IaR$^-6gFb%mbDkO_)9&V3CV$m-X7%&D@&G3*_jf^4du= z36Wq(y$`+J>dzxCk51jV$Z55(@_sygWfZjB2c&ODaF0Mq-p<~tCsj+QOU%>4+)%#*? zR12sqo;3*Ms&@LN@VHmspKw4Y%ViekD#2V!#}pTv)6i4LSS{Xs&B$oa&F?9Z5NH-q zWvL+cX8k%qDGKD2C=A@o-vB{Gkjqw!r~Y3Z-2 zt{Wi}c`}LEofQtdABE8J@@nf>y|qw?`RQF&+zK2Iriv7uQE@}ys7c9LXur-sg!>P~ zD`dtat~I+IFC+`J^oHXX1PtwnAfTYk;B2-%9MBP3_P9K3C+acs^UIyd%(vLJ<%2hvSns`mpVbE+SX`as8ZOTIFr9E1AFa)mT%SQ zQrgYW-X$hKVvYo!>JdNC#Z)P~GBcE=0)|&U2S5bPxlj70)Wq=Ekr@x?N0_5XEwLsqjGaI4X zxt2H(YAC>MIN%A~GG~jR<%txEi6G@~hkg6t5uyz_8+BSA1YE|g#M`zulJiyl9(;!3l=FUevf3&ik=ah%Ih zM`w`M;r-@`_hrhc4h!l{jojhi36o4H`nOPC*A&oXr<(u7_V zjHxLoL=g}Xp`YKHI=@K@1*!FGjJs1YNMP~+9I|RX>q$RN!li(A-XM|scPL8qx)Q%0 zL*XZ&sC|R3QLVHl?(4w@c03JX$qlMi(1S;>slb9!FiZ*S=Y3T9C>KD_?!MP%ZZ&PW zVyX0J&)S_#?T3E6oX{|-@Hm%IViKL402O$DbBzWbwU6EoNh5+kg}=RLrPSuvjDF`s zm_ds!)E4voWrTaZma?|9>o8I-;Zc6?ofPdLflGvUxjDREp?~OI3O#F|e7+u=Y#>Eo zNcq6fd@_Jyx20VPxH}W~vEw0#@=O2wL)NWy_#%ISLI0CyfG%HiMyfzV?1_&$wS4^;zhBOv^l_riZ5GvgD}6YTSr-{j0DGr=b=LhNv3$*aE$OTw21AR z&xrtaMjhgMc@_V0+~-Y-gHC}fgXYV0(5cRC;&Gx2AhR)Jz>*UWv@^0wx&t2$9S$p3 z>-rHLTn^`*xW2b~gpHW;@B9>VcPnwQlTpoDvh&2@du_)-R@!xpUpha2r{fH}`}%0^ zqm&Z>7|K+d?QBy|Yc|C}4w>ENREU6i40CYnn7lL})oDe0o&5v~tF2jVpj|#gFzA6L z4PjwnRk^H^ru|%~wa6Cj6&*~i^@v;;fi1QtU^&29Kg(6XP54IM+v82+r~61b>X8kl zcKj)-5q>o(HI<;%c~KwAx-g{QB^D_cNNa1S>Y`9m z7>}eD*_<*`Q%8OK_Ev~+Tro#{x@`A*{Yp%m*W-a!qtD~<`_47hNdAYy^mg-UTEe9s zh7GtF5(}G)t^Qh|j;#UIpguladUsFHz)u{#Blci_D^h&?$5z+#PdXbAeh#IRg<4;| zdhAUyyy{A|8=DnKJ_vkXb2{@bG96e>=XdDIx!9e>d{MvG5)F=ycT|9%F|pSpA0l z5s?*7|N8xvMIhn&cOitzjLSAPjmwRuK{O+Q5 zzX$+Pr2H|B@~UPTGeoz;@XwC;Wj4>c=CJhDFTI6FKu&G{L8j&>0SZ=)+F3FS zM@^bKwNmxiGn}8pIHl^E_o|+h(?I%PzES0=4&8U^Zm zGMrNH{YhHla2^|P_E0S#m}V$5o#}T+q+T)*wYC8};-gLA=6u7LG{zteo5+SZuytee z{@@ZjBdZ(`k}OyJ)LUTA*8?4sV7j<+8{)Tf;|+6mN28GF`*5tRs!O_WHm{_sSmf`( zGEpFJZiQA}b|S~Bcl@So4OKY_S>%oM*5}+LjDuPcfZ+~Yz1+sJQJ&YsRrK@Y?O-TV z8#QQ`v}Jk*HUdg~<~LL=p|~n}{ZrtEP8MoqP8xTK2=AV*CglOgr&jLXqN1NsBz^9; z6S>!xzH7a2m5$r$5Cp)9l6x$Ci|{LHdwH;E)rIP|r3_PRt_x1* zC#cv&2Ca8<5)Ijo@S3i!4KMp+Sph39NWsBiZ2rrAA0!k!@6OiUJD8UJUP1YTg;KCK zR0UO7fjXS#R6^zi5kzFZnB^@?jgJMO)RjJ+?M+97Kd}jecKn3Mn&ZrkWzv=V@XvEG zTGx=UF=aqfNF-pbub32USrIxr9Ir*|lutLXII}czppPS8dsQoritJp>sv$)OQMurd?|tpql(}>nWYC+Xzx;}zox_*o@AImawsJ5azQ!kX zR<#@JV9sQGrBd&_y@e^c%LtEwwzj(V43C0>CLbul&${KXLdsA{=%O6{ zGvE9yO{VZU@K}>=n)dS-Iu4ZO`SJpK#G_`_M{dyl78h;WTkB@d!*JOgRd4*E5wG-v zdaB?o$?4|%9L0Co{ij3%k)jcqS`?UI3aBzXmH6-00v6~kbK1}MBHlcZM}-@*SO*9^ zUrky>V)foHyR4d-kZ0C--rFzl#wmX+`abI3gKM$}3P7y*L6NhS8dz--I&Tjl@tw8P zv@I-5+vM1X%tAVv5s<8xLZN;3)BqAL)@jg+jGMlFLbOU9P+78j{qRii+9rX&$T47+ ziHjRk2fp1%3%AJkPCY`gbNas79HPpdQsUkxvN&_8-Q*++Rz?3!Qr0fYL9UDe1uu8C z^QgTFcI7y;}gJDIKu zzEdLpu75WXC4qqoYk<7n+TlTWzAT55R|4Bbr6Pk~iBi&%ZKqwwN5PuEta@N3LW!fc z+NsXf=q!5dEhzfnHlAzderIwTQo=7`nBE?lI51GgrDv;4!N(~R4@WZz?xLQx{wZQy zg|%fQyZ2r*55s85$=-<(@378`AOW*X@5OsLc%k3mU;~peb&fOcLf_&Z;@}+$=dXZ$ z1o|t23k?#fy4Gqn-AKflr>8`6A@%^?{>*wn!G(h|J36Bh|&!GmaoS(GH&T<%Z3V5e1M!BLavpzf@Bcq0~!GqIiZHbN!ao?6CBv%=9x8(jB-(U6XW9c zl<;~S7kishqlCi2o0!Kk(2c}>E-M$x-i(GX%&&SsejoAfvBQ7a0p2Hr61yG{JuYBv z%?f3`8eL+g^VKGY^LP9`w-mq9e0~pbN2-)0#wB4oi^NbxVS$r#t;S%aNwrWlJ9(|6a7l(f?;3A>aTMMy6kk00vhU?eKU#uT})rh zhwvADx%0P6)`;+=VLn)h`xx>MSS~$oViO8_uXS5jop!==s|U3gjiC+IS?xtCeICJE zDY%m`9?%NC-ESK0t9p5%-3=Jz)*2oEW}Qm+a~t5C@*ar{ppVu2+Snw?`hiq7k$?Vn z&9zhC>9r2NZ_&wl^UFP-U5-pO1M9I)eBpJy9(0D90HI@9;~4s$mV>vxMA#@5k)rT+ zHDDjv-rku}laQVtamQ+OIhuU6xYT3O_%ZDhPui$fg72kGX+mwr5;py*sU*CFP6@2Oweq(n&Ju>)RVL>FN*L6Nei*1AwGm6=;Cy?s?4GFb>BrK= zrG@>HA|fX4h^y$?Lg8{)W}%hHSr@0|*9&}8czF1S@#WX7Ehs#5=H|sP!Ma9kngQiG z&P`XVfT?dbQxad0A$E3*uN-zh581IVX4bh8s+Lz0tLZq!XY%%g+z3WJuj}}-QA_-a z`h#9GQ0%#gCg|nnDX{qoyDf2CVR5;RwC@R|J`aG*UfsdywRx_ViJPEQnlD5;9LeVW z{G>-o462*`Mn)bRUR7Hq?IX5NMyXN&L|vS7TT|<+8env;Prc{GYJgh75Zc}lY;^Tlq43?p`ml#XY7>81pSl@P`qSai z`?cW~(^M543Ot95XD>tgI%vPGbzdoyHT_&BHvkWFz2X6A{RB~d8nI4m9M=Sd&!J!C zf2Z({;w8&}u&`O14Z+@Pws$*453Exq&dK$x6V?I}`>iW#QoqZRT~HZ~WwTmekC^Y~ z7aZL&+#YHU7S}U(H}Kt2p+R2r3^1KBpy`5oZoR2gu2v90DEB)_8$0UHhF<#t{n( z;!^r5DJfkoQ#9OtA>QPH!J~Hh%zfsx%rbtn?fMxcBZdeht~GMVPfq@LVaUL@3dWF> z#3Oq+R3?S=GBsamuit+eSP^5Gd-j1z;dX{YGaC19e5aGh)6tTE3Dy1LW#3N%NY9r3 z6?Y@LY!7r3DcUE{y7*--U&(1SJrPHoVgf?(uA}NpWXE5XIg-$yMBR|srv$~}L$O?l zsBWJsG|+5c7Q4&16&|KFZy|;YD?`DTPM+3w@?+L@fJeI#!y9z=3VE8 zzU^0Av0UJ*0fs`vMD!V_UP zKy^Rn-lc|}P?2ylX{-?EZt*eZfQ^gh_e?e{+@fKQP_3gYJHH{zZ8*w(67~w`Z z_9=DUdh4_@Ene%T4~CTs5>Q!+N)VLP3bSvKITDu5dKlJ2u^D2rM1mH8Jk67<)2wj5 zi*dbgXZ@3~{ju&eE7y5e6v%}!>=Z!5EZ#pkQ_DM0k^JS)7uRLImlr$p8 z3qNV0u-crR0Jf^*JfH)8z?%1AFXsW<9VQu>l3yv)3UTdmvucp>%~Y}k8ZOT65YFD& zv;UFFP_KU8+oJcRucIX-_0wO|C-q{o)-p4q!Npsz|V7LE*M|_a0IFR`OE{D_? zs|Z6iiX$|6u;4m8c6M~1s-Ue^d$e4^$!JtD04ZCQ1h;VqXzYER)8!G&WLdT}^0uO%ogfNFglza`Sc5p_k@0dS>h@5x za-h9bQauv@2#H68ahV7Z{lt|@-sjo7Tf<0Ix^RbAc&Pnk6^dYg8GMo=u1xW?Ne z1RNpXlcwyK;QOdlBVxF-36kIPWcNO2S!_zlL zl(wbcbhsFZ)-muwg<7Co=8X?vkwy%8#O_muC{4*ljMHNk1zo$k9u{W=#A^kDs#UV|a_;SCSv0MxpLBE#H=W#%C1M>O5-$K@g`8;2%HSQ!^9OxJepPpsBiezqs9QbK) z%ee2~KoRoiFJI=79Zj2C&Ixem;dV$Y(0cv-8WbGxdc80Dsd$hBXSY9-u)-)mtp^cr z!MIk6j}y&XY^6 z_SHBZZZyHW5@#PKO0|HNF=*Lr{!~--_PW&W2!?e0n zc-{KXfmKv@l~&XY8!nNp%Rh;~V?Pm;45$;R;BYGx;0_Z~rFw{$9BQXN^(YBBdpZWU zTPoHrj5E7Zdb^jr1vmd%zLcT&h}#K`yO|wUiOgT84Gxp6b>h**mm^pW-yL+6;LUDB7bjQ2L(daFY z#Rn9q6qW{3yaVx^naXGm%d4*4?mFDwP?Bq6pQkJnQf1qLI2u-iA5e>8$LaXLQk~7c z#6B&IEdGL=RmkILyy8)-t`YR+Zw=d7Kq0UE>~&K3W{172O$5)$hvlF@5;loojI51K zW1p3H8VsRvo)oM%L0POQhY_xz&Yc^l$GXd?`C$5~gcV!u zOO+4=6Xe$`(Wp0hklGw^$Y;5vf>tO>i)H1c9JcQXt}HVw>wVbMHwiQ?*S(7eG9^U7 zmiwMoN+B}Ji^04U`Fb3<@Or1>7PT7|+pJwg(md48_~62XEIWI z!~FhnkPmcTfv30@hv}0Pv&EF7l#^!@DT<}7n#XGC@78ea-Su!xi&(pZ&1{n5eIGl2 zF@3;ZP+!w60oC7UO_~U07?{c z141$gO4GApJT7Afe7!NEKS%@3r{jp26FSKuhO=J3nel{-zG4=7KTy;&*rpo+iyjinvbABXx5bUqO28`I(t0e8AP1cFm*FrxCQjO4z0*QG7p>~| z>8++{oSd{r>b@8X<02BoHj<*X+HH!vfwa^yzk~Wnd~78Vl5}vGF;wy}@}d|TF?P=` zZqmve&Q!4YT1q(R7|NFpe9G|^bO7i$hD+%ZHGgF7kTAbz?eptQNudL+?{POoDfeKz zM_{7qwKd{s{mDlzrX1iFR=uKSMnFWYxMs9QqrakDHg9PE>yvfD?W8@X*sIq z9)6y*4+U=5ITjtEZL;@y=0DI}#G7lcJV9j8LQcmtKvk6%3|?-Zl;)*tGDO5?W*{ zAG&5}4(PIuEh(+QpTI&#_zU_Xh*S1R$b4yHFrDm4qdm(rr*e@Q6DkfgisyVKb(Q)8 zC8aBdN>g}3`m*v9ZJg}+bYX|q#9@E#2PVZ;$0;8b&buVv0(~tT@Noc zH%GqlqAm{Pu+`)9_PAo^M{pyRhay?!xL(0~zopc!Vkn_9MuDhQjDkV4sYVPJY*Y2@ zs_o>G;?`|jpjv+k0f{bu8^(UMY<5eV>iZ8GiCY!g@Kj12AsuJyT^6u3j#ZKYRE8a* zakU+peg$dHngO$25x?*n$s(Dgz<^}K_6FoXQ5C=Nh<*t)pjd0rQ?Ee{)07BY`3y&6 z2pv7^0aj@Ds)ioR^Co_&1!EuWpnxsiO8UdB1+j=fl25^Lu0c|T z@=@K7Po!-dmih>jozoUJjkX@x9}Y{alaI~{@m@n|`b{KfpN62(9W1LJ7X7ku&H)k*pLk3OtvA^XBl6>*Z;pYM;N z-@b0g921hS0}ctV$=(IWnS8~x0n85*>`tKvEB_Nu^A`qJa1w4x;9`>fv9lJGAe0d5 zXiZ%;2Z1`G7}FrH_Ac{)z$%kdQXNflUV4RL2R=0YXH-O|L=lSm>_kjemp48c5FrXq zi9G$ZB?$*?cxz<+~PnU#Uq$%J9*Ny|dOP;@>O!PaIS%$dP=9)&^3)Ll85a>LA=- z$X5h40kWJ>gsso$MOC&UO{I$|~vt+5XQ)>e9JLhuBoTH-{9I zxIU5v6XiNi4yia%jz*e2LB*auq?OX1oY=z8`x6taT($}fy}yMbQ?FHmMnLq&9vahh z6C!`hpd{zJg2s?ej3FflS7UAJ!g7`RRhbUS`8 zrSJEdYo(v^8dxQJZ{(DE?y3T?sD4Myujki^M5S0GKSB+eAF&Je zU?k72RIk1bZU!b9&P`~HH+&Wb?hX?_?cUOaR(umpP8GL$-41F(W0KDH{q;Gw1DBpe zqKxLlN&Vh49SA);29|-{8o9*vhfJGlK-?{N{zJOPyni7~|3tUeLcoR=Z&fqgU$lR} zkHI`3-@OWpD&ix{mC(+-E55)BA8+}7V8O2cb9#dTFwI}p?D~zKZ%TP(A%=Re+LJ{?<=3FI>yIsddpm2s0y+^o6Yw{u6n6tOin8H;*fqeKcz%3V8$M zr~Tz|CW*u`vol76QR)J2t}0VdAGIZsH*DB|Z#t)QaI zaETh%pIeS9De7DA7KczV%qA-=Dtw;&23-WW5rAm2p@MSyh*C;Ph|3cC(
#iDdN2vPi6LH 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 z%0A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 NeM9GZ&ei*Y{|BFgXj1?H literal 0 HcmV?d00001 diff --git a/find_and_replace_strings/__init__.py b/find_and_replace_strings/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/find_and_replace_strings/__main__.py b/find_and_replace_strings/__main__.py new file mode 100644 index 0000000..97aede6 --- /dev/null +++ b/find_and_replace_strings/__main__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +from .main import main + +if __name__ == "__main__": + main() diff --git a/find_and_replace_strings/main.py b/find_and_replace_strings/main.py new file mode 100755 index 0000000..45f8487 --- /dev/null +++ b/find_and_replace_strings/main.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import os +import argparse +import fileinput +import json +import sys +import logging + + +def replace_in_file(filename, search, replacement, dry_run=False): + logging.info(f"Replacing {search} with {replacement} in {filename}") + with fileinput.FileInput(filename, inplace=not dry_run) as file: + for line in file: + if search in line and dry_run: + logging.info(f"{search} would be replaced with {replacement} in {filename}") + elif not dry_run: + print(line.replace(rf"{search}", rf"{replacement}"), end='') + + +def print_usage(): + print("Example usages:") + print("python -m find_and_replace_strings --config e2e/.find-and-replace.json e2e/precommit-e2e.test --dry-run --verbose") + print("python -m find_and_replace_strings --config e2e/.find-and-replace.json e2e/precommit-e2e.test --dry-run --log-level=DEBUG") + print("python -m find_and_replace_strings --find 'old_string' --replacement 'new_string' example.txt --verbose") + print("python -m find_and_replace_strings --find 'old_string' --replacement 'new_string' example1.txt example2.txt --verbose") + print("python -m find_and_replace_strings --config my_config.json example.txt --dry-run --verbose") + print("python -m find_and_replace_strings --config e2e/.find-and-replace.json e2e/precommit-e2e.test --dry-run --log-level=INFO") + + +def main(): + parser = argparse.ArgumentParser( + description="""Perform find and replace operations on one or more target files. + By default, the script reads the search and replacement entries (strings) from a JSON file. + You can also specify the search and replacement strings directly as command line args by setting the + --find "search_string" and --replacement "replacement_string" argument options.""" + ) + parser.add_argument( + '--config', default='.find-and-replace.json', + help='PATH to JSON config file containing find and replacement entries' + ) + parser.add_argument( + '--find', dest='direct_mode', action='store_true', help='String to find in files' + ) + parser.add_argument( + '--replacement', dest='direct_mode', action='store_true', help='String to replace with in files' + ) + parser.add_argument( + 'files', nargs='*', help='File(s) on which to perform search and replace' + ) + parser.add_argument( + '--dry-run', action='store_true', help='Perform a dry run without making any changes' + ) + parser.add_argument( + '--log-level', default='WARNING', help='Set the logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)' + ) + parser.add_argument( + '--verbose', action='store_true', help='Print debug and info messages' + ) + parser.add_argument( + '--usage', action='store_true', help='Print example usages' + ) + args = parser.parse_args() + + if args.usage: + print_usage() + sys.exit(0) + + levels = {'DEBUG': logging.DEBUG, 'INFO': logging.INFO, 'WARNING': logging.WARNING, 'ERROR': logging.ERROR, 'CRITICAL': logging.CRITICAL} + level = levels.get(args.log_level.upper(), logging.WARNING) + if args.verbose: + level = logging.DEBUG + logging.basicConfig(level=level) + + if args.direct_mode: + logging.info("Running in direct mode") + for filename in args.files: + replace_in_file(filename, args.find, args.replacement, args.dry_run) + else: + logging.info("Running in default config file mode") + try: + with open(os.path.join(os.getcwd(), args.config), 'r') as f: + replacements = json.load(f) + except FileNotFoundError: + logging.error(f"Error: {args.config} file not found.") + sys.exit(1) + except json.JSONDecodeError: + logging.error(f"Error: {args.config} is not a valid JSON file.") + sys.exit(1) + + for filename in args.files: + for replacement in replacements: + replace_in_file(filename, replacement['search'], replacement['replacement'], args.dry_run) + + +if __name__ == "__main__": + main() diff --git a/pypi_bumpversion_check/check_version.py b/pypi_bumpversion_check/check_version.py new file mode 100644 index 0000000..4392fd3 --- /dev/null +++ b/pypi_bumpversion_check/check_version.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +import toml +import sys +import requests +import subprocess + + +def main(): + # Load the pyproject.toml file + data = toml.load(open("pyproject.toml")) + + # Get the current version + current_version = data["project"]["version"] + + # Check if the version is already published + response = requests.get(f"https://pypi.org/pypi/find-and-replace-strings/{current_version}/json") + + if response.status_code == 200: + print("This version is already published. Please bump the version in pyproject.toml.") + sys.exit(1) + + # Check if pyproject.toml has been modified but not committed + modified_files = subprocess.check_output(["git", "diff", "--name-only"]).decode().splitlines() + + if "pyproject.toml" in modified_files: + print("The version in pyproject.toml has been changed but not committed. Please commit your changes.") + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/pypi_bumpversion_check/requirements.txt b/pypi_bumpversion_check/requirements.txt new file mode 100644 index 0000000..a026d3b --- /dev/null +++ b/pypi_bumpversion_check/requirements.txt @@ -0,0 +1,5 @@ +# Requirements for pypi_bumpversion_check +toml +requests +#toml==0.10.2 +#requests==2.26.0 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..98f506f --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,43 @@ +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" + +[tool.setuptools] +packages = ["find_and_replace_strings"] + +[project] +name = "find-and-replace-strings" +version = "1.0.0" +description = "Python package and pre-commit-hook for finding and replacing string(s) in file(s)." +readme = "README.md" +license = { text = "GPLv3" } +authors = [{name = "OpenCEPK Open Cloud Engineering Platform Kit", email = "opencepk@gmail.com"}] +requires-python = ">=3.9" + +keywords = ["find", "replace", "string", "file", "pre-commit", "hook", "git", "tool", "utility", "opencepk"] +classifiers = [ + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "Natural Language :: English", + "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", + "Topic :: File Formats :: JSON", + "Topic :: Software Development :: Pre-processors", + "Topic :: Software Development :: Version Control :: Git", + "Topic :: Text Processing", + "Topic :: Text Processing :: Filters", + "Topic :: Text Processing :: General", + "Topic :: Utilities" +] + +[project.scripts] +find-and-replace-strings = "find_and_replace_strings.__main__:main" diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..977789b --- /dev/null +++ b/setup.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +from setuptools import setup, find_packages + +setup() diff --git a/tests-package-e2e/.find-and-replace.json b/tests-package-e2e/.find-and-replace.json new file mode 100644 index 0000000..1adb550 --- /dev/null +++ b/tests-package-e2e/.find-and-replace.json @@ -0,0 +1,30 @@ +[ + { + "search": "{{BUSINESS_UNIT}}", + "replacement": "opencepk" + }, + { + "search": "{{PROJECT_NAME}}", + "replacement": "exampleproject" + }, + { + "search": "{{GITHUB_REPO_URL}}", + "replacement": "https://github.com/opencepk/opencepk-exampleproject" + }, + { + "search": "{{PROJECT_DESCRIPTION_SLUG}}", + "replacement": "Example project used to demonstrate all aspects of a project development and deployment" + }, + { + "search": "{{PROJECT_TEAM}}", + "replacement": "opencepk/opencepk-exampleteam" + }, + { + "search": "{{PROJECT_CONTRIBUTORS}}", + "replacement": "* [Open CEPK](mailto:opencepk@gmail.com)" + }, + { + "search": "{{SPECIAL_CHARACTER_TESTS}}", + "replacement": "\"SomeValueInDoubleQuotes\"" + } +] diff --git a/tests-package-e2e/README_TEST_PACKAGE.md b/tests-package-e2e/README_TEST_PACKAGE.md new file mode 100644 index 0000000..b0b9c9f --- /dev/null +++ b/tests-package-e2e/README_TEST_PACKAGE.md @@ -0,0 +1,9 @@ +# Target test file for find-and-replace-strings python package + +business unit is: {{BUSINESS_UNIT}} +project name is: {{PROJECT_NAME}} +github url is: {{GITHUB_REPO_URL}} +project description slug is: {{PROJECT_DESCRIPTION_SLUG}} +project team is: {{PROJECT_TEAM}} +project contributors are: {{PROJECT_CONTRIBUTORS}} +special character tests: {{SPECIAL_CHARACTER_TESTS}} diff --git a/tests-package-e2e/README_TEST_PACKAGE.md.expected b/tests-package-e2e/README_TEST_PACKAGE.md.expected new file mode 100644 index 0000000..ac6d9f7 --- /dev/null +++ b/tests-package-e2e/README_TEST_PACKAGE.md.expected @@ -0,0 +1,9 @@ +# Target test file for find-and-replace-strings python package + +business unit is: opencepk +project name is: exampleproject +github url is: https://github.com/opencepk/opencepk-exampleproject +project description slug is: Example project used to demonstrate all aspects of a project development and deployment +project team is: opencepk/opencepk-exampleteam +project contributors are: * [Open CEPK](mailto:opencepk@gmail.com) +special character tests: "SomeValueInDoubleQuotes" diff --git a/tests-package-e2e/README_TEST_PACKAGE.md.template b/tests-package-e2e/README_TEST_PACKAGE.md.template new file mode 100644 index 0000000..b0b9c9f --- /dev/null +++ b/tests-package-e2e/README_TEST_PACKAGE.md.template @@ -0,0 +1,9 @@ +# Target test file for find-and-replace-strings python package + +business unit is: {{BUSINESS_UNIT}} +project name is: {{PROJECT_NAME}} +github url is: {{GITHUB_REPO_URL}} +project description slug is: {{PROJECT_DESCRIPTION_SLUG}} +project team is: {{PROJECT_TEAM}} +project contributors are: {{PROJECT_CONTRIBUTORS}} +special character tests: {{SPECIAL_CHARACTER_TESTS}} diff --git a/tests-package-e2e/check-commit-hook.sh b/tests-package-e2e/check-commit-hook.sh new file mode 100755 index 0000000..053847b --- /dev/null +++ b/tests-package-e2e/check-commit-hook.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +# Create target test file +cp -f "tests-e2e/target-find-and-replace.txt.template" "tests-e2e/target-find-and-replace.txt" + +# Store the original content of the file +original_content=$(cat tests-e2e/precommit-e2e.test) +echo "Original content: $original_content" + +# Run the hook +python find_and_replace_strings/main.py --config tests-e2e/.find-and-replace.json tests-e2e/precommit-e2e.test + +# Check if the expected changes have been made +content=$(cat tests-e2e/precommit-e2e.test) +echo "Content after running the hook: $content" + +if [[ "$content" != "# exampleproject" ]]; then + # If the changes are not as expected, print the exit code and exit with a status code of 1 + echo "Exit code: 1" + exit 1 +fi + +# Restore the original content of the file +echo "$original_content" > tests-e2e/precommit-e2e.test + +# If the changes are as expected, print the exit code and exit with a status code of 0 +echo "Exit code: 0" +exit 0 diff --git a/tests-package-e2e/test-package-e2e.sh b/tests-package-e2e/test-package-e2e.sh new file mode 100755 index 0000000..8deaebd --- /dev/null +++ b/tests-package-e2e/test-package-e2e.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +TEST_DIR="tests-package-e2e" +TEST_SCRIPT_FILENAME=$(basename -- "$0") +TEST_SCRIPT_NAME="${TEST_SCRIPT_FILENAME%.*}" +TEST_TARGET_FILE="README_TEST_PACKAGE.md" + +#------------------------------- +# Run the python package +#------------------------------- +echo "${TEST_SCRIPT_NAME}: Running pre-commit using ${TEST_DIR}/.pre-commit-config.yaml" +(cd .. && python find_and_replace_strings/main.py --config ${TEST_DIR}/.find-and-replace.json ${TEST_DIR}/${TEST_TARGET_FILE}) + +#------------------------------- +# Evaluate results +#------------------------------- +echo "${TEST_SCRIPT_NAME}: Running (diff ${TEST_TARGET_FILE} ${TEST_TARGET_FILE}.expected)" +echo "${TEST_SCRIPT_NAME}: Fetching return code of diff" +diff "${TEST_TARGET_FILE}" "${TEST_TARGET_FILE}.expected" +evaluate_diff_status=$? + +#------------------------------- +# Run the pre-commit hook reset +#------------------------------- +echo "${TEST_SCRIPT_NAME}: Resetting ${TEST_TARGET_FILE} file to original state" +cp -pf "${TEST_TARGET_FILE}.template" "${TEST_TARGET_FILE}" + +#------------------------------- +# Exit with exit code of diff evaluation +#------------------------------- +echo "${TEST_SCRIPT_NAME}: Exit code = $evaluate_diff_status" +exit $evaluate_diff_status diff --git a/tests-pre-commit-hook/.find-and-replace.json b/tests-pre-commit-hook/.find-and-replace.json new file mode 100644 index 0000000..1adb550 --- /dev/null +++ b/tests-pre-commit-hook/.find-and-replace.json @@ -0,0 +1,30 @@ +[ + { + "search": "{{BUSINESS_UNIT}}", + "replacement": "opencepk" + }, + { + "search": "{{PROJECT_NAME}}", + "replacement": "exampleproject" + }, + { + "search": "{{GITHUB_REPO_URL}}", + "replacement": "https://github.com/opencepk/opencepk-exampleproject" + }, + { + "search": "{{PROJECT_DESCRIPTION_SLUG}}", + "replacement": "Example project used to demonstrate all aspects of a project development and deployment" + }, + { + "search": "{{PROJECT_TEAM}}", + "replacement": "opencepk/opencepk-exampleteam" + }, + { + "search": "{{PROJECT_CONTRIBUTORS}}", + "replacement": "* [Open CEPK](mailto:opencepk@gmail.com)" + }, + { + "search": "{{SPECIAL_CHARACTER_TESTS}}", + "replacement": "\"SomeValueInDoubleQuotes\"" + } +] diff --git a/tests-pre-commit-hook/.pre-commit-config.yaml b/tests-pre-commit-hook/.pre-commit-config.yaml new file mode 100644 index 0000000..f276adb --- /dev/null +++ b/tests-pre-commit-hook/.pre-commit-config.yaml @@ -0,0 +1,14 @@ +--- +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: + - repo: local + hooks: + - id: find-and-replace-strings + name: find-and-replace-strings + entry: "./find_and_replace_strings/main.py" + language: python + exclude_types: + - binary + args: ["--config", "./tests-pre-commit-hook/.find-and-replace.json"] + files: "tests-pre-commit-hook/README_TEST_PRE_COMMIT.md" diff --git a/tests-pre-commit-hook/README_TEST_PRE_COMMIT.md b/tests-pre-commit-hook/README_TEST_PRE_COMMIT.md new file mode 100644 index 0000000..8757397 --- /dev/null +++ b/tests-pre-commit-hook/README_TEST_PRE_COMMIT.md @@ -0,0 +1,9 @@ +# Target test file for find-and-replace-strings pre-commit hook + +business unit is: {{BUSINESS_UNIT}} +project name is: {{PROJECT_NAME}} +github url is: {{GITHUB_REPO_URL}} +project description slug is: {{PROJECT_DESCRIPTION_SLUG}} +project team is: {{PROJECT_TEAM}} +project contributors are: {{PROJECT_CONTRIBUTORS}} +special character tests: {{SPECIAL_CHARACTER_TESTS}} diff --git a/tests-pre-commit-hook/README_TEST_PRE_COMMIT.md.expected b/tests-pre-commit-hook/README_TEST_PRE_COMMIT.md.expected new file mode 100644 index 0000000..f5752d5 --- /dev/null +++ b/tests-pre-commit-hook/README_TEST_PRE_COMMIT.md.expected @@ -0,0 +1,9 @@ +# Target test file for find-and-replace-strings pre-commit hook + +business unit is: opencepk +project name is: exampleproject +github url is: https://github.com/opencepk/opencepk-exampleproject +project description slug is: Example project used to demonstrate all aspects of a project development and deployment +project team is: opencepk/opencepk-exampleteam +project contributors are: * [Open CEPK](mailto:opencepk@gmail.com) +special character tests: "SomeValueInDoubleQuotes" diff --git a/tests-pre-commit-hook/README_TEST_PRE_COMMIT.md.template b/tests-pre-commit-hook/README_TEST_PRE_COMMIT.md.template new file mode 100644 index 0000000..8757397 --- /dev/null +++ b/tests-pre-commit-hook/README_TEST_PRE_COMMIT.md.template @@ -0,0 +1,9 @@ +# Target test file for find-and-replace-strings pre-commit hook + +business unit is: {{BUSINESS_UNIT}} +project name is: {{PROJECT_NAME}} +github url is: {{GITHUB_REPO_URL}} +project description slug is: {{PROJECT_DESCRIPTION_SLUG}} +project team is: {{PROJECT_TEAM}} +project contributors are: {{PROJECT_CONTRIBUTORS}} +special character tests: {{SPECIAL_CHARACTER_TESTS}} diff --git a/tests-pre-commit-hook/test-pre-commit-hook.sh b/tests-pre-commit-hook/test-pre-commit-hook.sh new file mode 100755 index 0000000..3083a47 --- /dev/null +++ b/tests-pre-commit-hook/test-pre-commit-hook.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +TEST_DIR="tests-pre-commit-hook" +TEST_SCRIPT_FILENAME=$(basename -- "$0") +TEST_SCRIPT_NAME="${TEST_SCRIPT_FILENAME%.*}" +TEST_TARGET_FILE="README_TEST_PRE_COMMIT.md" + +#------------------------------- +# Run the pre-commit hook test +#------------------------------- +echo "${TEST_SCRIPT_NAME}: Running pre-commit using ${TEST_DIR}/.pre-commit-config.yaml" +(cd .. && pre-commit run -a -c "${TEST_DIR}/.pre-commit-config.yaml") + +#------------------------------- +# Evaluate results +#------------------------------- +echo "${TEST_SCRIPT_NAME}: Running (cd ${TEST_DIR} && diff ${TEST_TARGET_FILE} ${TEST_TARGET_FILE}.expected)" +echo "${TEST_SCRIPT_NAME}: Fetching return code of diff" +diff "./${TEST_TARGET_FILE}" "${TEST_TARGET_FILE}.expected" +evaluate_diff_status=$? + +#------------------------------- +# Run the pre-commit hook reset +#------------------------------- +echo "${TEST_SCRIPT_NAME}: Resetting ${TEST_DIR}/${TEST_TARGET_FILE} file to original state" +cp -pf "${TEST_TARGET_FILE}.template" "${TEST_TARGET_FILE}" + +#------------------------------- +# Exit with exit code of diff evaluation +#------------------------------- +echo "${TEST_SCRIPT_NAME}: Exit code = $evaluate_diff_status" +exit $evaluate_diff_status diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_main.py b/tests/test_main.py new file mode 100644 index 0000000..2241392 --- /dev/null +++ b/tests/test_main.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +import unittest +import argparse +from unittest.mock import patch, mock_open +from find_and_replace_strings.main import replace_in_file, main + + +class TestMainFunctions(unittest.TestCase): + @patch('fileinput.FileInput') + def test_replace_in_file(self, mock_fileinput): + """ + This test checks if the replace_in_file function correctly opens the file and replaces the specified text. + """ + # Mock the file input to return a specific line of text + mock_fileinput.return_value.__enter__.return_value = ['hello world'] + # Call the function with a specific search and replacement + replace_in_file('dummy.txt', 'hello', 'hi') + # Assert that the file was opened correctly + mock_fileinput.assert_called_once_with('dummy.txt', inplace=True) + + +@patch('argparse.ArgumentParser.parse_args') +@patch('find_and_replace.main.replace_in_file') +@patch('os.getcwd', return_value='/dummy/path') +@patch('builtins.open', new_callable=mock_open, read_data='{"search": "hello", "replacement": "hi"}') +@patch('json.load', return_value=[{"search": "hello", "replacement": "hi"}]) +def test_main(self, mock_json_load, mock_open, mock_getcwd, mock_replace_in_file, mock_parse_args): + """ + This test checks if the main function correctly reads the configuration file and calls the replace_in_file function with the correct arguments. + """ + # Mock the command line arguments + mock_parse_args.return_value = argparse.Namespace(files=['dummy.txt'], find=None, replacement=None, direct_mode=False, config='.find-and-replace.json') + # Call the main function + main() + # Assert that the config file was opened correctly and the replace_in_file function was called with the correct arguments + mock_open.assert_called_once_with('/dummy/path/.find-and-replace.json', 'r') + mock_replace_in_file.assert_called_once_with('dummy.txt', 'hello', 'hi') + + +if __name__ == '__main__': + unittest.main()