For those of you who don’t know, the Snoopy calendar is a gimmick of the hacker culture. It basically refers to a line printer calendar for 1969 featuring the iconic beagle from Peanuts, that apparently hangs on the wall of every Real Programmer’s office. I’m not entirely certain of the origins of this meme, but it dates back at least to the humorous essay Real Programmers Don’t Use Pascal, which was posted to Usenet back in 1983.
I have a certain fondness for this particular gimmick which I can’t quite explain. Perhaps it is the fact that it provides a feasible and exciting challenge for me – that of making my own Snoopy calendar. I have actually done this a couple of times. This time I used a combination of C programming and several Unix programs like enscript
, figlet
, cal
, and sed
.
There are three components to the Snoopy calendar. The first is the Snoopy ASCII/line printer art, the second is the calendar, and the third is the year number banner. These components must be pasted together in the same text file, which can then be converted to Postscript for printing. The Unix paste
program is not satisfactory for this, since it doesn’t align the text, so I wrote my own program. Here is the final result:
1 /*************************************************
2 * Paste V. 1.0 *
3 * *
4 * Description: Pastes two files side-by-side *
5 * with the edges aligned. Does not work with *
6 * files that contain tabs. *
7 * *
8 * Author: Michael Warren *
9 * License: Micheal Warren Free Software License *
10 * Date: November 13, 2016 *
11 *************************************************/
12
13
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <errno.h>
18
19 // Maximum line length
20 #ifndef _MAXLL_
21 # define _MAXLL_ 80
22 #endif
23
24 struct line{
25 int linenum;
26 int linelen;
27 char text[_MAXLL_]; // Text of line
28 struct line *next; // Next line in current file
29 struct line *corr; // Corresponding line in other file
30 };
31
32 struct line *topl; // Top of left file
33 struct line *topr; // Top of right file
34 struct line *curl; // Current line in left file
35 struct line *curr; // Current line in right file
36
37 int main( int argc, char **argv ){
38 FILE *l, *r; // left and right files
39 int llen = 0, rlen = 0; // Number of lines in each file
40 int rmaxll = 0, lmaxll = 0; // Length of longest line in each file
41 if( !argv[1] || !argv[2] ){
42 printf( "\nUsage:\n%s <leftfile> <rightfile>\n\n", argv[0] );
43 return 1;
44 }
45 if( (l = fopen( argv[1], "r" )) == NULL ){
46 fprintf( stderr, "%s: ", argv[0] );
47 switch( errno ){
48 case EPERM : fprintf( stderr, "Operation not permitted.\n" ); break;
49 case ENOENT : fprintf( stderr, "%s: No such file or directory.\n", argv[1] ); break;
50 case EINTR : fprintf( stderr, "Interrupted system call.\n" ); break;
51 case EIO : fprintf( stderr, "Input/outpur error.\n" ); break;
52 case EDEADLK : fprintf( stderr, "Deadlock avoided.\n" ); break;
53 case ENOMEM : fprintf( stderr, "Cannot allocate memory.\n" ); break;
54 case EACCES : fprintf( stderr, "Permission denied.\n" ); break;
55 case ENODEV : fprintf( stderr, "Operation not supported by device.\n" ); break;
56 case EISDIR : fprintf( stderr, "%s is a directory.\n", argv[1] ); break;
57 case EINVAL : fprintf( stderr, "%s: Invalid argument.\n", argv[1] ); break;
58 case ENFILE : fprintf( stderr, "Too many open files in system.\n" ); break;
59 case EMFILE : fprintf( stderr, "Too many open files.\n" ); break;
60 case EFBIG : fprintf( stderr, "File %s is too large.\n", argv[1] ); break;
61 default : fprintf( stderr, "An error occurred. Error #: %d\n", errno );
62 }
63 return errno;
64 }
65 if( (r = fopen( argv[2], "r" )) == NULL ){
66 fprintf( stderr, "%s: ", argv[0] );
67 switch( errno ){
68 case EPERM : fprintf( stderr, "Operation not permitted.\n" ); break;
69 case ENOENT : fprintf( stderr, "%s: No such file or directory.\n", argv[2] ); break;
70 case EINTR : fprintf( stderr, "Interrupted system call.\n" ); break;
71 case EIO : fprintf( stderr, "Input/outpur error.\n" ); break;
72 case EDEADLK : fprintf( stderr, "Deadlock avoided.\n" ); break;
73 case ENOMEM : fprintf( stderr, "Cannot allocate memory.\n" ); break;
74 case EACCES : fprintf( stderr, "Permission denied.\n" ); break;
75 case ENODEV : fprintf( stderr, "Operation not supported by device.\n" ); break;
76 case EISDIR : fprintf( stderr, "%s is a directory.\n", argv[2] ); break;
77 case EINVAL : fprintf( stderr, "%s: Invalid argument.\n", argv[2] ); break;
78 case ENFILE : fprintf( stderr, "Too many open files in system.\n" ); break;
79 case EMFILE : fprintf( stderr, "Too many open files.\n" ); break;
80 case EFBIG : fprintf( stderr, "File %s is too large.\n", argv[2] ); break;
81 default : fprintf( stderr, "An error occurred. Error #: %d\n", errno );
82 }
83 return errno;
84 }
85 topl = (struct line *) malloc( sizeof( struct line ) );
86 topr = (struct line *) malloc( sizeof( struct line ) );
87 curl = topl; curr = topr;
88 char c;
89 // Build left file:
90 while( (c = fgetc( l )) != EOF ){
91 ungetc( c, l );
92 curl->next = (struct line *) malloc( sizeof( struct line ) );
93 curl = curl->next;
94 fgets( curl->text, _MAXLL_, l );
95 curl->linelen = strlen( curl->text );
96 lmaxll = (lmaxll < curl->linelen)?(curl->linelen):lmaxll;
97 curl->text[strlen( curl->text )-1] = '\0';
98 curl->linenum = ++llen;
99 }
100 // Build right file:
101 while( (c = fgetc( r )) != EOF ){
102 ungetc( c, r );
103 curr->next = (struct line *) malloc( sizeof( struct line ) );
104 curr = curr->next;
105 fgets( curr->text, _MAXLL_, r );
106 curr->linelen = strlen( curr->text );
107 rmaxll = (rmaxll < curr->linelen)?(curr->linelen):rmaxll;
108 curr->text[strlen( curr->text )-1] = '\0';
109 curr->linenum = ++rlen;
110 }
111 const int llenc = llen;
112 const int rlenc = rlen;
113 // Extend right file if shorter:
114 if( llen > rlen ){
115 int diff = llen - rlen;
116 for( int i = 0; i < diff; i++ ){
117 curr->next = (struct line *) malloc( sizeof( struct line ) );
118 curr = curr->next;
119 for( int j = 0; j < rmaxll; j++ ){
120 (curr->text)[j] = ' ';
121 }
122 (curr->text)[rmaxll] = '\0';
123 curr->linenum = ++rlen;
124 }
125 }
126 // Extend left file if shorter:
127 else if( llen < rlen ){
128 int diff = rlen - llen;
129 for( int i = 0; i < diff; i++ ){
130 curl->next = (struct line *) malloc( sizeof( struct line ) );
131 curl = curl->next;
132 for( int j = 0; j < lmaxll; j++ ){
133 (curl->text)[j] = ' ';
134 }
135 (curl->text)[lmaxll] = '\0';
136 curl->linenum = ++llen;
137 }
138 }
139 // Begin paste operation
140 curl = topl; curr = topr;
141 unsigned int len = (llenc < rlenc )?llenc:rlenc;
142 for( int i = 0; i < len; i++ ){
143 curl = curl->next;
144 curr = curr->next;
145 printf( "%s ", curl->text );
146 int lendif = lmaxll - curl->linelen;
147 for( int j = 0; j < lendif; j++ ){
148 putchar( ' ' );
149 }
150 printf( "%s", curr->text );
151 lendif = rmaxll - curr->linelen;
152 for( int j = 0; j < lendif; j++ ){
153 putchar( ' ' );
154 }
155 putchar( '\n' );
156 }
157 while( (curl = curl->next) != NULL ){
158 curr = curr->next;
159 printf( "%s %s\n", curl->text, curr->text );
160 }
161 // Cleanup:
162 curl = topl->next; curr = topr->next;
163 while( curl != NULL ){
164 struct line *auxl = curl->next;
165 free( curl );
166 curl = auxl;
167 }
168 while( curr != NULL ){
169 struct line *auxr = curr->next;
170 free( curr );
171 curr = auxr;
172 }
173 free( topl ); free( topr ); fclose( l ); fclose( r );
174 return 0;
175 }
I created the banner using the following command:
figlet -f banner 1969 | sed "s/ / /g" | sed "s/#/##/g" | sed "p"
The three sed
commands are there to double the width and height of the banner. It results in the following output:
## ########## ########## ##########
## ########## ########## ##########
#### ## ## ## ## ## ##
#### ## ## ## ## ## ##
## ## ## ## ## ## ##
## ## ## ## ## ## ##
## ############ ############ ############
## ############ ############ ############
## ## ## ## ##
## ## ## ## ##
## ## ## ## ## ## ##
## ## ## ## ## ## ##
########## ########## ########## ##########
########## ########## ########## ##########
I then did some manual editing to round out the corners. I put this output above the output of cal -y 1969
in Vim, and then ran the program that I had written to paste it onto a Snoopy ASCII picture (I used a different picture this time). I then ran it through enscript
, using landscape orientation and reducing the text size to it would all fit on one page, and also telling it to run in line printer emulation mode.
The final result: