Last active
September 22, 2024 15:49
-
-
Save mofosyne/93ad8d64fb40ef80f6af3b09dc755414 to your computer and use it in GitHub Desktop.
Revisions
-
mofosyne renamed this gist
Sep 22, 2024 . 1 changed file with 0 additions and 0 deletions.There are no files selected for viewing
File renamed without changes. -
mofosyne revised this gist
Sep 22, 2024 . 1 changed file with 119 additions and 72 deletions.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 charactersOriginal file line number Diff line number Diff line change @@ -1,144 +1,191 @@ #!/usr/bin/env python3 # KiCADv8 Style Prettify S-Expression Formatter (sexp formatter) # By Brian Khuu, 2024 # This script reformats KiCad-like S-expressions to match a specific formatting style. # Note: This script modifies formatting only; it does not perform linting or validation. # Context: Compact element settings are added to support KiCAD-specific handling for readability, e.g., PCB_PLUGIN::formatPolyPts. import os import argparse from pathlib import Path def prettify_sexpr(sexpr_str, compact_element_settings): """ Prettifies KiCad-like S-expressions according to a KiCADv8-style formatting. Args: sexpr_str (str): The input S-expression string to be formatted. compact_element_settings (list of dict): A list of dictionaries containing element-specific settings. Each dictionary should contain: - "prefix" (str): The prefix of elements that should be handled specially. - "elements per line" (int): The number of elements per line in compact mode (optional). Returns: str: The prettified S-expression string. Example: # Input S-expression string sexpr = "(module (fp_text \"example\"))" # Settings for compact element handling compact_settings = [{"prefix": "pts", "elements per line": 4}] # Prettify the S-expression formatted_sexpr = prettify_sexpr(sexpr, compact_settings) print(formatted_sexpr) """ indent = 0 result = [] in_quote = False escape_next_char = False singular_element = False in_prefix_scan = False prefix_keyword_buffer = "" prefix_stack = [] element_count_stack = [0] # Iterate through the s-expression and apply formatting for char in sexpr_str: if char == '"' or in_quote: # Handle quoted strings, preserving their internal formatting result.append(char) if escape_next_char: escape_next_char = False elif char == '\\': escape_next_char = True elif char == '"': in_quote = not in_quote elif char == '(': # Check for compact element handling in_compact_mode = False elements_per_line = 0 if compact_element_settings: parent_prefix = prefix_stack[-1] if (len(prefix_stack) > 0) else None for setting in compact_element_settings: if setting.get("prefix") in prefix_stack: in_compact_mode = True if setting.get("prefix") == parent_prefix: elements_per_line = setting.get("elements per line", 0) if in_compact_mode: if elements_per_line > 0: parent_element_count = element_count_stack[-1] if parent_element_count != 0 and ((parent_element_count % elements_per_line) == 0): result.append('\n' + '\t' * indent) result.append('(') else: # New line and add an opening parenthesis with the current indentation result.append('\n' + '\t' * indent + '(') # Start Prefix Keyword Scanning in_prefix_scan = True prefix_keyword_buffer = "" # Start tracking if element is singular singular_element = True # Element Count Tracking element_count_stack[-1] += 1 element_count_stack.append(0) indent += 1 elif char == ')': # Handle closing elements indent -= 1 element_count_stack.pop() if singular_element: result.append(')') singular_element = False else: result.append('\n' + '\t' * indent + ')') if in_prefix_scan: prefix_stack.append(prefix_keyword_buffer) in_prefix_scan = False prefix_stack.pop() elif char.isspace(): # Handling spaces if result and not result[-1].isspace() and result[-1] != '(': result.append(' ') if in_prefix_scan: # Capture Prefix Keyword prefix_stack.append(prefix_keyword_buffer) # Handle special compact elements if compact_element_settings: for setting in compact_element_settings: if setting.get("prefix") == prefix_keyword_buffer: result.append('\n' + '\t' * indent) break in_prefix_scan = False else: # Handle any other characters result.append(char) # Capture Prefix Keyword if in_prefix_scan: prefix_keyword_buffer += char # Dev Note: In my opinion, this shouldn't be here... but is here so that we can match KiCADv8's behavior when a ')' is following a non ')' singular_element = True # Join results list and strip out any spaces in the beginning and end of the document formatted_sexp = ''.join(result).strip() # Strip out any extra space on the right hand side of each line formatted_sexp = '\n'.join(line.rstrip() for line in formatted_sexp.split('\n')) + '\n' # Formatting of s-expression completed return formatted_sexp # Argument Parsing parser = argparse.ArgumentParser(description="Prettify S-expression files") parser.add_argument("src", type=Path, help="Source file path") parser.add_argument("--dst", type=Path, help="Destination file path") args = parser.parse_args() src_file = args.src.resolve() dst_file = args.dst.resolve() if args.dst else None # Open source file with open(src_file, "r") as file: sexp_data = file.read() # Compact element settings for special handling compact_element_settings = [] src_basename = os.path.basename(src_file) if src_basename.endswith(".kicad_sym"): compact_element_settings.append({"prefix":"pts", "elements per line": 6}) elif src_basename.endswith(".kicad_mod"): pass elif src_basename.endswith(".kicad_sch"): compact_element_settings.append({"prefix":"pts", "elements per line": 6}) elif src_basename.endswith(".kicad_pcb"): compact_element_settings.append({"prefix":"pts", "elements per line": 4}) # Format the S-expression pretty_sexpr = prettify_sexpr(sexp_data, compact_element_settings) # Save or output the result if dst_file: with open(dst_file, "w") as file: file.write(pretty_sexpr) else: print(pretty_sexpr) -
mofosyne revised this gist
Sep 20, 2024 . 1 changed file with 85 additions and 53 deletions.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 charactersOriginal file line number Diff line number Diff line change @@ -1,112 +1,144 @@ #!/usr/bin/env python3 # KiCADv8 Style Prettify S-Expression Formatter (sexp formatter) # By Brian Khuu 2024 # This script reformats KiCad-like S-expressions to match a specific formatting style. # Design Goal: To format S-expressions according to a style similar to KiCADv8. # Note: This script modifies the formatting of S-expressions and does not perform linting or validation. # Dev Note: one_liner_prefix is added to support KiCAD specific handling of certain s-expression elements # for better readability e.g. `PCB_PLUGIN::formatPolyPts` in KiCAD source code. import argparse from pathlib import Path def prettify_sexpr(sexpr_str, one_liner_prefix=["pts"]): # KiCADv8 Style Prettify S-Expression Formatter (sexp formatter) # Function to prettify KiCad-like S-expressions # This script reformats KiCad-like S-expressions to match a specific formatting style. # Design Goal: To format S-expressions according to a style similar to KiCADv8. # Note: This script modifies the formatting of S-expressions and does not perform linting or validation. indent = 0 result = [] in_quote = False in_next_char_escaped = False in_singular_element = False in_prefix = False prefix_keyword_scan_buffer = None prefix_indent = [] for char in sexpr_str: if char == '"' or in_quote: # Inside a quoted string, just add characters as-is result.append(char) if in_next_char_escaped: # Add the escaped character and reset escape state in_next_char_escaped = False elif char == '\\': # Set flag to escape the next character in_next_char_escaped = True elif char == '"': # Quote Start/Stop Control character in_quote = not in_quote elif char == '(': # Check if we are in one liner mode for readability mode one_liner_mode = False for prefix_entry in one_liner_prefix: if prefix_entry in prefix_indent: one_liner_mode = True if one_liner_mode: # Certain prefixes elements do not do indents for readability reasons result.append('(') else: # New line and add an opening parenthesis with the current indentation result.append('\n' + '\t' * indent + '(') # Start Prefix Keyword Scanning in_prefix = True prefix_keyword_scan_buffer = "" # Start tracking if element is singular in_singular_element = True indent += 1 elif char == ')': indent -= 1 if in_singular_element: # Closing immediately after an opening means it's a singular element, keep it inline result.append(')') in_singular_element = False else: # Close parenthesis at the current indentation level result.append('\n' + '\t' * indent + ')') # Check if still scanning for prefix keyword if in_prefix: prefix_indent.append(prefix_keyword_scan_buffer) in_prefix = False # Pop element tracking stack prefix_indent.pop() elif char.isspace(): if result and not result[-1].isspace() and result[-1] != '(': result.append(' ') # Capture Prefix Keyword if in_prefix: prefix_indent.append(prefix_keyword_scan_buffer) # Check if prefix keyword is special one liner structure for prefix_entry in one_liner_prefix: if prefix_entry == prefix_keyword_scan_buffer: result.append('\n' + '\t' * indent) break in_prefix = False else: result.append(char) # Capture Prefix Keyword if in_prefix: prefix_keyword_scan_buffer += char # Dev Note: In my opinion, this shouldn't be here... but is here so that # we can match KiCADv8's behavior when a ')' is following a non ')' in_singular_element = True # Join results list and strip out any spaces in the beginning and end of the document formatted_sexp = ''.join(result).strip() # Strip out any extra space on the right hand side of each line formatted_sexp = '\n'.join([line.rstrip() for line in formatted_sexp.split('\n')]) + '\n' # Formatting of s-expression completed return formatted_sexp # Arg Parsing parser = argparse.ArgumentParser(description="Format s-expression") parser.add_argument("src", type=Path) parser.add_argument("--dst", type=Path) p = parser.parse_args() src_file = p.src.resolve() dst_file = p.dst.resolve() # Open source file with open(src_file, "r") as file: sexp_data = file.read() # Format s-expression source pretty_sexpr = prettify_sexpr(sexp_data) # Save formatted output if dst_file: with open(dst_file, "w") as file: file.write(pretty_sexpr) else: print(pretty_sexpr) -
mofosyne revised this gist
Sep 20, 2024 . 1 changed file with 1 addition and 1 deletion.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 charactersOriginal file line number Diff line number Diff line change @@ -1,5 +1,5 @@ #!/usr/bin/env python3 # KiCADv8 Style Prettyfy S-Expression Formatter (sexp formatter) # By Brian Khuu 2024 # This script reformats KiCad-like S-expressions to match a specific formatting style. # Design Goal: To format S-expressions according to a style similar to KiCADv8. -
mofosyne revised this gist
Sep 20, 2024 . No changes.There are no files selected for viewing
-
mofosyne revised this gist
Sep 20, 2024 . No changes.There are no files selected for viewing
-
mofosyne revised this gist
Sep 20, 2024 . 1 changed file with 12 additions and 8 deletions.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 charactersOriginal file line number Diff line number Diff line change @@ -1,19 +1,23 @@ #!/usr/bin/env python3 # KiCADv8 Style Prettyfy S-Expression Formatter # By Brian Khuu 2024 # This script reformats KiCad-like S-expressions to match a specific formatting style. # Design Goal: To format S-expressions according to a style similar to KiCADv8. # Note: This script modifies the formatting of S-expressions and does not perform linting or validation. from os import path import argparse from pathlib import Path def prettify_sexpr(sexpr_str): # Prettyfy S-Expression # Function to prettify KiCad-like S-expressions # This script reformats KiCad-like S-expressions to match a specific formatting style. # Design Goal: To format S-expressions according to a style similar to KiCADv8. # Note: This script modifies the formatting of S-expressions and does not perform linting or validation. # Warning: This diverges from KiCADv8's output style in that the 'xy' element inside 'pts' is also indented on a new line. # This is because KiCADv8's styling rule is not consistent specifically regarding the 'pts' element. # To help keep complexity low with this script, we do not support such special handling. indent = 0 result = [] i = 0 -
mofosyne revised this gist
Sep 20, 2024 . 1 changed file with 1 addition and 1 deletion.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 charactersOriginal file line number Diff line number Diff line change @@ -1,5 +1,5 @@ #!/usr/bin/env python3 # KiCADv8 Style Prettyfy S-Expression Linter # By Brian Khuu 2024 from os import path -
mofosyne revised this gist
Sep 20, 2024 . No changes.There are no files selected for viewing
-
mofosyne revised this gist
Sep 20, 2024 . 1 changed file with 2 additions and 2 deletions.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 charactersOriginal file line number Diff line number Diff line change @@ -31,8 +31,8 @@ def prettify_sexpr(sexpr_str): elif char == '\\': # Set flag to escape the next character result.append(char) escape_next = True elif char == '"': if escape_next: @@ -41,8 +41,8 @@ def prettify_sexpr(sexpr_str): escape_next = False else: # Toggle the in_quote state result.append(char) in_quote = not in_quote in_element = True elif in_quote: -
mofosyne revised this gist
Sep 20, 2024 . 1 changed file with 7 additions and 8 deletions.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 charactersOriginal file line number Diff line number Diff line change @@ -1,19 +1,18 @@ #!/usr/bin/env python3 # KiCADv8 style sexp linter # By Brian Khuu 2024 from os import path import argparse from pathlib import Path # Prettyfy S-Expression # Function to prettify KiCad-like S-expressions # Design Goal: To match KiCADv8 formatting style as close as possible. # This script formats S-expressions and is designed to be standalone without external modules. # Note: This diverges from KiCADv8's output style in that the 'xy' element inside 'pts' is also indented on a new line. # This is because KiCADv8's styling rule is not consistent specifically regarding the 'pts' element. # To help keep complexity low with this script, we do not support such special handling. def prettify_sexpr(sexpr_str): indent = 0 result = [] -
mofosyne revised this gist
Sep 20, 2024 . 1 changed file with 6 additions and 1 deletion.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 charactersOriginal file line number Diff line number Diff line change @@ -1,9 +1,14 @@ #!/usr/bin/env python3 # KiCADv8 style s-exp linter # By Brian Khuu 2024 # Design Goal: To match KiCADv8 formatting style as close as possible. # This script formats S-expressions and is designed to be standalone without external modules. # Note: This diverges from KiCADv8's output style in that the 'xy' element inside 'pts' is also indented on a new line. # This is because KiCADv8's styling rule is not consistent specifically regarding the 'pts' element. # To help keep complexity low with this script, we do not support such special handling. from os import path import argparse from pathlib import Path -
mofosyne revised this gist
Sep 20, 2024 . No changes.There are no files selected for viewing
-
mofosyne revised this gist
Sep 20, 2024 . 1 changed file with 23 additions and 21 deletions.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 charactersOriginal file line number Diff line number Diff line change @@ -1,27 +1,25 @@ #!/usr/bin/env python3 # KiCADv8 style s-exp linter # By Brian Khuu 2024 # Design Goal: To match KiCADv8 formatting style as close as possible # This script formats S-expressions and is designed to be standalone without external modules. from os import path import argparse from pathlib import Path # Function to prettify KiCad-like S-expressions def prettify_sexpr(sexpr_str): indent = 0 result = [] i = 0 in_quote = False escape_next = False in_element = False while i < len(sexpr_str): char = sexpr_str[i] if escape_next: # Add the escaped character and reset escape state result.append(char) @@ -30,7 +28,7 @@ def prettify_sexpr(sexpr_str): elif char == '\\': # Set flag to escape the next character escape_next = True result.append(char) elif char == '"': if escape_next: @@ -40,8 +38,8 @@ def prettify_sexpr(sexpr_str): else: # Toggle the in_quote state in_quote = not in_quote result.append(char) in_element = True elif in_quote: # Inside a quoted string, just add characters as-is @@ -70,33 +68,37 @@ def prettify_sexpr(sexpr_str): in_element = False elif char.isspace(): if result and not result[-1].isspace() and result[-1] != '(': result.append(' ') in_element = False else: if not in_quote: result.append(char) in_element = True else: result.append(char) i += 1 # Join results list and strip out any spaces in the beginning and end of the document formatted_sexp = ''.join(result).strip() # Strip out any extra space on the right hand side of each line formatted_sexp = '\n'.join([line.rstrip() for line in formatted_sexp.split('\n')]) # Formatting of s-expression completed return formatted_sexp # Arg Parsing parser = argparse.ArgumentParser(description="Format s-expression") parser.add_argument("src", type=Path) p = parser.parse_args() src_file = p.src.resolve() # Open and process the file with open(src_file, "r") as file: sexp_data = file.read() pretty_sexpr = prettify_sexpr(sexp_data) print(pretty_sexpr) -
mofosyne revised this gist
Sep 20, 2024 . 1 changed file with 9 additions and 9 deletions.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 charactersOriginal file line number Diff line number Diff line change @@ -9,14 +9,6 @@ import argparse from pathlib import Path # Function to prettify KiCad-like S-expressions def prettify_sexpr(sexpr_str): @@ -97,6 +89,14 @@ def prettify_sexpr(sexpr_str): # Formatting of s-expression completed return formatted_sexp # Arg Parsing parser=argparse.ArgumentParser(description="Format s-expression") parser.add_argument("src", type=Path) p = parser.parse_args() src_file = p.src.resolve() base_path = path.dirname(path.realpath(__file__)) # Open and process the file sexp_data = open(src_file, "r").read() pretty_sexpr = prettify_sexpr(sexp_data) print(pretty_sexpr) -
mofosyne revised this gist
Sep 20, 2024 . 1 changed file with 2 additions and 1 deletion.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 charactersOriginal file line number Diff line number Diff line change @@ -90,10 +90,11 @@ def prettify_sexpr(sexpr_str): i += 1 # Strip out any extra space on the right hand side of each line formatted_sexp = ''.join(result).strip() formatted_sexp = '\n'.join([line.rstrip() for line in formatted_sexp.split('\n')]) # Formatting of s-expression completed return formatted_sexp # Call the prettify function and print the result -
mofosyne revised this gist
Sep 20, 2024 . 1 changed file with 1 addition and 4 deletions.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 charactersOriginal file line number Diff line number Diff line change @@ -85,10 +85,7 @@ def prettify_sexpr(sexpr_str): else: # Handle any other character (non-whitespace, non-parenthesis) result.append(char) in_element = True i += 1 -
mofosyne created this gist
Sep 20, 2024 .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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,104 @@ #!/usr/bin/env python3 # KiCADv8 style s-exp linter # By Brian Khuu 2024 # Design Goal: To match KiCADv8 formatting style as close as possible (not perfectly however as that's a bit too much work) # This will allow for easier comparison between file format versions and to investigate kicad bugs via diffing from os import path import argparse from pathlib import Path # Arg Parsing parser=argparse.ArgumentParser(description="Format s-expression") parser.add_argument("src", type=Path) p = parser.parse_args() src_file = p.src.resolve() base_path = path.dirname(path.realpath(__file__)) sexp_data = open(src_file, "r").read() # Function to prettify KiCad-like S-expressions def prettify_sexpr(sexpr_str): indent = 0 result = [] i = 0 in_element = False in_quote = False # Track if we're inside a quoted string escape_next = False # Track if the next character is escaped while i < len(sexpr_str): char = sexpr_str[i] if escape_next: # Add the escaped character and reset escape state result.append(char) escape_next = False elif char == '\\': # Set flag to escape the next character escape_next = True result.append(char) # Add the backslash as part of the string elif char == '"': if escape_next: # If the quote is escaped, treat it as a regular character result.append(char) escape_next = False else: # Toggle the in_quote state in_quote = not in_quote result.append(char) # Always add the quote character in_element = True # We're still in an element (quoted content) elif in_quote: # Inside a quoted string, just add characters as-is result.append(char) elif char == '(': if in_element: # If we are in an element, continue without a newline result.append(' (') else: # New line and add an opening parenthesis with the current indentation result.append('\n' + '\t' * indent + '(') indent += 1 in_element = False elif char == ')': indent -= 1 if in_element: # Closing immediately after an opening means an empty list, keep it inline result.append(')') else: # Close parenthesis at the current indentation level result.append('\n' + '\t' * indent + ')') in_element = False elif char.isspace(): # Skip redundant spaces, but add space if it's part of an element if result and not result[-1].endswith('(') and not result[-1].isspace(): result.append(' ') in_element = False else: # Handle any other character (non-whitespace, non-parenthesis) if result and result[-1].endswith('('): result.append(char) else: result.append(char) in_element = True i += 1 formatted_sexp = ''.join(result).strip() formatted_sexp = '\n'.join([line.rstrip() for line in formatted_sexp.split('\n')]) return formatted_sexp # Call the prettify function and print the result pretty_sexpr = prettify_sexpr(sexp_data) print(pretty_sexpr)