diff --git a/main.cpp b/main.cpp index 1ad722f..c007815 100644 --- a/main.cpp +++ b/main.cpp @@ -25,23 +25,30 @@ struct Bucket { int main(int argc, char *argv[]) { bool quiet = false; + std::string output_path = "palette.json"; std::string imagefile; for (int i = 1; i < argc; i++) { std::string arg = argv[i]; if (arg == "--help" || arg == "-h") { - cout << + cout << "ccolors - extract color palette from images\n\n" "Usage:\n" " " << argv[0] << " [options]\n\n" "Options:\n" - " -h, --help Show this help page\n" - " -q, --quiet Suppress terminal output (JSON is still written)\n\n" + " -h, --help Show this help page\n" + " -q, --quiet Suppress terminal output (JSON is still written)\n" + " -o, --output Write JSON palette to (default: palette.json)\n\n" "Output:\n" " Prints dominant colors as HEX and terminal swatches\n" - " Also writes palette.json\n"; + " Also writes palette.json (or the path given with -o)\n"; return 0; } else if (arg == "--quiet" || arg == "-q") { quiet = true; + } else if ((arg == "--output" || arg == "-o") && i + 1 < argc) { + output_path = argv[++i]; + } else if ((arg == "--output" || arg == "-o") && i + 1 >= argc) { + cout << "Option " << arg << " requires an argument.\n"; + return 1; } else if (imagefile.empty()) { imagefile = arg; } else { @@ -167,7 +174,12 @@ int main(int argc, char *argv[]) { int paletteSize = std::min(5, (int)buckets.size()); - std::ofstream json("palette.json"); + std::ofstream json(output_path); + if (!json) { + cout << "Cannot open output file: " << output_path << "\n"; + stbi_image_free(data); + return 1; + } json << "{\n"; json << " \"image\": \"" << imagefile << "\",\n"; diff --git a/test_runner.py b/test_runner.py index 0ba438e..550f7a1 100755 --- a/test_runner.py +++ b/test_runner.py @@ -126,6 +126,34 @@ def test_invalid_format(binary, workdir): "missing unknown-image-format message") +def test_custom_output(binary, workdir): + img = os.path.join(workdir, "solid.png") + make_png(img, 40, 40, (100, 150, 200)) + custom_json = os.path.join(workdir, "custom_palette.json") + rc, out = run(binary, img, "-o", custom_json, cwd=workdir) + assert_(rc == 0, "custom-output: expected exit 0, got %d" % rc) + assert_(os.path.exists(custom_json), "custom JSON file was not written at: %s" % custom_json) + # Default palette.json must NOT be written when -o is given with a different path. + default_json = os.path.join(workdir, "palette.json") + assert_(not os.path.exists(default_json), "default palette.json should not be written when -o is used") + with open(custom_json) as f: + data = json.load(f) + assert_(data["image"] == img, "custom palette.json image field mismatch") + palette = data["palette"] + assert_(isinstance(palette, list) and 1 <= len(palette) <= 5, + "palette must be a non-empty list of at most 5 entries") + + +def test_custom_output_bad_path(binary, workdir): + img = os.path.join(workdir, "solid.png") + make_png(img, 40, 40, (100, 150, 200)) + bad_path = os.path.join(workdir, "nonexistent_dir", "out.json") + rc, out = run(binary, img, "-o", bad_path, cwd=workdir) + assert_(rc != 0, "bad-output-path: expected non-zero exit, got 0") + assert_(b"Cannot open output file" in out, + "missing 'Cannot open output file' error message") + + def main(): if len(sys.argv) < 2: print("usage: %s " % sys.argv[0], file=sys.stderr) @@ -141,6 +169,8 @@ def main(): ("happy_path_jpeg", lambda d: test_happy_path_jpeg(binary, d)), ("file_not_found", lambda d: test_file_not_found(binary, d)), ("invalid_format", lambda d: test_invalid_format(binary, d)), + ("custom_output", lambda d: test_custom_output(binary, d)), + ("custom_output_bad_path", lambda d: test_custom_output_bad_path(binary, d)), ] failures = [] for name, fn in tests: