Merge pull request #564 from reinlemmens/feature/batch-bullet-list-support
Add create_bullet_list operation to batch_update_doc
This commit is contained in:
@@ -113,6 +113,7 @@ def build_paragraph_style(
|
|||||||
indent_end: float = None,
|
indent_end: float = None,
|
||||||
space_above: float = None,
|
space_above: float = None,
|
||||||
space_below: float = None,
|
space_below: float = None,
|
||||||
|
named_style_type: str = None,
|
||||||
) -> tuple[Dict[str, Any], list[str]]:
|
) -> tuple[Dict[str, Any], list[str]]:
|
||||||
"""
|
"""
|
||||||
Build paragraph style object for Google Docs API requests.
|
Build paragraph style object for Google Docs API requests.
|
||||||
@@ -126,6 +127,8 @@ def build_paragraph_style(
|
|||||||
indent_end: Right/end indent in points
|
indent_end: Right/end indent in points
|
||||||
space_above: Space above paragraph in points
|
space_above: Space above paragraph in points
|
||||||
space_below: Space below paragraph in points
|
space_below: Space below paragraph in points
|
||||||
|
named_style_type: Direct named style (TITLE, SUBTITLE, HEADING_1..6, NORMAL_TEXT).
|
||||||
|
Takes precedence over heading_level when both are provided.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple of (paragraph_style_dict, list_of_field_names)
|
Tuple of (paragraph_style_dict, list_of_field_names)
|
||||||
@@ -133,7 +136,20 @@ def build_paragraph_style(
|
|||||||
paragraph_style = {}
|
paragraph_style = {}
|
||||||
fields = []
|
fields = []
|
||||||
|
|
||||||
if heading_level is not None:
|
if named_style_type is not None:
|
||||||
|
valid_styles = [
|
||||||
|
"NORMAL_TEXT", "TITLE", "SUBTITLE",
|
||||||
|
"HEADING_1", "HEADING_2", "HEADING_3",
|
||||||
|
"HEADING_4", "HEADING_5", "HEADING_6",
|
||||||
|
]
|
||||||
|
if named_style_type not in valid_styles:
|
||||||
|
raise ValueError(
|
||||||
|
f"Invalid named_style_type '{named_style_type}'. "
|
||||||
|
f"Must be one of: {', '.join(valid_styles)}"
|
||||||
|
)
|
||||||
|
paragraph_style["namedStyleType"] = named_style_type
|
||||||
|
fields.append("namedStyleType")
|
||||||
|
elif heading_level is not None:
|
||||||
if heading_level < 0 or heading_level > 6:
|
if heading_level < 0 or heading_level > 6:
|
||||||
raise ValueError("heading_level must be between 0 (normal text) and 6")
|
raise ValueError("heading_level must be between 0 (normal text) and 6")
|
||||||
if heading_level == 0:
|
if heading_level == 0:
|
||||||
@@ -321,6 +337,7 @@ def create_update_paragraph_style_request(
|
|||||||
space_above: float = None,
|
space_above: float = None,
|
||||||
space_below: float = None,
|
space_below: float = None,
|
||||||
tab_id: Optional[str] = None,
|
tab_id: Optional[str] = None,
|
||||||
|
named_style_type: str = None,
|
||||||
) -> Optional[Dict[str, Any]]:
|
) -> Optional[Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
Create an updateParagraphStyle request for Google Docs API.
|
Create an updateParagraphStyle request for Google Docs API.
|
||||||
@@ -337,6 +354,7 @@ def create_update_paragraph_style_request(
|
|||||||
space_above: Space above paragraph in points
|
space_above: Space above paragraph in points
|
||||||
space_below: Space below paragraph in points
|
space_below: Space below paragraph in points
|
||||||
tab_id: Optional ID of the tab to target
|
tab_id: Optional ID of the tab to target
|
||||||
|
named_style_type: Direct named style (TITLE, SUBTITLE, HEADING_1..6, NORMAL_TEXT)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dictionary representing the updateParagraphStyle request, or None if no styles provided
|
Dictionary representing the updateParagraphStyle request, or None if no styles provided
|
||||||
@@ -350,6 +368,7 @@ def create_update_paragraph_style_request(
|
|||||||
indent_end,
|
indent_end,
|
||||||
space_above,
|
space_above,
|
||||||
space_below,
|
space_below,
|
||||||
|
named_style_type,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not paragraph_style:
|
if not paragraph_style:
|
||||||
@@ -628,6 +647,33 @@ def create_bullet_list_request(
|
|||||||
return requests
|
return requests
|
||||||
|
|
||||||
|
|
||||||
|
def create_delete_bullet_list_request(
|
||||||
|
start_index: int,
|
||||||
|
end_index: int,
|
||||||
|
doc_tab_id: Optional[str] = None,
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Create a deleteParagraphBullets request to remove bullet/list formatting.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
start_index: Start of the paragraph range
|
||||||
|
end_index: End of the paragraph range
|
||||||
|
doc_tab_id: Optional ID of the tab to target
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary representing the deleteParagraphBullets request
|
||||||
|
"""
|
||||||
|
range_obj = {"startIndex": start_index, "endIndex": end_index}
|
||||||
|
if doc_tab_id:
|
||||||
|
range_obj["tabId"] = doc_tab_id
|
||||||
|
|
||||||
|
return {
|
||||||
|
"deleteParagraphBullets": {
|
||||||
|
"range": range_obj,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def validate_operation(operation: Dict[str, Any]) -> tuple[bool, str]:
|
def validate_operation(operation: Dict[str, Any]) -> tuple[bool, str]:
|
||||||
"""
|
"""
|
||||||
Validate a batch operation dictionary.
|
Validate a batch operation dictionary.
|
||||||
@@ -652,6 +698,7 @@ def validate_operation(operation: Dict[str, Any]) -> tuple[bool, str]:
|
|||||||
"insert_table": ["index", "rows", "columns"],
|
"insert_table": ["index", "rows", "columns"],
|
||||||
"insert_page_break": ["index"],
|
"insert_page_break": ["index"],
|
||||||
"find_replace": ["find_text", "replace_text"],
|
"find_replace": ["find_text", "replace_text"],
|
||||||
|
"create_bullet_list": ["start_index", "end_index"],
|
||||||
"insert_doc_tab": ["title", "index"],
|
"insert_doc_tab": ["title", "index"],
|
||||||
"delete_doc_tab": ["tab_id"],
|
"delete_doc_tab": ["tab_id"],
|
||||||
"update_doc_tab": ["tab_id", "title"],
|
"update_doc_tab": ["tab_id", "title"],
|
||||||
|
|||||||
@@ -872,6 +872,10 @@ async def batch_update_doc(
|
|||||||
insert_page_break- required: index (int)
|
insert_page_break- required: index (int)
|
||||||
find_replace - required: find_text (str), replace_text (str)
|
find_replace - required: find_text (str), replace_text (str)
|
||||||
optional: match_case (bool, default false)
|
optional: match_case (bool, default false)
|
||||||
|
create_bullet_list - required: start_index (int), end_index (int)
|
||||||
|
optional: list_type ('UNORDERED'|'ORDERED'|'NONE', default UNORDERED),
|
||||||
|
nesting_level (0-8), paragraph_start_indices (list[int])
|
||||||
|
Use list_type='NONE' to remove existing bullet/list formatting
|
||||||
insert_doc_tab - required: title (str), index (int)
|
insert_doc_tab - required: title (str), index (int)
|
||||||
optional: parent_tab_id (str)
|
optional: parent_tab_id (str)
|
||||||
delete_doc_tab - required: tab_id (str)
|
delete_doc_tab - required: tab_id (str)
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ from gdocs.docs_helpers import (
|
|||||||
create_find_replace_request,
|
create_find_replace_request,
|
||||||
create_insert_table_request,
|
create_insert_table_request,
|
||||||
create_insert_page_break_request,
|
create_insert_page_break_request,
|
||||||
|
create_bullet_list_request,
|
||||||
|
create_delete_bullet_list_request,
|
||||||
create_insert_doc_tab_request,
|
create_insert_doc_tab_request,
|
||||||
create_delete_doc_tab_request,
|
create_delete_doc_tab_request,
|
||||||
create_update_doc_tab_request,
|
create_update_doc_tab_request,
|
||||||
@@ -244,6 +246,7 @@ class BatchOperationManager:
|
|||||||
op.get("space_above"),
|
op.get("space_above"),
|
||||||
op.get("space_below"),
|
op.get("space_below"),
|
||||||
tab_id,
|
tab_id,
|
||||||
|
op.get("named_style_type"),
|
||||||
)
|
)
|
||||||
|
|
||||||
if not request:
|
if not request:
|
||||||
@@ -301,6 +304,31 @@ class BatchOperationManager:
|
|||||||
)
|
)
|
||||||
description = f"find/replace '{op['find_text']}' → '{op['replace_text']}'"
|
description = f"find/replace '{op['find_text']}' → '{op['replace_text']}'"
|
||||||
|
|
||||||
|
elif op_type == "create_bullet_list":
|
||||||
|
list_type = op.get("list_type", "UNORDERED")
|
||||||
|
if list_type not in ("UNORDERED", "ORDERED", "NONE"):
|
||||||
|
raise ValueError(
|
||||||
|
f"Invalid list_type '{list_type}'. Must be 'UNORDERED', 'ORDERED', or 'NONE'"
|
||||||
|
)
|
||||||
|
if list_type == "NONE":
|
||||||
|
request = create_delete_bullet_list_request(
|
||||||
|
op["start_index"], op["end_index"], tab_id
|
||||||
|
)
|
||||||
|
description = f"remove bullets {op['start_index']}-{op['end_index']}"
|
||||||
|
else:
|
||||||
|
request = create_bullet_list_request(
|
||||||
|
op["start_index"],
|
||||||
|
op["end_index"],
|
||||||
|
list_type,
|
||||||
|
op.get("nesting_level"),
|
||||||
|
op.get("paragraph_start_indices"),
|
||||||
|
tab_id,
|
||||||
|
)
|
||||||
|
style = "bulleted" if list_type == "UNORDERED" else "numbered"
|
||||||
|
description = f"create {style} list {op['start_index']}-{op['end_index']}"
|
||||||
|
if op.get("nesting_level"):
|
||||||
|
description += f" (nesting level {op['nesting_level']})"
|
||||||
|
|
||||||
elif op_type == "insert_doc_tab":
|
elif op_type == "insert_doc_tab":
|
||||||
request = create_insert_doc_tab_request(
|
request = create_insert_doc_tab_request(
|
||||||
op["title"], op["index"], op.get("parent_tab_id")
|
op["title"], op["index"], op.get("parent_tab_id")
|
||||||
@@ -327,6 +355,7 @@ class BatchOperationManager:
|
|||||||
"insert_table",
|
"insert_table",
|
||||||
"insert_page_break",
|
"insert_page_break",
|
||||||
"find_replace",
|
"find_replace",
|
||||||
|
"create_bullet_list",
|
||||||
"insert_doc_tab",
|
"insert_doc_tab",
|
||||||
"delete_doc_tab",
|
"delete_doc_tab",
|
||||||
"update_doc_tab",
|
"update_doc_tab",
|
||||||
@@ -460,6 +489,11 @@ class BatchOperationManager:
|
|||||||
"optional": ["match_case"],
|
"optional": ["match_case"],
|
||||||
"description": "Find and replace text throughout document",
|
"description": "Find and replace text throughout document",
|
||||||
},
|
},
|
||||||
|
"create_bullet_list": {
|
||||||
|
"required": ["start_index", "end_index"],
|
||||||
|
"optional": ["list_type", "nesting_level", "paragraph_start_indices"],
|
||||||
|
"description": "Apply or remove native bullet/numbered list formatting (list_type: UNORDERED, ORDERED, or NONE to remove; nesting_level: 0-8)",
|
||||||
|
},
|
||||||
"insert_doc_tab": {
|
"insert_doc_tab": {
|
||||||
"required": ["title", "index"],
|
"required": ["title", "index"],
|
||||||
"description": "Insert a new document tab with given title at specified index",
|
"description": "Insert a new document tab with given title at specified index",
|
||||||
|
|||||||
@@ -280,6 +280,7 @@ class ValidationManager:
|
|||||||
indent_end: Optional[float] = None,
|
indent_end: Optional[float] = None,
|
||||||
space_above: Optional[float] = None,
|
space_above: Optional[float] = None,
|
||||||
space_below: Optional[float] = None,
|
space_below: Optional[float] = None,
|
||||||
|
named_style_type: Optional[str] = None,
|
||||||
) -> Tuple[bool, str]:
|
) -> Tuple[bool, str]:
|
||||||
"""
|
"""
|
||||||
Validate paragraph style parameters.
|
Validate paragraph style parameters.
|
||||||
@@ -293,6 +294,7 @@ class ValidationManager:
|
|||||||
indent_end: Right/end indent in points
|
indent_end: Right/end indent in points
|
||||||
space_above: Space above paragraph in points
|
space_above: Space above paragraph in points
|
||||||
space_below: Space below paragraph in points
|
space_below: Space below paragraph in points
|
||||||
|
named_style_type: Direct named style (TITLE, SUBTITLE, HEADING_1..6, NORMAL_TEXT)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple of (is_valid, error_message)
|
Tuple of (is_valid, error_message)
|
||||||
@@ -306,11 +308,24 @@ class ValidationManager:
|
|||||||
indent_end,
|
indent_end,
|
||||||
space_above,
|
space_above,
|
||||||
space_below,
|
space_below,
|
||||||
|
named_style_type,
|
||||||
]
|
]
|
||||||
if all(param is None for param in style_params):
|
if all(param is None for param in style_params):
|
||||||
return (
|
return (
|
||||||
False,
|
False,
|
||||||
"At least one paragraph style parameter must be provided (heading_level, alignment, line_spacing, indent_first_line, indent_start, indent_end, space_above, or space_below)",
|
"At least one paragraph style parameter must be provided (heading_level, alignment, line_spacing, indent_first_line, indent_start, indent_end, space_above, space_below, or named_style_type)",
|
||||||
|
)
|
||||||
|
|
||||||
|
if named_style_type is not None:
|
||||||
|
valid_styles = [
|
||||||
|
"NORMAL_TEXT", "TITLE", "SUBTITLE",
|
||||||
|
"HEADING_1", "HEADING_2", "HEADING_3",
|
||||||
|
"HEADING_4", "HEADING_5", "HEADING_6",
|
||||||
|
]
|
||||||
|
if named_style_type not in valid_styles:
|
||||||
|
return (
|
||||||
|
False,
|
||||||
|
f"Invalid named_style_type '{named_style_type}'. Must be one of: {', '.join(valid_styles)}",
|
||||||
)
|
)
|
||||||
|
|
||||||
if heading_level is not None:
|
if heading_level is not None:
|
||||||
@@ -627,6 +642,7 @@ class ValidationManager:
|
|||||||
op.get("indent_end"),
|
op.get("indent_end"),
|
||||||
op.get("space_above"),
|
op.get("space_above"),
|
||||||
op.get("space_below"),
|
op.get("space_below"),
|
||||||
|
op.get("named_style_type"),
|
||||||
)
|
)
|
||||||
if not is_valid:
|
if not is_valid:
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user