flexdown 0.1.0__tar.gz

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,17 @@
1
+ Metadata-Version: 2.1
2
+ Name: flexdown
3
+ Version: 0.1.0
4
+ Summary: Write interactive documentation with Markdown and Python.
5
+ Author: Nikhil Rao
6
+ Author-email: nikhil@reflex.dev
7
+ Requires-Python: >=3.9,<4.0
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.9
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Requires-Dist: mistletoe (>=1.2.1,<2.0.0)
13
+ Requires-Dist: pyyaml (>=6.0.1,<7.0.0)
14
+ Requires-Dist: reflex (>=0.2.6,<0.3.0)
15
+ Description-Content-Type: text/markdown
16
+
17
+
File without changes
@@ -0,0 +1,76 @@
1
+ import reflex as rx
2
+
3
+ from . import templates
4
+ from .flexdown import Flexdown
5
+ from .document import Document
6
+
7
+
8
+ # The default Flexdown instance.
9
+ flexdown = Flexdown()
10
+
11
+
12
+ def parse(source) -> Document:
13
+ """Parse a Flexdown document.
14
+
15
+ Args:
16
+ source: The source code of the Flexdown document.
17
+
18
+ Returns:
19
+ The parsed Flexdown document.
20
+ """
21
+ return Document.from_source(source)
22
+
23
+
24
+ def parse_file(path: str) -> Document:
25
+ """Parse a Flexdown file.
26
+
27
+ Args:
28
+ path: The path to the Flexdown file.
29
+
30
+ Returns:
31
+ The parsed Flexdown document.
32
+ """
33
+ return Document.from_file(path)
34
+
35
+
36
+ def render(source: str, **kwargs) -> rx.Component:
37
+ """Render Flexdown source code into a Reflex component.
38
+
39
+ Args:
40
+ source: The source code of the Flexdown file.
41
+ **kwargs: The keyword arguments to pass to the Flexdown constructor.
42
+
43
+ Returns:
44
+ The Reflex component representing the Flexdown file.
45
+ """
46
+ return Flexdown(**kwargs).render(source)
47
+
48
+
49
+ def render_file(path: str, **kwargs) -> rx.Component:
50
+ """Render a Flexdown file into a Reflex component.
51
+
52
+ Args:
53
+ path: The path to the Flexdown file.
54
+ **kwargs: The keyword arguments to pass to the Flexdown constructor.
55
+
56
+ Returns:
57
+ The Reflex component representing the Flexdown file.
58
+ """
59
+ return Flexdown(**kwargs).render_file(path)
60
+
61
+
62
+ def app(path: str, **kwargs) -> rx.App:
63
+ """Create a Reflex app from a directory of Flexdown files.
64
+
65
+ Args:
66
+ path: The path to the directory of Flexdown files.
67
+ **kwargs: The keyword arguments to pass to the Flexdown constructor.
68
+
69
+ Returns:
70
+ The Reflex app representing the directory of Flexdown files.
71
+ """
72
+
73
+ class State(rx.State):
74
+ pass
75
+
76
+ return Flexdown(**kwargs).create_app(path)
@@ -0,0 +1,5 @@
1
+ from .block import Block
2
+ from .code_block import CodeBlock
3
+ from .eval_block import EvalBlock
4
+ from .exec_block import ExecBlock
5
+ from .markdown_block import MarkdownBlock
@@ -0,0 +1,122 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Callable, ClassVar
4
+
5
+ import reflex as rx
6
+
7
+ from flexdown import types
8
+
9
+
10
+ class Block(rx.Base):
11
+ """Base class for all Flexdown blocks."""
12
+
13
+ # The type of the block (defined by the subclass).
14
+ type: ClassVar[str]
15
+
16
+ # The string denoting the start of the block.
17
+ starting_indicator: ClassVar[str | None] = None
18
+
19
+ # The string denoting the end of the block.
20
+ ending_indicator: ClassVar[str] = ""
21
+
22
+ # Whether to include the indicators in the content.
23
+ include_indicators: ClassVar[bool] = False
24
+
25
+ # List of transformations to apply to each line.
26
+ line_transforms: ClassVar[list[Callable[[str], str]]] = []
27
+
28
+ # The lines of text in the block.
29
+ lines: list[str] = []
30
+
31
+ # The starting line number of the block.
32
+ start_line_number: int = 0
33
+
34
+ @classmethod
35
+ def from_line(cls, line: str, line_number: int = 0) -> Block | None:
36
+ """Try to create a block from a line of text.
37
+
38
+ This method checks if the line of text is the start of a block.
39
+
40
+ Args:
41
+ line: The line of text to check.
42
+ line_number: The line number of the line of text.
43
+
44
+ Returns:
45
+ The block if the line is the start of a block, otherwise `None`.
46
+ """
47
+ # If there is no starting indicator, or the line starts with the
48
+ # starting indicator, then create a block.
49
+ if cls.starting_indicator is None or line.startswith(cls.starting_indicator):
50
+ return cls(start_line_number=line_number).append(line)
51
+
52
+ # Otherwise, return `None`.
53
+ return None
54
+
55
+ def _apply_transforms(self, line: str, env: types.Env) -> str:
56
+ """Apply transformations to a line of text.
57
+
58
+ Args:
59
+ line: The line of text to transform.
60
+ env: The environment variables to use for line transformations.
61
+
62
+ Returns:
63
+ The transformed line of text.
64
+ """
65
+ for transform in self.line_transforms:
66
+ line = transform(line, env)
67
+ return line
68
+
69
+ def get_content(self, env: types.Env) -> str:
70
+ """The content of the block.
71
+
72
+ Args:
73
+ env: The environment variables to use for line transformations.
74
+
75
+ Returns:
76
+ The content of the block.
77
+ """
78
+ start_index = (
79
+ 0 if self.include_indicators or self.starting_indicator is None else 1
80
+ )
81
+ end_index = None if self.include_indicators else -1
82
+ lines = [
83
+ self._apply_transforms(line, env)
84
+ for line in self.lines[start_index:end_index]
85
+ ]
86
+ return "\n".join(lines)
87
+
88
+ def append(self, line: str) -> Block:
89
+ """Append a line of text to the block.
90
+
91
+ Args:
92
+ line: The line of text to append.
93
+
94
+ Returns:
95
+ The block with the line of text appended.
96
+ """
97
+ self.lines.append(line)
98
+ return self
99
+
100
+ def is_finished(self) -> bool:
101
+ """Whether the block is finished."""
102
+ return len(self.lines) > 0 and self.lines[-1] == self.ending_indicator
103
+
104
+ def render(
105
+ self,
106
+ env: types.Env | None = None,
107
+ component_map: types.ComponentMap | None = None,
108
+ ) -> rx.Component:
109
+ """Render a block to a Reflex component.
110
+
111
+ Args:
112
+ env: The environment to use for rendering.
113
+ component_map: The component map to use.
114
+
115
+ Returns:
116
+ The rendered Reflex component.
117
+ """
118
+ pass
119
+
120
+
121
+ # Functions that process blocks before rendering.
122
+ BlockProcessor = Callable[[list[Block], types.Env], list[Block]]
@@ -0,0 +1,13 @@
1
+ import reflex as rx
2
+
3
+ from flexdown import types
4
+ from flexdown.blocks.markdown_block import MarkdownBlock
5
+
6
+
7
+ class CodeBlock(MarkdownBlock):
8
+ """A block of code."""
9
+
10
+ type = "code"
11
+ starting_indicator = "```"
12
+ ending_indicator = "```"
13
+ include_indicators = True
@@ -0,0 +1,15 @@
1
+ import reflex as rx
2
+
3
+ from flexdown import types
4
+ from flexdown.blocks.block import Block
5
+
6
+
7
+ class EvalBlock(Block):
8
+ """A block that evaluates a Reflex component to display."""
9
+
10
+ type = "eval"
11
+ starting_indicator = "```python eval"
12
+ ending_indicator = "```"
13
+
14
+ def render(self, env: types.Env, **_) -> rx.Component:
15
+ return eval(self.get_content(env), env, env)
@@ -0,0 +1,57 @@
1
+ import importlib
2
+ import os
3
+ import sys
4
+
5
+ import reflex as rx
6
+
7
+ from flexdown import types
8
+ from flexdown.blocks.block import Block
9
+
10
+
11
+ MODULES = "modules"
12
+
13
+
14
+ class ExecBlock(Block):
15
+ """A block of executable Python code."""
16
+
17
+ type = "exec"
18
+ starting_indicator = "```python exec"
19
+ ending_indicator = "```"
20
+
21
+ def render(self, **_) -> rx.Component:
22
+ # Return an empty component.
23
+ return rx.fragment()
24
+
25
+ @classmethod
26
+ def process_blocks(cls, blocks: list[Block], env: types.Env):
27
+ """Execute all the exec blocks.
28
+
29
+ Args:
30
+ blocks: The blocks to process.
31
+ env: The environment to use for processing.
32
+ """
33
+ # Get all the exec blocks.
34
+ exec_blocks = [block for block in blocks if block.type == cls.type]
35
+
36
+ # Concat the content of all the exec blocks.
37
+ content = "\n".join([block.get_content(env) for block in exec_blocks])
38
+
39
+ # Get the path to the directory where the module should be saved.
40
+ flexdown_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
41
+ module_dir = os.path.join(flexdown_dir, MODULES)
42
+
43
+ # Write the content to a file in the module directory.
44
+ os.makedirs(module_dir, exist_ok=True)
45
+ module_name = rx.vars.get_unique_variable_name()
46
+ module_path = os.path.join(module_dir, f"{module_name}.py")
47
+ with open(module_path, "w", encoding="utf-8") as f:
48
+ f.write(content)
49
+
50
+ # Import the module to execute the code.
51
+ module_name = f"flexdown.{MODULES}.{module_name}"
52
+ if module_name in sys.modules:
53
+ module = importlib.reload(sys.modules[module_name])
54
+ else:
55
+ module = importlib.import_module(module_name)
56
+
57
+ env.update(vars(module))
@@ -0,0 +1,16 @@
1
+ import reflex as rx
2
+
3
+ from flexdown import types, utils
4
+ from flexdown.blocks.block import Block
5
+
6
+
7
+ class MarkdownBlock(Block):
8
+ """A block of Markdown."""
9
+
10
+ type = "markdown"
11
+ line_transforms = [
12
+ utils.evaluate_templates,
13
+ ]
14
+
15
+ def render(self, env: types.Env, component_map: types.ComponentMap) -> rx.Component:
16
+ return rx.markdown(self.get_content(env), component_map=component_map)
@@ -0,0 +1,29 @@
1
+ """The Flexdown CLI.""" ""
2
+ import os
3
+
4
+ from reflex.reflex import typer
5
+ from reflex.utils.processes import new_process
6
+
7
+ from flexdown import constants
8
+
9
+
10
+ # The command line app.
11
+ app = typer.Typer()
12
+
13
+
14
+ @app.command()
15
+ def run(path: str):
16
+ # Create a .flexdown directory in the current directory.
17
+ os.makedirs(constants.FLEXDOWN_DIR, exist_ok=True)
18
+
19
+ # Create a reflex project.
20
+ new_process(
21
+ ["reflex", "init"], cwd=constants.FLEXDOWN_DIR, show_logs=True, run=True
22
+ )
23
+
24
+ # Replace the app file with a template.
25
+ with open(constants.FLEXDOWN_FILE, "w") as f:
26
+ f.write(constants.APP_TEMPLATE.format(path=f"../../{path}"))
27
+
28
+ # Run the reflex project.
29
+ new_process(["reflex", "run"], cwd=constants.FLEXDOWN_DIR, show_logs=True, run=True)
@@ -0,0 +1,27 @@
1
+ """Constants used in flexdown."""
2
+
3
+ # The extension for Flexdown files.
4
+ FLEXDOWN_EXTENSION = ".md"
5
+
6
+ # The Flexdown app directory.
7
+ FLEXDOWN_DIR = ".flexdown/flexd"
8
+ FLEXDOWN_FILE = f"{FLEXDOWN_DIR}/flexd/flexd.py"
9
+ FLEXDOWN_MODULES_DIR = "modules"
10
+
11
+ # Regex for front matter.
12
+ FRONT_MATTER_REGEX = r"^---\s*\n(.+?)\n---\s*\n(.*)$"
13
+ # Regex for template placeholders.
14
+ TEMPLATE_REGEX = r"(?<!\\)(?<!\\\\){(?!\\)(.*?)(?<!\\)}"
15
+
16
+ # The default app template.
17
+ APP_TEMPLATE = """import flexdown
18
+ import reflex as rx
19
+ component_map = {{
20
+ "a": lambda value, **props: rx.link(value, color="blue", **props),
21
+ }}
22
+ app = flexdown.app(
23
+ '{path}',
24
+ page_template=flexdown.templates.base_template,
25
+ component_map=component_map
26
+ )
27
+ """
@@ -0,0 +1,61 @@
1
+ """Flexdown documents."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from typing import Any
7
+
8
+ import yaml
9
+ import reflex as rx
10
+
11
+ from flexdown import constants, types
12
+
13
+
14
+ class Document(rx.Base):
15
+ """A Flexdown document."""
16
+
17
+ # Document metadata in the flexdown frontmatter.
18
+ metadata: dict[str, Any] = {}
19
+
20
+ # The content of the document.
21
+ content: str
22
+
23
+ @staticmethod
24
+ def parse_front_matter(source: str) -> tuple[types.FrontMatter, str]:
25
+ # Extract the front matter and content using the pattern
26
+ match = re.match(constants.FRONT_MATTER_REGEX, source, re.DOTALL)
27
+
28
+ # If there is no front matter, return an empty dictionary
29
+ if not match:
30
+ return {}, source
31
+
32
+ # Get the front matter and content
33
+ front_matter = yaml.safe_load(match.group(1))
34
+ content = match.group(2)
35
+ return front_matter, content
36
+
37
+ @classmethod
38
+ def from_source(cls, source: str) -> Document:
39
+ """Create a document from a source string.
40
+
41
+ Args:
42
+ source: The source string of the document.
43
+
44
+ Returns:
45
+ The document.
46
+ """
47
+ front_matter, content = cls.parse_front_matter(source)
48
+ return cls(metadata=front_matter, content=content)
49
+
50
+ @classmethod
51
+ def from_file(cls, path: str) -> Document:
52
+ """Create a document from a file.
53
+
54
+ Args:
55
+ path: The path to the file.
56
+
57
+ Returns:
58
+ The document.
59
+ """
60
+ with open(path, "r", encoding="utf-8") as file:
61
+ return cls.from_source(file.read())
@@ -0,0 +1,9 @@
1
+ """Exceptions for flexdown."""
2
+
3
+
4
+ class TemplateEvaluationError(Exception):
5
+ """An error when evaluating a template placeholder."""
6
+
7
+
8
+ class RenderError(Exception):
9
+ """An error when rendering a block."""
@@ -0,0 +1,194 @@
1
+ """The main flexdown module."""
2
+ from typing import Callable
3
+
4
+ import reflex as rx
5
+
6
+ from flexdown import errors, utils, types
7
+ from flexdown.blocks import block, Block, MarkdownBlock, CodeBlock, ExecBlock, EvalBlock
8
+ from flexdown.document import Document
9
+
10
+ component_map = {
11
+ "code": lambda source: rx.code(source, color="#1F1944", bg="#EAE4FD"),
12
+ }
13
+
14
+
15
+ class Flexdown(rx.Base):
16
+ """Class to parse and render flexdown files."""
17
+
18
+ # The list of accepted block types to parse.
19
+ blocks: list[type[Block]] = [
20
+ ExecBlock,
21
+ EvalBlock,
22
+ CodeBlock,
23
+ MarkdownBlock,
24
+ ]
25
+
26
+ # The default block type.
27
+ default_block_type: type[Block] = MarkdownBlock
28
+
29
+ # List of processors to apply to blocks before rendering.
30
+ block_processors: list[block.BlockProcessor] = [
31
+ ExecBlock.process_blocks,
32
+ ]
33
+
34
+ # The template to use when rendering pages.
35
+ page_template: Callable[[rx.Component], rx.Component] = rx.fragment
36
+
37
+ # Mapping from markdown tag to a rendering function for Reflex components.
38
+ component_map: types.ComponentMap = component_map
39
+
40
+ def _get_block(self, line: str, line_number: int) -> Block:
41
+ """Get the block type for a line of text.
42
+
43
+ Args:
44
+ line: The line of text to check.
45
+ line_number: The line number of the line.
46
+
47
+ Returns:
48
+ The block type for the line of text.
49
+ """
50
+ # Search for a block type that can parse the line.
51
+ for block_type in self.blocks:
52
+
53
+ # Try to create a block from the line.
54
+ block = block_type.from_line(line, line_number=line_number)
55
+
56
+ # If a block was created, then return it.
57
+ if block is not None:
58
+ return block
59
+
60
+ # If no block was created, then return the default block type.
61
+ return self.default_block_type().append(line)
62
+
63
+ def get_blocks(self, source: str) -> list[Block]:
64
+ """Parse a Flexdown file into blocks.
65
+
66
+ Args:
67
+ source: The source code of the Flexdown file.
68
+
69
+ Returns:
70
+ The list of blocks in the Flexdown file.
71
+ """
72
+ # The list of parsed blocks.
73
+ blocks: list[Block] = []
74
+ current_block = None
75
+
76
+ # Iterate over each line in the source code.
77
+ for line_number, line in enumerate(source.splitlines()):
78
+
79
+ # If there is no current block, then create a new block.
80
+ if current_block is None:
81
+ # If the line is empty, then skip it.
82
+ if line == "":
83
+ continue
84
+
85
+ # Otherwise, create a new block.
86
+ current_block = self._get_block(line, line_number)
87
+
88
+ else:
89
+ # Add the line to the current block.
90
+ current_block.append(line)
91
+
92
+ # Check if the current block is finished.
93
+ if current_block.is_finished():
94
+ blocks.append(current_block)
95
+ current_block = None
96
+
97
+ # Add the final block if it exists.
98
+ if current_block is not None:
99
+ blocks.append(current_block)
100
+
101
+ # Return the list of blocks.
102
+ return blocks
103
+
104
+ def process_blocks(self, blocks: list[Block], env: types.Env) -> list[Block]:
105
+ """Process a list of blocks to execute any side effects.
106
+
107
+ Args:
108
+ blocks: The list of blocks to process.
109
+ env: The environment variables to use for processing.
110
+
111
+ Returns:
112
+ The list of processed blocks.
113
+ """
114
+ for processor in self.block_processors:
115
+ processor(blocks, env)
116
+
117
+ def render(self, source: str | Document) -> rx.Component:
118
+ """Render a Flexdown file into a Reflex component.
119
+
120
+ Args:
121
+ source: The source code of the Flexdown file.
122
+
123
+ Returns:
124
+ The Reflex component representing the Flexdown file.
125
+ """
126
+ # Convert the source to a document.
127
+ if isinstance(source, str):
128
+ source = Document.from_source(source)
129
+
130
+ # The environment used for execing and evaling code.
131
+ env: types.Env = source.metadata
132
+
133
+ # Get the content of the document.
134
+ source = source.content
135
+
136
+ # Get the blocks in the source code.
137
+ blocks = self.get_blocks(source)
138
+ self.process_blocks(blocks, env)
139
+
140
+ # Render each block.
141
+ out: list[rx.Component] = []
142
+ for block in blocks:
143
+ try:
144
+ out.append(block.render(env=env, component_map=self.component_map))
145
+ except Exception as e:
146
+ raise errors.RenderError(
147
+ f"Error while rendering block {block.type} on line {block.start_line_number}. "
148
+ f"\n{block.get_content(env)}"
149
+ ) from e
150
+
151
+ # Wrap the output in the page template.
152
+ return self.page_template(rx.fragment(*out))
153
+
154
+ def render_file(self, path: str) -> rx.Component:
155
+ """Render a Flexdown file into a Reflex component.
156
+
157
+ Args:
158
+ path: The path to the Flexdown file.
159
+
160
+ Returns:
161
+ The Reflex component representing the Flexdown file.
162
+ """
163
+ # Render the source code.
164
+ return self.render(Document.from_file(path))
165
+
166
+ def create_app(self, path: str) -> rx.App:
167
+ """Create a Reflex app from a directory of Flexdown files.
168
+
169
+ Args:
170
+ path: The path to the directory of Flexdown files.
171
+
172
+ Returns:
173
+ The Reflex app representing the directory of Flexdown files.
174
+ """
175
+ # Get all the flexdown files in the directory.
176
+ files = utils.get_flexdown_files(path)
177
+
178
+ # Create the Reflex app.
179
+ app = rx.App()
180
+
181
+ # Create a base state.
182
+ class State(rx.State):
183
+ pass
184
+
185
+ # Add each page to the app.
186
+ for file in files:
187
+ route = file.replace(path, "").replace(".md", "")
188
+ app.add_page(self.render_file(file), route=route)
189
+
190
+ # Compile the app.
191
+ app.compile()
192
+
193
+ # Return the app.
194
+ return app
@@ -0,0 +1 @@
1
+ from .base_template import base_template
@@ -0,0 +1,8 @@
1
+ import reflex as rx
2
+
3
+
4
+ def base_template(content: rx.Component) -> rx.Component:
5
+ return rx.container(
6
+ content,
7
+ margin_y="5em",
8
+ )
@@ -0,0 +1,13 @@
1
+ """Types for Flexdown.""" ""
2
+ from typing import Any, Callable
3
+
4
+ import reflex as rx
5
+
6
+ # An environment for executing and evaluating code.
7
+ Env = dict[str, Any]
8
+
9
+ # The frontmatter of a Flexdown document.
10
+ Frontmatter = dict[str, Any]
11
+
12
+ # Mapping from markdown tag to a rendering function for Reflex components.
13
+ ComponentMap = dict[str, Callable[[str], rx.Component]]
@@ -0,0 +1,78 @@
1
+ """Utility functions for Flexdown."""
2
+
3
+ import glob
4
+ import inspect
5
+ import os
6
+ import re
7
+ import textwrap
8
+
9
+ from flexdown import constants, errors, types
10
+
11
+
12
+ def get_flexdown_files(path: str) -> list[str]:
13
+ """Recursively get the Flexdown files in a directory.
14
+
15
+ Args:
16
+ path: The path to the directory to search.
17
+
18
+ Returns:
19
+ The list of Flexdown files in the directory.
20
+ """
21
+ flexdown_files = []
22
+ for root, _, files in os.walk(path):
23
+ flexdown_files.extend(
24
+ [
25
+ os.path.join(root, file)
26
+ for file in files
27
+ if file.endswith(constants.FLEXDOWN_EXTENSION)
28
+ ]
29
+ )
30
+ return flexdown_files
31
+
32
+
33
+ def evaluate_templates(line: str, env: types.Env):
34
+ """Evaluate template expressions in a line of text.
35
+
36
+ Args:
37
+ line: The line of text to evaluate.
38
+ env: The environment variables to use for evaluation.
39
+ """
40
+ # Find all template placeholders in the line.
41
+ matches = re.findall(constants.TEMPLATE_REGEX, line)
42
+
43
+ # Iterate over each template placeholder.
44
+ for match in matches:
45
+ try:
46
+ # Evaluate the Python expression and replace the template placeholder
47
+ eval_result = str(eval(match, env, env))
48
+ line = line.replace("{" + match + "}", eval_result)
49
+ except Exception as e:
50
+ # If the evaluation fails, leave the template placeholder unchanged
51
+ raise errors.TemplateEvaluationError(
52
+ f"Failed to evaluate expression '{match}'"
53
+ ) from e
54
+
55
+ # Return the line with the template placeholders replaced.
56
+ return line
57
+
58
+
59
+ def get_source(fn):
60
+ """Get the source code of a function.
61
+
62
+ Args:
63
+ fn: The function to get the source code of.
64
+ """
65
+ # Get the source code of the function.
66
+ source = inspect.getsource(fn)
67
+
68
+ # Remove the function definition.
69
+ source = re.sub(r"def \w+\(.*?\):", "", source, flags=re.DOTALL)
70
+
71
+ # Remove the indentation.
72
+ source = textwrap.dedent(source)
73
+
74
+ # Remove the trailing newline.
75
+ source = source[:-1]
76
+
77
+ # Return the source code.
78
+ return source
@@ -0,0 +1,22 @@
1
+ [tool.poetry]
2
+ name = "flexdown"
3
+ version = "0.1.0"
4
+ description = "Write interactive documentation with Markdown and Python."
5
+ authors = ["Nikhil Rao <nikhil@reflex.dev>"]
6
+ readme = "README.md"
7
+
8
+ [tool.poetry.dependencies]
9
+ python = "^3.9"
10
+ reflex = "^0.2.6"
11
+ mistletoe = "^1.2.1"
12
+ pyyaml = "^6.0.1"
13
+
14
+ [tool.poetry.scripts]
15
+ flexdown = "flexdown.cli:app"
16
+
17
+ [tool.poetry.group.dev.dependencies]
18
+ pytest = "^7.4.2"
19
+
20
+ [build-system]
21
+ requires = ["poetry-core"]
22
+ build-backend = "poetry.core.masonry.api"