#! /bin/bash
# vim: ft=awk

# DOC: \page Plumedcheck Validating syntax with plumedcheck
# DOC: 
# DOC: Here is a list of coding rules that are enforced by the plumedcheck script.
# DOC: These rules are necessary to be sure that the codebase is consistent.
# DOC: Code not satisfying these rules will make the automatic test on travis-ci fail.
# DOC: In the following we provide a list of errors that are detected automatically,
# DOC: together with a short explanation of the reason we consider these as errors.
# DOC: The names used are the same ones reported by plumedcheck.
# DOC: The `plumedcheck` script can be used as is, but the simplest way to obtain a report
# DOC: is to go to the `buildroot/src` directory and type `make plumedcheck`.
# DOC: In case you think you have a valid reason for a code not passing the check
# DOC: to be merged, please contact the developers.

TEMP=$(mktemp -t plumed.XXXXXX)

cat > $TEMP << \EOF

# print usage 
function usage(){
  print "Usage:"
  print " plumedcheck [options] [files]"
  print
  print "Possible options are:"
  print " -h/--help: print help"
  print " --global-check: run global check; requires all sources and ac files to be passed"
  print
  print "Files can be"
  print " .h files: interpreted as c++ header file"
  print " .cpp files: interpreted as c++ source file"
  print " .ac files: interpreted as autoconf files"
  exit(0)
}

# print message
function message(severity,id,mes){
  if(global_check){
    printf("[global_check] (%s) :plmd_%s: %s\n",severity,id,mes) > "/dev/stderr";
  } else {
    printf("[%s:%s] (%s) :plmd_%s: %s\n",FILENAME,FNR,severity,id,mes) > "/dev/stderr";
  }
}

# print error message
# error messages make codecheck fail
function error(id,mes){
  message("error",id,mes)
}

# print information message
# information messages are neutral
function information(id,mes){
  message("information",id,mes)
}

# print style message
# style messages are possibly bad ideas but do not trigger errors
function style(id,mes){
  message("style",id,mes)
}

BEGIN{
# interpret command line arguments
  newargv[0]=ARGV[0]
  pre=""
  opt_global_check=0
  astyle="astyle"
  autoconf="autoconf"
  for(i=1;i<ARGC;i++){
    opt=pre ARGV[i]
    pre=""
    switch(opt){
      case "-h":
      case "--help":
        usage(); break;
      case "--global-check":
        opt_global_check=1; break;
      case /--astyle=.*/:
        sub("^--astyle=","",opt);
        astyle=opt;
      case /--astyle-options=.*/:
        sub("^--astyle-options=","",opt);
        astyle_options=opt;
        break;
        break;
      case /--autoconf=.*/:
        sub("^--autoconf=","",opt);
        autoconf=opt;
      case /-.*/:
        print "Unknown option " opt; exit(1)
      default:
        newargv[length(newargv)]=opt
    }
  }
# copy new command arguments
  ARGC=length(newargv)
  for(i=1;i<ARGC;i++) ARGV[i]=newargv[i]

# no arguments, print help
  if(ARGC==1) usage()


# setup:

# list of deprecated c++ headers
  deprecated_headers["complex.h"]=1
  deprecated_headers["ctype.h"]=1
  deprecated_headers["errno.h"]=1
  deprecated_headers["fenv.h"]=1
  deprecated_headers["float.h"]=1
  deprecated_headers["inttypes.h"]=1
  deprecated_headers["iso646.h"]=1
  deprecated_headers["limits.h"]=1
  deprecated_headers["locale.h"]=1
  deprecated_headers["math.h"]=1
  deprecated_headers["setjmp.h"]=1
  deprecated_headers["signal.h"]=1
  deprecated_headers["stdalign.h"]=1
  deprecated_headers["stdarg.h"]=1
  deprecated_headers["stdbool.h"]=1
  deprecated_headers["stddef.h"]=1
  deprecated_headers["stdint.h"]=1
  deprecated_headers["stdio.h"]=1
  deprecated_headers["stdlib.h"]=1
  deprecated_headers["string.h"]=1
  deprecated_headers["tgmath.h"]=1

# list of allowed module types
  allowed_module_types["always"]=1
  allowed_module_types["default-on"]=1
  allowed_module_types["default-off"]=1

# list of "outer modules", that is those
# that are not included in the kernel library
  outer_modules["wrapper"]=1
  outer_modules["main"]=1

# list of "core modules", that is those for which 
# a specific module namespace is not required
  core_modules["core"]=1
  core_modules["tools"]=1
  core_modules["reference"]=1

# declare these as empty arrays
  used_modules[0]=1
  delete used_modules

# create tmp dir for future usage
  "mktemp -dt plumed.XXXXXX" | getline tmpdir
  close("mktemp -dt plumed.XXXXXX")

# checking astyle presence
  astyle_available=!system(astyle " --version > /dev/null 2> /dev/null") 
  if(!astyle_available) error("astyle_not_available","astyle not available on this system, skipping astyle checks")
}

