# [Advanced Usage](<#advanced-usage>)

## [Programmatic Use](<#programmatic-use>)

### [Converting Strings in Memory](<#converting-strings-in-memory>)

The simplest entry point is `convert()`, which accepts a Markdown string and returns a plain-text string ready for LinkedIn:

```python
from md2linkedin import convert

result = convert("**Hello**, *world*!")
print(result)  # 𝗛𝗲𝗹𝗹𝗼, 𝘸𝘰𝘳𝘭𝘥!
```

### [Converting Files](<#converting-files>)

Use `convert_file()` to read a `.md` file and write the output to a `.txt` file. The function returns the `Path` of the written file so you can chain further operations:

```python
from md2linkedin import convert_file

out = convert_file("post.md")
print(f"Written to: {out}")  # post.linkedin.txt

# Override the output path
out = convert_file("post.md", "my_post.txt")
```

### [Stripping and Preserving Links](<#stripping-and-preserving-links>)

By default, links are reduced to their display text (`[GitHub](https://github.com)` → `GitHub`). Pass `preserve_links=True` to keep the full link syntax, including any title. Autolinks and bare URLs come out in angle brackets:

```python
convert("[GitHub](https://github.com)", preserve_links=True)
# [GitHub](https://github.com)

convert("See https://github.com", preserve_links=True)
# See <https://github.com>

out = convert_file("post.md", preserve_links=True)
```

### [Direct Unicode Mapping](<#direct-unicode-mapping>)

The Unicode mapping functions are public and useful for applying a specific style to a plain string programmatically:

```python
from md2linkedin import (
    to_sans_bold,
    to_sans_italic,
    to_sans_bold_italic,
    to_monospace,
    apply_style,
)

to_sans_bold("Open to Work")  # 𝗢𝗽𝗲𝗻 𝘁𝗼 𝗪𝗼𝗿𝗸
to_sans_italic("3 years of exp")  # 3 𝘺𝘦𝘢𝘳𝘴 𝘰𝘧 𝘦𝘹𝘱
to_sans_bold_italic("Key insight")  # 𝙆𝙚𝙮 𝙞𝙣𝙨𝙞𝙜𝙝𝙩
to_monospace("print('hi')")  # 𝚙𝚛𝚒𝚗𝚝('𝚑𝚒')

# Choose the style at runtime: "bold", "italic" or "bold_italic"
apply_style("Hiring!", "bold")  # 𝗛𝗶𝗿𝗶𝗻𝗴!
```

---

## [What Gets Transformed (and What Doesn't)](<#what-gets-transformed-and-what-doesnt>)

| Markdown construct | Output |
| --- | --- |
| `**bold**` / `__bold__` | Unicode 𝗯𝗼𝗹𝗱 |
| `*italic*` / `_italic_` | Unicode 𝘪𝘵𝘢𝘭𝘪𝘤 |
| `***bold-italic***` / `___bold-italic___` | Unicode 𝙗𝙤𝙡𝙙-𝙞𝙩𝙖𝙡𝙞𝙘 |
| `~~strikethrough~~` | `strikethrough` (markers dropped) |
| `==highlight==` | `highlight` (markers dropped) |
| `` `inline code` `` | Unicode 𝚖𝚘𝚗𝚘𝚜𝚙𝚊𝚌𝚎 (backticks stripped) |
| ```` ```fenced block``` ```` | Unicode 𝚖𝚘𝚗𝚘𝚜𝚙𝚊𝚌𝚎 (fences stripped) |
| Four-space indented block | Unicode 𝚖𝚘𝚗𝚘𝚜𝚙𝚊𝚌𝚎 (indent stripped) |
| `# H1` | Bold uppercase Unicode + `━` borders |
| `## H2`–`###### H6` | Bold Unicode, no border |
| `Title` + `=====` / `-----` (setext) | Same as `# H1` / `## H2` |
| `[text](url)` | `text` (URL discarded) |
| `[text][ref]` + `[ref]: url` | `text` (definition consumed) |
| `<url>` and bare URLs | The URL, unstyled |
| `![alt](url)` | `alt` text (URL discarded) |
| `- item` | `• item` |
| `- nested` | `‣ nested` |
| `- nested twice` | `◦ nested twice` |
| `1. item` / `1) item` | `1. item` (renumbered in sequence) |
| `\\| a \\| b \\|` table | Bold header, cells joined by `\\|` |
| `> blockquote` | `>` prefix removed, at every depth |
| `---` / `* * *` thematic break | Dropped |
| Two trailing spaces, `<br>` | A line break |
| `<span>...</span>` | Tags removed, text kept |
| `<script>` / `<style>` | Dropped, content and all |
| `<!-- comment -->` | Dropped |
| `&amp;` / `&gt;` etc. | Decoded to `&` / `>` |
| `\*` backslash escapes | Resolved to literal `*` |
| Emojis, accented chars | Passed through unchanged |
| Digits inside bold | Also converted (`**123**` → `𝟭𝟮𝟯`) |
| Digits inside italic / bold-italic | Unchanged: Unicode has no such digits |
| `_snake_case_` in middle of word | **Not** italicised |

