fedora-csb-system-manager
1#!/usr/bin/env bash
2# journelly-manager - CLI tool for Journelly journal file manipulation via Emacs batch mode
3# Copyright (C) 2026 Vincent Demeester
4# Loads elisp from site-lisp for consistency with interactive Emacs
5
6set -euo pipefail
7
8# Configuration
9EMACS="${EMACS:-emacs}"
10EMACS_DIR="${EMACS_DIR:-$HOME/.config/emacs}"
11SITE_LISP="$EMACS_DIR/site-lisp"
12
13# Debug mode
14DEBUG="${DEBUG:-0}"
15
16# Colors for output (if not outputting JSON)
17if [[ -t 1 ]] && [[ "${JSON_OUTPUT:-1}" != "1" ]]; then
18 RED='\033[0;31m'
19 GREEN='\033[0;32m'
20 YELLOW='\033[1;33m'
21 BLUE='\033[0;34m'
22 NC='\033[0m' # No Color
23else
24 RED=''
25 GREEN=''
26 YELLOW=''
27 BLUE=''
28 NC=''
29fi
30
31# Error handling
32error() {
33 echo -e "${RED}Error: $*${NC}" >&2
34 exit 1
35}
36
37debug() {
38 if [[ "$DEBUG" == "1" ]]; then
39 echo -e "${YELLOW}Debug: $*${NC}" >&2
40 fi
41}
42
43info() {
44 if [[ "${JSON_OUTPUT:-1}" != "1" ]]; then
45 echo -e "${BLUE}$*${NC}" >&2
46 fi
47}
48
49success() {
50 if [[ "${JSON_OUTPUT:-1}" != "1" ]]; then
51 echo -e "${GREEN}$*${NC}" >&2
52 fi
53}
54
55# Check dependencies
56check_deps() {
57 if ! command -v "$EMACS" &> /dev/null; then
58 error "Emacs not found. Set EMACS environment variable or install emacs."
59 fi
60
61 if [[ ! -d "$SITE_LISP" ]]; then
62 error "Emacs site-lisp directory not found at: $SITE_LISP"
63 fi
64
65 if [[ ! -f "$SITE_LISP/journelly-batch-functions.el" ]]; then
66 error "journelly-batch-functions.el not found in site-lisp"
67 fi
68}
69
70# Run Emacs batch command
71run_batch() {
72 local function_call="$1"
73 debug "Running: $EMACS --batch --directory \"$SITE_LISP\" --load journelly-batch-functions.el --eval \"$function_call\""
74
75 "$EMACS" --batch \
76 --directory "$SITE_LISP" \
77 --load journelly-batch-functions.el \
78 --eval "$function_call" 2>&1
79}
80
81# Usage information
82usage() {
83 cat <<EOF
84journelly-manager - CLI tool for managing Journelly journal files
85
86USAGE:
87 journelly-manager <command> [arguments]
88
89COMMANDS:
90 create FILE LOCATION CONTENT [options]
91 Create new journal entry
92
93 Options:
94 --latitude=LAT GPS latitude
95 --longitude=LON GPS longitude
96 --temperature=TEMP Temperature (e.g., "15,2°C")
97 --condition=COND Weather condition (e.g., "Cloudy")
98 --symbol=SYM Weather symbol (e.g., "cloud")
99
100 Examples:
101 journelly-manager create ~/desktop/org/Journelly.org "Home" "Today was great"
102
103 journelly-manager create ~/desktop/org/Journelly.org "Kyushu" \\
104 "Work session notes" \\
105 --latitude=48.8534 --longitude=2.3488 \\
106 --temperature="15°C" --condition="Cloudy" --symbol="cloud"
107
108 append FILE DATE CONTENT
109 Append to existing journal entry by date (YYYY-MM-DD)
110
111 Examples:
112 journelly-manager append ~/desktop/org/Journelly.org \\
113 "2026-01-16" "Additional thoughts"
114
115 list FILE [--limit=N]
116 List recent journal entries
117
118 Options:
119 --limit=N Number of entries to show (default: 10)
120
121 Examples:
122 journelly-manager list ~/desktop/org/Journelly.org
123 journelly-manager list ~/desktop/org/Journelly.org --limit=20
124
125 search FILE QUERY
126 Search journal entries for keyword
127
128 Examples:
129 journelly-manager search ~/desktop/org/Journelly.org "wireguard"
130
131 get FILE DATE
132 Get specific entry by date (YYYY-MM-DD)
133
134 Examples:
135 journelly-manager get ~/desktop/org/Journelly.org "2026-01-16"
136
137ENVIRONMENT:
138 EMACS Emacs executable (default: emacs)
139 EMACS_DIR Emacs config directory (default: ~/.config/emacs)
140 DEBUG Set to 1 for debug output
141 JSON_OUTPUT Set to 1 for JSON output (no colors)
142
143EXAMPLES:
144 # Create entry with auto location/weather (use get-location/get-weather)
145 LOC=\$(get-location --json)
146 WEATHER=\$(get-weather --json)
147 journelly-manager create ~/desktop/org/Journelly.org \\
148 "\$(echo \$LOC | jq -r .city)" "Entry content" \\
149 --latitude="\$(echo \$LOC | jq -r .lat)" \\
150 --longitude="\$(echo \$LOC | jq -r .lon)" \\
151 --temperature="\$(echo \$WEATHER | jq -r .temperature)" \\
152 --condition="\$(echo \$WEATHER | jq -r .condition)" \\
153 --symbol="\$(echo \$WEATHER | jq -r .symbol)"
154
155 # Append to today's entry
156 journelly-manager append ~/desktop/org/Journelly.org \\
157 "\$(date +%Y-%m-%d)" "More thoughts"
158
159 # Search entries
160 journelly-manager search ~/desktop/org/Journelly.org "claude"
161
162EOF
163 exit 0
164}
165
166# Parse create command
167cmd_create() {
168 local file="$1"
169 local location="$2"
170 local content="$3"
171 shift 3
172
173 local latitude="" longitude="" temperature="" condition="" symbol=""
174
175 # Parse options
176 while [[ $# -gt 0 ]]; do
177 case "$1" in
178 --latitude=*)
179 latitude="${1#*=}"
180 shift
181 ;;
182 --longitude=*)
183 longitude="${1#*=}"
184 shift
185 ;;
186 --temperature=*)
187 temperature="${1#*=}"
188 shift
189 ;;
190 --condition=*)
191 condition="${1#*=}"
192 shift
193 ;;
194 --symbol=*)
195 symbol="${1#*=}"
196 shift
197 ;;
198 *)
199 error "Unknown option: $1"
200 ;;
201 esac
202 done
203
204 # Build Emacs Lisp call
205 local elisp_call="(journelly-batch-create-entry \"$file\" \"$location\" \"$content\""
206
207 if [[ -n "$latitude" ]]; then elisp_call="$elisp_call :latitude \"$latitude\""; fi
208 if [[ -n "$longitude" ]]; then elisp_call="$elisp_call :longitude \"$longitude\""; fi
209 if [[ -n "$temperature" ]]; then elisp_call="$elisp_call :temperature \"$temperature\""; fi
210 if [[ -n "$condition" ]]; then elisp_call="$elisp_call :condition \"$condition\""; fi
211 if [[ -n "$symbol" ]]; then elisp_call="$elisp_call :symbol \"$symbol\""; fi
212
213 elisp_call="$elisp_call)"
214
215 run_batch "$elisp_call"
216 success "Journal entry created"
217}
218
219# Parse append command
220cmd_append() {
221 local file="$1"
222 local date="$2"
223 local content="$3"
224
225 local elisp_call="(journelly-batch-append-to-date \"$file\" \"$date\" \"$content\")"
226 run_batch "$elisp_call"
227 success "Content appended to entry"
228}
229
230# Parse list command
231cmd_list() {
232 local file="$1"
233 shift
234
235 local limit="10"
236
237 # Parse options
238 while [[ $# -gt 0 ]]; do
239 case "$1" in
240 --limit=*)
241 limit="${1#*=}"
242 shift
243 ;;
244 *)
245 error "Unknown option: $1"
246 ;;
247 esac
248 done
249
250 local elisp_call="(journelly-batch-list-entries \"$file\" $limit)"
251 run_batch "$elisp_call"
252}
253
254# Parse search command
255cmd_search() {
256 local file="$1"
257 local query="$2"
258
259 local elisp_call="(journelly-batch-search \"$file\" \"$query\")"
260 run_batch "$elisp_call"
261}
262
263# Parse get command
264cmd_get() {
265 local file="$1"
266 local date="$2"
267
268 local elisp_call="(journelly-batch-get-entry \"$file\" \"$date\")"
269 run_batch "$elisp_call"
270}
271
272# Main command dispatcher
273main() {
274 if [[ $# -eq 0 ]]; then
275 usage
276 fi
277
278 local command="$1"
279 shift
280
281 case "$command" in
282 -h|--help|help)
283 usage
284 ;;
285 create)
286 check_deps
287 if [[ $# -lt 3 ]]; then
288 error "create requires: FILE LOCATION CONTENT"
289 fi
290 cmd_create "$@"
291 ;;
292 append)
293 check_deps
294 if [[ $# -lt 3 ]]; then
295 error "append requires: FILE DATE CONTENT"
296 fi
297 cmd_append "$@"
298 ;;
299 list)
300 check_deps
301 if [[ $# -lt 1 ]]; then
302 error "list requires: FILE"
303 fi
304 cmd_list "$@"
305 ;;
306 search)
307 check_deps
308 if [[ $# -lt 2 ]]; then
309 error "search requires: FILE QUERY"
310 fi
311 cmd_search "$@"
312 ;;
313 get)
314 check_deps
315 if [[ $# -lt 2 ]]; then
316 error "get requires: FILE DATE"
317 fi
318 cmd_get "$@"
319 ;;
320 *)
321 error "Unknown command: $command (try --help)"
322 ;;
323 esac
324}
325
326main "$@"