mdedit 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
mdedit/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """mdedit — a terminal Markdown viewer and editor built with Textual."""
2
+
3
+ __version__ = "0.1.0"
mdedit/__main__.py ADDED
@@ -0,0 +1,40 @@
1
+ """Command-line entry point for mdedit."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ from pathlib import Path
7
+
8
+ from mdedit import __version__
9
+ from mdedit.app import MDEditApp
10
+
11
+
12
+ def build_parser() -> argparse.ArgumentParser:
13
+ parser = argparse.ArgumentParser(
14
+ prog="mdedit",
15
+ description="A terminal Markdown viewer and editor.",
16
+ )
17
+ parser.add_argument(
18
+ "file",
19
+ nargs="?",
20
+ default=None,
21
+ help="Markdown file to open (created on first save if it doesn't exist yet)",
22
+ )
23
+ parser.add_argument(
24
+ "--version",
25
+ action="version",
26
+ version=f"mdedit {__version__}",
27
+ )
28
+ return parser
29
+
30
+
31
+ def main(argv: list[str] | None = None) -> None:
32
+ parser = build_parser()
33
+ args = parser.parse_args(argv)
34
+ file_path = Path(args.file).expanduser() if args.file else None
35
+ app = MDEditApp(file_path=file_path)
36
+ app.run()
37
+
38
+
39
+ if __name__ == "__main__":
40
+ main()
mdedit/app.py ADDED
@@ -0,0 +1,55 @@
1
+ """The mdedit Textual application."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from textual import work
8
+ from textual.app import App
9
+
10
+ from mdedit.document import Document, DocumentError
11
+ from mdedit.screens.editor import EditorScreen
12
+ from mdedit.screens.quit_confirm import QuitConfirmScreen
13
+ from mdedit.screens.welcome import WelcomeScreen
14
+
15
+
16
+ class MDEditApp(App[None]):
17
+ """A terminal Markdown viewer/editor."""
18
+
19
+ TITLE = "mdedit"
20
+ CSS_PATH = "styles/mdedit.tcss"
21
+
22
+ def __init__(self, file_path: Path | None = None) -> None:
23
+ super().__init__()
24
+ self._initial_path = file_path
25
+
26
+ def on_mount(self) -> None:
27
+ if self._initial_path is not None:
28
+ self.open_path(self._initial_path)
29
+ else:
30
+ self.push_screen(WelcomeScreen())
31
+
32
+ def open_path(self, path: Path) -> None:
33
+ """Load ``path`` as a Document and switch to the editor screen."""
34
+ try:
35
+ document = Document.from_path(path)
36
+ except DocumentError as exc:
37
+ self.notify(str(exc), severity="error", timeout=6)
38
+ return
39
+ self.push_screen(EditorScreen(document))
40
+
41
+ @work
42
+ async def action_quit(self) -> None:
43
+ screen = self.screen
44
+ document = getattr(screen, "document", None)
45
+ if document is not None and document.dirty:
46
+ choice = await self.push_screen_wait(QuitConfirmScreen())
47
+ if choice == "cancel":
48
+ return
49
+ if choice == "save":
50
+ try:
51
+ document.save()
52
+ except DocumentError as exc:
53
+ self.notify(str(exc), severity="error", timeout=6)
54
+ return
55
+ self.exit()
mdedit/document.py ADDED
@@ -0,0 +1,105 @@
1
+ """Core document model.
2
+
3
+ This module is intentionally free of any Textual (or other UI-framework)
4
+ imports. It is the seam between the on-disk file and whichever widget is
5
+ currently displaying it (rendered view or source editor), so that a future
6
+ "always-rendered" WYSIWYG editing mode could be layered in later behind the
7
+ same narrow interface (``.text`` / ``.set_text()`` / ``.dirty``) without
8
+ rewriting the screens or widgets that consume it today.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from pathlib import Path
14
+
15
+
16
+ class DocumentError(Exception):
17
+ """Raised when a document fails to load or save."""
18
+
19
+
20
+ class Document:
21
+ """An in-memory Markdown document, optionally backed by a file on disk."""
22
+
23
+ def __init__(self, path: Path | None = None, text: str = "") -> None:
24
+ self.path = path
25
+ self._text = text
26
+ self._saved_text = text
27
+ self._dirty = False
28
+
29
+ def __repr__(self) -> str:
30
+ return f"Document(path={self.path!r}, dirty={self._dirty!r})"
31
+
32
+ @classmethod
33
+ def from_path(cls, path: Path) -> Document:
34
+ """Load a document from ``path``.
35
+
36
+ If ``path`` does not exist, an empty new document is returned with
37
+ ``path`` pre-set (matching common editor UX: the file is created on
38
+ first save). Genuine I/O errors (permission denied, path is a
39
+ directory, etc.) raise :class:`DocumentError`.
40
+ """
41
+ path = Path(path)
42
+ if not path.exists():
43
+ return cls(path=path, text="")
44
+ try:
45
+ text = path.read_text(encoding="utf-8")
46
+ except OSError as exc:
47
+ raise DocumentError(f"Could not read {path}: {exc}") from exc
48
+ return cls(path=path, text=text)
49
+
50
+ @property
51
+ def text(self) -> str:
52
+ """The current in-memory content."""
53
+ return self._text
54
+
55
+ def set_text(self, new_text: str) -> None:
56
+ """Replace the in-memory content, updating the dirty flag."""
57
+ self._text = new_text
58
+ self._dirty = self._text != self._saved_text
59
+
60
+ @property
61
+ def dirty(self) -> bool:
62
+ """Whether the in-memory content differs from what's on disk."""
63
+ return self._dirty
64
+
65
+ @property
66
+ def is_new(self) -> bool:
67
+ """Whether this document has no backing file yet."""
68
+ return self.path is None
69
+
70
+ @property
71
+ def display_name(self) -> str:
72
+ """A short, human-readable name for status/title display."""
73
+ return self.path.name if self.path is not None else "untitled.md"
74
+
75
+ def save(self, path: Path | None = None) -> None:
76
+ """Write the current content to disk.
77
+
78
+ Pass ``path`` to save-as; otherwise saves to the document's existing
79
+ path. Raises :class:`DocumentError` if there is no path to save to,
80
+ or if the write fails.
81
+ """
82
+ target = path if path is not None else self.path
83
+ if target is None:
84
+ raise DocumentError("No path to save to")
85
+ target = Path(target)
86
+ try:
87
+ target.parent.mkdir(parents=True, exist_ok=True)
88
+ target.write_text(self._text, encoding="utf-8")
89
+ except OSError as exc:
90
+ raise DocumentError(f"Could not write {target}: {exc}") from exc
91
+ self.path = target
92
+ self._saved_text = self._text
93
+ self._dirty = False
94
+
95
+ def reload(self) -> None:
96
+ """Discard in-memory changes and re-read the file from disk."""
97
+ if self.path is None:
98
+ raise DocumentError("Cannot reload a document with no path")
99
+ try:
100
+ text = self.path.read_text(encoding="utf-8")
101
+ except OSError as exc:
102
+ raise DocumentError(f"Could not read {self.path}: {exc}") from exc
103
+ self._text = text
104
+ self._saved_text = text
105
+ self._dirty = False
File without changes
@@ -0,0 +1,102 @@
1
+ """The main screen: a view/edit toggle over a single Document."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from textual.app import ComposeResult
6
+ from textual.containers import Vertical
7
+ from textual.screen import Screen
8
+ from textual.widgets import ContentSwitcher, Footer, Header
9
+
10
+ from mdedit.document import Document, DocumentError
11
+ from mdedit.widgets.source_editor import SourceEditor
12
+ from mdedit.widgets.status_bar import StatusBar
13
+ from mdedit.widgets.viewer import DocumentViewer
14
+
15
+
16
+ class EditorScreen(Screen):
17
+ """Displays a Document in either rendered (view) or source (edit) mode."""
18
+
19
+ BINDINGS = [
20
+ ("ctrl+t", "toggle_mode", "Toggle view/edit"),
21
+ ("ctrl+s", "save", "Save"),
22
+ ("ctrl+r", "reload", "Reload"),
23
+ ]
24
+
25
+ VIEW_ID = "viewer"
26
+ EDIT_ID = "editor"
27
+
28
+ DEFAULT_CSS = """
29
+ EditorScreen > Vertical {
30
+ height: 1fr;
31
+ }
32
+
33
+ EditorScreen ContentSwitcher {
34
+ height: 1fr;
35
+ }
36
+ """
37
+
38
+ def __init__(self, document: Document) -> None:
39
+ super().__init__()
40
+ self.document = document
41
+
42
+ def compose(self) -> ComposeResult:
43
+ yield Header()
44
+ with Vertical():
45
+ with ContentSwitcher(initial=self.VIEW_ID):
46
+ yield DocumentViewer(id=self.VIEW_ID, show_table_of_contents=True)
47
+ yield SourceEditor.for_document(self.document, id=self.EDIT_ID)
48
+ yield StatusBar()
49
+ yield Footer()
50
+
51
+ async def on_mount(self) -> None:
52
+ await self.query_one(DocumentViewer).load_document(self.document)
53
+ self._refresh_status()
54
+
55
+ @property
56
+ def mode(self) -> str:
57
+ switcher = self.query_one(ContentSwitcher)
58
+ return "EDIT" if switcher.current == self.EDIT_ID else "VIEW"
59
+
60
+ def _refresh_status(self) -> None:
61
+ self.query_one(StatusBar).update_status(self.document, self.mode)
62
+ self.title = self.document.display_name
63
+
64
+ async def action_toggle_mode(self) -> None:
65
+ switcher = self.query_one(ContentSwitcher)
66
+ if switcher.current == self.EDIT_ID:
67
+ # Leaving edit mode: sync the editor's text back into the
68
+ # Document before switching, so view mode always reflects the
69
+ # latest edits.
70
+ source = self.query_one(SourceEditor).text
71
+ self.document.set_text(source)
72
+ await self.query_one(DocumentViewer).load_document(self.document)
73
+ switcher.current = self.VIEW_ID
74
+ else:
75
+ switcher.current = self.EDIT_ID
76
+ self.query_one(SourceEditor).focus()
77
+ self._refresh_status()
78
+
79
+ def action_save(self) -> None:
80
+ # If currently in edit mode, make sure in-flight edits are captured
81
+ # before writing to disk.
82
+ if self.mode == "EDIT":
83
+ self.document.set_text(self.query_one(SourceEditor).text)
84
+ try:
85
+ self.document.save()
86
+ except DocumentError as exc:
87
+ self.notify(str(exc), severity="error")
88
+ else:
89
+ self.notify(f"Saved {self.document.display_name}")
90
+ self._refresh_status()
91
+
92
+ async def action_reload(self) -> None:
93
+ try:
94
+ self.document.reload()
95
+ except DocumentError as exc:
96
+ self.notify(str(exc), severity="error")
97
+ return
98
+ self.query_one(SourceEditor).text = self.document.text
99
+ if self.mode == "VIEW":
100
+ await self.query_one(DocumentViewer).load_document(self.document)
101
+ self.notify(f"Reloaded {self.document.display_name}")
102
+ self._refresh_status()
@@ -0,0 +1,54 @@
1
+ """Modal shown when quitting with unsaved changes."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from textual.app import ComposeResult
6
+ from textual.containers import Horizontal, Vertical
7
+ from textual.screen import ModalScreen
8
+ from textual.widgets import Button, Label
9
+
10
+
11
+ class QuitConfirmScreen(ModalScreen[str]):
12
+ """Ask the user whether to save, discard, or cancel before quitting.
13
+
14
+ Dismisses with one of ``"save"``, ``"discard"``, or ``"cancel"``.
15
+ """
16
+
17
+ DEFAULT_CSS = """
18
+ QuitConfirmScreen {
19
+ align: center middle;
20
+ }
21
+
22
+ QuitConfirmScreen > Vertical {
23
+ width: auto;
24
+ height: auto;
25
+ border: thick $panel;
26
+ padding: 1 2;
27
+ }
28
+
29
+ QuitConfirmScreen Label {
30
+ width: 100%;
31
+ content-align: center middle;
32
+ padding-bottom: 1;
33
+ }
34
+
35
+ QuitConfirmScreen Horizontal {
36
+ width: auto;
37
+ height: auto;
38
+ }
39
+
40
+ QuitConfirmScreen Button {
41
+ margin: 0 1;
42
+ }
43
+ """
44
+
45
+ def compose(self) -> ComposeResult:
46
+ with Vertical():
47
+ yield Label("You have unsaved changes. Save before quitting?")
48
+ with Horizontal():
49
+ yield Button("Save", id="save", variant="success")
50
+ yield Button("Discard", id="discard", variant="error")
51
+ yield Button("Cancel", id="cancel")
52
+
53
+ def on_button_pressed(self, event: Button.Pressed) -> None:
54
+ self.dismiss(event.button.id)
@@ -0,0 +1,47 @@
1
+ """Shown on startup when no file was given on the command line."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from textual.app import ComposeResult
8
+ from textual.containers import Vertical
9
+ from textual.screen import Screen
10
+ from textual.widgets import Footer, Header, Input, Static
11
+
12
+
13
+ class WelcomeScreen(Screen):
14
+ """Prompt for a Markdown file path to open (or create)."""
15
+
16
+ DEFAULT_CSS = """
17
+ WelcomeScreen {
18
+ align: center middle;
19
+ }
20
+
21
+ WelcomeScreen > Vertical {
22
+ width: 60;
23
+ height: auto;
24
+ border: thick $panel;
25
+ padding: 1 2;
26
+ }
27
+
28
+ WelcomeScreen Static {
29
+ padding-bottom: 1;
30
+ }
31
+ """
32
+
33
+ def compose(self) -> ComposeResult:
34
+ yield Header()
35
+ with Vertical():
36
+ yield Static("mdedit — open or create a Markdown file")
37
+ yield Input(placeholder="path/to/file.md", id="path-input")
38
+ yield Footer()
39
+
40
+ def on_mount(self) -> None:
41
+ self.query_one(Input).focus()
42
+
43
+ def on_input_submitted(self, event: Input.Submitted) -> None:
44
+ value = event.value.strip()
45
+ if not value:
46
+ return
47
+ self.app.open_path(Path(value).expanduser())
@@ -0,0 +1,6 @@
1
+ /* Global styles for mdedit. Most widgets rely on Textual's defaults; this
2
+ file only overrides what needs project-specific tweaks. */
3
+
4
+ Screen {
5
+ background: $surface;
6
+ }
File without changes
@@ -0,0 +1,25 @@
1
+ """The editable, syntax-highlighted source view of a Document."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from textual.widgets import TextArea
6
+
7
+ from mdedit.document import Document
8
+
9
+
10
+ class SourceEditor(TextArea):
11
+ """A TextArea configured for editing Markdown source."""
12
+
13
+ @classmethod
14
+ def for_document(cls, document: Document, id: str | None = None) -> SourceEditor:
15
+ """Build a SourceEditor pre-populated with ``document``'s text."""
16
+ editor = cls.code_editor(
17
+ document.text,
18
+ language="markdown",
19
+ theme="monokai",
20
+ soft_wrap=True,
21
+ tab_behavior="indent",
22
+ id=id,
23
+ )
24
+ editor.show_line_numbers = False
25
+ return editor
@@ -0,0 +1,33 @@
1
+ """A small status bar: filename, dirty indicator, current mode."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from textual.reactive import reactive
6
+ from textual.widgets import Static
7
+
8
+ from mdedit.document import Document
9
+
10
+
11
+ class StatusBar(Static):
12
+ """Shows the open file's name, dirty state, and view/edit mode."""
13
+
14
+ # Deliberately NOT docked: EditorScreen's Footer already docks to the
15
+ # bottom of the screen, and a second bottom-docked widget in the same
16
+ # container overlaps it exactly rather than stacking above it. Instead
17
+ # StatusBar is a normal flow child placed just above the Footer, inside
18
+ # a wrapping Vertical (see EditorScreen).
19
+ DEFAULT_CSS = """
20
+ StatusBar {
21
+ height: 1;
22
+ background: $panel;
23
+ color: $text-muted;
24
+ padding: 0 1;
25
+ }
26
+ """
27
+
28
+ mode: reactive[str] = reactive("VIEW")
29
+
30
+ def update_status(self, document: Document, mode: str) -> None:
31
+ self.mode = mode
32
+ dirty = " •" if document.dirty else ""
33
+ self.update(f"{document.display_name}{dirty} — {mode}")
@@ -0,0 +1,15 @@
1
+ """The read-only rendered view of a Document."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from textual.widgets import MarkdownViewer
6
+
7
+ from mdedit.document import Document
8
+
9
+
10
+ class DocumentViewer(MarkdownViewer):
11
+ """A MarkdownViewer bound to a :class:`~mdedit.document.Document`."""
12
+
13
+ async def load_document(self, document: Document) -> None:
14
+ """Render ``document``'s current text."""
15
+ await self.document.update(document.text)
@@ -0,0 +1,102 @@
1
+ Metadata-Version: 2.5
2
+ Name: mdedit
3
+ Version: 0.1.0
4
+ Summary: A terminal Markdown viewer and editor built with Textual
5
+ Project-URL: Homepage, https://github.com/matplo/mdedit
6
+ Project-URL: Repository, https://github.com/matplo/mdedit
7
+ Author-email: matplo <ploskon@gmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: editor,markdown,terminal,textual,tui
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Terminals
21
+ Classifier: Topic :: Text Editors
22
+ Requires-Python: >=3.10
23
+ Requires-Dist: rich>=13.7
24
+ Requires-Dist: textual[syntax]<9,>=8.2
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest; extra == 'dev'
27
+ Requires-Dist: pytest-asyncio; extra == 'dev'
28
+ Requires-Dist: ruff; extra == 'dev'
29
+ Requires-Dist: textual-dev; extra == 'dev'
30
+ Description-Content-Type: text/markdown
31
+
32
+ # mdedit
33
+
34
+ A terminal Markdown viewer and editor, built with
35
+ [Textual](https://github.com/Textualize/textual) and
36
+ [Rich](https://github.com/Textualize/rich).
37
+
38
+ Open a `.md` file to a fully rendered view (headings, tables, code blocks,
39
+ lists, block quotes...) and press a single key to toggle into a
40
+ syntax-highlighted source editor for the same file — then toggle back to see
41
+ your changes rendered.
42
+
43
+ ## Install
44
+
45
+ ```bash
46
+ pip install git+https://github.com/matplo/mdedit
47
+ ```
48
+
49
+ ## Usage
50
+
51
+ ```bash
52
+ mdedit path/to/file.md # open (or create) a file
53
+ mdedit # start with a prompt to open/create a file
54
+ ```
55
+
56
+ ### Key bindings
57
+
58
+ | Key | Action |
59
+ |----------|----------------------------------|
60
+ | `ctrl+t` | Toggle between view and edit mode |
61
+ | `ctrl+s` | Save |
62
+ | `ctrl+r` | Reload from disk |
63
+ | `ctrl+q` | Quit (prompts if unsaved changes) |
64
+
65
+ > **Note:** if `ctrl+s` doesn't seem to do anything in your terminal, it may
66
+ > be intercepted by terminal flow control (XON/XOFF). Run `stty -ixon` in
67
+ > your shell first, or use the Save option from the command palette
68
+ > (`ctrl+p`).
69
+
70
+ ## Development
71
+
72
+ ```bash
73
+ pip install -e ".[dev]"
74
+ pytest
75
+ ruff check .
76
+ ruff format --check .
77
+ ```
78
+
79
+ ### Releasing to PyPI
80
+
81
+ Releases publish automatically via GitHub Actions using
82
+ [trusted publishing](https://docs.pypi.org/trusted-publishers/) (no API
83
+ token stored anywhere). To cut a release:
84
+
85
+ 1. Bump `__version__` in `src/mdedit/__init__.py`, commit it.
86
+ 2. Tag and push: `git tag vX.Y.Z && git push origin vX.Y.Z`
87
+ 3. The `Publish to PyPI` workflow builds the sdist/wheel and uploads them.
88
+
89
+ ## Roadmap
90
+
91
+ v1 is intentionally scoped to a single-file, toggle-based view/edit
92
+ experience. Not yet included (candidates for a future version):
93
+
94
+ - Always-rendered, true WYSIWYG editing (edit directly on rendered text,
95
+ no separate source view)
96
+ - Split-pane side-by-side edit + preview
97
+ - Browser-style navigation history, bookmarks, remote/URL loading
98
+ - Multiple open files / tabs
99
+
100
+ ## License
101
+
102
+ MIT
@@ -0,0 +1,18 @@
1
+ mdedit/__init__.py,sha256=6nvmAQxGX91qhslvshvDCMrgguixzp8tG-rPVmkkuW4,98
2
+ mdedit/__main__.py,sha256=n881eX_eppF5E8swG6W3AILLKPmTCEPhskvMZ85vbl0,952
3
+ mdedit/app.py,sha256=I8mlNXIvADdQKDhkkYlpeTygzk0J0p-YIgJpu17fzG0,1724
4
+ mdedit/document.py,sha256=IrGoyotEJRtDSrwKl7LopBFARFyqO5T7X4JGcvOtKyc,3762
5
+ mdedit/screens/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ mdedit/screens/editor.py,sha256=seYYo8aeAwqdYtw5JEtdwMW7yhu_pfTU12I84M8i5oo,3516
7
+ mdedit/screens/quit_confirm.py,sha256=SSPYApFmlSBYffKrw8v7xagnaBOI6Dclzk2C1iqZSuw,1419
8
+ mdedit/screens/welcome.py,sha256=OYVLLdfvWi2gCPcAcL0tFe70euCl44T27PCxXujKBjE,1195
9
+ mdedit/styles/mdedit.tcss,sha256=xzIOl1pQDoesI2MYjaDa6pLlB2vohxpLfw8qLtbat-4,175
10
+ mdedit/widgets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ mdedit/widgets/source_editor.py,sha256=lpvyxfHiW2vKU9KV_O9yKGq8cp1f4wT4LzW_1Svyocs,728
12
+ mdedit/widgets/status_bar.py,sha256=rjgb0zIwn2TUCYcARptv9HaO3sT4DuJyEuhK61RAxoY,1063
13
+ mdedit/widgets/viewer.py,sha256=NuJeOV9WaiR86nimG0W3svP5H6IQ6D5hntfrLxxVHJc,445
14
+ mdedit-0.1.0.dist-info/METADATA,sha256=mJ3fyFNL0v7MQwtiol9mDKgjw9mMAs6lfHswCLoI4QE,3217
15
+ mdedit-0.1.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
16
+ mdedit-0.1.0.dist-info/entry_points.txt,sha256=rWiWFUVHpkV3IK_qIKvx1oqF-VnZzrslwz2NRkEGBek,48
17
+ mdedit-0.1.0.dist-info/licenses/LICENSE,sha256=FdvapQASUK3J-7zKooV8mCVgxSTQvLoNS4JV7YAiDvw,1063
18
+ mdedit-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ mdedit = mdedit.__main__:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 matplo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.