add option "-M" ("--files-and-matches")
3 files changed, 78 insertions(+), 29 deletions(-)

M ChangeLog.md
M README.md
M xgrep.py
M ChangeLog.md +5 -0
@@ 1,3 1,8 @@ 
+2020-04-25:
+
+* add option `-M` (`--files-and-matches`)
+* bump version to 2.8
+
 2020-04-24:
 
 * rename option `-n` (`--declare-ns`) to `-N`

          
M README.md +8 -7
@@ 27,6 27,7 @@ Usage:
              [-l] [--files-with-matches]
              [-L] [--files-without-match]
              [-m] [--matches]
+             [-M] [--files-and-matches]
              [-n] [--line-number]
              [-N] [--declare-ns]
              [-q] [--quiet]

          
@@ 39,14 40,14 @@ Usage:
     xgrep.py [-h] [--help]
 
 Normally, `xgrep.py` outputs the matching parts of the XML files together with
-their file names and the XPath expression. If `-r <ns>` is set, the
+their file names and the XPath expression. The option `-m` outputs only the
+matching parts, without file names or XPath expressions; with `-M`, the matching
+parts are prefixed with the corresponding file name. If `-r <ns>` is set, the
 [EXSLT function `<ns>:test()`](http://exslt.org/regexp/functions/test/) can be
-used in the XPath expression for matching regular expressions. The option `-m`
-outputs only the matching parts, without file names or XPath expressions. The
-option `-i` indents the matching parts, and the option `-N` includes namespace
-declarations. The `-C` option preserves color and formatting codes when piping
-output through [GNU less](http://www.gnu.org/software/less/) and similar
-programs.
+used in the XPath expression for matching regular expressions. The option `-i`
+indents the matching parts, and the option `-N` includes namespace declarations.
+The `-C` option preserves color and formatting codes when piping output through
+[GNU less](http://www.gnu.org/software/less/) and similar programs.
 
 The options `-c`, `-l`, `-L`, `-n`, and `-q` mimic the behaviour of
 [GNU grep](http://www.gnu.org/software/grep/). The latter option suppresses any

          
M xgrep.py +65 -22
@@ 1,6 1,6 @@ 
 #! /usr/bin/python3
 # xgrep.py -- search for elements in XML files, using XPath 1.0 expressions
-# Andreas Nolda 2020-04-24
+# Andreas Nolda 2020-04-25
 
 import sys
 import argparse

          
@@ 8,7 8,7 @@ import re
 from blessings import Terminal
 from lxml import etree
 
-version=2.7
+version=2.8
 
 parser = argparse.ArgumentParser()
 parser.add_argument("expr",

          
@@ 27,6 27,8 @@ parser.add_argument("-L", "--files-witho
                     help="output list of non-matching files")
 parser.add_argument("-m", "--matches", action="store_true",
                     help="output list of matches")
+parser.add_argument("-M", "--files-and-matches", action="store_true",
+                    help="output list of files and matches")
 parser.add_argument("-n", "--line-number", action="store_true",
                     help="output line number of match start")
 parser.add_argument("-N", "--declare-ns", action="store_true",

          
@@ 84,36 86,77 @@ def remove_ns(match):
     string = re.sub('\s+xmlns(:[A-Za-z_][A-Za-z0-9._-]*)?="[^"]+"', '', match)
     return string
 
-def print_filename(file, int):
-    if args.count:
-        print(term.bold(file) + ":{0}".format(int))
-    else:
-        print(term.bold(file))
+def print_filename(file, end):
+    print(term.bold(file), end=end)
+
+def print_total(matches, end):
+    print(len(matches), end=end)
+
+def print_expr(expr, end):
+    print(term.bold(expr), end=end)
 
-def print_match(match, int):
-    if not args.matches:
-        if args.count:
-            print(term.bold("{0}[{1}]".format(args.expr, int)) + ":")
-        else:
-            print(term.bold(args.expr) + ":")
-    if args.line_number:
-        print(term.bright_black("{0}".format(match.sourceline)) + ":", end="")
+def print_index(int, end):
+    print(term.bold("[{0}]".format(int + 1)), end=end)
+
+def print_line_number(match, end):
+    print(term.bright_black("{0}".format(match.sourceline)), end=end)
+
+def print_match(match, end):
     if args.declare_ns:
-        print(serialize_match(match))
+        print(serialize_match(match), end=end)
     else:
-        print(remove_ns(serialize_match(match)))
+        print(remove_ns(serialize_match(match)), end=end)
 
 def print_matches(matches, file):
     if matches:
-        if not args.matches:
-            print_filename(file, len(matches))
-        if not args.files_with_matches:
+        if args.files_with_matches:
+            if args.count:
+                print_filename(file, ":")
+                print_total(matches, "\n")
+            else:
+                print_filename(file, "\n")
+        elif args.matches:
+            for i, match in enumerate(matches):
+                if args.count:
+                    print_index(i, ":")
+                if args.line_number:
+                    print_line_number(match, ":")
+                print_match(match, "\n")
+        elif args.files_and_matches:
             for i, match in enumerate(matches):
-                print_match(match, i + 1)
+                if args.count:
+                    print_filename(file, "")
+                    print_index(i, ":")
+                else:
+                    print_filename(file, ":")
+                if args.line_number:
+                    print_line_number(match, ":")
+                print_match(match, "\n")
+        else:
+            if args.count:
+                print_filename(file, ":")
+                print_total(matches, "\n")
+                for i, match in enumerate(matches):
+                    print_expr(args.expr, "")
+                    print_index(i, ":\n")
+                    if args.line_number:
+                        print_line_number(match, ":")
+                    print_match(match, "\n")
+            else:
+                print_filename(file, "\n")
+                for i, match in enumerate(matches):
+                    print_expr(args.expr, ":\n")
+                    if args.line_number:
+                        print_line_number(match, ":")
+                    print_match(match, "\n")
 
 def print_nonmatches(matches, file):
     if not matches:
-        print_filename(file, len(matches))
+        if args.count:
+            print_filename(file, ":")
+            print_total(matches, "\n")
+        else:
+            print_filename(file, "\n")
 
 def main():
     n = 0