# for each input file
BEGINFILE{
# these variables are used to track if a file containes at least one item of a specific kind
# as such they should be reset for every new input file
  found_namespace_plmd=0
  found_namespace_module=0
  found_guard_ifdef=0
  found_non_preprocessor_lines=0

## not used, see below
#  found_include_system_header=0
#  report_include_system_headers=0

# guess type of file from extension
  filetype=""
  if(match(FILENAME,".*\\.h")) filetype="header"
  if(match(FILENAME,".*\\.cpp")) filetype="source"
  if(match(FILENAME,".*\\.ac")) filetype="autoconf"

# guess module name from directory
# only works when path is specified
  filename=FILENAME
  gsub("/[.]/","/",filename);
  gsub("^[.]/","",filename);
  if(!match(filename,".*/.*")) filename=ENVIRON["PWD"] "/" filename
  gsub("/+","/",filename); # fix multiple slashes
  filepath=filename
  module=""
  nf=split(filename,ff,"/");
  if(nf>1){
    module=ff[nf-1];
    if(!(module in module_type)){
      path=filepath
      sub("/.*$","/module.type",path);
# detect module type
      if((getline < path > 0) ){
        module_type[module]=$0
        information("module_type","module " module " has type '" module_type[module] "'" );
# DOC: :wrong_module_type:
# DOC: Module type as indicated in the `module.type` file located in the module directory is not valid.
# DOC: The admitted types are: `default-on`, which means the module is on by default, `default-off`, which means that
# DOC: they should be explicitly enabled, and `always`, which means they are always required.
        if(!(module_type[module] in allowed_module_types)) error("wrong_module_type","module " module " has a wrong module type '" module_type[module] "'");
      }
      close(path)
    }
# detect used modules
    if(filetype=="source" || filetype=="header") {
      if(!(module in used_modules)){
        tempfile = tmpdir "/used_modules." module
        path=filepath
        sub("/[^/]*$","",path);
# an explicit cd is required since the command might be run from a different directory
        system("cd " path "; make show_used_modules 2>/dev/null > " tempfile)
        while(getline < tempfile >0) if($1=="used_modules") for(i=2;i<=NF;i++) used_modules[module][$i]=1
        close(tempfile)
        system("rm " tempfile)
        used_modules_here=""
        if(isarray(used_modules[module])) {
          for(mod in used_modules[module]) used_modules_here=used_modules_here " " mod
        }
        information("used_modules",module " uses:" used_modules_here)
# DOC: :used_wrapper_module:
# DOC: Wrapper module should not be used in other modules (via `USE=wrapper` in `Makefile`).
# DOC: The reason is that this makes `libplumedKernel` dependent on the PLUMED wrappers.
# DOC: An exception is the `main` module, which needs to use the `wrapper` module.
# DOC: Notice that within the PLUMED library there is no need to use the external `cmd` interface
# DOC: since one can directly declare a `PlumedMain` object.
        if(!(module in outer_modules) && isarray(used_modules[module]) && ("wrapper" in used_modules[module]))
           error("used_wrapper_module","wrapped module should not be used except in main")
      }
    }
  }
  file=ff[nf];
  filebase=file;
  sub("\\..*","",filebase);

# checks that are done only on some modules will be skipped if the module name is not known
  if((filetype=="source" || filetype=="header") && !module) information("missing_module_name","module name is not know, some check will be skipped");

  if(astyle_available && (filetype=="source" || filetype=="header")){
    tempfile = tmpdir "/astyle"
    system(astyle " --options=" astyle_options " < " FILENAME " > " tempfile)
    s=system("diff -q " FILENAME " " tempfile ">/dev/null 2>/dev/null")
# check if astyle has been applied correctly
# DOC: :astyle:
# DOC: In order to keep the code readable, we use the astyle program to format
# DOC: all source files. This error indicates that the reported file has not been
# DOC: formatted correctly.  It is important that the version of astyle and the options
# DOC: exactly match the ones we use for testing. To this aim, you should use
# DOC: a command such as `make astyle` from the plumed root directory.
    if(s!=0) error("astyle","astyle not satisfied")
    system("rm " tempfile)
  }

# check if configure.ac is consistent with configure
  if(filetype=="autoconf"){
    tempfile = tmpdir "/configure"
    configurefile = FILENAME
    sub("\\.ac","",configurefile)
    system("autoconf " FILENAME " > " tempfile)
    s=system("diff -q " configurefile " " tempfile ">/dev/null 2>/dev/null")
# check if autoconf has been applied correctly
# DOC: :autoconf:
# DOC: In our git repository we distribute both `./configure` and `./configure.ac` files.
# DOC: When you modify the latter, the former should be regenerated using `autoconf` and
# DOC: committed to git. This error indicates that the `./configure.ac` and `./configure` files
# DOC: in the repository are not consistent.
    if(s!=0) error("autoconf","autoconf not satisfied")
    system("rm " tempfile)
  }

}

