Skip to content

Commit

Permalink
Merge pull request #63 from entangled/41-windows-compat
Browse files Browse the repository at this point in the history
enable testing on windows and macos
  • Loading branch information
jhidding authored Mar 5, 2025
2 parents 40186b2 + bdd86a3 commit 1da5e35
Show file tree
Hide file tree
Showing 16 changed files with 224 additions and 20 deletions.
7 changes: 6 additions & 1 deletion .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,16 @@ on:
jobs:
build:

runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
os: ["windows-latest", "ubuntu-latest", "macos-latest"]
python-version: ["3.11", "3.12", "3.13"]
exclude:
- os: "windows-latest"
python-version: "3.13"

runs-on: ${{matrix.os}}

steps:
- uses: actions/checkout@v3
Expand Down
8 changes: 4 additions & 4 deletions entangled/code_reader.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from dataclasses import dataclass, field
from pathlib import Path
from pathlib import Path, PurePath

import mawk
import re
Expand All @@ -18,9 +18,9 @@ class Frame:
class CodeReader(mawk.RuleSet):
"""Reads an annotated code file."""

def __init__(self, path: str, refs: ReferenceMap):
def __init__(self, path: PurePath, refs: ReferenceMap):
self.location = TextLocation(path, 0)
self.stack: list[Frame] = [Frame(ReferenceId("#root#", "", -1), "")]
self.stack: list[Frame] = [Frame(ReferenceId("#root#", PurePath("-"), -1), "")]
self.refs: ReferenceMap = refs

@property
Expand Down Expand Up @@ -56,7 +56,7 @@ def on_block_begin(self, m: re.Match):

self.stack.append(
Frame(
ReferenceId(m["ref_name"], m["source"], ref_count), m["indent"], content
ReferenceId(m["ref_name"], PurePath(m["source"]), ref_count), m["indent"], content
)
)
return []
Expand Down
2 changes: 1 addition & 1 deletion entangled/commands/stitch.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def stitch(*, force: bool = False, show: bool = False):
logging.debug("reading `%s`", path)
t.update(path)
with open(path, "r") as f:
CodeReader(str(path), refs).run(f.read())
CodeReader(path, refs).run(f.read())

for path in input_file_list:
t.write(path, stitch_markdown(refs, content[path]), [])
Expand Down
5 changes: 3 additions & 2 deletions entangled/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from collections import defaultdict
from functools import singledispatchmethod
from itertools import chain
from pathlib import PurePath

from .config import Language, AnnotationMethod, config
from .properties import Property, get_attribute
Expand All @@ -17,7 +18,7 @@ def length(iter: Iterable[Any]) -> int:
@dataclass
class ReferenceId:
name: str
file: str
file: PurePath
ref_count: int

def __hash__(self):
Expand Down Expand Up @@ -71,7 +72,7 @@ def by_name(self, n: str) -> Iterable[CodeBlock]:

return (self.map[r] for r in self.index[n])

def new_id(self, filename: str, name: str) -> ReferenceId:
def new_id(self, filename: PurePath, name: str) -> ReferenceId:
c = length(filter(lambda r: r.file == filename, self.index[name]))
return ReferenceId(name, filename, c)

Expand Down
12 changes: 6 additions & 6 deletions entangled/markdown_reader.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Optional
from copy import copy
from pathlib import Path
from pathlib import Path, PurePath

