return s;
}
-/* @server-version
+/*! @server-version
*
* Expands to the server's version string, or a (safe to use) error
* value if the server is unavailable or broken.
return sink_writes(output, cgi_sgmlquote(v)) < 0 ? -1 : 0;
}
-/* @version
+/*! @version
*
* Expands to the local version string.
*/
cgi_sgmlquote(disorder_short_version_string)) < 0 ? -1 : 0;
}
-/* @url
+/*! @url
*
* Expands to the base URL of the web interface.
*/
cgi_sgmlquote(config->url)) < 0 ? -1 : 0;
}
-/* @arg{NAME}
+/*! @arg{NAME}
*
* Expands to the UNQUOTED form of CGI argument NAME, or the empty string if
* there is no such argument. Use @argq for a quick way to quote the argument.
return 0;
}
-/* @argq{NAME}
+/*! @argq{NAME}
*
* Expands to the (quoted) form of CGI argument NAME, or the empty string if
* there is no such argument. Use @arg for the unquoted argument.
return 0;
}
-/* @user
+/*! @user
*
* Expands to the logged-in username (which might be "guest"), or to
* the empty string if not connected.
return 0;
}
-/* @part{TRACK|ID}{PART}{CONTEXT}
+/*! @part{TRACK|ID}{PART}{CONTEXT}
*
* Expands to a track name part.
*
void attribute((unused)) *u) {
const char *track = args[0], *part = args[1];
const char *context = nargs > 2 ? args[2] : "display";
- char *s;
+ const char *s;
if(track[0] != '/') {
struct queue_entry *q = dcgi_findtrack(track);
return 0;
}
if(dcgi_client
- && !disorder_part(dcgi_client, &s,
+ && !disorder_part(dcgi_client, (char **)&s,
track,
!strcmp(context, "short") ? "display" : context,
- part))
+ part)) {
+ if(!strcmp(context, "short"))
+ s = truncate_for_display(s, config->short_display);
return sink_writes(output, cgi_sgmlquote(s)) < 0 ? -1 : 0;
+ }
return 0;
}
-/* @quote{STRING}
+/*! @quote{STRING}
*
* SGML-quotes STRING. Note that most expansion results are already suitable
* quoted, so this expansion is usually not required.
return sink_writes(output, cgi_sgmlquote(args[0])) < 0 ? -1 : 0;
}
-/* @who{ID}
+/*! @who{ID}
*
* Expands to the name of the submitter of track ID, which must be a playing
* track, in the queue, or in the recent list.
return 0;
}
-/* @when{ID}
+/*! @when{ID}
*
* Expands to the time a track started or is expected to start. The track must
* be a playing track, in the queue, or in the recent list.
return sink_writes(output, " ") < 0 ? -1 : 0;
}
-/* @length{ID|TRACK}
+/*! @length{ID|TRACK}
*
* Expands to the length of a track, identified by its queue ID or its name.
* If it is the playing track (identified by ID) then the amount played so far
return sink_writes(output, " ") < 0 ? -1 : 0;
}
-/* @removable{ID}
+/*! @removable{ID}
*
* Expands to "true" if track ID is removable (or scratchable, if it is the
* playing track) and "false" otherwise.
(dcgi_rights, disorder_user(dcgi_client), q));
}
-/* @movable{ID}{DIR}
+/*! @movable{ID}{DIR}
*
* Expands to "true" if track ID is movable and "false" otherwise.
*
q));
}
-/* @playing{TEMPLATE}
+/*! @playing{TEMPLATE}
*
* Expands to TEMPLATE, with the following expansions:
* - @id: the queue ID of the playing track
output, u);
}
-/* @queue{TEMPLATE}
+/*! @queue{TEMPLATE}
*
* For each track in the queue, expands TEMPLATE with the following expansions:
* - @id: the queue ID of the track
return 0;
}
-/* @recent{TEMPLATE}
+/*! @recent{TEMPLATE}
*
* For each track in the recently played list, expands TEMPLATE with the
* following expansions:
return 0;
}
-/* @new{TEMPLATE}
+/*! @new{TEMPLATE}
*
* For each track in the newly added list, expands TEMPLATE wit the following
* expansions:
return 0;
}
-/* @volume{CHANNEL}
+/*! @volume{CHANNEL}
*
* Expands to the volume in a given channel. CHANNEL must be "left" or
* "right".
? dcgi_volume_left : dcgi_volume_right) < 0 ? -1 : 0;
}
-/* @isplaying
+/*! @isplaying
*
* Expands to "true" if there is a playing track, otherwise "false".
*/
return mx_bool_result(output, !!dcgi_playing);
}
-/* @isqueue
+/*! @isqueue
*
* Expands to "true" if there the queue is nonempty, otherwise "false".
*/
return mx_bool_result(output, !!dcgi_queue);
}
-/* @isrecent@
+/*! @isrecent@
*
* Expands to "true" if there the recently played list is nonempty, otherwise
* "false".
return mx_bool_result(output, !!dcgi_recent);
}
-/* @isnew
+/*! @isnew
*
* Expands to "true" if there the newly added track list is nonempty, otherwise
* "false".
return mx_bool_result(output, !!dcgi_nnew);
}
-/* @pref{TRACK}{KEY}
+/*! @pref{TRACK}{KEY}
*
* Expands to a track preference.
*/
return 0;
}
-/* @prefs{TRACK}{TEMPLATE}
+/*! @prefs{TRACK}{TEMPLATE}
*
* For each track preference of track TRACK, expands TEMPLATE with the
* following expansions:
return 0;
}
-/* @transform{TRACK}{TYPE}{CONTEXT}
+/*! @transform{TRACK}{TYPE}{CONTEXT}
*
* Transforms a track name (if TYPE is "track") or directory name (if type is
* "dir"). CONTEXT should be the context, if it is left out then "display" is
return sink_writes(output, cgi_sgmlquote(t)) < 0 ? -1 : 0;
}
-/* @enabled@
+/*! @enabled@
*
* Expands to "true" if playing is enabled, otherwise "false".
*/
return mx_bool_result(output, e);
}
-/* @random-enabled
+/*! @random-enabled
*
* Expands to "true" if random play is enabled, otherwise "false".
*/
return mx_bool_result(output, e);
}
-/* @trackstate{TRACK}
+/*! @trackstate{TRACK}
*
* Expands to "playing" if TRACK is currently playing, or "queue" if it is in
* the queue, otherwise to nothing.
return 0;
}
-/* @thisurl
+/*! @thisurl
*
* Expands to an UNQUOTED URL which points back to the current page. (NB it
* might not be byte for byte identical - for instance, CGI arguments might be
return sink_writes(output, cgi_thisurl(config->url)) < 0 ? -1 : 0;
}
-/* @resolve{TRACK}
+/*! @resolve{TRACK}
*
* Expands to an UNQUOTED name for the TRACK that is not an alias, or to
* nothing if it is not a valid track.
return 0;
}
-/* @paused
+/*! @paused
*
* Expands to "true" if the playing track is paused, to "false" if it is
* playing (or if there is no playing track at all).
&& dcgi_playing->state == playing_paused));
}
-/* @state{ID}@
+/*! @state{ID}@
*
* Expands to the current state of track ID.
*/
return 0;
}
-/* @right{RIGHT}{WITH-RIGHT}{WITHOUT-RIGHT}@
+/*! @right{RIGHT}{WITH-RIGHT}{WITHOUT-RIGHT}@
*
* Expands to WITH-RIGHT if the current user has right RIGHT, otherwise to
* WITHOUT-RIGHT. The WITHOUT-RIGHT argument may be left out.
return 0;
}
-/* @userinfo{PROPERTY}
+/*! @userinfo{PROPERTY}
*
* Expands to the named property of the current user.
*/
return 0;
}
-/* @error
+/*! @error
*
* Expands to the latest error string.
*/
< 0 ? -1 : 0;
}
-/* @status
+/*! @status
*
* Expands to the latest status string.
*/
< 0 ? -1 : 0;
}
-/* @image{NAME}
+/*! @image{NAME}
*
* Expands to the URL of the image called NAME.
*
}
-/** @tracks{DIR}{RE}{TEMPLATE}
+/*! @tracks{DIR}{RE}{TEMPLATE}
*
* For each track below DIR, expands TEMPLATE with the
* following expansions:
return exp__files_dirs(nargs, args, output, u, "track", disorder_files);
}
-/** @dirs{DIR}{RE}{TEMPLATE}
+/*! @dirs{DIR}{RE}{TEMPLATE}
*
* For each directory below DIR, expands TEMPLATE with the
* following expansions:
return disorder_search(c, terms, vecp, nvecp);
}
-/** @search{KEYWORDS}{TEMPLATE}
+/*! @search{KEYWORDS}{TEMPLATE}
*
* For each track matching KEYWORDS, expands TEMPLATE with the
* following expansions:
return exp__files_dirs(nargs, args, output, u, "track", exp__search_shim);
}
+/*! @label{NAME}
+ *
+ * Expands to label NAME from options.labels. Undefined lables expand to the
+ * last dot-separated component, e.g. X.Y.Z to Z.
+ */
static int exp_label(int attribute((unused)) nargs,
char **args,
struct sink *output,
return sink_writes(output, option_label(args[0])) < 0 ? -1 : 0;
}
+/*! @breadcrumbs{DIR}{TEMPLATE}
+ *
+ * Expands TEMPLATE for each directory in the path up to DIR, excluding the root
+ * but including DIR itself, with the following expansions:
+ * - @dir: the directory
+ * - @last: "true" if this is the last directory, otherwise "false"
+ *
+ * DIR must be an absolute path.
+ */
+static int exp_breadcrumbs(int attribute((unused)) nargs,
+ const struct mx_node **args,
+ struct sink *output,
+ void attribute((unused)) *u) {
+ int rc;
+ char *dir, *parent, *ptr;
+
+ if((rc = mx_expandstr(args[0], &dir, u, "argument #0 (DIR)")))
+ return rc;
+ /* Reject relative paths */
+ if(dir[0] != '/') {
+ error(0, "breadcrumbs: '%s' is a relative path", dir);
+ return 0;
+ }
+ /* Skip the root */
+ ptr = dir + 1;
+ while(*ptr) {
+ /* Find the end of this directory */
+ while(*ptr && *ptr != '/')
+ ++ptr;
+ parent = xstrndup(dir, ptr - dir);
+ if((rc = mx_expand(mx_rewritel(args[1],
+ "dir", parent,
+ "last", *ptr ? "false" : "true",
+ (char *)0),
+ output, u)))
+ return rc;
+ if(*ptr)
+ ++ptr;
+ }
+ return 0;
+}
+
/** @brief Register DisOrder-specific expansions */
void dcgi_expansions(void) {
mx_register("arg", 1, 1, exp_arg);
mx_register("volume", 1, 1, exp_volume);
mx_register("when", 1, 1, exp_when);
mx_register("who", 1, 1, exp_who);
+ mx_register_magic("breadcrumbs", 2, 2, exp_breadcrumbs);
mx_register_magic("dirs", 2, 3, exp_dirs);
mx_register_magic("new", 1, 1, exp_new);
mx_register_magic("playing", 0, 1, exp_playing);