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,37 @@
|
|
|
1
|
+
from typing import override
|
|
2
|
+
|
|
3
|
+
from notionary.blocks.enums import BlockColor
|
|
4
|
+
from notionary.blocks.rich_text.markdown_rich_text_converter import (
|
|
5
|
+
MarkdownRichTextConverter,
|
|
6
|
+
)
|
|
7
|
+
from notionary.blocks.schemas import CreateParagraphBlock, CreateParagraphData
|
|
8
|
+
from notionary.page.content.parser.parsers.base import (
|
|
9
|
+
BlockParsingContext,
|
|
10
|
+
LineParser,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ParagraphParser(LineParser):
|
|
15
|
+
def __init__(self, rich_text_converter: MarkdownRichTextConverter) -> None:
|
|
16
|
+
super().__init__()
|
|
17
|
+
self._rich_text_converter = rich_text_converter
|
|
18
|
+
|
|
19
|
+
@override
|
|
20
|
+
def _can_handle(self, context: BlockParsingContext) -> bool:
|
|
21
|
+
if context.is_inside_parent_context():
|
|
22
|
+
return False
|
|
23
|
+
return bool(context.line)
|
|
24
|
+
|
|
25
|
+
@override
|
|
26
|
+
async def _process(self, context: BlockParsingContext) -> None:
|
|
27
|
+
block = await self._create_paragraph_block(context.line)
|
|
28
|
+
if block:
|
|
29
|
+
context.result_blocks.append(block)
|
|
30
|
+
|
|
31
|
+
async def _create_paragraph_block(self, text: str) -> CreateParagraphBlock | None:
|
|
32
|
+
if not text:
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
rich_text = await self._rich_text_converter.to_rich_text(text)
|
|
36
|
+
paragraph_content = CreateParagraphData(rich_text=rich_text, color=BlockColor.DEFAULT)
|
|
37
|
+
return CreateParagraphBlock(paragraph=paragraph_content)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Parser for PDF blocks."""
|
|
2
|
+
|
|
3
|
+
from typing import override
|
|
4
|
+
|
|
5
|
+
from notionary.blocks.schemas import (
|
|
6
|
+
CreatePdfBlock,
|
|
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 PdfParser(LineParser):
|
|
16
|
+
def __init__(self, syntax_registry: SyntaxRegistry) -> None:
|
|
17
|
+
super().__init__(syntax_registry)
|
|
18
|
+
self._syntax = syntax_registry.get_pdf_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
|
+
pdf_data = FileData(
|
|
33
|
+
type=FileType.EXTERNAL,
|
|
34
|
+
external=ExternalFile(url=url),
|
|
35
|
+
caption=[],
|
|
36
|
+
)
|
|
37
|
+
block = CreatePdfBlock(pdf=pdf_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,125 @@
|
|
|
1
|
+
from typing import override
|
|
2
|
+
|
|
3
|
+
from notionary.blocks.rich_text.markdown_rich_text_converter import MarkdownRichTextConverter
|
|
4
|
+
from notionary.blocks.schemas import BlockColor, CreateQuoteBlock, CreateQuoteData
|
|
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 QuoteParser(LineParser):
|
|
13
|
+
def __init__(self, syntax_registry: SyntaxRegistry, rich_text_converter: MarkdownRichTextConverter) -> None:
|
|
14
|
+
super().__init__(syntax_registry)
|
|
15
|
+
self._syntax = syntax_registry.get_quote_syntax()
|
|
16
|
+
self._rich_text_converter = rich_text_converter
|
|
17
|
+
|
|
18
|
+
@override
|
|
19
|
+
def _can_handle(self, context: BlockParsingContext) -> bool:
|
|
20
|
+
if context.is_inside_parent_context():
|
|
21
|
+
return False
|
|
22
|
+
return self._is_quote(context.line)
|
|
23
|
+
|
|
24
|
+
def _is_quote(self, line: str) -> bool:
|
|
25
|
+
return self._syntax.regex_pattern.match(line) is not None
|
|
26
|
+
|
|
27
|
+
@override
|
|
28
|
+
async def _process(self, context: BlockParsingContext) -> None:
|
|
29
|
+
quote_lines = self._collect_quote_lines(context)
|
|
30
|
+
|
|
31
|
+
block = await self._create_quote_block(quote_lines)
|
|
32
|
+
if not block:
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
# Lines consumed: all quote lines minus the current line (which is already being processed)
|
|
36
|
+
context.lines_consumed = len(quote_lines) - 1
|
|
37
|
+
|
|
38
|
+
await self._process_nested_children(block, context, quote_lines)
|
|
39
|
+
context.result_blocks.append(block)
|
|
40
|
+
|
|
41
|
+
def _collect_quote_lines(self, context: BlockParsingContext) -> list[str]:
|
|
42
|
+
quote_lines = [context.line]
|
|
43
|
+
for line in context.get_remaining_lines():
|
|
44
|
+
if not self._is_quote(line):
|
|
45
|
+
break
|
|
46
|
+
quote_lines.append(line)
|
|
47
|
+
return quote_lines
|
|
48
|
+
|
|
49
|
+
async def _process_nested_children(
|
|
50
|
+
self, block: CreateQuoteBlock, context: BlockParsingContext, quote_lines: list[str]
|
|
51
|
+
) -> None:
|
|
52
|
+
# Calculate indent level after all quote lines
|
|
53
|
+
last_quote_line_index = len(quote_lines) - 1
|
|
54
|
+
child_lines = self._collect_child_lines_after_quote(context, last_quote_line_index)
|
|
55
|
+
|
|
56
|
+
if not child_lines:
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
child_blocks = await self._parse_child_blocks(child_lines, context)
|
|
60
|
+
if child_blocks:
|
|
61
|
+
block.quote.children = child_blocks
|
|
62
|
+
|
|
63
|
+
context.lines_consumed += len(child_lines)
|
|
64
|
+
|
|
65
|
+
def _collect_child_lines_after_quote(self, context: BlockParsingContext, last_quote_index: int) -> list[str]:
|
|
66
|
+
"""Collect indented children after the quote block."""
|
|
67
|
+
parent_indent_level = context.get_line_indentation_level()
|
|
68
|
+
remaining_lines = context.get_remaining_lines()
|
|
69
|
+
|
|
70
|
+
# Skip the quote lines we already processed
|
|
71
|
+
lines_after_quote = remaining_lines[last_quote_index:]
|
|
72
|
+
|
|
73
|
+
child_lines = []
|
|
74
|
+
expected_child_indent = parent_indent_level + 1
|
|
75
|
+
|
|
76
|
+
for line in lines_after_quote:
|
|
77
|
+
if not line.strip():
|
|
78
|
+
child_lines.append(line)
|
|
79
|
+
continue
|
|
80
|
+
|
|
81
|
+
line_indent = context.get_line_indentation_level(line)
|
|
82
|
+
if line_indent >= expected_child_indent:
|
|
83
|
+
child_lines.append(line)
|
|
84
|
+
else:
|
|
85
|
+
break
|
|
86
|
+
|
|
87
|
+
return child_lines
|
|
88
|
+
|
|
89
|
+
async def _parse_child_blocks(self, child_lines: list[str], context: BlockParsingContext) -> list[CreateQuoteBlock]:
|
|
90
|
+
stripped_lines = self._remove_parent_indentation(child_lines, context)
|
|
91
|
+
children_text = self._convert_lines_to_text(stripped_lines)
|
|
92
|
+
return await context.parse_nested_markdown(children_text)
|
|
93
|
+
|
|
94
|
+
def _remove_parent_indentation(self, lines: list[str], context: BlockParsingContext) -> list[str]:
|
|
95
|
+
return context.strip_indentation_level(lines, levels=1)
|
|
96
|
+
|
|
97
|
+
def _convert_lines_to_text(self, lines: list[str]) -> str:
|
|
98
|
+
return "\n".join(lines)
|
|
99
|
+
|
|
100
|
+
async def _create_quote_block(self, quote_lines: list[str]) -> CreateQuoteBlock | None:
|
|
101
|
+
contents = self._extract_quote_contents(quote_lines)
|
|
102
|
+
if not contents:
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
content = self._join_contents_for_multiline_quote(contents)
|
|
106
|
+
rich_text = await self._convert_to_rich_text(content)
|
|
107
|
+
return self._build_block(rich_text)
|
|
108
|
+
|
|
109
|
+
def _extract_quote_contents(self, quote_lines: list[str]) -> list[str]:
|
|
110
|
+
contents = []
|
|
111
|
+
for line in quote_lines:
|
|
112
|
+
match = self._syntax.regex_pattern.match(line)
|
|
113
|
+
if match:
|
|
114
|
+
contents.append(match.group(1).strip())
|
|
115
|
+
return contents
|
|
116
|
+
|
|
117
|
+
def _join_contents_for_multiline_quote(self, contents: list[str]) -> str:
|
|
118
|
+
return "\n".join(contents)
|
|
119
|
+
|
|
120
|
+
async def _convert_to_rich_text(self, content: str):
|
|
121
|
+
return await self._rich_text_converter.to_rich_text(content)
|
|
122
|
+
|
|
123
|
+
def _build_block(self, rich_text) -> CreateQuoteBlock:
|
|
124
|
+
quote_data = CreateQuoteData(rich_text=rich_text, color=BlockColor.DEFAULT)
|
|
125
|
+
return CreateQuoteBlock(quote=quote_data)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from typing import override
|
|
2
|
+
|
|
3
|
+
from notionary.blocks.enums import BlockColor
|
|
4
|
+
from notionary.blocks.schemas import CreateParagraphBlock, CreateParagraphData
|
|
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 SpaceParser(LineParser):
|
|
13
|
+
def __init__(self, syntax_registry: SyntaxRegistry) -> None:
|
|
14
|
+
super().__init__(syntax_registry)
|
|
15
|
+
self._syntax = syntax_registry.get_space_syntax()
|
|
16
|
+
|
|
17
|
+
@override
|
|
18
|
+
def _can_handle(self, context: BlockParsingContext) -> bool:
|
|
19
|
+
if context.is_inside_parent_context():
|
|
20
|
+
return False
|
|
21
|
+
|
|
22
|
+
if self._is_explicit_space_marker(context):
|
|
23
|
+
return True
|
|
24
|
+
|
|
25
|
+
return self._is_second_consecutive_empty_line(context)
|
|
26
|
+
|
|
27
|
+
def _is_explicit_space_marker(self, context: BlockParsingContext) -> bool:
|
|
28
|
+
return self._syntax.regex_pattern.match(context.line.strip()) is not None
|
|
29
|
+
|
|
30
|
+
def _is_second_consecutive_empty_line(self, context: BlockParsingContext) -> bool:
|
|
31
|
+
return context.line.strip() == "" and context.is_previous_line_empty
|
|
32
|
+
|
|
33
|
+
@override
|
|
34
|
+
async def _process(self, context: BlockParsingContext) -> None:
|
|
35
|
+
block = self._create_space_block()
|
|
36
|
+
if block:
|
|
37
|
+
context.result_blocks.append(block)
|
|
38
|
+
|
|
39
|
+
def _create_space_block(self) -> CreateParagraphBlock:
|
|
40
|
+
paragraph_data = CreateParagraphData(rich_text=[], color=BlockColor.DEFAULT)
|
|
41
|
+
return CreateParagraphBlock(paragraph=paragraph_data)
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
from typing import override
|
|
2
|
+
|
|
3
|
+
from notionary.blocks.rich_text.markdown_rich_text_converter import MarkdownRichTextConverter
|
|
4
|
+
from notionary.blocks.rich_text.models import RichText
|
|
5
|
+
from notionary.blocks.schemas import CreateTableBlock, CreateTableData, CreateTableRowBlock, TableRowData
|
|
6
|
+
from notionary.page.content.parser.parsers import BlockParsingContext, LineParser
|
|
7
|
+
from notionary.page.content.syntax import SyntaxRegistry
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TableParser(LineParser):
|
|
11
|
+
def __init__(self, syntax_registry: SyntaxRegistry, rich_text_converter: MarkdownRichTextConverter) -> None:
|
|
12
|
+
super().__init__(syntax_registry)
|
|
13
|
+
self._syntax = syntax_registry.get_table_syntax()
|
|
14
|
+
self._separator_syntax = syntax_registry.get_table_row_syntax()
|
|
15
|
+
self.rich_text_converter = rich_text_converter
|
|
16
|
+
|
|
17
|
+
@override
|
|
18
|
+
def _can_handle(self, context: BlockParsingContext) -> bool:
|
|
19
|
+
if context.is_inside_parent_context():
|
|
20
|
+
return False
|
|
21
|
+
return self._is_table_start(context)
|
|
22
|
+
|
|
23
|
+
@override
|
|
24
|
+
async def _process(self, context: BlockParsingContext) -> None:
|
|
25
|
+
if not self._is_table_start(context):
|
|
26
|
+
return
|
|
27
|
+
|
|
28
|
+
await self._process_complete_table(context)
|
|
29
|
+
|
|
30
|
+
def _is_table_start(self, context: BlockParsingContext) -> bool:
|
|
31
|
+
return self._syntax.regex_pattern.match(context.line) is not None
|
|
32
|
+
|
|
33
|
+
async def _process_complete_table(self, context: BlockParsingContext) -> None:
|
|
34
|
+
table_lines = [context.line]
|
|
35
|
+
remaining_lines = context.get_remaining_lines()
|
|
36
|
+
lines_consumed = self._collect_table_lines(table_lines, remaining_lines)
|
|
37
|
+
|
|
38
|
+
block = await self._create_table_block(table_lines)
|
|
39
|
+
|
|
40
|
+
if block:
|
|
41
|
+
context.lines_consumed = lines_consumed
|
|
42
|
+
context.result_blocks.append(block)
|
|
43
|
+
|
|
44
|
+
def _collect_table_lines(self, table_lines: list[str], remaining_lines: list[str]) -> int:
|
|
45
|
+
lines_consumed = 0
|
|
46
|
+
|
|
47
|
+
for index, line in enumerate(remaining_lines):
|
|
48
|
+
line_stripped = line.strip()
|
|
49
|
+
|
|
50
|
+
if not line_stripped:
|
|
51
|
+
table_lines.append(line)
|
|
52
|
+
continue
|
|
53
|
+
|
|
54
|
+
if self._is_table_line(line_stripped):
|
|
55
|
+
table_lines.append(line)
|
|
56
|
+
else:
|
|
57
|
+
lines_consumed = index
|
|
58
|
+
break
|
|
59
|
+
else:
|
|
60
|
+
lines_consumed = len(remaining_lines)
|
|
61
|
+
|
|
62
|
+
return lines_consumed
|
|
63
|
+
|
|
64
|
+
def _is_table_line(self, line: str) -> bool:
|
|
65
|
+
return self._syntax.regex_pattern.match(line) or self._separator_syntax.regex_pattern.match(line)
|
|
66
|
+
|
|
67
|
+
async def _create_table_block(self, table_lines: list[str]) -> CreateTableBlock | None:
|
|
68
|
+
if not table_lines:
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
first_row = self._find_first_table_row(table_lines)
|
|
72
|
+
if not first_row:
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
header_cells = self._parse_table_row(first_row)
|
|
76
|
+
column_count = len(header_cells)
|
|
77
|
+
|
|
78
|
+
table_rows, has_separator = await self._process_table_rows(table_lines)
|
|
79
|
+
|
|
80
|
+
table_data = CreateTableData(
|
|
81
|
+
table_width=column_count,
|
|
82
|
+
has_column_header=has_separator,
|
|
83
|
+
has_row_header=False,
|
|
84
|
+
children=table_rows,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
return CreateTableBlock(table=table_data)
|
|
88
|
+
|
|
89
|
+
def _find_first_table_row(self, table_lines: list[str]) -> str | None:
|
|
90
|
+
for line in table_lines:
|
|
91
|
+
line_stripped = line.strip()
|
|
92
|
+
if line_stripped and self._syntax.regex_pattern.match(line_stripped):
|
|
93
|
+
return line_stripped
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
async def _process_table_rows(self, table_lines: list[str]) -> tuple[list[CreateTableRowBlock], bool]:
|
|
97
|
+
table_rows = []
|
|
98
|
+
has_separator = False
|
|
99
|
+
|
|
100
|
+
for line in table_lines:
|
|
101
|
+
line_stripped = line.strip()
|
|
102
|
+
|
|
103
|
+
if not line_stripped:
|
|
104
|
+
continue
|
|
105
|
+
|
|
106
|
+
if self._is_separator_line(line_stripped):
|
|
107
|
+
has_separator = True
|
|
108
|
+
continue
|
|
109
|
+
|
|
110
|
+
if self._syntax.regex_pattern.match(line_stripped):
|
|
111
|
+
table_row = await self._create_table_row(line_stripped)
|
|
112
|
+
table_rows.append(table_row)
|
|
113
|
+
|
|
114
|
+
return table_rows, has_separator
|
|
115
|
+
|
|
116
|
+
def _is_separator_line(self, line: str) -> bool:
|
|
117
|
+
return self._separator_syntax.regex_pattern.match(line) is not None
|
|
118
|
+
|
|
119
|
+
async def _create_table_row(self, line: str) -> CreateTableRowBlock:
|
|
120
|
+
cells = self._parse_table_row(line)
|
|
121
|
+
rich_text_cells = await self._convert_cells_to_rich_text(cells)
|
|
122
|
+
table_row_data = TableRowData(cells=rich_text_cells)
|
|
123
|
+
return CreateTableRowBlock(table_row=table_row_data)
|
|
124
|
+
|
|
125
|
+
async def _convert_cells_to_rich_text(self, cells: list[str]) -> list[list[RichText]]:
|
|
126
|
+
rich_text_cells = []
|
|
127
|
+
|
|
128
|
+
for cell in cells:
|
|
129
|
+
rich_text = await self.rich_text_converter.to_rich_text(cell)
|
|
130
|
+
rich_text_cells.append(rich_text)
|
|
131
|
+
|
|
132
|
+
return rich_text_cells
|
|
133
|
+
|
|
134
|
+
def _parse_table_row(self, row_text: str) -> list[str]:
|
|
135
|
+
"""Parse a table row by splitting on the table delimiter from SyntaxRegistry."""
|
|
136
|
+
row_content = row_text.strip()
|
|
137
|
+
delimiter = self._syntax.start_delimiter
|
|
138
|
+
|
|
139
|
+
if row_content.startswith(delimiter):
|
|
140
|
+
row_content = row_content[1:]
|
|
141
|
+
if row_content.endswith(delimiter):
|
|
142
|
+
row_content = row_content[:-1]
|
|
143
|
+
|
|
144
|
+
return row_content.split(delimiter)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from typing import override
|
|
2
|
+
|
|
3
|
+
from notionary.blocks.schemas import BlockColor, CreateTableOfContentsBlock, TableOfContentsData
|
|
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 TableOfContentsParser(LineParser):
|
|
12
|
+
def __init__(self, syntax_registry: SyntaxRegistry) -> None:
|
|
13
|
+
super().__init__(syntax_registry)
|
|
14
|
+
self._syntax = syntax_registry.get_table_of_contents_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_toc(context.line)
|
|
21
|
+
|
|
22
|
+
@override
|
|
23
|
+
async def _process(self, context: BlockParsingContext) -> None:
|
|
24
|
+
block = self._create_toc_block()
|
|
25
|
+
context.result_blocks.append(block)
|
|
26
|
+
|
|
27
|
+
def _is_toc(self, line: str) -> bool:
|
|
28
|
+
return self._syntax.regex_pattern.match(line) is not None
|
|
29
|
+
|
|
30
|
+
def _create_toc_block(self) -> CreateTableOfContentsBlock:
|
|
31
|
+
toc_data = TableOfContentsData(color=BlockColor.DEFAULT)
|
|
32
|
+
return CreateTableOfContentsBlock(table_of_contents=toc_data)
|
|
@@ -0,0 +1,96 @@
|
|
|
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 BlockColor, CreateToDoBlock, CreateToDoData
|
|
7
|
+
from notionary.page.content.parser.parsers.base import (
|
|
8
|
+
BlockParsingContext,
|
|
9
|
+
LineParser,
|
|
10
|
+
)
|
|
11
|
+
from notionary.page.content.syntax import SyntaxRegistry
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TodoParser(LineParser):
|
|
15
|
+
def __init__(self, syntax_registry: SyntaxRegistry, rich_text_converter: MarkdownRichTextConverter) -> None:
|
|
16
|
+
super().__init__(syntax_registry)
|
|
17
|
+
self._syntax = syntax_registry.get_todo_syntax()
|
|
18
|
+
self._syntax_done = syntax_registry.get_todo_done_syntax()
|
|
19
|
+
self._rich_text_converter = rich_text_converter
|
|
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_todo_line(context.line)
|
|
26
|
+
|
|
27
|
+
def _is_todo_line(self, line: str) -> bool:
|
|
28
|
+
return (
|
|
29
|
+
self._syntax.regex_pattern.match(line) is not None
|
|
30
|
+
or self._syntax_done.regex_pattern.match(line) is not None
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
@override
|
|
34
|
+
async def _process(self, context: BlockParsingContext) -> None:
|
|
35
|
+
block = await self._create_todo_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: CreateToDoBlock, 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.to_do.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(self, child_lines: list[str], context: BlockParsingContext) -> list[CreateToDoBlock]:
|
|
58
|
+
stripped_lines = self._remove_parent_indentation(child_lines, context)
|
|
59
|
+
children_text = self._convert_lines_to_text(stripped_lines)
|
|
60
|
+
return await context.parse_nested_markdown(children_text)
|
|
61
|
+
|
|
62
|
+
def _remove_parent_indentation(self, lines: list[str], context: BlockParsingContext) -> list[str]:
|
|
63
|
+
return context.strip_indentation_level(lines, levels=1)
|
|
64
|
+
|
|
65
|
+
def _convert_lines_to_text(self, lines: list[str]) -> str:
|
|
66
|
+
return "\n".join(lines)
|
|
67
|
+
|
|
68
|
+
async def _create_todo_block(self, text: str) -> CreateToDoBlock | None:
|
|
69
|
+
content, checked = self._extract_todo_content(text)
|
|
70
|
+
if content is None:
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
rich_text = await self._convert_to_rich_text(content)
|
|
74
|
+
return self._build_block(rich_text, checked)
|
|
75
|
+
|
|
76
|
+
def _extract_todo_content(self, text: str) -> tuple[str | None, bool]:
|
|
77
|
+
done_match = self._syntax_done.regex_pattern.match(text)
|
|
78
|
+
if done_match:
|
|
79
|
+
return done_match.group(1), True
|
|
80
|
+
|
|
81
|
+
todo_match = self._syntax.regex_pattern.match(text)
|
|
82
|
+
if todo_match:
|
|
83
|
+
return todo_match.group(1), False
|
|
84
|
+
|
|
85
|
+
return None, False
|
|
86
|
+
|
|
87
|
+
async def _convert_to_rich_text(self, content: str):
|
|
88
|
+
return await self._rich_text_converter.to_rich_text(content)
|
|
89
|
+
|
|
90
|
+
def _build_block(self, rich_text, checked: bool) -> CreateToDoBlock:
|
|
91
|
+
todo_content = CreateToDoData(
|
|
92
|
+
rich_text=rich_text,
|
|
93
|
+
checked=checked,
|
|
94
|
+
color=BlockColor.DEFAULT,
|
|
95
|
+
)
|
|
96
|
+
return CreateToDoBlock(to_do=todo_content)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from typing import override
|
|
2
|
+
|
|
3
|
+
from notionary.blocks.rich_text.markdown_rich_text_converter import MarkdownRichTextConverter
|
|
4
|
+
from notionary.blocks.schemas import BlockColor, CreateToggleBlock, CreateToggleData
|
|
5
|
+
from notionary.page.content.parser.parsers import (
|
|
6
|
+
BlockParsingContext,
|
|
7
|
+
LineParser,
|
|
8
|
+
)
|
|
9
|
+
from notionary.page.content.syntax import SyntaxRegistry
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ToggleParser(LineParser):
|
|
13
|
+
def __init__(self, syntax_registry: SyntaxRegistry, rich_text_converter: MarkdownRichTextConverter) -> None:
|
|
14
|
+
super().__init__(syntax_registry)
|
|
15
|
+
self._syntax = syntax_registry.get_toggle_syntax()
|
|
16
|
+
self._heading_syntax = syntax_registry.get_toggleable_heading_syntax()
|
|
17
|
+
self._rich_text_converter = rich_text_converter
|
|
18
|
+
|
|
19
|
+
@override
|
|
20
|
+
def _can_handle(self, context: BlockParsingContext) -> bool:
|
|
21
|
+
return self._is_toggle_start(context)
|
|
22
|
+
|
|
23
|
+
@override
|
|
24
|
+
async def _process(self, context: BlockParsingContext) -> None:
|
|
25
|
+
if self._is_toggle_start(context):
|
|
26
|
+
await self._process_toggle(context)
|
|
27
|
+
|
|
28
|
+
def _is_toggle_start(self, context: BlockParsingContext) -> bool:
|
|
29
|
+
if not self._syntax.regex_pattern.match(context.line):
|
|
30
|
+
return False
|
|
31
|
+
|
|
32
|
+
# Exclude toggleable heading patterns to be more resilient to wrong order of chain
|
|
33
|
+
return not self.is_heading_start(context.line)
|
|
34
|
+
|
|
35
|
+
def is_heading_start(self, line: str) -> bool:
|
|
36
|
+
return self._heading_syntax.regex_pattern.match(line) is not None
|
|
37
|
+
|
|
38
|
+
async def _process_toggle(self, context: BlockParsingContext) -> None:
|
|
39
|
+
block = await self._create_toggle_block(context.line)
|
|
40
|
+
if not block:
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
await self._process_nested_children(block, context)
|
|
44
|
+
|
|
45
|
+
context.result_blocks.append(block)
|
|
46
|
+
|
|
47
|
+
async def _create_toggle_block(self, line: str) -> CreateToggleBlock | None:
|
|
48
|
+
if not (match := self._syntax.regex_pattern.match(line)):
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
title = match.group(1).strip()
|
|
52
|
+
rich_text = await self._rich_text_converter.to_rich_text(title)
|
|
53
|
+
|
|
54
|
+
toggle_content = CreateToggleData(rich_text=rich_text, color=BlockColor.DEFAULT, children=[])
|
|
55
|
+
return CreateToggleBlock(toggle=toggle_content)
|
|
56
|
+
|
|
57
|
+
async def _process_nested_children(self, block: CreateToggleBlock, context: BlockParsingContext) -> None:
|
|
58
|
+
parent_indent_level = context.get_line_indentation_level()
|
|
59
|
+
child_lines = context.collect_indented_child_lines(parent_indent_level)
|
|
60
|
+
|
|
61
|
+
if not child_lines:
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
stripped_lines = context.strip_indentation_level(child_lines, levels=1)
|
|
65
|
+
child_markdown = "\n".join(stripped_lines)
|
|
66
|
+
|
|
67
|
+
child_blocks = await context.parse_nested_markdown(child_markdown)
|
|
68
|
+
block.toggle.children = child_blocks
|
|
69
|
+
|
|
70
|
+
context.lines_consumed = len(child_lines)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Parser for video blocks."""
|
|
2
|
+
|
|
3
|
+
from typing import override
|
|
4
|
+
|
|
5
|
+
from notionary.blocks.schemas import (
|
|
6
|
+
CreateVideoBlock,
|
|
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 VideoParser(LineParser):
|
|
16
|
+
def __init__(self, syntax_registry: SyntaxRegistry) -> None:
|
|
17
|
+
super().__init__(syntax_registry)
|
|
18
|
+
self._syntax = syntax_registry.get_video_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
|
+
video_data = FileData(
|
|
33
|
+
type=FileType.EXTERNAL,
|
|
34
|
+
external=ExternalFile(url=url),
|
|
35
|
+
caption=[],
|
|
36
|
+
)
|
|
37
|
+
block = CreateVideoBlock(video=video_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
|