diff --git a/README.md b/README.md index 7d9ec59..651179e 100644 --- a/README.md +++ b/README.md @@ -131,9 +131,6 @@ Disable/enable work the same as for timer jobs — disable stops the service and ### Managing jobs ```bash -# Print all jobs in crontab-like format to stdout (useful for scripting) -systab -l - # Edit all jobs in your $EDITOR (crontab-style) systab -e @@ -209,7 +206,6 @@ Management (accept hex ID or name): -D Disable a job -E Enable a disabled job -e Edit jobs in crontab-like format - -l Print jobs in crontab-like format to stdout -L [id|name] [filter] List job logs (optionally for a specific job and/or filtered) -S [id|name] Show status of all managed jobs (or a specific job) -C Clean up completed one-time jobs diff --git a/badges/tests.json b/badges/tests.json index 3e4522a..bd09d8e 100644 --- a/badges/tests.json +++ b/badges/tests.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, "label": "tests", - "message": "88 passed", + "message": "83 passed", "color": "brightgreen" } diff --git a/demo/editmode.tape b/demo/editmode.tape index 8ddf4e9..fdb6f66 100644 --- a/demo/editmode.tape +++ b/demo/editmode.tape @@ -25,18 +25,6 @@ Type "systab -t daily -c '/home/user/backup.sh' -i" Enter Sleep 2s -# List jobs in crontab format without opening editor -Hide -Type "./demo/note.sh 'Listing jobs in crontab format (-l)'" -Enter -Sleep 500ms -Show -Sleep 1s -Type "systab -l" -Sleep 500ms -Enter -Sleep 2s - # Open edit mode with EDITOR=nano for visibility Hide Type "./demo/note.sh 'Opening edit mode — add notifications, create and modify jobs'" diff --git a/systab b/systab index e21273f..e85e8ff 100755 --- a/systab +++ b/systab @@ -18,7 +18,6 @@ opt_notify=false opt_email="" opt_edit=false opt_list=false -opt_print_crontab=false opt_clean=false opt_status=false opt_disable="" @@ -49,7 +48,6 @@ Management Options (accept hex ID or name): -D Disable a job -E Enable a disabled job -e Edit jobs in crontab-like format - -l Print jobs in crontab-like format to stdout -L [id|name] [filter] List job logs (optionally for a specific job and/or filtered) -S [id|name] Show status of all managed jobs (or a specific job) -C Clean up completed one-time jobs @@ -79,9 +77,6 @@ EXAMPLES: # Create a persistent service job $SCRIPT_NAME -s -n foobar -c "/usr/bin/foobar.sh" - # Print jobs in crontab-like format (useful for scripting) - $SCRIPT_NAME -l - # Edit existing jobs (supports adding notifications via ID:flags syntax) $SCRIPT_NAME -e @@ -614,70 +609,6 @@ createJobFromEdit() { echo "$_created_id" } -# Print aligned job lines to stdout (no header/footer) -printCrontabContent() { - local -a id_fields schedules commands disabled - local job - - while IFS= read -r job; do - [[ -z "$job" ]] && continue - local timer_file="$SYSTEMD_USER_DIR/${job}.timer" - local service_file="$SYSTEMD_USER_DIR/${job}.service" - if [[ -f "$timer_file" && -f "$service_file" ]]; then - local id schedule command flags id_field - id=$(jobId "$job") - schedule=$(grep "^OnCalendar=" "$timer_file" | cut -d= -f2-) - command=$(getJobCommand "$service_file") - flags=$(grep "^# SYSTAB_FLAGS=" "$service_file" 2>/dev/null | sed 's/^# SYSTAB_FLAGS=//' || true) - [[ -n "$flags" ]] && id_field="$id:$flags" || id_field="$id" - id_fields+=("$id_field") - schedules+=("$schedule") - commands+=("$command") - if isJobEnabled "$job"; then disabled+=(false); else disabled+=(true); fi - fi - done < <(getManagedUnits timer) - - while IFS= read -r job; do - [[ -z "$job" ]] && continue - local service_file="$SYSTEMD_USER_DIR/${job}.service" - if [[ -f "$service_file" ]]; then - local id command flags id_field - id=$(jobId "$job") - command=$(getJobCommand "$service_file") - flags=$(grep "^# SYSTAB_FLAGS=" "$service_file" 2>/dev/null | sed 's/^# SYSTAB_FLAGS=//' || true) - [[ -n "$flags" ]] && id_field="$id:$flags" || id_field="$id:s" - id_fields+=("$id_field") - schedules+=("service") - commands+=("$command") - if isJobEnabled "$job"; then disabled+=(false); else disabled+=(true); fi - fi - done < <(getManagedServiceJobs) - - # Compute column widths; disabled lines account for the "# " prefix - local max_id=0 max_sched=0 i len - for i in "${!id_fields[@]}"; do - len=${#id_fields[$i]} - ${disabled[$i]} && len=$((len + 2)) - [[ $len -gt $max_id ]] && max_id=$len - len=${#schedules[$i]} - [[ $len -gt $max_sched ]] && max_sched=$len - done - - # Print aligned lines - for i in "${!id_fields[@]}"; do - if ${disabled[$i]}; then - printf '# %-*s | %-*s | %s\n' $((max_id - 2)) "${id_fields[$i]}" "$max_sched" "${schedules[$i]}" "${commands[$i]}" - else - printf '%-*s | %-*s | %s\n' "$max_id" "${id_fields[$i]}" "$max_sched" "${schedules[$i]}" "${commands[$i]}" - fi - done -} - -# List jobs in crontab-like format to stdout -listCrontab() { - printCrontabContent -} - # Edit jobs in crontab-like format editJobs() { local temp_file orig_file @@ -686,16 +617,82 @@ editJobs() { # shellcheck disable=SC2064 trap "rm -f '$temp_file' '$orig_file'" EXIT - # Build crontab content: jobs first, condensed hint at bottom + # Build crontab content { - printCrontabContent - cat <<'HINT' -### ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # -### format: ID[:FLAGS] | SCHEDULE | COMMAND # -### add: new | daily | cmd # -### flags: n=name / i / e=addr / o[=N] / s # -### comment out = disable / delete = remove # -HINT + cat <<'HEADER' +# systab jobs — edit schedule/command, enable/disable, add/remove lines +# Format: ID[:FLAGS] | SCHEDULE | COMMAND (pipe-separated) +# Remove a line to delete a job. +# Add a line with "new" as ID to create a job: new | daily | /path/to/cmd +# Comment out a line to disable, uncomment to re-enable. +# +# Flags (append to ID with ':'): s - service, i - notify desktop, e=addr - send email, +# o - include output (default 10 lines), o=N - include N lines of output, +# n=name = human-readable name (usable in place of hex ID) +# a1b2c3:n=backup,i | daily | cmd named job with desktop notification +# a1b2c3:i,e=user@host | daily | cmd email and desktop notificaton +# a1b2c3:s | service | cmd persistent service (no timer) +# new:s,n=name | service | cmd new persistent service +# +# Schedule formats (systemd OnCalendar): +# service persistent service (started on login, auto-restarted) +# hourly, daily, weekly, monthly, yearly +# *:0/15 every 15 minutes +# *-*-* 02:00:00 daily at 2am +# Mon *-*-* 09:00 every Monday at 9am +# *-*-01 00:00:00 first of every month +######################### +### add entries below ### +######################### +HEADER + + local job + while IFS= read -r job; do + [[ -z "$job" ]] && continue + + local timer_file="$SYSTEMD_USER_DIR/${job}.timer" + local service_file="$SYSTEMD_USER_DIR/${job}.service" + + if [[ -f "$timer_file" && -f "$service_file" ]]; then + local id schedule command flags id_field + id=$(jobId "$job") + schedule=$(grep "^OnCalendar=" "$timer_file" | cut -d= -f2-) + command=$(getJobCommand "$service_file") + flags=$(grep "^# SYSTAB_FLAGS=" "$service_file" 2>/dev/null | sed 's/^# SYSTAB_FLAGS=//' || true) + if [[ -n "$flags" ]]; then + id_field="$id:$flags" + else + id_field="$id" + fi + if isJobEnabled "$job"; then + printf '%s | %s | %s\n' "$id_field" "$schedule" "$command" + else + printf '# %s | %s | %s\n' "$id_field" "$schedule" "$command" + fi + fi + done < <(getManagedUnits timer) + + while IFS= read -r job; do + [[ -z "$job" ]] && continue + + local service_file="$SYSTEMD_USER_DIR/${job}.service" + if [[ -f "$service_file" ]]; then + local id command flags id_field + id=$(jobId "$job") + command=$(getJobCommand "$service_file") + flags=$(grep "^# SYSTAB_FLAGS=" "$service_file" 2>/dev/null | sed 's/^# SYSTAB_FLAGS=//' || true) + if [[ -n "$flags" ]]; then + id_field="$id:$flags" + else + id_field="$id:s" + fi + if isJobEnabled "$job"; then + printf '%s | service | %s\n' "$id_field" "$command" + else + printf '# %s | service | %s\n' "$id_field" "$command" + fi + fi + done < <(getManagedServiceJobs) } > "$temp_file" # Save original for diffing @@ -833,7 +830,7 @@ HINT for id in "${!orig_jobs[@]}"; do all_orig_ids["$id"]=1; done for id in "${!orig_commented[@]}"; do all_orig_ids["$id"]=1; done - local created=0 deleted=0 updated=0 enabled=0 n_disabled=0 needs_reload=false + local created=0 deleted=0 updated=0 enabled=0 disabled=0 needs_reload=false # Deletions: IDs in original (active or commented) but absent from edited entirely for id in "${!all_orig_ids[@]}"; do @@ -905,7 +902,7 @@ HINT if ! $was_commented && $now_commented; then disableJob "$jname" echo "Disabled: $id" - n_disabled=$((n_disabled + 1)) + disabled=$((disabled + 1)) needs_reload=true elif $was_commented && ! $now_commented; then enableJob "$jname" @@ -976,7 +973,7 @@ HINT fi echo "" - echo "Summary: $created created, $updated updated, $deleted deleted, $enabled enabled, $n_disabled disabled" + echo "Summary: $created created, $updated updated, $deleted deleted, $enabled enabled, $disabled disabled" } # Build a job list from opt_jobid (single job) or getManagedUnits (all jobs) @@ -1185,7 +1182,7 @@ cleanJobs() { # Parse command-line options parseOptions() { - while getopts "t:sc:f:n:im:oD:E:elLSCh" opt; do + while getopts "t:sc:f:n:im:oD:E:eLSCh" opt; do case $opt in t) opt_time="$OPTARG" ;; s) opt_service=true ;; @@ -1209,7 +1206,6 @@ parseOptions() { D) opt_disable="$OPTARG" ;; E) opt_enable="$OPTARG" ;; e) opt_edit=true ;; - l) opt_print_crontab=true ;; L) opt_list=true ;; S) opt_status=true ;; C) opt_clean=true ;; @@ -1251,17 +1247,16 @@ parseOptions() { [[ -n "$opt_disable" ]] && manage_count=$((manage_count + 1)) [[ -n "$opt_enable" ]] && manage_count=$((manage_count + 1)) $opt_edit && manage_count=$((manage_count + 1)) - $opt_print_crontab && manage_count=$((manage_count + 1)) $opt_list && manage_count=$((manage_count + 1)) $opt_status && manage_count=$((manage_count + 1)) $opt_clean && manage_count=$((manage_count + 1)) if [[ $manage_count -gt 1 ]]; then - error "Options -D, -E, -e, -l, -L, -S, and -C are mutually exclusive" + error "Options -D, -E, -e, -L, -S, and -C are mutually exclusive" fi if [[ $manage_count -gt 0 ]] && { [[ -n "$opt_time$opt_command$opt_file" ]] || $opt_service; }; then - error "Management options -D, -E, -e, -l, -L, -S, and -C cannot be used with job creation options" + error "Management options -D, -E, -e, -L, -S, and -C cannot be used with job creation options" fi if [[ -n "$opt_command" && -n "$opt_file" ]]; then @@ -1292,8 +1287,6 @@ main() { toggleJobById "$opt_enable" enable elif $opt_edit; then editJobs - elif $opt_print_crontab; then - listCrontab elif $opt_list; then listLogs elif $opt_status; then diff --git a/test.sh b/test.sh index 972d94e..1cdb824 100755 --- a/test.sh +++ b/test.sh @@ -404,46 +404,12 @@ assert_output "enable already enabled service" "Already enabled:" $SYSTAB -E "$i # Edit mode shows service jobs with 'service' in schedule column # (mirrors tape: EDITOR=nano systab -e shows "id:s | service | cmd") edit_output=$(EDITOR=cat $SYSTAB -e 2>&1 || true) -if [[ "$edit_output" == *"| service"* ]]; then +if [[ "$edit_output" == *"| service |"* ]]; then pass "edit mode shows service job with 'service' schedule" else fail "edit mode shows service job with 'service' schedule" "not found in: $edit_output" fi -# -l prints crontab format to stdout -list_output=$($SYSTAB -l 2>&1) -if [[ "$list_output" == *"| service"* ]]; then - pass "-l prints service job with 'service' schedule" -else - fail "-l prints service job with 'service' schedule" "not found in: $list_output" -fi -if [[ "$list_output" == *"$id_recurring"* ]]; then - pass "-l includes timer job" -else - fail "-l includes timer job" "not found in: $list_output" -fi - -# -l pipe separators are aligned (all first pipes at same column) -pipe_cols=() -while IFS= read -r line; do - [[ "$line" == *"|"* ]] || continue - # skip hint/separator lines (not job entries) - [[ "$line" =~ ^# ]] && [[ ! "$line" =~ ^#[[:space:]][0-9a-f]{6} ]] && continue - pipe_prefix="${line%%|*}" - pipe_cols+=("${#pipe_prefix}") -done <<< "$list_output" -if [[ ${#pipe_cols[@]} -gt 1 ]]; then - unique_cols=$(printf '%s\n' "${pipe_cols[@]}" | sort -u | wc -l) - if [[ "$unique_cols" -eq 1 ]]; then - pass "-l pipe separators are aligned" - else - fail "-l pipe separators are aligned" "first-pipe columns: ${pipe_cols[*]}" - fi -fi - -assert_failure "-l and -e are mutually exclusive" $SYSTAB -l -e -assert_failure "-l cannot be used with job creation options" $SYSTAB -l -t daily -c "echo test" - # Mutually exclusive flags (mirrors tape design: -s conflicts with -t/-i/-m/-o) assert_failure "-s and -t are mutually exclusive" $SYSTAB -s -t daily -c "echo test" assert_failure "-s and -i are mutually exclusive" $SYSTAB -s -i -c "echo test"