EdText

Monday 9 February 2026

edtext is a utility inspired by the ed editor for selecting and manipulating lines of text.

I have a new small project: edtext provides text selection and manipulation functions inspired by the classic ed text editor.

I’ve long used cog to build documentation and HTML presentations. Cog interpolates text from elsewhere, like source code or execution output. Often I don’t want the full source file or all of the lines of output. I want to be able to choose the lines, and sometimes I need to tweak the lines with a regex to get the results I want.

Long ago I wrote my own ad-hoc function to include a file and over the years it had grown “organically”, to use a positive word. It had become baroque and confusing. Worse, it still didn’t do all the things I needed.

The old function has 16 arguments (!), nine of which are for selecting the lines of text:

start=None,
end=None,
start_has=None,
end_has=None,
start_from=None,
end_at=None,
start_nth=1,
end_nth=1,
line_count=None,

Recently I started a new presentation, and when I couldn’t express what I needed with these nine arguments, I thought of a better way: the ed text editor has concise mechanisms for addressing lines of text. Ed addressing evolved into vim and sed, and probably other things too, so it might already be familiar to you.

I wrote edtext to replace my ad-hoc function that I was copying from project to project. Edtext lets me select subsets of lines using ed/sed/vim address ranges. Now if I have a source file like this with section-marking comments:

import pytest

# section1
def six_divided(x):
    return 6 / x

# Check the happy paths

@pytest.mark.parametrize(
    "x, expected",
    [ (4, 1.5), (3, 2.0), (2, 3.0), ]
)
def test_six_divided(x, expected):
    assert six_divided(x) == expected
# end

# section2
# etc....

then with an include_file helper that reads the file and gives me an EdText object, I can select just section1 with:

include_file("test_six_divided.py")["/# section1/+;/# end/-"]

EdText allows slicing with a string containing an ed address range. Ed addresses often (but don’t always) use regexes, and they have a similar powerful compact feeling. “/# section1/” finds the next line containing that string, and the “+” suffix adds one, so our range starts with the line after the section1 comment. The semicolon means to look for the end line starting from the start line, then we find “# end”, and the “-” suffix means subtract one. So our range ends with the line before the “# end” comment, giving us:

def six_divided(x):
    return 6 / x

# Check the happy paths

@pytest.mark.parametrize(
    "x, expected",
    [ (4, 1.5), (3, 2.0), (2, 3.0), ]
)
def test_six_divided(x, expected):
    assert six_divided(x) == expected

Most of ed addressing is implemented, and there’s a sub() method to make regex replacements on selected lines. I can run pytest, put the output into an EdText object, then use:

pytest_edtext["1", "/collected/,$-"].sub("g/====", r"0.0\ds", "0.01s")

This slice uses two address ranges. The first selects just the first line, the pytest command itself. The second range gets the lines from “collected” to the second-to-last line. Slicing gives me a new EdText object, then I use .sub() to tweak the output: on any line containing “====”, change the total time to “0.01s” so that slight variations in the duration of the test run doesn’t cause needless changes in the output.

It was very satisfying to write edtext: it’s small in scope, but useful. It has a full test suite. It might even be done!

Comments

Add a comment:

Ignore this:
Leave this empty:
Name is required. Either email or web are required. Email won't be displayed and I won't spam you. Your web site won't be indexed by search engines.
Don't put anything here:
Leave this empty:
Comment text is Markdown.