notionary 0.2.27__py3-none-any.whl → 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- notionary/__init__.py +5 -20
- notionary/blocks/__init__.py +4 -4
- notionary/blocks/client.py +90 -216
- notionary/blocks/enums.py +167 -0
- notionary/blocks/rich_text/markdown_rich_text_converter.py +280 -0
- notionary/blocks/rich_text/models.py +178 -0
- notionary/blocks/rich_text/name_id_resolver/__init__.py +13 -0
- notionary/blocks/rich_text/name_id_resolver/data_source.py +32 -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 +144 -0
- notionary/blocks/rich_text/rich_text_patterns.py +42 -0
- notionary/blocks/schemas.py +778 -0
- notionary/comments/__init__.py +1 -22
- notionary/comments/client.py +52 -187
- notionary/comments/factory.py +38 -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 +104 -0
- notionary/data_source/properties/schemas.py +402 -0
- notionary/data_source/query/builder.py +448 -0
- notionary/data_source/query/resolver.py +114 -0
- notionary/data_source/query/schema.py +302 -0
- notionary/data_source/query/validator.py +73 -0
- notionary/data_source/schema/registry.py +104 -0
- notionary/data_source/schema/service.py +136 -0
- notionary/data_source/schemas.py +27 -0
- notionary/data_source/service.py +377 -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 +168 -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 +57 -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 +204 -0
- notionary/http/models.py +50 -0
- notionary/page/blocks/client.py +1 -0
- notionary/page/content/factory.py +73 -0
- notionary/page/content/markdown/__init__.py +5 -0
- notionary/page/content/markdown/builder.py +226 -0
- notionary/page/content/markdown/nodes/__init__.py +52 -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 +41 -0
- notionary/page/content/markdown/nodes/callout.py +34 -0
- notionary/page/content/markdown/nodes/code.py +28 -0
- notionary/page/content/markdown/nodes/columns.py +69 -0
- notionary/page/content/markdown/nodes/container.py +64 -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 +36 -0
- notionary/page/content/markdown/nodes/image.py +23 -0
- notionary/page/content/markdown/nodes/mixins/__init__.py +5 -0
- notionary/page/content/markdown/nodes/mixins/caption.py +12 -0
- notionary/page/content/markdown/nodes/numbered_list.py +38 -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 +27 -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 +38 -0
- notionary/page/content/markdown/nodes/toggle.py +27 -0
- notionary/page/content/markdown/nodes/video.py +23 -0
- notionary/page/content/parser/context.py +126 -0
- notionary/page/content/parser/factory.py +210 -0
- notionary/page/content/parser/parsers/__init__.py +58 -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 +85 -0
- notionary/page/content/parser/parsers/callout.py +100 -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 +76 -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 +115 -0
- notionary/page/content/parser/parsers/image.py +42 -0
- notionary/page/content/parser/parsers/numbered_list.py +89 -0
- notionary/page/content/parser/parsers/paragraph.py +37 -0
- notionary/page/content/parser/parsers/pdf.py +42 -0
- notionary/page/content/parser/parsers/quote.py +125 -0
- notionary/page/content/parser/parsers/space.py +41 -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 +96 -0
- notionary/page/content/parser/parsers/toggle.py +70 -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 +95 -0
- notionary/page/content/parser/post_processing/handlers/rich_text_length_truncation.py +114 -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 +11 -0
- notionary/page/content/parser/pre_processsing/handlers/column_syntax.py +130 -0
- notionary/page/content/parser/pre_processsing/handlers/indentation.py +84 -0
- notionary/page/content/parser/pre_processsing/handlers/port.py +7 -0
- notionary/page/content/parser/pre_processsing/handlers/whitespace.py +73 -0
- notionary/page/content/parser/pre_processsing/service.py +15 -0
- notionary/page/content/parser/service.py +78 -0
- notionary/page/content/renderer/context.py +51 -0
- notionary/page/content/renderer/factory.py +231 -0
- notionary/page/content/renderer/post_processing/handlers/__init__.py +5 -0
- notionary/page/content/renderer/post_processing/handlers/numbered_list.py +156 -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 +55 -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 +50 -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 +53 -0
- notionary/page/content/renderer/renderers/column_list.py +44 -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 +95 -0
- notionary/page/content/renderer/renderers/image.py +31 -0
- notionary/page/content/renderer/renderers/numbered_list.py +42 -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 +52 -0
- notionary/page/content/renderer/renderers/video.py +31 -0
- notionary/page/content/renderer/service.py +50 -0
- notionary/page/content/service.py +68 -0
- notionary/page/content/syntax/__init__.py +4 -0
- notionary/page/content/syntax/grammar.py +10 -0
- notionary/page/content/syntax/models.py +66 -0
- notionary/page/content/syntax/registry.py +393 -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 +308 -0
- notionary/page/properties/service.py +261 -0
- notionary/page/schemas.py +13 -0
- notionary/page/service.py +225 -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/shared/typings.py +3 -0
- notionary/user/__init__.py +4 -8
- notionary/user/base.py +138 -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/date.py +51 -0
- notionary/utils/decorators.py +122 -0
- notionary/utils/fuzzy.py +68 -0
- notionary/utils/mixins/logging.py +58 -0
- notionary/utils/pagination.py +100 -0
- notionary/utils/uuid_utils.py +20 -0
- notionary/workspace/__init__.py +4 -0
- notionary/workspace/client.py +62 -0
- notionary/workspace/query/__init__.py +3 -0
- notionary/workspace/query/builder.py +60 -0
- notionary/workspace/query/models.py +61 -0
- notionary/workspace/query/service.py +100 -0
- notionary/workspace/schemas.py +21 -0
- notionary/workspace/service.py +116 -0
- notionary-0.3.0.dist-info/METADATA +201 -0
- notionary-0.3.0.dist-info/RECORD +209 -0
- {notionary-0.2.27.dist-info → notionary-0.3.0.dist-info}/WHEEL +1 -1
- {notionary-0.2.27.dist-info → notionary-0.3.0.dist-info/licenses}/LICENSE +9 -9
- notionary/base_notion_client.py +0 -219
- 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/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/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/logging_mixin.py +0 -59
- 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/METADATA +0 -270
- notionary-0.2.27.dist-info/RECORD +0 -202
- /notionary/{database → user}/factory.py +0 -0
|
@@ -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, CodingLanguage, CreateCodeBlock
|
|
7
|
+
from notionary.page.content.parser.parsers.base import BlockParsingContext, LineParser
|
|
8
|
+
from notionary.page.content.syntax import SyntaxRegistry
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CodeParser(LineParser):
|
|
12
|
+
DEFAULT_LANGUAGE = CodingLanguage.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) -> CodingLanguage:
|
|
71
|
+
return CodingLanguage.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,76 @@
|
|
|
1
|
+
from typing import override
|
|
2
|
+
|
|
3
|
+
from notionary.blocks.schemas import CreateColumnBlock, CreateColumnData
|
|
4
|
+
from notionary.page.content.parser.parsers.base import (
|
|
5
|
+
BlockParsingContext,
|
|
6
|
+
LineParser,
|
|
7
|
+
)
|
|
8
|
+
from notionary.page.content.syntax import SyntaxRegistry
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ColumnParser(LineParser):
|
|
12
|
+
MIN_WIDTH_RATIO = 0
|
|
13
|
+
MAX_WIDTH_RATIO = 1.0
|
|
14
|
+
|
|
15
|
+
def __init__(self, syntax_registry: SyntaxRegistry) -> None:
|
|
16
|
+
super().__init__(syntax_registry)
|
|
17
|
+
self._syntax = syntax_registry.get_column_syntax()
|
|
18
|
+
|
|
19
|
+
@override
|
|
20
|
+
def _can_handle(self, context: BlockParsingContext) -> bool:
|
|
21
|
+
return self._is_column_start(context)
|
|
22
|
+
|
|
23
|
+
@override
|
|
24
|
+
async def _process(self, context: BlockParsingContext) -> None:
|
|
25
|
+
if self._is_column_start(context):
|
|
26
|
+
await self._process_column(context)
|
|
27
|
+
|
|
28
|
+
def _is_column_start(self, context: BlockParsingContext) -> bool:
|
|
29
|
+
return self._syntax.regex_pattern.match(context.line) is not None
|
|
30
|
+
|
|
31
|
+
async def _process_column(self, context: BlockParsingContext) -> None:
|
|
32
|
+
block = self._create_column_block(context.line)
|
|
33
|
+
if not block:
|
|
34
|
+
return
|
|
35
|
+
|
|
36
|
+
await self._populate_children(block, context)
|
|
37
|
+
context.result_blocks.append(block)
|
|
38
|
+
|
|
39
|
+
def _create_column_block(self, line: str) -> CreateColumnBlock | None:
|
|
40
|
+
match = self._syntax.regex_pattern.match(line)
|
|
41
|
+
if not match:
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
width_ratio = self._parse_width_ratio(match.group(1))
|
|
45
|
+
column_data = CreateColumnData(width_ratio=width_ratio, children=[])
|
|
46
|
+
|
|
47
|
+
return CreateColumnBlock(column=column_data)
|
|
48
|
+
|
|
49
|
+
def _parse_width_ratio(self, ratio_str: str | None) -> float | None:
|
|
50
|
+
if not ratio_str:
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
width_ratio = float(ratio_str)
|
|
55
|
+
return width_ratio if self._is_valid_width_ratio(width_ratio) else None
|
|
56
|
+
except ValueError:
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
def _is_valid_width_ratio(self, width_ratio: float) -> bool:
|
|
60
|
+
return self.MIN_WIDTH_RATIO < width_ratio <= self.MAX_WIDTH_RATIO
|
|
61
|
+
|
|
62
|
+
async def _populate_children(self, block: CreateColumnBlock, context: BlockParsingContext) -> None:
|
|
63
|
+
parent_indent_level = context.get_line_indentation_level()
|
|
64
|
+
child_lines = context.collect_indented_child_lines(parent_indent_level)
|
|
65
|
+
|
|
66
|
+
if not child_lines:
|
|
67
|
+
return
|
|
68
|
+
|
|
69
|
+
child_blocks = await self._parse_indented_children(child_lines, context)
|
|
70
|
+
block.column.children = child_blocks
|
|
71
|
+
context.lines_consumed = len(child_lines)
|
|
72
|
+
|
|
73
|
+
async def _parse_indented_children(self, child_lines: list[str], context: BlockParsingContext) -> list:
|
|
74
|
+
stripped_lines = context.strip_indentation_level(child_lines, levels=1)
|
|
75
|
+
child_markdown = "\n".join(stripped_lines)
|
|
76
|
+
return await context.parse_nested_markdown(child_markdown)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from typing import override
|
|
2
|
+
|
|
3
|
+
from notionary.blocks.enums import BlockType
|
|
4
|
+
from notionary.blocks.schemas import BlockCreatePayload, CreateColumnListBlock, CreateColumnListData
|
|
5
|
+
from notionary.page.content.parser.parsers.base import (
|
|
6
|
+
BlockParsingContext,
|
|
7
|
+
LineParser,
|
|
8
|
+
)
|
|
9
|
+
from notionary.page.content.syntax import SyntaxRegistry
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ColumnListParser(LineParser):
|
|
13
|
+
def __init__(self, syntax_registry: SyntaxRegistry) -> None:
|
|
14
|
+
super().__init__(syntax_registry)
|
|
15
|
+
self._syntax = syntax_registry.get_column_list_syntax()
|
|
16
|
+
|
|
17
|
+
@override
|
|
18
|
+
def _can_handle(self, context: BlockParsingContext) -> bool:
|
|
19
|
+
return self._is_column_list_start(context)
|
|
20
|
+
|
|
21
|
+
@override
|
|
22
|
+
async def _process(self, context: BlockParsingContext) -> None:
|
|
23
|
+
if self._is_column_list_start(context):
|
|
24
|
+
await self._process_column_list(context)
|
|
25
|
+
|
|
26
|
+
def _is_column_list_start(self, context: BlockParsingContext) -> bool:
|
|
27
|
+
return self._syntax.regex_pattern.match(context.line) is not None
|
|
28
|
+
|
|
29
|
+
async def _process_column_list(self, context: BlockParsingContext) -> None:
|
|
30
|
+
block = self._create_column_list_block()
|
|
31
|
+
await self._populate_columns(block, context)
|
|
32
|
+
context.result_blocks.append(block)
|
|
33
|
+
|
|
34
|
+
def _create_column_list_block(self) -> CreateColumnListBlock:
|
|
35
|
+
column_list_data = CreateColumnListData(children=[])
|
|
36
|
+
return CreateColumnListBlock(column_list=column_list_data)
|
|
37
|
+
|
|
38
|
+
async def _populate_columns(self, block: CreateColumnListBlock, context: BlockParsingContext) -> None:
|
|
39
|
+
parent_indent_level = context.get_line_indentation_level()
|
|
40
|
+
child_lines = self._collect_children_allowing_empty_lines(context, parent_indent_level)
|
|
41
|
+
|
|
42
|
+
if not child_lines:
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
column_blocks = await self._parse_column_children(child_lines, context)
|
|
46
|
+
block.column_list.children = column_blocks
|
|
47
|
+
context.lines_consumed = len(child_lines)
|
|
48
|
+
|
|
49
|
+
async def _parse_column_children(self, child_lines: list[str], context: BlockParsingContext) -> list:
|
|
50
|
+
stripped_lines = context.strip_indentation_level(child_lines, levels=1)
|
|
51
|
+
child_markdown = "\n".join(stripped_lines)
|
|
52
|
+
parsed_blocks = await context.parse_nested_markdown(child_markdown)
|
|
53
|
+
return self._extract_column_blocks(parsed_blocks)
|
|
54
|
+
|
|
55
|
+
def _collect_children_allowing_empty_lines(
|
|
56
|
+
self, context: BlockParsingContext, parent_indent_level: int
|
|
57
|
+
) -> list[str]:
|
|
58
|
+
child_lines = []
|
|
59
|
+
expected_child_indent = parent_indent_level + 1
|
|
60
|
+
remaining_lines = context.get_remaining_lines()
|
|
61
|
+
|
|
62
|
+
for line in remaining_lines:
|
|
63
|
+
if self._should_include_as_child(line, expected_child_indent, context):
|
|
64
|
+
child_lines.append(line)
|
|
65
|
+
else:
|
|
66
|
+
break
|
|
67
|
+
|
|
68
|
+
return child_lines
|
|
69
|
+
|
|
70
|
+
def _should_include_as_child(self, line: str, expected_indent: int, context: BlockParsingContext) -> bool:
|
|
71
|
+
if not line.strip():
|
|
72
|
+
return True
|
|
73
|
+
|
|
74
|
+
line_indent = context.get_line_indentation_level(line)
|
|
75
|
+
return line_indent >= expected_indent
|
|
76
|
+
|
|
77
|
+
def _extract_column_blocks(self, blocks: list[BlockCreatePayload]) -> list:
|
|
78
|
+
return [block for block in blocks if self._is_valid_column_block(block)]
|
|
79
|
+
|
|
80
|
+
def _is_valid_column_block(self, block: BlockCreatePayload) -> bool:
|
|
81
|
+
return block.type == BlockType.COLUMN and block.column is not None
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from typing import override
|
|
2
|
+
|
|
3
|
+
from notionary.blocks.schemas import CreateDividerBlock, DividerData
|
|
4
|
+
from notionary.page.content.parser.parsers.base import (
|
|
5
|
+
BlockParsingContext,
|
|
6
|
+
LineParser,
|
|
7
|
+
)
|
|
8
|
+
from notionary.page.content.syntax import SyntaxRegistry
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DividerParser(LineParser):
|
|
12
|
+
def __init__(self, syntax_registry: SyntaxRegistry) -> None:
|
|
13
|
+
super().__init__(syntax_registry)
|
|
14
|
+
self._syntax = syntax_registry.get_divider_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_divider(context.line)
|
|
21
|
+
|
|
22
|
+
@override
|
|
23
|
+
async def _process(self, context: BlockParsingContext) -> None:
|
|
24
|
+
block = self._create_divider_block()
|
|
25
|
+
if block:
|
|
26
|
+
context.result_blocks.append(block)
|
|
27
|
+
|
|
28
|
+
def _is_divider(self, line: str) -> bool:
|
|
29
|
+
return self._syntax.regex_pattern.match(line) is not None
|
|
30
|
+
|
|
31
|
+
def _create_divider_block(self) -> CreateDividerBlock:
|
|
32
|
+
divider_data = DividerData()
|
|
33
|
+
return CreateDividerBlock(divider=divider_data)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Parser for embed blocks."""
|
|
2
|
+
|
|
3
|
+
from typing import override
|
|
4
|
+
|
|
5
|
+
from notionary.blocks.schemas import CreateEmbedBlock, EmbedData
|
|
6
|
+
from notionary.page.content.parser.parsers.base import BlockParsingContext, LineParser
|
|
7
|
+
from notionary.page.content.syntax import SyntaxRegistry
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class EmbedParser(LineParser):
|
|
11
|
+
def __init__(self, syntax_registry: SyntaxRegistry) -> None:
|
|
12
|
+
super().__init__(syntax_registry)
|
|
13
|
+
self._syntax = syntax_registry.get_embed_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
|
+
embed_data = EmbedData(url=url, caption=[])
|
|
28
|
+
block = CreateEmbedBlock(embed=embed_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,65 @@
|
|
|
1
|
+
from typing import override
|
|
2
|
+
|
|
3
|
+
from notionary.blocks.schemas import CreateEquationBlock, EquationData
|
|
4
|
+
from notionary.page.content.parser.parsers.base import (
|
|
5
|
+
BlockParsingContext,
|
|
6
|
+
LineParser,
|
|
7
|
+
)
|
|
8
|
+
from notionary.page.content.syntax import SyntaxRegistry
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class EquationParser(LineParser):
|
|
12
|
+
def __init__(self, syntax_registry: SyntaxRegistry) -> None:
|
|
13
|
+
super().__init__(syntax_registry)
|
|
14
|
+
self._syntax = syntax_registry.get_equation_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_equation_delimiter(context.line)
|
|
21
|
+
|
|
22
|
+
@override
|
|
23
|
+
async def _process(self, context: BlockParsingContext) -> None:
|
|
24
|
+
equation_content = self._collect_equation_content(context)
|
|
25
|
+
lines_consumed = self._count_lines_consumed(context)
|
|
26
|
+
|
|
27
|
+
block = self._create_equation_block(opening_line=context.line, equation_lines=equation_content)
|
|
28
|
+
|
|
29
|
+
if block:
|
|
30
|
+
context.lines_consumed = lines_consumed
|
|
31
|
+
context.result_blocks.append(block)
|
|
32
|
+
|
|
33
|
+
def _is_equation_delimiter(self, line: str) -> bool:
|
|
34
|
+
return self._syntax.regex_pattern.match(line) is not None
|
|
35
|
+
|
|
36
|
+
def _collect_equation_content(self, context: BlockParsingContext) -> list[str]:
|
|
37
|
+
content_lines = []
|
|
38
|
+
|
|
39
|
+
for line in context.get_remaining_lines():
|
|
40
|
+
if self._is_equation_delimiter(line):
|
|
41
|
+
break
|
|
42
|
+
content_lines.append(line)
|
|
43
|
+
|
|
44
|
+
return content_lines
|
|
45
|
+
|
|
46
|
+
def _count_lines_consumed(self, context: BlockParsingContext) -> int:
|
|
47
|
+
for line_index, line in enumerate(context.get_remaining_lines()):
|
|
48
|
+
if self._is_equation_delimiter(line):
|
|
49
|
+
return line_index + 1
|
|
50
|
+
|
|
51
|
+
return len(context.get_remaining_lines())
|
|
52
|
+
|
|
53
|
+
def _create_equation_block(self, opening_line: str, equation_lines: list[str]) -> CreateEquationBlock | None:
|
|
54
|
+
if opening_line.strip() != self._syntax.start_delimiter:
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
if not equation_lines:
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
expression = "\n".join(equation_lines).strip()
|
|
61
|
+
|
|
62
|
+
if expression:
|
|
63
|
+
return CreateEquationBlock(equation=EquationData(expression=expression))
|
|
64
|
+
|
|
65
|
+
return None
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Parser for file blocks."""
|
|
2
|
+
|
|
3
|
+
from typing import override
|
|
4
|
+
|
|
5
|
+
from notionary.blocks.schemas import (
|
|
6
|
+
CreateFileBlock,
|
|
7
|
+
ExternalFile,
|
|
8
|
+
FileData,
|
|
9
|
+
FileType,
|
|
10
|
+
)
|
|
11
|
+
from notionary.page.content.parser.parsers.base import BlockParsingContext, LineParser
|
|
12
|
+
from notionary.page.content.syntax import SyntaxRegistry
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class FileParser(LineParser):
|
|
16
|
+
def __init__(self, syntax_registry: SyntaxRegistry) -> None:
|
|
17
|
+
super().__init__(syntax_registry)
|
|
18
|
+
self._syntax = syntax_registry.get_file_syntax()
|
|
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.search(context.line) is not None
|
|
25
|
+
|
|
26
|
+
@override
|
|
27
|
+
async def _process(self, context: BlockParsingContext) -> None:
|
|
28
|
+
url = self._extract_url(context.line)
|
|
29
|
+
if not url:
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
file_data = FileData(
|
|
33
|
+
type=FileType.EXTERNAL,
|
|
34
|
+
external=ExternalFile(url=url),
|
|
35
|
+
caption=[],
|
|
36
|
+
)
|
|
37
|
+
block = CreateFileBlock(file=file_data)
|
|
38
|
+
context.result_blocks.append(block)
|
|
39
|
+
|
|
40
|
+
def _extract_url(self, line: str) -> str | None:
|
|
41
|
+
match = self._syntax.regex_pattern.search(line)
|
|
42
|
+
return match.group(1).strip() if match else None
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
from typing import override
|
|
2
|
+
|
|
3
|
+
from notionary.blocks.rich_text.markdown_rich_text_converter import MarkdownRichTextConverter
|
|
4
|
+
from notionary.blocks.schemas import (
|
|
5
|
+
BlockColor,
|
|
6
|
+
BlockCreatePayload,
|
|
7
|
+
BlockType,
|
|
8
|
+
CreateHeading1Block,
|
|
9
|
+
CreateHeading2Block,
|
|
10
|
+
CreateHeading3Block,
|
|
11
|
+
CreateHeadingBlock,
|
|
12
|
+
CreateHeadingData,
|
|
13
|
+
)
|
|
14
|
+
from notionary.page.content.parser.parsers.base import (
|
|
15
|
+
BlockParsingContext,
|
|
16
|
+
LineParser,
|
|
17
|
+
)
|
|
18
|
+
from notionary.page.content.syntax import SyntaxRegistry
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class HeadingParser(LineParser):
|
|
22
|
+
MIN_HEADING_LEVEL = 1
|
|
23
|
+
MAX_HEADING_LEVEL = 3
|
|
24
|
+
|
|
25
|
+
def __init__(self, syntax_registry: SyntaxRegistry, rich_text_converter: MarkdownRichTextConverter) -> None:
|
|
26
|
+
super().__init__(syntax_registry)
|
|
27
|
+
self._syntax = syntax_registry.get_heading_syntax()
|
|
28
|
+
self._rich_text_converter = rich_text_converter
|
|
29
|
+
|
|
30
|
+
@override
|
|
31
|
+
def _can_handle(self, context: BlockParsingContext) -> bool:
|
|
32
|
+
if context.is_inside_parent_context():
|
|
33
|
+
return False
|
|
34
|
+
return self._syntax.regex_pattern.match(context.line) is not None
|
|
35
|
+
|
|
36
|
+
@override
|
|
37
|
+
async def _process(self, context: BlockParsingContext) -> None:
|
|
38
|
+
block = await self._create_heading_block(context.line)
|
|
39
|
+
if not block:
|
|
40
|
+
return
|
|
41
|
+
|
|
42
|
+
await self._process_nested_children(block, context)
|
|
43
|
+
context.result_blocks.append(block)
|
|
44
|
+
|
|
45
|
+
async def _process_nested_children(self, block: CreateHeadingBlock, context: BlockParsingContext) -> None:
|
|
46
|
+
parent_indent_level = context.get_line_indentation_level()
|
|
47
|
+
child_lines = context.collect_indented_child_lines(parent_indent_level)
|
|
48
|
+
|
|
49
|
+
if not child_lines:
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
child_lines = self._remove_trailing_empty_lines(child_lines)
|
|
53
|
+
|
|
54
|
+
if not child_lines:
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
self._set_heading_toggleable(block, True)
|
|
58
|
+
|
|
59
|
+
stripped_lines = context.strip_indentation_level(child_lines, levels=1)
|
|
60
|
+
child_markdown = "\n".join(stripped_lines)
|
|
61
|
+
|
|
62
|
+
child_blocks = await context.parse_nested_markdown(child_markdown)
|
|
63
|
+
self._set_heading_children(block, child_blocks)
|
|
64
|
+
|
|
65
|
+
context.lines_consumed = len(child_lines)
|
|
66
|
+
|
|
67
|
+
def _set_heading_toggleable(self, block: CreateHeadingBlock, is_toggleable: bool) -> None:
|
|
68
|
+
if block.type == BlockType.HEADING_1:
|
|
69
|
+
block.heading_1.is_toggleable = is_toggleable
|
|
70
|
+
elif block.type == BlockType.HEADING_2:
|
|
71
|
+
block.heading_2.is_toggleable = is_toggleable
|
|
72
|
+
elif block.type == BlockType.HEADING_3:
|
|
73
|
+
block.heading_3.is_toggleable = is_toggleable
|
|
74
|
+
|
|
75
|
+
def _set_heading_children(self, block: CreateHeadingBlock, children: list[BlockCreatePayload]) -> None:
|
|
76
|
+
if block.type == BlockType.HEADING_1:
|
|
77
|
+
block.heading_1.children = children
|
|
78
|
+
elif block.type == BlockType.HEADING_2:
|
|
79
|
+
block.heading_2.children = children
|
|
80
|
+
elif block.type == BlockType.HEADING_3:
|
|
81
|
+
block.heading_3.children = children
|
|
82
|
+
|
|
83
|
+
def _remove_trailing_empty_lines(self, lines: list[str]) -> list[str]:
|
|
84
|
+
while lines and not lines[-1].strip():
|
|
85
|
+
lines.pop()
|
|
86
|
+
return lines
|
|
87
|
+
|
|
88
|
+
async def _create_heading_block(self, line: str) -> CreateHeadingBlock | None:
|
|
89
|
+
match = self._syntax.regex_pattern.match(line)
|
|
90
|
+
if not match:
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
level = len(match.group(1))
|
|
94
|
+
content = match.group(2).strip()
|
|
95
|
+
|
|
96
|
+
if not self._is_valid_heading(level, content):
|
|
97
|
+
return None
|
|
98
|
+
|
|
99
|
+
heading_data = await self._build_heading_data(content)
|
|
100
|
+
return self._create_heading_block_by_level(level, heading_data)
|
|
101
|
+
|
|
102
|
+
def _is_valid_heading(self, level: int, content: str) -> bool:
|
|
103
|
+
return self.MIN_HEADING_LEVEL <= level <= self.MAX_HEADING_LEVEL and bool(content)
|
|
104
|
+
|
|
105
|
+
async def _build_heading_data(self, content: str) -> CreateHeadingData:
|
|
106
|
+
rich_text = await self._rich_text_converter.to_rich_text(content)
|
|
107
|
+
return CreateHeadingData(rich_text=rich_text, color=BlockColor.DEFAULT, is_toggleable=False, children=[])
|
|
108
|
+
|
|
109
|
+
def _create_heading_block_by_level(self, level: int, heading_data: CreateHeadingData) -> CreateHeadingBlock:
|
|
110
|
+
if level == 1:
|
|
111
|
+
return CreateHeading1Block(heading_1=heading_data)
|
|
112
|
+
elif level == 2:
|
|
113
|
+
return CreateHeading2Block(heading_2=heading_data)
|
|
114
|
+
else:
|
|
115
|
+
return CreateHeading3Block(heading_3=heading_data)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Parser for image blocks."""
|
|
2
|
+
|
|
3
|
+
from typing import override
|
|
4
|
+
|
|
5
|
+
from notionary.blocks.schemas import (
|
|
6
|
+
CreateImageBlock,
|
|
7
|
+
ExternalFile,
|
|
8
|
+
FileData,
|
|
9
|
+
FileType,
|
|
10
|
+
)
|
|
11
|
+
from notionary.page.content.parser.parsers.base import BlockParsingContext, LineParser
|
|
12
|
+
from notionary.page.content.syntax import SyntaxRegistry
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ImageParser(LineParser):
|
|
16
|
+
def __init__(self, syntax_registry: SyntaxRegistry) -> None:
|
|
17
|
+
super().__init__(syntax_registry)
|
|
18
|
+
self._syntax = syntax_registry.get_image_syntax()
|
|
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.search(context.line) is not None
|
|
25
|
+
|
|
26
|
+
@override
|
|
27
|
+
async def _process(self, context: BlockParsingContext) -> None:
|
|
28
|
+
url = self._extract_url(context.line)
|
|
29
|
+
if not url:
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
image_data = FileData(
|
|
33
|
+
type=FileType.EXTERNAL,
|
|
34
|
+
external=ExternalFile(url=url),
|
|
35
|
+
caption=[],
|
|
36
|
+
)
|
|
37
|
+
block = CreateImageBlock(image=image_data)
|
|
38
|
+
context.result_blocks.append(block)
|
|
39
|
+
|
|
40
|
+
def _extract_url(self, line: str) -> str | None:
|
|
41
|
+
match = self._syntax.regex_pattern.search(line)
|
|
42
|
+
return match.group(1).strip() if match else None
|
|
@@ -0,0 +1,89 @@
|
|
|
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 (
|
|
7
|
+
BlockColor,
|
|
8
|
+
CreateNumberedListItemBlock,
|
|
9
|
+
CreateNumberedListItemData,
|
|
10
|
+
)
|
|
11
|
+
from notionary.page.content.parser.parsers.base import (
|
|
12
|
+
BlockParsingContext,
|
|
13
|
+
LineParser,
|
|
14
|
+
)
|
|
15
|
+
from notionary.page.content.syntax import SyntaxRegistry
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class NumberedListParser(LineParser):
|
|
19
|
+
def __init__(self, syntax_registry: SyntaxRegistry, rich_text_converter: MarkdownRichTextConverter) -> None:
|
|
20
|
+
super().__init__(syntax_registry)
|
|
21
|
+
self._syntax = syntax_registry.get_numbered_list_syntax()
|
|
22
|
+
self._rich_text_converter = rich_text_converter
|
|
23
|
+
|
|
24
|
+
@override
|
|
25
|
+
def _can_handle(self, context: BlockParsingContext) -> bool:
|
|
26
|
+
if context.is_inside_parent_context():
|
|
27
|
+
return False
|
|
28
|
+
return self._is_numbered_list_line(context.line)
|
|
29
|
+
|
|
30
|
+
def _is_numbered_list_line(self, line: str) -> bool:
|
|
31
|
+
return self._syntax.regex_pattern.match(line) is not None
|
|
32
|
+
|
|
33
|
+
@override
|
|
34
|
+
async def _process(self, context: BlockParsingContext) -> None:
|
|
35
|
+
block = await self._create_numbered_list_block(context.line)
|
|
36
|
+
if not block:
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
await self._process_nested_children(block, context)
|
|
40
|
+
context.result_blocks.append(block)
|
|
41
|
+
|
|
42
|
+
async def _process_nested_children(self, block: CreateNumberedListItemBlock, context: BlockParsingContext) -> None:
|
|
43
|
+
child_lines = self._collect_child_lines(context)
|
|
44
|
+
if not child_lines:
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
child_blocks = await self._parse_child_blocks(child_lines, context)
|
|
48
|
+
if child_blocks:
|
|
49
|
+
block.numbered_list_item.children = child_blocks
|
|
50
|
+
|
|
51
|
+
context.lines_consumed = len(child_lines)
|
|
52
|
+
|
|
53
|
+
def _collect_child_lines(self, context: BlockParsingContext) -> list[str]:
|
|
54
|
+
parent_indent_level = context.get_line_indentation_level()
|
|
55
|
+
return context.collect_indented_child_lines(parent_indent_level)
|
|
56
|
+
|
|
57
|
+
async def _parse_child_blocks(
|
|
58
|
+
self, child_lines: list[str], context: BlockParsingContext
|
|
59
|
+
) -> list[CreateNumberedListItemBlock]:
|
|
60
|
+
stripped_lines = self._remove_parent_indentation(child_lines, context)
|
|
61
|
+
children_text = self._convert_lines_to_text(stripped_lines)
|
|
62
|
+
return await context.parse_nested_markdown(children_text)
|
|
63
|
+
|
|
64
|
+
def _remove_parent_indentation(self, lines: list[str], context: BlockParsingContext) -> list[str]:
|
|
65
|
+
return context.strip_indentation_level(lines, levels=1)
|
|
66
|
+
|
|
67
|
+
def _convert_lines_to_text(self, lines: list[str]) -> str:
|
|
68
|
+
return "\n".join(lines)
|
|
69
|
+
|
|
70
|
+
async def _create_numbered_list_block(self, text: str) -> CreateNumberedListItemBlock | None:
|
|
71
|
+
content = self._extract_list_content(text)
|
|
72
|
+
if content is None:
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
rich_text = await self._convert_to_rich_text(content)
|
|
76
|
+
return self._build_block(rich_text)
|
|
77
|
+
|
|
78
|
+
def _extract_list_content(self, text: str) -> str | None:
|
|
79
|
+
match = self._syntax.regex_pattern.match(text)
|
|
80
|
+
if not match:
|
|
81
|
+
return None
|
|
82
|
+
return match.group(3)
|
|
83
|
+
|
|
84
|
+
async def _convert_to_rich_text(self, content: str):
|
|
85
|
+
return await self._rich_text_converter.to_rich_text(content)
|
|
86
|
+
|
|
87
|
+
def _build_block(self, rich_text) -> CreateNumberedListItemBlock:
|
|
88
|
+
numbered_list_content = CreateNumberedListItemData(rich_text=rich_text, color=BlockColor.DEFAULT)
|
|
89
|
+
return CreateNumberedListItemBlock(numbered_list_item=numbered_list_content)
|