Login
f-wiki.c at [39121d2e23]
Login

File f-apps/f-wiki.c artifact 922745c538 part of check-in 39121d2e23


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