Robert Clipsham avatar Robert Clipsham committed 3eb88ab

Added lots of documentation to Serenity.d and Controller.d.
Logging is now implemented and set-up in the bootstrap.
Logging can now be done in controllers with no setup.
Requests are now timed.
Reverted changes to build script as they didn't take into account dependencies
between modules.

Comments (0)

Files changed (5)

 
 import serenity.Serenity;
 import serenity.database.Sqlite;
-import serenity.SqlitePrinter;
 
 import controllers;
 
 
 int main(char[][] args)
 {
+    /// Set logging type and level
+    Log.type = Log.Type.Stderr;
+    Log.level = Log.Level.Trace;
+
+    /// Create a new SQLite Database using the given file
+    auto db = new SqliteDatabase("/home/robert/projects/serenity/test.db");
     scope(exit) Database.finalize();
-    auto db = new SqliteDatabase("/home/robert/projects/serenity/test.db");
     Database.addDatabase(db);
+
+    /// Set up routing
     Router.addRoutes([
                         "/"[]                           : "example/Home"[],
                         "/[plugin]"                     : "[plugin]/Default",
                         "/[plugin]/[controller]/[args]" : "[plugin]/[controller]/[args]"
                     ]);
     Router.errorRoute("example", "Error");
+
+    /// Launch Serenity
     Serenity.setNumberOfThreads(2, 4);
     return Serenity.exec(args);
 }
 
 rm -f lib/libserenity.a
 for f in $(find serenity -name \*.d); do
-    if [[ $f -nt .obj/$(dirname $f)/$(basename $f .d).o ]]; then
-        $DC -unittest $ARGS $f $OD.obj -op || exit 1
-    fi
+    $DC -unittest $ARGS $f $OD.obj -op || exit 1
 done
 ar rs lib/libserenity.a $(find .obj -name \*.o) || exit 1
 
 
 rm -f lib/libserenity-example.a
 for f in $(find example/controllers/ -name \*.d); do
-    if [[ $f -nt .obj/$(dirname $f)/$(basename $f .d).o ]]; then
-        $DC $ARGS $f $OD.obj -op
-    fi
+    $DC $ARGS $f $OD.obj -op
     m=$(basename $f .d | perl -pe 's|/|.|g')
     echo "import example.controllers.$m;" >> controllers.d
 done
 for f in $(find example/models/ -name \*.d); do
-    if [[ $f -nt .obj/$(dirname $f)/$(basename $f .d).o ]]; then
-        $DC $ARGS $f $OD.obj -op || exit 1
-    fi
+    $DC $ARGS $f $OD.obj -op || exit 1
 done
 ar rs lib/libserenity-example.a $(find .obj -name \*.o) || exit 1
 

serenity/Controller.d

 module serenity.Controller;
 
 public import serenity.HtmlDocument;
+import serenity.Log;
 import serenity.Model;
 import serenity.Response : Headers;
 
 public import tango.core.RuntimeTraits : isDerived;
 
+/**
+ * Register a class as a controller
+ *
+ * Examples:
+ * ----
+ *  class MyController : Controller
+ *  {
+ *      mixin registerController!(MyController);
+ *  }
+ * ----
+ */
 template registerController(T)
 {
     static this()
     }
 }
 
+/**
+ * Register a class as a controller with a given model
+ *
+ * This is a wrapper around registerController!(T) to add in convinience
+ * methods for using models.
+ * Examples:
+ * ----
+ *  import myPlugin.models.MyModel;
+ *
+ *  class MyController : Controller
+ *  {
+ *      mixin registerController!(MyController, MyModel);
+ *      void myMethod()
+ *      {
+ *          foreach (item; model.getItems())
+ *          {
+ *              /// Do something
+ *          }
+ *      }
+ *  }
+ * ----
+ * See_Also:
+ *  example/controllers/Home.d
+ */
 template registerController(T, M : Model)
 {
     mixin registerController!(T);
     {
         _model = new M;
     }
-    M model()
+
+    protected M model()
     {
         return _model;
     }
 }
 
+/**
+ * Thrown when Controller not found
+ */
 class ControllerNotFoundException : Exception
 {
     this(char[] msg)
     }
 }
 