Two GitHub extensions are deliberately **not** enabled, because LinkedIn has nowhere to put their output: task-list checkboxes stay as the literal text `[ ]` / `[x]` in front of the bullet, and footnotes are read as ordinary link syntax, so `text[^1]` becomes `text^1` and the `[^1]: …` definition is consumed. Strip both before converting if you need them gone.

---

## [How Conversion Works](<#how-conversion-works>)

`convert()` is a two-stage pipeline:

1. [`comrak`](<https://pypi.org/project/comrak/>), Python bindings to a Rust CommonMark parser, parses the Markdown and renders it to HTML, with the tables, strikethrough, autolink and highlight extensions enabled.
2. [`selectolax`](<https://pypi.org/project/selectolax/>) parses that HTML, and a small renderer walks the tree, replacing each element with its LinkedIn equivalent: Unicode styling for emphasis and code, bullets and numbers for lists, `━` rules around a top-level heading.

Nothing is ever rendered *as* HTML. The intermediate document only exists so that the renderer sees a structured tree instead of a stream of characters, so raw HTML in the input cannot inject anything into the output — its tags are read for their structure and dropped.

The practical consequence is that every construct is read the way CommonMark defines it, in context: a four-space indent is a code block rather than prose, a `>` inside a `>` is a second level of quoting, a `[ref]: url` definition is consumed rather than printed, and a thematic break inside a list item is a break rather than a bullet.

---

## [Handling Edge Cases](<#handling-edge-cases>)

### [Nested Formatting](<#nested-formatting>)

Emphasis nests structurally, so the styles combine: italic inside bold comes out bold-italic, while the rest of the outer span stays bold:

```python
convert("***very important***")  # → 𝙫𝙚𝙧𝙮 𝙞𝙢𝙥𝙤𝙧𝙩𝙖𝙣𝙩
convert("**bold and *italic* inside**")  # → 𝗯𝗼𝗹𝗱 𝗮𝗻𝗱 𝙞𝙩𝙖𝙡𝙞𝙘 𝗶𝗻𝘀𝗶𝗱𝗲
```

### [Nested Bullet Lists](<#nested-bullet-lists>)

Each nesting level gets its own marker — `•`, `‣`, `◦`, `▪` — and two spaces of indentation. Levels deeper than the fourth reuse `▪`, with the indentation still growing by two spaces per level.

Depth is counted from the parsed tree, not from the raw indentation, so a document indented by four spaces per level nests exactly like one indented by two. Conversely, an item indented one space past its predecessor stays its sibling, since CommonMark allows a top-level item up to three leading spaces.

```python
convert("- Role\n  - Applications\n    - AI Launchpad")
# • Role
#   ‣ Applications
#     ◦ AI Launchpad
```

Ordered items are numbered in sequence from the list's first number, so `1.`, `1.`, `1.` becomes `1.`, `2.`, `3.` and a list that starts at `3.` keeps counting from three. A `)` delimiter becomes `.`. Bullets nested under an ordered item are indented to the width of its marker, so they line up under its text rather than under its number.

Everything else an item holds lines up the same way. A continuation paragraph, a wrapped line or a fenced code block inside an item is indented to the column the marker opened, and a loose list keeps the blank line between its items:

````python
convert("- item\n\n  ```\n  a = 1\n  ```")
# • item
#
#   𝚊 = 𝟷
````

### [Code Is Rendered in Monospace](<#code-is-rendered-in-monospace>)

By default, inline code and code blocks are converted to Unicode Mathematical Monospace characters. Only ASCII letters and digits are mapped; everything else, including Markdown syntax like `**`, passes through unchanged:

````python
convert("Use `**bold**` in Markdown")
# → Use **𝚋𝚘𝚕𝚍** in Markdown   (backticks stripped, letters monospaced, ** kept)

convert("```\nprint('hi')\n```")
# → 𝚙𝚛𝚒𝚗𝚝('𝚑𝚒')   (fences stripped, content monospaced)
````

To keep code as plain text instead:

````python
convert("Use `**bold**` in Markdown", monospace_code=False)
# → Use **bold** in Markdown   (backticks stripped, content as plain text)

convert("```\n**not bold**\n```", monospace_code=False)
# → **not bold**               (fences stripped, content as plain text)
````

Either way the content itself is untouched: nothing inside a code span or block is read as Markdown, so the `**` above survives.

### [Underscore Italic vs. Snake Case](<#underscore-italic-vs-snake-case>)

Single underscores are italicised only when they sit at word boundaries:

```python
convert("_italic_")  # → italic Unicode
convert("snake_case_variable")  # → unchanged
```

### [Unmatched / Orphaned Markers](<#unmatched-orphaned-markers>)

Unmatched asterisks or underscores are left untouched — `md2linkedin` never crashes on malformed input:

```python
convert("price: $10 * 3")  # → price: $10 * 3 (no crash)
convert("under_score alone")  # → unchanged
```

### [Large Documents](<#large-documents>)

`convert()` does no I/O and holds only the document it is given, the HTML it renders from it, and the output. Parsing happens in Rust and C rather than in Python, and the cost scales linearly with input length, so it is safe to call on large files:

```python
from pathlib import Path

big_md = Path("quarterly_report.md").read_text()
result = convert(big_md)
```

### [Emojis and Non-ASCII Characters](<#emojis-and-non-ascii-characters>)

Emojis and accented characters pass through unchanged — only ASCII letters and digits are remapped to Unicode Mathematical variants:

```python
from md2linkedin import convert

result = convert("**Excited to share** 🎉 *check this out*!")
print(result)  # 𝗘𝘅𝗰𝗶𝘁𝗲𝗱 𝘁𝗼 𝘀𝗵𝗮𝗿𝗲 🎉 𝘤𝘩𝘦𝘤𝘬 𝘵𝘩𝘪𝘴 𝘰𝘶𝘵!
```

---

## [Integration with Pandoc (LaTeX → LinkedIn)](<#integration-with-pandoc-latex-linkedin>)

If you write in LaTeX (e.g. a résumé), you can pipe through `pandoc` first:

```bash
pandoc --from=latex --to=markdown_strict --wrap=none resume.tex | md2linkedin
```

Or in Python:

```python
import subprocess
from pathlib import Path

from md2linkedin import convert

result = subprocess.run(
    ["pandoc", "--from=latex", "--to=markdown_strict", "--wrap=none"],
    input=Path("resume.tex").read_text(),
    capture_output=True,
    text=True,
    check=True,
)
linkedin_text = convert(result.stdout)
```

---

## [CLI Reference](<#cli-reference>)

Run `md2linkedin --help` for the full text.

```text
Usage: md2linkedin [OPTIONS] [INPUT_FILE]

Options:
  -V, --version        Show the version and exit.
  -o, --output FILE    Output file path. Defaults to INPUT_FILE with a
                       '.linkedin.txt' extension. Ignored when reading from
                       stdin.
  --preserve-links     Keep Markdown link syntax ([text](url)) in the output
                       instead of stripping URLs.
  --no-monospace-code  Disable monospace Unicode rendering for inline code and
                       fenced code blocks. When set, code is kept as plain
                       text, with the backticks and fences stripped.
  -h, --help           Show this message and exit.
```
