@@ -505,3 +505,117 @@ function bashunit::helper::find_function_at_line() {
505505
506506 echo " $best_match "
507507}
508+
509+ #
510+ # Extracts @tag annotations for a specific function from a test file.
511+ # Looks for comment lines `# @tag <name>` immediately above the function definition.
512+ #
513+ # @param $1 string Function name
514+ # @param $2 string Script file path
515+ #
516+ # @return string Comma-separated list of tags, or empty if none
517+ #
518+ function bashunit::helper::get_tags_for_function() {
519+ local function_name=" $1 "
520+ local script=" $2 "
521+
522+ if [[ ! -f " $script " && -n " ${BASHUNIT_WORKING_DIR:- } " ]]; then
523+ script=" $BASHUNIT_WORKING_DIR /$script "
524+ fi
525+
526+ if [[ ! -f " $script " ]]; then
527+ return
528+ fi
529+
530+ # Find the line number of the function definition
531+ local fn_line_num
532+ fn_line_num=$( grep -n -E " (function[[:space:]]+)?${function_name} [[:space:]]*\(\)" " $script " 2> /dev/null | head -1)
533+ if [ -z " $fn_line_num " ]; then
534+ return
535+ fi
536+ fn_line_num=" ${fn_line_num%%:* } "
537+
538+ # Walk backwards from the line above the function, collecting @tag comments
539+ local tags=" "
540+ local check_line=$(( fn_line_num - 1 ))
541+ while [ " $check_line " -ge 1 ]; do
542+ local content
543+ content=$( sed -n " ${check_line} p" " $script " )
544+ local _re=' ^[[:space:]]*#[[:space:]]*@tag[[:space:]]'
545+ if [[ " $content " =~ $_re ]]; then
546+ local tag_name
547+ tag_name=$( echo " $content " | sed -nE ' s/^[[:space:]]*#[[:space:]]*@tag[[:space:]]+//p' )
548+ if [ -n " $tag_name " ]; then
549+ if [ -z " $tags " ]; then
550+ tags=" $tag_name "
551+ else
552+ tags=" $tags ,$tag_name "
553+ fi
554+ fi
555+ elif [[ " $content " =~ ^[[:space:]]* # ]]; then
556+ # Other comment line, keep walking
557+ :
558+ elif [[ " $content " =~ ^[[:space:]]* $ ]]; then
559+ # Empty line, stop looking
560+ break
561+ else
562+ # Non-comment, non-empty line, stop
563+ break
564+ fi
565+ check_line= $(( check_line - 1 ))
566+ done
567+
568+ echo " $tags "
569+ }
570+
571+ #
572+ # Checks if a function's tags match the include/exclude filters.
573+ # Include uses OR logic (any match passes).
574+ # Exclude uses OR logic (any match fails).
575+ # Exclude takes precedence over include.
576+ #
577+ # @param $1 string Comma-separated tags for the function
578+ # @param $2 string Comma-separated include tags (empty = no filter)
579+ # @param $3 string Comma-separated exclude tags (empty = no filter)
580+ #
581+ # @return 0 if function should run, 1 if it should be skipped
582+ #
583+ function bashunit::helper::function_matches_tags() {
584+ local fn_tags=" $1 "
585+ local include_tags=" $2 "
586+ local exclude_tags=" $3 "
587+
588+ # Check exclude tags first (exclude wins over include)
589+ if [ -n " $exclude_tags " ]; then
590+ local IFS=' ,'
591+ local etag
592+ for etag in $exclude_tags ; do
593+ local check_tag
594+ for check_tag in $fn_tags ; do
595+ if [ " $check_tag " = " $etag " ]; then
596+ return 1
597+ fi
598+ done
599+ done
600+ fi
601+
602+ # Check include tags (OR logic: any match passes)
603+ if [ -n " $include_tags " ]; then
604+ if [ -z " $fn_tags " ]; then
605+ return 1
606+ fi
607+ local IFS=' ,'
608+ local itag
609+ for itag in $include_tags ; do
610+ local check_tag
611+ for check_tag in $fn_tags ; do
612+ if [ " $check_tag " = " $itag " ]; then
613+ return 0
614+ fi
615+ done
616+ done
617+ return 1
618+ fi
619+
620+ return 0
621+ }
0 commit comments