Markdown Link Management
pymdtools.mdcommon provides utilities for inspecting and rewriting links
inside Markdown documents. It is meant for workflows that update a set of
Markdown files together: collecting link targets, moving relative paths,
renaming labels, replacing obsolete targets, or normalizing references after a
documentation tree has been reorganized.
The module works on Markdown text and leaves file discovery, encoding, backups,
and writes to higher-level helpers such as pymdtools.common and
pymdtools.filetools. This keeps link transformations easy to test before the
updated content is written back to disk.
Supported Links
The parser handles the two Markdown link forms used by the project:
inline links, such as
[label](target "title");reference-style links, such as
[label][id]with a matching[id]: target "title"definition.
External links are detected separately so that bulk path updates can skip URLs
such as https://example.org or mailto:contact@example.org.
Link Records
Links are exchanged as small dictionaries. This representation makes it simple to serialize, compare, transform, and feed discovered links back into rewrite functions.
Common keys are:
namestores the visible link label;urlstores the link target;titlestores the optional Markdown title;linestores the one-based line number for discovered links;id_linkis used for reference-style links;name_to_replacecan be provided when replacing a label with another one.
Common Usage
Extract links from Markdown text:
from pymdtools.mdcommon import search_link_in_md_text
links = search_link_in_md_text('[Docs](docs/index.md "Documentation")')
Replace one link by label:
from pymdtools.mdcommon import update_links_in_md_text
updated = update_links_in_md_text(
"[old](old.md)",
{"name_to_replace": "old", "name": "new", "url": "new.md"},
)
Move relative link targets under a new base path while leaving external links unchanged:
from pymdtools.mdcommon import move_base_path_in_md_text
updated = move_base_path_in_md_text("[Guide](guide.md)", "docs")
Apply a transformation to a Markdown tree:
from pathlib import Path
from pymdtools.common import get_file_content, set_file_content
from pymdtools.mdcommon import move_base_path_in_md_text
for md_file in Path("docs").rglob("*.md"):
original = get_file_content(md_file)
updated = move_base_path_in_md_text(original, "archive")
if updated != original:
set_file_content(md_file, updated)
Choosing A Rewrite Function
Use update_link_in_md_text when the visible label is the stable identifier.
Use update_link_from_old_link when both the previous label and previous
target must match before replacing a link. Use update_links_from_old_link
to apply several old/new replacements in sequence.
Use move_base_path_in_md_text for documentation moves where every relative
target in one Markdown document needs to be prefixed with the same base path.
Public API
Markdown link management helpers.
pymdtools.mdcommon provides tools to inspect and rewrite links inside
Markdown documents. It is designed for workflows that modify a set of Markdown
files together, for example after moving documentation folders, renaming pages,
or replacing obsolete link targets.
Links are represented as dictionaries with keys such as name, url,
title, line, id_link and name_to_replace. This small data model
is easy to serialize, compare, transform, and feed back into rewrite functions.
The helpers operate on Markdown text and avoid mutating caller-provided link
dictionaries. File-oriented helpers delegate path validation and text loading to
pymdtools.common.
- class pymdtools.mdcommon.Link
Bases:
dict[str,str|int|None]Dictionary-compatible link object.
The class stores the same fields returned by link extraction helpers while exposing convenience properties for
name,label,urlandtitle. AssigningNoneremoves the underlying key.- property name: str | None
Return the link label, stored under the
namekey.
- property title: str | None
Return the optional link title.
- property url: str | None
Return the link target URL.
- pymdtools.mdcommon.get_domain_name(url: str) str
Return the domain part of an external URL.
Non-external URLs are returned unchanged.
mailtoURLs return their path, which is the email address.- Parameters:
url – URL or Markdown link target to inspect.
- Returns:
Domain name for external HTTP(S) URLs, email address for
mailtoURLs, or the original value for local links.
- pymdtools.mdcommon.is_external_link(url: str) bool
Return whether
urlis an external link.httpandhttpsURLs require both a scheme and a network location. Other schemes, such asmailto, are considered external when their scheme is present. Invalid URLs returnFalse.- Parameters:
url – URL or Markdown link target to classify.
- Returns:
Truewhen the target is considered external, otherwiseFalse.
- pymdtools.mdcommon.markdown_code_ranges(text: str) list[tuple[int, int]]
Locate fenced, indented, and inline Markdown code regions.
- pymdtools.mdcommon.merge_ranges(ranges: Sequence[tuple[int, int]]) list[tuple[int, int]]
Return sorted, overlapping character ranges as disjoint ranges.
- pymdtools.mdcommon.move_base_path_in_md_text(text_md: str, mv_base_path: str | PathLike[str] | Path) str
Prefix relative Markdown links with
mv_base_path.External URLs are left unchanged. The generated Markdown URLs use POSIX separators, even on Windows.
- Parameters:
text_md – Markdown text to update.
mv_base_path – Base path to prepend to relative link targets.
- Returns:
Updated Markdown text.
- Raises:
TypeError – If extracted link records do not contain string URLs.
- pymdtools.mdcommon.position_in_ranges(index: int, ranges: Sequence[tuple[int, int]]) bool
Return whether
indexbelongs to one of the sorted ranges.
- pymdtools.mdcommon.search_link_in_md_file(filename: str | PathLike[str] | Path, filename_ext: str = '.md', encoding: str | None = 'utf-8', previous_links: Sequence[Mapping[str, str | int | None]] | None = None) list[dict[str, str | int | None]]
Extract Markdown links from a file.
The file is validated with
pymdtools.common.check_file()before being read withpymdtools.common.get_file_content().- Parameters:
filename – Markdown file to inspect.
filename_ext – Expected file extension, including the leading dot.
encoding – Encoding used to read the file.
Nonetriggers automatic detection inpymdtools.common.previous_links – Optional existing links to prepend to the result.
- Returns:
A list of link records extracted from the file.
- Raises:
RuntimeError – Propagated from file validation helpers.
OSError – Propagated from file reading helpers.
- pymdtools.mdcommon.search_link_in_md_text(text: str, previous_links: Sequence[Mapping[str, str | int | None]] | None = None) list[dict[str, str | int | None]]
Extract Markdown links from text.
Both inline links, such as
[label](target "title"), and reference links, such as[label][id]plus[id]: target "title", are returned. The returned records containname,url,titleandline.previous_linksis copied before new entries are appended.- Parameters:
text – Markdown text to inspect.
previous_links – Optional existing links to prepend to the result.
- Returns:
A list of link records in discovery order. Each extracted record contains
name,url,titleandlinewhen available.
- pymdtools.mdcommon.search_link_in_md_text_json(text_md: str) str
Return links found in Markdown text as formatted JSON.
- Parameters:
text_md – Markdown text to inspect.
- Returns:
A deterministic, pretty-printed JSON string describing extracted links.
- pymdtools.mdcommon.sub_string_link_by_ref_md(unused_dummy: object, link: Mapping[str, str | int | None]) str
Build a Markdown reference definition from a link mapping.
- Parameters:
unused_dummy – Kept for compatibility with
re.subcallbacks.link – Mapping containing
url, optionalid_linkand optionaltitle.
- Returns:
Markdown reference definition line.
- Raises:
TypeError – If required fields are missing or are not strings.
- pymdtools.mdcommon.sub_string_link_md(unused_dummy: object, link: Mapping[str, str | int | None]) str
Build an inline Markdown link string from a link mapping.
- Parameters:
unused_dummy – Kept for compatibility with
re.subcallbacks.link – Mapping containing
name,urland optionaltitle.
- Returns:
Markdown inline link string.
- Raises:
TypeError – If required fields are missing or are not strings.
- pymdtools.mdcommon.sub_string_name_by_ref_md(unused_dummy: object, link: Mapping[str, str | int | None]) str
Build the visible part of a Markdown reference-style link.
- Parameters:
unused_dummy – Kept for compatibility with
re.subcallbacks.link – Mapping containing
nameandid_link.
- Returns:
Markdown reference-style link label.
- Raises:
TypeError – If required fields are missing or are not strings.
- pymdtools.mdcommon.update_link_from_old_link(text_md: str, old_link: Mapping[str, str | int | None], new_link: Mapping[str, str | int | None]) str
Replace a link identified by its previous label and URL.
This is stricter than
update_link_in_md_text(): both the old label and old URL must match before an inline link is replaced.- Parameters:
text_md – Markdown text to update.
old_link – Mapping describing the existing link. Requires
nameandurl.new_link – Mapping describing the replacement link.
- Returns:
Updated Markdown text.
- Raises:
TypeError – If required fields are missing or are not strings.
- pymdtools.mdcommon.update_link_in_md_text(text_md: str, name: str, new_link: Mapping[str, str | int | None]) str
Replace links identified by their visible label.
Inline links and reference-style links are both supported.
new_linkis copied internally, so the caller-provided mapping is not modified.- Parameters:
text_md – Markdown text to update.
name – Existing visible link label to replace.
new_link – Mapping containing at least
nameandurl.titleis optional.
- Returns:
Updated Markdown text.
- Raises:
TypeError – If required fields in
new_linkare missing or are not strings.
- pymdtools.mdcommon.update_links_from_old_link(text_md: str, links_couple: Sequence[tuple[Mapping[str, str | int | None], Mapping[str, str | int | None]]]) str
Apply several
(old_link, new_link)replacements to Markdown text.- Parameters:
text_md – Markdown text to update.
links_couple – Sequence of
(old_link, new_link)pairs.
- Returns:
Updated Markdown text after all replacements have been applied in order.
- Raises:
TypeError – If any replacement mapping misses required string fields.
- pymdtools.mdcommon.update_links_in_md_text(text_md: str, links: Mapping[str, str | int | None] | Sequence[Mapping[str, str | int | None]]) str
Replace one or more links in Markdown text.
A single link mapping or a sequence of link mappings is accepted. When
name_to_replaceis provided, it is used as the search key andnameis used as the replacement label.- Parameters:
text_md – Markdown text to update.
links – One link mapping or a sequence of mappings describing the new link values.
- Returns:
Updated Markdown text.
- Raises:
TypeError – If required link fields are missing or are not strings.