1:- module(canny_cover,
    2          [ coverages_by_module/2,      % :Goal,-Coverages:dict
    3            coverage_for_modules/4      % :Goal,+Modules,-Module,-Coverage
    4          ]).    5:- autoload(library(apply), [convlist/3]).    6:- autoload(library(strings), [string_lines/2]).    7:- autoload(library(prolog_coverage), [coverage/1, show_coverage/1]).    8:- autoload(library(yall), [(>>)/4]).    9:- autoload(library(dcg/basics), [whites/2, integer/3, number/3, string/3]).
 coverages_by_module(:Goal, -Coverages:dict) is det
Calls Goal within show_coverage/1 while capturing the resulting lines of output; Goal is typically run_tests/0 for running all loaded tests. Parses the lines for coverage statistics by module. Ignores lines that do not represent coverage, and also ignores lines that cover non-module files. Automatically matches prefix-truncated coverage paths as well as full paths.
Arguments:
Coverages- is a module-keyed dictionary of sub-dictionaries carrying three keys: clauses, cov and fail.
   23coverages_by_module(Goal, Coverages) :-
   24    coverage(Goal),
   25    with_output_to(string(String), show_coverage([])),
   26    string_lines(String, Lines),
   27    convlist([Line, Module=coverage{
   28                               clauses:Clauses,
   29                               cov:Cov,
   30                               fail:Fail
   31                           }]>>
   32             (   string_codes(Line, Codes),
   33                 phrase(cover_line(Module, Clauses, Cov, Fail), Codes)
   34             ), Lines, Data),
   35    dict_create(Coverages, coverages, Data).
   36
   37cover_line(Module, Clauses, Cov, Fail) -->
   38    cover_file(Module),
   39    whites,
   40    dots,
   41    whites,
   42    integer(Clauses),
   43    whites,
   44    number(Cov),
   45    whites,
   46    number(Fail).
   47
   48cover_file(Module) -->
   49    "...",
   50    !,
   51    { module_property(Module, file(File)),
   52      sub_atom(File, _, _, 0, Suffix),
   53      atom_codes(Suffix, Codes)
   54    },
   55    string(Codes).
   56cover_file(Module) -->
   57    { module_property(Module, file(File)),
   58      atom_codes(File, Codes)
   59    },
   60    string(Codes).
   61
   62dots --> ".", !, dots.
   63dots --> [].
 coverage_for_modules(:Goal, +Modules, -Module, -Coverage) is nondet
Non-deterministically finds Coverage dictionaries for all Modules. Bypasses those modules excluded from the required list, typically the list of modules belonging to a particular pack and excluding all system and other supporting modules.
   72coverage_for_modules(Goal, Modules, Module, Coverage) :-
   73    coverages_by_module(Goal, Coverages),
   74    Coverage = Coverages.Module,
   75    memberchk(Module, Modules)