Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement pipe support (resolves #13) #14

Merged
merged 7 commits into from
Dec 1, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 82 additions & 15 deletions src/imgcat.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,20 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

/* Feature-test macro for fileno(3). */
#define _XOPEN_SOURCE
/* Feature-test macro for fileno(3), fdopen(3), and mkstemp(3). */
#define _XOPEN_SOURCE 500
#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <err.h>

#include <getopt.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <errno.h>
#include <sysexits.h>

#include <term.h>
Expand Down Expand Up @@ -62,6 +64,10 @@ static struct {
.optimum_format = F_8_COLOR
};

/* Global temporary filename for dumping stdin into. */
#define MAX_TEMPFILE_NAME 128
static char tempfile_name[MAX_TEMPFILE_NAME + 1];

/* Long options */
static struct option long_options[] = {
/* Options affecting output colour depth. */
Expand All @@ -86,28 +92,35 @@ static struct option long_options[] = {
};


/* Returns index of first positional argument. */
static int parse_args(int argc, char **argv);
/** Returns the filename of the image to open. */
static const char* parse_args(int argc, char **argv);
static void bad_usage(const char *msg, ...) __attribute__((noreturn));
static void fatal_error(int code, const char *msg, ...) __attribute__((noreturn));
static void determine_terminal_capabilities();
static void usage(FILE *dest);
static const char *dump_stdin_into_tempfile();

/* Set first thing in main(). */
static char const* program_name;


int main(int argc, char **argv) {
int width, image_name_index;
int width;
bool status;
const char *image_name;
Format color_format = F_UNSET;
program_name = argv[0];

image_name_index = parse_args(argc, argv);

/* No image file specified. */
if (image_name_index == argc) {
bad_usage("Must specify an image file.");
image_name = parse_args(argc, argv);
if (image_name == NULL) {
if (isatty(fileno(stdin))) {
/* No image is specified on the command line, and there's nothing
* redirected to stdin. */
bad_usage("Must specify an image file.");
} else {
/* There's an image redirected to stdin. */
image_name = dump_stdin_into_tempfile();
}
}

determine_terminal_capabilities();
Expand All @@ -131,9 +144,6 @@ int main(int argc, char **argv) {
color_format = terminal.optimum_format;
}

/* TODO: multiple images OR stdin. */
image_name = argv[image_name_index];

status = print_image(image_name, width, color_format);

if (!status) {
Expand Down Expand Up @@ -191,6 +201,45 @@ static void determine_terminal_capabilities() {
}
}

/**
* Removes the temporary file. Intended to be the atexit() callback.
*/
static void unlink_tempfile(void) {
remove(tempfile_name);
}

/**
* This is necessary because CImg likes to close and reopen the file it's
* reading, which discards header data when reading from /dev/stdin.
*/
static const char *dump_stdin_into_tempfile() {
int byte;
/* Set up the mutable buffer for mkstemp() to do its magic. */
strncpy(tempfile_name, "/tmp/image.XXXXXXXX", MAX_TEMPFILE_NAME);
int output_fd = mkstemp(tempfile_name);
if (output_fd == -1) {
fatal_error(EX_TEMPFAIL, "could not create temporary file: %s",
strerror(errno));
}

FILE *output = fdopen(output_fd, "wb");
if (output == NULL) {
fatal_error(EX_IOERR, "could not open temporary file: %s",
strerror(errno));
}

/* TODO: do something better than a byte-for-byte file transfer. */
while ((byte = getchar()) != EOF) {
fputc(byte, output);
}
fclose(output);

/* Remove the file at ordinary exit. */
atexit(unlink_tempfile);

return tempfile_name;
}

static void usage(FILE *dest) {
const int field_width = strlen(program_name);
fprintf(dest, "Usage:\n");
Expand Down Expand Up @@ -219,6 +268,18 @@ static void bad_usage(const char *msg, ...) {
exit(EX_USAGE);
}

static void fatal_error(int code, const char *msg, ...) {
va_list args;

fprintf(stderr, "%s: ", program_name);

va_start(args, msg);
vfprintf(stderr, msg, args);
va_end(args);
fprintf(stderr, "\n");

exit(code);
}

static Format parse_format(const char *arg) {
# define argeq(b) (strncmp(arg, (b), (sizeof(b))) == 0)
Expand All @@ -235,7 +296,7 @@ static Format parse_format(const char *arg) {
# undef argeq
}

static int parse_args(int argc, char **argv) {
static const char* parse_args(int argc, char **argv) {
int c;
/* Disable getopt_long from printing to stderr. */
extern int opterr;
Expand Down Expand Up @@ -287,5 +348,11 @@ static int parse_args(int argc, char **argv) {
}
}

return optind;
if (argc == optind) {
/* This means getopt() has exhausted all of argv and has not found a
* valid image name argument. */
return NULL;
} else {
return argv[optind];
}
}
2 changes: 1 addition & 1 deletion src/load_image.cc
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ bool load_image(const char *filename, Image *image, struct LoadOpts* options) {
cimg_library::CImg<unsigned char> img;
try {
img.assign(filename);
} catch (cimg_library::CImgException& ex) {
} catch (cimg_library::CImgIOException& ex) {
// Could not load the image for some reason.
return false;
}
Expand Down
14 changes: 13 additions & 1 deletion tests/run
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,14 @@ imgcat_tests() {
assert_eq 1px_8_table.out imgcat --8 1px_8_table.png
assert_eq 1px_256_iterm2.out imgcat --iterm2 1px_256_table.png

# command that should fail
# Test invocations that should fail
assert_fail imgcat
assert_fail imgcat --width=-3 "$ANY_IMAGE"
assert_fail imgcat -w mank3y "$ANY_IMAGE"
assert_fail imgcat --fake-option

# Test that we can pipe in images and have them render
assert_eq 1px_256_table.out pipe 1px_256_table.png "$IMGCAT" --256
}


Expand Down Expand Up @@ -111,6 +115,14 @@ _add_failure() {
FAILURES+=("$((items - 1))")
}

# Can't specify command line redirection in assert commands,
# so use this to do it for use.
pipe() {
local filename="$1"; shift;
<"$filename" "$@"
}


print_success() {
echo "${ANSI_GRN}$ncases tests passed${ANSI_RST}"
}
Expand Down