import re
import mawk
Expand All @@ -20,8 +20,8 @@ class MarkdownLexer(mawk.RuleSet):
content."""
def __init__(
self,
filename: str
):
filename: Path
):
self.location = TextLocation(filename)
self.raw_content: list[RawContent] = []
self.inside_codeblock: bool = False
Expand Down Expand Up @@ -122,13 +122,13 @@ def read_markdown_file(
-> tuple[ReferenceMap, list[Content]]:

with open(path, "r") as f:
path_str = str(path.resolve().relative_to(Path.cwd()))
return read_markdown_string(f.read(), path_str, refs, hooks)
rel_path = path.resolve().relative_to(Path.cwd())
return read_markdown_string(f.read(), rel_path, refs, hooks)


def read_markdown_string(
text: str,
path_str: str = "-",
path_str: Path = Path("-"),
refs: ReferenceMap | None = None,
hooks: list[HookBase] | None = None) \
-> tuple[ReferenceMap, list[Content]]:
Expand Down
2 changes: 1 addition & 1 deletion entangled/tangle.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def on_begin(self):
if self.cb.header is not None:
result.append(self.cb.header)
result.append(
f"{self.cb.language.comment.open} ~/~ begin <<{self.ref.file}#{self.ref.name}>>[{count}]{self.close_comment}"
f"{self.cb.language.comment.open} ~/~ begin <<{self.ref.file.as_posix()}#{self.ref.name}>>[{count}]{self.close_comment}"
)
return result

Expand Down
3 changes: 2 additions & 1 deletion entangled/text_location.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from dataclasses import dataclass
from pathlib import PurePath


@dataclass
class TextLocation:
filename: str
filename: PurePath
line_number: int = 0

def __str__(self):
Expand Down
4 changes: 4 additions & 0 deletions test/data/os_interop/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
build
.entangled
euler

24 changes: 24 additions & 0 deletions test/data/os_interop/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# ~/~ begin <<doc/index.md#Makefile>>[init]
.RECIPEPREFIX = >
.PHONY: clean

build_dir = ./build
source_files = src/euler_number.cc

obj_files = $(source_files:%.cc=$(build_dir)/%.o)
dep_files = $(obj_files:%.o=%.d)

euler: $(obj_files)
> @echo -e "Linking \e[32;1m$@\e[m"
> @gcc $^ -o $@

$(build_dir)/%.o: %.c
> @echo -e "Compiling \e[33m$@\e[m"
> @mkdir -p $(@D)
> @gcc -MMD -c $< -o $@

clean:
> rm -rf build euler

-include $(dep_files)
# ~/~ end
110 changes: 110 additions & 0 deletions test/data/os_interop/doc/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
---
title: Testing Windows/Linux interop
subtitle: a story of a mangled path
author: Johan Hidding
---

Paths on Windows are spelled with backslashes in between them, for example:

```
C:\Program Files\I have no clue\WhatImDoing.exe
```

On UNIXes (Linux and to and extend MacOS), paths are separated using slashes:

```
/usr/share/doc/man-pages/still-have-no-clue
```

Entangled uses Python's `Path` API throughout to deal with this difference transparently. Paths find a representation in the comment annotations left in tangled files, so that the markdown source of a piece of code can be found. We want consistent behaviour when using Entangled between different OS, so all paths should be encoded UNIX style.

This test creates a source file in a different path from this markdown source.

## Euler's number

We'll compute Euler's number in C. Euler's number is the exponential base for which the differential equation,

$$y_t = y,$$

holds. With a Taylor expansion we may see that the constant $e$ in the solution $A e^t$ can be computed as,

$$e = \sum_{n=0}^{\infty} \frac{1}{n!} = 1 + \frac{1}{1!} + \frac{1}{2!} + \dots$$

In C, we can compute this number, here to the 9th term:

``` {.c #series-expansion}
double euler_number = 1.0;
int factorial = 1;
for (int i = 1; i < 10; ++i) {
factorial *= i;
euler_number += 1.0 / factorial;
}
```

Wrapping this in an example program:

``` {.c file=src/euler_number.c}
#include <stdlib.h>
#include <stdio.h>

int main() {
<<series-expansion>>
printf("Euler's number e = %e\n", euler_number);
return EXIT_SUCCESS;
}
```

To build this program, the following `Makefile` can be used:

``` {.make file=Makefile}
.RECIPEPREFIX = >
.PHONY: clean

build_dir = ./build
source_files = src/euler_number.cc

obj_files = $(source_files:%.cc=$(build_dir)/%.o)
dep_files = $(obj_files:%.o=%.d)

euler: $(obj_files)
> @echo -e "Linking \e[32;1m$@\e[m"
> @gcc $^ -o $@

$(build_dir)/%.o: %.c
> @echo -e "Compiling \e[33m$@\e[m"
> @mkdir -p $(@D)
> @gcc -MMD -c $< -o $@

clean:
> rm -rf build euler

-include $(dep_files)
```

I know, overkill.

## Expected output

When we tangle this program, this looks like:

```c
/* ~/~ begin <<doc/index.md#src/euler_number.c>>[init] */
#include <stdlib.h>
#include <stdio.h>

int main() {
/* ~/~ begin <<doc/index.md#series-expansion>>[init] */
double euler_number = 1.0;
int factorial = 1;
for (int i = 1; i < 10; ++i) {
factorial *= i;
euler_number += 1.0 / factorial;
}
/* ~/~ end */
printf("Euler's number e = %e\n", euler_number);
return EXIT_SUCCESS;
}
/* ~/~ end */
```

Notice, the forward slashes in the paths.
17 changes: 17 additions & 0 deletions test/data/os_interop/src/euler_number.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/* ~/~ begin <<doc/index.md#src/euler_number.c>>[init] */
#include <stdlib.h>
#include <stdio.h>

int main() {
/* ~/~ begin <<doc/index.md#series-expansion>>[init] */
double euler_number = 1.0;
int factorial = 1;
for (int i = 1; i < 10; ++i) {
factorial *= i;
euler_number += 1.0 / factorial;
}
/* ~/~ end */
printf("Euler's number e = %e\n", euler_number);
return EXIT_SUCCESS;
}
/* ~/~ end */
17 changes: 17 additions & 0 deletions test/data/os_interop/src/euler_number.c.edit
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/* ~/~ begin <<doc/index.md#src/euler_number.c>>[init] */
#include <stdlib.h>
#include <stdio.h>

int main() {
/* ~/~ begin <<doc/index.md#series-expansion>>[init] */
double euler_number = 1.0;
int factorial = 1;
for (int i = 1; i < 20; ++i) {
factorial *= i;
euler_number += 1.0 / factorial;
}
/* ~/~ end */
printf("Euler's number e = %e\n", euler_number);
return EXIT_SUCCESS;
}
/* ~/~ end */
23 changes: 23 additions & 0 deletions test/test_os_interop.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from entangled.markdown_reader import read_markdown_file
from entangled.tangle import tangle_ref
from entangled.code_reader import CodeReader

from pathlib import Path, PurePath
import os
from shutil import copytree, move
from contextlib import chdir


def test_tangle_ref(data, tmp_path):
copytree(data / "os_interop", tmp_path / "os_interop")
with chdir(tmp_path / "os_interop"):
refs, _ = read_markdown_file(Path("doc/index.md"))
tangled, deps = tangle_ref(refs, "src/euler_number.c")
assert deps == {PurePath("doc/index.md")}
with open("src/euler_number.c", "r") as f:
assert f.read().strip() == tangled.strip()

cb_old = next(refs["series-expansion"]).source
cr = CodeReader("-", refs).run(Path("src/euler_number.c.edit").read_text())
cb_new = next(refs["series-expansion"]).source
assert cb_old != cb_new
4 changes: 2 additions & 2 deletions test/test_tangle.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from entangled.markdown_reader import read_markdown_file
from entangled.tangle import tangle_ref
from entangled.code_reader import CodeReader
from pathlib import Path
from pathlib import Path, PurePath
import os
from shutil import copytree, move
from contextlib import chdir
Expand All @@ -12,7 +12,7 @@ def test_tangle_ref(data, tmp_path):
with chdir(tmp_path / "hello-world"):
refs, _ = read_markdown_file(Path("hello-world.md"))
tangled, deps = tangle_ref(refs, "hello_world.cc")
assert deps == {"hello-world.md"}
assert deps == {PurePath("hello-world.md")}
with open("hello_world.cc", "r") as f:
assert f.read() == tangled

Expand Down
4 changes: 3 additions & 1 deletion test/test_typst.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
from subprocess import run
from pathlib import Path
from shutil import copytree
import sys


def test_typst(tmp_path: Path, data: Path):
copytree(data / "typst", tmp_path / "typst")
run(["python", "-m", "entangled.main", "tangle"],
run([sys.executable, "-m", "entangled.main", "tangle"],
cwd=tmp_path / "typst", check=True)
assert (tmp_path / "typst" / "fib.py").exists()
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 1da5e35

Please sign in to comment.