# line by line analysis
{
  if(filetype=="source" || filetype=="header"){
# detect plumed manual
    if(match($0,"^//[+]PLUMEDOC ")) in_plumed_doc=$NF
# detect doxygen manual - at least with /** and */ on separate lines
    if($1=="/**") in_doxygen=1
# detect copyright lines
    if($0=="/* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++") in_copyright=1;
# check direct inclusion of headers that would bypass module dependencies
    if(module && match($0,"^# *include +\"\\.\\.")) {
      included_module_name=$0;
      sub("^# *include +\"\\.\\./","",included_module_name);
      sub("/.*$","",included_module_name);
# DOC: :include_dots:
# DOC: It is considered an error to include a file using a path starting with `../dir`,
# DOC: where `dir` is not the name of a module explicitly declared as used in the Makefile.
# DOC: This might result in undetected dependences between the modules, which means that
# DOC: by enabling some set of modules one would result in a non-linkable library.
# DOC: As an exception, one can include files such as `../dir` where `dir` is the current module
# DOC: since this would obviously create no problem.
# DOC: This would simplify life when moving files from a directory to another.
      if(module != included_module_name) # ignore case where module is the same
      if(!isarray(used_modules[module]) || (included_module_name in used_modules[module])==0)
        error("include_dots","wrong include. Module '" included_module_name "' is not used.");
    }

# check if there is anything behind copyright and preprocessor commands
    if(!in_copyright && !match($0,"^# *include") && !match($0,"^# *ifndef") && !match($0,"^# *endif") && !match($0,"^# *define") && !match($0,"^ *$")) found_non_preprocessor_lines=1

## check if system headers are included after local headers.
## I am not sure it is a good idea, so I let it commented for now.
#    if(match($0,"^# *include +<")) found_include_system_header=1
## it is a bad habit to include local headers after system headers
## notice that I explicitly check for the final "h" in the included file so as to exclude
## included files ending with tmp that are somewhere used in plumed to include automatically generated source code
#   if(match($0,"^# *include +\".*h\"")){
#     if(found_include_system_header && !report_include_system_headers){
#       style("include_system_headers","include system headers before local headers is discouraged");
#       report_include_system_headers=1 # only report once per file
#     }
#   }

# check #include <file>
    if(!in_doxygen && match($0,"^# *include +<[^>]+>.*$")){
      h=$0
      sub("^# *include +<","",h)
      sub(">.*$","",h)
# check if deprecated headers are included
# DOC: :deprecated_headers:
# DOC: There is a list of headers that have been deprecated in C++ and should not be
# DOC: included. These headers should be replaced with their equivalent from the stdlib.
# DOC: E.g., use `#include <cstdlib>` instead of `#include <stdlib.h>`. Notice that
# DOC: using the modern header all the functions are declared inside the `std` namespace.
# DOC: Also notice that we have a special exception to allow them in Plumed.h.
      if(h in deprecated_headers && !(module in outer_modules)) error("deprecated_header","including deprecated header " h);
# DOC: :unsupported_header_regex:
# DOC: Header `<regex>`, although part of the C++11 standard, is not supported by gcc 4.8.
# DOC: In order to allow compatibility with old compilers, this header should not be used.
      if(h == "regex") error("unsupported_header_regex","including unsupported header " h);
# DOC: :external_header: (style)
# DOC: Header files should possibly not include other header files from external libraries.
# DOC: Indeed, once PLUMED is installed, if paths are not set correctly the include files of the other libraries
# DOC: might not be reachable anymore. This is only a stylistic warning now. Notice that
# DOC: with the current implementation of class Communicator it is necessary to include
# DOC: `mpi.h` in a header file.
      if(filetype=="header" && h~"\\.h") style("external_header","including external header " h " in a header file");
    }

# check #include "file"
    if(!in_doxygen && match($0,"^# *include +\"[^\"]+\".*$")){
      h=$0
      sub("^# *include +\"","",h)
      sub("\".*$","",h)
# check if a .inc file, which is temporary and not installed, is included in a header file.
# this might create inconsistencies in the installed plumed header files.
# DOC: :non_h_header:
# DOC: Files with `.inc` extension are generated by PLUMED while it is compiled and are
# DOC: not installed. They should not be directly included in header files, otherwise
# DOC: those header files could be not usable once PLUMED is installed.
      if(filetype=="header" && h!~"\\.h") error("non_h_header","including non '.h' file " h " in a header file");
      if(h~"^[A-Za-z0-9_][A-Za-z0-9_]*/") {
        sub("/.*$","",h)
# DOC: :include_non_used_module:
# DOC: When including a file in the form `"dir/file.h"`, `dir` should be an used module.
# DOC: This makes sure that we do not include system files that by chance are named as PLUMED modules.
# DOC: Indeed, when including `"file"` if the file is not found in the current path it is searched in
# DOC: system directories.
        if(!isarray(used_modules[module]) || (h in used_modules[module])==0)
            error("include_non_used_module","including file in form " h "/file where " h " is not a used module");
      }
    }

# check if namespace PLMD is present
    if(match($0,"^ *namespace *PLMD")) found_namespace_plmd=1

# check if namespace for module is present
    if(match($0,"^ *namespace *" module)) found_namespace_module=1

# take note of registered actions
    if(match($0,"^ *PLUMED_REGISTER_ACTION")){
      action=$0
      sub("^ *PLUMED_REGISTER_ACTION\\(.*, *[\"]","",action);
      sub("[\"].*$","",action);
      information("registered_action","action " action);
# DOC: :multi_registered_action:
# DOC: Each action should be registered (with `PLUMED_REGISTER_ACTION`) once and only once.
      if(action in registered_actions) error("multi_registered_action","action " action " already registered at "registered_actions[action]);
      registered_actions[action]=FILENAME ":" FNR;
    }

# take note of registered cltools
    if(match($0,"^ *PLUMED_REGISTER_CLTOOL")){
      cltool=$0
      sub("^ *PLUMED_REGISTER_CLTOOL\\(.*, *[\"]","",cltool);
      sub("[\"].*$","",cltool);
      information("registered_cltool","cltool " cltool);
# DOC: :multi_registered_cltool:
# DOC: Each cltool should be registered (with `PLUMED_REGISTER_CLTOOL`) once and only once.
      if(cltool in registered_cltools) error("multi_registered_cltool","cltool " cltool " already registered at "registered_cltools[cltool]);
      registered_cltools[cltool]=FILENAME ":" FNR;
    }

# take note of registered vessels
    if(match($0,"^ *PLUMED_REGISTER_VESSEL")){
      vessel=$0
      sub("^ *PLUMED_REGISTER_VESSEL\\(.*, *[\"]","",vessel);
      sub("[\"].*$","",vessel);
      information("registered_vessel","vessel " vessel);
# DOC: :multi_registered_vessel:
# DOC: Each vessel should be registered (with `PLUMED_REGISTER_VESSEL`) once and only once.
      if(vessel in registered_vessels) error("multi_registered_vessel","vessel " vessel " already registered at "registered_vessels[vessel]);
      registered_vessels[vessel]=FILENAME ":" FNR;
    }

# take note of registered metrics
    if(match($0,"^ *PLUMED_REGISTER_METRIC")){
      metric=$0
      sub("^ *PLUMED_REGISTER_METRIC\\(.*, *[\"]","",metric);
      sub("[\"].*$","",metric);
      information("registered_metric","metric " metric);
# DOC: :multi_registered_metric:
# DOC: Each metric should be registered (with `PLUMED_REGISTER_METRIC`) once and only once.
      if(metric in registered_metrics) error("multi_registered_metric","metric " metric " already registered at "registered_metrics[metric]);
      registered_metrics[metric]=FILENAME ":" FNR;
    }

# take note of documented actions or cltools
    if(match($0,"^//[+]PLUMEDOC ")){
      doc=$NF;
# DOC: :multi_doc:
# DOC: An action or cltool should be documented once and only once. Notice that because of the
# DOC: way the manual is generated, a cltool cannot have the same name of an action.
      if(doc in plumed_doc) error("multi_doc","doc " doc " already at "plumed_doc[action]);
      plumed_doc[doc]=FILENAME ":" FNR;
      n=split($(NF-1),array,"_");
      if(n==1) {
        switch($(NF-1)){
        case "TOOLS":
          plumed_doc_cltools[doc]=FILENAME ":" FNR;
          information("documented_cltool","doc " doc);
          break;
        case "INTERNAL":
          plumed_doc_internal[doc]=FILENAME ":" FNR;
          information("documented_internal","doc " doc);
          break;
        default:
          plumed_doc_action[doc]=FILENAME ":" FNR;
          information("documented_action","doc " doc);
        }
      } else {
        switch(array[2]){
        case "TOOLS":
          plumed_doc_cltools[doc]=FILENAME ":" FNR;
          information("documented_cltool","doc " doc);
          break;
        case "INTERNAL":
          plumed_doc_internal[doc]=FILENAME ":" FNR;
          information("documented_internal","doc " doc);
          break;
        default:
          plumed_doc_action[doc]=FILENAME ":" FNR;
          information("documented_action","doc " doc);
        }
      }
    }

# check if, besides the \par Examples text, there is a verbatim command
    if(in_plumed_doc && (in_plumed_doc in provide_examples) && match($0,"^ *\\\\verbatim *$")){
      provide_verbatim[in_plumed_doc]=1
    }
# check if, besides the \par Examples text, there is a plumedfile command
# now it is considered equivalent to a verbatim
# later on we might make plumedfile compulsory instead of verbatim
    if(in_plumed_doc && (in_plumed_doc in provide_examples) && match($0,"^ *\\\\plumedfile *$")){
      provide_plumedfile[in_plumed_doc]=1
    }
#print match($0,"^ *\\\\verbatim *$"),"X" $0 "X"

# take note of actions or cltools that provide examples
    if(match($0,"\\\\par Examples")){
      provide_examples[in_plumed_doc]=FILENAME ":" FNR;
    }

# check if guard is present
    guard="^#ifndef __PLUMED_" module "_" filebase "_h"
    if(match($0,guard)) found_guard_ifdef=1

# check that "using namespace" statements are not used in header files
# DOC: :using_namespace_in_header:
# DOC: A statement `using namespace` in header files is considered bad practice. Indeed, this would imply that
# DOC: any other file including that header file would be using the same namespace.
# DOC: This is true also for `std` namespace, and that's why you typically find full specifications
# DOC: such as `std::string` in header files. In source files you are free to use `using namespace PLMD` and/or
# DOC: `using namespace std` if you like, since these commands will only affect the source file where they are used.
    if(filetype=="header" && match($0,"using *namespace") && !in_plumed_doc && !in_doxygen) error("using_namespace_in_header","using namespace statement in header file")
  }

# analyze HAS macros (bouth source and autoconf)
  if(match($0,"__PLUMED_HAS_[A-Z_]*")){
    string=substr( $0, RSTART, RLENGTH )
    sub("__PLUMED_HAS_","",string);
    if(filetype=="autoconf"){
# take note of defined HAS macros
      if(plumed_definehas_file[string,FILENAME]!="used") information("defined_has",string);
      plumed_definehas_file[string,FILENAME]="used";
      plumed_definehas[string]=FILENAME ":" FNR;
    } else {
# take note of used HAS macros
      if(plumed_usehas_file[string,FILENAME]!="used") information("used_has",string);
# DOC: :used_has_in_header: (style)
# DOC: Using `__PLUMED_HAS_SOMETHING` macros in headers should be avoided since
# DOC: it could make it difficult to make sure the same macros are correctly defined
# DOC: when using PLUMED as a library. This is only a stylistic warning now. Notice that
# DOC: with the current implementation of class Communicator it is necessary to include
# DOC: `mpi.h` in a header file and thus to use `__PLUMED_HAS_MPI`.
      if(plumed_usehas_file[string,FILENAME]!="used" && filetype=="header") style("used_has_in_header",string);
      plumed_usehas_file[string,FILENAME]="used";
      plumed_usehas[string]=FILENAME ":" FNR;
    }
  }

  if(filetype=="source" || filetype=="header"){
# detect plumed manual
    if(match($0,"^//[+]ENDPLUMEDOC")) in_plumed_doc=0
# detect doxygen manual - at least with /** and */ on separate lines
    if($1=="*/") in_doxygen=0
# detect copyright lines
    if($0=="+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */") in_copyright=0;
  }

}

