notionary 0.2.26__py3-none-any.whl → 0.2.28__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- notionary/__init__.py +5 -20
- notionary/blocks/client.py +87 -215
- notionary/blocks/enums.py +167 -0
- notionary/blocks/rich_text/markdown_rich_text_converter.py +266 -0
- notionary/blocks/rich_text/models.py +164 -0
- notionary/blocks/rich_text/name_id_resolver/__init__.py +11 -0
- notionary/blocks/rich_text/name_id_resolver/database.py +31 -0
- notionary/blocks/rich_text/name_id_resolver/page.py +34 -0
- notionary/blocks/rich_text/name_id_resolver/person.py +37 -0
- notionary/blocks/rich_text/name_id_resolver/port.py +11 -0
- notionary/blocks/rich_text/rich_text_markdown_converter.py +132 -0
- notionary/blocks/rich_text/rich_text_patterns.py +39 -0
- notionary/blocks/schemas.py +746 -0
- notionary/comments/client.py +52 -187
- notionary/comments/factory.py +40 -0
- notionary/comments/models.py +5 -127
- notionary/comments/schemas.py +240 -0
- notionary/comments/service.py +34 -0
- notionary/data_source/http/client.py +11 -0
- notionary/data_source/http/data_source_instance_client.py +94 -0
- notionary/data_source/properties/models.py +406 -0
- notionary/data_source/query/builder.py +429 -0
- notionary/data_source/query/resolver.py +114 -0
- notionary/data_source/query/schema.py +304 -0
- notionary/data_source/query/validator.py +73 -0
- notionary/data_source/schemas.py +27 -0
- notionary/data_source/service.py +353 -0
- notionary/database/client.py +30 -135
- notionary/database/database_metadata_update_client.py +19 -0
- notionary/database/schemas.py +29 -0
- notionary/database/service.py +169 -0
- notionary/exceptions/__init__.py +33 -0
- notionary/exceptions/api.py +41 -0
- notionary/exceptions/base.py +2 -0
- notionary/exceptions/block_parsing.py +16 -0
- notionary/exceptions/data_source/__init__.py +6 -0
- notionary/exceptions/data_source/builder.py +182 -0
- notionary/exceptions/data_source/properties.py +34 -0
- notionary/exceptions/properties.py +58 -0
- notionary/exceptions/search.py +33 -0
- notionary/file_upload/client.py +18 -30
- notionary/file_upload/models.py +7 -8
- notionary/file_upload/{notion_file_upload.py → service.py} +29 -64
- notionary/http/client.py +205 -0
- notionary/http/models.py +49 -0
- notionary/page/blocks/client.py +1 -0
- notionary/page/content/factory.py +68 -0
- notionary/page/content/markdown/__init__.py +5 -0
- notionary/page/content/markdown/builder.py +304 -0
- notionary/page/content/markdown/nodes/__init__.py +54 -0
- notionary/page/content/markdown/nodes/audio.py +23 -0
- notionary/page/content/markdown/nodes/base.py +12 -0
- notionary/page/content/markdown/nodes/bookmark.py +25 -0
- notionary/page/content/markdown/nodes/breadcrumb.py +14 -0
- notionary/page/content/markdown/nodes/bulleted_list.py +18 -0
- notionary/page/content/markdown/nodes/callout.py +32 -0
- notionary/page/content/markdown/nodes/code.py +30 -0
- notionary/page/content/markdown/nodes/columns.py +51 -0
- notionary/page/content/markdown/nodes/divider.py +14 -0
- notionary/page/content/markdown/nodes/embed.py +23 -0
- notionary/page/content/markdown/nodes/equation.py +19 -0
- notionary/page/content/markdown/nodes/file.py +23 -0
- notionary/page/content/markdown/nodes/heading.py +16 -0
- notionary/page/content/markdown/nodes/image.py +23 -0
- notionary/page/content/markdown/nodes/mixins/caption.py +12 -0
- notionary/page/content/markdown/nodes/numbered_list.py +15 -0
- notionary/page/content/markdown/nodes/paragraph.py +14 -0
- notionary/page/content/markdown/nodes/pdf.py +23 -0
- notionary/page/content/markdown/nodes/quote.py +15 -0
- notionary/page/content/markdown/nodes/space.py +14 -0
- notionary/page/content/markdown/nodes/table.py +45 -0
- notionary/page/content/markdown/nodes/table_of_contents.py +14 -0
- notionary/page/content/markdown/nodes/todo.py +22 -0
- notionary/page/content/markdown/nodes/toggle.py +28 -0
- notionary/page/content/markdown/nodes/toggleable_heading.py +35 -0
- notionary/page/content/markdown/nodes/video.py +23 -0
- notionary/page/content/parser/context.py +49 -0
- notionary/page/content/parser/factory.py +219 -0
- notionary/page/content/parser/parsers/__init__.py +60 -0
- notionary/page/content/parser/parsers/audio.py +40 -0
- notionary/page/content/parser/parsers/base.py +30 -0
- notionary/page/content/parser/parsers/bookmark.py +33 -0
- notionary/page/content/parser/parsers/breadcrumb.py +33 -0
- notionary/page/content/parser/parsers/bulleted_list.py +41 -0
- notionary/page/content/parser/parsers/callout.py +129 -0
- notionary/page/content/parser/parsers/caption.py +55 -0
- notionary/page/content/parser/parsers/code.py +81 -0
- notionary/page/content/parser/parsers/column.py +117 -0
- notionary/page/content/parser/parsers/column_list.py +81 -0
- notionary/page/content/parser/parsers/divider.py +33 -0
- notionary/page/content/parser/parsers/embed.py +33 -0
- notionary/page/content/parser/parsers/equation.py +65 -0
- notionary/page/content/parser/parsers/file.py +42 -0
- notionary/page/content/parser/parsers/heading.py +58 -0
- notionary/page/content/parser/parsers/image.py +42 -0
- notionary/page/content/parser/parsers/numbered_list.py +45 -0
- notionary/page/content/parser/parsers/paragraph.py +36 -0
- notionary/page/content/parser/parsers/pdf.py +42 -0
- notionary/page/content/parser/parsers/quote.py +65 -0
- notionary/page/content/parser/parsers/space.py +35 -0
- notionary/page/content/parser/parsers/table.py +144 -0
- notionary/page/content/parser/parsers/table_of_contents.py +32 -0
- notionary/page/content/parser/parsers/todo.py +58 -0
- notionary/page/content/parser/parsers/toggle.py +127 -0
- notionary/page/content/parser/parsers/toggleable_heading.py +150 -0
- notionary/page/content/parser/parsers/video.py +42 -0
- notionary/page/content/parser/post_processing/handlers/__init__.py +5 -0
- notionary/page/content/parser/post_processing/handlers/rich_text_length.py +93 -0
- notionary/page/content/parser/post_processing/handlers/rich_text_length_truncation.py +93 -0
- notionary/page/content/parser/post_processing/port.py +9 -0
- notionary/page/content/parser/post_processing/service.py +16 -0
- notionary/page/content/parser/pre_processsing/handlers/__init__.py +9 -0
- notionary/page/content/parser/pre_processsing/handlers/column_syntax.py +80 -0
- notionary/page/content/parser/pre_processsing/handlers/port.py +7 -0
- notionary/page/content/parser/pre_processsing/handlers/whitespace.py +68 -0
- notionary/page/content/parser/pre_processsing/service.py +15 -0
- notionary/page/content/parser/service.py +69 -0
- notionary/page/content/renderer/context.py +48 -0
- notionary/page/content/renderer/factory.py +240 -0
- notionary/page/content/renderer/post_processing/handlers/__init__.py +5 -0
- notionary/page/content/renderer/post_processing/handlers/numbered_list_placeholdere.py +62 -0
- notionary/page/content/renderer/post_processing/port.py +7 -0
- notionary/page/content/renderer/post_processing/service.py +15 -0
- notionary/page/content/renderer/renderers/__init__.py +57 -0
- notionary/page/content/renderer/renderers/audio.py +31 -0
- notionary/page/content/renderer/renderers/base.py +31 -0
- notionary/page/content/renderer/renderers/bookmark.py +25 -0
- notionary/page/content/renderer/renderers/breadcrumb.py +21 -0
- notionary/page/content/renderer/renderers/bulleted_list.py +48 -0
- notionary/page/content/renderer/renderers/callout.py +65 -0
- notionary/page/content/renderer/renderers/captioned_block.py +58 -0
- notionary/page/content/renderer/renderers/code.py +34 -0
- notionary/page/content/renderer/renderers/column.py +44 -0
- notionary/page/content/renderer/renderers/column_list.py +31 -0
- notionary/page/content/renderer/renderers/divider.py +22 -0
- notionary/page/content/renderer/renderers/embed.py +25 -0
- notionary/page/content/renderer/renderers/equation.py +37 -0
- notionary/page/content/renderer/renderers/fallback.py +24 -0
- notionary/page/content/renderer/renderers/file.py +40 -0
- notionary/page/content/renderer/renderers/heading.py +69 -0
- notionary/page/content/renderer/renderers/image.py +31 -0
- notionary/page/content/renderer/renderers/numbered_list.py +41 -0
- notionary/page/content/renderer/renderers/paragraph.py +40 -0
- notionary/page/content/renderer/renderers/pdf.py +31 -0
- notionary/page/content/renderer/renderers/quote.py +49 -0
- notionary/page/content/renderer/renderers/table.py +115 -0
- notionary/page/content/renderer/renderers/table_of_contents.py +26 -0
- notionary/page/content/renderer/renderers/table_row.py +17 -0
- notionary/page/content/renderer/renderers/todo.py +56 -0
- notionary/page/content/renderer/renderers/toggle.py +53 -0
- notionary/page/content/renderer/renderers/toggleable_heading.py +78 -0
- notionary/page/content/renderer/renderers/video.py +31 -0
- notionary/page/content/renderer/service.py +50 -0
- notionary/page/content/service.py +65 -0
- notionary/page/content/syntax/models.py +68 -0
- notionary/page/content/syntax/service.py +453 -0
- notionary/page/page_context.py +7 -16
- notionary/page/page_http_client.py +15 -0
- notionary/page/page_metadata_update_client.py +19 -0
- notionary/page/properties/client.py +144 -0
- notionary/page/properties/factory.py +26 -0
- notionary/page/properties/models.py +307 -0
- notionary/page/properties/service.py +257 -0
- notionary/page/schemas.py +13 -0
- notionary/page/service.py +222 -0
- notionary/shared/entity/client.py +29 -0
- notionary/shared/entity/dto_parsers.py +53 -0
- notionary/shared/entity/entity_metadata_update_client.py +41 -0
- notionary/shared/entity/schemas.py +45 -0
- notionary/shared/entity/service.py +171 -0
- notionary/shared/models/cover.py +20 -0
- notionary/shared/models/file.py +21 -0
- notionary/shared/models/icon.py +28 -0
- notionary/shared/models/parent.py +41 -0
- notionary/shared/properties/type.py +30 -0
- notionary/user/__init__.py +4 -8
- notionary/user/base.py +89 -0
- notionary/user/bot.py +70 -0
- notionary/user/client.py +22 -111
- notionary/user/person.py +41 -0
- notionary/user/schemas.py +67 -0
- notionary/user/service.py +65 -0
- notionary/utils/async_retry.py +39 -0
- notionary/utils/date.py +51 -0
- notionary/utils/fuzzy.py +56 -0
- notionary/{util/logging_mixin.py → utils/mixins/logging.py} +4 -16
- notionary/utils/pagination.py +50 -0
- notionary/utils/singleton.py +13 -0
- notionary/utils/uuid_utils.py +20 -0
- notionary/workspace/__init__.py +3 -0
- notionary/workspace/client.py +62 -0
- notionary/workspace/query/builder.py +60 -0
- notionary/workspace/query/models.py +60 -0
- notionary/workspace/query/service.py +93 -0
- notionary/workspace/schemas.py +21 -0
- notionary/workspace/service.py +116 -0
- {notionary-0.2.26.dist-info → notionary-0.2.28.dist-info}/METADATA +54 -49
- notionary-0.2.28.dist-info/RECORD +200 -0
- {notionary-0.2.26.dist-info → notionary-0.2.28.dist-info}/WHEEL +1 -1
- {notionary-0.2.26.dist-info → notionary-0.2.28.dist-info/licenses}/LICENSE +9 -9
- notionary/base_notion_client.py +0 -219
- notionary/blocks/__init__.py +0 -5
- notionary/blocks/_bootstrap.py +0 -271
- notionary/blocks/audio/__init__.py +0 -11
- notionary/blocks/audio/audio_element.py +0 -158
- notionary/blocks/audio/audio_markdown_node.py +0 -24
- notionary/blocks/audio/audio_models.py +0 -10
- notionary/blocks/base_block_element.py +0 -42
- notionary/blocks/bookmark/__init__.py +0 -12
- notionary/blocks/bookmark/bookmark_element.py +0 -83
- notionary/blocks/bookmark/bookmark_markdown_node.py +0 -28
- notionary/blocks/bookmark/bookmark_models.py +0 -15
- notionary/blocks/breadcrumbs/__init__.py +0 -15
- notionary/blocks/breadcrumbs/breadcrumb_element.py +0 -39
- notionary/blocks/breadcrumbs/breadcrumb_markdown_node.py +0 -13
- notionary/blocks/breadcrumbs/breadcrumb_models.py +0 -12
- notionary/blocks/bulleted_list/__init__.py +0 -15
- notionary/blocks/bulleted_list/bulleted_list_element.py +0 -74
- notionary/blocks/bulleted_list/bulleted_list_markdown_node.py +0 -20
- notionary/blocks/bulleted_list/bulleted_list_models.py +0 -17
- notionary/blocks/callout/__init__.py +0 -12
- notionary/blocks/callout/callout_element.py +0 -99
- notionary/blocks/callout/callout_markdown_node.py +0 -19
- notionary/blocks/callout/callout_models.py +0 -33
- notionary/blocks/child_database/__init__.py +0 -14
- notionary/blocks/child_database/child_database_element.py +0 -59
- notionary/blocks/child_database/child_database_models.py +0 -12
- notionary/blocks/child_page/__init__.py +0 -9
- notionary/blocks/child_page/child_page_element.py +0 -94
- notionary/blocks/child_page/child_page_models.py +0 -12
- notionary/blocks/code/__init__.py +0 -11
- notionary/blocks/code/code_element.py +0 -149
- notionary/blocks/code/code_markdown_node.py +0 -80
- notionary/blocks/code/code_models.py +0 -94
- notionary/blocks/column/__init__.py +0 -25
- notionary/blocks/column/column_element.py +0 -65
- notionary/blocks/column/column_list_element.py +0 -52
- notionary/blocks/column/column_list_markdown_node.py +0 -34
- notionary/blocks/column/column_markdown_node.py +0 -42
- notionary/blocks/column/column_models.py +0 -26
- notionary/blocks/divider/__init__.py +0 -12
- notionary/blocks/divider/divider_element.py +0 -41
- notionary/blocks/divider/divider_markdown_node.py +0 -11
- notionary/blocks/divider/divider_models.py +0 -12
- notionary/blocks/embed/__init__.py +0 -12
- notionary/blocks/embed/embed_element.py +0 -98
- notionary/blocks/embed/embed_markdown_node.py +0 -19
- notionary/blocks/embed/embed_models.py +0 -14
- notionary/blocks/equation/__init__.py +0 -13
- notionary/blocks/equation/equation_element.py +0 -133
- notionary/blocks/equation/equation_element_markdown_node.py +0 -23
- notionary/blocks/equation/equation_models.py +0 -11
- notionary/blocks/file/__init__.py +0 -23
- notionary/blocks/file/file_element.py +0 -133
- notionary/blocks/file/file_element_markdown_node.py +0 -24
- notionary/blocks/file/file_element_models.py +0 -39
- notionary/blocks/heading/__init__.py +0 -19
- notionary/blocks/heading/heading_element.py +0 -112
- notionary/blocks/heading/heading_markdown_node.py +0 -16
- notionary/blocks/heading/heading_models.py +0 -29
- notionary/blocks/image_block/__init__.py +0 -11
- notionary/blocks/image_block/image_element.py +0 -130
- notionary/blocks/image_block/image_markdown_node.py +0 -25
- notionary/blocks/image_block/image_models.py +0 -10
- notionary/blocks/markdown/markdown_builder.py +0 -525
- notionary/blocks/markdown/markdown_document_model.py +0 -0
- notionary/blocks/markdown/markdown_node.py +0 -25
- notionary/blocks/mixins/captions/__init__.py +0 -4
- notionary/blocks/mixins/captions/caption_markdown_node_mixin.py +0 -31
- notionary/blocks/mixins/captions/caption_mixin.py +0 -92
- notionary/blocks/mixins/file_upload/__init__.py +0 -3
- notionary/blocks/mixins/file_upload/file_upload_mixin.py +0 -320
- notionary/blocks/models.py +0 -174
- notionary/blocks/numbered_list/__init__.py +0 -16
- notionary/blocks/numbered_list/numbered_list_element.py +0 -65
- notionary/blocks/numbered_list/numbered_list_markdown_node.py +0 -17
- notionary/blocks/numbered_list/numbered_list_models.py +0 -17
- notionary/blocks/paragraph/__init__.py +0 -15
- notionary/blocks/paragraph/paragraph_element.py +0 -58
- notionary/blocks/paragraph/paragraph_markdown_node.py +0 -16
- notionary/blocks/paragraph/paragraph_models.py +0 -16
- notionary/blocks/pdf/__init__.py +0 -11
- notionary/blocks/pdf/pdf_element.py +0 -146
- notionary/blocks/pdf/pdf_markdown_node.py +0 -24
- notionary/blocks/pdf/pdf_models.py +0 -11
- notionary/blocks/quote/__init__.py +0 -14
- notionary/blocks/quote/quote_element.py +0 -75
- notionary/blocks/quote/quote_markdown_node.py +0 -16
- notionary/blocks/quote/quote_models.py +0 -18
- notionary/blocks/registry/__init__.py +0 -3
- notionary/blocks/registry/block_registry.py +0 -150
- notionary/blocks/rich_text/__init__.py +0 -33
- notionary/blocks/rich_text/rich_text_models.py +0 -221
- notionary/blocks/rich_text/text_inline_formatter.py +0 -456
- notionary/blocks/syntax_prompt_builder.py +0 -137
- notionary/blocks/table/__init__.py +0 -19
- notionary/blocks/table/table_element.py +0 -225
- notionary/blocks/table/table_markdown_node.py +0 -42
- notionary/blocks/table/table_models.py +0 -28
- notionary/blocks/table_of_contents/__init__.py +0 -17
- notionary/blocks/table_of_contents/table_of_contents_element.py +0 -80
- notionary/blocks/table_of_contents/table_of_contents_markdown_node.py +0 -21
- notionary/blocks/table_of_contents/table_of_contents_models.py +0 -18
- notionary/blocks/todo/__init__.py +0 -12
- notionary/blocks/todo/todo_element.py +0 -81
- notionary/blocks/todo/todo_markdown_node.py +0 -21
- notionary/blocks/todo/todo_models.py +0 -18
- notionary/blocks/toggle/__init__.py +0 -12
- notionary/blocks/toggle/toggle_element.py +0 -112
- notionary/blocks/toggle/toggle_markdown_node.py +0 -31
- notionary/blocks/toggle/toggle_models.py +0 -17
- notionary/blocks/toggleable_heading/__init__.py +0 -11
- notionary/blocks/toggleable_heading/toggleable_heading_element.py +0 -115
- notionary/blocks/toggleable_heading/toggleable_heading_markdown_node.py +0 -34
- notionary/blocks/types.py +0 -130
- notionary/blocks/video/__init__.py +0 -11
- notionary/blocks/video/video_element.py +0 -187
- notionary/blocks/video/video_element_models.py +0 -10
- notionary/blocks/video/video_markdown_node.py +0 -26
- notionary/comments/__init__.py +0 -26
- notionary/database/__init__.py +0 -4
- notionary/database/database.py +0 -480
- notionary/database/database_filter_builder.py +0 -173
- notionary/database/database_provider.py +0 -227
- notionary/database/exceptions.py +0 -13
- notionary/database/factory.py +0 -0
- notionary/database/models.py +0 -337
- notionary/database/notion_database.py +0 -487
- notionary/file_upload/__init__.py +0 -7
- notionary/page/client.py +0 -124
- notionary/page/markdown_whitespace_processor.py +0 -129
- notionary/page/models.py +0 -322
- notionary/page/notion_page.py +0 -674
- notionary/page/page_content_deleting_service.py +0 -117
- notionary/page/page_content_writer.py +0 -80
- notionary/page/property_formatter.py +0 -99
- notionary/page/reader/handler/__init__.py +0 -19
- notionary/page/reader/handler/base_block_renderer.py +0 -44
- notionary/page/reader/handler/block_processing_context.py +0 -35
- notionary/page/reader/handler/block_rendering_context.py +0 -48
- notionary/page/reader/handler/column_list_renderer.py +0 -51
- notionary/page/reader/handler/column_renderer.py +0 -60
- notionary/page/reader/handler/equation_renderer.py +0 -0
- notionary/page/reader/handler/line_renderer.py +0 -73
- notionary/page/reader/handler/numbered_list_renderer.py +0 -85
- notionary/page/reader/handler/toggle_renderer.py +0 -69
- notionary/page/reader/handler/toggleable_heading_renderer.py +0 -89
- notionary/page/reader/page_content_retriever.py +0 -81
- notionary/page/search_filter_builder.py +0 -132
- notionary/page/utils.py +0 -60
- notionary/page/writer/handler/__init__.py +0 -24
- notionary/page/writer/handler/code_handler.py +0 -72
- notionary/page/writer/handler/column_handler.py +0 -141
- notionary/page/writer/handler/column_list_handler.py +0 -139
- notionary/page/writer/handler/equation_handler.py +0 -74
- notionary/page/writer/handler/line_handler.py +0 -35
- notionary/page/writer/handler/line_processing_context.py +0 -54
- notionary/page/writer/handler/regular_line_handler.py +0 -86
- notionary/page/writer/handler/table_handler.py +0 -66
- notionary/page/writer/handler/toggle_handler.py +0 -159
- notionary/page/writer/handler/toggleable_heading_handler.py +0 -174
- notionary/page/writer/markdown_to_notion_converter.py +0 -139
- notionary/page/writer/markdown_to_notion_converter_context.py +0 -30
- notionary/page/writer/markdown_to_notion_text_length_post_processor.py +0 -0
- notionary/page/writer/notion_text_length_processor.py +0 -150
- notionary/schemas/__init__.py +0 -3
- notionary/schemas/base.py +0 -73
- notionary/shared/__init__.py +0 -3
- notionary/shared/name_to_id_resolver.py +0 -203
- notionary/telemetry/__init__.py +0 -19
- notionary/telemetry/service.py +0 -136
- notionary/telemetry/views.py +0 -73
- notionary/user/base_notion_user.py +0 -53
- notionary/user/models.py +0 -84
- notionary/user/notion_bot_user.py +0 -226
- notionary/user/notion_user.py +0 -255
- notionary/user/notion_user_manager.py +0 -101
- notionary/util/__init__.py +0 -15
- notionary/util/concurrency_limiter.py +0 -0
- notionary/util/factory_decorator.py +0 -0
- notionary/util/factory_only.py +0 -37
- notionary/util/fuzzy.py +0 -75
- notionary/util/page_id_utils.py +0 -27
- notionary/util/singleton.py +0 -18
- notionary/util/singleton_metaclass.py +0 -22
- notionary/workspace.py +0 -105
- notionary-0.2.26.dist-info/RECORD +0 -202
@@ -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.service 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.service 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,58 @@
|
|
1
|
+
"""Parser for todo/checkbox blocks."""
|
2
|
+
|
3
|
+
from typing import override
|
4
|
+
|
5
|
+
from notionary.blocks.rich_text.markdown_rich_text_converter import (
|
6
|
+
MarkdownRichTextConverter,
|
7
|
+
)
|
8
|
+
from notionary.blocks.schemas import BlockColor, CreateToDoBlock, ToDoData
|
9
|
+
from notionary.page.content.parser.parsers.base import (
|
10
|
+
BlockParsingContext,
|
11
|
+
LineParser,
|
12
|
+
)
|
13
|
+
from notionary.page.content.syntax.service import SyntaxRegistry
|
14
|
+
|
15
|
+
|
16
|
+
class TodoParser(LineParser):
|
17
|
+
def __init__(self, syntax_registry: SyntaxRegistry, rich_text_converter: MarkdownRichTextConverter) -> None:
|
18
|
+
super().__init__(syntax_registry)
|
19
|
+
self._syntax = syntax_registry.get_todo_syntax()
|
20
|
+
self._syntax_done = syntax_registry.get_todo_done_syntax()
|
21
|
+
self._rich_text_converter = rich_text_converter
|
22
|
+
|
23
|
+
@override
|
24
|
+
def _can_handle(self, context: BlockParsingContext) -> bool:
|
25
|
+
if context.is_inside_parent_context():
|
26
|
+
return False
|
27
|
+
|
28
|
+
return (
|
29
|
+
self._syntax.regex_pattern.match(context.line) is not None
|
30
|
+
or self._syntax_done.regex_pattern.match(context.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 block:
|
37
|
+
context.result_blocks.append(block)
|
38
|
+
|
39
|
+
async def _create_todo_block(self, text: str) -> CreateToDoBlock | None:
|
40
|
+
done_match = self._syntax_done.regex_pattern.match(text)
|
41
|
+
todo_match = None if done_match else self._syntax.regex_pattern.match(text)
|
42
|
+
|
43
|
+
if done_match:
|
44
|
+
content = done_match.group(1)
|
45
|
+
checked = True
|
46
|
+
elif todo_match:
|
47
|
+
content = todo_match.group(1)
|
48
|
+
checked = False
|
49
|
+
else:
|
50
|
+
return None
|
51
|
+
|
52
|
+
rich_text = await self._rich_text_converter.to_rich_text(content)
|
53
|
+
todo_content = ToDoData(
|
54
|
+
rich_text=rich_text,
|
55
|
+
checked=checked,
|
56
|
+
color=BlockColor.DEFAULT,
|
57
|
+
)
|
58
|
+
return CreateToDoBlock(to_do=todo_content)
|
@@ -0,0 +1,127 @@
|
|
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
|
+
ParentBlockContext,
|
9
|
+
)
|
10
|
+
from notionary.page.content.syntax.service import SyntaxRegistry
|
11
|
+
|
12
|
+
|
13
|
+
class ToggleParser(LineParser):
|
14
|
+
def __init__(self, syntax_registry: SyntaxRegistry, rich_text_converter: MarkdownRichTextConverter) -> None:
|
15
|
+
super().__init__(syntax_registry)
|
16
|
+
self._syntax = syntax_registry.get_toggle_syntax()
|
17
|
+
self._heading_syntax = syntax_registry.get_toggleable_heading_syntax()
|
18
|
+
self._rich_text_converter = rich_text_converter
|
19
|
+
|
20
|
+
@override
|
21
|
+
def _can_handle(self, context: BlockParsingContext) -> bool:
|
22
|
+
return self._is_toggle_start(context) or self._is_toggle_end(context) or self._is_toggle_content(context)
|
23
|
+
|
24
|
+
@override
|
25
|
+
async def _process(self, context: BlockParsingContext) -> None:
|
26
|
+
if self._is_toggle_start(context):
|
27
|
+
await self._start_toggle(context)
|
28
|
+
|
29
|
+
if self._is_toggle_end(context):
|
30
|
+
await self._finalize_toggle(context)
|
31
|
+
|
32
|
+
if self._is_toggle_content(context):
|
33
|
+
self._add_toggle_content(context)
|
34
|
+
|
35
|
+
def _is_toggle_start(self, context: BlockParsingContext) -> bool:
|
36
|
+
if not self._syntax.regex_pattern.match(context.line):
|
37
|
+
return False
|
38
|
+
|
39
|
+
# Exclude toggleable heading patterns to be more resilient to wrong order of chain
|
40
|
+
return not self.is_heading_start(context.line)
|
41
|
+
|
42
|
+
def is_heading_start(self, line: str) -> bool:
|
43
|
+
return self._heading_syntax.regex_pattern.match(line) is not None
|
44
|
+
|
45
|
+
def _is_toggle_end(self, context: BlockParsingContext) -> bool:
|
46
|
+
if not self._syntax.end_regex_pattern.match(context.line):
|
47
|
+
return False
|
48
|
+
|
49
|
+
if not context.parent_stack:
|
50
|
+
return False
|
51
|
+
|
52
|
+
current_parent = context.parent_stack[-1]
|
53
|
+
return isinstance(current_parent.block, CreateToggleBlock)
|
54
|
+
|
55
|
+
async def _start_toggle(self, context: BlockParsingContext) -> None:
|
56
|
+
block = await self._create_toggle_block(context.line)
|
57
|
+
if not block:
|
58
|
+
return
|
59
|
+
|
60
|
+
parent_context = ParentBlockContext(
|
61
|
+
block=block,
|
62
|
+
child_lines=[],
|
63
|
+
)
|
64
|
+
context.parent_stack.append(parent_context)
|
65
|
+
|
66
|
+
async def _create_toggle_block(self, line: str) -> CreateToggleBlock | None:
|
67
|
+
if not (match := self._syntax.regex_pattern.match(line)):
|
68
|
+
return None
|
69
|
+
|
70
|
+
title = match.group(1).strip()
|
71
|
+
rich_text = await self._rich_text_converter.to_rich_text(title)
|
72
|
+
|
73
|
+
toggle_content = CreateToggleData(rich_text=rich_text, color=BlockColor.DEFAULT, children=[])
|
74
|
+
return CreateToggleBlock(toggle=toggle_content)
|
75
|
+
|
76
|
+
async def _finalize_toggle(self, context: BlockParsingContext) -> None:
|
77
|
+
toggle_context = context.parent_stack.pop()
|
78
|
+
await self._assign_toggle_children_if_any(toggle_context, context)
|
79
|
+
|
80
|
+
if self._is_nested_in_other_parent_context(context):
|
81
|
+
self._assign_to_parent_context(context, toggle_context)
|
82
|
+
else:
|
83
|
+
context.result_blocks.append(toggle_context.block)
|
84
|
+
|
85
|
+
def _is_nested_in_other_parent_context(self, context: BlockParsingContext) -> bool:
|
86
|
+
return context.parent_stack
|
87
|
+
|
88
|
+
def _assign_to_parent_context(self, context: BlockParsingContext, toggle_context: ParentBlockContext) -> None:
|
89
|
+
parent_context = context.parent_stack[-1]
|
90
|
+
parent_context.add_child_block(toggle_context.block)
|
91
|
+
|
92
|
+
async def _assign_toggle_children_if_any(
|
93
|
+
self, toggle_context: ParentBlockContext, context: BlockParsingContext
|
94
|
+
) -> None:
|
95
|
+
all_children = []
|
96
|
+
|
97
|
+
# Process text lines
|
98
|
+
if toggle_context.child_lines:
|
99
|
+
children_text = "\n".join(toggle_context.child_lines)
|
100
|
+
text_blocks = await self._parse_nested_content(children_text, context)
|
101
|
+
all_children.extend(text_blocks)
|
102
|
+
|
103
|
+
if toggle_context.child_blocks:
|
104
|
+
all_children.extend(toggle_context.child_blocks)
|
105
|
+
|
106
|
+
toggle_context.block.toggle.children = all_children
|
107
|
+
|
108
|
+
def _is_toggle_content(self, context: BlockParsingContext) -> bool:
|
109
|
+
if not context.parent_stack:
|
110
|
+
return False
|
111
|
+
|
112
|
+
current_parent = context.parent_stack[-1]
|
113
|
+
if not isinstance(current_parent.block, CreateToggleBlock):
|
114
|
+
return False
|
115
|
+
|
116
|
+
return not (
|
117
|
+
self._syntax.regex_pattern.match(context.line) or self._syntax.end_regex_pattern.match(context.line)
|
118
|
+
)
|
119
|
+
|
120
|
+
def _add_toggle_content(self, context: BlockParsingContext) -> None:
|
121
|
+
context.parent_stack[-1].add_child_line(context.line)
|
122
|
+
|
123
|
+
async def _parse_nested_content(self, text: str, context: BlockParsingContext) -> list:
|
124
|
+
if not text.strip():
|
125
|
+
return []
|
126
|
+
|
127
|
+
return await context.parse_nested_content(text)
|
@@ -0,0 +1,150 @@
|
|
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 import (
|
15
|
+
BlockParsingContext,
|
16
|
+
LineParser,
|
17
|
+
ParentBlockContext,
|
18
|
+
)
|
19
|
+
from notionary.page.content.syntax.service import SyntaxRegistry
|
20
|
+
|
21
|
+
|
22
|
+
class ToggleableHeadingParser(LineParser):
|
23
|
+
MIN_HEADING_LEVEL = 1
|
24
|
+
MAX_HEADING_LEVEL = 3
|
25
|
+
|
26
|
+
HEADING_BLOCK_TYPES = (CreateHeading1Block, CreateHeading2Block, CreateHeading3Block)
|
27
|
+
|
28
|
+
def __init__(self, syntax_registry: SyntaxRegistry, rich_text_converter: MarkdownRichTextConverter) -> None:
|
29
|
+
super().__init__(syntax_registry)
|
30
|
+
self._syntax = syntax_registry.get_toggleable_heading_syntax()
|
31
|
+
self._rich_text_converter = rich_text_converter
|
32
|
+
|
33
|
+
@override
|
34
|
+
def _can_handle(self, context: BlockParsingContext) -> bool:
|
35
|
+
return self._is_heading_start(context) or self._is_heading_end(context) or self._is_heading_content(context)
|
36
|
+
|
37
|
+
@override
|
38
|
+
async def _process(self, context: BlockParsingContext) -> None:
|
39
|
+
if self._is_heading_start(context):
|
40
|
+
await self._start_toggleable_heading(context)
|
41
|
+
elif self._is_heading_end(context):
|
42
|
+
await self._finalize_toggleable_heading(context)
|
43
|
+
elif self._is_heading_content(context):
|
44
|
+
await self._add_heading_content(context)
|
45
|
+
|
46
|
+
def _is_heading_start(self, context: BlockParsingContext) -> bool:
|
47
|
+
return self._syntax.regex_pattern.match(context.line) is not None
|
48
|
+
|
49
|
+
def _is_heading_end(self, context: BlockParsingContext) -> bool:
|
50
|
+
if not self._syntax.end_regex_pattern.match(context.line):
|
51
|
+
return False
|
52
|
+
return self._has_heading_on_stack(context)
|
53
|
+
|
54
|
+
def _is_heading_content(self, context: BlockParsingContext) -> bool:
|
55
|
+
if not self._has_heading_on_stack(context):
|
56
|
+
return False
|
57
|
+
|
58
|
+
return not (
|
59
|
+
self._syntax.regex_pattern.match(context.line) or self._syntax.end_regex_pattern.match(context.line)
|
60
|
+
)
|
61
|
+
|
62
|
+
def _has_heading_on_stack(self, context: BlockParsingContext) -> bool:
|
63
|
+
if not context.parent_stack:
|
64
|
+
return False
|
65
|
+
current_parent = context.parent_stack[-1]
|
66
|
+
return isinstance(current_parent.block, self.HEADING_BLOCK_TYPES)
|
67
|
+
|
68
|
+
async def _start_toggleable_heading(self, context: BlockParsingContext) -> None:
|
69
|
+
block = await self._create_heading_block(context.line)
|
70
|
+
if not block:
|
71
|
+
return
|
72
|
+
|
73
|
+
parent_context = ParentBlockContext(
|
74
|
+
block=block,
|
75
|
+
child_lines=[],
|
76
|
+
)
|
77
|
+
context.parent_stack.append(parent_context)
|
78
|
+
|
79
|
+
async def _create_heading_block(self, line: str) -> CreateHeadingBlock | None:
|
80
|
+
match = self._syntax.regex_pattern.match(line)
|
81
|
+
if not match:
|
82
|
+
return None
|
83
|
+
|
84
|
+
level = len(match.group("level"))
|
85
|
+
content = match.group(2).strip()
|
86
|
+
|
87
|
+
if not self._is_valid_heading(level, content):
|
88
|
+
return None
|
89
|
+
|
90
|
+
heading_data = await self._build_heading_data(content)
|
91
|
+
|
92
|
+
if level == 1:
|
93
|
+
return CreateHeading1Block(heading_1=heading_data)
|
94
|
+
elif level == 2:
|
95
|
+
return CreateHeading2Block(heading_2=heading_data)
|
96
|
+
else:
|
97
|
+
return CreateHeading3Block(heading_3=heading_data)
|
98
|
+
|
99
|
+
def _is_valid_heading(self, level: int, content: str) -> bool:
|
100
|
+
return self.MIN_HEADING_LEVEL <= level <= self.MAX_HEADING_LEVEL and bool(content)
|
101
|
+
|
102
|
+
async def _build_heading_data(self, content: str) -> CreateHeadingData:
|
103
|
+
rich_text = await self._rich_text_converter.to_rich_text(content)
|
104
|
+
return CreateHeadingData(rich_text=rich_text, color=BlockColor.DEFAULT, is_toggleable=True, children=[])
|
105
|
+
|
106
|
+
async def _add_heading_content(self, context: BlockParsingContext) -> None:
|
107
|
+
context.parent_stack[-1].add_child_line(context.line)
|
108
|
+
|
109
|
+
async def _finalize_toggleable_heading(self, context: BlockParsingContext) -> None:
|
110
|
+
heading_context = context.parent_stack.pop()
|
111
|
+
await self._assign_children(heading_context, context)
|
112
|
+
self._add_to_parent_or_result(heading_context.block, context)
|
113
|
+
|
114
|
+
async def _assign_children(self, heading_context: ParentBlockContext, context: BlockParsingContext) -> None:
|
115
|
+
children = await self._collect_children(heading_context, context)
|
116
|
+
self._set_heading_children(heading_context.block, children)
|
117
|
+
|
118
|
+
async def _collect_children(
|
119
|
+
self, heading_context: ParentBlockContext, context: BlockParsingContext
|
120
|
+
) -> list[BlockCreatePayload]:
|
121
|
+
children = []
|
122
|
+
|
123
|
+
if heading_context.child_lines:
|
124
|
+
text = "\n".join(heading_context.child_lines)
|
125
|
+
text_blocks = await self._parse_nested_content(text, context)
|
126
|
+
children.extend(text_blocks)
|
127
|
+
|
128
|
+
if heading_context.child_blocks:
|
129
|
+
children.extend(heading_context.child_blocks)
|
130
|
+
|
131
|
+
return children
|
132
|
+
|
133
|
+
def _set_heading_children(self, block: CreateHeadingBlock, children: list[BlockCreatePayload]) -> None:
|
134
|
+
if block.type == BlockType.HEADING_1:
|
135
|
+
block.heading_1.children = children
|
136
|
+
elif block.type == BlockType.HEADING_2:
|
137
|
+
block.heading_2.children = children
|
138
|
+
elif block.type == BlockType.HEADING_3:
|
139
|
+
block.heading_3.children = children
|
140
|
+
|
141
|
+
def _add_to_parent_or_result(self, block: CreateHeadingBlock, context: BlockParsingContext) -> None:
|
142
|
+
if context.parent_stack:
|
143
|
+
context.parent_stack[-1].add_child_block(block)
|
144
|
+
else:
|
145
|
+
context.result_blocks.append(block)
|
146
|
+
|
147
|
+
async def _parse_nested_content(self, text: str, context: BlockParsingContext) -> list:
|
148
|
+
if not text.strip():
|
149
|
+
return []
|
150
|
+
return await context.parse_nested_content(text)
|
@@ -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.service 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
|
@@ -0,0 +1,93 @@
|
|
1
|
+
"""
|
2
|
+
Handles request limits for rich texts (see https://developers.notion.com/reference/request-limits)
|
3
|
+
"""
|
4
|
+
|
5
|
+
from typing import Any, override
|
6
|
+
|
7
|
+
from notionary.blocks.rich_text.models import RichText, RichTextType
|
8
|
+
from notionary.blocks.schemas import BlockCreatePayload
|
9
|
+
from notionary.page.content.parser.post_processing.port import PostProcessor
|
10
|
+
from notionary.utils.mixins.logging import LoggingMixin
|
11
|
+
|
12
|
+
type _NestedBlockList = BlockCreatePayload | list["_NestedBlockList"]
|
13
|
+
|
14
|
+
|
15
|
+
class RichTextLengthTruncationPostProcessor(PostProcessor, LoggingMixin):
|
16
|
+
NOTION_MAX_LENGTH = 2000
|
17
|
+
|
18
|
+
def __init__(self, max_text_length: int = NOTION_MAX_LENGTH) -> None:
|
19
|
+
self.max_text_length = max_text_length
|
20
|
+
|
21
|
+
@override
|
22
|
+
def process(self, blocks: list[BlockCreatePayload]) -> list[BlockCreatePayload]:
|
23
|
+
if not blocks:
|
24
|
+
return blocks
|
25
|
+
|
26
|
+
flattened_blocks = self._flatten_blocks(blocks)
|
27
|
+
return [self._process_block(block) for block in flattened_blocks]
|
28
|
+
|
29
|
+
def _flatten_blocks(self, blocks: list[_NestedBlockList]) -> list[BlockCreatePayload]:
|
30
|
+
flattened: list[BlockCreatePayload] = []
|
31
|
+
|
32
|
+
for item in blocks:
|
33
|
+
if isinstance(item, list):
|
34
|
+
flattened.extend(self._flatten_blocks(item))
|
35
|
+
else:
|
36
|
+
flattened.append(item)
|
37
|
+
|
38
|
+
return flattened
|
39
|
+
|
40
|
+
def _process_block(self, block: BlockCreatePayload) -> BlockCreatePayload:
|
41
|
+
block_copy = block.model_copy(deep=True)
|
42
|
+
content = self._get_block_content(block_copy)
|
43
|
+
|
44
|
+
if content is not None:
|
45
|
+
self._truncate_content(content)
|
46
|
+
|
47
|
+
return block_copy
|
48
|
+
|
49
|
+
def _get_block_content(self, block: BlockCreatePayload) -> Any | None:
|
50
|
+
content = getattr(block, block.type.value, None)
|
51
|
+
|
52
|
+
if content is None:
|
53
|
+
return None
|
54
|
+
|
55
|
+
if hasattr(content, "rich_text") or hasattr(content, "children"):
|
56
|
+
return content
|
57
|
+
|
58
|
+
return None
|
59
|
+
|
60
|
+
def _truncate_content(self, content: object) -> None:
|
61
|
+
if hasattr(content, "rich_text"):
|
62
|
+
self._truncate_rich_text_list(content.rich_text)
|
63
|
+
|
64
|
+
if hasattr(content, "caption"):
|
65
|
+
self._truncate_rich_text_list(content.caption)
|
66
|
+
|
67
|
+
if hasattr(content, "children"):
|
68
|
+
for child in content.children:
|
69
|
+
child_content = self._get_block_content(child)
|
70
|
+
if child_content:
|
71
|
+
self._truncate_content(child_content)
|
72
|
+
|
73
|
+
def _truncate_rich_text_list(self, rich_text_list: list[RichText]) -> None:
|
74
|
+
for rich_text in rich_text_list:
|
75
|
+
if not self._is_text_type(rich_text):
|
76
|
+
continue
|
77
|
+
|
78
|
+
content = rich_text.text.content
|
79
|
+
if len(content) > self.max_text_length:
|
80
|
+
self.logger.warning(
|
81
|
+
"Truncating text content from %d to %d characters",
|
82
|
+
len(content),
|
83
|
+
self.max_text_length,
|
84
|
+
)
|
85
|
+
truncated_content = self._create_truncated_text_with_ellipsis(content)
|
86
|
+
rich_text.text.content = truncated_content
|
87
|
+
|
88
|
+
def _create_truncated_text_with_ellipsis(self, content: str) -> str:
|
89
|
+
cutoff = self.max_text_length - 3
|
90
|
+
return content[:cutoff] + "..."
|
91
|
+
|
92
|
+
def _is_text_type(self, rich_text: RichText) -> bool:
|
93
|
+
return rich_text.type == RichTextType.TEXT and rich_text.text and rich_text.text.content
|