/* -*- 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); }