#include #include #include #include #include #include #include static const char *ename; /* Name of the executable */ #define INFINITE_COLUMNS ((size_t) -1) static size_t _ascii_to_led(char*, const char*, size_t); static int _display_led(FILE*, const char*, size_t, size_t); static size_t _getcols(FILE*); static void _printusage_and_die(FILE*, const char*); static void _write_led(FILE*, const char*, size_t); /* usage: led [-w] number [number ...] Prints each [number] to STDOUT If "-w" is specified, attempt to determine the width of the output TTY and wrap accordingly. */ int main(int argc, char** argv) { static char output_buffer[256]; /* Output buffer */ size_t arglen, /* Length of each argument */ columns, /* Number of columns to align to */ errors; /* Number of non-converted characters */ int i; /* Iterator for [argv] */ assert( argc > 0 ); assert( argv != NULL ); ename = argv[0] ? argv[0] : "led"; columns = INFINITE_COLUMNS; /* Parse options... */ for( i = 1; i < argc && argv[i][0] == '-'; i++ ) { assert( argv[i] != NULL ); if( !strcmp("-w", argv[i]) ) columns = _getcols(stdout); else if( !strcmp("-?", argv[i]) || !strcmp("-h", argv[i]) || !strcmp("--help", argv[i]) ) _printusage_and_die(stdout, NULL); else if( !strcmp("--", argv[i]) ) { ++i; break; } else { (void) snprintf((char*) &output_buffer, (size_t) sizeof(output_buffer), "bad option: %s", argv[i]); _printusage_and_die(stderr, (const char*) &output_buffer); } } if( argc <= i ) _printusage_and_die(stderr, "nothing to be done!"); /* And finally print the numbers... */ for( ; i < argc; i++ ) { assert( argv[i] != NULL ); arglen = strlen(argv[i]); if( arglen == 0 ) continue; if( arglen > sizeof(output_buffer) ) { (void) fprintf(stderr, "led: warning: input string was truncated\n"); arglen = sizeof(output_buffer); } errors = _ascii_to_led((char*) &output_buffer, argv[i], arglen); if( errors == arglen ) { (void) fprintf(stderr, "led: error: input string `%s' is not a number...\n", argv[i]); continue; } if( errors > 0 ) (void) fprintf(stderr, "led: warning: not all characters in input `%s' could be converted\n", argv[i]); if( _display_led(stdout, (const char*) &output_buffer, arglen, columns) ) exit( EXIT_FAILURE ); (void) fputc('\n', stdout); } return EXIT_SUCCESS; } /* _ascii_to_led output - output buffer (must be large enough for at least [input_len] bytes) input - input ASCII string input_len - length of [input], in bytes Converts a sequence of ASCII digits ('0', '1', '2', etc) to "LED" digits. Any non-digits are represented as 'E'. RETURNS: The number of characters that could not be converted */ static size_t _ascii_to_led(char* output, const char* input, size_t input_len) { static const char digits[] = { /* _ 1 |_| 2, 4, 8 |_| 16, 32, 64 */ 0x7b, 0x48, 0x3d, 0x6d, 0x4e, /* 0, 1, 2, 3, 4 */ 0x67, 0x77, 0x49, 0x7f, 0x6f, /* 5, 6, 7, 8, 9 */ 0x37 /* E */ }; size_t i, /* Iterator for [input] */ err; /* Error(s) */ assert( output != NULL ); assert( input != NULL ); assert( CHAR_BIT >= 7 ); i = 0; err = 0; for( ; i < input_len; i++ ) { if( input[i] >= '0' && input[i] <= '9' ) output[i] = digits[input[i] - '0']; else { output[i] = digits[10]; ++err; } } return err; } /* _display_led output - Output stream ledstr - String of "LED" digits (see _ascii_to_led()) ledlen - Length of [ledstr], in bytes wrap - Number of columns to wrap to; can be INFINITE_COLUMNS This function assumes that each ASCII char will be represented by one column Writes [ledstr] to [output], being careful to wrap any digits that won't fit in the output. RETURNS: Zero on success, nonzero on failure (wrap < 5) */ static int _display_led(FILE* output, const char* ledstr, size_t ledlen, size_t wrap) { int err; /* Error(s)? */ size_t cpl, /* Number of characters per scan line */ printlen; /* Number of bytes to print */ const char *iter, /* Iterator for [ledstr] */ *end; /* End of [ledstr] */ assert( output != NULL ); assert( ledstr != NULL ); assert( ledlen > 0 ); assert( wrap > 5 ); err = 1; cpl = (wrap - 1) >> 2; if( cpl == 0 ) { (void) fprintf(stderr, "led: I'm feeling a little too cramped here\n"); goto END; } iter = ledstr; end = ledstr + ledlen; do { printlen = (size_t) (end - iter); if( printlen > cpl ) { _write_led(output, iter, cpl); iter += cpl; } else { _write_led(output, iter, printlen); break; } } while( 1 ); err = 0; END: return err; } /* _get_width_from_tty col - (out) Number of columns in the output terminal fd - The output terminal's file descriptor Attempts to use ioctl() to determine the number of columns in the output TTY. If ioctl() fails, then [col] is left unchanged. */ static void _get_width_from_tty(size_t* col, int fd) { struct winsize wsz; /* The window size, according to ioctl()... I hope... */ assert( col != NULL ); assert( fd >= 0 ); if( ioctl(fd, TIOCGWINSZ, &wsz) != -1 ) *col = (size_t) wsz.ws_col; } /* _getcols output - output stream Tries to determine the number of columns in the output TTY. If [output] does not point to a TTY, or if the call to ioctl() fails, [output] is assumed to have 80 columns. RETURNS: The number of columns in [output] */ static size_t _getcols(FILE* output) { size_t col; /* Number of columns */ int fd; /* File descriptor for [output] */ assert( output != NULL ); col = 80; /* A reasonable-ish value, I think? */ fd = fileno(output); assert( fd >= 0 ); if( !isatty(fd) ) goto END; _get_width_from_tty(&col, fd); END: return col; } /* _printusage_and_die out - output stream msg - optional error message Prints usage information to [out], then calls exit() */ static void _printusage_and_die(FILE* out, const char *msg) { (void) fprintf(out, "usage: %s [-w] number [number ...]\n\n", ename); if( msg != NULL && msg[0] != '\0' ) (void) fprintf(out, "error: %s\n\n", msg); (void) fprintf(out, "Prints each [number] to standard output\n\n"); (void) fprintf(out, "Options:\n" " -?\n" " -h\n" " --help - Print this message and quit\n" " -w - Line wrap\n"); exit( EXIT_FAILURE ); } /* _write_led output - output stream ledstr - string of "LED" digits to write (see _ascii_to_led()) ledlen - length of [ledstr], in bytes Dumps [ledstr] to [output] */ static void _write_led(FILE* output, const char* ledstr, size_t ledlen) { static const char scanline[8][3] = { " ", "| ", " _ ", "|_ ", " |", "| |", " _|", "|_|" }; static const char *zeroscanline[2] = { /* The "zero" (i.e. the 0th addressable "LED") scan line is a special case since it is only one bit wide */ (const char*) scanline[0], /* " " */ (const char*) scanline[2] /* " _ " */ }; size_t i, /* Iterator for [ledstr] */ line; /* Scanline to print */ assert( output != NULL ); assert( ledstr != NULL ); /* Boldly assume that writes cannot fail */ #define SCANLINE(CACHE, INDEX) for( i = 0; i < ledlen; i++ ) {\ line = (size_t) (INDEX);\ (void) fputc(' ', output);\ (void) fwrite(CACHE[line], 1, 3, output);\ }\ (void) fputc('\n', output) /* Scan some lines and stuff */ SCANLINE(zeroscanline, (size_t) ledstr[i] & 1); SCANLINE(scanline, ((size_t) ledstr[i] >> 1) & 7); SCANLINE(scanline, ((size_t) ledstr[i] >> 4) & 7); }