push
This commit is contained in:
156
Doc/cmus-remote.txt
Normal file
156
Doc/cmus-remote.txt
Normal file
@@ -0,0 +1,156 @@
|
||||
@title CMUS-REMOTE 1 05/11/2006 cmus
|
||||
|
||||
@h1 NAME
|
||||
|
||||
cmus-remote - control cmus
|
||||
|
||||
|
||||
@h1 SYNOPSIS
|
||||
|
||||
cmus-remote [*OPTION*]... [`FILE`|`DIR`|`PLAYLIST`]...@br
|
||||
cmus-remote *-C* `COMMAND`...@br
|
||||
cmus-remote
|
||||
|
||||
|
||||
@h1 DESCRIPTION
|
||||
|
||||
Add `FILE/DIR/PLAYLIST` to playlist, library (*-l*) or play queue (*-q*).
|
||||
|
||||
If no arguments are given cmus-remote reads raw commands from stdin (one
|
||||
command per line). Raw commands are cmus' command mode commands. These same
|
||||
commands are used in configuration files and key bindings. *cmus*(1) contains
|
||||
full list of commands. For consistency also searching is supported:
|
||||
*-C /text*.
|
||||
|
||||
If *-C* is given, all command line arguments are treated as raw commands.
|
||||
|
||||
@h1 OPTIONS
|
||||
|
||||
--server SOCKET
|
||||
Connect using socket *SOCKET* instead of `$XDG_RUNTIME_DIR/cmus-socket`.
|
||||
|
||||
--passwd PASSWD
|
||||
password to use for TCP/IP connection
|
||||
|
||||
--help
|
||||
Display usage information and exit.
|
||||
|
||||
--version
|
||||
Display version information and exit.
|
||||
|
||||
-p, --play
|
||||
Start playing.
|
||||
|
||||
-u, --pause
|
||||
Toggle pause.
|
||||
|
||||
-U, --pause-playback
|
||||
Pause if currently playing.
|
||||
|
||||
-s, --stop
|
||||
Stop playing.
|
||||
|
||||
-n, --next
|
||||
Skip forward in playlist.
|
||||
|
||||
-r, --prev
|
||||
Skip backward in playlist.
|
||||
|
||||
-R, --repeat
|
||||
Toggle repeat.
|
||||
|
||||
-S, --shuffle
|
||||
Toggle shuffle.
|
||||
|
||||
-v, --volume VOL
|
||||
Change volume. See *vol* command in *cmus*(1).
|
||||
|
||||
-k, --seek SEEK
|
||||
Seek. See *seek* command in *cmus*(1).
|
||||
|
||||
-f, --file FILE
|
||||
Play from file.
|
||||
|
||||
-Q
|
||||
Get player status information. Same as *-C status*. Note that
|
||||
*status* is a special command only available to cmus-remote.
|
||||
|
||||
-l, --library
|
||||
Modify library instead of playlist.
|
||||
|
||||
-P, --playlist
|
||||
Modify playlist (default).
|
||||
|
||||
-q, --queue
|
||||
Modify play queue instead of playlist.
|
||||
|
||||
-c, --clear
|
||||
Clear playlist, library (*-l*) or play queue (*-q*).
|
||||
|
||||
-C, --raw
|
||||
Treat arguments (instead of stdin) as raw commands.
|
||||
|
||||
@h1 REMOTE COMMANDS
|
||||
|
||||
Special commands only available to cmus-remote.
|
||||
|
||||
status
|
||||
Print information about currently playing track.
|
||||
|
||||
format_print
|
||||
Print arguments as `Format Strings`. Each argument starts a new line.
|
||||
|
||||
@h1 EXAMPLES
|
||||
|
||||
Add playlists/files/directories/URLs to library view (1 & 2):
|
||||
|
||||
@pre
|
||||
$ cmus-remote -l music.m3u \\
|
||||
http://live.urn1350.net:8080/urn_high.ogg
|
||||
@endpre
|
||||
|
||||
Load (clear and add) playlist to playlist view (3):
|
||||
|
||||
@pre
|
||||
$ cmus-remote -c music.m3u
|
||||
@endpre
|
||||
|
||||
Three different ways to toggle repeat:
|
||||
|
||||
@pre
|
||||
$ cmus-remote -R
|
||||
$ cmus-remote -C "toggle repeat"
|
||||
$ cmus-remote
|
||||
toggle repeat
|
||||
^D
|
||||
@endpre
|
||||
|
||||
Query settings or key bindings:
|
||||
|
||||
@pre
|
||||
$ cmus-remote -C "set repeat?"
|
||||
setting: 'repeat=false'
|
||||
$ cmus-remote -C "showbind common a"
|
||||
bind common a win-add-l
|
||||
@endpre
|
||||
|
||||
Dump the playlist to stdout:
|
||||
|
||||
@pre
|
||||
$ cmus-remote -C "save -p -"
|
||||
[...]
|
||||
@endpre
|
||||
|
||||
Search works too:
|
||||
|
||||
@pre
|
||||
$ cmus-remote -C /beatles
|
||||
@endpre
|
||||
|
||||
@h1 SEE ALSO
|
||||
|
||||
*cmus*(1)
|
||||
|
||||
@h1 AUTHOR
|
||||
|
||||
Written by Timo Hirvonen <tihirvon\@gmail.com>
|
||||
264
Doc/cmus-tutorial.txt
Normal file
264
Doc/cmus-tutorial.txt
Normal file
@@ -0,0 +1,264 @@
|
||||
@title cmus-tutorial 7 14/02/2010 cmus
|
||||
|
||||
|
||||
@h1 NAME
|
||||
|
||||
cmus - C\* Music Player tutorial
|
||||
|
||||
|
||||
@h1 CONTENTS
|
||||
|
||||
Step 1: Starting Cmus
|
||||
|
||||
Step 2: Adding Music
|
||||
|
||||
Step 3: Playing Tracks From The Library
|
||||
|
||||
Step 4: Managing The Queue
|
||||
|
||||
Step 5: The Playlists
|
||||
|
||||
Step 6: Find that track
|
||||
|
||||
Step 7: Customization
|
||||
|
||||
Step 8: Quit
|
||||
|
||||
Step 9: Further Reading
|
||||
|
||||
|
||||
@h1 Step 1: Starting Cmus
|
||||
|
||||
When you first launch cmus (just type `cmus` in a terminal and press Enter) it
|
||||
will open to the album/artist view, which looks something like this:
|
||||
|
||||
@pre
|
||||
+---------------------------------------------------------------------+
|
||||
| Library Empty (use :add) |
|
||||
| | |
|
||||
| | |
|
||||
| | |
|
||||
| | |
|
||||
| | |
|
||||
| | |
|
||||
| | |
|
||||
| |
|
||||
| . 00:00 library | 100% | C |
|
||||
| |
|
||||
+---------------------------------------------------------------------+
|
||||
@endpre
|
||||
|
||||
This is the view where your artists and albums will be displayed.
|
||||
|
||||
|
||||
@h1 Step 2: Adding Music
|
||||
|
||||
Press *5* to switch to the file-browser view so we can add some music. You
|
||||
should see something like this:
|
||||
|
||||
@pre
|
||||
+---------------------------------------------------------------------+
|
||||
| Browser - /home/jasonwoof |
|
||||
| ../ |
|
||||
| Desktop/ |
|
||||
| MySqueak/ |
|
||||
| audio-projects/ |
|
||||
| audio/ |
|
||||
| bin/ |
|
||||
| config/ |
|
||||
| |
|
||||
| . 00:00 library | 100% | C |
|
||||
| |
|
||||
+---------------------------------------------------------------------+
|
||||
@endpre
|
||||
|
||||
Now, use the arrow keys, Enter and Backspace to navigate to where you have
|
||||
audio files stored. To add music to your cmus library, use the arrow keys to
|
||||
highlight a file or folder, and press *a*. When you press *a* cmus will move you
|
||||
to the next line down (so that it is easy to add a bunch of files/folders in a
|
||||
row) and start adding the file/folder you pressed *a* on to your library. This
|
||||
can take a while if you added a folder with a lot in it. As files are added,
|
||||
you will see the second time in the bottom right go up. This is the total
|
||||
duration of all the music in the cmus library.
|
||||
|
||||
Note: cmus does not move, duplicate or change your files. It just remembers
|
||||
where they are and caches the metadata (duration, artist, etc.)
|
||||
|
||||
Just to be on the safe side, let's save. Type *:save* and press Enter.
|
||||
|
||||
Note: Cmus automatically saves your settings and library and everything when
|
||||
you quit, so you probably won't use the save command much.
|
||||
|
||||
|
||||
@h1 Step 3: Playing Tracks From The Library
|
||||
|
||||
Press *2* to go to the simple library view. You should see something like
|
||||
this:
|
||||
|
||||
@pre
|
||||
+---------------------------------------------------------------------+
|
||||
| Library - 31 tracks (1:35:11) sorted by albumartist date album dis… |
|
||||
| Flying Lizards . Money (That's What I Want) 02:31 |
|
||||
| Jason Woofenden . VoR Theme 2009 01:20 |
|
||||
| Keali'i Reichel 06. Wanting Memories 1994 04:28 |
|
||||
| Molly Lewis . Tom Cruise Crazy 03:13 |
|
||||
| NonMemory . pista1 2009 03:18 |
|
||||
| NonMemory 01. pista1 2009-04-21 04:13 |
|
||||
| Ray Charles 06. Halleluja I Love Her So 02:33 |
|
||||
| |
|
||||
| . 00:00 artist from library | 100% | C |
|
||||
| |
|
||||
+---------------------------------------------------------------------+
|
||||
@endpre
|
||||
|
||||
Use the up and down arrow keys to select a track you'd like to hear, and press
|
||||
Enter to play it. Here are some keys to control playback:
|
||||
|
||||
Press *c* to pause/unpause.
|
||||
Press right/left to seek by 10 seconds.
|
||||
Press *,*/*.* seek backwards/forwards one minute.
|
||||
Press *z* to play the previous track and *b* to play the next track.
|
||||
|
||||
cmus has some great options to control what plays next (if anything) when the
|
||||
track ends. The state of these settings is shown in the bottom right corner.
|
||||
The first of these shows what collection of tracks we are playing (shown here
|
||||
as "artist from library"). Press *m* to cycle through the different options for
|
||||
this setting. To the right of that (past the volume) cmus shows the state of four
|
||||
toggles. Only toggles which are "on" are shown, so now we only see the *C*.
|
||||
Here are the toggles:
|
||||
|
||||
[C]ontinue
|
||||
|
||||
If this is off, cmus will always stop at the end of the track. You can
|
||||
toggle this setting by pressing *shift-C*.
|
||||
|
||||
[F]ollow
|
||||
|
||||
If this is on, cmus will select the currently playing track on track change.
|
||||
Press *f* to toggle this option.
|
||||
|
||||
[R]epeat
|
||||
|
||||
If this is on (and continue is on), when cmus reaches the end of the group
|
||||
of tracks you're playing (selected with the *m* key) it will start again from
|
||||
the beginning. Press *r* to toggle this setting.
|
||||
|
||||
[S]huffle or [&]lbum shuffle
|
||||
|
||||
If this is 'S', cmus will choose a random order to play all tracks once,
|
||||
while '&' will do the same for whole albums. Press *s* to toggle this option.
|
||||
|
||||
|
||||
@h1 Step 4: Managing The Queue
|
||||
|
||||
Lets say you're listening to a song, and you want to select which song will
|
||||
play next, without interrupting the currently playing song. No problem! Just go
|
||||
to the song you want to hear next (in any of the views) and press *e*. The
|
||||
queue is FIFO, meaning if you queue up another track, it will play after the
|
||||
one you already had queued up.
|
||||
|
||||
Note: The queue is not affected by the "shuffle" option described above.
|
||||
|
||||
Press *4* to view/edit the queue. This view works and looks a lot like the
|
||||
simple library view. The main difference is that you can change the order of
|
||||
the tracks with the *p* and *P* keys. You can press *shift-D* to remove a track
|
||||
from the queue.
|
||||
|
||||
When cmus is ready to play another track (it's reached the end of a track and
|
||||
the "continue" setting is on) it will remove the top entry from the queue and
|
||||
start playing it.
|
||||
|
||||
|
||||
@h1 Step 5: The Playlists
|
||||
|
||||
The playlists work like another set of libraries (like view *2*) except that
|
||||
(like the queue) you manually set the order of the tracks. This can be quite
|
||||
useful if you want to create a mix of specific tracks or if you want to
|
||||
listen to an audio book without having the chapters play when you're playing
|
||||
from the library.
|
||||
|
||||
The playlists are on view *3*. But before we go there, let's add some tracks.
|
||||
Press *2* to go to the simple library view, go to a track you want and press
|
||||
*y* to add it to a playlist. The only visual feedback you'll get that anything
|
||||
happened is that the highlight will move down one row. Add a few more so you
|
||||
have something to work with.
|
||||
|
||||
Now press *3* to go to the playlist. You should see something like this:
|
||||
|
||||
@pre
|
||||
+---------------------------------------------------------------------+
|
||||
| Playlist Default 11:32 |
|
||||
| * Default | Flying Lizards . Money (Th... 02:31 |
|
||||
| | Jason Woofenden . VoR T... 2009 01:20 |
|
||||
| | Keali'i Reichel 06. Wanti... 1994 04:28 |
|
||||
| | Molly Lewis . Tom Cruis... 03:13 |
|
||||
| | |
|
||||
| | |
|
||||
| | |
|
||||
| |
|
||||
| . 00:00 library | 100% | C |
|
||||
| |
|
||||
+---------------------------------------------------------------------+
|
||||
@endpre
|
||||
|
||||
Just like the queue, you can use the *p*, *P* and *D* keys to move and delete
|
||||
tracks from the playlist.
|
||||
|
||||
Note: Changing the view (e.g. by pressing *3*) does not affect what cmus will
|
||||
play next. To put cmus into "play from the playlist" mode, press Enter on one
|
||||
of the tracks in the playlist. To switch modes without interrupting the
|
||||
currently-playing song, you can press *shift-M*.
|
||||
|
||||
|
||||
@h1 Step 6: Find that track
|
||||
|
||||
This step shows various ways you can find track(s) you're looking for.
|
||||
|
||||
Search: Press *2* to be sure you're on the simple library view, then press */*
|
||||
to start a search. Type a word or two from the track you're looking for. cmus
|
||||
will search for tracks that have all those words in them. Press enter to get
|
||||
the keyboard out of the search command, and *n* to find the next match.
|
||||
|
||||
Tree View: Press *1* to select the tree view. Scroll to the artist, press
|
||||
*space* to show their albums, scroll to the album you want, then press tab so
|
||||
the keyboard controls the right column. Press tab again to get back to the left
|
||||
column.
|
||||
|
||||
Filters: See the reference manual (see Further Reading below) for a detailed
|
||||
description on how to quickly (and temporarily) hide most of your music.
|
||||
|
||||
|
||||
@h1 Step 7: Customization
|
||||
|
||||
Cmus has some very cool settings you can tweak, like changing the way tracks
|
||||
are displayed (e.g. to display disk numbers), enabling replaygain support or
|
||||
changing the keybindings.
|
||||
|
||||
Press *7* for a quick overview of the current keybindings and settings.
|
||||
|
||||
To change a setting or keybind, just select it (up/down keys) and press enter.
|
||||
This will put the command for the current setting in the command line (bottom
|
||||
left of your screen), which you can edit to put in a new value/key.
|
||||
|
||||
Please see the reference manual (see Further Reading below) for a detailed
|
||||
description of all the commands and settings available.
|
||||
|
||||
|
||||
@h1 Step 8: Quit
|
||||
|
||||
When you're done, type *:q* and press Enter to quit. This will save your
|
||||
settings, library, playlist and queue.
|
||||
|
||||
|
||||
@h1 Step 9: Further Reading
|
||||
|
||||
Cmus comes with a great reference manual. Now that you've got the basics down
|
||||
it should be intelligible. Try *man cmus* in a terminal. If that's not
|
||||
installed, try opening up `cmus.txt` from the `Doc` directory, or read the latest
|
||||
version online:
|
||||
|
||||
`https://github.com/cmus/cmus/blob/master/Doc/cmus.txt`
|
||||
|
||||
There are more commands and features not covered here like loading and saving
|
||||
playlists, controlling cmus remotely with `cmus-remote`, etc.
|
||||
|
||||
1856
Doc/cmus.txt
Normal file
1856
Doc/cmus.txt
Normal file
File diff suppressed because it is too large
Load Diff
883
Doc/ttman.c
Normal file
883
Doc/ttman.c
Normal file
@@ -0,0 +1,883 @@
|
||||
/*
|
||||
* ttman - text to man converter
|
||||
*
|
||||
* Copyright 2006 Timo Hirvonen <tihirvon@gmail.com>
|
||||
*
|
||||
* This file is licensed under the GPLv2.
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
|
||||
struct token {
|
||||
struct token *next;
|
||||
struct token *prev;
|
||||
enum {
|
||||
TOK_TEXT, // max one line w/o \n
|
||||
TOK_NL, // \n
|
||||
TOK_ITALIC, // `
|
||||
TOK_BOLD, // *
|
||||
TOK_INDENT, // \t
|
||||
|
||||
// keywords (@...)
|
||||
TOK_H1,
|
||||
TOK_H2,
|
||||
TOK_LI,
|
||||
TOK_BR,
|
||||
TOK_PRE,
|
||||
TOK_ENDPRE, // must be after TOK_PRE
|
||||
TOK_RAW,
|
||||
TOK_ENDRAW, // must be after TOK_RAW
|
||||
TOK_TITLE, // WRITE 2 2001-12-13 "Linux 2.0.32" "Linux Programmer's Manual"
|
||||
} type;
|
||||
int line;
|
||||
|
||||
// not NUL-terminated
|
||||
const char *text;
|
||||
// length of text
|
||||
int len;
|
||||
};
|
||||
|
||||
static const char *program;
|
||||
static const char *filename;
|
||||
static char tmp_file[1024];
|
||||
static FILE *outfile;
|
||||
static int cur_line = 1;
|
||||
static struct token head = { &head, &head, TOK_TEXT, 0, NULL, 0 };
|
||||
|
||||
#define CONST_STR(str) { str, sizeof(str) - 1 }
|
||||
static const struct {
|
||||
const char *str;
|
||||
int len;
|
||||
} token_names[] = {
|
||||
CONST_STR("text"),
|
||||
CONST_STR("nl"),
|
||||
CONST_STR("italic"),
|
||||
CONST_STR("bold"),
|
||||
CONST_STR("indent"),
|
||||
|
||||
// keywords
|
||||
CONST_STR("h1"),
|
||||
CONST_STR("h2"),
|
||||
CONST_STR("li"),
|
||||
CONST_STR("br"),
|
||||
CONST_STR("pre"),
|
||||
CONST_STR("endpre"),
|
||||
CONST_STR("raw"),
|
||||
CONST_STR("endraw"),
|
||||
CONST_STR("title")
|
||||
};
|
||||
#define NR_TOKEN_NAMES (sizeof(token_names) / sizeof(token_names[0]))
|
||||
#define BUG() die("BUG in %s\n", __FUNCTION__)
|
||||
|
||||
#ifdef __GNUC__
|
||||
#define CMUS_NORETURN __attribute__((__noreturn__))
|
||||
#else
|
||||
#define CMUS_NORETURN
|
||||
#endif
|
||||
|
||||
static CMUS_NORETURN void quit(void)
|
||||
{
|
||||
if (tmp_file[0])
|
||||
unlink(tmp_file);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static CMUS_NORETURN void die(const char *format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
fprintf(stderr, "%s: ", program);
|
||||
va_start(ap, format);
|
||||
vfprintf(stderr, format, ap);
|
||||
va_end(ap);
|
||||
quit();
|
||||
}
|
||||
|
||||
static CMUS_NORETURN void syntax(int line, const char *format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
fprintf(stderr, "%s:%d: error: ", filename, line);
|
||||
va_start(ap, format);
|
||||
vfprintf(stderr, format, ap);
|
||||
va_end(ap);
|
||||
quit();
|
||||
}
|
||||
|
||||
static inline const char *keyword_name(int type)
|
||||
{
|
||||
if (type < TOK_H1 || type > TOK_TITLE)
|
||||
die("BUG: no keyword name for type %d\n", type);
|
||||
return token_names[type].str;
|
||||
}
|
||||
|
||||
static void *xmalloc(size_t size)
|
||||
{
|
||||
void *ret = malloc(size);
|
||||
|
||||
if (!ret)
|
||||
die("OOM when allocating %ul bytes\n", size);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static char *memdup(const char *str, int len)
|
||||
{
|
||||
char *s = xmalloc(len + 1);
|
||||
memcpy(s, str, len);
|
||||
s[len] = 0;
|
||||
return s;
|
||||
}
|
||||
|
||||
static struct token *new_token(int type)
|
||||
{
|
||||
struct token *tok = xmalloc(sizeof(struct token));
|
||||
|
||||
tok->prev = NULL;
|
||||
tok->next = NULL;
|
||||
tok->type = type;
|
||||
tok->line = cur_line;
|
||||
return tok;
|
||||
}
|
||||
|
||||
static void free_token(struct token *tok)
|
||||
{
|
||||
struct token *prev = tok->prev;
|
||||
struct token *next = tok->next;
|
||||
|
||||
if (tok == &head)
|
||||
BUG();
|
||||
|
||||
prev->next = next;
|
||||
next->prev = prev;
|
||||
free(tok);
|
||||
}
|
||||
|
||||
static void emit_token(struct token *tok)
|
||||
{
|
||||
tok->prev = head.prev;
|
||||
tok->next = &head;
|
||||
head.prev->next = tok;
|
||||
head.prev = tok;
|
||||
}
|
||||
|
||||
static void emit(int type)
|
||||
{
|
||||
struct token *tok = new_token(type);
|
||||
tok->len = 0;
|
||||
tok->text = NULL;
|
||||
emit_token(tok);
|
||||
}
|
||||
|
||||
static int emit_keyword(const char *buf, int size)
|
||||
{
|
||||
int i, len;
|
||||
|
||||
for (len = 0; len < size; len++) {
|
||||
if (!isalnum((unsigned char)buf[len]))
|
||||
break;
|
||||
}
|
||||
|
||||
if (!len)
|
||||
syntax(cur_line, "keyword expected\n");
|
||||
|
||||
for (i = TOK_H1; i < NR_TOKEN_NAMES; i++) {
|
||||
if (len != token_names[i].len)
|
||||
continue;
|
||||
if (!strncmp(buf, token_names[i].str, len)) {
|
||||
emit(i);
|
||||
return len;
|
||||
}
|
||||
}
|
||||
syntax(cur_line, "invalid keyword '@%s'\n", memdup(buf, len));
|
||||
}
|
||||
|
||||
static int emit_text(const char *buf, int size)
|
||||
{
|
||||
struct token *tok;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < size; i++) {
|
||||
int c = buf[i];
|
||||
if (c == '@' || c == '`' || c == '*' || c == '\n' || c == '\\' || c == '\t')
|
||||
break;
|
||||
}
|
||||
tok = new_token(TOK_TEXT);
|
||||
tok->text = buf;
|
||||
tok->len = i;
|
||||
emit_token(tok);
|
||||
return i;
|
||||
}
|
||||
|
||||
static void tokenize(const char *buf, int size)
|
||||
{
|
||||
int pos = 0;
|
||||
|
||||
while (pos < size) {
|
||||
struct token *tok;
|
||||
int ch;
|
||||
|
||||
ch = buf[pos++];
|
||||
switch (ch) {
|
||||
case '@':
|
||||
pos += emit_keyword(buf + pos, size - pos);
|
||||
break;
|
||||
case '`':
|
||||
emit(TOK_ITALIC);
|
||||
break;
|
||||
case '*':
|
||||
emit(TOK_BOLD);
|
||||
break;
|
||||
case '\n':
|
||||
emit(TOK_NL);
|
||||
cur_line++;
|
||||
break;
|
||||
case '\t':
|
||||
emit(TOK_INDENT);
|
||||
break;
|
||||
case '\\':
|
||||
tok = new_token(TOK_TEXT);
|
||||
tok->text = buf + pos;
|
||||
tok->len = 1;
|
||||
pos++;
|
||||
if (pos == size || buf[pos] == '\n') {
|
||||
// just one '\\'
|
||||
tok->text--;
|
||||
}
|
||||
|
||||
if (tok->text[0] == '\\') {
|
||||
tok->text = "\\\\";
|
||||
tok->len = 2;
|
||||
}
|
||||
|
||||
emit_token(tok);
|
||||
break;
|
||||
default:
|
||||
pos--;
|
||||
pos += emit_text(buf + pos, size - pos);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int is_empty_line(const struct token *tok)
|
||||
{
|
||||
while (tok != &head) {
|
||||
int i;
|
||||
|
||||
switch (tok->type) {
|
||||
case TOK_TEXT:
|
||||
for (i = 0; i < tok->len; i++) {
|
||||
if (tok->text[i] != ' ')
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
case TOK_INDENT:
|
||||
break;
|
||||
case TOK_NL:
|
||||
return 1;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
tok = tok->next;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static struct token *remove_line(struct token *tok)
|
||||
{
|
||||
while (tok != &head) {
|
||||
struct token *next = tok->next;
|
||||
int type = tok->type;
|
||||
|
||||
free_token(tok);
|
||||
tok = next;
|
||||
if (type == TOK_NL)
|
||||
break;
|
||||
}
|
||||
return tok;
|
||||
}
|
||||
|
||||
static struct token *skip_after(struct token *tok, int type)
|
||||
{
|
||||
struct token *save = tok;
|
||||
|
||||
while (tok != &head) {
|
||||
if (tok->type == type) {
|
||||
tok = tok->next;
|
||||
if (tok->type != TOK_NL)
|
||||
syntax(tok->line, "newline expected after @%s\n",
|
||||
keyword_name(type));
|
||||
return tok->next;
|
||||
}
|
||||
if (tok->type >= TOK_H1)
|
||||
syntax(tok->line, "keywords not allowed betweed @%s and @%s\n",
|
||||
keyword_name(type-1), keyword_name(type));
|
||||
tok = tok->next;
|
||||
}
|
||||
syntax(save->prev->line, "missing @%s\n", keyword_name(type));
|
||||
}
|
||||
|
||||
static struct token *get_next_line(struct token *tok)
|
||||
{
|
||||
while (tok != &head) {
|
||||
int type = tok->type;
|
||||
|
||||
tok = tok->next;
|
||||
if (type == TOK_NL)
|
||||
break;
|
||||
}
|
||||
return tok;
|
||||
}
|
||||
|
||||
static struct token *get_indent(struct token *tok, int *ip)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
while (tok != &head && tok->type == TOK_INDENT) {
|
||||
tok = tok->next;
|
||||
i++;
|
||||
}
|
||||
*ip = i;
|
||||
return tok;
|
||||
}
|
||||
|
||||
// line must be non-empty
|
||||
static struct token *check_line(struct token *tok, int *ip)
|
||||
{
|
||||
struct token *start;
|
||||
int tok_type;
|
||||
|
||||
start = tok = get_indent(tok, ip);
|
||||
|
||||
tok_type = tok->type;
|
||||
switch (tok_type) {
|
||||
case TOK_TEXT:
|
||||
case TOK_BOLD:
|
||||
case TOK_ITALIC:
|
||||
case TOK_BR:
|
||||
tok = tok->next;
|
||||
while (tok != &head) {
|
||||
switch (tok->type) {
|
||||
case TOK_TEXT:
|
||||
case TOK_BOLD:
|
||||
case TOK_ITALIC:
|
||||
case TOK_BR:
|
||||
case TOK_INDENT:
|
||||
break;
|
||||
case TOK_NL:
|
||||
return start;
|
||||
default:
|
||||
syntax(tok->line, "@%s not allowed inside paragraph\n",
|
||||
keyword_name(tok->type));
|
||||
}
|
||||
tok = tok->next;
|
||||
}
|
||||
break;
|
||||
case TOK_H1:
|
||||
case TOK_H2:
|
||||
case TOK_TITLE:
|
||||
if (*ip)
|
||||
goto indentation;
|
||||
|
||||
// check arguments
|
||||
tok = tok->next;
|
||||
while (tok != &head) {
|
||||
switch (tok->type) {
|
||||
case TOK_TEXT:
|
||||
case TOK_INDENT:
|
||||
break;
|
||||
case TOK_NL:
|
||||
return start;
|
||||
default:
|
||||
syntax(tok->line, "@%s can contain only text\n",
|
||||
keyword_name(tok_type));
|
||||
}
|
||||
tok = tok->next;
|
||||
}
|
||||
break;
|
||||
case TOK_LI:
|
||||
// check arguments
|
||||
tok = tok->next;
|
||||
while (tok != &head) {
|
||||
switch (tok->type) {
|
||||
case TOK_TEXT:
|
||||
case TOK_BOLD:
|
||||
case TOK_ITALIC:
|
||||
case TOK_INDENT:
|
||||
break;
|
||||
case TOK_NL:
|
||||
return start;
|
||||
default:
|
||||
syntax(tok->line, "@%s not allowed inside @li\n",
|
||||
keyword_name(tok->type));
|
||||
}
|
||||
tok = tok->next;
|
||||
}
|
||||
break;
|
||||
case TOK_PRE:
|
||||
// checked later
|
||||
break;
|
||||
case TOK_RAW:
|
||||
if (*ip)
|
||||
goto indentation;
|
||||
// checked later
|
||||
break;
|
||||
case TOK_ENDPRE:
|
||||
case TOK_ENDRAW:
|
||||
syntax(tok->line, "@%s not expected\n", keyword_name(tok->type));
|
||||
break;
|
||||
case TOK_NL:
|
||||
case TOK_INDENT:
|
||||
BUG();
|
||||
break;
|
||||
}
|
||||
return start;
|
||||
indentation:
|
||||
syntax(tok->line, "indentation before @%s\n", keyword_name(tok->type));
|
||||
}
|
||||
|
||||
static void insert_nl_before(struct token *next)
|
||||
{
|
||||
struct token *prev = next->prev;
|
||||
struct token *new = new_token(TOK_NL);
|
||||
|
||||
new->prev = prev;
|
||||
new->next = next;
|
||||
prev->next = new;
|
||||
next->prev = new;
|
||||
}
|
||||
|
||||
static void normalize(void)
|
||||
{
|
||||
struct token *tok = head.next;
|
||||
/*
|
||||
* >= 0 if previous line was text (== amount of indent)
|
||||
* -1 if previous block was @pre (amount of indent doesn't matter)
|
||||
* -2 otherwise (@h1 etc., indent was 0)
|
||||
*/
|
||||
int prev_indent = -2;
|
||||
|
||||
while (tok != &head) {
|
||||
struct token *start;
|
||||
int i, new_para = 0;
|
||||
|
||||
// remove empty lines
|
||||
while (is_empty_line(tok)) {
|
||||
tok = remove_line(tok);
|
||||
new_para = 1;
|
||||
if (tok == &head)
|
||||
return;
|
||||
}
|
||||
|
||||
// skips indent
|
||||
start = tok;
|
||||
tok = check_line(tok, &i);
|
||||
|
||||
switch (tok->type) {
|
||||
case TOK_TEXT:
|
||||
case TOK_ITALIC:
|
||||
case TOK_BOLD:
|
||||
case TOK_BR:
|
||||
// normal text
|
||||
if (new_para && prev_indent >= -1) {
|
||||
// previous line/block was text or @pre
|
||||
// and there was a empty line after it
|
||||
insert_nl_before(start);
|
||||
}
|
||||
|
||||
if (!new_para && prev_indent == i) {
|
||||
// join with previous line
|
||||
struct token *nl = start->prev;
|
||||
|
||||
if (nl->type != TOK_NL)
|
||||
BUG();
|
||||
|
||||
if ((nl->prev != &head && nl->prev->type == TOK_BR) ||
|
||||
tok->type == TOK_BR) {
|
||||
// don't convert \n after/before @br to ' '
|
||||
free_token(nl);
|
||||
} else {
|
||||
// convert "\n" to " "
|
||||
nl->type = TOK_TEXT;
|
||||
nl->text = " ";
|
||||
nl->len = 1;
|
||||
}
|
||||
|
||||
// remove indent
|
||||
while (start->type == TOK_INDENT) {
|
||||
struct token *next = start->next;
|
||||
free_token(start);
|
||||
start = next;
|
||||
}
|
||||
}
|
||||
|
||||
prev_indent = i;
|
||||
tok = get_next_line(tok);
|
||||
break;
|
||||
case TOK_PRE:
|
||||
case TOK_RAW:
|
||||
// these can be directly after normal text
|
||||
// but not joined with the previous line
|
||||
if (new_para && prev_indent >= -1) {
|
||||
// previous line/block was text or @pre
|
||||
// and there was a empty line after it
|
||||
insert_nl_before(start);
|
||||
}
|
||||
tok = skip_after(tok->next, tok->type + 1);
|
||||
prev_indent = -1;
|
||||
break;
|
||||
case TOK_H1:
|
||||
case TOK_H2:
|
||||
case TOK_LI:
|
||||
case TOK_TITLE:
|
||||
// remove white space after H1, H2, L1 and TITLE
|
||||
tok = tok->next;
|
||||
while (tok != &head) {
|
||||
int type = tok->type;
|
||||
struct token *next;
|
||||
|
||||
if (type == TOK_TEXT) {
|
||||
while (tok->len && *tok->text == ' ') {
|
||||
tok->text++;
|
||||
tok->len--;
|
||||
}
|
||||
if (tok->len)
|
||||
break;
|
||||
}
|
||||
if (type != TOK_INDENT)
|
||||
break;
|
||||
|
||||
// empty TOK_TEXT or TOK_INDENT
|
||||
next = tok->next;
|
||||
free_token(tok);
|
||||
tok = next;
|
||||
}
|
||||
// not normal text. can't be joined
|
||||
prev_indent = -2;
|
||||
tok = get_next_line(tok);
|
||||
break;
|
||||
case TOK_NL:
|
||||
case TOK_INDENT:
|
||||
case TOK_ENDPRE:
|
||||
case TOK_ENDRAW:
|
||||
BUG();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define output(...) fprintf(outfile, __VA_ARGS__)
|
||||
|
||||
static void output_buf(const char *buf, int len)
|
||||
{
|
||||
fwrite(buf, 1, len, outfile);
|
||||
}
|
||||
|
||||
static void output_text(struct token *tok)
|
||||
{
|
||||
char buf[1024];
|
||||
const char *str = tok->text;
|
||||
int len = tok->len;
|
||||
int pos = 0;
|
||||
|
||||
while (len) {
|
||||
int c = *str++;
|
||||
|
||||
if (pos >= sizeof(buf) - 1) {
|
||||
output_buf(buf, pos);
|
||||
pos = 0;
|
||||
}
|
||||
if (c == '-')
|
||||
buf[pos++] = '\\';
|
||||
buf[pos++] = c;
|
||||
len--;
|
||||
}
|
||||
|
||||
if (pos)
|
||||
output_buf(buf, pos);
|
||||
}
|
||||
|
||||
static int bold = 0;
|
||||
static int italic = 0;
|
||||
static int indent = 0;
|
||||
|
||||
static struct token *output_pre(struct token *tok)
|
||||
{
|
||||
int bol = 1;
|
||||
|
||||
if (tok->type != TOK_NL)
|
||||
syntax(tok->line, "newline expected after @pre\n");
|
||||
|
||||
output(".nf\n");
|
||||
tok = tok->next;
|
||||
while (tok != &head) {
|
||||
if (bol) {
|
||||
int i;
|
||||
|
||||
tok = get_indent(tok, &i);
|
||||
if (i != indent && tok->type != TOK_NL)
|
||||
syntax(tok->line, "indent changed in @pre\n");
|
||||
}
|
||||
|
||||
switch (tok->type) {
|
||||
case TOK_TEXT:
|
||||
if (bol && tok->len && tok->text[0] == '.')
|
||||
output("\\&");
|
||||
output_text(tok);
|
||||
break;
|
||||
case TOK_NL:
|
||||
output("\n");
|
||||
bol = 1;
|
||||
tok = tok->next;
|
||||
continue;
|
||||
case TOK_ITALIC:
|
||||
output("`");
|
||||
break;
|
||||
case TOK_BOLD:
|
||||
output("*");
|
||||
break;
|
||||
case TOK_INDENT:
|
||||
// FIXME: warn
|
||||
output(" ");
|
||||
break;
|
||||
case TOK_ENDPRE:
|
||||
output(".fi\n");
|
||||
tok = tok->next;
|
||||
if (tok != &head && tok->type == TOK_NL)
|
||||
tok = tok->next;
|
||||
return tok;
|
||||
default:
|
||||
BUG();
|
||||
break;
|
||||
}
|
||||
bol = 0;
|
||||
tok = tok->next;
|
||||
}
|
||||
return tok;
|
||||
}
|
||||
|
||||
static struct token *output_raw(struct token *tok)
|
||||
{
|
||||
if (tok->type != TOK_NL)
|
||||
syntax(tok->line, "newline expected after @raw\n");
|
||||
|
||||
tok = tok->next;
|
||||
while (tok != &head) {
|
||||
switch (tok->type) {
|
||||
case TOK_TEXT:
|
||||
if (tok->len == 2 && !strncmp(tok->text, "\\\\", 2)) {
|
||||
/* ugly special case
|
||||
* "\\" (\) was converted to "\\\\" (\\) because
|
||||
* nroff does escaping too.
|
||||
*/
|
||||
output("\\");
|
||||
} else {
|
||||
output_buf(tok->text, tok->len);
|
||||
}
|
||||
break;
|
||||
case TOK_NL:
|
||||
output("\n");
|
||||
break;
|
||||
case TOK_ITALIC:
|
||||
output("`");
|
||||
break;
|
||||
case TOK_BOLD:
|
||||
output("*");
|
||||
break;
|
||||
case TOK_INDENT:
|
||||
output("\t");
|
||||
break;
|
||||
case TOK_ENDRAW:
|
||||
tok = tok->next;
|
||||
if (tok != &head && tok->type == TOK_NL)
|
||||
tok = tok->next;
|
||||
return tok;
|
||||
default:
|
||||
BUG();
|
||||
break;
|
||||
}
|
||||
tok = tok->next;
|
||||
}
|
||||
return tok;
|
||||
}
|
||||
|
||||
static struct token *output_para(struct token *tok)
|
||||
{
|
||||
int bol = 1;
|
||||
|
||||
while (tok != &head) {
|
||||
switch (tok->type) {
|
||||
case TOK_TEXT:
|
||||
output_text(tok);
|
||||
break;
|
||||
case TOK_ITALIC:
|
||||
italic ^= 1;
|
||||
if (italic) {
|
||||
output("\\fI");
|
||||
} else {
|
||||
output("\\fR");
|
||||
}
|
||||
break;
|
||||
case TOK_BOLD:
|
||||
bold ^= 1;
|
||||
if (bold) {
|
||||
output("\\fB");
|
||||
} else {
|
||||
output("\\fR");
|
||||
}
|
||||
break;
|
||||
case TOK_BR:
|
||||
if (bol) {
|
||||
output(".br\n");
|
||||
} else {
|
||||
output("\n.br\n");
|
||||
}
|
||||
bol = 1;
|
||||
tok = tok->next;
|
||||
continue;
|
||||
case TOK_NL:
|
||||
output("\n");
|
||||
return tok->next;
|
||||
case TOK_INDENT:
|
||||
output(" ");
|
||||
break;
|
||||
default:
|
||||
BUG();
|
||||
break;
|
||||
}
|
||||
bol = 0;
|
||||
tok = tok->next;
|
||||
}
|
||||
return tok;
|
||||
}
|
||||
|
||||
static struct token *title(struct token *tok, const char *cmd)
|
||||
{
|
||||
output("%s", cmd);
|
||||
return output_para(tok->next);
|
||||
}
|
||||
|
||||
static struct token *dump_one(struct token *tok)
|
||||
{
|
||||
int i;
|
||||
|
||||
tok = get_indent(tok, &i);
|
||||
if (tok->type != TOK_RAW) {
|
||||
while (indent < i) {
|
||||
output(".RS\n");
|
||||
indent++;
|
||||
}
|
||||
while (indent > i) {
|
||||
output(".RE\n");
|
||||
indent--;
|
||||
}
|
||||
}
|
||||
|
||||
switch (tok->type) {
|
||||
case TOK_TEXT:
|
||||
case TOK_ITALIC:
|
||||
case TOK_BOLD:
|
||||
case TOK_BR:
|
||||
if (tok->type == TOK_TEXT && tok->len && tok->text[0] == '.')
|
||||
output("\\&");
|
||||
tok = output_para(tok);
|
||||
break;
|
||||
case TOK_H1:
|
||||
tok = title(tok, ".SH ");
|
||||
break;
|
||||
case TOK_H2:
|
||||
tok = title(tok, ".SS ");
|
||||
break;
|
||||
case TOK_LI:
|
||||
tok = title(tok, ".TP\n");
|
||||
break;
|
||||
case TOK_PRE:
|
||||
tok = output_pre(tok->next);
|
||||
break;
|
||||
case TOK_RAW:
|
||||
tok = output_raw(tok->next);
|
||||
break;
|
||||
case TOK_TITLE:
|
||||
tok = title(tok, ".TH ");
|
||||
// must be after .TH
|
||||
// no hyphenation, adjust left
|
||||
output(".nh\n.ad l\n");
|
||||
break;
|
||||
case TOK_NL:
|
||||
output("\n");
|
||||
tok = tok->next;
|
||||
break;
|
||||
case TOK_ENDPRE:
|
||||
case TOK_ENDRAW:
|
||||
case TOK_INDENT:
|
||||
BUG();
|
||||
break;
|
||||
}
|
||||
return tok;
|
||||
}
|
||||
|
||||
static void dump(void)
|
||||
{
|
||||
struct token *tok = head.next;
|
||||
|
||||
while (tok != &head)
|
||||
tok = dump_one(tok);
|
||||
}
|
||||
|
||||
static void process(void)
|
||||
{
|
||||
struct stat s = {};
|
||||
const char *buf;
|
||||
int fd;
|
||||
|
||||
fd = open(filename, O_RDONLY);
|
||||
if (fd == -1)
|
||||
die("opening `%s' for reading: %s\n", filename, strerror(errno));
|
||||
fstat(fd, &s);
|
||||
if (s.st_size) {
|
||||
buf = mmap(NULL, s.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
if (buf == MAP_FAILED)
|
||||
die("mmap: %s\n", strerror(errno));
|
||||
|
||||
tokenize(buf, s.st_size);
|
||||
normalize();
|
||||
}
|
||||
close(fd);
|
||||
dump();
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
const char *dest;
|
||||
int fd;
|
||||
|
||||
program = argv[0];
|
||||
if (argc != 3) {
|
||||
fprintf(stderr, "Usage: %s <in> <out>\n", program);
|
||||
return 1;
|
||||
}
|
||||
filename = argv[1];
|
||||
dest = argv[2];
|
||||
|
||||
snprintf(tmp_file, sizeof(tmp_file), "%s.XXXXXX", dest);
|
||||
fd = mkstemp(tmp_file);
|
||||
if (fd < 0)
|
||||
die("creating %s: %s\n", tmp_file, strerror(errno));
|
||||
outfile = fdopen(fd, "w");
|
||||
if (!outfile)
|
||||
die("opening %s: %s\n", tmp_file, strerror(errno));
|
||||
|
||||
process();
|
||||
if (rename(tmp_file, dest))
|
||||
die("renaming %s to %s: %s\n", tmp_file, dest, strerror(errno));
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user