changeset 218:fba20c625fb3

Added `wk init` command to create a new wiki.
author Ludovic Chabant <ludovic@chabant.com>
date Mon, 03 Mar 2014 08:15:16 -0800
parents 657edcb91804
children 139b9b82db78
files wikked/auth.py wikked/commands/manage.py wikked/db/base.py wikked/db/sql.py wikked/fs.py wikked/indexer/base.py wikked/resources/init/Main page.md wikked/resources/init/Sandbox.md wikked/scm/base.py wikked/scm/mercurial.py wikked/wiki.py wikked/witch.py
diffstat 12 files changed, 197 insertions(+), 63 deletions(-) [+]
line wrap: on
line diff
--- a/wikked/auth.py	Mon Mar 03 08:15:03 2014 -0800
+++ b/wikked/auth.py	Mon Mar 03 08:15:16 2014 -0800
@@ -36,6 +36,15 @@
         self._updatePermissions(config)
         self._updateUserInfos(config)
 
+    def start(self, wiki):
+        pass
+
+    def init(self, wiki):
+        pass
+
+    def postInit(self):
+        pass
+
     def getUsers(self):
         for user in self._users:
             yield self._createUser(user)
--- a/wikked/commands/manage.py	Mon Mar 03 08:15:03 2014 -0800
+++ b/wikked/commands/manage.py	Mon Mar 03 08:15:16 2014 -0800
@@ -1,3 +1,6 @@
+import os
+import os.path
+import shutil
 import logging
 from wikked.commands.base import WikkedCommand, register_command
 
@@ -6,6 +9,50 @@
 
 
 @register_command
+class InitCommand(WikkedCommand):
+    def __init__(self):
+        super(InitCommand, self).__init__()
+        self.name = 'init'
+        self.description = "Creates a new wiki"
+        self.requires_wiki = False
+
+    def setupParser(self, parser):
+        parser.add_argument('destination',
+                help="The destination directory to create the wiki")
+        parser.add_argument('--hg',
+                help="Use Mercurial as a revision system (default)",
+                action='store_true')
+        parser.add_argument('--git',
+                help="Use Git as a revision system",
+                action='store_true')
+        parser.add_argument('--bare',
+                help="Don't create the default pages",
+                action='store_true')
+
+    def run(self, ctx):
+        if ctx.args.git:
+            raise Exception("Git is not yet fully supported.")
+
+        path = ctx.args.destination or os.getcwd()
+        path = os.path.abspath(path)
+        if not os.path.isdir(path):
+            os.makedirs(path)
+
+        logger.info("Initializing new wiki at: %s" % path)
+        from wikked.wiki import WikiParameters, Wiki
+        parameters = WikiParameters(path)
+        wiki = Wiki(parameters, for_init=True)
+        wiki.init()
+
+        if not ctx.args.bare:
+            src_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)),
+                                   'resources', 'init')
+
+            shutil.copy(os.path.join(src_dir, 'Main page.md'), path)
+            shutil.copy(os.path.join(src_dir, 'Sandbox.md'), path)
+
+
+@register_command
 class ResetCommand(WikkedCommand):
     def __init__(self):
         super(ResetCommand, self).__init__()
--- a/wikked/db/base.py	Mon Mar 03 08:15:03 2014 -0800
+++ b/wikked/db/base.py	Mon Mar 03 08:15:16 2014 -0800
@@ -8,7 +8,13 @@
         pass
 
     def start(self, wiki):
-        raise NotImplementedError()
+        pass
+
+    def init(self, wiki):
+        pass
+
+    def postInit(self):
+        pass
 
     def close(self):
         raise NotImplementedError()
--- a/wikked/db/sql.py	Mon Mar 03 08:15:03 2014 -0800
+++ b/wikked/db/sql.py	Mon Mar 03 08:15:16 2014 -0800
@@ -12,7 +12,7 @@
 from sqlalchemy.ext.declarative import declarative_base
 from sqlalchemy.orm import (
     scoped_session, sessionmaker,
-    relationship, backref, noload, load_only, subqueryload)
+    relationship, backref, load_only, subqueryload)
 from sqlalchemy.orm.exc import NoResultFound
 from wikked.db.base import Database
 from wikked.page import Page, PageData
@@ -135,31 +135,10 @@
         self._engine = None
         self._session = None
 
-    def start(self, wiki):
-        self.wiki = wiki
-
-    def createDb(self):
-        create_schema = False
-        if self.engine_url != 'sqlite:///:memory:':
-            # The existing schema is outdated, re-create it.
-            schema_version = self._getSchemaVersion()
-            if schema_version < self.schema_version:
-                logger.debug(
-                    "SQL database is outdated (got version %s), "
-                    "will re-create.",
-                    schema_version)
-                create_schema = True
-            else:
-                logger.debug(
-                    "SQL database has up-to-date schema.")
-        else:
-            create_schema = True
-        if create_schema:
-            self._createSchema()
-
     @property
     def engine(self):
         if self._engine is None:
