-
Notifications
You must be signed in to change notification settings - Fork 264
feat: Enhanced Google Doc Editing Granularity #159
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 12 commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
f369ca1
feat(docs): add comprehensive Google Docs editing capabilities
cfdude fcf4072
Update gdocs/docs_tools.py
taylorwilsdon 0279fff
gitignore cleanup
taylorwilsdon 6fe2fcd
creating tables actually working reliably
taylorwilsdon 28d0ef1
Merge commit '6fe2fcdfa21e1a89f7b534bf7cc1846a6aeb2ec9' into enhanced…
taylorwilsdon d67e1fc
clean up docstrings
taylorwilsdon 8ca2c4e
add helpers
taylorwilsdon e29ff04
refactor into modules
taylorwilsdon 543f85a
helpers for formatting
taylorwilsdon 5bcd149
lots of fixes
taylorwilsdon 0c1edf9
fixes
taylorwilsdon 6022d2c
consolidate the doc modification functions
taylorwilsdon a3db9ce
table data checks
taylorwilsdon File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,299 @@ | ||
| """ | ||
| Google Docs Helper Functions | ||
|
|
||
| This module provides utility functions for common Google Docs operations | ||
| to simplify the implementation of document editing tools. | ||
| """ | ||
| import logging | ||
| from typing import Dict, Any, Optional, Tuple | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| def build_text_style( | ||
| bold: bool = None, | ||
| italic: bool = None, | ||
| underline: bool = None, | ||
| font_size: int = None, | ||
| font_family: str = None | ||
| ) -> tuple[Dict[str, Any], list[str]]: | ||
| """ | ||
| Build text style object for Google Docs API requests. | ||
|
|
||
| Args: | ||
| bold: Whether text should be bold | ||
| italic: Whether text should be italic | ||
| underline: Whether text should be underlined | ||
| font_size: Font size in points | ||
| font_family: Font family name | ||
|
|
||
| Returns: | ||
| Tuple of (text_style_dict, list_of_field_names) | ||
| """ | ||
| text_style = {} | ||
| fields = [] | ||
|
|
||
| if bold is not None: | ||
| text_style['bold'] = bold | ||
| fields.append('bold') | ||
|
|
||
| if italic is not None: | ||
| text_style['italic'] = italic | ||
| fields.append('italic') | ||
|
|
||
| if underline is not None: | ||
| text_style['underline'] = underline | ||
| fields.append('underline') | ||
|
|
||
| if font_size is not None: | ||
| text_style['fontSize'] = {'magnitude': font_size, 'unit': 'PT'} | ||
| fields.append('fontSize') | ||
|
|
||
| if font_family is not None: | ||
| text_style['weightedFontFamily'] = {'fontFamily': font_family} | ||
| fields.append('weightedFontFamily') | ||
|
|
||
| return text_style, fields | ||
|
|
||
| def create_insert_text_request(index: int, text: str) -> Dict[str, Any]: | ||
| """ | ||
| Create an insertText request for Google Docs API. | ||
|
|
||
| Args: | ||
| index: Position to insert text | ||
| text: Text to insert | ||
|
|
||
| Returns: | ||
| Dictionary representing the insertText request | ||
| """ | ||
| return { | ||
| 'insertText': { | ||
| 'location': {'index': index}, | ||
| 'text': text | ||
| } | ||
| } | ||
|
|
||
| def create_delete_range_request(start_index: int, end_index: int) -> Dict[str, Any]: | ||
| """ | ||
| Create a deleteContentRange request for Google Docs API. | ||
|
|
||
| Args: | ||
| start_index: Start position of content to delete | ||
| end_index: End position of content to delete | ||
|
|
||
| Returns: | ||
| Dictionary representing the deleteContentRange request | ||
| """ | ||
| return { | ||
| 'deleteContentRange': { | ||
| 'range': { | ||
| 'startIndex': start_index, | ||
| 'endIndex': end_index | ||
| } | ||
| } | ||
| } | ||
|
|
||
| def create_format_text_request( | ||
| start_index: int, | ||
| end_index: int, | ||
| bold: bool = None, | ||
| italic: bool = None, | ||
| underline: bool = None, | ||
| font_size: int = None, | ||
| font_family: str = None | ||
| ) -> Optional[Dict[str, Any]]: | ||
| """ | ||
| Create an updateTextStyle request for Google Docs API. | ||
|
|
||
| Args: | ||
| start_index: Start position of text to format | ||
| end_index: End position of text to format | ||
| bold: Whether text should be bold | ||
| italic: Whether text should be italic | ||
| underline: Whether text should be underlined | ||
| font_size: Font size in points | ||
| font_family: Font family name | ||
|
|
||
| Returns: | ||
| Dictionary representing the updateTextStyle request, or None if no styles provided | ||
| """ | ||
| text_style, fields = build_text_style(bold, italic, underline, font_size, font_family) | ||
|
|
||
| if not text_style: | ||
| return None | ||
|
|
||
| return { | ||
| 'updateTextStyle': { | ||
| 'range': { | ||
| 'startIndex': start_index, | ||
| 'endIndex': end_index | ||
| }, | ||
| 'textStyle': text_style, | ||
| 'fields': ','.join(fields) | ||
| } | ||
| } | ||
|
|
||
| def create_find_replace_request( | ||
| find_text: str, | ||
| replace_text: str, | ||
| match_case: bool = False | ||
| ) -> Dict[str, Any]: | ||
| """ | ||
| Create a replaceAllText request for Google Docs API. | ||
|
|
||
| Args: | ||
| find_text: Text to find | ||
| replace_text: Text to replace with | ||
| match_case: Whether to match case exactly | ||
|
|
||
| Returns: | ||
| Dictionary representing the replaceAllText request | ||
| """ | ||
| return { | ||
| 'replaceAllText': { | ||
| 'containsText': { | ||
| 'text': find_text, | ||
| 'matchCase': match_case | ||
| }, | ||
| 'replaceText': replace_text | ||
| } | ||
| } | ||
|
|
||
| def create_insert_table_request(index: int, rows: int, columns: int) -> Dict[str, Any]: | ||
| """ | ||
| Create an insertTable request for Google Docs API. | ||
|
|
||
| Args: | ||
| index: Position to insert table | ||
| rows: Number of rows | ||
| columns: Number of columns | ||
|
|
||
| Returns: | ||
| Dictionary representing the insertTable request | ||
| """ | ||
| return { | ||
| 'insertTable': { | ||
| 'location': {'index': index}, | ||
| 'rows': rows, | ||
| 'columns': columns | ||
| } | ||
| } | ||
|
|
||
| def create_insert_page_break_request(index: int) -> Dict[str, Any]: | ||
| """ | ||
| Create an insertPageBreak request for Google Docs API. | ||
|
|
||
| Args: | ||
| index: Position to insert page break | ||
|
|
||
| Returns: | ||
| Dictionary representing the insertPageBreak request | ||
| """ | ||
| return { | ||
| 'insertPageBreak': { | ||
| 'location': {'index': index} | ||
| } | ||
| } | ||
|
|
||
| def create_insert_image_request( | ||
| index: int, | ||
| image_uri: str, | ||
| width: int = None, | ||
| height: int = None | ||
| ) -> Dict[str, Any]: | ||
| """ | ||
| Create an insertInlineImage request for Google Docs API. | ||
|
|
||
| Args: | ||
| index: Position to insert image | ||
| image_uri: URI of the image (Drive URL or public URL) | ||
| width: Image width in points | ||
| height: Image height in points | ||
|
|
||
| Returns: | ||
| Dictionary representing the insertInlineImage request | ||
| """ | ||
| request = { | ||
| 'insertInlineImage': { | ||
| 'location': {'index': index}, | ||
| 'uri': image_uri | ||
| } | ||
| } | ||
|
|
||
| # Add size properties if specified | ||
| object_size = {} | ||
| if width is not None: | ||
| object_size['width'] = {'magnitude': width, 'unit': 'PT'} | ||
| if height is not None: | ||
| object_size['height'] = {'magnitude': height, 'unit': 'PT'} | ||
|
|
||
| if object_size: | ||
| request['insertInlineImage']['objectSize'] = object_size | ||
|
|
||
| return request | ||
|
|
||
| def create_bullet_list_request( | ||
| start_index: int, | ||
| end_index: int, | ||
| list_type: str = "UNORDERED" | ||
| ) -> Dict[str, Any]: | ||
| """ | ||
| Create a createParagraphBullets request for Google Docs API. | ||
|
|
||
| Args: | ||
| start_index: Start of text range to convert to list | ||
| end_index: End of text range to convert to list | ||
| list_type: Type of list ("UNORDERED" or "ORDERED") | ||
|
|
||
| Returns: | ||
| Dictionary representing the createParagraphBullets request | ||
| """ | ||
| bullet_preset = ( | ||
| 'BULLET_DISC_CIRCLE_SQUARE' | ||
| if list_type == "UNORDERED" | ||
| else 'NUMBERED_DECIMAL_ALPHA_ROMAN' | ||
| ) | ||
|
|
||
| return { | ||
| 'createParagraphBullets': { | ||
| 'range': { | ||
| 'startIndex': start_index, | ||
| 'endIndex': end_index | ||
| }, | ||
| 'bulletPreset': bullet_preset | ||
| } | ||
| } | ||
|
|
||
| def validate_operation(operation: Dict[str, Any]) -> Tuple[bool, str]: | ||
| """ | ||
| Validate a batch operation dictionary. | ||
|
|
||
| Args: | ||
| operation: Operation dictionary to validate | ||
|
|
||
| Returns: | ||
| Tuple of (is_valid, error_message) | ||
| """ | ||
| op_type = operation.get('type') | ||
| if not op_type: | ||
| return False, "Missing 'type' field" | ||
|
|
||
| # Validate required fields for each operation type | ||
| required_fields = { | ||
| 'insert_text': ['index', 'text'], | ||
| 'delete_text': ['start_index', 'end_index'], | ||
| 'replace_text': ['start_index', 'end_index', 'text'], | ||
| 'format_text': ['start_index', 'end_index'], | ||
| 'insert_table': ['index', 'rows', 'columns'], | ||
| 'insert_page_break': ['index'], | ||
| 'find_replace': ['find_text', 'replace_text'] | ||
| } | ||
|
|
||
| if op_type not in required_fields: | ||
| return False, f"Unsupported operation type: {op_type or 'None'}" | ||
|
|
||
| for field in required_fields[op_type]: | ||
| if field not in operation: | ||
| return False, f"Missing required field: {field}" | ||
|
|
||
| return True, "" | ||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing import for Tuple. The function uses Tuple in its return type annotation but Tuple is not imported from the typing module at the top of the file.