Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ dependencies = [
"markdown-it-py>=3.0.0"
]

[project.scripts]
slackify-markdown = "slackify_markdown.cli:main"

[project.urls]
"Homepage" = "https://github.com/thesmallstar/slackify-markdown-python"
"Bug Tracker" = "https://github.com/thesmallstar/slackify-markdown-python/issues"
Expand Down
18 changes: 17 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,16 @@ This library converts regular Markdown to Slack-specific markdown, making it eas

## Installation

**With pip**
```bash
pip install slackify-markdown
```

**With uv**
```bash
uv add slackify-markdown
```

## Usage

```python
Expand Down Expand Up @@ -54,6 +60,16 @@ This is *bold text* and this is _italic text_.
"""
```

## Command-line usage

After installation, use the `slackify-markdown` command to convert a file or stdin:

```bash
slackify-markdown README.md
slackify-markdown README.md --output README.slack.md
cat README.md | slackify-markdown
```

## Features

- Converts headers to Slack-compatible bold text
Expand Down Expand Up @@ -89,4 +105,4 @@ This project is licensed under the MIT License - see the LICENSE file for detail

## Acknowledgements

Shoutout to [jsarafaj](https://github.com/jsarafajr), his JS library has inspired the code and tests for this package.
Shoutout to [jsarafaj](https://github.com/jsarafajr), his JS library has inspired the code and tests for this package.
78 changes: 78 additions & 0 deletions src/slackify_markdown/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
from __future__ import annotations

import argparse
import sys
from pathlib import Path
from typing import Sequence, TextIO

from .service import slackify_markdown


def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
prog="slackify-markdown",
description="Convert Markdown to Slack mrkdwn.",
)
parser.add_argument(
"input",
nargs="?",
type=Path,
help="Markdown file to convert. Reads stdin when omitted or set to '-'.",
)
parser.add_argument(
"-o",
"--output",
type=Path,
help="Write output to this file instead of stdout.",
)
return parser


def read_markdown(input_path: Path | None, stdin: TextIO) -> str:
if input_path is None or str(input_path) == "-":
return stdin.read()

try:
return input_path.read_text(encoding="utf-8")
except OSError as exc:
raise RuntimeError(f"cannot read {input_path}: {exc}") from exc


def write_output(slack_text: str, output_path: Path | None, stdout: TextIO) -> None:
if output_path is None:
stdout.write(slack_text)
if slack_text and not slack_text.endswith("\n"):
stdout.write("\n")
return

try:
output_path.write_text(slack_text, encoding="utf-8")
except OSError as exc:
raise RuntimeError(f"cannot write {output_path}: {exc}") from exc


def run(
argv: Sequence[str] | None = None,
*,
stdin: TextIO = sys.stdin,
stdout: TextIO = sys.stdout,
stderr: TextIO = sys.stderr,
) -> int:
args = build_parser().parse_args(argv)

try:
markdown = read_markdown(args.input, stdin)
write_output(slackify_markdown(markdown), args.output, stdout)
except RuntimeError as exc:
print(f"slackify-markdown: {exc}", file=stderr)
return 1

return 0


def main() -> None:
raise SystemExit(run())


if __name__ == "__main__":
main()
35 changes: 35 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from io import StringIO
from pathlib import Path

import pytest

from slackify_markdown.cli import run


def test_cli_reads_stdin() -> None:
stdout = StringIO()

exit_code = run([], stdin=StringIO("[Link](https://example.com)"), stdout=stdout)

assert exit_code == 0
assert stdout.getvalue() == "<https://example.com|Link>\n"


def test_cli_reads_and_writes_files(tmp_path: Path) -> None:
input_path = tmp_path / "input.md"
output_path = tmp_path / "output.txt"
input_path.write_text("*italic*", encoding="utf-8")

exit_code = run([str(input_path), "--output", str(output_path)])

assert exit_code == 0
assert output_path.read_text(encoding="utf-8") == "_italic_\n"


def test_cli_reports_missing_input() -> None:
stderr = StringIO()

exit_code = run(["missing.md"], stderr=stderr)

assert exit_code == 1
assert "cannot read missing.md" in stderr.getvalue()