# at the end of each file:
ENDFILE{
# check for namespaces
  if(found_non_preprocessor_lines && (filetype=="source" || filetype=="header") && !(module in outer_modules) ){
# DOC: :missing_namespace_plmd:
# DOC: Every source file should contain a `PLMD` namespace.
# DOC: Notice that files in the "outer  modules" `wrapper` and `main`, that is those that
# DOC: are not included in the kernel library, are exempted from this rule.
    if(!found_namespace_plmd) error("missing_namespace_plmd","missing PLMD namespace");
# DOC: :missing_namespace_module:
# DOC: Every source file should contain a sub namespace with the same name of the module itself.
# DOC: Notice that there are important exceptions to this rule:
# DOC: - "outer modules" `wrapper` and `main`, that are not included in the kernel library.
# DOC: - "core modules" that are part of the core of PLUMED. Since the classes implemented here are
# DOC:   widely used within PLUMED, they are directly defined in the PLMD namespace.
    if(module && !(module in core_modules) && !found_namespace_module) error("missing_namespace_module","missing " module " namespace")
  }

  if(filetype=="header"){
# this can be done only if module name is known:
# DOC: :missing_guard:
# DOC: Every header file should contain a proper guard to avoid double inclusion.
# DOC: The format of the guard is fixed and should be `__PLUMED_modulename_filename_h`, where
# DOC: `modulename` is the name of the module and `filename` is the name of the file, without suffix.
# DOC: This choice makes guards unique within the whole codebase.
    if(module && !found_guard_ifdef) error("missing_guard","missing ifndef guard");
  }

# check if every header has a corresponding cpp
# this is done only for non-trivial headers (i.e. we skip headers that just include another header)
   if(filetype=="header" && found_non_preprocessor_lines){
     notfound=0
     cppfile=FILENAME
     sub("\\.h$",".cpp",cppfile);
     if((getline < cppfile < 0)){
       cppfile=FILENAME
       sub("\\.h$",".c",cppfile);
       if((getline < cppfile < 0)) notfound=1
     }
     if(notfound){
# DOC: :non_existing_cpp:
# DOC: For every header file there should exist a corresponding source file with the same name.
# DOC: Notice that dummy headers that only include another header or which only define preprocessor
# DOC: macros are exempted from this rule.
       error("non_existing_cpp","file " file " is a header but there is no corresponding source");
     }
     while((getline < cppfile)>=0){
       if(match($0,"^ *#include")){
         sub("^ *#include","");
# DOC: :non_included_h:
# DOC: The source file corresponding to a header file (that is: with the same name) should include it as the first included file.
# DOC: This is to make sure that all the header files that we install can be individually included and compiled.
         if($1!="\"" file "\"") error("non_included_h","file " file " is a header but " cppfile " does not include it as first include");
         break;
       }
     }
     close(cppfile)
   }
}

