Give the interpreter real Celtic Cross meanings and --format/--lang support
- guidance.c now prints last, after the full spread; both it and main.c's new Celtic Cross readout pull real Waite card meanings via tarot_data.c instead of just naming cards. - interpreter-cli gains --format text|html|json and --lang en|de, matching deck-engine - required linking engine/src/tarot_data.c and i18n.c into the interpreter binary (a narrow, documented exception to its earlier "no engine .c files" policy) and writing German translations for narrative.c's/guidance.c's own text.
This commit is contained in:
+400
-52
@@ -1,25 +1,63 @@
|
||||
#define _POSIX_C_SOURCE 200809L /* for open_memstream */
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "../../engine/src/i18n.h"
|
||||
#include "guidance.h"
|
||||
#include "narrative.h"
|
||||
#include "reading_io.h"
|
||||
#include "significance.h"
|
||||
|
||||
typedef enum { FORMAT_TEXT, FORMAT_HTML, FORMAT_JSON } OutputFormat;
|
||||
|
||||
static void print_usage(const char *prog) {
|
||||
fprintf(stderr,
|
||||
"Usage: %s [reading.json]\n\n"
|
||||
"Usage: %s [--format text|html|json] [--lang <code>] [--i18n-dir <path>]\n"
|
||||
" [reading.json]\n\n"
|
||||
"Reads a DailyReading in deck-engine's --format json shape - from the\n"
|
||||
"given file, or from stdin if no file is given (or it is \"-\") - and\n"
|
||||
"prints a significance report: the day's overall level and the top\n"
|
||||
"aspects driving it, each with a short narrative note.\n\n"
|
||||
"prints a full report: the day's overall significance level, the\n"
|
||||
"top aspects driving it (each with a short narrative note), a full\n"
|
||||
"readout of the Celtic Cross spread with every card's meaning, and\n"
|
||||
"finally guidance tying the day's top transit to the spread's\n"
|
||||
"Attitude/Outcome cards.\n\n"
|
||||
" --format Output format, defaults to text. html links card art via\n"
|
||||
" img/<file>, same convention as deck-engine's own html\n"
|
||||
" output - save/run this next to the img/ directory\n"
|
||||
" `make images` copies into dist/. json embeds the same\n"
|
||||
" narrative/guidance prose as text (in whatever --lang was\n"
|
||||
" loaded) alongside stable, language-independent slugs.\n"
|
||||
" --lang Reading language code, defaults to en. Matches a file\n"
|
||||
" named <code>.lang in --i18n-dir (see engine/i18n/) -\n"
|
||||
" note this is this binary's OWN output language, not\n"
|
||||
" related to whatever --lang deck-engine was run with to\n"
|
||||
" produce its --format json input (which is always\n"
|
||||
" language-independent - see docs/input-output-format.md).\n"
|
||||
" --i18n-dir Directory to look up <lang>.lang in. Defaults to the\n"
|
||||
" i18n/ directory next to this binary (`make i18n`\n"
|
||||
" copies engine/i18n/ there as dist/i18n/).\n\n"
|
||||
"Typical use:\n"
|
||||
" dist/deck-engine ... --format json | %s\n"
|
||||
" dist/deck-engine ... --format json > reading.json && %s reading.json\n",
|
||||
prog, prog, prog);
|
||||
}
|
||||
|
||||
/* Same convention as engine/src/main.c's own default_i18n_path() -
|
||||
* duplicated rather than shared, since the two binaries are never
|
||||
* linked together (see CLAUDE.md's "Interpretation" section). */
|
||||
static void default_i18n_path(const char *argv0, const char *lang, char *out, size_t out_size) {
|
||||
const char *slash = strrchr(argv0, '/');
|
||||
if (slash) {
|
||||
int dir_len = (int)(slash - argv0);
|
||||
snprintf(out, out_size, "%.*s/i18n/%s.lang", dir_len, argv0, lang);
|
||||
} else {
|
||||
snprintf(out, out_size, "i18n/%s.lang", lang);
|
||||
}
|
||||
}
|
||||
|
||||
static char *read_all(FILE *f, size_t *out_length) {
|
||||
size_t cap = 4096;
|
||||
size_t len = 0;
|
||||
@@ -39,63 +77,391 @@ static char *read_all(FILE *f, size_t *out_length) {
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* Display names for the report - deliberately a small local table rather
|
||||
* than linking engine/src/astro.c's astro_body_name()/astro_aspect_name()
|
||||
* (which would pull in the vendored Astronomy Engine just for cosmetic
|
||||
* text). Same policy as reading_io.c's slug tables. */
|
||||
static const char *ui(const char *key, const char *fallback) { return i18n_get(key, fallback); }
|
||||
|
||||
/* Slugs for i18n key building (body.<slug>/sign.<slug>/aspect.<slug>,
|
||||
* mirroring astro.c's own tables) and reused as-is for --format json's
|
||||
* language-independent fields - deliberately a small local table rather
|
||||
* than linking astro.c (would pull in the vendored Astronomy Engine just
|
||||
* for cosmetic text/slugs). Same policy as reading_io.c's own tables. */
|
||||
static const char *const k_body_slug[NUM_BODIES] = {
|
||||
"sun", "moon", "mercury", "venus", "mars",
|
||||
"jupiter", "saturn", "uranus", "neptune", "pluto",
|
||||
};
|
||||
static const char *const k_sign_slug[12] = {
|
||||
"aries", "taurus", "gemini", "cancer", "leo", "virgo",
|
||||
"libra", "scorpio", "sagittarius", "capricorn", "aquarius", "pisces",
|
||||
};
|
||||
static const char *const k_aspect_slug[5] = {
|
||||
"conjunction", "sextile", "square", "trine", "opposition",
|
||||
};
|
||||
static const char *const k_day_level_slug[DAY_SIGNIFICANCE_COUNT] = {
|
||||
"quiet", "notable", "significant", "major",
|
||||
};
|
||||
|
||||
static const char *body_display_name(Body body) {
|
||||
static const char *const names[NUM_BODIES] = {
|
||||
static const char *const fallback[NUM_BODIES] = {
|
||||
"Sun", "Moon", "Mercury", "Venus", "Mars",
|
||||
"Jupiter", "Saturn", "Uranus", "Neptune", "Pluto",
|
||||
};
|
||||
return names[body];
|
||||
char key[32];
|
||||
snprintf(key, sizeof key, "body.%s", k_body_slug[body]);
|
||||
return ui(key, fallback[body]);
|
||||
}
|
||||
|
||||
static const char *aspect_display_name(AspectType type) {
|
||||
static const char *const names[5] = {
|
||||
static const char *const fallback[5] = {
|
||||
"Conjunction", "Sextile", "Square", "Trine", "Opposition",
|
||||
};
|
||||
return names[type];
|
||||
char key[32];
|
||||
snprintf(key, sizeof key, "aspect.%s", k_aspect_slug[type]);
|
||||
return ui(key, fallback[type]);
|
||||
}
|
||||
|
||||
static const char *sign_display_name(ZodiacSign sign) {
|
||||
static const char *const names[12] = {
|
||||
static const char *const fallback[12] = {
|
||||
"Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo",
|
||||
"Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces",
|
||||
};
|
||||
return names[sign];
|
||||
char key[32];
|
||||
snprintf(key, sizeof key, "sign.%s", k_sign_slug[sign]);
|
||||
return ui(key, fallback[sign]);
|
||||
}
|
||||
|
||||
static const char *day_level_name(DaySignificance level) {
|
||||
switch (level) {
|
||||
case DAY_QUIET: return ui("interp.day_level.quiet", "Quiet");
|
||||
case DAY_NOTABLE: return ui("interp.day_level.notable", "Notable");
|
||||
case DAY_SIGNIFICANT: return ui("interp.day_level.significant", "Significant");
|
||||
case DAY_MAJOR: return ui("interp.day_level.major", "Major");
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static const char *reversed_marker(bool reversed) {
|
||||
static char buf[32];
|
||||
if (!reversed) return "";
|
||||
snprintf(buf, sizeof buf, " %s", ui("ui.reversed", "(Reversed)"));
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* pos->house is 0 when reading_load_json had no sign/house data for this
|
||||
* body (see parse_bodies() in reading_io.c) - a valid whole-sign house
|
||||
* is always 1-12, so this is a safe "unknown, say nothing" sentinel. */
|
||||
static void print_body_in_sign(const PlanetPosition *pos) {
|
||||
static void print_body_in_sign_text(FILE *out, const PlanetPosition *pos) {
|
||||
if (pos->house < 1 || pos->house > 12) return;
|
||||
printf(" in %s (house %d)", sign_display_name(pos->sign), pos->house);
|
||||
char buf[64];
|
||||
snprintf(buf, sizeof buf, ui("interp.in_sign_house", "in %s (house %d)"),
|
||||
sign_display_name(pos->sign), pos->house);
|
||||
fprintf(out, " %s", buf);
|
||||
}
|
||||
|
||||
static const char *day_level_name(DaySignificance level) {
|
||||
switch (level) {
|
||||
case DAY_QUIET: return "Quiet";
|
||||
case DAY_NOTABLE: return "Notable";
|
||||
case DAY_SIGNIFICANT: return "Significant";
|
||||
case DAY_MAJOR: return "Major";
|
||||
/* Captures narrative_print()/guidance_print()'s output (they only know
|
||||
* how to write to a FILE*) into a heap string, for embedding into JSON -
|
||||
* avoids changing either module's public API just for this one caller.
|
||||
* Strips a single trailing newline; caller must free() the result. */
|
||||
static char *capture_narrative(const Aspect *aspect, int house) {
|
||||
char *buf = NULL;
|
||||
size_t size = 0;
|
||||
FILE *mem = open_memstream(&buf, &size);
|
||||
narrative_print(mem, "", aspect, house);
|
||||
fclose(mem);
|
||||
if (size > 0 && buf[size - 1] == '\n') buf[size - 1] = '\0';
|
||||
return buf;
|
||||
}
|
||||
|
||||
static char *capture_guidance(const DailyInterpretation *interp, const CelticCrossSpread *spread) {
|
||||
char *buf = NULL;
|
||||
size_t size = 0;
|
||||
FILE *mem = open_memstream(&buf, &size);
|
||||
guidance_print(mem, "", interp, spread);
|
||||
fclose(mem);
|
||||
if (size > 0 && buf[size - 1] == '\n') buf[size - 1] = '\0';
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void print_top_transits_header(FILE *out, int count) {
|
||||
if (count == 1) {
|
||||
fprintf(out, "%s\n", ui("interp.top_transits.one", "Top significant transit:"));
|
||||
} else {
|
||||
char buf[64];
|
||||
snprintf(buf, sizeof buf, ui("interp.top_transits.many", "Top %d significant transits:"), count);
|
||||
fprintf(out, "%s\n", buf);
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
/* ===== --format text ===== */
|
||||
|
||||
static void print_celtic_cross_text(FILE *out, const CelticCrossSpread *spread) {
|
||||
fprintf(out, "%s:\n", ui("ui.celtic_cross", "Celtic Cross"));
|
||||
for (int i = 0; i < TAROT_SPREAD_SIZE; i++) {
|
||||
const TarotDraw *draw = &spread->positions[i];
|
||||
fprintf(out, " %d. %s: %s%s\n", i + 1, tarot_position_name((CelticCrossPosition)i),
|
||||
tarot_card_name(draw->card), reversed_marker(draw->reversed));
|
||||
fprintf(out, " %s\n", tarot_position_description((CelticCrossPosition)i));
|
||||
fprintf(out, " %s\n", tarot_card_meaning(draw->card, draw->reversed));
|
||||
}
|
||||
}
|
||||
|
||||
static void interp_print_text(FILE *out, const DailyReading *reading, const DailyInterpretation *interp) {
|
||||
fprintf(out, "%s %s (%d/%d)\n", ui("interp.day_significance", "Day significance:"),
|
||||
day_level_name(interp->day_level), interp->day_level + 1, DAY_SIGNIFICANCE_COUNT);
|
||||
if (interpretation_deserves_framing(interp)) {
|
||||
fprintf(out, "%s\n", ui("interp.major_framing",
|
||||
"(a major transit today - worth a deeper Celtic Cross look)"));
|
||||
}
|
||||
fprintf(out, "\n");
|
||||
|
||||
if (interp->top_item_count == 0) {
|
||||
fprintf(out, "%s\n", ui("interp.no_notable_transits", "No notable transits today."));
|
||||
} else {
|
||||
print_top_transits_header(out, interp->top_item_count);
|
||||
for (int i = 0; i < interp->top_item_count; i++) {
|
||||
const SignificantItem *item = &interp->top_items[i];
|
||||
const Aspect *a = &item->aspect;
|
||||
|
||||
fprintf(out, " %d. %s %s", i + 1, ui("ui.transiting", "Transiting"),
|
||||
body_display_name(a->transiting_planet));
|
||||
print_body_in_sign_text(out, &reading->transits.bodies[a->transiting_planet]);
|
||||
fprintf(out, " %s %s %s", aspect_display_name(a->type), ui("ui.natal", "natal"),
|
||||
body_display_name(a->natal_planet));
|
||||
print_body_in_sign_text(out, &reading->natal.bodies[a->natal_planet]);
|
||||
fprintf(out, " (%s %.1f\xc2\xb0, %s %.2f)\n", ui("ui.orb", "orb"), a->orb,
|
||||
ui("interp.score", "score"), item->score);
|
||||
narrative_print(out, " ", a, reading->transits.bodies[a->transiting_planet].house);
|
||||
}
|
||||
}
|
||||
fprintf(out, "\n");
|
||||
|
||||
print_celtic_cross_text(out, &reading->spread);
|
||||
fprintf(out, "\n");
|
||||
|
||||
guidance_print(out, "", interp, &reading->spread);
|
||||
}
|
||||
|
||||
/* ===== --format html ===== */
|
||||
|
||||
static void interp_print_html(FILE *out, const DailyReading *reading, const DailyInterpretation *interp) {
|
||||
fprintf(out,
|
||||
"<!doctype html>\n<html><head><meta charset=\"utf-8\">\n"
|
||||
"<title>%s</title>\n"
|
||||
"<style>\n"
|
||||
"body { font-family: sans-serif; max-width: 900px; margin: 2em auto; }\n"
|
||||
"table { border-collapse: collapse; margin-bottom: 1.5em; }\n"
|
||||
"td { padding: 2px 10px 2px 0; }\n"
|
||||
"h1, h2 { border-bottom: 1px solid #ccc; }\n"
|
||||
".spread { display: flex; flex-wrap: wrap; gap: 1.5em; }\n"
|
||||
".card { width: 160px; }\n"
|
||||
".card img { width: 140px; display: block; }\n"
|
||||
".card img.reversed { transform: rotate(180deg); }\n"
|
||||
".card .position { font-weight: bold; }\n"
|
||||
".card .name { font-style: italic; }\n"
|
||||
".guidance { background: #f6f6f6; padding: 1em; border-radius: 6px; }\n"
|
||||
"</style></head><body>\n",
|
||||
ui("interp.page_title", "Deck in a Dash - Interpretation"));
|
||||
|
||||
fprintf(out, "<h1>%s</h1>\n", ui("interp.heading", "Daily Interpretation"));
|
||||
|
||||
fprintf(out, "<h2>%s</h2>\n<p>%s %s (%d/%d)",
|
||||
ui("interp.heading_day_significance", "Day Significance"),
|
||||
ui("interp.day_significance", "Day significance:"), day_level_name(interp->day_level),
|
||||
interp->day_level + 1, DAY_SIGNIFICANCE_COUNT);
|
||||
if (interpretation_deserves_framing(interp)) {
|
||||
fprintf(out, "<br>%s", ui("interp.major_framing",
|
||||
"(a major transit today - worth a deeper Celtic Cross look)"));
|
||||
}
|
||||
fprintf(out, "</p>\n");
|
||||
|
||||
fprintf(out, "<h2>%s</h2>\n", ui("interp.heading_transits", "Significant Transits"));
|
||||
if (interp->top_item_count == 0) {
|
||||
fprintf(out, "<p>%s</p>\n", ui("interp.no_notable_transits", "No notable transits today."));
|
||||
} else {
|
||||
fprintf(out, "<table>\n");
|
||||
for (int i = 0; i < interp->top_item_count; i++) {
|
||||
const SignificantItem *item = &interp->top_items[i];
|
||||
const Aspect *a = &item->aspect;
|
||||
char *text = capture_narrative(a, reading->transits.bodies[a->transiting_planet].house);
|
||||
|
||||
fprintf(out, "<tr><td>%d.</td><td>%s %s %s %s %s</td>"
|
||||
"<td>%s %.1f°, %s %.2f</td></tr>\n",
|
||||
i + 1, ui("ui.transiting", "Transiting"), body_display_name(a->transiting_planet),
|
||||
aspect_display_name(a->type), ui("ui.natal", "natal"),
|
||||
body_display_name(a->natal_planet), ui("ui.orb", "orb"), a->orb,
|
||||
ui("interp.score", "score"), item->score);
|
||||
if (text[0] != '\0') fprintf(out, "<tr><td></td><td colspan=\"2\">%s</td></tr>\n", text);
|
||||
free(text);
|
||||
}
|
||||
fprintf(out, "</table>\n");
|
||||
}
|
||||
|
||||
fprintf(out, "<h2>%s</h2>\n<div class=\"spread\">\n", ui("ui.celtic_cross", "Celtic Cross"));
|
||||
for (int i = 0; i < TAROT_SPREAD_SIZE; i++) {
|
||||
const TarotDraw *draw = &reading->spread.positions[i];
|
||||
fprintf(out,
|
||||
"<div class=\"card\">\n"
|
||||
" <div class=\"position\">%s</div>\n"
|
||||
" <img class=\"%s\" src=\"img/%s\" alt=\"%s\">\n"
|
||||
" <div class=\"name\">%s%s</div>\n"
|
||||
" <div class=\"desc\">%s</div>\n"
|
||||
" <div class=\"meaning\">%s</div>\n"
|
||||
"</div>\n",
|
||||
tarot_position_name((CelticCrossPosition)i), draw->reversed ? "reversed" : "",
|
||||
tarot_card_image_file(draw->card), tarot_card_name(draw->card), tarot_card_name(draw->card),
|
||||
reversed_marker(draw->reversed), tarot_position_description((CelticCrossPosition)i),
|
||||
tarot_card_meaning(draw->card, draw->reversed));
|
||||
}
|
||||
fprintf(out, "</div>\n");
|
||||
|
||||
char *guidance = capture_guidance(interp, &reading->spread);
|
||||
fprintf(out, "<h2>%s</h2>\n<div class=\"guidance\">\n", ui("interp.heading_guidance", "Guidance"));
|
||||
const char *start = guidance;
|
||||
for (const char *p = guidance; ; p++) {
|
||||
if (*p == '\n' || *p == '\0') {
|
||||
if (p > start) fprintf(out, "<p>%.*s</p>\n", (int)(p - start), start);
|
||||
if (*p == '\0') break;
|
||||
start = p + 1;
|
||||
}
|
||||
}
|
||||
free(guidance);
|
||||
fprintf(out, "</div>\n");
|
||||
|
||||
fprintf(out, "</body></html>\n");
|
||||
}
|
||||
|
||||
/* ===== --format json ===== */
|
||||
|
||||
static void json_string(FILE *out, const char *s) {
|
||||
fputc('"', out);
|
||||
for (; *s; s++) {
|
||||
unsigned char c = (unsigned char)*s;
|
||||
switch (c) {
|
||||
case '"': fputs("\\\"", out); break;
|
||||
case '\\': fputs("\\\\", out); break;
|
||||
case '\n': fputs("\\n", out); break;
|
||||
case '\r': fputs("\\r", out); break;
|
||||
case '\t': fputs("\\t", out); break;
|
||||
default:
|
||||
if (c < 0x20) fprintf(out, "\\u%04x", c);
|
||||
else fputc((char)c, out);
|
||||
}
|
||||
}
|
||||
fputc('"', out);
|
||||
}
|
||||
|
||||
static void interp_print_json(FILE *out, const DailyReading *reading, const DailyInterpretation *interp) {
|
||||
fprintf(out, "{\n");
|
||||
|
||||
fprintf(out, " \"day_significance\": {\n");
|
||||
fprintf(out, " \"level\": \"%s\",\n", k_day_level_slug[interp->day_level]);
|
||||
fprintf(out, " \"rank\": %d,\n", interp->day_level + 1);
|
||||
fprintf(out, " \"count\": %d,\n", DAY_SIGNIFICANCE_COUNT);
|
||||
fprintf(out, " \"deserves_framing\": %s\n", interpretation_deserves_framing(interp) ? "true" : "false");
|
||||
fprintf(out, " },\n");
|
||||
|
||||
fprintf(out, " \"significant_transits\": [\n");
|
||||
for (int i = 0; i < interp->top_item_count; i++) {
|
||||
const SignificantItem *item = &interp->top_items[i];
|
||||
const Aspect *a = &item->aspect;
|
||||
const PlanetPosition *tp = &reading->transits.bodies[a->transiting_planet];
|
||||
const PlanetPosition *np = &reading->natal.bodies[a->natal_planet];
|
||||
char *text = capture_narrative(a, tp->house);
|
||||
|
||||
fprintf(out, " {\n");
|
||||
fprintf(out, " \"transiting_planet\": \"%s\",\n", k_body_slug[a->transiting_planet]);
|
||||
fprintf(out, " \"transiting_sign\": \"%s\",\n", k_sign_slug[tp->sign]);
|
||||
fprintf(out, " \"transiting_house\": %d,\n", tp->house);
|
||||
fprintf(out, " \"natal_planet\": \"%s\",\n", k_body_slug[a->natal_planet]);
|
||||
fprintf(out, " \"natal_sign\": \"%s\",\n", k_sign_slug[np->sign]);
|
||||
fprintf(out, " \"natal_house\": %d,\n", np->house);
|
||||
fprintf(out, " \"aspect\": \"%s\",\n", k_aspect_slug[a->type]);
|
||||
fprintf(out, " \"orb\": %.4f,\n", a->orb);
|
||||
fprintf(out, " \"score\": %.4f,\n", item->score);
|
||||
fprintf(out, " \"narrative\": ");
|
||||
json_string(out, text);
|
||||
fprintf(out, "\n }%s\n", i + 1 < interp->top_item_count ? "," : "");
|
||||
free(text);
|
||||
}
|
||||
fprintf(out, " ],\n");
|
||||
|
||||
fprintf(out, " \"celtic_cross\": [\n");
|
||||
for (int i = 0; i < TAROT_SPREAD_SIZE; i++) {
|
||||
const TarotDraw *draw = &reading->spread.positions[i];
|
||||
fprintf(out, " {\"position\": \"%s\", \"card\": \"%s\", \"reversed\": %s, \"card_name\": ",
|
||||
tarot_position_slug((CelticCrossPosition)i), tarot_card_slug(draw->card),
|
||||
draw->reversed ? "true" : "false");
|
||||
json_string(out, tarot_card_name(draw->card));
|
||||
fprintf(out, ", \"meaning\": ");
|
||||
json_string(out, tarot_card_meaning(draw->card, draw->reversed));
|
||||
fprintf(out, "}%s\n", i + 1 < TAROT_SPREAD_SIZE ? "," : "");
|
||||
}
|
||||
fprintf(out, " ],\n");
|
||||
|
||||
char *guidance = capture_guidance(interp, &reading->spread);
|
||||
fprintf(out, " \"guidance\": ");
|
||||
json_string(out, guidance);
|
||||
fprintf(out, "\n");
|
||||
free(guidance);
|
||||
|
||||
fprintf(out, "}\n");
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc > 2 || (argc == 2 && (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0))) {
|
||||
print_usage(argv[0]);
|
||||
return argc > 2 ? 1 : 0;
|
||||
const char *format_str = "text";
|
||||
const char *lang = "en", *i18n_dir_arg = NULL;
|
||||
const char *input_path = NULL;
|
||||
|
||||
for (int i = 1; i < argc; i++) {
|
||||
const char *arg = argv[i];
|
||||
if (strcmp(arg, "--help") == 0 || strcmp(arg, "-h") == 0) {
|
||||
print_usage(argv[0]);
|
||||
return 0;
|
||||
} else if (strcmp(arg, "--format") == 0) {
|
||||
if (++i >= argc) { print_usage(argv[0]); return 1; }
|
||||
format_str = argv[i];
|
||||
} else if (strcmp(arg, "--lang") == 0) {
|
||||
if (++i >= argc) { print_usage(argv[0]); return 1; }
|
||||
lang = argv[i];
|
||||
} else if (strcmp(arg, "--i18n-dir") == 0) {
|
||||
if (++i >= argc) { print_usage(argv[0]); return 1; }
|
||||
i18n_dir_arg = argv[i];
|
||||
} else if (arg[0] == '-' && strcmp(arg, "-") != 0) {
|
||||
fprintf(stderr, "Unknown argument: %s\n", arg);
|
||||
print_usage(argv[0]);
|
||||
return 1;
|
||||
} else if (input_path == NULL) {
|
||||
input_path = arg;
|
||||
} else {
|
||||
print_usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
OutputFormat format;
|
||||
if (strcmp(format_str, "text") == 0) format = FORMAT_TEXT;
|
||||
else if (strcmp(format_str, "html") == 0) format = FORMAT_HTML;
|
||||
else if (strcmp(format_str, "json") == 0) format = FORMAT_JSON;
|
||||
else {
|
||||
fprintf(stderr, "Invalid --format, expected text, html, or json\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
char i18n_path[512];
|
||||
if (i18n_dir_arg) {
|
||||
snprintf(i18n_path, sizeof i18n_path, "%s/%s.lang", i18n_dir_arg, lang);
|
||||
} else {
|
||||
default_i18n_path(argv[0], lang, i18n_path, sizeof i18n_path);
|
||||
}
|
||||
if (!i18n_load(i18n_path) && strcmp(lang, "en") != 0) {
|
||||
fprintf(stderr, "warning: could not load translations for '%s' from %s - "
|
||||
"falling back to built-in English\n", lang, i18n_path);
|
||||
}
|
||||
|
||||
FILE *in = stdin;
|
||||
bool opened_file = false;
|
||||
if (argc == 2 && strcmp(argv[1], "-") != 0) {
|
||||
in = fopen(argv[1], "r");
|
||||
if (input_path && strcmp(input_path, "-") != 0) {
|
||||
in = fopen(input_path, "r");
|
||||
if (!in) {
|
||||
fprintf(stderr, "error: could not open %s\n", argv[1]);
|
||||
fprintf(stderr, "error: could not open %s\n", input_path);
|
||||
return 1;
|
||||
}
|
||||
opened_file = true;
|
||||
@@ -116,30 +482,12 @@ int main(int argc, char **argv) {
|
||||
DailyInterpretation interp;
|
||||
interpret_daily_reading(&reading, &interp);
|
||||
|
||||
printf("Day significance: %s (%d/%d)\n", day_level_name(interp.day_level),
|
||||
interp.day_level + 1, DAY_SIGNIFICANCE_COUNT);
|
||||
if (interpretation_deserves_framing(&interp)) {
|
||||
printf("(a major transit today - worth a deeper Celtic Cross look)\n");
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
if (interp.top_item_count == 0) {
|
||||
printf("No notable transits today.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
printf("Top %d significant transit%s:\n", interp.top_item_count,
|
||||
interp.top_item_count == 1 ? "" : "s");
|
||||
for (int i = 0; i < interp.top_item_count; i++) {
|
||||
const SignificantItem *item = &interp.top_items[i];
|
||||
const Aspect *a = &item->aspect;
|
||||
|
||||
printf(" %d. Transiting %s", i + 1, body_display_name(a->transiting_planet));
|
||||
print_body_in_sign(&reading.transits.bodies[a->transiting_planet]);
|
||||
printf(" %s natal %s", aspect_display_name(a->type), body_display_name(a->natal_planet));
|
||||
print_body_in_sign(&reading.natal.bodies[a->natal_planet]);
|
||||
printf(" (orb %.1f\xc2\xb0, score %.2f)\n", a->orb, item->score);
|
||||
narrative_print(stdout, " ", a, reading.transits.bodies[a->transiting_planet].house);
|
||||
if (format == FORMAT_HTML) {
|
||||
interp_print_html(stdout, &reading, &interp);
|
||||
} else if (format == FORMAT_JSON) {
|
||||
interp_print_json(stdout, &reading, &interp);
|
||||
} else {
|
||||
interp_print_text(stdout, &reading, &interp);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user