/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/*
Copyright (c) 2013 D. Richard Hipp
This program is free software; you can redistribute it and/or
modify it under the terms of the Simplified BSD License (also
known as the "2-Clause License" or "FreeBSD License".)
This program is distributed in the hope that it will be useful,
but without any warranty; without even the implied warranty of
merchantability or fitness for a particular purpose.
Author contact information:
drh@hwaci.com
http://www.hwaci.com/drh/
*****************************************************************************
This file implements some test/demo code for working with wiki pages.
*/
#include "fossil-scm/fossil-cli.h" /* Fossil App mini-framework */
static void fcli_local_help(){
printf("Usage:\n\t%s [options] command [options]\n\n", fcli.appName);
puts("Wiki commands and their options:");
puts("\n\texport PageName OutputFile\n\t"
"Exports the most recent version of "
"the given page (to stdout by default). "
"PageName may optionally be specified using [-p|--page=name] "
"and OutputFile optionally via [-o|--output-file=FILENAME]."
);
puts("\n\tls [-n|--names] [--utc]\n\t"
"Lists the most recent versions of all wiki pages. "
"Use -V for more info. The --names option causes only "
"the names to be output. The --utc flag switches from local to "
"UTC times.");
puts("\n\tsave PageName InputFile "
"[-n|--dry-run] "
"[-t|--mime-type=TYPE] "
"[--new] "
);
puts("\t"
"Imports a file to a wiki page. "
"PageName and InputFile may optionally be specified using "
"[-p|--page=name] resp. [-f|--file=name]. The --new option allows "
"the creation of new pages. Without that option, saving will "
"fail for non-existing pages in order to avoid that typos fill "
"the repo with unwanted pages. The --mime-type flag specifies "
"the content type. If not specified, fossil(1) generally assumes "
"\"text/x-fossil-wiki\"."
);
}
fsl_int32_t wiki_page_count(){
return fsl_db_g_int32(fsl_cx_db_repo(fcli.f), -1,
"SELECT count(*) FROM tag "
"WHERE tagname GLOB 'wiki-*'");
}
static struct {
char listNamesOnly;
char * pageName;
char utcTime;
} WikiApp = {
0/*listNamesOnly*/,
NULL/*pageName*/,
0/*utcTime*/
};
/**
A fsl_deck_visitor_f() impl which expects d to be a WIKI
artifact, and it outputs various info about it.
*/
static int cb_f_wiki_list( fsl_cx * f, fsl_deck const * d,
void * state ){
int * counter = (int*)state;
if(WikiApp.listNamesOnly){
f_out("%s\n", d->L);
}else{
fsl_db * db = fsl_cx_db_repo(fcli.f);
char * ts = fsl_db_julian_to_iso8601(db, d->D, 0, !WikiApp.utcTime);
assert('T'==ts[10]);
ts[10] = ' ';
if(0 == (*counter)++){
if(fcli.verbose){
f_out("RID ");
}
f_out("%-20s %-13s %-6s Name\n",
(WikiApp.utcTime
? "Time (UTC)"
: "Time (local time)"),
"UUID", "Size");
}
if(fcli.verbose){
f_out("%-6"FSL_ID_T_PFMT" ", d->rid);
}
f_out("%-20s %.*s %-6"FSL_SIZE_T_PFMT" %s\n",
ts, 12, d->uuid, (fsl_size_t)d->W.used, d->L);
fsl_free(ts);
}
#if defined(DEBUG)
{
int rc;
fsl_id_t ridCheck = 0;
rc = fsl_wiki_latest_rid(f, d->L, &ridCheck);
assert(!rc);
if(d->rid!=ridCheck){
f_out("d->rid=%"FSL_ID_T_PFMT", ridCheck=%"FSL_ID_T_PFMT"\n",
d->rid, ridCheck);
}
assert(d->rid==ridCheck);
}
#endif
return 0;
}
static int fcmd_wiki_list(){
fsl_cx * f = fcli.f;
int counter = 0;
int rc;
WikiApp.listNamesOnly = fcli_flag2("n", "names", NULL);
if(fcli_has_unused_flags(0)){
return FSL_RC_MISUSE;
}
if(WikiApp.listNamesOnly){
fsl_list li = fsl_list_empty;
fsl_size_t i;
rc = fsl_wiki_names_get(f, &li);
for( i = 0; !rc && (i<li.used); ++i){
char const * n = (char const *)li.list[i];
f_out("%s\n", n);
}
fsl_list_clear(&li, fsl_list_v_fsl_free, NULL);
}else{
rc = fsl_wiki_foreach_page(f, cb_f_wiki_list, &counter );
}
return rc;
}
static int fcmd_wiki_export(){
fsl_cx * f = fcli.f;
char * pName = WikiApp.pageName;
char * oFile = NULL;
int rc;
fsl_deck d = fsl_deck_empty;
if(!pName && !(pName = fcli_next_arg(1))){
rc = fcli_err_set(FSL_RC_MISUSE,
"Expecting wiki page name argument.");
goto end;
}
fcli_flag_or_arg("o", "ouput-file", &oFile);
if(fcli_has_unused_flags(0)){
rc = FSL_RC_MISUSE;
goto end;
}
rc = fsl_wiki_load_latest(f, pName, &d);
if(!rc){
if(oFile){
FILE * of = fsl_fopen(oFile, "w");
if(!of){
rc = fcli_err_set(FSL_RC_IO,
"Could not open [%s] for writing.",
oFile);
}else{
fwrite(d.W.mem, d.W.used, 1, of);
fsl_fclose(of);
}
}else{
f_out("%b", &d.W);
}
}
end:
if(WikiApp.pageName != pName) fsl_free(pName);
fsl_free(oFile);
fsl_deck_finalize(&d);
return rc;
}
static int fcmd_wiki_save(){
fsl_cx * f = fcli.f;
char * pName = WikiApp.pageName;
char * iFile = NULL;
char * mimeType = NULL;
int rc;
fsl_buffer buf = fsl_buffer_empty;
fsl_db * db = fsl_cx_db_repo(f);
char const dryRun = fcli.fDryRun || fcli_flag("n", NULL);
char const allowNew = fcli_flag("new", NULL);
char pageExists;
char const * userName;
fcli_flag2("f", "file", &iFile);
fcli_flag2("t", "mime-type", &mimeType);
if(fcli_has_unused_flags(0)){
rc = FSL_RC_MISUSE;
goto end;
}
userName = fsl_cx_user_get(f);
if(!pName && !(pName = fcli_next_arg(1))){
rc = fcli_err_set(FSL_RC_MISUSE,
"Expecting wiki page name argument.");
goto end;
}
if(!iFile && !(iFile = fcli_next_arg(1))){
rc = fcli_err_set(FSL_RC_MISUSE,
"Expecting input file name.");
goto end;
}
rc = fsl_buffer_fill_from_filename(&buf, iFile);
if(rc){
rc = fcli_err_set(rc, "Error opening file: %s", iFile);
goto end;
}
pageExists = fsl_wiki_page_exists(f, pName);
if(!pageExists && !allowNew){
rc = fcli_err_set(FSL_RC_NOT_FOUND,
"Page [%s] does not exists. "
"Use --new to allow "
"creation of new pages.", pName);
goto end;
}
fsl_db_transaction_begin(db);
rc = fsl_wiki_save(f, pName, &buf, userName,
mimeType, FSL_WIKI_SAVE_MODE_UPSERT);
if(!rc){
if(dryRun){
f_out("Dry-run mode _not_ saving changes.\n");
}
rc = fsl_db_transaction_end(db, dryRun ? 1 : 0);
}else{
fsl_db_transaction_end(db, 1);
}
end:
if(pName != WikiApp.pageName) fsl_free(pName);
fsl_free(iFile);
fsl_free(mimeType);
fsl_buffer_clear(&buf);
return rc;
}
static FossilCommand aCommandsWiki[] = {
{"export", fcmd_wiki_export},
{"ls", fcmd_wiki_list},
{"save", fcmd_wiki_save},
{NULL,NULL}/*empty sentinel is required by traversal algos*/
};
static void WikiApp_atexit(){
fsl_free(WikiApp.pageName);
WikiApp.pageName = NULL;
}
/**
Just experimenting with fsl_xlink_listener() and friends.
*/
static int wiki_xlink_f(fsl_cx * f, fsl_deck const * d, void * state){
FCLI_V(("Crosslink callback for %s artifact [%.*s] (RID %"FSL_ID_T_PFMT")\n",
fsl_catype_cstr(d->type), 8, d->uuid, d->rid));
return *((char const *)state) /* demonstrate what happens when crosslinking fails. */
? FSL_RC_NYI
: 0;
}
int main(int argc, char * const * argv ){
int rc;
fcli.appHelp = fcli_local_help;
rc = fcli_setup(argc, argv);
if(FSL_RC_BREAK==rc) /* --help */ return 0;
else if(!rc){
if(!fsl_cx_db_repo(fcli.f)){
rc = fcli_err_set(FSL_RC_MISUSE,
"This app requires a repository db.");
}
else{
char failCrosslink = fcli_flag2("fx", "fail-xlink", NULL);;
fsl_xlink_listener( fcli.f, fcli.appName, wiki_xlink_f, &failCrosslink );
WikiApp.utcTime = fcli_flag("utc", NULL);
atexit(WikiApp_atexit);
fcli_flag2("p", "page", &WikiApp.pageName);
rc = fcli_dispatch_commands(aCommandsWiki, 0);
}
}
/* fsl_free(symToTag); */
return fcli_end_of_main(rc);
}