Skip to content

Instantly share code, notes, and snippets.

@allista
Created September 14, 2024 14:05
Show Gist options
  • Select an option

  • Save allista/58a4c32152858339cda38e31251ea24c to your computer and use it in GitHub Desktop.

Select an option

Save allista/58a4c32152858339cda38e31251ea24c to your computer and use it in GitHub Desktop.
Simple python script to generate kewords.txt for arduino library from its sources
#!/usr/bin/env python3
import os
import re
import argparse
class Keywords:
def __init__(self):
self.functions = set()
self.classes = set()
self.constants = set()
def update(self, other):
self.functions.update(other.functions)
self.classes.update(other.classes)
self.constants.update(other.constants)
def prune(self):
self.functions -= self.classes
def _write_set(self, f, keywords_set, keyword_type):
for keyword in sorted(keywords_set):
f.write(f'{keyword}\t{keyword_type}\n')
f.write('\n')
def write(self, output_path):
with open(output_path, 'w', encoding='utf-8') as f:
if self.classes:
f.write('### CLASSES/STRUCTS ###\n')
self._write_set(f, self.classes, 'KEYWORD1')
if self.functions:
f.write('### FUNCTIONS ###\n')
self._write_set(f, self.functions, 'KEYWORD2')
if self.functions:
f.write('### CONSTANTS ###\n')
self._write_set(f, self.constants, 'LITERAL1')
def __len__(self):
return len(self.functions)+len(self.classes)+len(self.constants)
# Regular expressions to capture function, class, and constant definitions
function_pattern = re.compile(r'^([a-zA-Z_][a-zA-Z0-9_]+\s*(\*+)?\s*)(?P<name>[a-zA-Z_][a-zA-Z0-9_]+)\s*\([^)]*\)')
class_pattern = re.compile(r'^(class|struct)\s+(?P<name>[a-zA-Z_][a-zA-Z0-9_]*)')
constant_pattern = re.compile(r'^(#define|((static\s+)?const\s+\w+))\s+(?P<name>[a-zA-Z_][a-zA-Z0-9_]*)')
# Regular expression to match header guards like #ifndef MyLibrary_h
header_guard_pattern = re.compile(r'^#ifndef\s+([a-zA-Z_][a-zA-Z0-9_]*_h)')
def extract_keywords_from_file(file_path):
keywords = Keywords()
with open(file_path, 'r', encoding='utf-8') as f:
# Track the current header guard to skip its corresponding #define
current_header_guard = None
for line in f:
# Remove comments from lines
line = re.sub(r'//.*', '', line)
line = re.sub(r'/\*.*?\*/', '', line)
# Detect header guard and store the guard name
header_guard_match = header_guard_pattern.match(line)
if header_guard_match:
current_header_guard = header_guard_match.group(1) # Store the guard symbol
continue
# Skip the matching #define that corresponds to the header guard
if current_header_guard and re.match(rf'#define\s+{current_header_guard}', line):
current_header_guard = None # Reset after skipping the matching #define
continue
# Check for function, class, and constant definitions
function_matches = [match.group('name') for match in function_pattern.finditer(line)]
class_matches = [match.group('name') for match in class_pattern.finditer(line)]
constant_matches = [match.group('name') for match in constant_pattern.finditer(line)]
if function_matches:
keywords.functions.update(function_matches)
if class_matches:
keywords.classes.update(class_matches)
if constant_matches:
keywords.constants.update(constant_matches)
return keywords
def scan_library_for_keywords(library_path):
keywords = Keywords()
for root, dirs, files in os.walk(library_path):
for file in files:
if file.endswith('.h'):
file_path = os.path.join(root, file)
keywords.update(extract_keywords_from_file(file_path))
return keywords
def main():
# Set up argument parser
parser = argparse.ArgumentParser(description='Generate keywords.txt for Arduino libraries.')
# Add arguments for input and output paths
parser.add_argument('source_path', type=str, help='Path to the Arduino library source files')
parser.add_argument('output_path', type=str, help='Output path for keywords.txt')
# Parse the arguments
args = parser.parse_args()
# Get keywords from source path
keywords = scan_library_for_keywords(args.source_path)
keywords.prune()
# Write the keywords.txt to the output path
keywords.write(args.output_path)
print(f'{args.output_path} has been generated with {len(keywords)} keyword(s).')
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment