thorsson_cups 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- data/ext/cups.c +480 -0
- data/ext/extconf.rb +15 -0
- data/ext/ruby_cups.h +14 -0
- data/lib/cups/print_job/transient.rb +56 -0
- data/lib/cups/printer/printer.rb +35 -0
- data/test/cups_test.rb +159 -0
- data/test/sample.txt +9 -0
- data/test/sample_blank.txt +0 -0
- metadata +74 -0
data/ext/cups.c
ADDED
@@ -0,0 +1,480 @@
|
|
1
|
+
#include <ruby_cups.h>
|
2
|
+
|
3
|
+
cups_dest_t *dests, *dest;
|
4
|
+
VALUE rubyCups, printJobs;
|
5
|
+
|
6
|
+
// Need to abstract this out of cups.c
|
7
|
+
VALUE ipp_state_to_symbol(int state)
|
8
|
+
{
|
9
|
+
|
10
|
+
VALUE jstate;
|
11
|
+
|
12
|
+
switch (state) {
|
13
|
+
case IPP_JOB_PENDING :
|
14
|
+
jstate = ID2SYM(rb_intern("pending"));
|
15
|
+
break;
|
16
|
+
case IPP_JOB_HELD :
|
17
|
+
jstate = ID2SYM(rb_intern("held"));
|
18
|
+
break;
|
19
|
+
case IPP_JOB_PROCESSING :
|
20
|
+
jstate = ID2SYM(rb_intern("processing"));
|
21
|
+
break;
|
22
|
+
case IPP_JOB_STOPPED :
|
23
|
+
jstate = ID2SYM(rb_intern("stopped"));
|
24
|
+
break;
|
25
|
+
case IPP_JOB_CANCELED :
|
26
|
+
jstate = ID2SYM(rb_intern("cancelled"));
|
27
|
+
break;
|
28
|
+
case IPP_JOB_ABORTED :
|
29
|
+
jstate = ID2SYM(rb_intern("aborted"));
|
30
|
+
break;
|
31
|
+
case IPP_JOB_COMPLETED :
|
32
|
+
jstate = ID2SYM(rb_intern("completed"));
|
33
|
+
break;
|
34
|
+
default:
|
35
|
+
jstate = ID2SYM(rb_intern("unknown"));
|
36
|
+
}
|
37
|
+
return jstate;
|
38
|
+
}
|
39
|
+
|
40
|
+
int printer_exists(VALUE printer){
|
41
|
+
// First call Cups#show_destinations
|
42
|
+
VALUE dest_list = rb_funcall(rubyCups, rb_intern("show_destinations"), 0);
|
43
|
+
// Then check the printer arg is included in the returned array...
|
44
|
+
rb_ary_includes(dest_list, printer) ? 1 : 0;
|
45
|
+
}
|
46
|
+
|
47
|
+
/*
|
48
|
+
* call-seq:
|
49
|
+
* PrintJob.new(filename, printer=nil)
|
50
|
+
*
|
51
|
+
* Initializes a new PrintJob object. If no target printer/class is specified, the default is chosen.
|
52
|
+
* Note the specified file does not have to exist until print is called.
|
53
|
+
*/
|
54
|
+
static VALUE job_init(int argc, VALUE* argv, VALUE self)
|
55
|
+
{
|
56
|
+
VALUE filename, printer, job_options;
|
57
|
+
|
58
|
+
rb_scan_args(argc, argv, "12", &filename, &printer, &job_options);
|
59
|
+
|
60
|
+
rb_iv_set(self, "@filename", filename);
|
61
|
+
|
62
|
+
if (NIL_P(job_options)) {
|
63
|
+
rb_iv_set(self, "@job_options", rb_hash_new());
|
64
|
+
} else {
|
65
|
+
rb_iv_set(self, "@job_options", job_options);
|
66
|
+
}
|
67
|
+
|
68
|
+
if (NIL_P(printer)) {
|
69
|
+
|
70
|
+
// Fall back to default printer
|
71
|
+
VALUE def_p = rb_funcall(rubyCups, rb_intern("default_printer"), 0);
|
72
|
+
|
73
|
+
if (def_p == Qfalse) {
|
74
|
+
rb_raise(rb_eRuntimeError, "There is no default printer!");
|
75
|
+
} else {
|
76
|
+
rb_iv_set(self, "@printer", def_p);
|
77
|
+
}
|
78
|
+
|
79
|
+
} else {
|
80
|
+
if (printer_exists(printer)) {
|
81
|
+
rb_iv_set(self, "@printer", printer);
|
82
|
+
} else {
|
83
|
+
rb_raise(rb_eRuntimeError, "The printer or destination doesn't exist!");
|
84
|
+
}
|
85
|
+
}
|
86
|
+
return self;
|
87
|
+
}
|
88
|
+
|
89
|
+
/*
|
90
|
+
* Note: rb_hash_keys is defined in 1.8.6, but not in 1.8.7 ubuntu shared lib
|
91
|
+
* This is so that I can get a list of keys to convert to options
|
92
|
+
*/
|
93
|
+
static int
|
94
|
+
cups_keys_i(key, value, ary)
|
95
|
+
VALUE key, value, ary;
|
96
|
+
{
|
97
|
+
if (key == Qundef) return ST_CONTINUE;
|
98
|
+
rb_ary_push(ary, key);
|
99
|
+
return ST_CONTINUE;
|
100
|
+
}
|
101
|
+
|
102
|
+
/*
|
103
|
+
* call-seq:
|
104
|
+
* print_job.print -> Fixnum
|
105
|
+
*
|
106
|
+
* Submit a print job to the selected printer or class. Returns true on success.
|
107
|
+
*/
|
108
|
+
static VALUE cups_print(VALUE self)
|
109
|
+
{
|
110
|
+
int job_id;
|
111
|
+
VALUE file = rb_iv_get(self, "@filename");
|
112
|
+
VALUE printer = rb_iv_get(self, "@printer");
|
113
|
+
|
114
|
+
char *fname = RSTRING_PTR(file); // Filename
|
115
|
+
char *target = RSTRING_PTR(printer); // Target printer string
|
116
|
+
|
117
|
+
FILE *fp = fopen(fname,"r");
|
118
|
+
// Check @filename actually exists...
|
119
|
+
if( fp ) {
|
120
|
+
fclose(fp);
|
121
|
+
|
122
|
+
VALUE job_options = rb_iv_get(self, "@job_options");
|
123
|
+
|
124
|
+
// Create an array of the keys from the job_options hash
|
125
|
+
VALUE job_options_keys = rb_ary_new();
|
126
|
+
rb_hash_foreach(job_options, cups_keys_i, job_options_keys);
|
127
|
+
|
128
|
+
VALUE iter;
|
129
|
+
int num_options = 0;
|
130
|
+
cups_option_t *options = NULL;
|
131
|
+
|
132
|
+
// foreach option in the job options array
|
133
|
+
while (! NIL_P(iter = rb_ary_pop(job_options_keys))) {
|
134
|
+
|
135
|
+
VALUE value = rb_hash_aref(job_options, iter);
|
136
|
+
|
137
|
+
// assert the key and value are strings
|
138
|
+
if (NIL_P(rb_check_string_type(iter)) || NIL_P(rb_check_string_type(value))) {
|
139
|
+
cupsFreeOptions(num_options, options);
|
140
|
+
rb_raise(rb_eTypeError, "job options is not string => string hash");
|
141
|
+
return Qfalse;
|
142
|
+
}
|
143
|
+
|
144
|
+
// convert to char pointers and add to cups optoins
|
145
|
+
char * iter_str = rb_string_value_ptr(&iter);
|
146
|
+
char * value_str = rb_string_value_ptr(&value);
|
147
|
+
cupsAddOption(iter_str, value_str, num_options++, &options);
|
148
|
+
}
|
149
|
+
|
150
|
+
job_id = cupsPrintFile(target, fname, "rCUPS", num_options, options); // Do it. "rCups" should be the filename/path
|
151
|
+
|
152
|
+
cupsFreeOptions(num_options, options);
|
153
|
+
|
154
|
+
rb_iv_set(self, "@job_id", INT2NUM(job_id));
|
155
|
+
|
156
|
+
return Qtrue;
|
157
|
+
} else {
|
158
|
+
// and if it doesn't...
|
159
|
+
rb_raise(rb_eRuntimeError, "Couldn't find file");
|
160
|
+
return Qfalse;
|
161
|
+
}
|
162
|
+
}
|
163
|
+
|
164
|
+
/*
|
165
|
+
* call-seq:
|
166
|
+
* Cups.show_destinations -> Array
|
167
|
+
*
|
168
|
+
* Show all destinations on the default server
|
169
|
+
*/
|
170
|
+
static VALUE cups_show_dests(VALUE self)
|
171
|
+
{
|
172
|
+
VALUE dest_list;
|
173
|
+
int i;
|
174
|
+
int num_dests = cupsGetDests(&dests); // Size of dest_list array
|
175
|
+
dest_list = rb_ary_new2(num_dests);
|
176
|
+
|
177
|
+
for (i = num_dests, dest = dests; i > 0; i --, dest ++) {
|
178
|
+
VALUE destination = rb_str_new2(dest->name);
|
179
|
+
rb_ary_push(dest_list, destination); // Add this testination name to dest_list string
|
180
|
+
}
|
181
|
+
return dest_list;
|
182
|
+
}
|
183
|
+
|
184
|
+
/*
|
185
|
+
* call-seq:
|
186
|
+
* Cups.default_printer -> String or nil
|
187
|
+
*
|
188
|
+
* Get default printer or class. Returns a string or false if there is no default
|
189
|
+
*/
|
190
|
+
static VALUE cups_get_default(VALUE self)
|
191
|
+
{
|
192
|
+
const char *default_printer;
|
193
|
+
default_printer = cupsGetDefault();
|
194
|
+
|
195
|
+
if (default_printer != NULL) {
|
196
|
+
VALUE def_p = rb_str_new2(default_printer);
|
197
|
+
return def_p;
|
198
|
+
}
|
199
|
+
// should return nil if no default printer is found!
|
200
|
+
}
|
201
|
+
|
202
|
+
/*
|
203
|
+
* call-seq:
|
204
|
+
* print_job.cancel -> true or false
|
205
|
+
*
|
206
|
+
* Cancel the current job. Returns true if successful, false otherwise.
|
207
|
+
*/
|
208
|
+
static VALUE cups_cancel(VALUE self)
|
209
|
+
{
|
210
|
+
VALUE printer, job_id;
|
211
|
+
printer = rb_iv_get(self, "@printer");
|
212
|
+
job_id = rb_iv_get(self, "@job_id");
|
213
|
+
|
214
|
+
if (NIL_P(job_id)) {
|
215
|
+
return Qfalse; // If @job_id is nil
|
216
|
+
} else { // Otherwise attempt to cancel
|
217
|
+
int job = NUM2INT(job_id);
|
218
|
+
char *target = RSTRING_PTR(printer); // Target printer string
|
219
|
+
int cancellation;
|
220
|
+
cancellation = cupsCancelJob(target, job);
|
221
|
+
return Qtrue;
|
222
|
+
}
|
223
|
+
}
|
224
|
+
|
225
|
+
/*
|
226
|
+
* call-seq:
|
227
|
+
* print_job.failed? -> true or false
|
228
|
+
*
|
229
|
+
* Did this job fail?
|
230
|
+
*/
|
231
|
+
static VALUE cups_job_failed(VALUE self)
|
232
|
+
{
|
233
|
+
VALUE job_id = rb_iv_get(self, "@job_id");
|
234
|
+
|
235
|
+
if (NIL_P(job_id) || !NUM2INT(job_id) == 0) {
|
236
|
+
return Qfalse;
|
237
|
+
} else {
|
238
|
+
return Qtrue;
|
239
|
+
}
|
240
|
+
}
|
241
|
+
|
242
|
+
/*
|
243
|
+
* call-seq:
|
244
|
+
* print_job.error_reason -> String
|
245
|
+
*
|
246
|
+
* Get the last human-readable error string
|
247
|
+
*/
|
248
|
+
static VALUE cups_get_error_reason(VALUE self)
|
249
|
+
{
|
250
|
+
VALUE job_id = rb_iv_get(self, "@job_id");
|
251
|
+
|
252
|
+
if (NIL_P(job_id) || !NUM2INT(job_id) == 0) {
|
253
|
+
return Qnil;
|
254
|
+
} else {
|
255
|
+
VALUE error_exp = rb_str_new2(cupsLastErrorString());
|
256
|
+
return error_exp;
|
257
|
+
}
|
258
|
+
}
|
259
|
+
|
260
|
+
/*
|
261
|
+
* call-seq:
|
262
|
+
* print_job.error_code -> Fixnum
|
263
|
+
*
|
264
|
+
* Get the last IPP error code.
|
265
|
+
*/
|
266
|
+
static VALUE cups_get_error_code(VALUE self)
|
267
|
+
{
|
268
|
+
VALUE job_id = rb_iv_get(self, "@job_id");
|
269
|
+
|
270
|
+
if (NIL_P(job_id) || !NUM2INT(job_id) == 0) {
|
271
|
+
return Qnil;
|
272
|
+
} else {
|
273
|
+
VALUE ipp_error_code = INT2NUM(cupsLastError());
|
274
|
+
return ipp_error_code;
|
275
|
+
}
|
276
|
+
}
|
277
|
+
|
278
|
+
/*
|
279
|
+
* call-seq:
|
280
|
+
* print_job.state -> String
|
281
|
+
*
|
282
|
+
* Get human-readable state of current job.
|
283
|
+
*/
|
284
|
+
static VALUE cups_get_job_state(VALUE self)
|
285
|
+
{
|
286
|
+
VALUE job_id = rb_iv_get(self, "@job_id");
|
287
|
+
VALUE printer = rb_iv_get(self, "@printer");
|
288
|
+
VALUE jstate;
|
289
|
+
|
290
|
+
int num_jobs;
|
291
|
+
cups_job_t *jobs;
|
292
|
+
ipp_jstate_t job_state = IPP_JOB_PENDING;
|
293
|
+
int i;
|
294
|
+
char *printer_arg = RSTRING_PTR(printer);
|
295
|
+
|
296
|
+
if (NIL_P(job_id)) {
|
297
|
+
return Qnil;
|
298
|
+
} else {
|
299
|
+
num_jobs = cupsGetJobs(&jobs, printer_arg, 1, -1); // Get jobs
|
300
|
+
|
301
|
+
for (i = 0; i < num_jobs; i ++) {
|
302
|
+
if (jobs[i].id == NUM2INT(job_id)) {
|
303
|
+
job_state = jobs[i].state;
|
304
|
+
break;
|
305
|
+
}
|
306
|
+
}
|
307
|
+
|
308
|
+
// Free job array
|
309
|
+
cupsFreeJobs(num_jobs, jobs);
|
310
|
+
|
311
|
+
jstate = ipp_state_to_symbol(job_state);
|
312
|
+
|
313
|
+
return jstate;
|
314
|
+
}
|
315
|
+
}
|
316
|
+
|
317
|
+
/*
|
318
|
+
* call-seq:
|
319
|
+
* print_job.completed? -> true or false
|
320
|
+
*
|
321
|
+
* Has the job completed?
|
322
|
+
*/
|
323
|
+
static VALUE cups_job_completed(VALUE self)
|
324
|
+
{
|
325
|
+
VALUE job_id = rb_iv_get(self, "@job_id");
|
326
|
+
VALUE printer = rb_iv_get(self, "@printer");
|
327
|
+
VALUE jstate;
|
328
|
+
|
329
|
+
int num_jobs;
|
330
|
+
cups_job_t *jobs;
|
331
|
+
ipp_jstate_t job_state = IPP_JOB_PENDING;
|
332
|
+
int i;
|
333
|
+
char *printer_arg = RSTRING_PTR(printer);
|
334
|
+
|
335
|
+
if (NIL_P(job_id)) {
|
336
|
+
return Qfalse;
|
337
|
+
} else {
|
338
|
+
num_jobs = cupsGetJobs(&jobs, printer_arg, 1, -1); // Get jobs
|
339
|
+
// job_state = IPP_JOB_COMPLETED;
|
340
|
+
|
341
|
+
for (i = 0; i < num_jobs; i ++) {
|
342
|
+
if (jobs[i].id == NUM2INT(job_id)) {
|
343
|
+
job_state = jobs[i].state;
|
344
|
+
break;
|
345
|
+
}
|
346
|
+
|
347
|
+
// Free job array
|
348
|
+
cupsFreeJobs(num_jobs, jobs);
|
349
|
+
|
350
|
+
if (job_state == IPP_JOB_COMPLETED) {
|
351
|
+
return Qtrue;
|
352
|
+
} else {
|
353
|
+
return Qfalse;
|
354
|
+
}
|
355
|
+
|
356
|
+
}
|
357
|
+
}
|
358
|
+
}
|
359
|
+
|
360
|
+
/*
|
361
|
+
* call-seq:
|
362
|
+
* Cups.all_jobs(printer) -> Hash
|
363
|
+
*
|
364
|
+
* Get all jobs from default CUPS server. Takes a single printer/class string argument.
|
365
|
+
* Returned hash keys are CUPS job ids, and the values are hashes of job info
|
366
|
+
* with keys:
|
367
|
+
*
|
368
|
+
* [:title, :submitted_by, :size, :format, :state]
|
369
|
+
*/
|
370
|
+
|
371
|
+
|
372
|
+
static VALUE cups_get_jobs(VALUE self, VALUE printer)
|
373
|
+
{
|
374
|
+
// Don't have to lift a finger unless the printer exists.
|
375
|
+
if (!printer_exists(printer)){
|
376
|
+
rb_raise(rb_eRuntimeError, "The printer or destination doesn't exist!");
|
377
|
+
}
|
378
|
+
|
379
|
+
VALUE job_list, job_info_hash, jid, jtitle, juser, jsize, jformat, jstate;
|
380
|
+
int job_id;
|
381
|
+
int num_jobs;
|
382
|
+
cups_job_t *jobs;
|
383
|
+
ipp_jstate_t state;
|
384
|
+
int i;
|
385
|
+
char *printer_arg = RSTRING_PTR(printer);
|
386
|
+
|
387
|
+
num_jobs = cupsGetJobs(&jobs, printer_arg, 1, -1); // Get jobs
|
388
|
+
job_list = rb_hash_new();
|
389
|
+
|
390
|
+
for (i = 0; i < num_jobs; i ++) { // Construct a hash of individual job info
|
391
|
+
job_info_hash = rb_hash_new();
|
392
|
+
jid = INT2NUM(jobs[i].id);
|
393
|
+
jtitle = rb_str_new2(jobs[i].title);
|
394
|
+
juser = rb_str_new2(jobs[i].user);
|
395
|
+
jsize = INT2NUM(jobs[i].size);
|
396
|
+
jformat = rb_str_new2(jobs[i].format);
|
397
|
+
jstate = ipp_state_to_symbol(jobs[i].state);
|
398
|
+
|
399
|
+
rb_hash_aset(job_info_hash, ID2SYM(rb_intern("title")), jtitle);
|
400
|
+
rb_hash_aset(job_info_hash, ID2SYM(rb_intern("submitted_by")), juser);
|
401
|
+
rb_hash_aset(job_info_hash, ID2SYM(rb_intern("size")), jsize);
|
402
|
+
rb_hash_aset(job_info_hash, ID2SYM(rb_intern("format")), jformat);
|
403
|
+
rb_hash_aset(job_info_hash, ID2SYM(rb_intern("state")), jstate);
|
404
|
+
|
405
|
+
rb_hash_aset(job_list, jid, job_info_hash); // And push it all into job_list hash
|
406
|
+
}
|
407
|
+
|
408
|
+
// Free job array
|
409
|
+
cupsFreeJobs(num_jobs, jobs);
|
410
|
+
|
411
|
+
return job_list;
|
412
|
+
}
|
413
|
+
|
414
|
+
/*
|
415
|
+
* call-seq:
|
416
|
+
* Cups.options_for(name) -> Hash or nil
|
417
|
+
*
|
418
|
+
* Get all options from CUPS server with name. Returns a hash with key/value pairs
|
419
|
+
* based on server options, or nil if no server with name.
|
420
|
+
*/
|
421
|
+
static VALUE cups_get_options(VALUE self, VALUE printer)
|
422
|
+
{
|
423
|
+
// Don't have to lift a finger unless the printer exists.
|
424
|
+
if (!printer_exists(printer)){
|
425
|
+
rb_raise(rb_eRuntimeError, "The printer or destination doesn't exist!");
|
426
|
+
}
|
427
|
+
|
428
|
+
VALUE options_list;
|
429
|
+
int i;
|
430
|
+
char *printer_arg = RSTRING_PTR(printer);
|
431
|
+
|
432
|
+
options_list = rb_hash_new();
|
433
|
+
|
434
|
+
cups_dest_t *dests;
|
435
|
+
int num_dests = cupsGetDests(&dests);
|
436
|
+
cups_dest_t *dest = cupsGetDest(printer_arg, NULL, num_dests, dests);
|
437
|
+
|
438
|
+
if (dest == NULL) {
|
439
|
+
cupsFreeDests(num_dests, dests);
|
440
|
+
return Qnil;
|
441
|
+
} else {
|
442
|
+
for(i =0; i< dest->num_options; i++) {
|
443
|
+
rb_hash_aset(options_list, rb_str_new2(dest->options[i].name), rb_str_new2(dest->options[i].value));
|
444
|
+
}
|
445
|
+
|
446
|
+
cupsFreeDests(num_dests, dests);
|
447
|
+
return options_list;
|
448
|
+
}
|
449
|
+
|
450
|
+
}
|
451
|
+
|
452
|
+
/*
|
453
|
+
*/
|
454
|
+
|
455
|
+
void Init_cups() {
|
456
|
+
rubyCups = rb_define_module("Cups");
|
457
|
+
printJobs = rb_define_class_under(rubyCups, "PrintJob", rb_cObject);
|
458
|
+
|
459
|
+
// Cups::PrintJob Attributes
|
460
|
+
rb_define_attr(printJobs, "printer", 1, 0);
|
461
|
+
rb_define_attr(printJobs, "filename", 1, 0);
|
462
|
+
rb_define_attr(printJobs, "job_id", 1, 0);
|
463
|
+
rb_define_attr(printJobs, "job_options", 1, 0);
|
464
|
+
|
465
|
+
// Cups::PrintJob Methods
|
466
|
+
rb_define_method(printJobs, "initialize", job_init, -1);
|
467
|
+
rb_define_method(printJobs, "print", cups_print, 0);
|
468
|
+
rb_define_method(printJobs, "cancel", cups_cancel, 0);
|
469
|
+
rb_define_method(printJobs, "state", cups_get_job_state, 0);
|
470
|
+
rb_define_method(printJobs, "completed?", cups_job_completed, 0);
|
471
|
+
rb_define_method(printJobs, "failed?", cups_job_failed, 0);
|
472
|
+
rb_define_method(printJobs, "error_reason", cups_get_error_reason, 0);
|
473
|
+
rb_define_method(printJobs, "error_code", cups_get_error_code, 0);
|
474
|
+
|
475
|
+
// Cups Module Methods
|
476
|
+
rb_define_singleton_method(rubyCups, "show_destinations", cups_show_dests, 0);
|
477
|
+
rb_define_singleton_method(rubyCups, "default_printer", cups_get_default, 0);
|
478
|
+
rb_define_singleton_method(rubyCups, "all_jobs", cups_get_jobs, 1);
|
479
|
+
rb_define_singleton_method(rubyCups, "options_for", cups_get_options, 1);
|
480
|
+
}
|
data/ext/extconf.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require "mkmf"
|
2
|
+
|
3
|
+
unless have_library("cups") && find_executable("cups-config")
|
4
|
+
puts "Couldn't find CUPS libraries on your system. Check they're installed and in your path."
|
5
|
+
exit
|
6
|
+
end
|
7
|
+
|
8
|
+
cups_cflags = `cups-config --cflags`.chomp || ""
|
9
|
+
cups_libs = `cups-config --libs`.chomp || ""
|
10
|
+
|
11
|
+
with_cflags(cups_cflags) {
|
12
|
+
with_ldflags(cups_libs) {
|
13
|
+
create_makefile("cups")
|
14
|
+
}
|
15
|
+
}
|
data/ext/ruby_cups.h
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require "cups"
|
2
|
+
require "tempfile"
|
3
|
+
|
4
|
+
module Cups
|
5
|
+
|
6
|
+
class PrintJob
|
7
|
+
|
8
|
+
# Unlike its superclass, a Transient object takes a string of data (e.g. from an IO object).
|
9
|
+
# This is useful when you've got something like a PDF, an ImageMagick byte array, etc that you
|
10
|
+
# just want to send on its way rather than having to touch the file system directly.
|
11
|
+
# === A contrived example:
|
12
|
+
# Say we're using Prawn[http://prawn.majesticseacreature.com/] to generate PDF invoices for some nebulous,
|
13
|
+
# ever-expanding system that promises us sex, drugs, rock & roll, fame and perhaps a pay-rise. Instead of just
|
14
|
+
# generating a file, let's pass the rendered version to a Transient object and let it take care of things:
|
15
|
+
#
|
16
|
+
# ---
|
17
|
+
#
|
18
|
+
# require 'cups/print_job/transient'
|
19
|
+
# require 'prawn'
|
20
|
+
#
|
21
|
+
# invoice = Prawn::Document.new do
|
22
|
+
# text "Invoice."
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# paper_copy = Cups::PrintJob::Transient.new(invoice.render)
|
26
|
+
# paper_copy.print
|
27
|
+
#
|
28
|
+
# ---
|
29
|
+
#
|
30
|
+
# As of 0.0.5, all instance methods inherited from PrintJob are unaltered. This may change if I need to
|
31
|
+
# delay passing the print data or change it after initialization, just as PrintJob currently permits the
|
32
|
+
# file to be written to/moved/nonexistent until print is called.
|
33
|
+
#
|
34
|
+
# Enjoy.
|
35
|
+
|
36
|
+
class Transient < PrintJob
|
37
|
+
|
38
|
+
alias_method :old_init, :initialize
|
39
|
+
|
40
|
+
# Create a print job from a non-empty string. Takes optional printer argument, otherwise uses default.
|
41
|
+
# As the tempfile is written to and closed upon initialization, an error will be raised if an empty
|
42
|
+
# string is passed as the first argument.
|
43
|
+
def initialize(data_string, printer=nil)
|
44
|
+
raise "Temporary print job has no data!" if data_string.empty?
|
45
|
+
|
46
|
+
file = Tempfile.new('')
|
47
|
+
file.puts(data_string)
|
48
|
+
file.close
|
49
|
+
|
50
|
+
old_init(file.path, printer)
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require "cups"
|
2
|
+
|
3
|
+
module Cups
|
4
|
+
|
5
|
+
# Right now this just wraps the Cups.options_for hash using method_missing, allowing you to call
|
6
|
+
# things like Printer#printer_make_and_model on an instance.
|
7
|
+
class Printer
|
8
|
+
|
9
|
+
# Creates a Cups::Printer object. Defaults to the default printer, if there is one.
|
10
|
+
def initialize(printer=nil)
|
11
|
+
if printer.nil?
|
12
|
+
raise "There is no default printer!" if !Cups.default_printer
|
13
|
+
@printer = Cups.default_printer
|
14
|
+
else
|
15
|
+
raise "The printer or destination doesn't exist!" unless Cups.show_destinations.include?(printer)
|
16
|
+
@printer = printer
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Call an options key on this object (with hyphens substituted with underscores). Passes onto super if
|
21
|
+
# no key is found or its value is nil.
|
22
|
+
def method_missing(m)
|
23
|
+
get_options
|
24
|
+
key = m.to_s.gsub(/\_/, "-")
|
25
|
+
@options[key].nil? ? super : @options[key]
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def get_options
|
31
|
+
@options ||= Cups.options_for(@printer)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
data/test/cups_test.rb
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path( File.dirname(__FILE__) )
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require "cups"
|
5
|
+
require "test/unit"
|
6
|
+
|
7
|
+
# The tests which don't make use of mocking go on the operating assumption that you have
|
8
|
+
# the CUPS command line utilities installed and in your $PATH
|
9
|
+
|
10
|
+
class CupsTest < Test::Unit::TestCase
|
11
|
+
|
12
|
+
def setup
|
13
|
+
@printer = Cups.show_destinations.select {|p| p =~ /pdf/i}.first
|
14
|
+
raise "Can't find a PDF printer to run tests with." unless @printer
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_same_printers_returned
|
18
|
+
lplist = `lpstat -a`.split("\n").map { |pr| pr.split(' ').first }
|
19
|
+
cups_destinations = Cups.show_destinations
|
20
|
+
assert cups_destinations.is_a?(Array)
|
21
|
+
assert_equal cups_destinations, lplist
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_can_instantiate_print_job_object_with_correct_args
|
25
|
+
assert_raise(ArgumentError) do
|
26
|
+
Cups::PrintJob.new
|
27
|
+
end
|
28
|
+
|
29
|
+
assert_nothing_raised do
|
30
|
+
Cups::PrintJob.new("/path", @printer)
|
31
|
+
Cups::PrintJob.new("/path")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_job_defaults_to_default
|
36
|
+
assert_nothing_raised do
|
37
|
+
pj = Cups::PrintJob.new("/non/existent/file")
|
38
|
+
|
39
|
+
assert_equal Cups.default_printer, pj.instance_variable_get(:@printer)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_we_cant_print_to_nonexistent_printers
|
44
|
+
assert_raise(RuntimeError) do
|
45
|
+
pj = Cups::PrintJob.new("/non/existent/file", "bollocks_printer")
|
46
|
+
|
47
|
+
assert !Cups.show_destinations.include?(pj.instance_variable_get(:@printer))
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_we_cant_print_nonexistent_files
|
52
|
+
pj = Cups::PrintJob.new("soft_class")
|
53
|
+
|
54
|
+
assert_raise(RuntimeError) do
|
55
|
+
pj.print
|
56
|
+
end
|
57
|
+
|
58
|
+
assert_nil pj.job_id
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_we_can_pass_args_down_as_options
|
62
|
+
options = {:foo => 'bar'}
|
63
|
+
pj = Cups::PrintJob.new(sample, @printer, options)
|
64
|
+
assert_equal(options, pj.job_options)
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_we_can_only_pass_strings_down_as_options
|
68
|
+
options = {:foo => 'bar'}
|
69
|
+
pj = Cups::PrintJob.new(sample, @printer, options)
|
70
|
+
assert_raise(TypeError) { pj.print }
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_we_can_omit_options_and_will_set_to_empty
|
74
|
+
pj = Cups::PrintJob.new(sample, @printer)
|
75
|
+
assert_equal({}, pj.job_options)
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_print_job_cancellation
|
79
|
+
pj = Cups::PrintJob.new(sample, @printer)
|
80
|
+
pj.print
|
81
|
+
assert_not_nil pj.job_id
|
82
|
+
assert_equal pj.cancel, true
|
83
|
+
assert pj.job_id.is_a?(Fixnum)
|
84
|
+
end
|
85
|
+
|
86
|
+
def test_all_jobs_raises_with_nonexistent_printers
|
87
|
+
assert_raise(RuntimeError) { Cups.all_jobs(nil) }
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_all_jobs_returns_hash
|
91
|
+
assert Cups.all_jobs(Cups.default_printer).is_a?(Hash)
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_all_jobs_hash_contains_info_hash
|
95
|
+
pj = Cups::PrintJob.new(sample, @printer)
|
96
|
+
pj.print
|
97
|
+
info = Cups.all_jobs(@printer)[pj.job_id]
|
98
|
+
assert info.is_a?(Hash)
|
99
|
+
assert info.keys.all?{|key| [:title, :format, :submitted_by, :state, :size].include?(key)}
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_dest_list_returns_array
|
103
|
+
assert Cups.show_destinations.is_a?(Array)
|
104
|
+
end
|
105
|
+
|
106
|
+
def test_dest_options_returns_hash_if_real
|
107
|
+
assert Cups.options_for(@printer).is_a?(Hash)
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_dest_options_raises_exception_if_not_real
|
111
|
+
assert_raise(RuntimeError, "The printer or destination doesn't exist!") { Cups.options_for("bollocks_printer") }
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_job_failed_boolean
|
115
|
+
pj = Cups::PrintJob.new(sample, @printer)
|
116
|
+
pj.print
|
117
|
+
pj.cancel
|
118
|
+
assert !pj.failed?
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_returns_failure_string_on_cancellation
|
122
|
+
pj = Cups::PrintJob.new(blank_sample, @printer)
|
123
|
+
pj.print
|
124
|
+
|
125
|
+
# assert pj.job_id == 0 # Failed jobs have an ID of zero
|
126
|
+
# assert pj.failed?
|
127
|
+
|
128
|
+
# assert pj.error_reason.is_a?(Symbol)
|
129
|
+
end
|
130
|
+
|
131
|
+
def test_job_state_string
|
132
|
+
pj = Cups::PrintJob.new(sample, @printer)
|
133
|
+
assert_nil pj.state # A job can't have a state if it hasn't been assigned a job_id yet
|
134
|
+
assert !pj.completed?
|
135
|
+
|
136
|
+
pj.print
|
137
|
+
|
138
|
+
pj.cancel
|
139
|
+
assert pj.state == :cancelled
|
140
|
+
assert !pj.completed?
|
141
|
+
end
|
142
|
+
|
143
|
+
def test_print_job_attributes
|
144
|
+
pj = Cups::PrintJob.new(sample)
|
145
|
+
[:printer, :filename, :job_id].each do |attr|
|
146
|
+
assert pj.respond_to?(attr)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
private
|
151
|
+
|
152
|
+
def sample
|
153
|
+
"#{File.dirname(__FILE__)}/sample.txt"
|
154
|
+
end
|
155
|
+
|
156
|
+
def blank_sample
|
157
|
+
"#{File.dirname(__FILE__)}/sample_blank.txt"
|
158
|
+
end
|
159
|
+
end
|
data/test/sample.txt
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris rutrum, nibh ut hendrerit luctus, nisl risus ultrices lorem, sed luctus ipsum velit eget ante. Vestibulum eget est. Sed lectus odio, eleifend non, iaculis in, varius ut, sapien. Suspendisse suscipit urna in sem. Cras in mauris. Sed sapien urna, semper vitae, vulputate a, venenatis et, lacus. Praesent tristique turpis quis quam. Nullam a velit a erat imperdiet luctus. Cras vulputate dignissim leo. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Ut nulla lorem, dictum quis, cursus eu, dictum ac, nisi.
|
2
|
+
|
3
|
+
Pellentesque posuere commodo nunc. Sed eu turpis ac neque cursus aliquet. Nullam semper turpis. In sit amet metus. Nunc vel diam. Praesent sit amet ipsum varius dolor pulvinar pellentesque. Nullam hendrerit. Curabitur condimentum luctus nisi. Morbi eu erat vel eros aliquam sagittis. Sed pellentesque hendrerit augue. Pellentesque facilisis, dui at placerat vulputate, odio metus pulvinar metus, ac porta metus nibh a tortor. Cras lorem dolor, ornare quis, tempor quis, faucibus ut, metus. Donec ac ipsum. Sed pretium.
|
4
|
+
|
5
|
+
Praesent elementum dapibus sapien. Nam eleifend faucibus felis. Aliquam erat volutpat. In egestas, ipsum non rutrum cursus, ante libero hendrerit enim, ut lobortis nibh ligula sit amet enim. Nullam dui nulla, ullamcorper et, posuere ac, hendrerit vel, ligula. Suspendisse vel urna non nibh ultrices congue. Sed neque. Sed id tellus eget turpis aliquet mollis. Suspendisse sed eros. Vivamus nulla. Maecenas pulvinar. Aliquam sodales blandit mi.
|
6
|
+
|
7
|
+
Curabitur lacinia. Mauris vehicula enim at odio. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque augue sapien, faucibus et, pulvinar vel, tempor eget, elit. Cras a leo vitae justo aliquam laoreet. In et orci sed leo scelerisque condimentum. Integer scelerisque condimentum nisl. Aenean sit amet ligula. In fringilla. Nam elementum diam in dui.
|
8
|
+
|
9
|
+
Maecenas aliquam volutpat lectus. Vivamus tellus purus, scelerisque ac, suscipit sed, rhoncus id, justo. Quisque dolor sapien, accumsan vitae, dignissim sed, pulvinar nec, ligula. Cras ligula quam, iaculis ut, ultrices id, varius nec, neque. Mauris ornare nisl ac nunc. Maecenas interdum nisl et quam. Etiam purus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Nam sodales odio. Cras eu ipsum. Donec fringilla odio. Etiam vitae felis vitae lectus porttitor molestie. Nunc consectetur enim at elit tincidunt tempor. Ut fringilla, tortor in rutrum egestas, magna ante suscipit nunc, sit amet sagittis justo quam quis est. Nulla sit amet tortor nec ligula ultricies placerat. Cras congue quam in magna. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce a ante. Etiam congue nunc quis ligula. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
File without changes
|
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: thorsson_cups
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 8
|
9
|
+
version: 0.0.8
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Ivan Turkovic
|
13
|
+
- Chris Mowforth
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-12-08 00:00:00 +01:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: " Ruby CUPS provides a wrapper for the Common UNIX Printing System, allowing rubyists to perform basic tasks like printer discovery, job submission & querying.\n"
|
23
|
+
email:
|
24
|
+
- me@ivanturkovic.com
|
25
|
+
- chris@mowforth.com
|
26
|
+
executables: []
|
27
|
+
|
28
|
+
extensions:
|
29
|
+
- ext/extconf.rb
|
30
|
+
extra_rdoc_files: []
|
31
|
+
|
32
|
+
files:
|
33
|
+
- test/cups_test.rb
|
34
|
+
- test/sample_blank.txt
|
35
|
+
- test/sample.txt
|
36
|
+
- ext/cups.c
|
37
|
+
- ext/ruby_cups.h
|
38
|
+
- ext/extconf.rb
|
39
|
+
- lib/cups/printer/printer.rb
|
40
|
+
- lib/cups/print_job/transient.rb
|
41
|
+
has_rdoc: true
|
42
|
+
homepage: https://github.com/Thorsson/cups
|
43
|
+
licenses: []
|
44
|
+
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options: []
|
47
|
+
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
segments:
|
56
|
+
- 0
|
57
|
+
version: "0"
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
segments:
|
64
|
+
- 0
|
65
|
+
version: "0"
|
66
|
+
requirements: []
|
67
|
+
|
68
|
+
rubyforge_project: thorsson_cups
|
69
|
+
rubygems_version: 1.3.7
|
70
|
+
signing_key:
|
71
|
+
specification_version: 3
|
72
|
+
summary: A lightweight Ruby library for printing.
|
73
|
+
test_files: []
|
74
|
+
|