notionary 0.2.27__py3-none-any.whl → 0.2.28__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 +5 -20
- notionary/blocks/client.py +87 -215
- notionary/blocks/enums.py +167 -0
- notionary/blocks/rich_text/markdown_rich_text_converter.py +266 -0
- notionary/blocks/rich_text/models.py +164 -0
- notionary/blocks/rich_text/name_id_resolver/__init__.py +11 -0
- notionary/blocks/rich_text/name_id_resolver/database.py +31 -0
- notionary/blocks/rich_text/name_id_resolver/page.py +34 -0
- notionary/blocks/rich_text/name_id_resolver/person.py +37 -0
- notionary/blocks/rich_text/name_id_resolver/port.py +11 -0
- notionary/blocks/rich_text/rich_text_markdown_converter.py +132 -0
- notionary/blocks/rich_text/rich_text_patterns.py +39 -0
- notionary/blocks/schemas.py +746 -0
- notionary/comments/client.py +52 -187
- notionary/comments/factory.py +40 -0
- notionary/comments/models.py +5 -127
- notionary/comments/schemas.py +240 -0
- notionary/comments/service.py +34 -0
- notionary/data_source/http/client.py +11 -0
- notionary/data_source/http/data_source_instance_client.py +94 -0
- notionary/data_source/properties/models.py +406 -0
- notionary/data_source/query/builder.py +429 -0
- notionary/data_source/query/resolver.py +114 -0
- notionary/data_source/query/schema.py +304 -0
- notionary/data_source/query/validator.py +73 -0
- notionary/data_source/schemas.py +27 -0
- notionary/data_source/service.py +353 -0
- notionary/database/client.py +30 -135
- notionary/database/database_metadata_update_client.py +19 -0
- notionary/database/schemas.py +29 -0
- notionary/database/service.py +169 -0
- notionary/exceptions/__init__.py +33 -0
- notionary/exceptions/api.py +41 -0
- notionary/exceptions/base.py +2 -0
- notionary/exceptions/block_parsing.py +16 -0
- notionary/exceptions/data_source/__init__.py +6 -0
- notionary/exceptions/data_source/builder.py +182 -0
- notionary/exceptions/data_source/properties.py +34 -0
- notionary/exceptions/properties.py +58 -0
- notionary/exceptions/search.py +33 -0
- notionary/file_upload/client.py +18 -30
- notionary/file_upload/models.py +7 -8
- notionary/file_upload/{notion_file_upload.py → service.py} +29 -64
- notionary/http/client.py +205 -0
- notionary/http/models.py +49 -0
- notionary/page/blocks/client.py +1 -0
- notionary/page/content/factory.py +68 -0
- notionary/page/content/markdown/__init__.py +5 -0
- notionary/page/content/markdown/builder.py +304 -0
- notionary/page/content/markdown/nodes/__init__.py +54 -0
- notionary/page/content/markdown/nodes/audio.py +23 -0
- notionary/page/content/markdown/nodes/base.py +12 -0
- notionary/page/content/markdown/nodes/bookmark.py +25 -0
- notionary/page/content/markdown/nodes/breadcrumb.py +14 -0
- notionary/page/content/markdown/nodes/bulleted_list.py +18 -0
- notionary/page/content/markdown/nodes/callout.py +32 -0
- notionary/page/content/markdown/nodes/code.py +30 -0
- notionary/page/content/markdown/nodes/columns.py +51 -0
- notionary/page/content/markdown/nodes/divider.py +14 -0
- notionary/page/content/markdown/nodes/embed.py +23 -0
- notionary/page/content/markdown/nodes/equation.py +19 -0
- notionary/page/content/markdown/nodes/file.py +23 -0
- notionary/page/content/markdown/nodes/heading.py +16 -0
- notionary/page/content/markdown/nodes/image.py +23 -0
- notionary/page/content/markdown/nodes/mixins/caption.py +12 -0
- notionary/page/content/markdown/nodes/numbered_list.py +15 -0
- notionary/page/content/markdown/nodes/paragraph.py +14 -0
- notionary/page/content/markdown/nodes/pdf.py +23 -0
- notionary/page/content/markdown/nodes/quote.py +15 -0
- notionary/page/content/markdown/nodes/space.py +14 -0
- notionary/page/content/markdown/nodes/table.py +45 -0
- notionary/page/content/markdown/nodes/table_of_contents.py +14 -0
- notionary/page/content/markdown/nodes/todo.py +22 -0
- notionary/page/content/markdown/nodes/toggle.py +28 -0
- notionary/page/content/markdown/nodes/toggleable_heading.py +35 -0
- notionary/page/content/markdown/nodes/video.py +23 -0
- notionary/page/content/parser/context.py +49 -0
- notionary/page/content/parser/factory.py +219 -0
- notionary/page/content/parser/parsers/__init__.py +60 -0
- notionary/page/content/parser/parsers/audio.py +40 -0
- notionary/page/content/parser/parsers/base.py +30 -0
- notionary/page/content/parser/parsers/bookmark.py +33 -0
- notionary/page/content/parser/parsers/breadcrumb.py +33 -0
- notionary/page/content/parser/parsers/bulleted_list.py +41 -0
- notionary/page/content/parser/parsers/callout.py +129 -0
- notionary/page/content/parser/parsers/caption.py +55 -0
- notionary/page/content/parser/parsers/code.py +81 -0
- notionary/page/content/parser/parsers/column.py +117 -0
- notionary/page/content/parser/parsers/column_list.py +81 -0
- notionary/page/content/parser/parsers/divider.py +33 -0
- notionary/page/content/parser/parsers/embed.py +33 -0
- notionary/page/content/parser/parsers/equation.py +65 -0
- notionary/page/content/parser/parsers/file.py +42 -0
- notionary/page/content/parser/parsers/heading.py +58 -0
- notionary/page/content/parser/parsers/image.py +42 -0
- notionary/page/content/parser/parsers/numbered_list.py +45 -0
- notionary/page/content/parser/parsers/paragraph.py +36 -0
- notionary/page/content/parser/parsers/pdf.py +42 -0
- notionary/page/content/parser/parsers/quote.py +65 -0
- notionary/page/content/parser/parsers/space.py +35 -0
- notionary/page/content/parser/parsers/table.py +144 -0
- notionary/page/content/parser/parsers/table_of_contents.py +32 -0
- notionary/page/content/parser/parsers/todo.py +58 -0
- notionary/page/content/parser/parsers/toggle.py +127 -0
- notionary/page/content/parser/parsers/toggleable_heading.py +150 -0
- notionary/page/content/parser/parsers/video.py +42 -0
- notionary/page/content/parser/post_processing/handlers/__init__.py +5 -0
- notionary/page/content/parser/post_processing/handlers/rich_text_length.py +93 -0
- notionary/page/content/parser/post_processing/handlers/rich_text_length_truncation.py +93 -0
- notionary/page/content/parser/post_processing/port.py +9 -0
- notionary/page/content/parser/post_processing/service.py +16 -0
- notionary/page/content/parser/pre_processsing/handlers/__init__.py +9 -0
- notionary/page/content/parser/pre_processsing/handlers/column_syntax.py +80 -0
- notionary/page/content/parser/pre_processsing/handlers/port.py +7 -0
- notionary/page/content/parser/pre_processsing/handlers/whitespace.py +68 -0
- notionary/page/content/parser/pre_processsing/service.py +15 -0
- notionary/page/content/parser/service.py +69 -0
- notionary/page/content/renderer/context.py +48 -0
- notionary/page/content/renderer/factory.py +240 -0
- notionary/page/content/renderer/post_processing/handlers/__init__.py +5 -0
- notionary/page/content/renderer/post_processing/handlers/numbered_list_placeholdere.py +62 -0
- notionary/page/content/renderer/post_processing/port.py +7 -0
- notionary/page/content/renderer/post_processing/service.py +15 -0
- notionary/page/content/renderer/renderers/__init__.py +57 -0
- notionary/page/content/renderer/renderers/audio.py +31 -0
- notionary/page/content/renderer/renderers/base.py +31 -0
- notionary/page/content/renderer/renderers/bookmark.py +25 -0
- notionary/page/content/renderer/renderers/breadcrumb.py +21 -0
- notionary/page/content/renderer/renderers/bulleted_list.py +48 -0
- notionary/page/content/renderer/renderers/callout.py +65 -0
- notionary/page/content/renderer/renderers/captioned_block.py +58 -0
- notionary/page/content/renderer/renderers/code.py +34 -0
- notionary/page/content/renderer/renderers/column.py +44 -0
- notionary/page/content/renderer/renderers/column_list.py +31 -0
- notionary/page/content/renderer/renderers/divider.py +22 -0
- notionary/page/content/renderer/renderers/embed.py +25 -0
- notionary/page/content/renderer/renderers/equation.py +37 -0
- notionary/page/content/renderer/renderers/fallback.py +24 -0
- notionary/page/content/renderer/renderers/file.py +40 -0
- notionary/page/content/renderer/renderers/heading.py +69 -0
- notionary/page/content/renderer/renderers/image.py +31 -0
- notionary/page/content/renderer/renderers/numbered_list.py +41 -0
- notionary/page/content/renderer/renderers/paragraph.py +40 -0
- notionary/page/content/renderer/renderers/pdf.py +31 -0
- notionary/page/content/renderer/renderers/quote.py +49 -0
- notionary/page/content/renderer/renderers/table.py +115 -0
- notionary/page/content/renderer/renderers/table_of_contents.py +26 -0
- notionary/page/content/renderer/renderers/table_row.py +17 -0
- notionary/page/content/renderer/renderers/todo.py +56 -0
- notionary/page/content/renderer/renderers/toggle.py +53 -0
- notionary/page/content/renderer/renderers/toggleable_heading.py +78 -0
- notionary/page/content/renderer/renderers/video.py +31 -0
- notionary/page/content/renderer/service.py +50 -0
- notionary/page/content/service.py +65 -0
- notionary/page/content/syntax/models.py +68 -0
- notionary/page/content/syntax/service.py +453 -0
- notionary/page/page_context.py +7 -16
- notionary/page/page_http_client.py +15 -0
- notionary/page/page_metadata_update_client.py +19 -0
- notionary/page/properties/client.py +144 -0
- notionary/page/properties/factory.py +26 -0
- notionary/page/properties/models.py +307 -0
- notionary/page/properties/service.py +257 -0
- notionary/page/schemas.py +13 -0
- notionary/page/service.py +222 -0
- notionary/shared/entity/client.py +29 -0
- notionary/shared/entity/dto_parsers.py +53 -0
- notionary/shared/entity/entity_metadata_update_client.py +41 -0
- notionary/shared/entity/schemas.py +45 -0
- notionary/shared/entity/service.py +171 -0
- notionary/shared/models/cover.py +20 -0
- notionary/shared/models/file.py +21 -0
- notionary/shared/models/icon.py +28 -0
- notionary/shared/models/parent.py +41 -0
- notionary/shared/properties/type.py +30 -0
- notionary/user/__init__.py +4 -8
- notionary/user/base.py +89 -0
- notionary/user/bot.py +70 -0
- notionary/user/client.py +22 -111
- notionary/user/person.py +41 -0
- notionary/user/schemas.py +67 -0
- notionary/user/service.py +65 -0
- notionary/utils/async_retry.py +39 -0
- notionary/utils/date.py +51 -0
- notionary/utils/fuzzy.py +56 -0
- notionary/{util/logging_mixin.py → utils/mixins/logging.py} +4 -16
- notionary/utils/pagination.py +50 -0
- notionary/utils/singleton.py +13 -0
- notionary/utils/uuid_utils.py +20 -0
- notionary/workspace/__init__.py +3 -0
- notionary/workspace/client.py +62 -0
- notionary/workspace/query/builder.py +60 -0
- notionary/workspace/query/models.py +60 -0
- notionary/workspace/query/service.py +93 -0
- notionary/workspace/schemas.py +21 -0
- notionary/workspace/service.py +116 -0
- {notionary-0.2.27.dist-info → notionary-0.2.28.dist-info}/METADATA +54 -49
- notionary-0.2.28.dist-info/RECORD +200 -0
- {notionary-0.2.27.dist-info → notionary-0.2.28.dist-info}/WHEEL +1 -1
- {notionary-0.2.27.dist-info → notionary-0.2.28.dist-info/licenses}/LICENSE +9 -9
- notionary/base_notion_client.py +0 -219
- notionary/blocks/__init__.py +0 -5
- notionary/blocks/_bootstrap.py +0 -271
- notionary/blocks/audio/__init__.py +0 -11
- notionary/blocks/audio/audio_element.py +0 -158
- notionary/blocks/audio/audio_markdown_node.py +0 -24
- notionary/blocks/audio/audio_models.py +0 -10
- notionary/blocks/base_block_element.py +0 -42
- notionary/blocks/bookmark/__init__.py +0 -12
- notionary/blocks/bookmark/bookmark_element.py +0 -83
- notionary/blocks/bookmark/bookmark_markdown_node.py +0 -28
- notionary/blocks/bookmark/bookmark_models.py +0 -15
- notionary/blocks/breadcrumbs/__init__.py +0 -15
- notionary/blocks/breadcrumbs/breadcrumb_element.py +0 -39
- notionary/blocks/breadcrumbs/breadcrumb_markdown_node.py +0 -13
- notionary/blocks/breadcrumbs/breadcrumb_models.py +0 -12
- notionary/blocks/bulleted_list/__init__.py +0 -15
- notionary/blocks/bulleted_list/bulleted_list_element.py +0 -74
- notionary/blocks/bulleted_list/bulleted_list_markdown_node.py +0 -20
- notionary/blocks/bulleted_list/bulleted_list_models.py +0 -17
- notionary/blocks/callout/__init__.py +0 -12
- notionary/blocks/callout/callout_element.py +0 -99
- notionary/blocks/callout/callout_markdown_node.py +0 -19
- notionary/blocks/callout/callout_models.py +0 -33
- notionary/blocks/child_database/__init__.py +0 -14
- notionary/blocks/child_database/child_database_element.py +0 -59
- notionary/blocks/child_database/child_database_models.py +0 -12
- notionary/blocks/child_page/__init__.py +0 -9
- notionary/blocks/child_page/child_page_element.py +0 -94
- notionary/blocks/child_page/child_page_models.py +0 -12
- notionary/blocks/code/__init__.py +0 -11
- notionary/blocks/code/code_element.py +0 -149
- notionary/blocks/code/code_markdown_node.py +0 -80
- notionary/blocks/code/code_models.py +0 -94
- notionary/blocks/column/__init__.py +0 -25
- notionary/blocks/column/column_element.py +0 -65
- notionary/blocks/column/column_list_element.py +0 -52
- notionary/blocks/column/column_list_markdown_node.py +0 -34
- notionary/blocks/column/column_markdown_node.py +0 -42
- notionary/blocks/column/column_models.py +0 -26
- notionary/blocks/divider/__init__.py +0 -12
- notionary/blocks/divider/divider_element.py +0 -41
- notionary/blocks/divider/divider_markdown_node.py +0 -11
- notionary/blocks/divider/divider_models.py +0 -12
- notionary/blocks/embed/__init__.py +0 -12
- notionary/blocks/embed/embed_element.py +0 -98
- notionary/blocks/embed/embed_markdown_node.py +0 -19
- notionary/blocks/embed/embed_models.py +0 -14
- notionary/blocks/equation/__init__.py +0 -13
- notionary/blocks/equation/equation_element.py +0 -133
- notionary/blocks/equation/equation_element_markdown_node.py +0 -23
- notionary/blocks/equation/equation_models.py +0 -11
- notionary/blocks/file/__init__.py +0 -23
- notionary/blocks/file/file_element.py +0 -133
- notionary/blocks/file/file_element_markdown_node.py +0 -24
- notionary/blocks/file/file_element_models.py +0 -39
- notionary/blocks/heading/__init__.py +0 -19
- notionary/blocks/heading/heading_element.py +0 -112
- notionary/blocks/heading/heading_markdown_node.py +0 -16
- notionary/blocks/heading/heading_models.py +0 -29
- notionary/blocks/image_block/__init__.py +0 -11
- notionary/blocks/image_block/image_element.py +0 -130
- notionary/blocks/image_block/image_markdown_node.py +0 -25
- notionary/blocks/image_block/image_models.py +0 -10
- notionary/blocks/markdown/markdown_builder.py +0 -525
- notionary/blocks/markdown/markdown_document_model.py +0 -0
- notionary/blocks/markdown/markdown_node.py +0 -25
- notionary/blocks/mixins/captions/__init__.py +0 -4
- notionary/blocks/mixins/captions/caption_markdown_node_mixin.py +0 -31
- notionary/blocks/mixins/captions/caption_mixin.py +0 -92
- notionary/blocks/mixins/file_upload/__init__.py +0 -3
- notionary/blocks/mixins/file_upload/file_upload_mixin.py +0 -320
- notionary/blocks/models.py +0 -174
- notionary/blocks/numbered_list/__init__.py +0 -16
- notionary/blocks/numbered_list/numbered_list_element.py +0 -65
- notionary/blocks/numbered_list/numbered_list_markdown_node.py +0 -17
- notionary/blocks/numbered_list/numbered_list_models.py +0 -17
- notionary/blocks/paragraph/__init__.py +0 -15
- notionary/blocks/paragraph/paragraph_element.py +0 -58
- notionary/blocks/paragraph/paragraph_markdown_node.py +0 -16
- notionary/blocks/paragraph/paragraph_models.py +0 -16
- notionary/blocks/pdf/__init__.py +0 -11
- notionary/blocks/pdf/pdf_element.py +0 -146
- notionary/blocks/pdf/pdf_markdown_node.py +0 -24
- notionary/blocks/pdf/pdf_models.py +0 -11
- notionary/blocks/quote/__init__.py +0 -14
- notionary/blocks/quote/quote_element.py +0 -75
- notionary/blocks/quote/quote_markdown_node.py +0 -16
- notionary/blocks/quote/quote_models.py +0 -18
- notionary/blocks/registry/__init__.py +0 -3
- notionary/blocks/registry/block_registry.py +0 -150
- notionary/blocks/rich_text/__init__.py +0 -33
- notionary/blocks/rich_text/rich_text_models.py +0 -221
- notionary/blocks/rich_text/text_inline_formatter.py +0 -456
- notionary/blocks/syntax_prompt_builder.py +0 -137
- notionary/blocks/table/__init__.py +0 -19
- notionary/blocks/table/table_element.py +0 -225
- notionary/blocks/table/table_markdown_node.py +0 -42
- notionary/blocks/table/table_models.py +0 -28
- notionary/blocks/table_of_contents/__init__.py +0 -17
- notionary/blocks/table_of_contents/table_of_contents_element.py +0 -80
- notionary/blocks/table_of_contents/table_of_contents_markdown_node.py +0 -21
- notionary/blocks/table_of_contents/table_of_contents_models.py +0 -18
- notionary/blocks/todo/__init__.py +0 -12
- notionary/blocks/todo/todo_element.py +0 -81
- notionary/blocks/todo/todo_markdown_node.py +0 -21
- notionary/blocks/todo/todo_models.py +0 -18
- notionary/blocks/toggle/__init__.py +0 -12
- notionary/blocks/toggle/toggle_element.py +0 -112
- notionary/blocks/toggle/toggle_markdown_node.py +0 -31
- notionary/blocks/toggle/toggle_models.py +0 -17
- notionary/blocks/toggleable_heading/__init__.py +0 -11
- notionary/blocks/toggleable_heading/toggleable_heading_element.py +0 -115
- notionary/blocks/toggleable_heading/toggleable_heading_markdown_node.py +0 -34
- notionary/blocks/types.py +0 -130
- notionary/blocks/video/__init__.py +0 -11
- notionary/blocks/video/video_element.py +0 -187
- notionary/blocks/video/video_element_models.py +0 -10
- notionary/blocks/video/video_markdown_node.py +0 -26
- notionary/comments/__init__.py +0 -26
- notionary/database/__init__.py +0 -4
- notionary/database/database.py +0 -480
- notionary/database/database_filter_builder.py +0 -173
- notionary/database/database_provider.py +0 -227
- notionary/database/exceptions.py +0 -13
- notionary/database/factory.py +0 -0
- notionary/database/models.py +0 -337
- notionary/database/notion_database.py +0 -487
- notionary/file_upload/__init__.py +0 -7
- notionary/page/client.py +0 -124
- notionary/page/markdown_whitespace_processor.py +0 -129
- notionary/page/models.py +0 -322
- notionary/page/notion_page.py +0 -712
- notionary/page/page_content_deleting_service.py +0 -117
- notionary/page/page_content_writer.py +0 -80
- notionary/page/property_formatter.py +0 -99
- notionary/page/reader/handler/__init__.py +0 -19
- notionary/page/reader/handler/base_block_renderer.py +0 -44
- notionary/page/reader/handler/block_processing_context.py +0 -35
- notionary/page/reader/handler/block_rendering_context.py +0 -48
- notionary/page/reader/handler/column_list_renderer.py +0 -51
- notionary/page/reader/handler/column_renderer.py +0 -60
- notionary/page/reader/handler/equation_renderer.py +0 -0
- notionary/page/reader/handler/line_renderer.py +0 -73
- notionary/page/reader/handler/numbered_list_renderer.py +0 -85
- notionary/page/reader/handler/toggle_renderer.py +0 -69
- notionary/page/reader/handler/toggleable_heading_renderer.py +0 -89
- notionary/page/reader/page_content_retriever.py +0 -81
- notionary/page/search_filter_builder.py +0 -132
- notionary/page/utils.py +0 -60
- notionary/page/writer/handler/__init__.py +0 -24
- notionary/page/writer/handler/code_handler.py +0 -72
- notionary/page/writer/handler/column_handler.py +0 -141
- notionary/page/writer/handler/column_list_handler.py +0 -139
- notionary/page/writer/handler/equation_handler.py +0 -74
- notionary/page/writer/handler/line_handler.py +0 -35
- notionary/page/writer/handler/line_processing_context.py +0 -54
- notionary/page/writer/handler/regular_line_handler.py +0 -86
- notionary/page/writer/handler/table_handler.py +0 -66
- notionary/page/writer/handler/toggle_handler.py +0 -159
- notionary/page/writer/handler/toggleable_heading_handler.py +0 -174
- notionary/page/writer/markdown_to_notion_converter.py +0 -139
- notionary/page/writer/markdown_to_notion_converter_context.py +0 -30
- notionary/page/writer/markdown_to_notion_text_length_post_processor.py +0 -0
- notionary/page/writer/notion_text_length_processor.py +0 -150
- notionary/schemas/__init__.py +0 -3
- notionary/schemas/base.py +0 -73
- notionary/shared/__init__.py +0 -3
- notionary/shared/name_to_id_resolver.py +0 -203
- notionary/telemetry/__init__.py +0 -19
- notionary/telemetry/service.py +0 -136
- notionary/telemetry/views.py +0 -73
- notionary/user/base_notion_user.py +0 -53
- notionary/user/models.py +0 -84
- notionary/user/notion_bot_user.py +0 -226
- notionary/user/notion_user.py +0 -255
- notionary/user/notion_user_manager.py +0 -101
- notionary/util/__init__.py +0 -15
- notionary/util/concurrency_limiter.py +0 -0
- notionary/util/factory_decorator.py +0 -0
- notionary/util/factory_only.py +0 -37
- notionary/util/fuzzy.py +0 -75
- notionary/util/page_id_utils.py +0 -27
- notionary/util/singleton.py +0 -18
- notionary/util/singleton_metaclass.py +0 -22
- notionary/workspace.py +0 -105
- notionary-0.2.27.dist-info/RECORD +0 -202
@@ -0,0 +1,60 @@
|
|
1
|
+
from ..context import BlockParsingContext, ParentBlockContext
|
2
|
+
from .audio import AudioParser
|
3
|
+
from .base import LineParser
|
4
|
+
from .bookmark import BookmarkParser
|
5
|
+
from .breadcrumb import BreadcrumbParser
|
6
|
+
from .bulleted_list import BulletedListParser
|
7
|
+
from .callout import CalloutParser
|
8
|
+
from .caption import CaptionParser
|
9
|
+
from .code import CodeParser
|
10
|
+
from .column import ColumnParser
|
11
|
+
from .column_list import ColumnListParser
|
12
|
+
from .divider import DividerParser
|
13
|
+
from .embed import EmbedParser
|
14
|
+
from .equation import EquationParser
|
15
|
+
from .file import FileParser
|
16
|
+
from .heading import HeadingParser
|
17
|
+
from .image import ImageParser
|
18
|
+
from .numbered_list import NumberedListParser
|
19
|
+
from .paragraph import ParagraphParser
|
20
|
+
from .pdf import PdfParser
|
21
|
+
from .quote import QuoteParser
|
22
|
+
from .space import SpaceParser
|
23
|
+
from .table import TableParser
|
24
|
+
from .table_of_contents import TableOfContentsParser
|
25
|
+
from .todo import TodoParser
|
26
|
+
from .toggle import ToggleParser
|
27
|
+
from .toggleable_heading import ToggleableHeadingParser
|
28
|
+
from .video import VideoParser
|
29
|
+
|
30
|
+
__all__ = [
|
31
|
+
"AudioParser",
|
32
|
+
"BlockParsingContext",
|
33
|
+
"BookmarkParser",
|
34
|
+
"BreadcrumbParser",
|
35
|
+
"BulletedListParser",
|
36
|
+
"CalloutParser",
|
37
|
+
"CaptionParser",
|
38
|
+
"CodeParser",
|
39
|
+
"ColumnListParser",
|
40
|
+
"ColumnParser",
|
41
|
+
"DividerParser",
|
42
|
+
"EmbedParser",
|
43
|
+
"EquationParser",
|
44
|
+
"FileParser",
|
45
|
+
"HeadingParser",
|
46
|
+
"ImageParser",
|
47
|
+
"LineParser",
|
48
|
+
"NumberedListParser",
|
49
|
+
"ParagraphParser",
|
50
|
+
"ParentBlockContext",
|
51
|
+
"PdfParser",
|
52
|
+
"QuoteParser",
|
53
|
+
"SpaceParser",
|
54
|
+
"TableOfContentsParser",
|
55
|
+
"TableParser",
|
56
|
+
"TodoParser",
|
57
|
+
"ToggleParser",
|
58
|
+
"ToggleableHeadingParser",
|
59
|
+
"VideoParser",
|
60
|
+
]
|
@@ -0,0 +1,40 @@
|
|
1
|
+
from typing import override
|
2
|
+
|
3
|
+
from notionary.blocks.schemas import (
|
4
|
+
CreateAudioBlock,
|
5
|
+
ExternalFile,
|
6
|
+
FileData,
|
7
|
+
FileType,
|
8
|
+
)
|
9
|
+
from notionary.page.content.parser.parsers.base import BlockParsingContext, LineParser
|
10
|
+
from notionary.page.content.syntax.service import SyntaxRegistry
|
11
|
+
|
12
|
+
|
13
|
+
class AudioParser(LineParser):
|
14
|
+
def __init__(self, syntax_registry: SyntaxRegistry) -> None:
|
15
|
+
super().__init__(syntax_registry)
|
16
|
+
self._syntax = syntax_registry.get_audio_syntax()
|
17
|
+
|
18
|
+
@override
|
19
|
+
def _can_handle(self, context: BlockParsingContext) -> bool:
|
20
|
+
if context.is_inside_parent_context():
|
21
|
+
return False
|
22
|
+
return self._syntax.regex_pattern.search(context.line) is not None
|
23
|
+
|
24
|
+
@override
|
25
|
+
async def _process(self, context: BlockParsingContext) -> None:
|
26
|
+
url = self._extract_url(context.line)
|
27
|
+
if url is None:
|
28
|
+
return
|
29
|
+
|
30
|
+
audio_data = FileData(
|
31
|
+
type=FileType.EXTERNAL,
|
32
|
+
external=ExternalFile(url=url),
|
33
|
+
caption=[],
|
34
|
+
)
|
35
|
+
block = CreateAudioBlock(audio=audio_data)
|
36
|
+
context.result_blocks.append(block)
|
37
|
+
|
38
|
+
def _extract_url(self, line: str) -> str | None:
|
39
|
+
match = self._syntax.regex_pattern.search(line)
|
40
|
+
return match.group(1).strip() if match else None
|
@@ -0,0 +1,30 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from abc import ABC, abstractmethod
|
4
|
+
|
5
|
+
from notionary.page.content.parser.context import BlockParsingContext
|
6
|
+
from notionary.page.content.syntax.service import SyntaxRegistry
|
7
|
+
|
8
|
+
|
9
|
+
class LineParser(ABC):
|
10
|
+
def __init__(self, syntax_registry: SyntaxRegistry | None = None) -> None:
|
11
|
+
self._next_handler: LineParser | None = None
|
12
|
+
self._syntax_registry = syntax_registry
|
13
|
+
|
14
|
+
def set_next(self, handler: LineParser) -> LineParser:
|
15
|
+
self._next_handler = handler
|
16
|
+
return handler
|
17
|
+
|
18
|
+
async def handle(self, context: BlockParsingContext) -> None:
|
19
|
+
if self._can_handle(context):
|
20
|
+
await self._process(context)
|
21
|
+
elif self._next_handler:
|
22
|
+
await self._next_handler.handle(context)
|
23
|
+
|
24
|
+
@abstractmethod
|
25
|
+
def _can_handle(self, context: BlockParsingContext) -> bool:
|
26
|
+
pass
|
27
|
+
|
28
|
+
@abstractmethod
|
29
|
+
async def _process(self, context: BlockParsingContext) -> None:
|
30
|
+
pass
|
@@ -0,0 +1,33 @@
|
|
1
|
+
"""Parser for bookmark blocks."""
|
2
|
+
|
3
|
+
from typing import override
|
4
|
+
|
5
|
+
from notionary.blocks.schemas import BookmarkData, CreateBookmarkBlock
|
6
|
+
from notionary.page.content.parser.parsers.base import BlockParsingContext, LineParser
|
7
|
+
from notionary.page.content.syntax.service import SyntaxRegistry
|
8
|
+
|
9
|
+
|
10
|
+
class BookmarkParser(LineParser):
|
11
|
+
def __init__(self, syntax_registry: SyntaxRegistry) -> None:
|
12
|
+
super().__init__(syntax_registry)
|
13
|
+
self._syntax = syntax_registry.get_bookmark_syntax()
|
14
|
+
|
15
|
+
@override
|
16
|
+
def _can_handle(self, context: BlockParsingContext) -> bool:
|
17
|
+
if context.is_inside_parent_context():
|
18
|
+
return False
|
19
|
+
return self._syntax.regex_pattern.search(context.line) is not None
|
20
|
+
|
21
|
+
@override
|
22
|
+
async def _process(self, context: BlockParsingContext) -> None:
|
23
|
+
url = self._extract_url(context.line)
|
24
|
+
if not url:
|
25
|
+
return
|
26
|
+
|
27
|
+
bookmark_data = BookmarkData(url=url, caption=[])
|
28
|
+
block = CreateBookmarkBlock(bookmark=bookmark_data)
|
29
|
+
context.result_blocks.append(block)
|
30
|
+
|
31
|
+
def _extract_url(self, line: str) -> str | None:
|
32
|
+
match = self._syntax.regex_pattern.search(line)
|
33
|
+
return match.group(1) if match else None
|
@@ -0,0 +1,33 @@
|
|
1
|
+
from typing import override
|
2
|
+
|
3
|
+
from notionary.blocks.schemas import BreadcrumbData, CreateBreadcrumbBlock
|
4
|
+
from notionary.page.content.parser.parsers.base import (
|
5
|
+
BlockParsingContext,
|
6
|
+
LineParser,
|
7
|
+
)
|
8
|
+
from notionary.page.content.syntax.service import SyntaxRegistry
|
9
|
+
|
10
|
+
|
11
|
+
class BreadcrumbParser(LineParser):
|
12
|
+
def __init__(self, syntax_registry: SyntaxRegistry) -> None:
|
13
|
+
super().__init__(syntax_registry)
|
14
|
+
self._syntax = syntax_registry.get_breadcrumb_syntax()
|
15
|
+
|
16
|
+
@override
|
17
|
+
def _can_handle(self, context: BlockParsingContext) -> bool:
|
18
|
+
if context.is_inside_parent_context():
|
19
|
+
return False
|
20
|
+
return self._is_breadcrumb(context.line)
|
21
|
+
|
22
|
+
@override
|
23
|
+
async def _process(self, context: BlockParsingContext) -> None:
|
24
|
+
block = self._create_breadcrumb_block()
|
25
|
+
if block:
|
26
|
+
context.result_blocks.append(block)
|
27
|
+
|
28
|
+
def _is_breadcrumb(self, line: str) -> bool:
|
29
|
+
return self._syntax.regex_pattern.match(line) is not None
|
30
|
+
|
31
|
+
def _create_breadcrumb_block(self) -> CreateBreadcrumbBlock:
|
32
|
+
breadcrumb_data = BreadcrumbData()
|
33
|
+
return CreateBreadcrumbBlock(breadcrumb=breadcrumb_data)
|
@@ -0,0 +1,41 @@
|
|
1
|
+
from typing import override
|
2
|
+
|
3
|
+
from notionary.blocks.rich_text.markdown_rich_text_converter import (
|
4
|
+
MarkdownRichTextConverter,
|
5
|
+
)
|
6
|
+
from notionary.blocks.schemas import BulletedListItemData, CreateBulletedListItemBlock
|
7
|
+
from notionary.page.content.parser.parsers.base import (
|
8
|
+
BlockParsingContext,
|
9
|
+
LineParser,
|
10
|
+
)
|
11
|
+
from notionary.page.content.syntax.service import SyntaxRegistry
|
12
|
+
|
13
|
+
|
14
|
+
class BulletedListParser(LineParser):
|
15
|
+
def __init__(self, syntax_registry: SyntaxRegistry, rich_text_converter: MarkdownRichTextConverter) -> None:
|
16
|
+
super().__init__(syntax_registry)
|
17
|
+
self._syntax = syntax_registry.get_bulleted_list_syntax()
|
18
|
+
self._rich_text_converter = rich_text_converter
|
19
|
+
|
20
|
+
@override
|
21
|
+
def _can_handle(self, context: BlockParsingContext) -> bool:
|
22
|
+
if context.is_inside_parent_context():
|
23
|
+
return False
|
24
|
+
return self._syntax.regex_pattern.match(context.line) is not None
|
25
|
+
|
26
|
+
@override
|
27
|
+
async def _process(self, context: BlockParsingContext) -> None:
|
28
|
+
block = await self._create_bulleted_list_block(context.line)
|
29
|
+
if block:
|
30
|
+
context.result_blocks.append(block)
|
31
|
+
|
32
|
+
async def _create_bulleted_list_block(self, text: str) -> CreateBulletedListItemBlock | None:
|
33
|
+
match = self._syntax.regex_pattern.match(text)
|
34
|
+
if not match:
|
35
|
+
return None
|
36
|
+
|
37
|
+
content = match.group(2)
|
38
|
+
rich_text = await self._rich_text_converter.to_rich_text(content)
|
39
|
+
|
40
|
+
bulleted_list_content = BulletedListItemData(rich_text=rich_text)
|
41
|
+
return CreateBulletedListItemBlock(bulleted_list_item=bulleted_list_content)
|
@@ -0,0 +1,129 @@
|
|
1
|
+
from typing import override
|
2
|
+
|
3
|
+
from notionary.blocks.rich_text.markdown_rich_text_converter import MarkdownRichTextConverter
|
4
|
+
from notionary.blocks.schemas import CreateCalloutBlock, CreateCalloutData
|
5
|
+
from notionary.page.content.parser.context import ParentBlockContext
|
6
|
+
from notionary.page.content.parser.parsers.base import (
|
7
|
+
BlockParsingContext,
|
8
|
+
LineParser,
|
9
|
+
)
|
10
|
+
from notionary.page.content.syntax.service import SyntaxRegistry
|
11
|
+
from notionary.shared.models.icon import EmojiIcon
|
12
|
+
|
13
|
+
|
14
|
+
class CalloutParser(LineParser):
|
15
|
+
DEFAULT_EMOJI = "💡"
|
16
|
+
|
17
|
+
def __init__(self, syntax_registry: SyntaxRegistry, rich_text_converter: MarkdownRichTextConverter) -> None:
|
18
|
+
super().__init__(syntax_registry)
|
19
|
+
self._syntax = syntax_registry.get_callout_syntax()
|
20
|
+
self._start_pattern = self._syntax.regex_pattern
|
21
|
+
self._end_pattern = self._syntax.end_regex_pattern
|
22
|
+
self._rich_text_converter = rich_text_converter
|
23
|
+
|
24
|
+
@override
|
25
|
+
def _can_handle(self, context: BlockParsingContext) -> bool:
|
26
|
+
return self._is_callout_start(context) or self._is_callout_end(context) or self._is_callout_content(context)
|
27
|
+
|
28
|
+
@override
|
29
|
+
async def _process(self, context: BlockParsingContext) -> None:
|
30
|
+
if self._is_callout_start(context):
|
31
|
+
await self._start_callout(context)
|
32
|
+
|
33
|
+
if self._is_callout_end(context):
|
34
|
+
await self._finalize_callout(context)
|
35
|
+
|
36
|
+
if self._is_callout_content(context):
|
37
|
+
self._add_callout_content(context)
|
38
|
+
|
39
|
+
def _is_callout_start(self, context: BlockParsingContext) -> bool:
|
40
|
+
return self._start_pattern.match(context.line) is not None
|
41
|
+
|
42
|
+
def _is_callout_end(self, context: BlockParsingContext) -> bool:
|
43
|
+
if not self._end_pattern.match(context.line):
|
44
|
+
return False
|
45
|
+
|
46
|
+
if not context.parent_stack:
|
47
|
+
return False
|
48
|
+
|
49
|
+
current_parent = context.parent_stack[-1]
|
50
|
+
return isinstance(current_parent.block, CreateCalloutBlock)
|
51
|
+
|
52
|
+
async def _start_callout(self, context: BlockParsingContext) -> None:
|
53
|
+
block = await self._create_callout_block(context.line)
|
54
|
+
if not block:
|
55
|
+
return
|
56
|
+
|
57
|
+
parent_context = ParentBlockContext(
|
58
|
+
block=block,
|
59
|
+
child_lines=[],
|
60
|
+
)
|
61
|
+
context.parent_stack.append(parent_context)
|
62
|
+
|
63
|
+
async def _create_callout_block(self, line: str) -> CreateCalloutBlock | None:
|
64
|
+
match = self._start_pattern.match(line)
|
65
|
+
if not match:
|
66
|
+
return None
|
67
|
+
|
68
|
+
emoji_part = match.group(1)
|
69
|
+
emoji = emoji_part.strip() if emoji_part else self.DEFAULT_EMOJI
|
70
|
+
|
71
|
+
# Create callout with empty rich_text initially
|
72
|
+
# The actual content will be added as children
|
73
|
+
callout_data = CreateCalloutData(
|
74
|
+
rich_text=[],
|
75
|
+
icon=EmojiIcon(emoji=emoji),
|
76
|
+
children=[],
|
77
|
+
)
|
78
|
+
return CreateCalloutBlock(callout=callout_data)
|
79
|
+
|
80
|
+
async def _finalize_callout(self, context: BlockParsingContext) -> None:
|
81
|
+
callout_context = context.parent_stack.pop()
|
82
|
+
await self._assign_callout_children_if_any(callout_context, context)
|
83
|
+
|
84
|
+
if self._is_nested_in_other_parent_context(context):
|
85
|
+
self._assign_to_parent_context(context, callout_context)
|
86
|
+
else:
|
87
|
+
context.result_blocks.append(callout_context.block)
|
88
|
+
|
89
|
+
def _is_nested_in_other_parent_context(self, context: BlockParsingContext) -> bool:
|
90
|
+
return bool(context.parent_stack)
|
91
|
+
|
92
|
+
def _assign_to_parent_context(self, context: BlockParsingContext, callout_context: ParentBlockContext) -> None:
|
93
|
+
parent_context = context.parent_stack[-1]
|
94
|
+
parent_context.add_child_block(callout_context.block)
|
95
|
+
|
96
|
+
async def _assign_callout_children_if_any(
|
97
|
+
self, callout_context: ParentBlockContext, context: BlockParsingContext
|
98
|
+
) -> None:
|
99
|
+
all_children = []
|
100
|
+
|
101
|
+
if callout_context.child_lines:
|
102
|
+
children_text = "\n".join(callout_context.child_lines)
|
103
|
+
text_blocks = await self._parse_nested_content(children_text, context)
|
104
|
+
all_children.extend(text_blocks)
|
105
|
+
|
106
|
+
# Add any child blocks
|
107
|
+
if callout_context.child_blocks:
|
108
|
+
all_children.extend(callout_context.child_blocks)
|
109
|
+
|
110
|
+
callout_context.block.callout.children = all_children
|
111
|
+
|
112
|
+
def _is_callout_content(self, context: BlockParsingContext) -> bool:
|
113
|
+
if not context.parent_stack:
|
114
|
+
return False
|
115
|
+
|
116
|
+
current_parent = context.parent_stack[-1]
|
117
|
+
if not isinstance(current_parent.block, CreateCalloutBlock):
|
118
|
+
return False
|
119
|
+
|
120
|
+
return not (self._start_pattern.match(context.line) or self._end_pattern.match(context.line))
|
121
|
+
|
122
|
+
def _add_callout_content(self, context: BlockParsingContext) -> None:
|
123
|
+
context.parent_stack[-1].add_child_line(context.line)
|
124
|
+
|
125
|
+
async def _parse_nested_content(self, text: str, context: BlockParsingContext) -> list:
|
126
|
+
if not text.strip():
|
127
|
+
return []
|
128
|
+
|
129
|
+
return await context.parse_nested_content(text)
|
@@ -0,0 +1,55 @@
|
|
1
|
+
from typing import override
|
2
|
+
|
3
|
+
from notionary.blocks.rich_text.markdown_rich_text_converter import (
|
4
|
+
MarkdownRichTextConverter,
|
5
|
+
)
|
6
|
+
from notionary.blocks.schemas import BlockCreatePayload
|
7
|
+
from notionary.page.content.parser.parsers.base import (
|
8
|
+
BlockParsingContext,
|
9
|
+
LineParser,
|
10
|
+
)
|
11
|
+
from notionary.page.content.syntax.service import SyntaxRegistry
|
12
|
+
|
13
|
+
|
14
|
+
class CaptionParser(LineParser):
|
15
|
+
def __init__(self, syntax_registry: SyntaxRegistry, rich_text_converter: MarkdownRichTextConverter) -> None:
|
16
|
+
super().__init__(syntax_registry)
|
17
|
+
self._syntax = syntax_registry.get_caption_syntax()
|
18
|
+
self._rich_text_converter = rich_text_converter
|
19
|
+
|
20
|
+
@override
|
21
|
+
def _can_handle(self, context: BlockParsingContext) -> bool:
|
22
|
+
if context.is_inside_parent_context():
|
23
|
+
return False
|
24
|
+
|
25
|
+
if not self._syntax.regex_pattern.match(context.line):
|
26
|
+
return False
|
27
|
+
|
28
|
+
if not context.result_blocks:
|
29
|
+
return False
|
30
|
+
|
31
|
+
previous_block = context.result_blocks[-1]
|
32
|
+
return self._block_supports_caption(previous_block)
|
33
|
+
|
34
|
+
@override
|
35
|
+
async def _process(self, context: BlockParsingContext) -> None:
|
36
|
+
caption_match = self._syntax.regex_pattern.match(context.line)
|
37
|
+
if not caption_match:
|
38
|
+
return
|
39
|
+
|
40
|
+
caption_text = caption_match.group(1)
|
41
|
+
caption_rich_text = await self._rich_text_converter.to_rich_text(caption_text)
|
42
|
+
|
43
|
+
previous_block = context.result_blocks[-1]
|
44
|
+
self._attach_caption_to_block(previous_block, caption_rich_text)
|
45
|
+
|
46
|
+
def _block_supports_caption(self, block: BlockCreatePayload) -> bool:
|
47
|
+
block_data = getattr(block, block.type.value, None)
|
48
|
+
if block_data is None:
|
49
|
+
return False
|
50
|
+
return hasattr(block_data, "caption")
|
51
|
+
|
52
|
+
def _attach_caption_to_block(self, block: BlockCreatePayload, caption_rich_text: list) -> None:
|
53
|
+
block_data = getattr(block, block.type.value)
|
54
|
+
if hasattr(block_data, "caption"):
|
55
|
+
block_data.caption = caption_rich_text
|
@@ -0,0 +1,81 @@
|
|
1
|
+
import re
|
2
|
+
from typing import override
|
3
|
+
|
4
|
+
from notionary.blocks.rich_text.markdown_rich_text_converter import MarkdownRichTextConverter
|
5
|
+
from notionary.blocks.rich_text.models import RichText
|
6
|
+
from notionary.blocks.schemas import CodeData, CodeLanguage, CreateCodeBlock
|
7
|
+
from notionary.page.content.parser.parsers.base import BlockParsingContext, LineParser
|
8
|
+
from notionary.page.content.syntax.service import SyntaxRegistry
|
9
|
+
|
10
|
+
|
11
|
+
class CodeParser(LineParser):
|
12
|
+
DEFAULT_LANGUAGE = CodeLanguage.PLAIN_TEXT
|
13
|
+
|
14
|
+
def __init__(self, syntax_registry: SyntaxRegistry, rich_text_converter: MarkdownRichTextConverter) -> None:
|
15
|
+
super().__init__(syntax_registry)
|
16
|
+
self._syntax = syntax_registry.get_code_syntax()
|
17
|
+
self._rich_text_converter = rich_text_converter
|
18
|
+
self._code_start_pattern = self._syntax.regex_pattern
|
19
|
+
self._code_end_pattern = self._syntax.end_regex_pattern or re.compile(r"^```\s*$")
|
20
|
+
|
21
|
+
@override
|
22
|
+
def _can_handle(self, context: BlockParsingContext) -> bool:
|
23
|
+
if context.is_inside_parent_context():
|
24
|
+
return False
|
25
|
+
return self._is_code_fence_start(context.line)
|
26
|
+
|
27
|
+
@override
|
28
|
+
async def _process(self, context: BlockParsingContext) -> None:
|
29
|
+
code_lines = self._collect_code_lines(context)
|
30
|
+
lines_consumed = self._count_lines_consumed(context)
|
31
|
+
|
32
|
+
block = await self._create_code_block(opening_line=context.line, code_lines=code_lines)
|
33
|
+
if not block:
|
34
|
+
return
|
35
|
+
|
36
|
+
context.lines_consumed = lines_consumed
|
37
|
+
context.result_blocks.append(block)
|
38
|
+
|
39
|
+
def _is_code_fence_start(self, line: str) -> bool:
|
40
|
+
return self._code_start_pattern.match(line) is not None
|
41
|
+
|
42
|
+
def _is_code_fence_end(self, line: str) -> bool:
|
43
|
+
return self._code_end_pattern.match(line) is not None
|
44
|
+
|
45
|
+
def _collect_code_lines(self, context: BlockParsingContext) -> list[str]:
|
46
|
+
code_lines = []
|
47
|
+
for line in context.get_remaining_lines():
|
48
|
+
if self._is_code_fence_end(line):
|
49
|
+
break
|
50
|
+
code_lines.append(line)
|
51
|
+
return code_lines
|
52
|
+
|
53
|
+
def _count_lines_consumed(self, context: BlockParsingContext) -> int:
|
54
|
+
for line_index, line in enumerate(context.get_remaining_lines()):
|
55
|
+
if self._is_code_fence_end(line):
|
56
|
+
return line_index + 1
|
57
|
+
return len(context.get_remaining_lines())
|
58
|
+
|
59
|
+
async def _create_code_block(self, opening_line: str, code_lines: list[str]) -> CreateCodeBlock | None:
|
60
|
+
match = self._code_start_pattern.match(opening_line)
|
61
|
+
if not match:
|
62
|
+
return None
|
63
|
+
|
64
|
+
language = self._parse_language(match.group(1))
|
65
|
+
rich_text = await self._create_rich_text_from_code(code_lines)
|
66
|
+
|
67
|
+
code_data = CodeData(rich_text=rich_text, language=language, caption=[])
|
68
|
+
return CreateCodeBlock(code=code_data)
|
69
|
+
|
70
|
+
def _parse_language(self, language_str: str | None) -> CodeLanguage:
|
71
|
+
return CodeLanguage.from_string(language_str, default=self.DEFAULT_LANGUAGE)
|
72
|
+
|
73
|
+
async def _create_rich_text_from_code(self, code_lines: list[str]) -> list[RichText]:
|
74
|
+
content = "\n".join(code_lines) if code_lines else ""
|
75
|
+
return await self._rich_text_converter.to_rich_text(content)
|
76
|
+
|
77
|
+
def _is_code_fence_start(self, line: str) -> bool:
|
78
|
+
return self._code_start_pattern.match(line) is not None
|
79
|
+
|
80
|
+
def _is_code_fence_end(self, line: str) -> bool:
|
81
|
+
return self._code_end_pattern.match(line) is not None
|
@@ -0,0 +1,117 @@
|
|
1
|
+
from typing import override
|
2
|
+
|
3
|
+
from notionary.blocks.schemas import CreateColumnBlock, CreateColumnData, CreateColumnListBlock
|
4
|
+
from notionary.page.content.parser.context import ParentBlockContext
|
5
|
+
from notionary.page.content.parser.parsers.base import (
|
6
|
+
BlockParsingContext,
|
7
|
+
LineParser,
|
8
|
+
)
|
9
|
+
from notionary.page.content.syntax.service import SyntaxRegistry
|
10
|
+
|
11
|
+
|
12
|
+
class ColumnParser(LineParser):
|
13
|
+
MIN_WIDTH_RATIO = 0
|
14
|
+
MAX_WIDTH_RATIO = 1.0
|
15
|
+
|
16
|
+
def __init__(self, syntax_registry: SyntaxRegistry) -> None:
|
17
|
+
super().__init__(syntax_registry)
|
18
|
+
self._syntax = syntax_registry.get_column_syntax()
|
19
|
+
|
20
|
+
@override
|
21
|
+
def _can_handle(self, context: BlockParsingContext) -> bool:
|
22
|
+
return self._is_column_start(context) or self._is_column_end(context) or self._is_column_content(context)
|
23
|
+
|
24
|
+
@override
|
25
|
+
async def _process(self, context: BlockParsingContext) -> None:
|
26
|
+
if self._is_column_start(context):
|
27
|
+
await self._start_column(context)
|
28
|
+
elif self._is_column_end(context):
|
29
|
+
await self._finalize_column(context)
|
30
|
+
elif self._is_column_content(context):
|
31
|
+
await self._add_column_content(context)
|
32
|
+
|
33
|
+
def _is_column_start(self, context: BlockParsingContext) -> bool:
|
34
|
+
return self._syntax.regex_pattern.match(context.line) is not None
|
35
|
+
|
36
|
+
def _is_column_end(self, context: BlockParsingContext) -> bool:
|
37
|
+
if not self._syntax.end_regex_pattern.match(context.line):
|
38
|
+
return False
|
39
|
+
|
40
|
+
if not context.parent_stack:
|
41
|
+
return False
|
42
|
+
|
43
|
+
current_parent = context.parent_stack[-1]
|
44
|
+
return isinstance(current_parent.block, CreateColumnBlock)
|
45
|
+
|
46
|
+
def _is_column_content(self, context: BlockParsingContext) -> bool:
|
47
|
+
if not context.parent_stack:
|
48
|
+
return False
|
49
|
+
|
50
|
+
current_parent = context.parent_stack[-1]
|
51
|
+
if not isinstance(current_parent.block, CreateColumnBlock):
|
52
|
+
return False
|
53
|
+
|
54
|
+
line = context.line.strip()
|
55
|
+
return not (self._syntax.regex_pattern.match(line) or self._syntax.end_regex_pattern.match(line))
|
56
|
+
|
57
|
+
async def _add_column_content(self, context: BlockParsingContext) -> None:
|
58
|
+
context.parent_stack[-1].add_child_line(context.line)
|
59
|
+
|
60
|
+
async def _start_column(self, context: BlockParsingContext) -> None:
|
61
|
+
block = self._create_column_block(context.line)
|
62
|
+
if not block:
|
63
|
+
return
|
64
|
+
|
65
|
+
parent_context = ParentBlockContext(
|
66
|
+
block=block,
|
67
|
+
child_lines=[],
|
68
|
+
)
|
69
|
+
context.parent_stack.append(parent_context)
|
70
|
+
|
71
|
+
def _create_column_block(self, line: str) -> CreateColumnBlock | None:
|
72
|
+
match = self._syntax.regex_pattern.match(line)
|
73
|
+
if not match:
|
74
|
+
return None
|
75
|
+
|
76
|
+
width_ratio = self._parse_width_ratio(match.group(1))
|
77
|
+
column_data = CreateColumnData(width_ratio=width_ratio)
|
78
|
+
|
79
|
+
return CreateColumnBlock(column=column_data)
|
80
|
+
|
81
|
+
def _parse_width_ratio(self, ratio_str: str | None) -> float | None:
|
82
|
+
if not ratio_str:
|
83
|
+
return None
|
84
|
+
|
85
|
+
try:
|
86
|
+
width_ratio = float(ratio_str)
|
87
|
+
return width_ratio if self.MIN_WIDTH_RATIO < width_ratio <= self.MAX_WIDTH_RATIO else None
|
88
|
+
except ValueError:
|
89
|
+
return None
|
90
|
+
|
91
|
+
async def _finalize_column(self, context: BlockParsingContext) -> None:
|
92
|
+
column_context = context.parent_stack.pop()
|
93
|
+
await self._assign_column_children(column_context, context)
|
94
|
+
|
95
|
+
if self._has_column_list_parent(context):
|
96
|
+
parent = context.parent_stack[-1]
|
97
|
+
parent.add_child_block(column_context.block)
|
98
|
+
else:
|
99
|
+
context.result_blocks.append(column_context.block)
|
100
|
+
|
101
|
+
def _has_column_list_parent(self, context: BlockParsingContext) -> bool:
|
102
|
+
if not context.parent_stack:
|
103
|
+
return False
|
104
|
+
return isinstance(context.parent_stack[-1].block, CreateColumnListBlock)
|
105
|
+
|
106
|
+
async def _assign_column_children(self, column_context: ParentBlockContext, context: BlockParsingContext) -> None:
|
107
|
+
all_children = []
|
108
|
+
|
109
|
+
if column_context.child_lines:
|
110
|
+
children_text = "\n".join(column_context.child_lines)
|
111
|
+
text_blocks = await context.parse_nested_content(children_text)
|
112
|
+
all_children.extend(text_blocks)
|
113
|
+
|
114
|
+
if column_context.child_blocks:
|
115
|
+
all_children.extend(column_context.child_blocks)
|
116
|
+
|
117
|
+
column_context.block.column.children = all_children
|