Skip to content

Commit ab31179

Browse files
committed
Added MD command; fixed module declarations
1 parent de0120e commit ab31179

File tree

6 files changed

+93
-14
lines changed

6 files changed

+93
-14
lines changed

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
# Changelog
22

3+
All notable changes to this project will be documented in this file.
4+
5+
## [0.1.3] - 2025-02-18
6+
7+
### Added
8+
- New `$analyze-only` command to handle global imports without creating unnecessary files/folders
9+
- Improved handling of module declarations within package contexts
10+
11+
### Fixed
12+
- Fixed issue with imports cell creating unwanted `.py` files
13+
- Fixed module declaration within package contexts to properly create modules inside their parent packages
14+
15+
## 0.1.2 (2024-09-15)
16+
17+
_Initial version_
18+
319
<!-- ## 0.1.1 (2024-09-15)
420
[Compare the full difference.](https://github.com/ThunderStruct/nbrefactor/compare/0.1.1...0.1.0)
521

TODO.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727

2828
## Patches
2929

30+
- [x] 🔴 Fixed module declaration within package contexts to properly create modules inside their parent packages rather than as standalone files.
31+
3032
- [ ] 🟠 Add a "before and after" example to the docs
3133

3234
- [ ] 🟢 Restructure the docs' `index.rst` to have a more informative homepage than just the toctree. Also add the version number (already declared in [conf.py](docs/source/conf.py) and accessible through `|version|` in RST).

docs/source/markdown_commands.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ The following table lists all the possible Markdown commands and their functions
4646
``$ignore-cell`` Ignores the next code cell regardless of type.
4747
``$ignore-markdown`` Ignores the current Markdown cell (e.g., when used for
4848
instructions only).
49+
``$analyze-only`` Prevents the current markdown cell from creating a module/package
50+
but still analyzes any following code for dependencies.
51+
Useful for global imports.
4952
``$package=<name>`` Renames the current package and asserts the node type as
5053
'package'.
5154
``$module=<name>`` Renames the current module and asserts the node type as

src/nbrefactor/datastructs/markdown_element.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ class MarkdownCommandType(Enum):
2727
IGNORE_MARKDOWN = 'ignore-markdown' # ignores the current Markdown
2828
# cell (e.g. when MD is used
2929
# for instructions only)
30+
ANALYZE_ONLY = 'analyze-only' # analyzes the next code cell for
31+
# dependencies but does not create
32+
# a file for it
3033

3134
# Node-Manipulation Commands
3235
RENAME_PACKAGE = 'package' # sets the package name

src/nbrefactor/datastructs/module_node.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ def __init__(self, name, parent=None, depth=0, node_type=None):
2525
# under this node
2626
self.ignore_next_cell = False # ignores the next parsed cell
2727
# (handled intrinsically)
28+
self.analyze_only = False # analyze but don't create a file
2829

2930

3031
def add_child(self, child_node):
@@ -46,6 +47,9 @@ def has_code_cells(self):
4647
Checks if the node has non-empty code cells (we only write \
4748
modules/.py files if this is true)
4849
"""
50+
# Don't count this node as having code cells if it's analyze_only
51+
if self.analyze_only:
52+
return False
4953
return any([True for cell in self.parsed_cells \
5054
if isinstance(cell, ParsedCodeCell) \
5155
and len(cell.parsed_source.strip())])

src/nbrefactor/processor/processor.py

Lines changed: 65 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,18 @@ def process_notebook(notebook_path, output_path,
7676
# command is present
7777
continue
7878

79+
# Check for analyze-only command before processing headers
80+
analyze_only = any([True for e in parsed_md.elements \
81+
if isinstance(e, MarkdownCommand) \
82+
and e.type == MarkdownCommandType.ANALYZE_ONLY])
83+
7984
for md_element in parsed_md.elements:
8085
if isinstance(md_element, MarkdownHeader):
86+
# Skip header processing; we're only analyzing the cell
87+
# i.e. do not create a file/folder for it
88+
if analyze_only:
89+
continue
90+
8191
# handle MarkdownHeader
8292
header = md_element
8393
header_name = __sanitize_node_name(header.name)
@@ -230,6 +240,10 @@ def __handle_markdown_command(command, current_node, node_stack):
230240
# this is handled externally to avoid the processing cost
231241
pass
232242

243+
elif command.type == MarkdownCommandType.ANALYZE_ONLY:
244+
# Mark the current node to be analyzed but not written to a file
245+
current_node.analyze_only = True
246+
233247
# NODE-MANIPULATION COMMANDS
234248
elif command.type == MarkdownCommandType.RENAME_PACKAGE:
235249
# override the current node's name + assert package type
@@ -260,20 +274,57 @@ def __handle_markdown_command(command, current_node, node_stack):
260274
# create a new node and assert its node type to module
261275
node_name = __sanitize_node_name(command.value)
262276

263-
new_node_parent = current_node
264-
265-
# default to sibling-level if we're not at root level
266-
if current_node.parent is not None:
267-
node_stack.pop()
268-
new_node_parent = current_node.parent
269-
270-
new_node = ModuleNode(node_name, new_node_parent,
271-
depth=new_node_parent.depth + 1)
272-
273-
new_node.node_type = 'module'
274-
new_node_parent.add_child(new_node)
275-
276-
node_stack.append(new_node)
277+
# If we're in a package context, the module should be created inside that package
278+
if current_node.node_type == 'package':
279+
# Create module inside the current package
280+
new_node = ModuleNode(node_name, current_node,
281+
depth=current_node.depth + 1)
282+
new_node.node_type = 'module'
283+
current_node.add_child(new_node)
284+
node_stack.append(new_node)
285+
else:
286+
# Check if this should be a package structure (contains . or /)
287+
if '.' in node_name or '/' in node_name:
288+
# Split into package/module parts
289+
parts = node_name.replace('/', '.').split('.')
290+
module_name = parts[-1]
291+
package_parts = parts[:-1]
292+
293+
# Start from current node's parent if we're not at root
294+
new_node_parent = current_node
295+
if current_node.parent is not None:
296+
node_stack.pop()
297+
new_node_parent = current_node.parent
298+
299+
# Create package structure
300+
for package_part in package_parts:
301+
package_node = ModuleNode(package_part, new_node_parent,
302+
depth=new_node_parent.depth + 1)
303+
package_node.node_type = 'package'
304+
new_node_parent.add_child(package_node)
305+
new_node_parent = package_node
306+
node_stack.append(package_node)
307+
308+
# Create the actual module at the leaf
309+
new_node = ModuleNode(module_name, new_node_parent,
310+
depth=new_node_parent.depth + 1)
311+
new_node.node_type = 'module'
312+
new_node_parent.add_child(new_node)
313+
node_stack.append(new_node)
314+
else:
315+
new_node_parent = current_node
316+
317+
# default to sibling-level if we're not at root level
318+
if current_node.parent is not None:
319+
node_stack.pop()
320+
new_node_parent = current_node.parent
321+
322+
new_node = ModuleNode(node_name, new_node_parent,
323+
depth=new_node_parent.depth + 1)
324+
325+
new_node.node_type = 'module'
326+
new_node_parent.add_child(new_node)
327+
node_stack.append(new_node)
277328

278329
elif command.type == MarkdownCommandType.DECLARE_NODE:
279330
# create a new generic node (type will be automatically inferred)

0 commit comments

Comments
 (0)