+            logger.debug("Creating SQL engine from URL: %s" % self.engine_url)
             self._engine = create_engine(self.engine_url, convert_unicode=True)
         return self._engine
 
@@ -173,6 +152,56 @@
                 bind=self.engine))
         return self._session
 
+    def _needsSchemaUpdate(self):
+        if self.engine_url == 'sqlite:///:memory:':
+            # Always create the schema for a memory database.
+            return True
+
+        # The existing schema is outdated, re-create it.
+        schema_version = self._getSchemaVersion()
+        if schema_version < self.schema_version:
+            logger.debug(
+                "SQL database is outdated (got version %s), "
+                "will re-create.",
+                schema_version)
+            return True
+        else:
+            logger.debug(
+                "SQL database has up-to-date schema.")
+            return False
+
+    def _createSchema(self):
+        logger.debug("Creating new SQL schema.")
+        Base.metadata.drop_all(self.engine)
+        Base.metadata.create_all(self.engine)
+
+        ver = SQLInfo()
+        ver.name = 'schema_version'
+        ver.int_value = self.schema_version
+        self.session.add(ver)
+        self.session.commit()
+
+    def _getSchemaVersion(self):
+        try:
+            q = self.session.query(SQLInfo).\
+                    filter(SQLInfo.name == 'schema_version').\
+                    first()
+            if q is None:
+                return 0
+        except:
+            return -1
+        return q.int_value
+
+    def init(self, wiki):
+        pass
+
+    def postInit(self):
+        logger.info("Initializing SQL database.")
+        self._createSchema()
+
+    def start(self, wiki):
+        self.wiki = wiki
+
     def close(self, commit, exception):
         if self._session is not None:
             if commit and exception is None:
@@ -187,6 +216,10 @@
         self.session.commit()
 
     def update(self, pages, force=False):
+        if self._needsSchemaUpdate():
+            raise Exception("This wiki needs a database upgrade. "
+                            "Please run `wk reset`.")
+
         to_update = set()
         already_added = set()
         to_remove = []
@@ -365,27 +398,6 @@
                 query = query.options(subqueryload(sqf))
         return query
 
-    def _createSchema(self):
-        Base.metadata.drop_all(self.engine)
-        Base.metadata.create_all(self.engine)
-
-        ver = SQLInfo()
-        ver.name = 'schema_version'
-        ver.int_value = self.schema_version
-        self.session.add(ver)
-        self.session.commit()
-
-    def _getSchemaVersion(self):
-        try:
-            q = self.session.query(SQLInfo).\
-                    filter(SQLInfo.name == 'schema_version').\
-                    first()
-            if q is None:
-                return 0
-        except:
-            return -1
-        return q.int_value
-
     def _addPage(self, page):
         logger.debug("Adding page '%s' to SQL database." % page.url)
 
--- a/wikked/fs.py	Mon Mar 03 08:15:03 2014 -0800
+++ b/wikked/fs.py	Mon Mar 03 08:15:16 2014 -0800
@@ -54,6 +54,12 @@
         excluded += wiki.scm.getSpecialFilenames()
         self.excluded = [os.path.join(self.root, e) for e in excluded]
 
+    def init(self, wiki):
+        pass
+
+    def postInit(self):
+        pass
+
     def getPageInfos(self, subdir=None):
         basepath = self.root
         if subdir is not None:
--- a/wikked/indexer/base.py	Mon Mar 03 08:15:03 2014 -0800
+++ b/wikked/indexer/base.py	Mon Mar 03 08:15:16 2014 -0800
@@ -14,6 +14,12 @@
     def start(self, wiki):
         raise NotImplementedError()
 
+    def init(self, wiki):
+        pass
+
+    def postInit(self):
+        pass
+
     def reset(self, pages):
         raise NotImplementedError()
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/wikked/resources/init/Main page.md	Mon Mar 03 08:15:16 2014 -0800
@@ -0,0 +1,6 @@
+Welcome to your new wiki! If you need help, check out the [user manual][1]. If you want to try things out, use the [[Sandbox]].
+
+Now go and *have fun* with your new wiki!
+
+[1]: http://bolt80.com/wikked/
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/wikked/resources/init/Sandbox.md	Mon Mar 03 08:15:16 2014 -0800
@@ -0,0 +1,2 @@
+You can play around here. _Go nuts_, or go back [[home|Main Page]].
+
--- a/wikked/scm/base.py	Mon Mar 03 08:15:03 2014 -0800
+++ b/wikked/scm/base.py	Mon Mar 03 08:15:16 2014 -0800
@@ -17,7 +17,13 @@
         pass
 
     def start(self, wiki):
-        raise NotImplementedError()
+        pass
+
+    def init(self, wiki):
+        pass
+
+    def postInit(self):
+        pass
 
     def getSpecialFilenames(self):
         raise NotImplementedError()
--- a/wikked/scm/mercurial.py	Mon Mar 03 08:15:03 2014 -0800
+++ b/wikked/scm/mercurial.py	Mon Mar 03 08:15:16 2014 -0800
@@ -27,14 +27,16 @@
                 }
 
     def start(self, wiki):
