diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ec53e1c..52c069b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,42 +16,62 @@ concurrency: cancel-in-progress: true jobs: - format: name: "Check formatting" runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + # [[[cog cog.include("./steps/checkout.yml") ]]] + - name: "Check out the repo" + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false + # [[[end]]] + # [[[cog cog.include("./steps/ruff.yml") ]]] - uses: astral-sh/ruff-action@eaf0ecdd668ceea36159ff9d91882c9795d89b49 # v3.4.0 + # [[[end]]] with: - args: 'format --check' + args: "format --check" lint: name: "Lint" runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + # [[[cog cog.include("./steps/checkout.yml") ]]] + - name: "Check out the repo" + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false + # [[[end]]] + # [[[cog cog.include("./steps/ruff.yml") ]]] - uses: astral-sh/ruff-action@eaf0ecdd668ceea36159ff9d91882c9795d89b49 # v3.4.0 + # [[[end]]] + # [[[cog cog.include("./steps/setup-python.yml") ]]] - name: "Set up Python" uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + # [[[end]]] with: python-version: "3.13" + # [[[cog cog.include("./steps/install-dependencies.yml") ]]] - name: "Install dependencies" run: | python -m pip install -r requirements.pip - + # [[[end]]] - name: "Check the docs are up-to-date" run: | make lintdoc + - name: "Check that this file is up-to-date" + run: | + python -m cogapp -r --check --diff .github/workflows/ci.yml + if [ $? -ne 0 ]; then + echo 'Docs need to be updated: `python -m cogapp -r .github/workflows/ci.yml`'; + exit 1; + fi + tests: name: "Python ${{ matrix.python }} on ${{ matrix.os }}" runs-on: "${{ matrix.os }}-${{ matrix.os-version || 'latest' }}" @@ -79,20 +99,25 @@ jobs: os-version: "13" steps: + # [[[cog cog.include("./steps/checkout.yml") ]]] - name: "Check out the repo" uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false + # [[[end]]] + # [[[cog cog.include("./steps/setup-python.yml") ]]] - name: "Set up Python" uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + # [[[end]]] with: python-version: "${{ matrix.python }}" + # [[[cog cog.include("./steps/install-dependencies.yml") ]]] - name: "Install dependencies" run: | python -m pip install -r requirements.pip - + # [[[end]]] - name: "Run tox for ${{ matrix.python }}" run: | python -m tox @@ -111,21 +136,26 @@ jobs: runs-on: ubuntu-latest steps: + # [[[cog cog.include("./steps/checkout.yml") ]]] - name: "Check out the repo" uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: - fetch-depth: "0" persist-credentials: false + # [[[end]]] + fetch-depth: "0" + # [[[cog cog.include("./steps/setup-python.yml") ]]] - name: "Set up Python" uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + # [[[end]]] with: python-version: "3.9" + # [[[cog cog.include("./steps/install-dependencies.yml") ]]] - name: "Install dependencies" run: | python -m pip install -r requirements.pip - + # [[[end]]] - name: "Download coverage data" uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: diff --git a/.github/workflows/steps/checkout.yml b/.github/workflows/steps/checkout.yml new file mode 100644 index 0000000..42d23b5 --- /dev/null +++ b/.github/workflows/steps/checkout.yml @@ -0,0 +1,4 @@ +- name: "Check out the repo" + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false diff --git a/.github/workflows/steps/install-dependencies.yml b/.github/workflows/steps/install-dependencies.yml new file mode 100644 index 0000000..77b8255 --- /dev/null +++ b/.github/workflows/steps/install-dependencies.yml @@ -0,0 +1,3 @@ +- name: "Install dependencies" + run: | + python -m pip install -r requirements.pip diff --git a/.github/workflows/steps/ruff.yml b/.github/workflows/steps/ruff.yml new file mode 100644 index 0000000..fd0d36e --- /dev/null +++ b/.github/workflows/steps/ruff.yml @@ -0,0 +1 @@ +- uses: astral-sh/ruff-action@eaf0ecdd668ceea36159ff9d91882c9795d89b49 # v3.4.0 diff --git a/.github/workflows/steps/setup-python.yml b/.github/workflows/steps/setup-python.yml new file mode 100644 index 0000000..f98f779 --- /dev/null +++ b/.github/workflows/steps/setup-python.yml @@ -0,0 +1,2 @@ +- name: "Set up Python" + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 diff --git a/AUTHORS.txt b/AUTHORS.txt index 8762ecf..8890ad4 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -9,6 +9,7 @@ Daniel Murdin Doug Hellmann Hugh Perkins Jean-François Giraud +Joshua Cannon Panayiotis Gavriil Petr Gladkiy Phil Kirkpatrick diff --git a/cogapp/cogapp.py b/cogapp/cogapp.py index 972e900..a6a1d34 100644 --- a/cogapp/cogapp.py +++ b/cogapp/cogapp.py @@ -7,6 +7,7 @@ import io import linecache import os +import os.path import re import shlex import sys @@ -111,10 +112,11 @@ class CogCheckFailed(CogError): class CogGenerator(Redirectable): """A generator pulled from a source file.""" - def __init__(self, options=None): + def __init__(self, filename, options=None): super().__init__() self.markers = [] self.lines = [] + self.filename = filename self.options = options or CogOptions() def parse_marker(self, line): @@ -152,6 +154,7 @@ def evaluate(self, cog, globals, fname): cog.cogmodule.msg = self.msg cog.cogmodule.out = self.out cog.cogmodule.outl = self.outl + cog.cogmodule.include = self.include cog.cogmodule.error = self.error real_stdout = sys.stdout @@ -205,6 +208,12 @@ def outl(self, sOut="", **kw): self.out(sOut, **kw) self.out("\n") + def include(self, filepath, **kw): + """The cog.include function.""" + parent_dir = os.path.dirname(self.filename) + with open(os.path.join(parent_dir, filepath), "r") as f: + self.out(f.read(), **kw) + def error(self, msg="Error raised by cog generator."): """The cog.error function. @@ -466,7 +475,7 @@ def process_file(self, file_in, file_out, fname=None, globals=None): file_out.write(line) # `line` is the begin spec - gen = CogGenerator(options=self.options) + gen = CogGenerator(filename=file_name_in, options=self.options) gen.set_output(stdout=self.stdout) gen.parse_marker(line) first_line_num = file_in.linenumber() diff --git a/cogapp/test_cogapp.py b/cogapp/test_cogapp.py index 9a394b7..913c01e 100644 --- a/cogapp/test_cogapp.py +++ b/cogapp/test_cogapp.py @@ -1449,6 +1449,60 @@ def test_change_dir(self): output = self.output.getvalue() self.assertIn("(changed)", output) + def test_cog_include(self): + d = { + "coglet": "World!", + "sub": { + "coglet": "Hello!", + "test.cog": """\ + //[[[cog + cog.include("coglet") + cog.include("./coglet") + cog.include("../coglet") + //]]] + //[[[end]]] + """, + "test.out": """\ + //[[[cog + cog.include("coglet") + cog.include("./coglet") + cog.include("../coglet") + //]]] + Hello! + Hello! + World! + //[[[end]]] + """, + }, + } + + make_files(d) + self.cog.callable_main(["argv0", "-r", "sub/test.cog"]) + self.assertFilesSame("sub/test.cog", "sub/test.out") + output = self.output.getvalue() + self.assertIn("(changed)", output) + + def test_cog_include_with_stdin(self): + d = {"coglet": "Hello World!"} + make_files(d) + stdin = io.StringIO("--[[[cog cog.include('coglet') ]]]\n--[[[end]]]\n") + + def restore_stdin(old_stdin): + sys.stdin = old_stdin + + self.addCleanup(restore_stdin, sys.stdin) + sys.stdin = stdin + + stderr = io.StringIO() + self.cog.set_output(stderr=stderr) + self.cog.callable_main(["argv0", "-"]) + output = self.output.getvalue() + outerr = stderr.getvalue() + self.assertEqual( + output, "--[[[cog cog.include('coglet') ]]]\nHello World!\n--[[[end]]]\n" + ) + self.assertEqual(outerr, "") + class CogTestLineEndings(TestCaseWithTempDir): """Tests for -U option (force LF line-endings in output).""" diff --git a/docs/module.rst b/docs/module.rst index 6885058..702b173 100644 --- a/docs/module.rst +++ b/docs/module.rst @@ -25,6 +25,10 @@ The module contents are: **cog.outl** Same as **cog.out**, but adds a trailing newline. +**cog.include(filepath=, **kwargs)** + Outputs the contents of the file at filepath (relative to the current file's directory). + (Keyword-arguments forwarded to **cog.out**) + **cog.msg** `(msg)` Prints `msg` to stdout with a "Message: " prefix.