-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #517 from rswgnu/rsw
hyrolo.py - Include this new file
- Loading branch information
Showing
1 changed file
with
152 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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,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() |