diff --git a/.github/workflows/bandit.yml b/.github/workflows/bandit.yml index 767dcef..4a3696e 100644 --- a/.github/workflows/bandit.yml +++ b/.github/workflows/bandit.yml @@ -28,7 +28,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Bandit Scan uses: shundor/python-bandit-scan@9cc5aa4a006482b8a7f91134412df6772dbda22c with: diff --git a/.github/workflows/black.yml b/.github/workflows/black.yml index 2f58726..726d409 100644 --- a/.github/workflows/black.yml +++ b/.github/workflows/black.yml @@ -6,7 +6,7 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: psf/black@stable with: options: "--check --verbose" diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 5654712..e18c521 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -25,18 +25,18 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} - + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v3 + uses: github/codeql-action/autobuild@v4 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v4 diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index 34be4e7..6f8c9bd 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -8,11 +8,11 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.10", "3.12"] + python-version: ["3.13"] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/.github/workflows/pylint_scores.yml b/.github/workflows/pylint_scores.yml index d9472f0..def3e10 100644 --- a/.github/workflows/pylint_scores.yml +++ b/.github/workflows/pylint_scores.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.10"] + python-version: ["3.13"] steps: - uses: Silleellie/pylint-github-action@v2 with: diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 5d32b71..f74107b 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -8,35 +8,34 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.10"] + python-version: ["3.13"] env: CC_TEST_REPORTER_ID: 0ab46c7acdcb9951ded95c2cb362eeec513807aa51c459b035509daf84e8f81e steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 + - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} + - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements-tests.txt + - name: Test with pytest run: | - pytest --cov loglan_core - bash <(curl -s https://codecov.io/bash) - ocular --data-file ".coverage" --config-file ".coveragerc" - - name: Install CodeClimate Test-Reporter - run: | - # download test reporter as a static binary - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter - chmod +x ./cc-test-reporter - - name: Run coverage report - run: | - ./cc-test-reporter before-build - ./cc-test-reporter format-coverage -t coverage.py - ./cc-test-reporter upload-coverage - - name: Finish build + pytest --cov=loglan_core --cov-report=xml --cov-report=term + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + file: ./coverage.xml + fail_ci_if_error: false + + - name: Upload coverage to Ocular run: | - ./cc-test-reporter after-build --exit-code $? \ No newline at end of file + ocular --data-file ".coverage" --config-file ".coveragerc" + continue-on-error: true \ No newline at end of file diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index a29cd73..abee20b 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -19,11 +19,11 @@ jobs: VERSION=$(echo "${{ steps.get_version.outputs.version }}") echo $VERSION - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: - python-version: "3.10" + python-version: "3.13" - name: Update version and download URL in pyproject.toml run: | VERSION="${{ steps.get_version.outputs.version }}" diff --git a/README.md b/README.md index 5824fbd..65805fe 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ ![Codecov](https://img.shields.io/codecov/c/github/torrua/loglan_core?logo=Codecov&logoColor=%23F01F7A&label=codecov) ![Scrutinizer code quality (GitHub/Bitbucket)](https://img.shields.io/scrutinizer/quality/g/torrua/loglan_core/main?logo=Scrutinizer%20CI&logoColor=%238A9296&label=Scrutinizer%20CC&link=https%3A%2F%2Fscrutinizer-ci.com%2Fg%2Ftorrua%2Floglan_core%2F%3Fbranch%3Dmain) -![Code Climate maintainability](https://img.shields.io/codeclimate/maintainability-percentage/torrua/loglan_core?logo=Code%20Climate) ![pylint](https://img.shields.io/badge/PyLint-10.00-brightgreen?logo=python&logoColor=white) [![Pytest](https://github.com/torrua/loglan_core/actions/workflows/pytest.yml/badge.svg)](https://github.com/torrua/loglan_core/actions/workflows/pytest.yml) ![Bandit Status](https://img.shields.io/github/actions/workflow/status/torrua/loglan_core/bandit.yml?label=bandit) diff --git a/loglan_core/addons/word_sourcer.py b/loglan_core/addons/word_sourcer.py index 5110241..56be024 100644 --- a/loglan_core/addons/word_sourcer.py +++ b/loglan_core/addons/word_sourcer.py @@ -262,3 +262,56 @@ def words_from_source_cpd(cls, sources: list[str]) -> Select[tuple[BaseWord]]: .filter(BaseWord.name.in_(sources)) .filter(BaseWord.type_id.in_(type_ids)) ) + + @staticmethod + def prepare_origin(origin: str) -> str: + """ + Remove text in parentheses, reverse characters between slash, remove slash. + + Examples: + zav(lo)+da(n)z(a)+fo/l(ma) => zav+daz+flo + be(rt)i+n+(t)rac(i)+ve(sl)o => bei+n+rac+veo + + Args: + origin: str + + Returns: str + """ + origin_list = list(re.sub(r"\([^)]*\)", "", origin)) + for index, char in enumerate(origin_list): + if char == "/" and 0 < index < len(origin_list) - 1: + start_index = index - 1 + end_index = index + 2 + + origin_list[start_index:end_index] = reversed( + origin_list[start_index:end_index] + ) + return "".join(origin_list).replace("/", "") + + @staticmethod + def get_parent_complex(origin: str) -> str: + """ + + Args: + Example: + zavdazflo -> zav(lo)+da(n)z(a)+fo/l(ma) => dazflo + zanynurkokmio -> za(v)n(o)+y+nur+kok(fa)+mi(tr)o => nurkokmio + cabsrusia -> cab(ro)+su/r(na)+si(tf)a => srusia + beinracveo -> be(rt)i+n+(t)rac(i)+ve(sl)o => racveo + Returns: + """ + origin_list = WordSourcer.prepare_origin(origin).split("+") + origin_list = origin_list[1:] + origin_list = ( + origin_list if origin_list[0] not in ["y", "r", "n"] else origin_list[1:] + ) + return "".join(origin_list) + + +class OriginParser: # pylint: disable=too-few-public-methods + """ + Test Class + """ + + def __init__(self, word: BaseWord): + self.word = word diff --git a/tests/test_sync/test_loglan_core/test_addons/test_word_sourcer.py b/tests/test_sync/test_loglan_core/test_addons/test_word_sourcer.py index a223a54..6dc49cf 100644 --- a/tests/test_sync/test_loglan_core/test_addons/test_word_sourcer.py +++ b/tests/test_sync/test_loglan_core/test_addons/test_word_sourcer.py @@ -1,4 +1,5 @@ """Base Model unit tests.""" + import pytest from loglan_core import Word @@ -15,6 +16,7 @@ class TestWordSources: # words_objects = [Word(**obj) for obj in other_words] types = [] + def test_get_sources_afx(self, db_session): afx = Word.get_by_id(db_session, 3) @@ -35,7 +37,7 @@ def test_get_sources_prim_d(self, db_session): prim_d = WordSelector().by_name(name="humnu").scalar(db_session) result = self.aws.get_sources_prim(prim_d) - assert result == 'humnu: humni' + assert result == "humnu: humni" def test_not_get_sources_c_prim(self, db_session): db_session.add_all([Word(**w) for w in other_words]) @@ -49,10 +51,10 @@ def test_get_sources_cpx(self, db_session): cpx = WordSelector().by_name("prukao").scalar(db_session) result = db_session.execute(self.aws.get_sources_cpx(cpx)).scalars().all() assert len(result) == 2 - assert result[0].name in ["kakto", "pruci" ] + assert result[0].name in ["kakto", "pruci"] result = self.aws.get_sources_cpx(cpx, as_str=True) - assert sorted(result) == sorted(['pruci', 'kakto']) + assert sorted(result) == sorted(["pruci", "kakto"]) not_cpx = WordSelector().by_name("pru").scalar(db_session) result = self.aws.get_sources_cpx(not_cpx) @@ -68,7 +70,7 @@ def test_get_sources_cpd(self, db_session): result = self.aws.get_sources_cpd(cpd, as_str=True) assert len(result) == 2 - assert result == ['ai', 'ai'] + assert result == ["ai", "ai"] prim = WordSelector().by_name("kakto").scalar(db_session) result = self.aws.get_sources_cpd(prim, as_str=True) @@ -83,3 +85,57 @@ def test_prepare_sources_cpd(self, db_session): prim = WordSelector().by_name("cii").scalar(db_session) assert self.aws._prepare_sources_cpd(prim) == [] + +class TestWordSourcerPrepareOrigin: + """Unit tests for WordSourcer.prepare_origin method using pytest""" + + def test_basic_example_1(self): + """Test basic functionality with first example from docstring""" + # Input: zav(lo)+da(n)z(a)+fo/l(ma) + # After parenth removal: zav+da+z+fo/l + # After slash processing: za + result = WordSourcer.prepare_origin("zav(lo)+da(n)z(a)+fo/l(ma)") + assert result == "zav+daz+flo" + + def test_basic_example_2(self): + """Test basic functionality with second example from docstring""" + # Input: be(rt)i+n+(t)rac(i)+ve(sl)o + # After parenth removal: bei+n+rac+i+ve/lo + # After slash processing: bei+n+rac+i+velo + result = WordSourcer.prepare_origin("be(rt)i+n+(t)rac(i)+ve(sl)o") + assert result == "bei+n+rac+veo" + + def test_empty_string(self): + """Test handling of empty string""" + result = WordSourcer.prepare_origin("") + assert result == "" + + def test_no_parentheses(self): + """Test string with no parentheses""" + result = WordSourcer.prepare_origin("abc/def") + assert result == "abdcef" + + def test_no_slashes(self): + """Test string with no slashes""" + result = WordSourcer.prepare_origin("abc(def)+ghi(jkl)") + assert result == "abc+ghi" + + def test_multiple_slashes(self): + """Test string with multiple slashes""" + result = WordSourcer.prepare_origin("a/b/c+d/e/f") + assert result == "bca+efd" + + def test_slash_at_boundary(self): + """Test string with slash at beginning or end""" + result = WordSourcer.prepare_origin("/abc+def/") + assert result == "abc+def" + + def test_complex_case(self): + """Test complex case with multiple parentheses and slashes""" + result = WordSourcer.prepare_origin("a(b)c/d(e)f+g(h)i/j(k)l") + assert result == "adcf+gjil" + + def test_adjacent_operations(self): + """Test parentheses adjacent to slashes""" + result = WordSourcer.prepare_origin("a(b)/c(d)+e(f)/g(h)") + assert result == "ca+ge"