chiark / gitweb /
extended @movable expansion; more template...
[disorder] / server / macros-disorder.c
1 /*
2  * This file is part of DisOrder.
3  * Copyright (C) 2004-2008 Richard Kettlewell
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
18  * USA
19  */
20 /** @file server/macros-disorder.c
21  * @brief DisOrder-specific expansions
22  */
23
24 #include "disorder-cgi.h"
25
26 /** @brief For error template */
27 char *dcgi_error_string;
28
29 /** @brief Return @p i as a string */
30 static const char *make_index(int i) {
31   char *s;
32
33   byte_xasprintf(&s, "%d", i);
34   return s;
35 }
36
37 /* @server-version
38  *
39  * Expands to the server's version string, or a (safe to use) error
40  * value if the server is unavailable or broken.
41  */
42 static int exp_server_version(int attribute((unused)) nargs,
43                               char attribute((unused)) **args,
44                               struct sink *output,
45                               void attribute((unused)) *u) {
46   const char *v;
47   
48   if(dcgi_client) {
49     if(disorder_version(dcgi_client, (char **)&v))
50       v = "(cannot get version)";
51   } else
52     v = "(server not running)";
53   return sink_writes(output, cgi_sgmlquote(v)) < 0 ? -1 : 0;
54 }
55
56 /* @version
57  *
58  * Expands to the local version string.
59  */
60 static int exp_version(int attribute((unused)) nargs,
61                        char attribute((unused)) **args,
62                        struct sink *output,
63                        void attribute((unused)) *u) {
64   return sink_writes(output,
65                      cgi_sgmlquote(disorder_short_version_string)) < 0 ? -1 : 0;
66 }
67
68 /* @url
69  *
70  * Expands to the base URL of the web interface.
71  */
72 static int exp_url(int attribute((unused)) nargs,
73                    char attribute((unused)) **args,
74                    struct sink *output,
75                    void attribute((unused)) *u) {
76   return sink_writes(output,
77                      cgi_sgmlquote(config->url)) < 0 ? -1 : 0;
78 }
79
80 /* @arg{NAME}
81  *
82  * Expands to the CGI argument NAME, or the empty string if there is
83  * no such argument.
84  */
85 static int exp_arg(int attribute((unused)) nargs,
86                    char **args,
87                    struct sink *output,
88                    void attribute((unused)) *u) {
89   const char *s = cgi_get(args[0]);
90
91   if(s)
92     return sink_writes(output,
93                        cgi_sgmlquote(s)) < 0 ? -1 : 0;
94   else
95     return 0;
96 }
97
98 /* @user
99  *
100  * Expands to the logged-in username (which might be "guest"), or to
101  * the empty string if not connected.
102  */
103 static int exp_user(int attribute((unused)) nargs,
104                     char attribute((unused)) **args,
105                     struct sink *output,
106                     void attribute((unused)) *u) {
107   const char *user;
108   
109   if(dcgi_client && (user = disorder_user(dcgi_client)))
110     return sink_writes(output, cgi_sgmlquote(user)) < 0 ? -1 : 0;
111   return 0;
112 }
113
114 /* @part{TRACK|ID}{PART}{CONTEXT}
115  *
116  * Expands to a track name part.
117  *
118  * A track may be identified by name or by queue ID.
119  *
120  * CONTEXT may be omitted.  If it is then 'display' is assumed.
121  *
122  * If the CONTEXT is 'short' then the 'display' part is looked up, and the
123  * result truncated according to the length defined by the short_display
124  * configuration directive.
125  */
126 static int exp_part(int nargs,
127                     char **args,
128                     struct sink *output,
129                     void attribute((unused)) *u) {
130   const char *track = args[0], *part = args[1];
131   const char *context = nargs > 2 ? args[2] : "display";
132   char *s;
133
134   if(track[0] != '/') {
135     struct queue_entry *q = dcgi_findtrack(track);
136
137     if(q)
138       track = q->track;
139     else
140       return 0;
141   }
142   if(dcgi_client
143      && !disorder_part(dcgi_client, &s,
144                        track,
145                        !strcmp(context, "short") ? "display" : context,
146                        part))
147     return sink_writes(output, cgi_sgmlquote(s)) < 0 ? -1 : 0;
148   return 0;
149 }
150
151 /* @quote{STRING}
152  *
153  * SGML-quotes STRING.  Note that most expansion results are already suitable
154  * quoted, so this expansion is usually not required.
155  */
156 static int exp_quote(int attribute((unused)) nargs,
157                      char **args,
158                      struct sink *output,
159                      void attribute((unused)) *u) {
160   return sink_writes(output, cgi_sgmlquote(args[0])) < 0 ? -1 : 0;
161 }
162
163 /* @who{ID}
164  *
165  * Expands to the name of the submitter of track ID, which must be a playing
166  * track, in the queue, or in the recent list.
167  */
168 static int exp_who(int attribute((unused)) nargs,
169                    char **args,
170                    struct sink *output,
171                    void attribute((unused)) *u) {
172   struct queue_entry *q = dcgi_findtrack(args[0]);
173
174   if(q && q->submitter)
175     return sink_writes(output, cgi_sgmlquote(q->submitter)) < 0 ? -1 : 0;
176   return 0;
177 }
178
179 /* @when{ID}
180  *
181  * Expands to the time a track started or is expected to start.  The track must
182  * be a playing track, in the queue, or in the recent list.
183  */
184 static int exp_when(int attribute((unused)) nargs,
185                    char **args,
186                    struct sink *output,
187                     void attribute((unused)) *u) {
188   struct queue_entry *q = dcgi_findtrack(args[0]);
189   const struct tm *w = 0;
190
191   if(q) {
192     switch(q->state) {
193     case playing_isscratch:
194     case playing_unplayed:
195     case playing_random:
196       if(q->expected)
197         w = localtime(&q->expected);
198       break;
199     case playing_failed:
200     case playing_no_player:
201     case playing_ok:
202     case playing_scratched:
203     case playing_started:
204     case playing_paused:
205     case playing_quitting:
206       if(q->played)
207         w = localtime(&q->played);
208       break;
209     }
210     if(w)
211       return sink_printf(output, "%d:%02d", w->tm_hour, w->tm_min) < 0 ? -1 : 0;
212   }
213   return sink_writes(output, "&nbsp;") < 0 ? -1 : 0;
214 }
215
216 /* @length{ID|TRACK}
217  *
218  * Expands to the length of a track, identified by its queue ID or its name.
219  * If it is the playing track (identified by ID) then the amount played so far
220  * is included.
221  */
222 static int exp_length(int attribute((unused)) nargs,
223                    char **args,
224                    struct sink *output,
225                    void attribute((unused)) *u) {
226   struct queue_entry *q;
227   long length = 0;
228   const char *name;
229
230   if(args[0][0] == '/')
231     /* Track identified by name */
232     name = args[0];
233   else {
234     /* Track identified by queue ID */
235     if(!(q = dcgi_findtrack(args[0])))
236       return 0;
237     if(q->state == playing_started || q->state == playing_paused)
238       if(sink_printf(output, "%ld:%02ld/", q->sofar / 60, q->sofar % 60) < 0)
239         return -1;
240     name = q->track;
241   }
242   if(dcgi_client && disorder_length(dcgi_client, name, &length))
243     return sink_printf(output, "%ld:%02ld",
244                        length / 60, length % 60) < 0 ? -1 : 0;
245   return sink_writes(output, "&nbsp;") < 0 ? -1 : 0;
246 }
247
248 /* @removable{ID}
249  *
250  * Expands to "true" if track ID is removable (or scratchable, if it is the
251  * playing track) and "false" otherwise.
252  */
253 static int exp_removable(int attribute((unused)) nargs,
254                          char **args,
255                          struct sink *output,
256                          void attribute((unused)) *u) {
257   struct queue_entry *q = dcgi_findtrack(args[0]);
258   /* TODO would be better to reject recent */
259
260   if(!q || !dcgi_client)
261     return mx_bool_result(output, 0);
262   dcgi_lookup(DCGI_RIGHTS);
263   return mx_bool_result(output,
264                         (q == dcgi_playing ? right_scratchable : right_removable)
265                             (dcgi_rights, disorder_user(dcgi_client), q));
266 }
267
268 /* @movable{ID}{DIR}
269  *
270  * Expands to "true" if track ID is movable and "false" otherwise.
271  *
272  * DIR (which is optional) should be a non-zero integer.  If it is negative
273  * then the intended move is down (later in the queue), if it is positive then
274  * the intended move is up (earlier in the queue).  The first track is not
275  * movable up and the last track not movable down.
276  */
277 static int exp_movable(int  nargs,
278                        char **args,
279                        struct sink *output,
280                        void attribute((unused)) *u) {
281   struct queue_entry *q = dcgi_findtrack(args[0]);
282   /* TODO would be better to recent playing/recent */
283
284   if(!q || !dcgi_client)
285     return mx_bool_result(output, 0);
286   if(nargs > 1) {
287     const long dir = atoi(args[1]);
288
289     if(dir > 0 && q == dcgi_queue)
290       return mx_bool_result(output, 0);
291     if(dir < 0 && q->next == 0) 
292       return mx_bool_result(output, 0);
293   }
294   dcgi_lookup(DCGI_RIGHTS);
295   return mx_bool_result(output,
296                         right_movable(dcgi_rights,
297                                       disorder_user(dcgi_client),
298                                       q));
299 }
300
301 /* @playing{TEMPLATE}
302  *
303  * Expands to TEMPLATE, with:
304  * - @id@ expanded to the queue ID of the playing track
305  * - @track@ expanded to its UNQUOTED name
306  *
307  * If no track is playing expands to nothing.
308  *
309  * TEMPLATE is optional.  If it is left out then instead expands to the queue
310  * ID of the playing track.
311  */
312 static int exp_playing(int nargs,
313                        const struct mx_node **args,
314                        struct sink *output,
315                        void *u) {
316   dcgi_lookup(DCGI_PLAYING);
317   if(!dcgi_playing)
318     return 0;
319   if(!nargs)
320     return sink_writes(output, dcgi_playing->id) < 0 ? -1 : 0;
321   return mx_expand(mx_rewritel(args[0],
322                                "id", dcgi_playing->id,
323                                "track", dcgi_playing->track,
324                                (char *)0),
325                    output, u);
326 }
327
328 /* @queue{TEMPLATE}
329  *
330  * For each track in the queue, expands TEMPLATE with the following expansions:
331  * - @id@ to the queue ID of the track
332  * - @track@ to the UNQUOTED track name
333  * - @index@ to the track number from 0
334  * - @parity@ to "even" or "odd" alternately
335  * - @first@ to "true" on the first track and "false" otherwise
336  * - @last@ to "true" on the last track and "false" otherwise
337  */
338 static int exp_queue(int attribute((unused)) nargs,
339                      const struct mx_node **args,
340                      struct sink *output,
341                      void *u) {
342   struct queue_entry *q;
343   int rc, i;
344   
345   dcgi_lookup(DCGI_QUEUE);
346   for(q = dcgi_queue, i = 0; q; q = q->next, ++i)
347     if((rc = mx_expand(mx_rewritel(args[0],
348                                    "id", q->id,
349                                    "track", q->track,
350                                    "index", make_index(i),
351                                    "parity", i % 2 ? "odd" : "even",
352                                    "first", q == dcgi_queue ? "true" : "false",
353                                    "last", q->next ? "false" : "true",
354                                    (char *)0),
355                        output, u)))
356       return rc;
357   return 0;
358 }
359
360 /* @recent{TEMPLATE}
361  *
362  * For each track in the recently played list, expands TEMPLATE with the
363  * following expansions:
364  * - @id@ to the queue ID of the track
365  * - @track@  to the UNQUOTED track name
366  * - @index@ to the track number from 0
367  * - @parity@ to "even" or "odd" alternately
368  * - @first@ to "true" on the first track and "false" otherwise
369  * - @last@ to "true" on the last track and "false" otherwise
370  */
371 static int exp_recent(int attribute((unused)) nargs,
372                       const struct mx_node **args,
373                       struct sink *output,
374                       void *u) {
375   struct queue_entry *q;
376   int rc, i;
377   
378   dcgi_lookup(DCGI_RECENT);
379   for(q = dcgi_recent, i = 0; q; q = q->next, ++i)
380     if((rc = mx_expand(mx_rewritel(args[0],
381                                    "id", q->id,
382                                    "track", q->track,
383                                    "index", make_index(i),
384                                    "parity", i % 2 ? "odd" : "even",
385                                    "first", q == dcgi_recent ? "true" : "false",
386                                    "last", q->next ? "false" : "true",
387                                    (char *)0),
388                        output, u)))
389       return rc;
390   return 0;
391 }
392
393 /* @new{TEMPLATE}
394  *
395  * For each track in the newly added list, expands TEMPLATE wit the following
396  * expansions:
397  * - @track@ to the UNQUOTED track name
398  * - @index@ to the track number from 0
399  * - @parity@ to "even" or "odd" alternately
400  * - @first@ to "true" on the first track and "false" otherwise
401  * - @last@ to "true" on the last track and "false" otherwise
402  *
403  * Note that unlike @playing@, @queue@ and @recent@ which are otherwise
404  * superficially similar, there is no @id@ sub-expansion here.
405  */
406 static int exp_new(int attribute((unused)) nargs,
407                    const struct mx_node **args,
408                    struct sink *output,
409                    void *u) {
410   int rc, i;
411   
412   dcgi_lookup(DCGI_NEW);
413   /* TODO perhaps we should generate an ID value for tracks in the new list */
414   for(i = 0; i < dcgi_nnew; ++i)
415     if((rc = mx_expand(mx_rewritel(args[0],
416                                    "track", dcgi_new[i],
417                                    "index", make_index(i),
418                                    "parity", i % 2 ? "odd" : "even",
419                                    "first", i == 0 ? "true" : "false",
420                                    "last", i == dcgi_nnew - 1 ? "false" : "true",
421                                    (char *)0),
422                        output, u)))
423       return rc;
424   return 0;
425 }
426
427 /* @volume{CHANNEL}
428  *
429  * Expands to the volume in a given channel.  CHANNEL must be "left" or
430  * "right".
431  */
432 static int exp_volume(int attribute((unused)) nargs,
433                       char **args,
434                       struct sink *output,
435                       void attribute((unused)) *u) {
436   dcgi_lookup(DCGI_VOLUME);
437   return sink_printf(output, "%d",
438                      !strcmp(args[0], "left")
439                          ? dcgi_volume_left : dcgi_volume_right) < 0 ? -1 : 0;
440 }
441
442 /* @isplaying
443  *
444  * Expands to "true" if there is a playing track, otherwise "false".
445  */
446 static int exp_isplaying(int attribute((unused)) nargs,
447                          char attribute((unused)) **args,
448                          struct sink *output,
449                          void attribute((unused)) *u) {
450   dcgi_lookup(DCGI_PLAYING);
451   return mx_bool_result(output, !!dcgi_playing);
452 }
453
454 /* @isqueue
455  *
456  * Expands to "true" if there the queue is nonempty, otherwise "false".
457  */
458 static int exp_isqueue(int attribute((unused)) nargs,
459                        char attribute((unused)) **args,
460                        struct sink *output,
461                        void attribute((unused)) *u) {
462   dcgi_lookup(DCGI_QUEUE);
463   return mx_bool_result(output, !!dcgi_queue);
464 }
465
466 /* @isrecent@
467  *
468  * Expands to "true" if there the recently played list is nonempty, otherwise
469  * "false".
470  */
471 static int exp_isrecent(int attribute((unused)) nargs,
472                         char attribute((unused)) **args,
473                         struct sink *output,
474                         void attribute((unused)) *u) {
475   dcgi_lookup(DCGI_RECENT);
476   return mx_bool_result(output, !!dcgi_recent);
477 }
478
479 /* @isnew
480  *
481  * Expands to "true" if there the newly added track list is nonempty, otherwise
482  * "false".
483  */
484 static int exp_isnew(int attribute((unused)) nargs,
485                      char attribute((unused)) **args,
486                      struct sink *output,
487                      void attribute((unused)) *u) {
488   dcgi_lookup(DCGI_NEW);
489   return mx_bool_result(output, !!dcgi_nnew);
490 }
491
492 /* @pref{TRACK}{KEY}
493  *
494  * Expands to a track preference.
495  */
496 static int exp_pref(int attribute((unused)) nargs,
497                     char **args,
498                     struct sink *output,
499                     void attribute((unused)) *u) {
500   char *value;
501
502   if(dcgi_client && !disorder_get(dcgi_client, args[0], args[1], &value))
503     return sink_writes(output, cgi_sgmlquote(value)) < 0 ? -1 : 0;
504   return 0;
505 }
506
507 /* @prefs{TRACK}{TEMPLATE}
508  *
509  * For each track preference of track TRACK, expands TEMPLATE with the
510  * following expansions:
511  * - @name@ to the UNQUOTED preference name
512  * - @index@ to the preference number from 0
513  * - @value@ to the UNQUOTED preference value
514  * - @parity@ to "even" or "odd" alternately
515  * - @first@ to "true" on the first preference and "false" otherwise
516  * - @last@ to "true" on the last preference and "false" otherwise
517  *
518  * Use @quote@ to quote preference names and values where necessary; see below.
519  */
520 static int exp_prefs(int attribute((unused)) nargs,
521                      const struct mx_node **args,
522                      struct sink *output,
523                      void *u) {
524   int rc, i;
525   struct kvp *k, *head;
526   char *track;
527
528   if((rc = mx_expandstr(args[0], &track, u, "argument #0 (TRACK)")))
529     return rc;
530   if(!dcgi_client || disorder_prefs(dcgi_client, track, &head))
531     return 0;
532   for(k = head, i = 0; k; k = k->next, ++i)
533     if((rc = mx_expand(mx_rewritel(args[1],
534                                    "index", make_index(i),
535                                    "parity", i % 2 ? "odd" : "even",
536                                    "name", k->name,
537                                    "value", k->value,
538                                    "first", k == head ? "true" : "false",
539                                    "last", k->next ? "false" : "true",
540                                    (char *)0),
541                        output, u)))
542       return rc;
543   return 0;
544 }
545
546 /* @transform{TRACK}{TYPE}{CONTEXT}
547  *
548  * Transforms a track name (if TYPE is "track") or directory name (if type is
549  * "dir").  CONTEXT should be the context, if it is left out then "display" is
550  * assumed.
551  */
552 static int exp_transform(int nargs,
553                          char **args,
554                          struct sink *output,
555                          void attribute((unused)) *u) {
556   const char *t = trackname_transform(args[1], args[0],
557                                       (nargs > 2 ? args[2] : "display"));
558   return sink_writes(output, cgi_sgmlquote(t)) < 0 ? -1 : 0;
559 }
560
561 /* @enabled@
562  *
563  * Expands to "true" if playing is enabled, otherwise "false".
564  */
565 static int exp_enabled(int attribute((unused)) nargs,
566                        char attribute((unused)) **args,
567                        struct sink *output,
568                        void attribute((unused)) *u) {
569   int e = 0;
570
571   if(dcgi_client)
572     disorder_enabled(dcgi_client, &e);
573   return mx_bool_result(output, e);
574 }
575
576 /* @random-enabled
577  *
578  * Expands to "true" if random play is enabled, otherwise "false".
579  */
580 static int exp_random_enabled(int attribute((unused)) nargs,
581                               char attribute((unused)) **args,
582                               struct sink *output,
583                               void attribute((unused)) *u) {
584   int e = 0;
585
586   if(dcgi_client)
587     disorder_random_enabled(dcgi_client, &e);
588   return mx_bool_result(output, e);
589 }
590
591 /* @trackstate{TRACK}
592  *
593  * Expands to "playing" if TRACK is currently playing, or "queue" if it is in
594  * the queue, otherwise to nothing.
595  */
596 static int exp_trackstate(int attribute((unused)) nargs,
597                           char **args,
598                           struct sink *output,
599                           void attribute((unused)) *u) {
600   char *track;
601   struct queue_entry *q;
602
603   if(!dcgi_client)
604     return 0;
605   if(disorder_resolve(dcgi_client, &track, args[0]))
606     return 0;
607   dcgi_lookup(DCGI_PLAYING);
608   if(dcgi_playing && !strcmp(track, dcgi_playing->track))
609     return sink_writes(output, "playing") < 0 ? -1 : 0;
610   dcgi_lookup(DCGI_QUEUE);
611   for(q = dcgi_queue; q; q = q->next)
612     if(!strcmp(track, q->track))
613       return sink_writes(output, "queued") < 0 ? -1 : 0;
614   return 0;
615 }
616
617 /* @thisurl
618  *
619  * Expands to an UNQUOTED URL which points back to the current page.  (NB it
620  * might not be byte for byte identical - for instance, CGI arguments might be
621  * re-ordered.)
622  */
623 static int exp_thisurl(int attribute((unused)) nargs,
624                        char attribute((unused)) **args,
625                        struct sink *output,
626                        void attribute((unused)) *u) {
627   return sink_writes(output, cgi_thisurl(config->url)) < 0 ? -1 : 0;
628 }
629
630 /* @resolve{TRACK}
631  *
632  * Expands to an UNQUOTED name for the TRACK that is not an alias, or to
633  * nothing if it is not a valid track.
634  */
635 static int exp_resolve(int attribute((unused)) nargs,
636                        char **args,
637                        struct sink *output,
638                        void attribute((unused)) *u) {
639   char *r;
640
641   if(dcgi_client && !disorder_resolve(dcgi_client, &r, args[0]))
642     return sink_writes(output, r) < 0 ? -1 : 0;
643   return 0;
644 }
645
646 /* @paused
647  *
648  * Expands to "true" if the playing track is paused, to "false" if it is
649  * playing (or if there is no playing track at all).
650  */
651 static int exp_paused(int attribute((unused)) nargs,
652                       char attribute((unused)) **args,
653                       struct sink *output,
654                       void attribute((unused)) *u) {
655   dcgi_lookup(DCGI_PLAYING);
656   return mx_bool_result(output, (dcgi_playing
657                                  && dcgi_playing->state == playing_paused));
658 }
659
660 /* @state{ID}@
661  *
662  * Expands to the current state of track ID.
663  */
664 static int exp_state(int attribute((unused)) nargs,
665                      char **args,
666                      struct sink *output,
667                      void attribute((unused)) *u) {
668   struct queue_entry *q = dcgi_findtrack(args[0]);
669
670   if(q)
671     return sink_writes(output, playing_states[q->state]) < 0 ? -1 : 0;
672   return 0;
673 }
674
675 /* @right{RIGHT}{WITH-RIGHT}{WITHOUT-RIGHT}@
676  *
677  * Expands to WITH-RIGHT if the current user has right RIGHT, otherwise to
678  * WITHOUT-RIGHT.  The WITHOUT-RIGHT argument may be left out.
679  *
680  * If both WITH-RIGHT and WITHOUT-RIGHT are left out then expands to "true" if
681  * the user has the right and "false" otherwise.
682  *
683  * If there is no connection to the server then expands to nothing (in all
684  * cases).
685  */
686 static int exp_right(int nargs,
687                      const struct mx_node **args,
688                      struct sink *output,
689                      void *u) {
690   char *right;
691   rights_type r;
692   int rc;
693
694   if(!dcgi_client)
695     return 0;
696   dcgi_lookup(DCGI_RIGHTS);
697   if((rc = mx_expandstr(args[0], &right, u, "argument #0 (RIGHT)")))
698     return rc;
699   if(parse_rights(right, &r, 1/*report*/))
700     return 0;
701   /* Single-argument form */
702   if(nargs == 1)
703     return mx_bool_result(output, !!(r & dcgi_rights));
704   /* Multiple argument form */
705   if(r & dcgi_rights)
706     return mx_expand(args[1], output, u);
707   if(nargs == 3)
708     return mx_expand(args[2], output, u);
709   return 0;
710 }
711
712 /* @userinfo{PROPERTY}
713  *
714  * Expands to the named property of the current user.
715  */
716 static int exp_userinfo(int attribute((unused)) nargs,
717                         char **args,
718                         struct sink *output,
719                         void attribute((unused)) *u) {
720   char *v;
721
722   if(dcgi_client
723      && !disorder_userinfo(dcgi_client, disorder_user(dcgi_client),
724                            args[0], &v))
725     return sink_writes(output, v) < 0 ? -1 : 0;
726   return 0;
727 }
728
729 /* @error
730  *
731  * Expands to the latest error string.
732  */
733 static int exp_error(int attribute((unused)) nargs,
734                      char attribute((unused)) **args,
735                      struct sink *output,
736                      void attribute((unused)) *u) {
737   return sink_writes(output, cgi_sgmlquote(dcgi_error_string)) < 0 ? -1 : 0;
738 }
739
740 /** @brief Register DisOrder-specific expansions */
741 void dcgi_expansions(void) {
742   mx_register("arg", 1, 1, exp_arg);
743   mx_register("enabled", 0, 0, exp_enabled);
744   mx_register("error", 0, 0, exp_error);
745   mx_register("isnew", 0, 0, exp_isnew);
746   mx_register("isplaying", 0, 0, exp_isplaying);
747   mx_register("isplaying", 0, 0, exp_isqueue);
748   mx_register("isrecent", 0, 0, exp_isrecent);
749   mx_register("length", 1, 1, exp_length);
750   mx_register("movable", 1, 1, exp_movable);
751   mx_register("part", 2, 3, exp_part);
752   mx_register("paused", 0, 0, exp_paused);
753   mx_register("pref", 2, 2, exp_pref);
754   mx_register("quote", 1, 1, exp_quote);
755   mx_register("random-enabled", 0, 0, exp_random_enabled);
756   mx_register("removable", 1, 1, exp_removable);
757   mx_register("resolve", 1, 1, exp_resolve);
758   mx_register("server-version", 0, 0, exp_server_version);
759   mx_register("state", 1, 1, exp_state);
760   mx_register("thisurl", 0, 0, exp_thisurl);
761   mx_register("trackstate", 1, 1, exp_trackstate);
762   mx_register("transform", 2, 3, exp_transform);
763   mx_register("url", 0, 0, exp_url);
764   mx_register("user", 0, 0, exp_user);
765   mx_register("userinfo", 1, 1, exp_userinfo);
766   mx_register("version", 0, 0, exp_version);
767   mx_register("volume", 1, 1, exp_volume);
768   mx_register("when", 1, 1, exp_when);
769   mx_register("who", 1, 1, exp_who);
770   mx_register_magic("new", 1, 1, exp_new);
771   mx_register_magic("playing", 0, 1, exp_playing);
772   mx_register_magic("prefs", 2, 2, exp_prefs);
773   mx_register_magic("queue", 1, 1, exp_queue);
774   mx_register_magic("recent", 1, 1, exp_recent);
775   mx_register_magic("right", 1, 3, exp_right);
776 }
777
778 /*
779 Local Variables:
780 c-basic-offset:2
781 comment-column:40
782 fill-column:79
783 indent-tabs-mode:nil
784 End:
785 */