+/**
+ * Thrown when the specified Controller is not derived from Controller
+ */
 class InvalidControllerException : Exception
 {
     this(char[] msg)
     }
 }
 
+/**
+ * Represents a controller, where logic is implemented in a plugin
+ *
+ * All controllers should inherit from this
+ */
 abstract class Controller
 {
     private static ClassInfo[char[]] mControllers;
     protected char[][] mArguments;
     private Headers mHeaders;
+    private Logger mLog;
     private ushort mResponseCode = 200;
 
+    /**
+     * Create an instance of the given controller
+     *
+     * Params:
+     *  plugin   = The plugin containing the controller
+     *  subClass = The class name of the controller. This should the the same
+     *             as the module name. Only one controller may exist per file.
+     *  args     = Arguments to pass to the class
+     *  code     = The default response code for the controller if an error has
+     *             already occurred
+     * Throws:
+     *  ControllerNotFoundException when the given controller does not exist
+     * Returns:
+     *  An instance of the requested controller
+     */
     public static Controller create(char[] plugin, char[] subClass, char[][] args, ushort code=200)
     {
         char[] cname = plugin ~ ".controllers." ~ subClass ~ '.' ~ subClass;
             throw new ControllerNotFoundException("Controller not found: " ~ cname);
         }
         auto controller = cast(typeof(this))ci.create();
+        controller.mLog = Log.getLogger(cname);
         controller.mArguments = args;
         controller.mHeaders = new Headers;
         controller.mHeaders["Content-Type"] = "text/html; charset=utf-8";
         return controller;
     }
 
+    /**
+     * Check if the given controller exists
+     *
+     * Params:
+     *  plugin   = Name of the plugin containing the controller
+     *  subClass = Name of the controller
+     * Returns:
+     *  true if the controller exists
+     */
     public static bool find(char[] plugin, char[] subClass)
     {
         char[] cname = plugin ~ ".controllers." ~ subClass ~ '.' ~ subClass;
         return ci !is null;
     }
 
+    /**
+     * Register the given controller
+     *
+     * This should not be called directly, this is handled by registerController!(T)
+     * Params:
+     *  ci = ClassInfo of the controller to register
+     * Throws:
+     *  InvalidControllerException when the given controller does not extend
+     *  Controller
+     */
     public static void register(ClassInfo ci)
     {
         if (!isDerived(ci, this.classinfo))
         mControllers[ci.name] = ci;
     }
 
+    /**
+     * Return the logger for the current controller
+     *
+     * Examples:
+     * ----
+     *  void myMethod()
+     *  {
+     *      if (log.info) log.info("myMethod()");
+     *  }
+     * ----
+     * Returns:
+     *  Instance of Logger for the current controller
+     */
+    protected Logger log()
+    {
+        return mLog;
+    }
+
+    /**
+     * Get the HTTP response code of the given controller
+     *
+     * Returns:
+     *  HTTP response code for this controller
+     */
     public ushort getResponseCode()
     {
         return mResponseCode;
     }
 
+    /**
+     * Set the HTTP response code for this controller
+     *
+     * Examples:
+     * ----
+     *  HtmlDocument view()
+     *  {
+     *      ...
+     *      /// An error occurred
+     *      setResponseCode(500);
+     *      ...
+     *  }
+     * ----
+     */
     protected void setResponseCode(ushort code)
     {
         mResponseCode = code;
     }
 
+    /**
+     * Return a list of headers set by this controller
+     *
+     * Returns:
+     *  The headers set by this controller
+     */
     public Headers getHeaders()
     {
         return mHeaders;
     }
 
+    /**
+     * Set HTTP headers for this controller
+     *
+     * Examples:
+     * ----
+     *  HtmlDocument view()
+     *  {
+     *      ...
+     *      /// May as well expire the content at this point
+     *      setHeader("Expires", "Fri, 21 Dec 2012 00:00:00 GMT");
+     *      ...
+     *  }
+     * ----
+     */
     protected void setHeader(char[] name, char[] value)
     {
         mHeaders[name] = value;
     }
 
