branch: externals/hyperbole
commit e52c302d29463d44bf2343806048b430e778c48c
Author: Bob Weiner <[email protected]>
Commit: Bob Weiner <[email protected]>
hyrolo.py - Include this new file
---
hyrolo.py | 152 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 152 insertions(+)
diff --git a/hyrolo.py b/hyrolo.py
new file mode 100644
index 0000000000..661c8df594
--- /dev/null
+++ b/hyrolo.py
@@ -0,0 +1,152 @@
+#!/usr/bin/env python
+#
+# Summary: hyrolo.py --- Output file header and matching entries from
HyRolo files via the command-line
+# Usage: <main-module-name> <string-to-match> [<file1> ... <fileN>]
+# If no files are given, uses the env variable, HYROLO, or if
that is not found, the file
+# "~/.rolo.otl".
+#
+# Author: Bob Weiner
+#
+# Orig-Date: 1-Apr-24 at 01:45:27
+# Last-Mod: 15-Apr-24 at 00:04:58 by Bob Weiner
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# Copyright (C) 2024 Free Software Foundation, Inc.
+# See the "HY-COPY" file for license information.
+#
+# This file is part of GNU Hyperbole.
+
+# Commentary:
+# See the Info manual "(hyperbole)HyRolo" for a description of HyRolo and
associated file formats.
+#
+# Detects entries in files with Org, Markdown or Emacs outline formats.
+# Koutlines are not presently supported.
+#
+# Unlike hyrolo.el, this outputs only the innermost matching entries rather
than the entire
+# subtree of matching entries.
+#
+# This outputs a file header only if there is a matching entry in that file.
+
+# Code:
+
+import argparse
+import os
+import re
+
+# String to match at bol for file header start and end
+file_header_delimiter = '==='
+# Header to insert before a file's first entry match when file has no header.
+# Used with one argument, the file name.
+file_header_format = \
+
"===============================================================================\n"
\
+ "@loc> \"%s\"\n" \
+
"===============================================================================\n"
+
+# The ANSI escape sequence for the red color
+red = "\033[31m"
+# The ANSI escape sequence for inverting colors is \033[7m
+invert = "\033[7m"
+# The ANSI escape sequence to reset the color is \033[0m
+reset = "\033[0m"
+
+def find_matching_entries(match_string, file_paths):
+ quoted_match_string = re.escape(match_string)
+
+ # Remove any null items from file_paths and expand them
+ file_paths = [os.path.abspath(os.path.expanduser(os.path.expandvars(p)))
for p in file_paths if p]
+
+ for file_path in file_paths:
+ # Initialize variables
+ buffer = ''
+ file_header_buffer = ''
+ inside_entry = False
+ inside_file_header = False
+ first_line = True
+ first_entry = True
+ headline_match = False
+
+ # Open the file
+ with open(file_path, 'r') as file:
+ for line in file:
+ if first_line:
+ first_line = False
+ if line.startswith(file_header_delimiter):
+ inside_file_header = True
+ file_header_buffer += line
+ continue
+
+ if inside_file_header:
+ file_header_buffer += line
+ if line.startswith(file_header_delimiter):
+ inside_file_header = False
+ continue
+
+ headline_match = re.match(r'[\*\#]+[ \t]', line, re.IGNORECASE)
+ # If inside a entry and the line starts with an asterisk, check
+ # if the buffer contains the match string.
+ if inside_entry and headline_match:
+ if re.search(quoted_match_string, buffer, re.IGNORECASE):
+ if first_entry:
+ first_entry = False
+ if file_header_buffer:
+ print(file_header_buffer, end='')
+ file_header_buffer = ''
+ print("@loc> \"%s\"\n" % file_path)
+ else:
+ print(file_header_format % file_path)
+
+ highlight_matches(match_string, buffer)
+
+ buffer = ''
+ inside_entry = False
+
+ # If we're not inside a entry and the line starts with an
asterisk, start a new entry
+ elif not inside_entry and headline_match:
+ inside_entry = True
+
+ # If we're inside a entry, add the line to the buffer
+ if inside_entry:
+ buffer += line
+
+ # Check the last entry if it's still inside a entry
+ if inside_entry and re.search(quoted_match_string, buffer,
re.IGNORECASE):
+ if first_entry:
+ first_entry = False
+ if file_header_buffer:
+ print(file_header_buffer)
+ file_header_buffer = ''
+ else:
+ print(file_header_format % file_path)
+
+ highlight_matches(match_string, buffer)
+
+
+def highlight_matches(match_string, buffer):
+ "Split the last buffer into lines and print each line, inverting 'mymatch'
colors."
+ for b_line in buffer.splitlines():
+ if match_string.casefold() in b_line.casefold():
+ # Replace the search string with the inverted version
+ print(re.sub(re.escape(match_string), invert + match_string +
reset,
+ b_line, flags=re.IGNORECASE))
+ else:
+ print(b_line)
+
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument('match_string', help='string to match within HyRolo
entries')
+ parser.add_argument('files', nargs='*', help='list of HyRolo files to
search')
+ args = parser.parse_args()
+
+ # find_matching_entries('case_insensitive_string_to_match',
'hyrolo_contact_file')
+ if args.files:
+ pass
+ elif os.getenv("HYROLO"):
+ args.files = [os.getenv("HYROLO")]
+ else:
+ args.files = ["~/.rolo.otl"]
+ find_matching_entries(args.match_string, args.files)
+
+if __name__ == '__main__':
+ main()