notionary 0.2.10__py3-none-any.whl → 0.2.12__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.
- notionary/__init__.py +6 -0
- notionary/cli/main.py +184 -0
- notionary/cli/onboarding.py +0 -0
- notionary/database/database_discovery.py +1 -1
- notionary/database/notion_database.py +5 -4
- notionary/database/notion_database_factory.py +10 -5
- notionary/elements/audio_element.py +2 -2
- notionary/elements/bookmark_element.py +2 -2
- notionary/elements/bulleted_list_element.py +2 -2
- notionary/elements/callout_element.py +2 -2
- notionary/elements/code_block_element.py +2 -2
- notionary/elements/column_element.py +51 -44
- notionary/elements/divider_element.py +2 -2
- notionary/elements/embed_element.py +2 -2
- notionary/elements/heading_element.py +3 -3
- notionary/elements/image_element.py +2 -2
- notionary/elements/mention_element.py +2 -2
- notionary/elements/notion_block_element.py +36 -0
- notionary/elements/numbered_list_element.py +2 -2
- notionary/elements/paragraph_element.py +2 -2
- notionary/elements/qoute_element.py +2 -2
- notionary/elements/table_element.py +2 -2
- notionary/elements/text_inline_formatter.py +23 -1
- notionary/elements/todo_element.py +2 -2
- notionary/elements/toggle_element.py +2 -2
- notionary/elements/toggleable_heading_element.py +2 -2
- notionary/elements/video_element.py +2 -2
- notionary/notion_client.py +1 -1
- notionary/page/content/notion_page_content_chunker.py +1 -1
- notionary/page/content/page_content_retriever.py +1 -1
- notionary/page/content/page_content_writer.py +3 -3
- notionary/page/{markdown_to_notion_converter.py → formatting/markdown_to_notion_converter.py} +44 -140
- notionary/page/formatting/spacer_rules.py +483 -0
- notionary/page/metadata/metadata_editor.py +1 -1
- notionary/page/metadata/notion_icon_manager.py +1 -1
- notionary/page/metadata/notion_page_cover_manager.py +1 -1
- notionary/page/notion_page.py +1 -1
- notionary/page/notion_page_factory.py +161 -22
- notionary/page/properites/database_property_service.py +1 -1
- notionary/page/properites/page_property_manager.py +1 -1
- notionary/page/properites/property_formatter.py +1 -1
- notionary/page/properites/property_value_extractor.py +1 -1
- notionary/page/relations/notion_page_relation_manager.py +1 -1
- notionary/page/relations/notion_page_title_resolver.py +1 -1
- notionary/page/relations/page_database_relation.py +1 -1
- notionary/prompting/element_prompt_content.py +1 -0
- notionary/telemetry/__init__.py +7 -0
- notionary/telemetry/telemetry.py +226 -0
- notionary/telemetry/track_usage_decorator.py +76 -0
- notionary/util/__init__.py +5 -0
- notionary/util/logging_mixin.py +3 -0
- notionary/util/singleton.py +18 -0
- {notionary-0.2.10.dist-info → notionary-0.2.12.dist-info}/METADATA +3 -1
- notionary-0.2.12.dist-info/RECORD +70 -0
- {notionary-0.2.10.dist-info → notionary-0.2.12.dist-info}/WHEEL +1 -1
- notionary-0.2.12.dist-info/entry_points.txt +2 -0
- notionary-0.2.10.dist-info/RECORD +0 -61
- {notionary-0.2.10.dist-info → notionary-0.2.12.dist-info}/licenses/LICENSE +0 -0
- {notionary-0.2.10.dist-info → notionary-0.2.12.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,483 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import Dict, Any, List, Optional, Tuple
|
4
|
+
from abc import ABC, abstractmethod
|
5
|
+
from dataclasses import dataclass
|
6
|
+
from enum import Enum
|
7
|
+
import re
|
8
|
+
|
9
|
+
SPACER_MARKER = "---spacer---"
|
10
|
+
|
11
|
+
|
12
|
+
class LineType(Enum):
|
13
|
+
"""Enum for different line types"""
|
14
|
+
|
15
|
+
EMPTY = "empty"
|
16
|
+
HEADING = "heading"
|
17
|
+
DIVIDER = "divider"
|
18
|
+
CODE_BLOCK_MARKER = "code_block_marker"
|
19
|
+
SPACER_MARKER = SPACER_MARKER
|
20
|
+
PIPE_SYNTAX = "pipe_syntax"
|
21
|
+
TODO_ITEM = "todo_item"
|
22
|
+
REGULAR_CONTENT = "regular_content"
|
23
|
+
|
24
|
+
|
25
|
+
@dataclass
|
26
|
+
class LineContext:
|
27
|
+
"""Context of a line for spacer rule application"""
|
28
|
+
|
29
|
+
line: str
|
30
|
+
line_number: int
|
31
|
+
line_type: LineType
|
32
|
+
is_empty: bool
|
33
|
+
content: str
|
34
|
+
in_code_block: bool
|
35
|
+
last_line_was_spacer: bool
|
36
|
+
last_non_empty_was_heading: bool
|
37
|
+
has_content_before: bool
|
38
|
+
processed_lines: List[str]
|
39
|
+
|
40
|
+
|
41
|
+
@dataclass
|
42
|
+
class SpacerDecision:
|
43
|
+
"""Decision about inserting a spacer"""
|
44
|
+
|
45
|
+
should_add_spacer: bool
|
46
|
+
reason: str
|
47
|
+
rule_name: str
|
48
|
+
|
49
|
+
|
50
|
+
@dataclass
|
51
|
+
class ProcessingResult:
|
52
|
+
"""Result of processing a single line"""
|
53
|
+
|
54
|
+
output_lines: List[str]
|
55
|
+
new_state: Dict[str, Any]
|
56
|
+
spacer_added: bool = False
|
57
|
+
|
58
|
+
|
59
|
+
class SpacerRule(ABC):
|
60
|
+
"""Abstract base class for spacer rules"""
|
61
|
+
|
62
|
+
@property
|
63
|
+
@abstractmethod
|
64
|
+
def name(self) -> str:
|
65
|
+
"""Name of the rule for debugging"""
|
66
|
+
pass
|
67
|
+
|
68
|
+
@property
|
69
|
+
@abstractmethod
|
70
|
+
def description(self) -> str:
|
71
|
+
"""Description of what the rule does"""
|
72
|
+
pass
|
73
|
+
|
74
|
+
@abstractmethod
|
75
|
+
def applies_to(self, context: LineContext) -> bool:
|
76
|
+
"""Checks if this rule is relevant for the context"""
|
77
|
+
pass
|
78
|
+
|
79
|
+
@abstractmethod
|
80
|
+
def should_add_spacer(self, context: LineContext) -> SpacerDecision:
|
81
|
+
"""Decides whether a spacer should be added"""
|
82
|
+
pass
|
83
|
+
|
84
|
+
|
85
|
+
class HeadingSpacerRule(SpacerRule):
|
86
|
+
"""Rule: Add spacer before headings (except after other headings)"""
|
87
|
+
|
88
|
+
@property
|
89
|
+
def name(self) -> str:
|
90
|
+
return "HeadingSpacerRule"
|
91
|
+
|
92
|
+
@property
|
93
|
+
def description(self) -> str:
|
94
|
+
return "Adds spacer before headings, except when the previous line was already a heading"
|
95
|
+
|
96
|
+
def applies_to(self, context: LineContext) -> bool:
|
97
|
+
return context.line_type == LineType.HEADING
|
98
|
+
|
99
|
+
def should_add_spacer(self, context: LineContext) -> SpacerDecision:
|
100
|
+
# Rule: Insert spacer before heading when:
|
101
|
+
# 1. There is content before this heading
|
102
|
+
# 2. The last line was not a spacer
|
103
|
+
# 3. The last non-empty line was not a heading
|
104
|
+
|
105
|
+
if not context.has_content_before:
|
106
|
+
return SpacerDecision(False, "No content before this heading", self.name)
|
107
|
+
|
108
|
+
if context.last_line_was_spacer:
|
109
|
+
return SpacerDecision(
|
110
|
+
False, "Previous line was already a spacer", self.name
|
111
|
+
)
|
112
|
+
|
113
|
+
if context.last_non_empty_was_heading:
|
114
|
+
return SpacerDecision(
|
115
|
+
False,
|
116
|
+
"Previous non-empty line was a heading (consecutive headings)",
|
117
|
+
self.name,
|
118
|
+
)
|
119
|
+
|
120
|
+
return SpacerDecision(
|
121
|
+
True,
|
122
|
+
"Adding spacer before heading to separate from previous content",
|
123
|
+
self.name,
|
124
|
+
)
|
125
|
+
|
126
|
+
|
127
|
+
class DividerSpacerRule(SpacerRule):
|
128
|
+
"""Rule: Add spacer before dividers"""
|
129
|
+
|
130
|
+
@property
|
131
|
+
def name(self) -> str:
|
132
|
+
return "DividerSpacerRule"
|
133
|
+
|
134
|
+
@property
|
135
|
+
def description(self) -> str:
|
136
|
+
return "Adds spacer before dividers (---) to create visual distance"
|
137
|
+
|
138
|
+
def applies_to(self, context: LineContext) -> bool:
|
139
|
+
return context.line_type == LineType.DIVIDER
|
140
|
+
|
141
|
+
def should_add_spacer(self, context: LineContext) -> SpacerDecision:
|
142
|
+
# Rule: Insert spacer before divider except when last line was already a spacer
|
143
|
+
|
144
|
+
if context.last_line_was_spacer:
|
145
|
+
return SpacerDecision(
|
146
|
+
False, "Previous line was already a spacer", self.name
|
147
|
+
)
|
148
|
+
|
149
|
+
return SpacerDecision(
|
150
|
+
True, "Adding spacer before divider for visual separation", self.name
|
151
|
+
)
|
152
|
+
|
153
|
+
|
154
|
+
class ConsecutiveSpacerRule(SpacerRule):
|
155
|
+
"""Rule: Prevent consecutive spacers"""
|
156
|
+
|
157
|
+
@property
|
158
|
+
def name(self) -> str:
|
159
|
+
return "ConsecutiveSpacerRule"
|
160
|
+
|
161
|
+
@property
|
162
|
+
def description(self) -> str:
|
163
|
+
return "Prevents consecutive spacer markers"
|
164
|
+
|
165
|
+
def applies_to(self, context: LineContext) -> bool:
|
166
|
+
return context.line_type == LineType.SPACER_MARKER
|
167
|
+
|
168
|
+
def should_add_spacer(self, context: LineContext) -> SpacerDecision:
|
169
|
+
# Rule: Never allow consecutive spacers
|
170
|
+
|
171
|
+
if context.last_line_was_spacer:
|
172
|
+
return SpacerDecision(False, "Preventing consecutive spacers", self.name)
|
173
|
+
|
174
|
+
return SpacerDecision(True, "Adding spacer marker", self.name)
|
175
|
+
|
176
|
+
|
177
|
+
class CodeBlockSpacerRule(SpacerRule):
|
178
|
+
"""Rule: No spacers inside code blocks"""
|
179
|
+
|
180
|
+
@property
|
181
|
+
def name(self) -> str:
|
182
|
+
return "CodeBlockSpacerRule"
|
183
|
+
|
184
|
+
@property
|
185
|
+
def description(self) -> str:
|
186
|
+
return "Prevents spacer processing inside code blocks"
|
187
|
+
|
188
|
+
def applies_to(self, context: LineContext) -> bool:
|
189
|
+
return context.in_code_block and context.line_type != LineType.CODE_BLOCK_MARKER
|
190
|
+
|
191
|
+
def should_add_spacer(self, context: LineContext) -> SpacerDecision:
|
192
|
+
return SpacerDecision(
|
193
|
+
False, "Inside code block - no spacer processing", self.name
|
194
|
+
)
|
195
|
+
|
196
|
+
|
197
|
+
class StateBuilder:
|
198
|
+
"""Builder for creating and updating state"""
|
199
|
+
|
200
|
+
def __init__(self, initial_state: Dict[str, Any]):
|
201
|
+
self._state = initial_state.copy()
|
202
|
+
|
203
|
+
def toggle_code_block(self) -> StateBuilder:
|
204
|
+
"""Toggle the code block state"""
|
205
|
+
self._state["in_code_block"] = not self._state.get("in_code_block", False)
|
206
|
+
return self
|
207
|
+
|
208
|
+
def set_last_line_was_spacer(self, value: bool) -> StateBuilder:
|
209
|
+
"""Set whether the last line was a spacer"""
|
210
|
+
self._state["last_line_was_spacer"] = value
|
211
|
+
return self
|
212
|
+
|
213
|
+
def update_content_tracking(
|
214
|
+
self, line_type: LineType, has_content: bool
|
215
|
+
) -> StateBuilder:
|
216
|
+
"""Update content tracking state"""
|
217
|
+
if has_content:
|
218
|
+
self._state["last_non_empty_was_heading"] = line_type == LineType.HEADING
|
219
|
+
self._state["has_content_before"] = True
|
220
|
+
return self
|
221
|
+
|
222
|
+
def build(self) -> Dict[str, Any]:
|
223
|
+
"""Build the final state"""
|
224
|
+
return self._state
|
225
|
+
|
226
|
+
|
227
|
+
class LineProcessor(ABC):
|
228
|
+
"""Abstract processor for different line types"""
|
229
|
+
|
230
|
+
@abstractmethod
|
231
|
+
def can_process(self, line_type: LineType) -> bool:
|
232
|
+
"""Check if this processor can handle the line type"""
|
233
|
+
pass
|
234
|
+
|
235
|
+
@abstractmethod
|
236
|
+
def process(self, context: LineContext, state: Dict[str, Any]) -> ProcessingResult:
|
237
|
+
"""Process the line and return the result"""
|
238
|
+
pass
|
239
|
+
|
240
|
+
|
241
|
+
class EmptyLineProcessor(LineProcessor):
|
242
|
+
"""Processor for empty lines"""
|
243
|
+
|
244
|
+
def can_process(self, line_type: LineType) -> bool:
|
245
|
+
return line_type == LineType.EMPTY
|
246
|
+
|
247
|
+
def process(self, context: LineContext, state: Dict[str, Any]) -> ProcessingResult:
|
248
|
+
new_state = StateBuilder(state).set_last_line_was_spacer(False).build()
|
249
|
+
|
250
|
+
return ProcessingResult(output_lines=[context.line], new_state=new_state)
|
251
|
+
|
252
|
+
|
253
|
+
class CodeBlockMarkerProcessor(LineProcessor):
|
254
|
+
"""Processor for code block markers"""
|
255
|
+
|
256
|
+
def can_process(self, line_type: LineType) -> bool:
|
257
|
+
return line_type == LineType.CODE_BLOCK_MARKER
|
258
|
+
|
259
|
+
def process(self, context: LineContext, state: Dict[str, Any]) -> ProcessingResult:
|
260
|
+
new_state = (
|
261
|
+
StateBuilder(state)
|
262
|
+
.toggle_code_block()
|
263
|
+
.set_last_line_was_spacer(False)
|
264
|
+
.update_content_tracking(context.line_type, bool(context.content))
|
265
|
+
.build()
|
266
|
+
)
|
267
|
+
|
268
|
+
return ProcessingResult(output_lines=[context.line], new_state=new_state)
|
269
|
+
|
270
|
+
|
271
|
+
class SpacerMarkerProcessor(LineProcessor):
|
272
|
+
"""Processor for spacer marker lines"""
|
273
|
+
|
274
|
+
def __init__(self, spacer_marker: str, rules: List[SpacerRule]):
|
275
|
+
self.spacer_marker = spacer_marker
|
276
|
+
self.rules = rules
|
277
|
+
|
278
|
+
def can_process(self, line_type: LineType) -> bool:
|
279
|
+
return line_type == LineType.SPACER_MARKER
|
280
|
+
|
281
|
+
def process(self, context: LineContext, state: Dict[str, Any]) -> ProcessingResult:
|
282
|
+
# Apply spacer rules
|
283
|
+
spacer_decision = self._get_spacer_decision(context)
|
284
|
+
|
285
|
+
output_lines = []
|
286
|
+
spacer_added = False
|
287
|
+
|
288
|
+
if spacer_decision.should_add_spacer:
|
289
|
+
output_lines.append(context.line)
|
290
|
+
spacer_added = True
|
291
|
+
|
292
|
+
new_state = StateBuilder(state).set_last_line_was_spacer(spacer_added).build()
|
293
|
+
|
294
|
+
return ProcessingResult(
|
295
|
+
output_lines=output_lines, new_state=new_state, spacer_added=spacer_added
|
296
|
+
)
|
297
|
+
|
298
|
+
def _get_spacer_decision(self, context: LineContext) -> SpacerDecision:
|
299
|
+
"""Get spacer decision from rules"""
|
300
|
+
for rule in self.rules:
|
301
|
+
if rule.applies_to(context):
|
302
|
+
return rule.should_add_spacer(context)
|
303
|
+
|
304
|
+
# Default: don't add spacer
|
305
|
+
return SpacerDecision(False, "No applicable rule found", "DefaultRule")
|
306
|
+
|
307
|
+
|
308
|
+
class RegularContentProcessor(LineProcessor):
|
309
|
+
"""Processor for regular content lines"""
|
310
|
+
|
311
|
+
def __init__(self, spacer_marker: str, rules: List[SpacerRule]):
|
312
|
+
self.spacer_marker = spacer_marker
|
313
|
+
self.rules = rules
|
314
|
+
|
315
|
+
def can_process(self, line_type: LineType) -> bool:
|
316
|
+
return line_type in [
|
317
|
+
LineType.HEADING,
|
318
|
+
LineType.DIVIDER,
|
319
|
+
LineType.TODO_ITEM,
|
320
|
+
LineType.REGULAR_CONTENT,
|
321
|
+
LineType.PIPE_SYNTAX,
|
322
|
+
]
|
323
|
+
|
324
|
+
def process(self, context: LineContext, state: Dict[str, Any]) -> ProcessingResult:
|
325
|
+
output_lines = []
|
326
|
+
spacer_added = False
|
327
|
+
|
328
|
+
# Check if we should add a spacer before this line
|
329
|
+
spacer_decision = self._get_spacer_decision(context)
|
330
|
+
|
331
|
+
if spacer_decision.should_add_spacer:
|
332
|
+
output_lines.append(self.spacer_marker)
|
333
|
+
spacer_added = True
|
334
|
+
|
335
|
+
# Add the original line
|
336
|
+
output_lines.append(context.line)
|
337
|
+
|
338
|
+
# Build new state
|
339
|
+
new_state = (
|
340
|
+
StateBuilder(state)
|
341
|
+
.set_last_line_was_spacer(spacer_added)
|
342
|
+
.update_content_tracking(context.line_type, bool(context.content))
|
343
|
+
.build()
|
344
|
+
)
|
345
|
+
|
346
|
+
return ProcessingResult(
|
347
|
+
output_lines=output_lines, new_state=new_state, spacer_added=spacer_added
|
348
|
+
)
|
349
|
+
|
350
|
+
def _get_spacer_decision(self, context: LineContext) -> SpacerDecision:
|
351
|
+
"""Get spacer decision from rules"""
|
352
|
+
for rule in self.rules:
|
353
|
+
if rule.applies_to(context):
|
354
|
+
return rule.should_add_spacer(context)
|
355
|
+
|
356
|
+
# Default: don't add spacer
|
357
|
+
return SpacerDecision(False, "No applicable rule found", "DefaultRule")
|
358
|
+
|
359
|
+
|
360
|
+
class LineProcessorFactory:
|
361
|
+
"""Factory for creating line processors"""
|
362
|
+
|
363
|
+
def __init__(self, spacer_marker: str, rules: List[SpacerRule]):
|
364
|
+
self.processors = [
|
365
|
+
EmptyLineProcessor(),
|
366
|
+
CodeBlockMarkerProcessor(),
|
367
|
+
SpacerMarkerProcessor(spacer_marker, rules),
|
368
|
+
RegularContentProcessor(spacer_marker, rules),
|
369
|
+
]
|
370
|
+
|
371
|
+
def get_processor(self, line_type: LineType) -> Optional[LineProcessor]:
|
372
|
+
"""Get appropriate processor for the line type"""
|
373
|
+
for processor in self.processors:
|
374
|
+
if processor.can_process(line_type):
|
375
|
+
return processor
|
376
|
+
return None
|
377
|
+
|
378
|
+
|
379
|
+
class ContextFactory:
|
380
|
+
"""Factory for creating line contexts"""
|
381
|
+
|
382
|
+
@staticmethod
|
383
|
+
def create_context(
|
384
|
+
line: str, line_number: int, line_type: LineType, state: Dict[str, Any]
|
385
|
+
) -> LineContext:
|
386
|
+
"""Create a LineContext from line and state"""
|
387
|
+
return LineContext(
|
388
|
+
line=line,
|
389
|
+
line_number=line_number,
|
390
|
+
line_type=line_type,
|
391
|
+
is_empty=not line.strip(),
|
392
|
+
content=line.strip(),
|
393
|
+
in_code_block=state.get("in_code_block", False),
|
394
|
+
last_line_was_spacer=state.get("last_line_was_spacer", False),
|
395
|
+
last_non_empty_was_heading=state.get("last_non_empty_was_heading", False),
|
396
|
+
has_content_before=state.get("has_content_before", False),
|
397
|
+
processed_lines=state.get("processed_lines", []),
|
398
|
+
)
|
399
|
+
|
400
|
+
|
401
|
+
class SpacerRuleEngine:
|
402
|
+
"""Refactored engine with reduced complexity"""
|
403
|
+
|
404
|
+
def __init__(self, rules: Optional[List[SpacerRule]] = None):
|
405
|
+
self.rules = rules or self._get_default_rules()
|
406
|
+
self.SPACER_MARKER = SPACER_MARKER
|
407
|
+
|
408
|
+
# Initialize factories
|
409
|
+
self.processor_factory = LineProcessorFactory(
|
410
|
+
self.SPACER_MARKER,
|
411
|
+
self.rules,
|
412
|
+
)
|
413
|
+
self.context_factory = ContextFactory()
|
414
|
+
|
415
|
+
def process_line(
|
416
|
+
self, line: str, line_number: int, context_state: Dict[str, Any]
|
417
|
+
) -> Tuple[List[str], Dict[str, Any]]:
|
418
|
+
"""
|
419
|
+
Processes a line and returns the resulting lines + new state
|
420
|
+
|
421
|
+
Returns:
|
422
|
+
Tuple[List[str], Dict[str, Any]]: (processed_lines, new_state)
|
423
|
+
"""
|
424
|
+
# Step 1: Determine line type (single responsibility)
|
425
|
+
line_type = self._determine_line_type(
|
426
|
+
line, context_state.get("in_code_block", False)
|
427
|
+
)
|
428
|
+
|
429
|
+
# Step 2: Create context (factory pattern)
|
430
|
+
context = self.context_factory.create_context(
|
431
|
+
line, line_number, line_type, context_state
|
432
|
+
)
|
433
|
+
|
434
|
+
# Step 3: Get appropriate processor (strategy pattern)
|
435
|
+
processor = self.processor_factory.get_processor(line_type)
|
436
|
+
if not processor:
|
437
|
+
# Fallback to original line
|
438
|
+
return [line], context_state.copy()
|
439
|
+
|
440
|
+
# Step 4: Process line (delegation)
|
441
|
+
result = processor.process(context, context_state)
|
442
|
+
|
443
|
+
return result.output_lines, result.new_state
|
444
|
+
|
445
|
+
def _get_default_rules(self) -> List[SpacerRule]:
|
446
|
+
"""Default rule set"""
|
447
|
+
return [
|
448
|
+
CodeBlockSpacerRule(), # Highest priority - code blocks
|
449
|
+
ConsecutiveSpacerRule(), # Prevent duplicate spacers
|
450
|
+
HeadingSpacerRule(), # Spacer before headings
|
451
|
+
DividerSpacerRule(), # Spacer before dividers
|
452
|
+
]
|
453
|
+
|
454
|
+
def _determine_line_type(self, line: str, in_code_block: bool) -> LineType:
|
455
|
+
"""Determines the type of a line"""
|
456
|
+
content = line.strip()
|
457
|
+
|
458
|
+
# Guard clauses for early returns
|
459
|
+
if not content:
|
460
|
+
return LineType.EMPTY
|
461
|
+
|
462
|
+
if content.startswith("```"):
|
463
|
+
return LineType.CODE_BLOCK_MARKER
|
464
|
+
|
465
|
+
if in_code_block:
|
466
|
+
return LineType.REGULAR_CONTENT
|
467
|
+
|
468
|
+
if content == self.SPACER_MARKER:
|
469
|
+
return LineType.SPACER_MARKER
|
470
|
+
|
471
|
+
# Pattern matching with early returns
|
472
|
+
patterns = [
|
473
|
+
(r"^\|\s?(.*)$", LineType.PIPE_SYNTAX),
|
474
|
+
(r"^(#{1,6})\s+(.+)$", LineType.HEADING),
|
475
|
+
(r"^-{3,}$", LineType.DIVIDER),
|
476
|
+
(r"^\s*[-*+]\s+\[[ x]\]", LineType.TODO_ITEM),
|
477
|
+
]
|
478
|
+
|
479
|
+
for pattern, line_type in patterns:
|
480
|
+
if re.match(pattern, content if pattern.startswith("^#{") else line):
|
481
|
+
return line_type
|
482
|
+
|
483
|
+
return LineType.REGULAR_CONTENT
|
@@ -1,7 +1,7 @@
|
|
1
1
|
from typing import Any, Dict, Optional
|
2
2
|
from notionary.notion_client import NotionClient
|
3
3
|
from notionary.page.properites.property_formatter import NotionPropertyFormatter
|
4
|
-
from notionary.util
|
4
|
+
from notionary.util import LoggingMixin
|
5
5
|
|
6
6
|
|
7
7
|
class MetadataEditor(LoggingMixin):
|
@@ -3,7 +3,7 @@ from typing import Optional
|
|
3
3
|
|
4
4
|
from notionary.models.notion_page_response import EmojiIcon, ExternalIcon, FileIcon
|
5
5
|
from notionary.notion_client import NotionClient
|
6
|
-
from notionary.util
|
6
|
+
from notionary.util import LoggingMixin
|
7
7
|
|
8
8
|
|
9
9
|
class NotionPageIconManager(LoggingMixin):
|
notionary/page/notion_page.py
CHANGED
@@ -21,7 +21,7 @@ from notionary.page.content.page_content_writer import PageContentWriter
|
|
21
21
|
from notionary.page.properites.page_property_manager import PagePropertyManager
|
22
22
|
from notionary.page.relations.notion_page_title_resolver import NotionPageTitleResolver
|
23
23
|
from notionary.util.warn_direct_constructor_usage import warn_direct_constructor_usage
|
24
|
-
from notionary.util
|
24
|
+
from notionary.util import LoggingMixin
|
25
25
|
from notionary.util.page_id_utils import extract_and_validate_page_id
|
26
26
|
from notionary.page.relations.page_database_relation import PageDatabaseRelation
|
27
27
|
|