plain-parser 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- plain_parser-0.1.0/.flake8 +13 -0
- plain_parser-0.1.0/.github/workflows/lint-and-test.yml +44 -0
- plain_parser-0.1.0/.github/workflows/publish-dev-to-pypi.yml +147 -0
- plain_parser-0.1.0/.github/workflows/publish-to-pypi.yml +117 -0
- plain_parser-0.1.0/.gitignore +8 -0
- plain_parser-0.1.0/LICENSE +21 -0
- plain_parser-0.1.0/PKG-INFO +62 -0
- plain_parser-0.1.0/README.md +39 -0
- plain_parser-0.1.0/pyproject.toml +68 -0
- plain_parser-0.1.0/src/plain_parser/__init__.py +27 -0
- plain_parser-0.1.0/src/plain_parser/concept_utils.py +221 -0
- plain_parser-0.1.0/src/plain_parser/exceptions.py +39 -0
- plain_parser-0.1.0/src/plain_parser/liquid_nodes.py +109 -0
- plain_parser-0.1.0/src/plain_parser/loaders.py +101 -0
- plain_parser-0.1.0/src/plain_parser/plain_file.py +861 -0
- plain_parser-0.1.0/src/plain_parser/plain_spec.py +414 -0
- plain_parser-0.1.0/tests/__init__.py +0 -0
- plain_parser-0.1.0/tests/conftest.py +31 -0
- plain_parser-0.1.0/tests/data/imports/circular_imports_1.plain +8 -0
- plain_parser-0.1.0/tests/data/imports/circular_imports_2.plain +8 -0
- plain_parser-0.1.0/tests/data/imports/circular_imports_main.plain +12 -0
- plain_parser-0.1.0/tests/data/imports/diamond_import_1.plain +16 -0
- plain_parser-0.1.0/tests/data/imports/diamond_import_2.plain +16 -0
- plain_parser-0.1.0/tests/data/imports/diamond_import_common.plain +11 -0
- plain_parser-0.1.0/tests/data/imports/diamond_imports_main.plain +13 -0
- plain_parser-0.1.0/tests/data/imports/import_with_functionalities.plain +7 -0
- plain_parser-0.1.0/tests/data/imports/imports_module_with_functionalities.plain +12 -0
- plain_parser-0.1.0/tests/data/imports/non_existent_import.plain +12 -0
- plain_parser-0.1.0/tests/data/plainfile/add_new_task_modal_specification.yaml +1 -0
- plain_parser-0.1.0/tests/data/plainfile/duplicate_specification_heading.plain +7 -0
- plain_parser-0.1.0/tests/data/plainfile/empty_functional_specs_section.plain +4 -0
- plain_parser-0.1.0/tests/data/plainfile/invalid_specification_order.plain +7 -0
- plain_parser-0.1.0/tests/data/plainfile/missing_non_functional_requirements.plain +5 -0
- plain_parser-0.1.0/tests/data/plainfile/no_functional_specs_section.plain +5 -0
- plain_parser-0.1.0/tests/data/plainfile/plain_source_with_absolute_link.plain +9 -0
- plain_parser-0.1.0/tests/data/plainfile/plain_source_with_url_link.plain +9 -0
- plain_parser-0.1.0/tests/data/plainfile/task_list_ui_specification.yaml +1 -0
- plain_parser-0.1.0/tests/data/plainfile/task_manager_with_reference_links.plain +40 -0
- plain_parser-0.1.0/tests/data/plainfile/without_non_functional_requirement.plain +5 -0
- plain_parser-0.1.0/tests/data/plainfileparser/concept_validation_acceptance_tests.plain +20 -0
- plain_parser-0.1.0/tests/data/plainfileparser/concept_validation_acceptance_tests_nondefined.plain +20 -0
- plain_parser-0.1.0/tests/data/plainfileparser/concept_validation_defined_nondefined.plain +18 -0
- plain_parser-0.1.0/tests/data/plainfileparser/concept_validation_defined_nondefined_2.plain +18 -0
- plain_parser-0.1.0/tests/data/plainfileparser/concept_validation_definition.plain +16 -0
- plain_parser-0.1.0/tests/data/plainfileparser/concept_validation_noconcepts.plain +18 -0
- plain_parser-0.1.0/tests/data/plainfileparser/concept_validation_nonconcept.plain +15 -0
- plain_parser-0.1.0/tests/data/plainfileparser/concept_validation_nondefined.plain +18 -0
- plain_parser-0.1.0/tests/data/plainfileparser/concept_validation_redefinition.plain +19 -0
- plain_parser-0.1.0/tests/data/plainfileparser/concept_validation_several_concepts.plain +13 -0
- plain_parser-0.1.0/tests/data/plainfileparser/concept_validation_valid.plain +16 -0
- plain_parser-0.1.0/tests/data/plainfileparser/cyclic_definitions.plain +9 -0
- plain_parser-0.1.0/tests/data/plainfileparser/exported_concepts.plain +13 -0
- plain_parser-0.1.0/tests/data/plainfileparser/exported_concepts_base.plain +2 -0
- plain_parser-0.1.0/tests/data/plainfileparser/exported_concepts_defaults_exported.plain +13 -0
- plain_parser-0.1.0/tests/data/plainfileparser/exported_concepts_example.plain +13 -0
- plain_parser-0.1.0/tests/data/plainfileparser/exported_concepts_imported_exported.plain +12 -0
- plain_parser-0.1.0/tests/data/plainfileparser/exported_concepts_inherited_reexported.plain +15 -0
- plain_parser-0.1.0/tests/data/plainfileparser/exported_concepts_malformed.plain +13 -0
- plain_parser-0.1.0/tests/data/plainfileparser/exported_concepts_missing_definition.plain +14 -0
- plain_parser-0.1.0/tests/data/plainfileparser/exported_concepts_missing_definition_example.plain +13 -0
- plain_parser-0.1.0/tests/data/plainfileparser/exported_concepts_multiple_per_bullet.plain +14 -0
- plain_parser-0.1.0/tests/data/plainfileparser/exported_concepts_multiple_per_bullet_undefined.plain +13 -0
- plain_parser-0.1.0/tests/data/plainfileparser/exported_concepts_nested.plain +15 -0
- plain_parser-0.1.0/tests/data/plainfileparser/exported_concepts_nested_example.plain +13 -0
- plain_parser-0.1.0/tests/data/plainfileparser/exported_concepts_no_definitions.plain +10 -0
- plain_parser-0.1.0/tests/data/plainfileparser/exported_concepts_not_declared.plain +8 -0
- plain_parser-0.1.0/tests/data/plainfileparser/exported_concepts_not_declared_example.plain +13 -0
- plain_parser-0.1.0/tests/data/plainfileparser/exported_concepts_transitive_example.plain +13 -0
- plain_parser-0.1.0/tests/data/plainfileparser/exported_concepts_transitive_l1.plain +13 -0
- plain_parser-0.1.0/tests/data/plainfileparser/exported_concepts_transitive_l2.plain +13 -0
- plain_parser-0.1.0/tests/data/plainfileparser/plain_file_parser_with_comments.plain +8 -0
- plain_parser-0.1.0/tests/data/plainfileparser/plain_file_with_comments_indented.plain +10 -0
- plain_parser-0.1.0/tests/data/plainfileparser/regular_plain_source.plain +11 -0
- plain_parser-0.1.0/tests/data/plainfileparser/required_concepts.plain +7 -0
- plain_parser-0.1.0/tests/data/plainfileparser/required_concepts_defs.plain +2 -0
- plain_parser-0.1.0/tests/data/plainfileparser/required_concepts_example.plain +13 -0
- plain_parser-0.1.0/tests/data/plainfileparser/required_concepts_l1.plain +7 -0
- plain_parser-0.1.0/tests/data/plainfileparser/required_concepts_l2.plain +5 -0
- plain_parser-0.1.0/tests/data/plainfileparser/required_concepts_missing.plain +10 -0
- plain_parser-0.1.0/tests/data/plainfileparser/required_concepts_module.plain +11 -0
- plain_parser-0.1.0/tests/data/plainfileparser/required_concepts_partial.plain +13 -0
- plain_parser-0.1.0/tests/data/plainfileparser/required_concepts_partial_duplicate.plain +14 -0
- plain_parser-0.1.0/tests/data/plainfileparser/requires_without_definitions_example.plain +10 -0
- plain_parser-0.1.0/tests/data/plainfileparser/topological_sort.plain +14 -0
- plain_parser-0.1.0/tests/data/plainfileparser/topological_sort_not_referenced.plain +22 -0
- plain_parser-0.1.0/tests/data/requires/circular_requires_main.plain +18 -0
- plain_parser-0.1.0/tests/data/requires/circular_requires_sub.plain +12 -0
- plain_parser-0.1.0/tests/data/requires/diamond_requires_1.plain +12 -0
- plain_parser-0.1.0/tests/data/requires/diamond_requires_2.plain +12 -0
- plain_parser-0.1.0/tests/data/requires/diamond_requires_common.plain +7 -0
- plain_parser-0.1.0/tests/data/requires/diamond_requires_main.plain +19 -0
- plain_parser-0.1.0/tests/data/requires/independent_requires_1.plain +7 -0
- plain_parser-0.1.0/tests/data/requires/independent_requires_2.plain +7 -0
- plain_parser-0.1.0/tests/data/requires/independent_requires_main.plain +19 -0
- plain_parser-0.1.0/tests/data/requires/non_existent_require.plain +18 -0
- plain_parser-0.1.0/tests/data/requires/normal_requires_1.plain +12 -0
- plain_parser-0.1.0/tests/data/requires/normal_requires_2.plain +12 -0
- plain_parser-0.1.0/tests/data/requires/normal_requires_common.plain +7 -0
- plain_parser-0.1.0/tests/data/requires/normal_requires_main.plain +19 -0
- plain_parser-0.1.0/tests/data/sample_base64_image.txt +1 -0
- plain_parser-0.1.0/tests/data/simple.plain +7 -0
- plain_parser-0.1.0/tests/data/templates/block_level_include.plain +12 -0
- plain_parser-0.1.0/tests/data/templates/code_variables.plain +10 -0
- plain_parser-0.1.0/tests/data/templates/header.plain +7 -0
- plain_parser-0.1.0/tests/data/templates/implement.plain +3 -0
- plain_parser-0.1.0/tests/data/templates/implement_2.plain +3 -0
- plain_parser-0.1.0/tests/data/templates/template_include.plain +4 -0
- plain_parser-0.1.0/tests/data/templates/test_hardest_problem.plain +2 -0
- plain_parser-0.1.0/tests/test_exceptions.py +21 -0
- plain_parser-0.1.0/tests/test_imports.py +53 -0
- plain_parser-0.1.0/tests/test_loaders.py +98 -0
- plain_parser-0.1.0/tests/test_plainfile.py +270 -0
- plain_parser-0.1.0/tests/test_plainfileparser.py +734 -0
- plain_parser-0.1.0/tests/test_plainspec.py +133 -0
- plain_parser-0.1.0/tests/test_requires.py +28 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: Code Quality & Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
# Pushes to PR branches are covered by pull_request; listening to every push
|
|
5
|
+
# too would run the suite twice per commit.
|
|
6
|
+
push:
|
|
7
|
+
branches: [main]
|
|
8
|
+
pull_request:
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
check:
|
|
13
|
+
name: Lint and test
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
17
|
+
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
18
|
+
with:
|
|
19
|
+
python-version: "3.11"
|
|
20
|
+
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
21
|
+
with:
|
|
22
|
+
path: ~/.cache/pip
|
|
23
|
+
key: ${{ runner.os }}-pip-${{ hashFiles('pyproject.toml') }}
|
|
24
|
+
- name: Install dependencies
|
|
25
|
+
id: install
|
|
26
|
+
run: python -m pip install --upgrade pip && pip install -e ".[dev]"
|
|
27
|
+
|
|
28
|
+
# Each check runs even if an earlier one failed, so one run reports
|
|
29
|
+
# every failure.
|
|
30
|
+
- name: Black Formatting
|
|
31
|
+
if: ${{ !cancelled() && steps.install.outcome == 'success' }}
|
|
32
|
+
run: black . --check
|
|
33
|
+
- name: Import Sorting
|
|
34
|
+
if: ${{ !cancelled() && steps.install.outcome == 'success' }}
|
|
35
|
+
run: isort . --check-only --diff
|
|
36
|
+
- name: Flake8 Linting
|
|
37
|
+
if: ${{ !cancelled() && steps.install.outcome == 'success' }}
|
|
38
|
+
run: flake8 .
|
|
39
|
+
- name: MyPy Type Checking
|
|
40
|
+
if: ${{ !cancelled() && steps.install.outcome == 'success' }}
|
|
41
|
+
run: mypy src
|
|
42
|
+
- name: Tests
|
|
43
|
+
if: ${{ !cancelled() && steps.install.outcome == 'success' }}
|
|
44
|
+
run: pytest tests -v
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
name: Publish dev build to PyPI
|
|
2
|
+
|
|
3
|
+
# Every merge to main publishes a PEP 440 dev release (e.g. 0.1.1.dev7) to
|
|
4
|
+
# PyPI, so the latest main is always installable from the real index with real
|
|
5
|
+
# dependency resolution. Dev releases are pre-releases: pip and uv skip them
|
|
6
|
+
# unless asked, so ordinary installs keep getting the latest stable.
|
|
7
|
+
#
|
|
8
|
+
# Tagged releases (including rc/beta/alpha) are handled by publish-to-pypi.yml.
|
|
9
|
+
on:
|
|
10
|
+
push:
|
|
11
|
+
branches: [main]
|
|
12
|
+
# Ad-hoc runs, e.g. re-publishing after a PyPI outage.
|
|
13
|
+
workflow_dispatch:
|
|
14
|
+
|
|
15
|
+
# Least privilege by default: jobs get no GITHUB_TOKEN scopes unless they
|
|
16
|
+
# opt in below.
|
|
17
|
+
permissions: {}
|
|
18
|
+
|
|
19
|
+
# Serialize publishes so versions land in merge order. Queued runs are not
|
|
20
|
+
# cancelled — each merge should still get its own dev build.
|
|
21
|
+
concurrency:
|
|
22
|
+
group: publish-dev-to-pypi
|
|
23
|
+
cancel-in-progress: false
|
|
24
|
+
|
|
25
|
+
jobs:
|
|
26
|
+
build:
|
|
27
|
+
name: Build dev distribution
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
permissions:
|
|
30
|
+
contents: read
|
|
31
|
+
outputs:
|
|
32
|
+
should-publish: ${{ steps.version.outputs.should-publish }}
|
|
33
|
+
version: ${{ steps.version.outputs.version }}
|
|
34
|
+
|
|
35
|
+
steps:
|
|
36
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
37
|
+
with:
|
|
38
|
+
# hatch-vcs derives the version from git tags, so we need full history.
|
|
39
|
+
fetch-depth: 0
|
|
40
|
+
|
|
41
|
+
- name: Install uv
|
|
42
|
+
# uv provisions its own Python (honoring requires-python), so no
|
|
43
|
+
# separate actions/setup-python step is needed.
|
|
44
|
+
run: |
|
|
45
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
46
|
+
echo "$HOME/.local/bin" >> $GITHUB_PATH
|
|
47
|
+
|
|
48
|
+
- name: Compute dev version
|
|
49
|
+
id: version
|
|
50
|
+
# Between releases hatch-vcs produces something like
|
|
51
|
+
# "0.3.10.dev16+gb710c2982". The "+g..." local version segment is valid
|
|
52
|
+
# PEP 440 but is rejected by PyPI on upload, so it has to go. We keep
|
|
53
|
+
# setuptools-scm's own next-version guess (0.3.10 — the patch bump
|
|
54
|
+
# after the latest tag) rather than re-deriving it here, and use the
|
|
55
|
+
# run number as the dev counter: it is strictly increasing, so each
|
|
56
|
+
# merge sorts after the last and re-runs never collide.
|
|
57
|
+
run: |
|
|
58
|
+
RAW=$(uvx --from setuptools-scm python -c \
|
|
59
|
+
"from setuptools_scm import get_version; print(get_version())")
|
|
60
|
+
echo "setuptools-scm reports: $RAW"
|
|
61
|
+
|
|
62
|
+
# On a commit that IS a release tag, setuptools-scm returns a clean
|
|
63
|
+
# version with no .dev component (e.g. "0.3.9" or "0.4.0rc1").
|
|
64
|
+
# Appending ".dev<run>" to that yields 0.3.9.dev42, which sorts BELOW
|
|
65
|
+
# the released 0.3.9 — newer code would look older. A tagged commit
|
|
66
|
+
# is a real release and is published by publish-to-pypi.yml, so there
|
|
67
|
+
# is no dev build to make here.
|
|
68
|
+
case "$RAW" in
|
|
69
|
+
*.dev*) ;;
|
|
70
|
+
*)
|
|
71
|
+
echo "::notice::$RAW is a tagged release; skipping dev build."
|
|
72
|
+
echo "should-publish=false" >> $GITHUB_OUTPUT
|
|
73
|
+
exit 0
|
|
74
|
+
;;
|
|
75
|
+
esac
|
|
76
|
+
|
|
77
|
+
BASE=${RAW%%+*}
|
|
78
|
+
BASE=${BASE%%.dev*}
|
|
79
|
+
VERSION="${BASE}.dev${GITHUB_RUN_NUMBER}"
|
|
80
|
+
echo "Derived version: $RAW -> $VERSION"
|
|
81
|
+
echo "SETUPTOOLS_SCM_PRETEND_VERSION=$VERSION" >> $GITHUB_ENV
|
|
82
|
+
echo "should-publish=true" >> $GITHUB_OUTPUT
|
|
83
|
+
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
|
84
|
+
|
|
85
|
+
- name: Build package
|
|
86
|
+
if: steps.version.outputs.should-publish == 'true'
|
|
87
|
+
# hatch-vcs reads SETUPTOOLS_SCM_PRETEND_VERSION in place of the tag.
|
|
88
|
+
run: |
|
|
89
|
+
uv build
|
|
90
|
+
echo "Built distributions:"
|
|
91
|
+
ls -l dist/
|
|
92
|
+
|
|
93
|
+
- name: Store the distribution packages
|
|
94
|
+
if: steps.version.outputs.should-publish == 'true'
|
|
95
|
+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
96
|
+
with:
|
|
97
|
+
name: dev-package-distributions
|
|
98
|
+
path: dist/
|
|
99
|
+
|
|
100
|
+
publish-dev:
|
|
101
|
+
name: Publish dev build to PyPI
|
|
102
|
+
needs: build
|
|
103
|
+
if: needs.build.outputs.should-publish == 'true'
|
|
104
|
+
runs-on: ubuntu-latest
|
|
105
|
+
environment:
|
|
106
|
+
name: pypi
|
|
107
|
+
url: https://pypi.org/p/plain-parser
|
|
108
|
+
permissions:
|
|
109
|
+
# Mint a short-lived OIDC token so PyPI Trusted Publishing can
|
|
110
|
+
# authenticate this run — no long-lived API token needed.
|
|
111
|
+
id-token: write
|
|
112
|
+
|
|
113
|
+
env:
|
|
114
|
+
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
|
115
|
+
|
|
116
|
+
steps:
|
|
117
|
+
- name: Download distribution packages
|
|
118
|
+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
119
|
+
with:
|
|
120
|
+
name: dev-package-distributions
|
|
121
|
+
path: dist/
|
|
122
|
+
|
|
123
|
+
- name: Publish to PyPI
|
|
124
|
+
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
|
|
125
|
+
with:
|
|
126
|
+
# PyPI uploads are immutable, so a re-run of an already-published
|
|
127
|
+
# version should be a no-op, not a red build.
|
|
128
|
+
skip-existing: true
|
|
129
|
+
|
|
130
|
+
- name: Notify Slack (success)
|
|
131
|
+
if: ${{ success() && env.SLACK_WEBHOOK_URL != '' }}
|
|
132
|
+
env:
|
|
133
|
+
VERSION: ${{ needs.build.outputs.version }}
|
|
134
|
+
run: |
|
|
135
|
+
# Spell out the --pre install: a plain "pip install plain-parser" will
|
|
136
|
+
# NOT pick this up, which otherwise reads as a broken publish.
|
|
137
|
+
TEXT="*plain-parser*: dev build \`$VERSION\` published to PyPI :test_tube:. Normal installs are unaffected — try it with \`pip install --pre plain-parser==$VERSION\`."
|
|
138
|
+
curl -s -X POST "$SLACK_WEBHOOK_URL" \
|
|
139
|
+
-H 'Content-type: application/json' \
|
|
140
|
+
--data "$(jq -n --arg t "$TEXT" '{text:$t}')"
|
|
141
|
+
|
|
142
|
+
- name: Notify Slack (failure)
|
|
143
|
+
if: ${{ failure() && env.SLACK_WEBHOOK_URL != '' }}
|
|
144
|
+
run: |
|
|
145
|
+
curl -s -X POST "$SLACK_WEBHOOK_URL" \
|
|
146
|
+
-H 'Content-type: application/json' \
|
|
147
|
+
--data "{\"text\":\"*plain-parser*: Publishing dev build to PyPI FAILED :x: (commit \`${{ github.sha }}\`). See <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|the run log>.\"}"
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
# Least privilege by default: jobs get no GITHUB_TOKEN scopes unless they
|
|
8
|
+
# opt in below.
|
|
9
|
+
permissions: {}
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
build:
|
|
13
|
+
name: Build distribution
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
permissions:
|
|
16
|
+
contents: write
|
|
17
|
+
|
|
18
|
+
env:
|
|
19
|
+
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
|
20
|
+
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
23
|
+
with:
|
|
24
|
+
# hatch-vcs derives the version from git tags, so we need full history.
|
|
25
|
+
fetch-depth: 0
|
|
26
|
+
|
|
27
|
+
- name: Notify Slack (start)
|
|
28
|
+
if: ${{ env.SLACK_WEBHOOK_URL != '' }}
|
|
29
|
+
run: |
|
|
30
|
+
curl -s -X POST "$SLACK_WEBHOOK_URL" \
|
|
31
|
+
-H 'Content-type: application/json' \
|
|
32
|
+
--data "{\"text\":\"*plain-parser*: Release \`${{ github.ref_name }}\` published by *${{ github.actor }}* — building and publishing to PyPI...\"}"
|
|
33
|
+
|
|
34
|
+
- name: Install uv
|
|
35
|
+
# uv provisions its own Python (honoring requires-python), so no
|
|
36
|
+
# separate actions/setup-python step is needed.
|
|
37
|
+
run: |
|
|
38
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
39
|
+
echo "$HOME/.local/bin" >> $GITHUB_PATH
|
|
40
|
+
|
|
41
|
+
- name: Build package
|
|
42
|
+
# hatch-vcs reads the version straight from the release tag.
|
|
43
|
+
run: |
|
|
44
|
+
uv build
|
|
45
|
+
echo "Built distributions:"
|
|
46
|
+
ls -l dist/
|
|
47
|
+
|
|
48
|
+
- name: Store the distribution packages
|
|
49
|
+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
50
|
+
with:
|
|
51
|
+
name: python-package-distributions
|
|
52
|
+
path: dist/
|
|
53
|
+
|
|
54
|
+
- name: Upload assets to GitHub Release
|
|
55
|
+
env:
|
|
56
|
+
GH_TOKEN: ${{ github.token }}
|
|
57
|
+
run: |
|
|
58
|
+
gh release upload ${{ github.ref_name }} dist/* --clobber
|
|
59
|
+
|
|
60
|
+
- name: Notify Slack (build failure)
|
|
61
|
+
if: ${{ failure() && env.SLACK_WEBHOOK_URL != '' }}
|
|
62
|
+
run: |
|
|
63
|
+
curl -s -X POST "$SLACK_WEBHOOK_URL" \
|
|
64
|
+
-H 'Content-type: application/json' \
|
|
65
|
+
--data "{\"text\":\"*plain-parser*: Build of \`${{ github.ref_name }}\` FAILED :x:. Nothing was published to PyPI. See <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|the run log>.\"}"
|
|
66
|
+
|
|
67
|
+
publish-to-pypi:
|
|
68
|
+
name: Publish to PyPI
|
|
69
|
+
needs: build
|
|
70
|
+
runs-on: ubuntu-latest
|
|
71
|
+
environment:
|
|
72
|
+
name: pypi
|
|
73
|
+
url: https://pypi.org/p/plain-parser
|
|
74
|
+
permissions:
|
|
75
|
+
# Mint a short-lived OIDC token so PyPI Trusted Publishing can
|
|
76
|
+
# authenticate this run — no long-lived API token needed.
|
|
77
|
+
id-token: write
|
|
78
|
+
|
|
79
|
+
env:
|
|
80
|
+
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
|
81
|
+
|
|
82
|
+
steps:
|
|
83
|
+
- name: Download distribution packages
|
|
84
|
+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
85
|
+
with:
|
|
86
|
+
name: python-package-distributions
|
|
87
|
+
path: dist/
|
|
88
|
+
|
|
89
|
+
- name: Publish to PyPI
|
|
90
|
+
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
|
|
91
|
+
|
|
92
|
+
- name: Notify Slack (success)
|
|
93
|
+
if: ${{ success() && env.SLACK_WEBHOOK_URL != '' }}
|
|
94
|
+
env:
|
|
95
|
+
# GitHub marks the release itself as a pre-release; the tag
|
|
96
|
+
# (v0.2.0rc1) carries the same intent for PEP 440.
|
|
97
|
+
IS_PRERELEASE: ${{ github.event.release.prerelease }}
|
|
98
|
+
TAG: ${{ github.ref_name }}
|
|
99
|
+
run: |
|
|
100
|
+
VERSION="${TAG#v}"
|
|
101
|
+
if [ "$IS_PRERELEASE" = "true" ]; then
|
|
102
|
+
# pip and uv skip pre-releases by default, so "--upgrade" would
|
|
103
|
+
# NOT pick this up. Tell people the command that actually works.
|
|
104
|
+
TEXT="*plain-parser*: pre-release \`$TAG\` published to PyPI :test_tube:. Normal installs are unaffected — try it with \`pip install --pre plain-parser==$VERSION\`."
|
|
105
|
+
else
|
|
106
|
+
TEXT="*plain-parser*: \`$TAG\` published to PyPI :white_check_mark:. Install with \`pip install --upgrade plain-parser\`."
|
|
107
|
+
fi
|
|
108
|
+
curl -s -X POST "$SLACK_WEBHOOK_URL" \
|
|
109
|
+
-H 'Content-type: application/json' \
|
|
110
|
+
--data "$(jq -n --arg t "$TEXT" '{text:$t}')"
|
|
111
|
+
|
|
112
|
+
- name: Notify Slack (failure)
|
|
113
|
+
if: ${{ failure() && env.SLACK_WEBHOOK_URL != '' }}
|
|
114
|
+
run: |
|
|
115
|
+
curl -s -X POST "$SLACK_WEBHOOK_URL" \
|
|
116
|
+
-H 'Content-type: application/json' \
|
|
117
|
+
--data "{\"text\":\"*plain-parser*: Publishing \`${{ github.ref_name }}\` to PyPI FAILED :x:. See <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|the run log>.\"}"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Codeplain Inc.
|
|
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.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: plain-parser
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Parser for ***plain specification files
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Classifier: Intended Audience :: Developers
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Topic :: Software Development :: Compilers
|
|
11
|
+
Requires-Python: >=3.11
|
|
12
|
+
Requires-Dist: mistletoe<2,>=1.6.0
|
|
13
|
+
Requires-Dist: networkx<4,>=3.6.1
|
|
14
|
+
Requires-Dist: python-frontmatter<2,>=1.3.0
|
|
15
|
+
Requires-Dist: python-liquid2<0.5,>=0.4.0
|
|
16
|
+
Provides-Extra: dev
|
|
17
|
+
Requires-Dist: black==26.5.1; extra == 'dev'
|
|
18
|
+
Requires-Dist: flake8==7.3.0; extra == 'dev'
|
|
19
|
+
Requires-Dist: isort==8.0.1; extra == 'dev'
|
|
20
|
+
Requires-Dist: mypy==2.3.0; extra == 'dev'
|
|
21
|
+
Requires-Dist: pytest==9.1.1; extra == 'dev'
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# plain-parser
|
|
25
|
+
|
|
26
|
+
Python parser for [`***plain`](https://www.plainlang.org/docs/) specification files.
|
|
27
|
+
|
|
28
|
+
Reads a `.plain` module and its `import` / `requires` chain, resolves Liquid templates,
|
|
29
|
+
validates concepts and linked resources, and returns the marshalled specification tree
|
|
30
|
+
plus the ordered list of functionalities (FRIDs).
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install plain-parser
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from plain_parser import plain_file_parser
|
|
42
|
+
|
|
43
|
+
module_name, plain_source_tree, required_modules = plain_file_parser("my_spec.plain", ["."])
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`plain_source_tree` is a dict keyed by section (`definitions`, `implementation reqs`,
|
|
47
|
+
`test reqs`, `functional specs`), each a list of `{"markdown": ..., "linked_resources": [...]}`
|
|
48
|
+
entries. `plain_parser.plain_spec` has helpers for walking functionalities
|
|
49
|
+
(`get_frids`, `get_specifications_for_frid`, `collect_linked_resources`).
|
|
50
|
+
|
|
51
|
+
## Development
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
uv venv --python 3.11 && source .venv/bin/activate
|
|
55
|
+
uv pip install -e ".[dev]"
|
|
56
|
+
pytest
|
|
57
|
+
black . --check && isort . --check-only && flake8 . && mypy src
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## License
|
|
61
|
+
|
|
62
|
+
MIT, see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# plain-parser
|
|
2
|
+
|
|
3
|
+
Python parser for [`***plain`](https://www.plainlang.org/docs/) specification files.
|
|
4
|
+
|
|
5
|
+
Reads a `.plain` module and its `import` / `requires` chain, resolves Liquid templates,
|
|
6
|
+
validates concepts and linked resources, and returns the marshalled specification tree
|
|
7
|
+
plus the ordered list of functionalities (FRIDs).
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install plain-parser
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
from plain_parser import plain_file_parser
|
|
19
|
+
|
|
20
|
+
module_name, plain_source_tree, required_modules = plain_file_parser("my_spec.plain", ["."])
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`plain_source_tree` is a dict keyed by section (`definitions`, `implementation reqs`,
|
|
24
|
+
`test reqs`, `functional specs`), each a list of `{"markdown": ..., "linked_resources": [...]}`
|
|
25
|
+
entries. `plain_parser.plain_spec` has helpers for walking functionalities
|
|
26
|
+
(`get_frids`, `get_specifications_for_frid`, `collect_linked_resources`).
|
|
27
|
+
|
|
28
|
+
## Development
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
uv venv --python 3.11 && source .venv/bin/activate
|
|
32
|
+
uv pip install -e ".[dev]"
|
|
33
|
+
pytest
|
|
34
|
+
black . --check && isort . --check-only && flake8 . && mypy src
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## License
|
|
38
|
+
|
|
39
|
+
MIT, see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling", "hatch-vcs"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "plain-parser"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Parser for ***plain specification files"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
requires-python = ">=3.11"
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Intended Audience :: Developers",
|
|
15
|
+
"Operating System :: OS Independent",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Topic :: Software Development :: Compilers",
|
|
18
|
+
]
|
|
19
|
+
dependencies = [
|
|
20
|
+
"python-liquid2>=0.4.0,<0.5",
|
|
21
|
+
"mistletoe>=1.6.0,<2",
|
|
22
|
+
"python-frontmatter>=1.3.0,<2",
|
|
23
|
+
"networkx>=3.6.1,<4",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.optional-dependencies]
|
|
27
|
+
dev = [
|
|
28
|
+
"pytest==9.1.1",
|
|
29
|
+
"flake8==7.3.0",
|
|
30
|
+
"black==26.5.1",
|
|
31
|
+
"isort==8.0.1",
|
|
32
|
+
"mypy==2.3.0",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[tool.hatch.version]
|
|
36
|
+
source = "vcs"
|
|
37
|
+
|
|
38
|
+
[tool.hatch.build.targets.wheel]
|
|
39
|
+
packages = ["src/plain_parser"]
|
|
40
|
+
|
|
41
|
+
[tool.black]
|
|
42
|
+
line-length = 120
|
|
43
|
+
target-version = ['py311']
|
|
44
|
+
extend-exclude = '''
|
|
45
|
+
^/(.git|.venv|dist|tests/data)/
|
|
46
|
+
'''
|
|
47
|
+
|
|
48
|
+
[tool.isort]
|
|
49
|
+
profile = "black"
|
|
50
|
+
line_length = 120
|
|
51
|
+
skip = [".git", ".venv", "dist", "tests/data"]
|
|
52
|
+
skip_gitignore = true
|
|
53
|
+
|
|
54
|
+
[tool.mypy]
|
|
55
|
+
python_version = "3.11"
|
|
56
|
+
ignore_missing_imports = true
|
|
57
|
+
show_error_codes = true
|
|
58
|
+
disable_error_code = [
|
|
59
|
+
"var-annotated", "import-untyped", "assignment", "return-value", "attr-defined",
|
|
60
|
+
"no-redef", "func-returns-value", "override", "index", "union-attr", "valid-type",
|
|
61
|
+
"misc", "operator", "unreachable", "has-type", "return", "type-arg",
|
|
62
|
+
]
|
|
63
|
+
exclude = ["^\\.git/", "^\\.venv/", "^tests/"]
|
|
64
|
+
|
|
65
|
+
[tool.pytest.ini_options]
|
|
66
|
+
testpaths = ["tests"]
|
|
67
|
+
python_files = "test_*.py"
|
|
68
|
+
norecursedirs = ["tests/data"]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Parser for ***plain specification files."""
|
|
2
|
+
|
|
3
|
+
from plain_parser import concept_utils, exceptions, loaders, plain_file, plain_spec
|
|
4
|
+
from plain_parser.plain_file import (
|
|
5
|
+
PlainFileParseResult,
|
|
6
|
+
get_filename_from_module_name,
|
|
7
|
+
get_module_name_from_filename,
|
|
8
|
+
marshall_plain_source,
|
|
9
|
+
parse_plain_file,
|
|
10
|
+
parse_plain_source,
|
|
11
|
+
plain_file_parser,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"PlainFileParseResult",
|
|
16
|
+
"concept_utils",
|
|
17
|
+
"exceptions",
|
|
18
|
+
"get_filename_from_module_name",
|
|
19
|
+
"get_module_name_from_filename",
|
|
20
|
+
"loaders",
|
|
21
|
+
"marshall_plain_source",
|
|
22
|
+
"parse_plain_file",
|
|
23
|
+
"parse_plain_source",
|
|
24
|
+
"plain_file",
|
|
25
|
+
"plain_file_parser",
|
|
26
|
+
"plain_spec",
|
|
27
|
+
]
|