Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 159 additions & 0 deletions hed/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,11 @@ def validate():
is_flag=True,
help="Suppress log output to stderr; only applicable when --log-file is used (logs go only to file)",
)
@optgroup.option(
"--no-log",
is_flag=True,
help="Disable all logging output",
)
def validate_bids_cmd(
data_path,
error_limit,
Expand All @@ -194,6 +199,7 @@ def validate_bids_cmd(
log_level,
log_file,
log_quiet,
no_log,
output_file,
print_output,
suffixes,
Expand Down Expand Up @@ -221,6 +227,8 @@ def validate_bids_cmd(
args.extend(["-lf", log_file])
if log_quiet:
args.append("-lq")
if no_log:
args.append("--no-log")
if output_file:
args.extend(["-o", output_file])
if print_output:
Expand All @@ -239,6 +247,157 @@ def validate_bids_cmd(
validate_bids_main(args)


@validate.command(
name="hed-string",
epilog="""
This command validates a HED annotation string against a specified HED schema
version. It can optionally process definitions and check for warnings in addition
to errors. Multiple schema versions can be specified for validation with library schemas.

\b
Examples:
# Basic validation of a HED string
hedpy validate hed-string "Event, (Sensory-event, (Visual-presentation, (Computer-screen, Face)))" -sv 8.3.0

# Validate with definitions
hedpy validate hed-string "Event, Def/MyDef" -sv 8.4.0 -d "(Definition/MyDef, (Action, Move))"

# Validate with multiple schemas (base + library)
hedpy validate hed-string "Event, Action" -sv 8.3.0 -sv score_1.1.0

# Check for warnings as well as errors
hedpy validate hed-string "Event, Action/Button-press" -sv 8.4.0 --check-for-warnings

# Save validation results to a file
hedpy validate hed-string "Event" -sv 8.4.0 -o validation_results.txt

# Output results in JSON format
hedpy validate hed-string "Event, Action" -sv 8.4.0 -f json

# Verbose output with informational messages
hedpy validate hed-string "Event, (Action, Move)" -sv 8.4.0 --verbose
""",
)
@click.argument("hed_string")
# Validation options
@optgroup.group("Validation options")
@optgroup.option(
"-sv",
"--schema-version",
required=True,
multiple=True,
metavar="VERSION",
help="HED schema version(s) to validate against (e.g., '8.3.0'). Can be specified multiple times for multiple schemas (e.g., -sv 8.3.0 -sv score_1.1.0)",
)
@optgroup.option(
"-d",
"--definitions",
default="",
metavar=METAVAR_STRING,
help="A string containing relevant HED definitions to use during validation (e.g., '(Definition/MyDef, (Action, Move))')",
)
@optgroup.option(
"-w",
"--check-for-warnings",
is_flag=True,
help="Check for warnings as well as errors",
)
# Output options
@optgroup.group("Output options")
@optgroup.option(
"-f",
"--format",
type=click.Choice(["text", "json"]),
default="text",
show_default="text",
help="Output format for validation results (text: human-readable; json: structured format for programmatic use)",
)
@optgroup.option(
"-o",
"--output-file",
type=click.Path(),
default="",
metavar=METAVAR_FILE,
help="Path for output file to hold validation results; if not specified, output to stdout",
)
# Logging options
@optgroup.group("Logging options")
@optgroup.option(
"-l",
"--log-level",
type=click.Choice(["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]),
default="WARNING",
show_default="WARNING",
help="Log level for diagnostic messages",
)
@optgroup.option(
"-v",
"--verbose",
is_flag=True,
help="Output informational messages (equivalent to --log-level INFO)",
)
@optgroup.option(
"-lf",
"--log-file",
type=click.Path(),
metavar=METAVAR_FILE,
help="File path for saving log output; logs still go to stderr unless --log-quiet is also used",
)
@optgroup.option(
"-lq",
"--log-quiet",
is_flag=True,
help="Suppress log output to stderr; only applicable when --log-file is used (logs go only to file)",
)
@optgroup.option(
"--no-log",
is_flag=True,
help="Disable all logging output",
)
def validate_hed_string_cmd(
hed_string,
schema_version,
definitions,
check_for_warnings,
format,
output_file,
log_level,
log_file,
log_quiet,
no_log,
verbose,
):
"""Validate a HED annotation string.

HED_STRING: The HED annotation string to validate (use quotes for strings with spaces or special characters).
"""
from hed.scripts.validate_hed_string import main as validate_string_main

args = [hed_string]
for version in schema_version:
args.extend(["-sv", version])
if definitions:
args.extend(["-d", definitions])
if check_for_warnings:
args.append("-w")
if format:
args.extend(["-f", format])
if output_file:
args.extend(["-o", output_file])
if log_level:
args.extend(["-l", log_level])
if log_file:
args.extend(["-lf", log_file])
if log_quiet:
args.append("-lq")
if no_log:
args.append("--no-log")
if verbose:
args.append("-v")

validate_string_main(args)


@schema.command(name="validate")
@click.argument("schema_path", type=click.Path(exists=True), nargs=-1, required=True)
@click.option("--add-all-extensions", is_flag=True, help="Always verify all versions of the same schema are equal")
Expand Down
2 changes: 1 addition & 1 deletion hed/errors/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Error handling module for HED."""

from .error_reporter import ErrorHandler, get_printable_issue_string, sort_issues, replace_tag_references
from .error_reporter import ErrorHandler, get_printable_issue_string, sort_issues, replace_tag_references, iter_errors
from .error_types import (
DefinitionErrors,
TemporalErrors,
Expand Down
1 change: 0 additions & 1 deletion hed/schema/hed_cache_lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import os
import portalocker


TIMESTAMP_FILENAME = "last_update.txt"
CACHE_TIME_THRESHOLD = 300 * 6

Expand Down
1 change: 0 additions & 1 deletion hed/schema/schema_io/wiki2schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
from hed.schema.schema_io.wiki_constants import HedWikiSection, WIKI_EXTRA_DICT
from hed.schema.schema_io import text_util


extend_here_line = "extend here"
invalid_characters_to_strip = ["​"]
tag_name_expression = r"(\*+|\'{3})(.*?)(\'{3})?\s*([\[\{]|$)+"
Expand Down
1 change: 0 additions & 1 deletion hed/schema/schema_validation_util_deprecated.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from hed.errors.error_reporter import ErrorHandler
from hed.errors.error_types import SchemaWarnings


ALLOWED_TAG_CHARS = "-"
ALLOWED_DESC_CHARS = "-_:;,./()+ ^"

Expand Down
109 changes: 109 additions & 0 deletions hed/scripts/script_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
"""
Utility functions for HED command-line scripts.

This module provides common functionality used across multiple HED scripts,
including logging configuration and argument handling.
"""

import json
import logging
import sys
from hed import _version as vr
from hed.errors import get_printable_issue_string, ErrorHandler, iter_errors


def setup_logging(log_level, log_file=None, log_quiet=False, verbose=False, no_log=False):
"""Configure logging for HED scripts.

Sets up the root logger with appropriate handlers for console (stderr) and/or
file output based on the provided arguments.

Parameters:
log_level (str): Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
log_file (str or None): Path to log file, or None for no file logging
log_quiet (bool): If True and log_file is specified, suppress stderr output
verbose (bool): If True, override log_level to INFO
no_log (bool): If True, disable all logging output

Returns:
logging.Logger: Configured logger instance
"""
# Disable logging completely if requested
if no_log:
logging.basicConfig(level=logging.CRITICAL + 1, handlers=[logging.NullHandler()], force=True)
return logging.getLogger()

# Determine effective log level
level = logging.INFO if verbose else getattr(logging, log_level.upper())

# Configure handlers
handlers = []
if log_file:
handlers.append(logging.FileHandler(log_file, mode="w", encoding="utf-8"))
if not (log_file and log_quiet):
handlers.append(logging.StreamHandler(sys.stderr))

# Configure root logger
logging.basicConfig(level=level, format="%(levelname)s: %(message)s", handlers=handlers, force=True)

return logging.getLogger()


def format_validation_results(
issue_list, output_format="text", title_message="Validation errors:", error_limit=None, errors_by_file=False
):
"""Format validation results in the requested output format.

This function provides a consistent way to format validation issues across
different HED validation scripts. It supports text, JSON, and pretty-printed
JSON formats, with optional error limiting for large result sets.

Parameters:
issue_list (list): List of validation issues (HedIssue objects)
output_format (str): Output format - 'text', 'json', or 'json_pp' (default: 'text')
title_message (str): Title/header for text output (default: 'Validation errors:')
error_limit (int or None): Maximum errors per code type to include in text output (default: None)
errors_by_file (bool): Apply error limit per file rather than globally (default: False)

Returns:
str: Formatted validation results as a string

Examples:
>>> issues = validator.validate(hed_string)
>>> output = format_validation_results(issues, "text", "HED string validation:")
>>> output = format_validation_results(issues, "json")
>>> output = format_validation_results(issues, "json_pp")
"""
if output_format == "json_pp":
# Pretty-printed JSON with version metadata
# Convert issues to JSON-serializable format
serializable_issues = list(iter_errors(issue_list))
return json.dumps({"issues": serializable_issues, "hedtools_version": str(vr.get_versions())}, indent=4)

elif output_format == "json":
# Compact JSON array of issues
# Convert issues to JSON-serializable format
serializable_issues = list(iter_errors(issue_list))
return json.dumps(serializable_issues)

elif output_format == "text":
# Human-readable text format with counts and optional filtering
output = f"Using HEDTools version: {str(vr.get_versions())}\n"
output += f"Number of issues: {len(issue_list)}\n"

# Apply error limiting if requested
if error_limit:
filtered_issues, code_counts = ErrorHandler.filter_issues_by_count(issue_list, error_limit, by_file=errors_by_file)
output += "Error counts by code: "
output += " ".join(f"{code}:{count}" for code, count in code_counts.items()) + "\n"
output += f"Number of issues after filtering: {len(filtered_issues)}\n"
issue_list = filtered_issues

# Format the issues with title
if issue_list:
output += get_printable_issue_string(issue_list, title_message, skip_filename=False)

return output

else:
raise ValueError(f"Unknown output format: {output_format}")
Loading