-        pass
+        self._doStart()
 
-    def createRepo(self):
+    def init(self, wiki):
         # Make a Mercurial repo if there's none.
         if not os.path.isdir(os.path.join(self.root, '.hg')):
             logger.info("Creating Mercurial repository at: " + self.root)
             self._initRepo(self.root)
 
+        self._doStart()
+
         # Create a `.hgignore` file is there's none.
         ignore_path = os.path.join(self.root, '.hgignore')
         if not os.path.isfile(ignore_path):
@@ -43,6 +45,9 @@
                 f.write('.wiki')
             self.commit([ignore_path], {'message': "Created `.hgignore`."})
 
+    def _doStart(self):
+        pass
+
     def getSpecialFilenames(self):
         return ['.hg*']
 
@@ -176,11 +181,17 @@
 class MercurialCommandServerSourceControl(MercurialBaseSourceControl):
     def __init__(self, root, client=None):
         MercurialBaseSourceControl.__init__(self, root)
+        self.client = client
 
-        if client is None:
+    def _initRepo(self, root):
+        exe = ['hg', 'init', root]
+        logger.debug("Running Mercurial: " + str(exe))
+        return subprocess.check_output(exe)
+
+    def _doStart(self):
+        if self.client is None:
             import hglib
-            client = hglib.open(root)
-        self.client = client
+            self.client = hglib.open(self.root)
 
     def getHistory(self, path=None, limit=10):
         if path is not None:
--- a/wikked/wiki.py	Mon Mar 03 08:15:03 2014 -0800
+++ b/wikked/wiki.py	Mon Mar 03 08:15:16 2014 -0800
@@ -68,7 +68,7 @@
         from wikked.db.sql import SQLDatabase
         return SQLDatabase(self.config)
 
-    def scm_factory(self):
+    def scm_factory(self, for_init=False):
         if self._scm_factory is None:
             try:
                 scm_type = self.config.get('wiki', 'sourcecontrol')
@@ -83,9 +83,11 @@
                     scm_type = 'hg'
 
             if scm_type == 'hg':
-                # Only create the command server once.
-                import hglib
-                client = hglib.open(self.root)
+                client = None
+                if not for_init:
+                    # Only create the command server once.
+                    import hglib
+                    client = hglib.open(self.root)
 
                 def impl():
                     from wikked.scm.mercurial import (
@@ -171,7 +173,7 @@
 class Wiki(object):
     """ The wiki class! This is where the magic happens.
     """
-    def __init__(self, parameters):
+    def __init__(self, parameters, for_init=False):
         """ Creates a new wiki instance. It won't be fully functional
             until you call `start`, which does the actual initialization.
             This gives you a chance to customize a few more things before
@@ -194,7 +196,7 @@
         self.fs = parameters.fs_factory()
         self.index = parameters.index_factory()
         self.db = parameters.db_factory()
-        self.scm = parameters.scm_factory()
+        self.scm = parameters.scm_factory(for_init)
         self.auth = parameters.auth_factory()
 
         self._updateSetPage = parameters.getPageUpdater()
@@ -210,10 +212,28 @@
         self.scm.start(self)
         self.index.start(self)
         self.db.start(self)
+        self.auth.start(self)
 
         if update:
             self.update()
 
+    def init(self):
+        """ Creates a new wiki at the specified root directory.
+        """
+        self.fs.init(self)
+        self.scm.init(self)
+        self.index.init(self)
+        self.db.init(self)
+        self.auth.init(self)
+
+        self.start()
+
+        self.fs.postInit()
+        self.scm.postInit()
+        self.index.postInit()
+        self.db.postInit()
+        self.auth.postInit()
+
     def stop(self):
         self.db.close()
 
@@ -233,17 +253,16 @@
             fs_page = FileSystemPage(self, page_info)
             self.db.update([fs_page], force=True)
             updated_urls.append(url)
+            self._cachePages([url])
             self.index.update([self.getPage(url)])
         else:
             page_infos = self.fs.getPageInfos()
             fs_pages = FileSystemPage.fromPageInfos(self, page_infos)
             self.db.update(fs_pages)
+            self._cachePages()
             updated_urls += [p.url for p in fs_pages]
             self.index.update(self.getPages())
 
-        if cache_ext_data:
-            self._cachePages([url] if url else None)
-
     def getPageUrls(self, subdir=None):
         """ Returns all the page URLs in the wiki, or in the given
             sub-directory.
--- a/wikked/witch.py	Mon Mar 03 08:15:03 2014 -0800
+++ b/wikked/witch.py	Mon Mar 03 08:15:16 2014 -0800
@@ -111,9 +111,13 @@
 
     # Create the wiki.
     root = find_wiki_root(result.root)
-    params = WikiParameters(root)
-    wiki = Wiki(params)
-    wiki.start()
+    if root:
+        params = WikiParameters(root)
+        wiki = Wiki(params)
+        wiki.start()
+    else:
+        params = None
+        wiki = None
 
     # Run the command!
     before = datetime.datetime.now()