+    /**
+     * All controllers should implement this method
+     *
+     * Examples:
+     * ----
+     *  class MyController : Controller
+     *  {
+     *      mixin registerController!(MyController);
+     *      HtmlDocument view()
+     *      {
+     *          auto doc = new HtmlDocument;
+     *          // Do something here
+     *          return doc;
+     *      }
+     *  }
+     * ----
+     */
     abstract public HtmlDocument view();
 }
 import tango.util.log.LayoutDate;
 import tango.util.log.Log : TangoLog = Log;
 public import tango.util.log.Log : Logger;
+
 static class Log
 {
     /// Logging style

serenity/Serenity.d

 import serenity.Controller;
 import serenity.Dispatcher;
 import serenity.HtmlPrinter;
-import serenity.Log;
 import serenity.Request;
 
+public import serenity.Log;
 public import serenity.Router;
 
 import tango.core.Variant;
 import tango.io.stream.Format;
 import tango.sys.Environment;
 import tango.text.convert.Layout;
+import tango.time.StopWatch;
 
 class Serenity : FastCGIApplication
 {
     private static char[][] mArgs;
     private static Dispatcher mDispatcher;
+    private static Logger log;
     private static ThreadPool!(Variant[]) mPool;
     private static size_t mPoolThreads = 1;
     private static size_t mFcgiThreads = 0;
     private FormatOutput!(char) mStdout;
     private FormatOutput!(char) mStderr;
 
+    /**
+     * Create a new instance of the application
+     *
+     * Used by FastCGId, should not be used by applications
+     */
     this (int id, FastCGIRequest r)
     {
         auto layout = new Layout!(char)();
         
         mStdout = new FormatOutput!(char)(layout, r.stdout);
         mStderr = new FormatOutput!(char)(layout, r.stderr);
-
-        Log.setStreams(mStdout, mStderr);
     }
 
+    /**
+     * Append a new job to Serenity's thread pool
+     *
+     * Examples:
+     * ----
+     *  void doExpensiveTask(int id)
+     *  {
+     *      assert(id == 4);
+     *      // Do something intensive here
+     *  }
+     *  /// Call doExpensiveTask(4) in a seperate thread
+     *  Serenity.poolAppend(&doExpensiveTask, 4);
+     * ----
+     * Params:
+     *  dg = delegate to run on a separate thread
+     *  args = list of arguments to pass to the delegate
+     */
     public static void poolAppend(U...)(void delegate(U) dg, U args)
     {
         Variant[] dgArgs;
         mFcgiThreads = fcgi;
     }
 
+    /**
+     * Execute Serenity
+     *
+     * This method is used to execute Serenity
+     * Examples:
+     * ----
+     *  int main(char[][] args)
+     *  {
+     *      /// Serenity should be configured here
+     *
+     *      /// Execute Serenity
+     *      return Serenity.exec(args);
+     *  }
+     * ----
+     * See_Also:
+     *  bootstrap.d
+     * Params:
+     *  args = Command line arguments
+     * Returns:
+     *  Zero (0) on successful termination, non-zero on error 
+     */
     public static int exec(char[][] args)
     {
         mDispatcher = new Dispatcher;
         mArgs = args;
+        log = Log.getLogger("serenity.Serenity");
         mPool = new ThreadPool!(Variant[])(mPoolThreads);
         return typeof(super).loop!(typeof(this))(null, false, mFcgiThreads == 0 ? threadsPerCPU() : mFcgiThreads);
     }
 
-    public static char[][] getCliArgs()
-    {
-        return mArgs.dup;
-    }
-
+    /**
+     * Handle a request
+     *
+     * This is the main entry point for any request handled by Serenity. This
+     * can be called multiple times simoultaneously depending on how many
+     * FastCGI threads have been allocated using Serenity.setNumberOfThreads().
+     *
+     * Returns:
+     *  Zero (0) on success, one (1) otherwise
+     */
     public int run()
     {
+        StopWatch sw;
         try
         {
+            if (log.info) log.info("Accepting new request");
+            sw.start;
+            
+            /// Handle the request
             auto request = new Request(request().args(), mArgs);
             auto response = mDispatcher.dispatch(request);
             response.send(mStdout);
+            
+            sw.stop;
+            if (log.info) log.info("Request complete in {}µs", sw.microsec);
             return 0;
         }
         catch (Exception e)
         {
+            sw.stop;
             e.writeOut((char[] str) { mStderr(str); });
             return 1;
         }
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.