markup-plus 0.6.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.
@@ -0,0 +1,23 @@
1
+ """
2
+ Markup+ — A modern markup language with more features than Markdown.
3
+
4
+ Features:
5
+ - Markdown-compatible syntax
6
+ - Variables, conditionals, loops
7
+ - Reusable components
8
+ - Charts (bar, line, pie, doughnut)
9
+ - Math formulas (KaTeX)
10
+ - Tabs, collapse, alerts, timeline
11
+ - Auto RTL detection (Persian, Arabic, Hebrew)
12
+ - Rich code blocks with copy/download/preview
13
+ - Light/dark themes
14
+ """
15
+
16
+ __version__ = "0.6.0"
17
+ __author__ = "Hossein Ataee"
18
+ __license__ = "Markup+ — Non-Commercial License"
19
+
20
+ from .renderer import to_html
21
+ from .parser import parse_text
22
+
23
+ __all__ = ["to_html", "parse_text", "__version__"]
@@ -0,0 +1,8 @@
1
+ """Entry point for `python -m markup_plus`."""
2
+
3
+ import sys
4
+
5
+ from .cli import main
6
+
7
+ if __name__ == "__main__":
8
+ sys.exit(main())
markup_plus/ast.py ADDED
@@ -0,0 +1,260 @@
1
+ """
2
+ Markup+ Abstract Syntax Tree (AST)
3
+
4
+ Defines node types that represent the structure of a Markup+ document.
5
+ """
6
+
7
+ from dataclasses import dataclass, field
8
+ from typing import List, Dict, Optional, Any
9
+
10
+
11
+ # ============================================================
12
+ # Base
13
+ # ============================================================
14
+
15
+ @dataclass
16
+ class Node:
17
+ """Base class for all AST nodes."""
18
+ line: int = 0
19
+
20
+
21
+ # ============================================================
22
+ # Block-level nodes
23
+ # ============================================================
24
+
25
+ @dataclass
26
+ class Document(Node):
27
+ """Root node — contains all blocks + metadata + variables."""
28
+ children: List[Node] = field(default_factory=list)
29
+ meta: Dict[str, str] = field(default_factory=dict)
30
+ footnotes: Dict[str, str] = field(default_factory=dict)
31
+ variables: Dict[str, Any] = field(default_factory=dict)
32
+
33
+
34
+ @dataclass
35
+ class VariableDef(Node):
36
+ """@let name = value"""
37
+ name: str = ""
38
+ value: Any = None
39
+ raw_value: str = ""
40
+
41
+
42
+ @dataclass
43
+ class Heading(Node):
44
+ """# Heading 1, ## Heading 2, ### Heading 3"""
45
+ level: int = 1
46
+ text: str = ""
47
+ slug: str = ""
48
+
49
+
50
+ @dataclass
51
+ class Paragraph(Node):
52
+ """Regular paragraph."""
53
+ text: str = ""
54
+
55
+
56
+ @dataclass
57
+ class ListBlock(Node):
58
+ """Ordered or unordered list. `checked` for task lists."""
59
+ ordered: bool = False
60
+ items: List[str] = field(default_factory=list)
61
+ checked: List = field(default_factory=list)
62
+
63
+
64
+ @dataclass
65
+ class BlockQuote(Node):
66
+ """> Quoted text (can span multiple lines)"""
67
+ text: str = ""
68
+
69
+
70
+ @dataclass
71
+ class HorizontalRule(Node):
72
+ """--- separator"""
73
+ pass
74
+
75
+
76
+ @dataclass
77
+ class CodeBlock(Node):
78
+ """Fenced code block with advanced options."""
79
+ language: str = ""
80
+ code: str = ""
81
+ title: str = ""
82
+ copy: bool = True
83
+ download: bool = True
84
+ run: bool = False
85
+ share: bool = False
86
+ linenos: bool = False
87
+ highlight: List[int] = field(default_factory=list)
88
+ wrap: bool = False
89
+
90
+
91
+ @dataclass
92
+ class ImageBlock(Node):
93
+ """![alt](url "title"){options}"""
94
+ alt: str = ""
95
+ url: str = ""
96
+ title: str = ""
97
+ width: str = ""
98
+ height: str = ""
99
+ align: str = ""
100
+ link: str = ""
101
+ caption: str = ""
102
+ description: str = ""
103
+ zoomable: bool = True
104
+
105
+
106
+ @dataclass
107
+ class GalleryBlock(Node):
108
+ """@gallery {columns=N} ... @end"""
109
+ columns: int = 3
110
+ images: List[ImageBlock] = field(default_factory=list)
111
+ caption: str = ""
112
+
113
+
114
+ @dataclass
115
+ class TableBlock(Node):
116
+ """| Header | Header |"""
117
+ headers: List[str] = field(default_factory=list)
118
+ rows: List[List[str]] = field(default_factory=list)
119
+ alignments: List[str] = field(default_factory=list)
120
+
121
+
122
+ @dataclass
123
+ class TOCBlock(Node):
124
+ """@toc {title="..."}"""
125
+ title: str = "Table of Contents"
126
+
127
+
128
+ # ============================================================
129
+ # Control flow nodes (Phase 4)
130
+ # ============================================================
131
+
132
+ @dataclass
133
+ class IfBlock(Node):
134
+ """@if ... @elif ... @else ... @endif"""
135
+ branches: List = field(default_factory=list)
136
+
137
+
138
+ @dataclass
139
+ class EachBlock(Node):
140
+ """@each item in items ... @end"""
141
+ item_name: str = ""
142
+ index_name: str = ""
143
+ iterable_expr: str = ""
144
+ children: List[Node] = field(default_factory=list)
145
+
146
+
147
+ @dataclass
148
+ class ImportBlock(Node):
149
+ """@import "path/to/file.mup" — reserved"""
150
+ path: str = ""
151
+
152
+
153
+ # ============================================================
154
+ # Rich features (Phase 5)
155
+ # ============================================================
156
+
157
+ @dataclass
158
+ class ComponentDef(Node):
159
+ """@def Name(param1, param2) ... @end"""
160
+ name: str = ""
161
+ params: List[str] = field(default_factory=list)
162
+ children: List[Node] = field(default_factory=list)
163
+
164
+
165
+ @dataclass
166
+ class ComponentCall(Node):
167
+ """@Name(param1="value", param2="value")"""
168
+ name: str = ""
169
+ args: Dict[str, Any] = field(default_factory=dict)
170
+
171
+
172
+ @dataclass
173
+ class ChartBlock(Node):
174
+ """@chart(type="bar") data: [...] labels: [...] @end"""
175
+ chart_type: str = "bar"
176
+ data: List = field(default_factory=list)
177
+ labels: List[str] = field(default_factory=list)
178
+ title: str = ""
179
+ color: str = ""
180
+
181
+
182
+ @dataclass
183
+ class MathBlock(Node):
184
+ """$$ E = mc^2 $$"""
185
+ latex: str = ""
186
+ display: bool = True
187
+
188
+
189
+ @dataclass
190
+ class TabsBlock(Node):
191
+ """@tabs @tab "Title" ... @end @end"""
192
+ tabs: List = field(default_factory=list)
193
+
194
+
195
+ @dataclass
196
+ class CollapseBlock(Node):
197
+ """@collapse @item "Title" ... @end @end"""
198
+ items: List = field(default_factory=list)
199
+
200
+
201
+ @dataclass
202
+ class AlertBlock(Node):
203
+ """@note / @warning / @tip / @danger / @success ... @end"""
204
+ alert_type: str = "note"
205
+ children: List[Node] = field(default_factory=list)
206
+
207
+
208
+ @dataclass
209
+ class QuoteBlock(Node):
210
+ """@quote(author="...", source="...") Text... @end"""
211
+ author: str = ""
212
+ source: str = ""
213
+ text: str = ""
214
+
215
+
216
+ @dataclass
217
+ class TimelineBlock(Node):
218
+ """@timeline date: text @end"""
219
+ events: List = field(default_factory=list)
220
+
221
+
222
+ # ============================================================
223
+ # Inline-level nodes
224
+ # ============================================================
225
+
226
+ @dataclass
227
+ class Text(Node):
228
+ content: str = ""
229
+
230
+
231
+ @dataclass
232
+ class Bold(Node):
233
+ content: str = ""
234
+
235
+
236
+ @dataclass
237
+ class Italic(Node):
238
+ content: str = ""
239
+
240
+
241
+ @dataclass
242
+ class InlineCode(Node):
243
+ content: str = ""
244
+
245
+
246
+ @dataclass
247
+ class Strikethrough(Node):
248
+ content: str = ""
249
+
250
+
251
+ @dataclass
252
+ class Link(Node):
253
+ text: str = ""
254
+ url: str = ""
255
+ title: str = ""
256
+
257
+
258
+ @dataclass
259
+ class AutoLink(Node):
260
+ url: str = ""
@@ -0,0 +1,173 @@
1
+ """
2
+ Markup+ CLI — Command-line interface.
3
+
4
+ Provides the `mup` command.
5
+ """
6
+
7
+ import sys
8
+
9
+ from . import theme
10
+ from .. import __version__
11
+
12
+
13
+ HELP_TEXT = """
14
+ ╔══════════════════════════════════════════════════════════════╗
15
+ ║ Markup+ ║
16
+ ║ A modern markup language for everyone ║
17
+ ╚══════════════════════════════════════════════════════════════╝
18
+
19
+ USAGE
20
+ mup <command> [options]
21
+
22
+ COMMANDS
23
+ mup <file> Convert file to HTML
24
+ mup <file>.md Convert Markdown to HTML
25
+ mup new <name> Create a new .mup file
26
+ mup open <file> View file in terminal
27
+ mup check <file> Validate file for errors
28
+ mup init [name] Create a new project
29
+
30
+ OPTIONS
31
+ -d, --dark Use dark theme
32
+ -l, --light Use light theme (default)
33
+ -r, --rtl Right-to-left layout
34
+ --ltr Left-to-right layout
35
+ -o, --output <file> Output file path
36
+ --css <file> Apply custom CSS file
37
+ --debug Show debug info
38
+ -h, --help Show this help
39
+ -v, --version Show version
40
+
41
+ EXAMPLES
42
+ mup hello.mup Convert hello.mup
43
+ mup readme.md Convert markdown to HTML
44
+ mup hello.mup --dark Dark theme
45
+ mup hello.mup --css theme.css Custom CSS theme
46
+ mup hello.mup --debug Show debug info
47
+ mup new my-doc Create my-doc.mup
48
+ mup open hello.mup View in terminal
49
+ mup check hello.mup Validate file
50
+ """
51
+
52
+
53
+ def main() -> int:
54
+ """Main CLI entry point."""
55
+ args = sys.argv[1:]
56
+
57
+ # No arguments — show help
58
+ if not args:
59
+ print(HELP_TEXT)
60
+ return 0
61
+
62
+ # Parse global flags
63
+ show_help = False
64
+ show_version = False
65
+ dark = False
66
+ light = False
67
+ rtl = False
68
+ ltr = False
69
+ show_debug = False
70
+ output = None
71
+ css_file = None
72
+
73
+ remaining = []
74
+ i = 0
75
+ while i < len(args):
76
+ arg = args[i]
77
+
78
+ if arg in ("-h", "--help"):
79
+ show_help = True
80
+ elif arg in ("-v", "--version"):
81
+ show_version = True
82
+ elif arg in ("-d", "--dark"):
83
+ dark = True
84
+ elif arg in ("-l", "--light"):
85
+ light = True
86
+ elif arg in ("-r", "--rtl"):
87
+ rtl = True
88
+ elif arg == "--ltr":
89
+ ltr = True
90
+ elif arg == "--debug":
91
+ show_debug = True
92
+ elif arg == "--css":
93
+ if i + 1 < len(args):
94
+ css_file = args[i + 1]
95
+ i += 1
96
+ else:
97
+ print(theme.error("Error: --css requires a file path"))
98
+ return 1
99
+ elif arg in ("-o", "--output"):
100
+ if i + 1 < len(args):
101
+ output = args[i + 1]
102
+ i += 1
103
+ else:
104
+ print(theme.error("Error: --output requires a value"))
105
+ return 1
106
+ else:
107
+ remaining.append(arg)
108
+
109
+ i += 1
110
+
111
+ # Handle --help / --version
112
+ if show_help:
113
+ print(HELP_TEXT)
114
+ return 0
115
+
116
+ if show_version:
117
+ print(f"Markup+ v{__version__}")
118
+ return 0
119
+
120
+ # Determine theme
121
+ theme_name = "dark" if dark else "light"
122
+
123
+ # Determine direction
124
+ direction = None
125
+ if rtl:
126
+ direction = "rtl"
127
+ elif ltr:
128
+ direction = "ltr"
129
+
130
+ # No command
131
+ if not remaining:
132
+ print(HELP_TEXT)
133
+ return 0
134
+
135
+ # Dispatch commands
136
+ command = remaining[0]
137
+ command_args = remaining[1:]
138
+
139
+ from . import commands
140
+
141
+ if command == "new":
142
+ if not command_args:
143
+ print(theme.error("Error: 'new' requires a name"))
144
+ print(theme.dim("Usage: mup new <name>"))
145
+ return 1
146
+ return commands.new_file(command_args[0])
147
+
148
+ if command == "open":
149
+ if not command_args:
150
+ print(theme.error("Error: 'open' requires a file"))
151
+ print(theme.dim("Usage: mup open <file>"))
152
+ return 1
153
+ return commands.open_file(command_args[0])
154
+
155
+ if command == "check":
156
+ if not command_args:
157
+ print(theme.error("Error: 'check' requires a file"))
158
+ print(theme.dim("Usage: mup check <file>"))
159
+ return 1
160
+ return commands.check_file(command_args[0])
161
+ if command == "init":
162
+ name = command_args[0] if command_args else None
163
+ return commands.init_project(name)
164
+
165
+ # Default: treat as file path
166
+ return commands.build_file(
167
+ command,
168
+ theme_name=theme_name,
169
+ direction=direction,
170
+ output=output,
171
+ show_debug=show_debug,
172
+ css_file=css_file,
173
+ )
@@ -0,0 +1,8 @@
1
+ """Entry point for `python -m markup_plus.cli`."""
2
+
3
+ import sys
4
+
5
+ from . import main
6
+
7
+ if __name__ == "__main__":
8
+ sys.exit(main())