# after processing all files, perform some global check
END{

# checks are only done if enabled
  if(!opt_global_check) exit(0)

# this is required to properly report messages
  global_check=1

  for(action in registered_actions){
    if(!(action in plumed_doc_action)){
# DOC: :undocumented_action:
# DOC: Every action that is registered with `PLUMED_REGISTER_ACTION` should also be documented
# DOC: in a PLUMEDOC page.
      error("undocumented_action","action " action " at " registered_actions[action] " is not documented")
    } else if(!(action in provide_examples)){
# DOC: :action_without_examples:
# DOC: Every action that is registered should contain an `Example` section in its documentation.
# DOC: Notice that if the `Example` section is not added the manual will not show the registered keywords.
      error("action_without_examples","action " action " at " plumed_doc_action[action] " has no example")
    } else if(!(action in provide_plumedfile)){
# DOC: :action_without_verbatim:
# DOC: Every action that is registered should contain an example in the form of a `plumedfile` section.
# DOC: This is currently a stylistic warning since there are many actions not satisfying this requirement.
      style("action_without_plumedfile","action " action " at " provide_examples[action] " has no plumedfile")
    }
  }

  for(action in plumed_doc_action){
    if(!(action in registered_actions))
# DOC: :unregistered_action:
# DOC: Every action that is documented should be also registered using `PLUMED_REGISTER_ACTION`.
      error("unregistered_action"," action " action " at " plumed_doc_action[action] " is not registered");
  }

  for(cltool in registered_cltools){
    if(!(cltool in plumed_doc_cltools)){
# DOC: :undocumented_cltool:
# DOC: Every cltool that is registered with `PLUMED_REGISTER_CLTOOL` should also be documented
# DOC: in a PLUMEDOC page.
      error("undocumented_cltool","cltool " cltool " at " registered_cltools[cltool] " is not documented")
    } else if(!(cltool in provide_examples)){
# DOC: :cltool_without_examples:
# DOC: Every cltool that is registered should contain an `Example` section in its documentation.
# DOC: Notice that if the `Example` section is not added the manual will not show the registered options.
      error("cltool_without_examples","cltool " cltool " at " plumed_doc_cltools[cltool] " has no example")
    } else if(!(cltool in provide_verbatim)){
# DOC: :cltool_without_verbatim:
# DOC: Every cltool that is registered should contain an example in the form of a `verbatim` section.
      error("cltool_without_verbatim","cltool " cltool " at " provide_examples[cltool] " has no verbatim")
    }
  }

  for(cltool in plumed_doc_cltools){
    if(!(cltool in registered_cltools))
# DOC: :unregistered_cltool:
# DOC: Every cltool that is documented should be also registered using `PLUMED_REGISTER_CLTOOL`.
      error("unregistered_cltool"," cltool " cltool " at " plumed_doc_cltools[cltool] " is not registered");
  }

  for(has in plumed_usehas){
    if(!(has in plumed_definehas)){
# DOC: :undefined_has:
# DOC: Every macro in the form `__PLUMED_HAS_SOMETHING` that is used in the code
# DOC: should be defined in configure.ac. This check is made to avoid errors with mis-typed macros.
      error("undefined_has","has " has " at " plumed_usehas[has] " is not defined")
    }
  }

  for(has in plumed_definehas){
    if(!(has in plumed_usehas)){
# DOC: :unused_has:
# DOC: Every macro in the form `__PLUMED_HAS_SOMETHING` that is defined in configure.ac
# DOC: should be used at least once in the code. This check is made to avoid errors with mis-typed macros.
      error("unused_has","has " has " at " plumed_definehas[has] " is not used")
    }
  }

# remove temporary directory
  system("rmdir " tmpdir);
}

EOF

gawk -f $TEMP $@
