diff --git a/.npmignore b/.npmignore index 931f42576..692298c9b 100644 --- a/.npmignore +++ b/.npmignore @@ -5,6 +5,7 @@ *.bak *~ /tests +!/tests/harness/lib/yuitest/java/build arrowreport artifacts examples diff --git a/docs/dev_guide/api_overview/index.rst b/docs/dev_guide/api_overview/index.rst index b55e69908..8fcc879f3 100644 --- a/docs/dev_guide/api_overview/index.rst +++ b/docs/dev_guide/api_overview/index.rst @@ -13,17 +13,13 @@ The API contains the following five modules: features from within a controller function. - **Addons** - extensions that provide functionality that lives both on the server and/or client. Each addon provides additional functions through a namespace that is attached directly to the - ``Action Context`` object available in every controller function. + ``Action Context`` object available when required in a controller. - **CommonLibs** - is a utility library containing methods to handle cookies, access input parameters, and make REST calls. - **MojitoClient** - is the client-side Mojito runtime module containing methods that allow inter-mojit communication through the ``mojitProxy`` object. - **MojitServer** - is the module that provides access to the Mojito server. - -Table of Contents -################# - .. toctree:: :maxdepth: 2 diff --git a/docs/dev_guide/api_overview/mojito_action_context.rst b/docs/dev_guide/api_overview/mojito_action_context.rst index 9fa7efc0b..b2371dd29 100644 --- a/docs/dev_guide/api_overview/mojito_action_context.rst +++ b/docs/dev_guide/api_overview/mojito_action_context.rst @@ -1,18 +1,18 @@ - - ============== Action Context ============== -The Action Context is an essential element of the Mojito framework that gives you access to the -frameworks features from within a controller function. To use the Action Context, you create an -instance of the ``ActionContext`` class, which we will call ``ac`` for short. From ``ac``, you can -call methods to execute mojit actions within either a server or client context. See the -`ActionContext Class <../../api/classes/ActionContext.html>`_ for the methods available from ``ac``. +The Action Context is an essential element of the Mojito framework that gives you access +to the frameworks features from within a controller function. To use the Action Context, +you create an instance of the ``ActionContext`` class, which we will call ``ac`` for +short. From ``ac``, you can call methods to execute mojit actions within either a server +or client context. See the `ActionContext Class <../../api/classes/ActionContext.html>`_ +for the methods available from ``ac``. -One of the most common methods used from an instance of the ``ActionContext`` class is ``done``, -which lets you pass data from the controller to a view. In the example ``controller.server.js`` below, -the ``done`` method sends the ``data`` object to the ``index`` template. +One of the most common methods used from an instance of the ``ActionContext`` class is +``done``, which lets you pass data from the controller to a view. In the example +``controller.server.js`` below, the ``done`` method sends the ``data`` object to the +``index`` template. .. code-block:: javascript @@ -29,9 +29,6 @@ the ``done`` method sends the ``data`` object to the ``index`` template. * @constructor */ Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, /** * Method corresponding to the 'index' action. * diff --git a/docs/dev_guide/api_overview/mojito_addons.rst b/docs/dev_guide/api_overview/mojito_addons.rst index aadfdb1f5..efdb83b40 100644 --- a/docs/dev_guide/api_overview/mojito_addons.rst +++ b/docs/dev_guide/api_overview/mojito_addons.rst @@ -1,15 +1,14 @@ +===================== +Action Context Addons +===================== +The Action Context uses a mechanism called addons to provide functionality that lives both +on the server and client. Each addon provides additional functions through a namespacing +object, which is appended to the ``ActionContext`` object that is available in every +controller function. See the `ActionContext Class <../../api/classes/ActionContext.html>`_ +for the addon classes. -====== -Addons -====== - -The Action Context uses a mechanism called addons to provide functionality that lives both on the -server and client. Each addon provides additional functions through a namespacing object, -which is appended to the ``ActionContext`` object that is available in every controller function. -See the `ActionContext Class <../../api/classes/ActionContext.html>`_ for the addon classes. - -Addons allow you to do the following: +The Action Context addons allow you to do the following: - access assets, such as CSS and JavaScript files - get configuration information @@ -19,21 +18,74 @@ Addons allow you to do the following: - get and set HTTP headers - create URLs + +.. _mojito_addons-syntax: + Syntax -###### +====== -Using the ActionContext object ``ac``, you would call a ``{method}`` from an ``{addon}`` with the -following syntax: +Using the ``ActionContext`` object ``ac``, you would call a ``{method}`` from an +``{addon}`` with the following syntax: ``ac.{addon}.{method}`` -For example, to get all of the query string parameters, you would use the ``Params`` addon with the -``url`` method as seen here: +For example, to get all of the query string parameters, you would use the ``Params`` addon +with the ``url`` method as seen here: ``ac.params.url()`` + +.. _addons-requiring: + +Requiring Addons +================ + +Prior to version 0.5.0, Mojito attached addons to the ``ActionContext`` object for +every HTTP request and mojit instance. As a result, you were able to use +any of the Action Context addons by default. + +In Mojito versions 0.5.0 and later, you need to explicitly require an addon before you +can use it. You require an addon by including an associated string in the +``requires`` array of your controller. For example, in the controller below, +the ``Params`` addon is required by adding the string ``'mojito-params-addon'`` to the +``requires`` array. + + +.. code-block:: javascript + + YUI.add('Foo', function(Y, NAME) { + Y.namespace('mojito.controllers')[NAME] = { + index: function(ac) { + var all_params = ac.params.all(); + } + }; + // Require the addon by adding the param name to the requires array + }, '0.0.1', {requires: ['mojito', 'mojito-params-addon']}); + +The list below shows what strings are used to require addons. + +- ``Assets`` addon - ``requires ['mojito-assets-addon']`` +- ``Composite`` addon - ``requires ['mojito-composite-addon']`` +- ``Config`` addon - ``requires ['mojito-config-addon']`` +- ``Cookies`` addon - ``requires ['mojito-cookie-addon']`` +- ``Http`` addon - ``requires ['mojito-http-addon']`` +- ``Intl`` addon - ``requires ['mojito-intl-addon']`` +- ``Params`` addon - ``requires ['mojito-params-addon']`` +- ``Url`` addon - ``requires ['mojito-url-addon']`` + + +.. note:: + To run older applications with Mojito v0.5.0 and later, you will need to + modify your controllers so that the ActionContext addons that are being + used are required. The most common addons are ``Config``, ``Params``, ``Url``, + and ``Assets``. + + + +.. _mojito_addons-exs: + Addon Examples -############## +============== The following code examples use the addons in parentheses: @@ -44,8 +96,11 @@ The following code examples use the addons in parentheses: - `Internationalizing Your Application <../code_exs/i18n_apps.html>`_ (``Intl``) - `Using Multiple Mojits <../code_exs/multiple_mojits.html>`_ (``Composite``) + +.. _mojito_addons-create: + Creating Addons -############### +=============== Because customized addons are not part of the standard API, but an extension of the API, the instructions for creating addons can be found in diff --git a/docs/dev_guide/api_overview/mojito_client_obj.rst b/docs/dev_guide/api_overview/mojito_client_obj.rst index d4417759f..376b61247 100644 --- a/docs/dev_guide/api_overview/mojito_client_obj.rst +++ b/docs/dev_guide/api_overview/mojito_client_obj.rst @@ -1,5 +1,3 @@ - - ============= Client Object ============= @@ -9,8 +7,10 @@ created. The ``client`` object can be used to pause and resume mojits running wi See `Class Y.mojito.Client <../../api/classes/Y.mojito.Client.html>`_ in the `Mojito API Reference <../../api/>`_ for more details. +.. _mojito_client_obj-pause: + Pausing Mojits -############## +============== From the ``client`` object, you call the ``pause`` method as seen below to prevent any code from executing outside of the individual binders (within the Mojito framework) and to call ``onPause()`` @@ -18,8 +18,10 @@ on all binders. ``Y.mojito.client.pause()`` +.. _mojito_client_obj-resume: + Resuming Mojits -############### +=============== From the ``client`` object, you call the ``resume`` method as seen below to immediately execute all cached operations and notify all of the binders through the ``onResume`` function. diff --git a/docs/dev_guide/api_overview/mojito_rest_lib.rst b/docs/dev_guide/api_overview/mojito_rest_lib.rst index 85b979156..778a28d08 100644 --- a/docs/dev_guide/api_overview/mojito_rest_lib.rst +++ b/docs/dev_guide/api_overview/mojito_rest_lib.rst @@ -1,5 +1,3 @@ - - ============ REST Library ============ @@ -8,11 +6,13 @@ Mojito has a library to make it easier to make a REST calls to Web services from implementation details, see `Class Y.mojito.lib.REST <../../api/classes/Y.mojito.lib.REST.html>`_ in the Mojito API documentation. +.. _mojito_rest_lib-incl: + Including Library -################# +================= -To use the REST library, include the string 'mojito-rest-lib' in the ``requires`` array, which -instructs YUI to load the library. Once the library is loaded, you can use +To use the REST library, include the string 'mojito-rest-lib' in the ``requires`` array, +which instructs YUI to load the library. Once the library is loaded, you can use `Y.mojito.lib.REST <../../api/classes/Y.mojito.lib.REST.html>`_ to make REST calls.. .. code-block:: javascript @@ -25,8 +25,11 @@ instructs YUI to load the library. Once the library is loaded, you can use // Ask YUI to load the library w/ 'mojito-rest-lib'. }, '0.0.1', {requires: ['mojito', 'mojito-rest-lib']}); + +.. _mojito_rest_lib-ex: + Example -####### +======= In the model for the ``recipeSearch`` mojit below, the REST library is used to make a GET call to the Recipe Puppy API. @@ -35,9 +38,6 @@ the Recipe Puppy API. YUI.add('ProductSearchModel', function(Y, NAME) { Y.namespace('mojito.models')[NAME] = { - init: function(config) { - this.config = config; - }, recipeSearch: function(count, cb) { var url = 'http://www.recipepuppy.com/api/'; var params = { diff --git a/docs/dev_guide/code_exs/adding_assets.rst b/docs/dev_guide/code_exs/adding_assets.rst index 636a7de48..cb01f5212 100644 --- a/docs/dev_guide/code_exs/adding_assets.rst +++ b/docs/dev_guide/code_exs/adding_assets.rst @@ -2,10 +2,16 @@ Adding CSS ========== +.. raw:: html + + Time Estimate: 10 minutes   Difficulty: Beginner + **Time Estimate:** 10 minutes **Difficulty:** Beginner +.. _code_exs_css-summary: + Summary ======= @@ -16,12 +22,14 @@ The following topics will be covered: - configuring an application to have assets - including assets in the template +.. _code_exs_css-notes: + Implementation Notes ==================== -Each application has an ``assets`` directory for placing global CSS files that can be accessed by -all of your mojits. Each mojit has its own ``assets`` directory for local CSS files that are only -accessible by the mojit. +Each application has an ``assets`` directory for placing global CSS files that can be +accessed by all of your mojits. Each mojit has its own ``assets`` directory for local +CSS files that are only accessible by the mojit. The global assets are located in the ``{app_dir}/assets`` directory as shown here: @@ -36,8 +44,8 @@ The global assets are located in the ``{app_dir}/assets`` directory as shown her |-- routes.json |-- server.js -In the ``simple`` mojit below, you see the local ``assets`` directory for CSS files only available -to the ``simple`` mojit: +In the ``simple`` mojit below, you see the local ``assets`` directory for CSS files only +available to the ``simple`` mojit: :: @@ -52,8 +60,8 @@ to the ``simple`` mojit: |-- tests/ `-- views/ -This code example only uses local CSS, so the ``simple.css`` file is placed in the ``assets`` -directory under the ``simple`` mojit. +This code example only uses local CSS, so the ``simple.css`` file is placed in the +``assets`` directory under the ``simple`` mojit. .. code-block:: css @@ -67,18 +75,18 @@ directory under the ``simple`` mojit. } .toolbar li { display:inline; } -The CSS files in the mojit ``assets`` directory can be accessed in the template using the following -path syntax: +The CSS files in the mojit ``assets`` directory can be accessed in the template using the +following path syntax: ``/static/{mojit}/assets/{css_file}.css`` -This code example uses the ``simple`` mojit and the ``simple.css`` asset. To access ``simple.css``, -you would use the following path: +This code example uses the ``simple`` mojit and the ``simple.css`` asset. To access +``simple.css``, you would use the following path: ``/static/simple/assets/simple.css`` -The ``index.hb.html`` template below includes ``simple.css`` from the ``assets`` directory using the -path above. +The ``index.hb.html`` template below includes ``simple.css`` from the ``assets`` directory +using the path above. .. code-block:: html @@ -107,15 +115,19 @@ path above. -To access the global assets for the application, you use a similar syntax, replacing the mojit name -with the application name. Thus, if the application name is ``simple_assets`` and ``simple.css`` -is in ``simple_assets/assets/``, you would access ``simple.css`` with the following path: +To access the global assets for the application, you use a similar syntax, replacing the +mojit name with the application name. Thus, if the application name is ``simple_assets`` +and ``simple.css`` is in ``simple_assets/assets/``, you would access ``simple.css`` with +the following path: ``/static/simple_assets/assets/simple.css`` -.. note:: For the purpose of simplifying this code example, the ``setColor`` function was hardcoded - into the template. In your Mojito applications, you should avoid mixing the business and - presentation logic of your application by hardcoding JavaScript into your template. +.. note:: For the purpose of simplifying this code example, the ``setColor`` function was + hardcoded into the template. In your Mojito applications, you should avoid + mixing the business and presentation logic of your application by hardcoding + JavaScript into your template. + +.. _code_exs_css-setup: Setting Up this Example ======================= @@ -132,7 +144,8 @@ To create and run ``simple_assets``: ``$ mojito create mojit simple`` -#. To configure your application to use the ``simple`` mojit, replace the code in ``application.json`` with the following: +#. To configure your application to use the ``simple`` mojit, replace the code in + ``application.json`` with the following: .. code-block:: javascript @@ -147,7 +160,8 @@ To create and run ``simple_assets``: } ] -#. To configure routing, create the file ``routes.json`` with the following: +#. To configure routing, replace the code of the file ``routes.json`` with the + following: .. code-block:: javascript @@ -167,7 +181,8 @@ To create and run ``simple_assets``: ``$ cd mojits/simple`` -#. Modify your controller to pass an array of objects to the template by replacing the code in ``controller.server.js`` with the following: +#. Modify your controller to pass an array of objects to the template by replacing the + code in ``controller.server.js`` with the following: .. code-block:: javascript @@ -184,14 +199,12 @@ To create and run ``simple_assets``: * @constructor */ Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, - /** - * Method corresponding to the 'index' action. - * @param ac {Object} The action context that - * provides access to the Mojito API. - */ + + /** + * Method corresponding to the 'index' action. + * @param ac {Object} The action context that + * provides access to the Mojito API. + */ index: function(ac) { var data = { title: "Simple Assets", @@ -208,8 +221,8 @@ To create and run ``simple_assets``: }; }, '0.0.1', {requires: []}); -#. Include the assets in your template by replacing the code in ``views/index.hb.html`` with the - following: +#. Include the assets in your template by replacing the code in ``views/index.hb.html`` + with the following: .. code-block:: html @@ -259,6 +272,8 @@ To create and run ``simple_assets``: http://localhost:8666 +.. _code_exs_css-src: + Source Code =========== diff --git a/docs/dev_guide/code_exs/app_config.rst b/docs/dev_guide/code_exs/app_config.rst index 80975e4ec..31983218b 100644 --- a/docs/dev_guide/code_exs/app_config.rst +++ b/docs/dev_guide/code_exs/app_config.rst @@ -6,16 +6,21 @@ Basic Configuring of Applications **Difficulty Level:** Beginning +.. _code_exs_basic_config-summary: + Summary ======= This example shows how to configure a mojit and the routing for your application. +.. _code_exs_basic_config-notes: + Implementation Notes ==================== -The ``application.json`` file is used to specify the mojits that your application can use. The -example ``application.json`` below specifies that the application use the mojit ``SimpleMojit``. +The ``application.json`` file is used to specify the mojits that your application can use. +The example ``application.json`` below specifies that the application use the mojit +``SimpleMojit``. .. code-block:: javascript @@ -30,9 +35,9 @@ example ``application.json`` below specifies that the application use the mojit } ] -The routing configuration for Mojito applications is contained in ``routes.json``. In this example -``routes.json``, the Mojito server is told to call the ``index`` method in the controller when an -HTTP GET is called on the root path. +The routing configuration for Mojito applications is contained in ``routes.json``. +In this example ``routes.json``, the Mojito server is told to call the ``index`` +method in the controller when an HTTP GET is called on the root path. .. code-block:: javascript @@ -47,10 +52,12 @@ HTTP GET is called on the root path. } ] -The ``index`` method is a canned method in the controller when you create a mojit. To learn how to -create templates that get data from the controller, +The ``index`` method is a canned method in the controller when you create a +mojit. To learn how to create templates that get data from the controller, see `Creating a Simple View with Handlebars `_. +.. _code_exs_basic_config-setup: + Setting Up this Example ======================= @@ -63,8 +70,8 @@ To set up and run ``simple_config``: #. Create your mojit. ``$ mojito create mojit SimpleMojit`` -#. To specify that your application use ``SimpleMojit``, replace the code in ``application.json`` - with the following: +#. To specify that your application use ``SimpleMojit``, replace the code in + ``application.json`` with the following: .. code-block:: javascript @@ -101,6 +108,8 @@ To set up and run ``simple_config``: http://localhost:8666 +.. _code_exs_basic_config-src: + Source Code =========== diff --git a/docs/dev_guide/code_exs/assets.rst b/docs/dev_guide/code_exs/assets.rst index 81284bf7c..1df1b428c 100644 --- a/docs/dev_guide/code_exs/assets.rst +++ b/docs/dev_guide/code_exs/assets.rst @@ -2,7 +2,8 @@ Assets ====== -These examples show you how to add assets such as CSS and JavaScript to your application. +These examples show you how to add assets such as CSS and JavaScript to +your application. .. toctree:: :maxdepth: 1 diff --git a/docs/dev_guide/code_exs/binding_events.rst b/docs/dev_guide/code_exs/binding_events.rst index eaa8b7073..910ca8c1f 100644 --- a/docs/dev_guide/code_exs/binding_events.rst +++ b/docs/dev_guide/code_exs/binding_events.rst @@ -6,12 +6,16 @@ Binding Events **Difficulty Level:** Advanced +.. _code_exs_events-summary: + Summary ======= -This example shows how to bind events to a mojit, configure code to run on the client, and make AJAX -calls to the YQL Web service. The application listens for events and then makes AJAX calls to -YQL to get Flickr photo information. + +This example shows how to bind events to a mojit, configure code to run +on the client, and make AJAX calls to the YQL Web service. The application +listens for events and then makes AJAX calls to YQL to get Flickr photo +information. The following topics will be covered: @@ -20,24 +24,31 @@ The following topics will be covered: - binding events through the ``mojitProxy`` object - making AJAX calls to YQL from the binder -Prerequisite +.. _events_summary-req: + +Requirements ------------ You will need to `get a Flickr API key `_ to run this example. +.. _code_exs_events-notes: + Implementation Notes ==================== +.. _events_notes-client: + Configuring the Application to Run on the Client ------------------------------------------------ -Mojito lets you configure applications to run on either the server or client side. This example uses -binders that are deployed to the client, so we need to configure Mojito to deploy the application -to the client, where it will be executed by the browser. +Mojito lets you configure applications to run on either the server or client +side. This example uses binders that are deployed to the client, so we need +to configure Mojito to deploy the application to the client, where it will +be executed by the browser. -To configure Mojito to run on the client, you simply set the ``"deploy"`` property to ``true`` in -``application.json`` as seen below. +To configure Mojito to run on the client, you simply set the ``"deploy"`` +property to ``true`` in ``application.json`` as seen below. .. code-block:: javascript @@ -58,12 +69,14 @@ To configure Mojito to run on the client, you simply set the ``"deploy"`` proper } ] +.. _events_notes-data: + Getting Data with YQL in the Model ---------------------------------- -In the mojit model, the `YUI YQL Query Utility `_ is used to -get Flickr photo information. To access the utility in your model, specify ``'yql'`` in the -``requires`` array as seen in the code snippet below: +In the mojit model, the `YUI YQL Query Utility `_ +is used to get Flickr photo information. To access the utility in your model, +specify ``'yql'`` in the ``requires`` array as seen in the code snippet below: .. code-block:: javascript @@ -73,10 +86,10 @@ get Flickr photo information. To access the utility in your model, specify ``'yq ... }, '0.0.1', {requires: ['yql']}); -This code example uses the ``flickr.photos.search`` table to get information for photos that have a -title, description, or tags containing a string. For example, the YQL statement below returns Flickr -photo information for those photos that have a title, description, or tags containing the string -"Manhattan". +This code example uses the ``flickr.photos.search`` table to get information +for photos that have a title, description, or tags containing a string. For +example, the YQL statement below returns Flickr photo information for those +photos that have a title, description, or tags containing the string "Manhattan". Copy the query below into the `YQL Console `_, replace ``{your_flickr_api_key}`` with your own Flickr API key, and then click **TEST** @@ -84,16 +97,18 @@ to see the returned XML response. ``select * from flickr.photos.search where text="Manhattan" and api_key="{your_flickr_api_key}"`` -The returned response contains photo information in the ``photo`` element. You extract the ``farm``, -``server``, ``id``, and ``secret`` attributes from each photo element to create the photo URI as -seen here: +The returned response contains photo information in the ``photo`` element. You extract the +``farm``, ``server``, ``id``, and ``secret`` attributes from each photo element to create +the photo URI as seen here: ``http://farm + {farm} + static.flickr.com/ + {server} + / + {id} + _ + {secret} + .jpg`` -In the ``model.js`` of ``PagerMojit`` shown below, the ``YQL`` function uses the YQL statement above -to get photo data, then parses the returned response to create the photo URIs. The model then wraps -the photo information in an object and stores those objects in the ``images`` array that is sent to -the controller through the ``callback`` function. + +In the ``model.server.js`` of ``PagerMojit`` shown below, the ``YQL`` function uses the YQL +statement above to get photo data, then parses the returned response to create the photo +URIs. The model then wraps the photo information in an object and stores those objects in +the ``images`` array that is sent to the controller through the ``callback`` function. + .. code-block:: javascript @@ -146,16 +161,20 @@ the controller through the ``callback`` function. }, '0.0.1', {requires: [ 'yql']}); -For a more detailed explanation about how to use YQL in your Mojito application, see `Calling YQL -from a Mojit `_. For more information about YQL, see the +For a more detailed explanation about how to use YQL in your Mojito application, see +`Calling YQL from a Mojit `_. For more information about YQL, see the `YQL Guide `_. +.. _events_notes-bind_events: + Binding Events -------------- This section will discuss the basics of binding events in Mojito and then look at the binder used in this code example. +.. _bind_events-basics: + Binder Basics ############# @@ -197,6 +216,8 @@ binder. For more information, see `Mojito Binders <../intro/mojito_binders.html> Y.mojito.registerEventBinder('AwesomeMojit', Binder); }, '0.0.1', {requires: ['mojito']}); +.. _bind_events-pagemojitbinder: + Examining the PageMojitBinder ############################# @@ -411,6 +432,10 @@ the ``requires`` array. } }; }, '0.0.1', {requires: ['yql', 'io', 'dump']}); + + +.. _events_notes-paging: + Using Paging ------------ @@ -473,42 +498,43 @@ calculate the index of the first photo to display: ... To get the photo data, the controller depends on the model to call YQL to query the -Flickr API. Using ``actionContext.models.{model_name}`` lets you get a reference to the -model. In this example controller, the model of the ``PagerMojit`` is accessed through -``actionContext.models.PageMojit``, allowing you to call ``getData`` and get the returned -data from YQL in the callback function. +Flickr API. Using ``actionContext.get({model_name})`` lets you get a reference to the +model. The example controller below calls the ``getData`` from the model +``PagerMojitModel`` with ``actionContext.models.get('PagerMojitModel').getData`, which +will get the returned data from YQL in the callback function. To use methods from models, you need +to require the model in the ``requires`` array of the controller. .. code-block:: javascript ... - index: function(actionContext) { - ... - var model = actionContext.models.PagerMojit; + index: function(actionContext) { + ... // Data is an array of images - model.getData('mojito', start, PAGE_SIZE, function(data) { - Y.log('DATA: ' + Y.dump(data)); - var theData = { - data: data, // images - hasLink: false, - prev: { - title: "prev" // opportunity to localize - }, - next: { - link: createLink(actionContext, {page: page+1}), - title: "next" + actionContext.models.get('PagerMojitModel').getData('mojito', start, PAGE_SIZE, function(data) { + Y.log('DATA: ' + Y.dump(data)); + var theData = { + data: data, // images + hasLink: false, + prev: { + title: "prev" // opportunity to localize }, - query: 'mojito' - }; - if (page > 1) { - theData.prev.link = createLink(actionContext, {page: page-1}); - theData.hasLink = true; - } - actionContext.done(theData); - }); - } - ... - }; - ... + next: { + link: createLink(actionContext, {page: page+1}), + title: "next" + }, + query: 'mojito' + }; + if (page > 1) { + theData.prev.link = createLink(actionContext, {page: page-1}); + theData.hasLink = true; + } + actionContext.done(theData); + }); + } + ... + }; + }, '0.0.1', {requires: ['dump', 'mojito-url-addon', 'mojito-params-addon', 'PagerMojitModel']}); + The URLs for the **prev** and **next** links are created by passing the mojit instance, the method, and the query string parameters to the ``make`` method from the ``Url`` addon. @@ -546,9 +572,7 @@ create URLs for the **next** and **prev** links. * @constructor */ Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, + index: function(actionContext) { var page = actionContext.params.getFromMerged('page'); var start; @@ -558,9 +582,8 @@ create URLs for the **next** and **prev** links. } // Page param is 1 based, but the model is 0 based start = (page - 1) * PAGE_SIZE; - var model = actionContext.models.PagerMojit; // Data is an array of images - model.getData('mojito', start, PAGE_SIZE, function(data) { + actionContext.models.get('PagerMojitModel').getData('mojito', start, PAGE_SIZE, function(data) { Y.log('DATA: ' + Y.dump(data)); var theData = { data: data, // images @@ -593,7 +616,9 @@ create URLs for the **next** and **prev** links. } return actionContext.url.make('frame', 'index', Y.QueryString.stringify(mergedParams)); } - }, '0.0.1', {requires: ['dump']}); + }, '0.0.1', {requires: ['dump', 'mojito-url-addon', 'mojito-params-addon', 'PagerMojitModel']}); + +.. _code_exs_events-setup: Setting Up this Example ======================= @@ -663,58 +688,57 @@ To set up and run ``binding_events``: * @class Controller * @constructor */ - Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, - index: function(actionContext) { - var page = actionContext.params.getFromMerged('page'); - var start; - page = parseInt(page) || 1; - if ((!page) || (page<1)) { - page = 1; - } - // Page param is 1 based, but the model is 0 based - start = (page - 1) * PAGE_SIZE; - var model = actionContext.models.PagerMojit; - // Data is an array of images - model.getData('mojito', start, PAGE_SIZE, function(data) { - Y.log('DATA: ' + Y.dump(data)); - var theData = { - data: data, // images - hasLink: false, - prev: { - title: "prev" // opportunity to localize - }, - next: { - link: createLink(actionContext, {page: page+1}), - title: "next" - }, - query: 'mojito' - }; - if (page > 1) { - theData.prev.link = createLink(actionContext, {page: page-1}); - theData.hasLink = true; + Y.namespace('mojito.controllers')[NAME] = { + + index: function(actionContext) { + var page = actionContext.params.getFromMerged('page'); + var start; + page = parseInt(page) || 1; + if ((!page) || (page<1)) { + page = 1; } - actionContext.done(theData); - }); - } - }; - // Generate the link to the next page based on: - // - mojit id - // - action - // - params - function createLink(actionContext, params) { - var mergedParams = Y.mojito.util.copy(actionContext.params.getFromMerged()); - for (var k in params) { - mergedParams[k] = params[k]; + // Page param is 1 based, but the model is 0 based + start = (page - 1) * PAGE_SIZE; + // Data is an array of images + actionContext.models.get('PagerMojitModel').getData('mojito', start, PAGE_SIZE, function(data) { + Y.log('DATA: ' + Y.dump(data)); + var theData = { + data: data, // images + hasLink: false, + prev: { + title: "prev" // opportunity to localize + }, + next: { + link: createLink(actionContext, {page: page+1}), + title: "next" + }, + query: 'mojito' + }; + if (page > 1) { + theData.prev.link = createLink(actionContext, {page: page-1}); + theData.hasLink = true; + } + actionContext.done(theData); + }); + } + }; + // Generate the link to the next page based on: + // - mojit id + // - action + // - params + function createLink(actionContext, params) { + var mergedParams = Y.mojito.util.copy(actionContext.params.getFromMerged()); + for (var k in params) { + mergedParams[k] = params[k]; + } + return actionContext.url.make('frame', 'index', Y.QueryString.stringify(mergedParams)); } - return actionContext.url.make('frame', 'index', Y.QueryString.stringify(mergedParams)); - } - }, '0.0.1', {requires: ['dump']}); + }, '0.0.1', {requires: ['dump', 'mojito-url-addon', 'mojito-params-addon', 'PagerMojitModel']}); + -#. To get Flickr photo information using YQL, replace the code in ``model.server.js`` with - the following: +#. To get Flickr photo information using YQL, create the file ``models/model.server.js`` with + the code below. Be sure to replace the ``'{your_flickr_api_key}'`` with your own + Flickr API key. .. code-block:: javascript @@ -730,9 +754,7 @@ To set up and run ``binding_events``: * @constructor */ Y.namespace('mojito.models')[NAME] = { - init: function(config) { - this.config = config; - }, + getData: function(query, start, count, callback) { var q = null; // Get Flickr API key: http://www.flickr.com/services/api/keys/apply/ @@ -768,7 +790,8 @@ To set up and run ``binding_events``: }, '0.0.1', {requires: ['yql']}); #. To create the binder for click events and invoke the ``index`` function of the - controller, replace the code in ``binders/index.js`` with the following: + controller, replace the code in ``binders/index.js`` with the code below. Again, + Be sure to replace the ``'{your_flickr_api_key}'`` with your own Flickr API key. .. code-block:: javascript @@ -914,6 +937,8 @@ To set up and run ``binding_events``: http://localhost:8666 +.. _code_exs_events-src: + Source Code =========== diff --git a/docs/dev_guide/code_exs/calling_yql.rst b/docs/dev_guide/code_exs/calling_yql.rst index 5bb391763..1021b8e8f 100644 --- a/docs/dev_guide/code_exs/calling_yql.rst +++ b/docs/dev_guide/code_exs/calling_yql.rst @@ -1,4 +1,3 @@ - ======================== Calling YQL from a Mojit ======================== @@ -7,12 +6,15 @@ Calling YQL from a Mojit **Difficulty Level:** Intermediate +.. _code_exs_yql-summary: + Summary ======= -This example shows how to use YQL to get Flickr images from a Mojito application. YQL allows you to -get data from many sources in the form of JSON, JSONP, and XML. For more information about YQL, see -the `YQL Guide `_. For this example, you will need to +This example shows how to use YQL to get Flickr images from a Mojito application. YQL +allows you to get data from many sources in the form of JSON, JSONP, and XML. For more +information about YQL, see the `YQL Guide `_. For +this example, you will need to `get a Flickr API key `_. The following topics will be covered: @@ -22,6 +24,9 @@ The following topics will be covered: - getting query string parameters with the ``Params`` addon - calling the YQL Web service with the `YQL Module of YUI `_ + +.. _code_exs_yql-notes: + Implementation Notes ==================== @@ -31,25 +36,27 @@ The following screenshot shows the grid of Flickr images retrieved by YQL. :height: 373px :width: 401px +.. _code_exs_yql-statement: + Forming the YQL Statement and Flickr Photo URI ---------------------------------------------- -The mojit model needs a method to access data. This code example uses YQL to access Flickr data, so -we need to form the YQL statement to get the Flickr image information. Because the response from -the YQL statement contains photo information and not the URIs to images, you also need to form the -URI scheme for Flickr photos. +The mojit model needs a method to access data. This code example uses YQL to access Flickr +data, so we need to form the YQL statement to get the Flickr image information. Because +the response from the YQL statement contains photo information and not the URIs to images, +you also need to form the URI scheme for Flickr photos. -To get photo data from Flickr, you use the YQL table ``flickr.photos.search``. This table allows you -to get photos that are associated with a string. In the YQL statement below, we use the table to -return Flickr photos whose title, description, or tags contain the text "muppet". Click on the YQL -statement to open the YQL Console, and then click the **TEST** button to see the returned XML -response. +To get photo data from Flickr, you use the YQL table ``flickr.photos.search``. This table +allows you to get photos that are associated with a string. In the YQL statement below, we +use the table to return Flickr photos whose title, description, or tags contain the text +"muppet". Click on the YQL statement to open the YQL Console, and then click the **TEST** +button to see the returned XML response. `select * from flickr.photos.search where text="muppets" and api_key="84921e87fb8f2fc338c3ff9bf51a412e" `_ -As you can see from the partial response from YQL below, the photo URIs are not returned, just -metadata about the photos. You need to extract metadata and use it to form the photo URIs to get -the photos. We'll look at the URI scheme for the photos next. +As you can see from the partial response from YQL below, the photo URIs are not returned, +just metadata about the photos. You need to extract metadata and use it to form the photo +URIs to get the photos. We'll look at the URI scheme for the photos next. .. code-block:: xml @@ -62,12 +69,15 @@ the photos. We'll look at the URI scheme for the photos next. -Using the ``farm``, ``server``, ``id``, ``secret``, and ``title`` attributes from the response, you -form the photo URIs using the following URI scheme: +Using the ``farm``, ``server``, ``id``, ``secret``, and ``title`` attributes from the +response, you form the photo URIs using the following URI scheme: ``http://farm + {farm} + static.flickr.com/ + {server} + / + {id} + _ + {secret} + .jpg`` -Having formed the YQL statement and the Flickr photo URI to get data, we can create the model. +Having formed the YQL statement and the Flickr photo URI to get data, we can create the +model. + +.. _code_exs_yql-model: Creating the Model ------------------ @@ -80,21 +90,21 @@ The mojit model for this code example does the following: - forms the photo URIs - passes photo information to the controller -In the example ``model.server.js`` below, the ``search`` function creates the YQL statement and -passes it to the ``YQL`` function made available by the +In the example ``model.server.js`` below, the ``search`` function creates the YQL +statement and passes it to the ``YQL`` function made available by the `YQL Module of YUI `_. -The ``YQL`` function makes the REST call to the YQL Web services, and the response is passed to an -anonymous function. This function extracts the fields from the response that are needed to -create the photo URIs and then stores those photo URIs, photo IDs, and titles in objects. These -objects are stored in the ``photos`` array and passed to the controller through the ``callback`` -function. +The ``YQL`` function makes the REST call to the YQL Web services, and the response is +passed to an anonymous function. This function extracts the fields from the response that +are needed to create the photo URIs and then stores those photo URIs, photo IDs, and +titles in objects. These objects are stored in the ``photos`` array and passed to the +controller through the ``callback`` function. .. code-block: javascript YUI.add('flickrModel', function(Y, NAME) { // Flickr requires an API key - var API_KEY = '84921e87fb8f2fc338c3ff9bf51a412e'; + var API_KEY = '{your_flickr_api_key}'; Y.namespace('mojito.models')[NAME] = { init: function(config) { this.config = config; @@ -146,12 +156,15 @@ function. }; }, '0.0.1', {requires: ['yql']}); -.. note:: If you are new to the Node.js world, when you create models for your applications, take - these words to heart: **DON'T WRITE BLOCKING CODE**. Models need to be asynchronous in - order to allow the rest of Mojito execution to continue, so you cannot call any model - functions synchronously. You must call them with a callback function to be executed when - the model receives its data. Because you don't know when the model is getting its data, - you have to assume that it may block. +.. note:: If you are new to the Node.js world, when you create models for your + applications, take these words to heart: **DON'T WRITE BLOCKING CODE**. Models + need to be asynchronous in order to allow the rest of Mojito execution to + continue, so you cannot call any model functions synchronously. You must call + them with a callback function to be executed when the model receives its data. + Because you don't know when the model is getting its data, you have to assume + that it may block. + +.. _code_exs_yql-call_model: Calling the Model from the Controller ------------------------------------- @@ -160,17 +173,19 @@ The controller in this code example performs the following functions: - gets the query string parameters using the `Params addon <../../api/classes/Params.common.html>`_ - passes the query string parameters to the ``search`` function of the model -- receives the ``photos`` array from the ``search`` function and sends an object to the template +- receives the ``photos`` array from the ``search`` function and sends an object to the + template -The ``index`` function in the ``controller.server.js`` below uses the ``getFromUrl`` method of the -``Params`` addon to get the query string parameters to form the YQL statement. The YQL Statement and -the `paging and limit parameters `_ are then +The ``index`` function in the ``controller.server.js`` below uses the ``getFromUrl`` +method of the ``Params`` addon to get the query string parameters to form the YQL +statement. The YQL Statement and the +`paging and limit parameters `_ are then passed to the ``search`` function of the model. To access model functions from the controller, you use the Action Context (``ac``) object with the following syntax: ``ac.models.get({model_name})``. This code example uses the ``flickr`` mojit, so to access the model from the controller, you would use ``ac.models.get('flickrModel')`` as seen in the -``model.server.js`` below. Once the callback function passed to ``search`` returns the array of +``controller.server.js`` below. Once the callback function passed to ``search`` returns the array of photo objects, the ``done`` method sends the ``photos`` array and the query string parameters to the ``index`` template. @@ -178,9 +193,7 @@ the ``index`` template. YUI.add('flickr', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, + index: function(ac) { var q = ac.params.getFromUrl('q') || 'muppet', page = (ac.params.getFromUrl('page') || 0) / 1, @@ -188,18 +201,18 @@ the ``index`` template. start = page * count; var model = ac.models.get('flickrModel'); model.search (q, start, count, function(photos) { - ac.done ( - { - photos: photos, - page: page, - count: count, - start: start + ac.done ( + { + photos: photos, + page: page, + count: count, + start: start + }); }); - }); - } - }; - }, '0.0.1', {requires: [ - 'mojito-models-addon', + } + }; + }, '0.0.1', {requires: [ + 'mojito-models-addon', 'mojito-params-addon', 'flickrModel' ]}); @@ -211,14 +224,12 @@ To set up and run ``model_yql``: #. Create your application. ``$ mojito create app model_yql`` - #. Change to the application directory. #. Create your mojit. ``$ mojito create mojit flickr`` - -#. To specify that your application uses ``HTMLFrameMojit`` and the child ``flickr`` mojit, replace - the code in ``application.json`` with the following: +#. To specify that your application uses ``HTMLFrameMojit`` and the child ``flickr`` mojit, + replace the code in ``application.json`` with the following: .. code-block:: javascript @@ -246,8 +257,8 @@ To set up and run ``model_yql``: } ] -#. To configure the routing to call the ``index`` method an instance of ``HTMLFrameMojit``, replace - the code in ``routes.json`` with the following: +#. To configure the routing to call the ``index`` method an instance of ``HTMLFrameMojit``, + replace the code in ``routes.json`` with the following: .. code-block:: javascript @@ -263,15 +274,15 @@ To set up and run ``model_yql``: ] #. Change to ``mojits/flickr``. -#. Modify the mojit model to call YQL to get Flickr photos by replacing the code in - ``models/model.server.js`` with the following: +#. Modify the mojit model to call YQL to get Flickr photos by creating the model + ``models/model.server.js`` with the code below. .. code-block:: javascript YUI.add('flickrModel', function(Y, NAME) { - // Replace '{Flickr API Key}' with your own Flickr + // Replace '{your_flickr_api_key}' with your own Flickr // API key. - var API_KEY = '{Flickr API Key}'; + var API_KEY = '{your_flickr_api_key}'; Y.namespace('mojito.models')[NAME] = { init: function(config) { this.config = config; @@ -321,17 +332,17 @@ To set up and run ``model_yql``: }); } }; - }, '0.0.1', {requires: ['yql']}); + }, '0.0.1', {requires: ['mojito','yql']}); #. `Get a Flickr API key `_ and then replace the - string ``'{Flickr API Key}'`` in your model with your API key. + string ``'{your_flickr_api_key}'`` in your model with your API key. .. code-block:: javascript YUI.add('flickrModel', function(Y, NAME) { - // Replace '{Flickr API Key}' with your own Flickr + // Replace '{your_flickr_api_key}' with your own Flickr // API key. - var API_KEY = '{Flickr API Key}'; + var API_KEY = '{your_flickr_api_key}'; ... } @@ -343,9 +354,7 @@ To set up and run ``model_yql``: YUI.add('flickr', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, + index: function(ac) { // Use aliases to params addon // if they exist. @@ -373,7 +382,7 @@ To set up and run ``model_yql``: } }; }, '0.0.1', {requires: [ - 'mojito-models-addon', + 'mojito-models-addon', 'mojito-params-addon', 'flickrModel' ]}); @@ -426,6 +435,9 @@ To set up and run ``model_yql``: http://localhost:8666?q=mojito&size=50 + +.. _code_exs_yql-src: + Source Code =========== diff --git a/docs/dev_guide/code_exs/config.rst b/docs/dev_guide/code_exs/config.rst index ffdfbd990..dc8e0d859 100644 --- a/docs/dev_guide/code_exs/config.rst +++ b/docs/dev_guide/code_exs/config.rst @@ -3,8 +3,8 @@ Configuration ============= -These examples show you how to configure your applications, such as defining mojit instances, -routing paths, logging levels, and more. +These examples show you how to configure your applications, such as defining mojit +instances, routing paths, logging levels, and more. .. toctree:: :maxdepth: 1 diff --git a/docs/dev_guide/code_exs/cookies.rst b/docs/dev_guide/code_exs/cookies.rst index e51a35774..1a633b31e 100644 --- a/docs/dev_guide/code_exs/cookies.rst +++ b/docs/dev_guide/code_exs/cookies.rst @@ -6,6 +6,8 @@ Using Cookies **Difficulty Level:** Beginning +.. _code_exs_cookies-summary: + Summary ======= @@ -13,32 +15,32 @@ This example shows how to read and write cookies in a Mojito application. The following topics will be covered: -- using the `Params addon <../../api/classes/Params.common.html>`_ from the ``actionContext`` object +- using the `Params addon <../../api/classes/Params.common.html>`_ from the + ``actionContext`` object - getting and setting cookies from the mojit controller - using the `Cookie addon <../../api/classes/Cookie.server.html>`_ and the `YUI Cookie module `_ to get and set cookies +.. _code_exs_cookies-notes: + Implementation Notes ==================== To access many methods on the Mojito JavaScript library, you use `ActionContext addons <../../api/classes/ActionContext.html>`_. In this code example, the -`Cookie addon <../../api/classes/Cookie.server.html>`_ is used to call the methods ``getCookie`` -and ``setCookie`` to get and set cookies. +`Cookie addon <../../api/classes/Cookie.server.html>`_ is used to call the methods +``getCookie`` and ``setCookie`` to get and set cookies. -The ``index`` function in the ``controller.server.js`` below shows how to use ``cookie.get`` and -``cookie.set``. The ``cookie.set`` method also allows you to pass a third parameter that -contains the domain, the path, and the expiration date of the cookie. For those familiar with YUI 3, -these methods for getting and setting cookies should be familiar as Mojito uses the -`YUI 3 Cookie Module `_. +The ``index`` function in the ``controller.server.js`` below shows how to use ``cookie.get`` +and ``cookie.set``. The ``cookie.set`` method also allows you to pass a third parameter +that contains the domain, the path, and the expiration date of the cookie. For those +familiar with YUI 3, these methods for getting and setting cookies should be familiar as +Mojito uses the `YUI 3 Cookie Module `_. .. code-block:: javascript YUI.add('CookieMojit', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, index: function(actionContext) { var requestCookieValue = actionContext.cookie.get('request_cookie'); // Or use this API to set a session cookie @@ -52,21 +54,21 @@ these methods for getting and setting cookies should be familiar as Mojito uses ); } }; - }, '0.0.1', {requires: []}); + }, '0.0.1', {requires: ['mojito-cookie-addon']}); The code below from the ``index`` template interpolates the value of the variable ``{{request_cookie_value}}`` from the controller and uses the -`YUI Cookie module `_ to set and get a -cookie. To use the YUI Cookie module, first include the module with ``YUI().use`` and then call -``Y.Cookie.get`` and ``Y.Cookie.set``. +`YUI Cookie module `_ to set and +get a cookie. To use the YUI Cookie module, first include the module with ``YUI().use`` +and then call ``Y.Cookie.get`` and ``Y.Cookie.set``. .. code-block:: html

{{title}}

-

This is a demo showing how to read read cookies from browser, and how to write cookies to - browser from the Mojit.

+

This is a demo showing how to read read cookies from browser, and how to write + cookies to browser from the Mojit.

Value of request cookie sent by browser: {{request_cookie_value}}

@@ -87,6 +89,8 @@ cookie. To use the YUI Cookie module, first include the module with ``YUI().use` }); +.. _code_exs_cookies-setup: + Setting Up this Example ======================= @@ -99,8 +103,8 @@ To set up and run ``using_cookies``: #. Create your mojit. ``$ mojito create mojit CookieMojit`` -#. To configure your application to use the ``HTMLFrameMojit`` and its child mojit ``CookieMojit``, - replace the code in ``application.json`` with the following: +#. To configure your application to use the ``HTMLFrameMojit`` and its child mojit + ``CookieMojit``, replace the code in ``application.json`` with the following: .. code-block:: javascript @@ -131,6 +135,11 @@ To set up and run ``using_cookies``: "verbs": ["get"], "path": "/", "call": "frame.index" + }, + "example1": { + "verbs": ["get"], + "path": "/example1", + "call": "frame.example1" } } ] @@ -143,9 +152,7 @@ To set up and run ``using_cookies``: YUI.add('CookieMojit', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, + index: function(actionContext) { var requestCookieValue = actionContext.cookie.get('request_cookie'); // Or use this API to set a session cookie @@ -159,7 +166,7 @@ To set up and run ``using_cookies``: ); } }; - }, '0.0.1', {requires: []}); + }, '0.0.1', {requires: ['mojito-cookie-addon']}); #. To display the cookie values set in your controller, replace the code in ``views/index.hb.html`` with the following: @@ -198,6 +205,8 @@ To set up and run ``using_cookies``: http://localhost:8666 +.. _code_exs_cookies-src: + Source Code =========== diff --git a/docs/dev_guide/code_exs/dynamic_assets.rst b/docs/dev_guide/code_exs/dynamic_assets.rst index 493bdaa9a..8449268fd 100644 --- a/docs/dev_guide/code_exs/dynamic_assets.rst +++ b/docs/dev_guide/code_exs/dynamic_assets.rst @@ -6,18 +6,22 @@ Dynamically Adding CSS to Different Devices **Difficulty:** Intermediate +.. _code_exs_dynamic_css-summary: + Summary ======= -This example shows how to dynamically include assets (CSS) in the rendered template of a mojit. -The assets in this example are included in the rendered default template and +This example shows how to dynamically include assets (CSS) in the rendered template of a +mojit. The assets in this example are included in the rendered default template and device-specific templates. The following topics will be covered: - configuring an application to dynamically include assets -- using the ``addAssets`` method in the controller to dynamically add assets to both the rendered -default and device-specific templates +- using the ``addAssets`` method in the controller to dynamically add assets to both the + rendered default and device-specific templates + +.. _code_exs_dynamic_css-notes: Implementation Notes ==================== @@ -29,13 +33,14 @@ JavaScript. :height: 400px :width: 208px -The ``application.json`` in this code example configures Mojito to deploy mojit code to the client -and to use the ``HTMLFrameMojit``. To deploy mojit code to the client, you assign ``true`` to the -``deploy`` property as seen in the ``application.json`` below. The ``frame`` instance of -``HTMLFrameMojit`` becomes the parent mojit of the ``child`` instance of type ``device``. The -configurations for the context ``"device:iphone"`` define the identifier ``"iphone"`` -for file resources with the ``selector`` property, so Mojito will know to use the template -``index.iphone.hb.html`` if the context is ``"device:iphone"``. +The ``application.json`` in this code example configures Mojito to deploy mojit code to +the client and to use the ``HTMLFrameMojit``. To deploy mojit code to the client, you +assign ``true`` to the ``deploy`` property as seen in the ``application.json`` below. The +``frame`` instance of ``HTMLFrameMojit`` becomes the parent mojit of the ``child`` +instance of type ``device``. The configurations for the context ``"device:iphone"`` +define the identifier ``"iphone"`` for file resources with the ``selector`` property, +so Mojito will know to use the template ``index.iphone.hb.html`` if the context is +``"device:iphone"``. .. code-block:: javascript @@ -61,10 +66,11 @@ for file resources with the ``selector`` property, so Mojito will know to use th } ] -In the controller for the ``device`` mojit, the ``index`` function has to determine what device is -making the request and then attach the appropriate meta data and CSS. To determine the calling -device, you use the ``ActionContext`` object to access the ``device`` property of the ``context`` -object. Below is a partial ``ActionContext`` object that contains the ``context`` object: +In the controller for the ``device`` mojit, the ``index`` function has to determine what +device is making the request and then attach the appropriate meta data and CSS. To +determine the calling device, you use the ``ActionContext`` object to access the +``device`` property of the ``context`` object. Below is a partial ``ActionContext`` +object that contains the ``context`` object: .. code-block:: javascript @@ -85,18 +91,17 @@ object. Below is a partial ``ActionContext`` object that contains the ``context` } To dynamically add CSS and meta data from the controller, you use methods from the -`Assets addon <../../api/classes/Assets.common.html>`_. In the ``controller.server.js`` below, the -``index`` function determines the calling device using the ``context`` object seen above. -To add metadata for the iPhone, the ``addBlob`` method is called from the ``Assets`` addon. -The appropriate CSS file is dynamically attached to the template with ``ac.assets.addCss``. +`Assets addon <../../api/classes/Assets.common.html>`_. In the ``controller.server.js`` +below, the ``index`` function determines the calling device using the ``context`` object +seen above. To add metadata for the iPhone, the ``addBlob`` method is called from the +``Assets`` addon. The appropriate CSS file is dynamically attached to the template with +``ac.assets.addCss``. .. code-block:: javascript YUI.add('device', function(Y, NAME){ Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, + index: function(ac) { var device = ac.context.device, css = '/static/device/assets/simple'; if (device === 'iphone') { @@ -122,12 +127,13 @@ The appropriate CSS file is dynamically attached to the template with ``ac.asset }); } }; - }, '0.0.1', {requires: []}); + }, '0.0.1', {requires: ['mojito-assets-addon']}); -The ``index.iphone`` template below contains CSS for controlling the orientation of the page, -which is needed for displaying the page correctly on an iPhone. When the template is rendered, the -CSS is dynamically added, and the Handlebars expressions are replaced with values. If the device -making the call is an iPhone, the ``viewport`` meta data will also be added dynamically. +The ``index.iphone`` template below contains CSS for controlling the orientation of the +page, which is needed for displaying the page correctly on an iPhone. When the template is +rendered, the CSS is dynamically added, and the Handlebars expressions are replaced with +values. If the device making the call is an iPhone, the ``viewport`` meta data will also +be added dynamically. .. code-block:: html @@ -171,6 +177,8 @@ making the call is an iPhone, the ``viewport`` meta data will also be added dyna
+.. _code_exs_dynamic_css-exs: + Setting Up this Example ======================= @@ -183,8 +191,8 @@ To create and run ``device_assets``: #. Create your mojit. ``$ mojito create mojit device`` -#. To configure your application to use ``HTMLFrameMojit`` and include JavaScript, replace the code - in ``application.json`` with the following: +#. To configure your application to use ``HTMLFrameMojit`` and include JavaScript, + replace the code in ``application.json`` with the following: .. code-block:: javascript @@ -226,16 +234,14 @@ To create and run ``device_assets``: ] #. Change to ``mojits/device``. -#. Modify your controller to dynamically add assets to the rendered template by replacing the - code in ``controller.server.js`` with the following: +#. Modify your controller to dynamically add assets to the rendered template by replacing + the code in ``controller.server.js`` with the following: .. code-block:: javascript YUI.add('device', function(Y, NAME){ Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, + index: function(ac) { var device = ac.context.device, css = '/static/device/assets/simple'; if (device === 'iphone') { @@ -261,10 +267,10 @@ To create and run ``device_assets``: }); } }; - }, '0.0.1', {requires: []}); + }, '0.0.1', {requires: ['mojito-assets-addon']}); -#. To create the default ``index`` template, replace the code in ``views/index.hb.html`` with - the following: +#. To create the default ``index`` template, replace the code in ``views/index.hb.html`` + with the following: .. code-block:: html @@ -291,8 +297,8 @@ To create and run ``device_assets``: -#. To create the default iPhone template, create the file ``views/index.iphone.hb.html`` with - the following: +#. To create the default iPhone template, create the file ``views/index.iphone.hb.html`` + with the following: .. code-block:: html @@ -336,8 +342,8 @@ To create and run ``device_assets``:
-#. Create the file ``assets/simple.css`` for the CSS that is included in ``index.hb.html`` with the - following: +#. Create the file ``assets/simple.css`` for the CSS that is included in ``index.hb.html`` + with the following: .. code-block:: css @@ -385,11 +391,13 @@ To create and run ``device_assets``: #. To view your application, go to the URL: http://localhost:8666 -#. To see the page rendered for the iPhone, view the above URL from an iPhone or use the URL below - with the device parameter: +#. To see the page rendered for the iPhone, view the above URL from an iPhone or use the + URL below with the device parameter: http://localhost:8666?device=iphone +.. _code_exs_dynamic_css-src: + Source Code =========== diff --git a/docs/dev_guide/code_exs/events.rst b/docs/dev_guide/code_exs/events.rst index 46cba99cb..6cc154e6f 100644 --- a/docs/dev_guide/code_exs/events.rst +++ b/docs/dev_guide/code_exs/events.rst @@ -2,7 +2,8 @@ Events ====== -These examples show you how to create, bind, listen to, and use events in your applications. +These examples show you how to create, bind, listen to, and use events in your +applications. .. toctree:: :maxdepth: 1 diff --git a/docs/dev_guide/code_exs/framed_assets.rst b/docs/dev_guide/code_exs/framed_assets.rst index a6e2c4f65..bc239bf7f 100644 --- a/docs/dev_guide/code_exs/framed_assets.rst +++ b/docs/dev_guide/code_exs/framed_assets.rst @@ -6,28 +6,35 @@ Attaching Assets with HTMLFrameMojit **Difficulty:** Intermediate +.. _code_exs_frame_assets-summary: + Summary ======= -This example shows how to configure an application to use the HTML Frame Mojit (``HTMLFrameMojit``) -with predefined assets (CSS) that are attached to the rendered template of a mojit. +This example shows how to configure an application to use the HTML Frame Mojit +(``HTMLFrameMojit``) with predefined assets (CSS) that are attached to the rendered +template of a mojit. The following topics will be covered: - configuring the application to use the ``HTMLFrameMojit`` -- configuring the ``HTMLFrameMojit`` to automatically include assets in the rendered template +- configuring the ``HTMLFrameMojit`` to automatically include assets in the rendered + template + +.. _code_exs_frame_assets-notes: Implementation Notes ==================== -This example code's ``application.json``, shown below, configures the application to use the -HTML Frame Mojit and to include CSS assets. The ``HTMLFrameMojit`` creates the -HTML skeleton and includes the CSS in the ```` tag because of the ``"top"`` property. To -configure Mojito, place the CSS at the bottom, wrap the ``css`` array in the "bottom" property. -You can also include JavaScript by including the path to JavaScript files in a ``js`` array. If you -do not use the ``HTMLFrameMojit``, you have to explicitly include assets as -`static resources <../intro/mojito_static_resources.html>`_ in your template. To learn more about -the ``HTMLFrameMojit``, see the code example `Using the HTML Frame Mojit <./htmlframe_view.html>`_. +This example code's ``application.json``, shown below, configures the application to use +the HTML Frame Mojit and to include CSS assets. The ``HTMLFrameMojit`` creates the +HTML skeleton and includes the CSS in the ```` tag because of the ``"top"`` property. +To configure Mojito, place the CSS at the bottom, wrap the ``css`` array in the "bottom" +property. You can also include JavaScript by including the path to JavaScript files in a +``js`` array. If you do not use the ``HTMLFrameMojit``, you have to explicitly include +assets as `static resources <../intro/mojito_static_resources.html>`_ in your template. +To learn more about the ``HTMLFrameMojit``, see the code example +`Using the HTML Frame Mojit <./htmlframe_view.html>`_. .. code-block:: javascript @@ -54,16 +61,17 @@ the ``HTMLFrameMojit``, see the code example `Using the HTML Frame Mojit <./html } ] -The template ``index.hb.html`` below uses the asset ``index.css``, but you do not need to include -them in the file. If you use the same name for your CSS file as the name of your template and -place the CSS in the mojit ``assets`` directory, ``HTMLFrameMojit`` will automatically include the -assets in the ```` tag for you and then inject the rendered template into the ```` tag. +The template ``index.hb.html`` below uses the asset ``index.css``, but you do not need to +include them in the file. If you use the same name for your CSS file as the name of your +template and place the CSS in the mojit ``assets`` directory, ``HTMLFrameMojit`` will +automatically include the assets in the ```` tag for you and then inject the +rendered template into the ```` tag. -For example, the ``mojits/framed/assets/index.css`` file will automatically be included in the -```` tag of the rendered ``mojits/framed/views/index.hb.html`` template. When the -``index.hb.index`` template below is rendered, it will be embedded in an HTML skeleton that includes -a ````, ````, and ```` tags. If the ``/assets/index.css`` file exists, -it will automatically be injected into the ```` tag. +For example, the ``mojits/framed/assets/index.css`` file will automatically be included in +the ```` tag of the rendered ``mojits/framed/views/index.hb.html`` template. When the +``index.hb.index`` template below is rendered, it will be embedded in an HTML skeleton +that includes a ````, ````, and ```` tags. If the ``/assets/index.css`` +file exists, it will automatically be injected into the ```` tag. .. code-block:: html @@ -85,12 +93,15 @@ it will automatically be injected into the ```` tag. -.. note:: If you do not use the ``HTMLFrameMojit`` or use CSS with a different name than the - template, you will have to explicitly reference your CSS files in the ``assets`` directory. - For example, if you have ``/mojits/{mojit_name}/assets/simple.css``, you can use the HTML +.. note:: If you do not use the ``HTMLFrameMojit`` or use CSS with a different name than + the template, you will have to explicitly reference your CSS files in the + ``assets`` directory. For example, if you have + ``/mojits/{mojit_name}/assets/simple.css``, you can use the HTML ```` tag to reference the CSS at the following location: ``/static/{mojit_name}/assets/simple.css`` +.. _code_exs_frame_assets-setup: + Setting Up this Example ======================= @@ -103,8 +114,8 @@ To create and run ``framed_assets``: #. Create your mojit. ``$ mojito create mojit framed`` -#. To configure your application to have assets, replace the code in ``application.json`` with the - following: +#. To configure your application to have assets, replace the code in ``application.json`` + with the following: .. code-block:: javascript @@ -147,16 +158,14 @@ To create and run ``framed_assets``: ] #. Change to ``mojits/framed``. -#. Modify your controller to pass an array of objects to the template by replacing the code in - ``controller.server.js`` with the following: +#. Modify your controller to pass an array of objects to the template by replacing the + code in ``controller.server.js`` with the following: .. code-block:: javascript YUI.add('framed', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, + index: function(ac) { var data = { title: "Framed Assets", @@ -173,8 +182,8 @@ To create and run ``framed_assets``: }; }, '0.0.1', {requires: []}); -#. Include the assets in your template by replacing the code in ``views/index.hb.html`` with the - following: +#. Include the assets in your template by replacing the code in ``views/index.hb.html`` + with the following: .. code-block:: html @@ -220,6 +229,8 @@ To create and run ``framed_assets``: http://localhost:8666 +.. _code_exs_frame_assets-src: + Source Code =========== diff --git a/docs/dev_guide/code_exs/generating_urls.rst b/docs/dev_guide/code_exs/generating_urls.rst index d9716daba..ac0b2372e 100644 --- a/docs/dev_guide/code_exs/generating_urls.rst +++ b/docs/dev_guide/code_exs/generating_urls.rst @@ -6,29 +6,33 @@ Generating URLs **Difficulty Level:** Intermediate +.. _code_exs_gen_urls-summary: + Summary ======= -This example shows you a way to generate URLs to a particular view independent of the controller or -action of the mojit. +This example shows you a way to generate URLs to a particular view independent of the +controller or action of the mojit. The following topics will be covered: - configuring routing paths to call actions from mojit instances - creating a URL in the mojit controller with the `Url addon <../../api/classes/Url.common.html>`_ +.. _code_exs_gen_urls-notes: + Implementation Notes ==================== -The route paths for this code example are defined in the routing configuration file ``routes.json``. -You can define any path and then associate that path with a mojit instance and an action. -When the client makes an HTTP request on that path, the associated action on the mojit instance -defined in ``application.json`` will be executed. Before creating the routes for the application, -you first need to create the mojit instance. +The route paths for this code example are defined in the routing configuration file +``routes.json``. You can define any path and then associate that path with a mojit +instance and an action. When the client makes an HTTP request on that path, the associated +action on the mojit instance defined in ``application.json`` will be executed. Before +creating the routes for the application, you first need to create the mojit instance. -In the ``application.json`` below, you configure the application to use an instance of the mojit -``GenURLMojit``. The instance in this example is ``mymojit``, but the instance name can be -any string as defined by `RFC 4627 `_. +In the ``application.json`` below, you configure the application to use an instance of the +mojit ``GenURLMojit``. The instance in this example is ``mymojit``, but the instance name +can be any string as defined by `RFC 4627 `_. .. code-block:: javascript @@ -43,13 +47,14 @@ any string as defined by `RFC 4627 `_. } ] -In the ``routes.json``, you not only can define route paths, but you can also configure Mojito to -respond to specific HTTP methods called on those paths. The ``routes.json`` below defines -two route paths that only respond to HTTP GET calls. When HTTP GET calls are made on these two paths, -Mojito executes different methods from the ``mymojit`` instance. The ``index`` method -is executed when the root path is called, and the ``contactus`` method is executed when the -``/some-really-long-url-contactus`` path is called. The ``routes.json`` file gives you the -freedom to create route paths independent of the mojit controller. +In the ``routes.json``, you not only can define route paths, but you can also configure +Mojito to respond to specific HTTP methods called on those paths. The ``routes.json`` below +defines two route paths that only respond to HTTP GET calls. When HTTP GET calls are made +on these two paths, Mojito executes different methods from the ``mymojit`` instance. The +``index`` method is executed when the root path is called, and the ``contactus`` method +is executed when the ``/some-really-long-url-contactus`` path is called. The +``routes.json`` file gives you the freedom to create route paths independent of the mojit +controller. .. code-block:: javascript @@ -69,29 +74,29 @@ freedom to create route paths independent of the mojit controller. } ] -The mojit controller, however, can use the ``Url`` addon to access the route paths defined in -``routes.json`` to create URLs. For example, in the ``controller.server.js`` below, the route path -that calls the ``contactus`` action is formed with ``url.make`` in the ``index`` function. You just -pass the instance and action to ``url.make`` to create the URL based on the path defined in -``routes.json``. +The mojit controller, however, can use the ``Url`` addon to access the route paths defined +in ``routes.json`` to create URLs. For example, in the ``controller.server.js`` below, the +route path that calls the ``contactus`` action is formed with ``url.make`` in the ``index`` +function. You just pass the instance and action to ``url.make`` to create the URL based on +the path defined in ``routes.json``. .. code-block:: javascript YUI.add('GenURLMojit', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, + index: function(actionContext) { var url = actionContext.url.make('mymojit', 'contactus', ''); actionContext.done({contactus_url: url}); }, contactus: function(actionContext) { - var currentTime = actionContext.i18n.formatDate(new Date()); + var currentTime = actionContext.intl.formatDate(new Date()); actionContext.done({currentTime: currentTime}); } }; - }, '0.0.1', {requires: ['mojito-intl-addon']}); + }, '0.0.1', {requires: ['mojito-intl-addon', 'mojito-url-addon']}); + +.. _code_exs_gen_urls-setup: Setting Up this Example ======================= @@ -105,8 +110,8 @@ To set up and run ``generating_urls``: #. Create your mojit. ``$ mojito create mojit GenURLMojit`` -#. To configure your application to use ``GenURLMojit``, replace the code in ``application.json`` - with the following: +#. To configure your application to use ``GenURLMojit``, replace the code in + ``application.json`` with the following: .. code-block:: javascript @@ -142,16 +147,14 @@ To set up and run ``generating_urls``: ] #. Change to ``mojits/GenURLMojit``. -#. Enable the controller to create a URL using the route paths defined in ``routes.json`` by - replacing the code in ``controller.server.js`` with the following: +#. Enable the controller to create a URL using the route paths defined in ``routes.json`` + by replacing the code in ``controller.server.js`` with the following: .. code-block:: javascript YUI.add('GenURLMojit', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, + index: function(actionContext) { var url = actionContext.url.make('mymojit', 'contactus', ''); actionContext.done({contactus_url: url}); @@ -161,10 +164,10 @@ To set up and run ``generating_urls``: actionContext.done({currentTime: currentTime}); } }; - }, '0.0.1', {requires: ['mojito-intl-addon']}); + }, '0.0.1', {requires: ['mojito-intl-addon', 'mojito-url-addon']}); -#. To display the rendered ``index`` template when HTTP GET is called on the root path, replace - the code in ``views/index.hb.html`` with the following: +#. To display the rendered ``index`` template when HTTP GET is called on the root path, + replace the code in ``views/index.hb.html`` with the following: .. code-block:: html @@ -198,9 +201,12 @@ To set up and run ``generating_urls``: #. Run the server and open the following URL in a browser: http://localhost:8666/ -#. From your application, click on the `here `_ +#. From your application, click on the + `here `_ link to see the URL with the long path. +.. _code_exs_gen_urls-src: + Source Code =========== diff --git a/docs/dev_guide/code_exs/global_assets.rst b/docs/dev_guide/code_exs/global_assets.rst index 6f6764361..2940edcc4 100644 --- a/docs/dev_guide/code_exs/global_assets.rst +++ b/docs/dev_guide/code_exs/global_assets.rst @@ -6,12 +6,16 @@ Using Global Assets **Difficulty:** Intermediate +.. _code_exs_assets-summary: + Summary ======= -This example shows how to configure and use global assets in Mojito applications. In this example, -the ``HTMLFrameMojit`` inserts global assets into the rendered view. See -`Adding CSS <./adding_assets.html>`_ for an example that uses mojit-level assets. +This example shows how to configure and use global assets in Mojito +applications. In this +example, the ``HTMLFrameMojit`` inserts global assets into the rendered +view. See `Adding CSS <./adding_assets.html>`_ for an example that uses +mojit-level assets. The following topics will be covered: @@ -19,24 +23,31 @@ The following topics will be covered: - creating and storing your global assets - static URLs to global assets +.. _code_exs_assets-notes: + Implementation Notes ==================== +.. _assets-notes-what: + What Are Assets? ---------------- -Assets are resources that are required on the clients. These resources are primarily CSS but can also -be JavaScript. Your assets should not be the core components of your application. +Assets are resources that are required on the clients. These resources +are primarily CSS but can also be JavaScript. Your assets should not be +the core components of your application. + +.. _assets-notes-loc: Location of Assets ------------------ -Mojito applications can have both global and local assets. Global assets are placed in the -``assets`` directory under the application directory. Assets at the mojit level are placed in the -``assets`` directory under the mojit directory. +Mojito applications can have both global and local assets. Global assets are +placed in the ``assets`` directory under the application directory. Assets at +the mojit level are placed in the ``assets`` directory under the mojit directory. -The directory structure of this example below shows the location of the global ``assets`` directory -with the asset files. +The directory structure of this example below shows the location of the global +``assets`` directory with the asset files. :: @@ -59,12 +70,17 @@ with the asset files. ├── routes.json └── server.js +.. _assets-notes-static_url: + Static URLs to Assets --------------------- -Mojito provides static URLs to application-level and mojit-level assets. You can refer to these -assets in your templates, or if you are using the ``HTMLFrameMojit``, you configure your -application to automatically insert the assets into the rendered view. +Mojito provides static URLs to application-level and mojit-level assets. You +can refer to these assets in your templates, or if you are using the +``HTMLFrameMojit``, you configure your application to automatically insert +the assets into the rendered view. + +.. _static_url-syntax: Syntax ###### @@ -73,16 +89,18 @@ For application-level assets, the static URL has the following syntax: ``/static/{application_name}/assets/{asset_file}`` +.. _static_url-ex: + Examples ######## -The path to the application-level asset ``sadwalrus.jpeg`` of the ``global_assets`` application -would be the following: +The path to the application-level asset ``sadwalrus.jpeg`` of the ``global_assets`` +application would be the following: ``/static/global_assets/assets/sadwalrus.jpeg`` -In the template, the application-level assets above can be referred to using the static URLs as -seen here. +In the template, the application-level assets above can be referred to using the +static URLs as seen here. .. code-block:: html @@ -90,16 +108,20 @@ seen here. walrus smile fail +.. _assets-notes-htmlframemojit: + Configuring HTMLFrameMojit to Include Assets -------------------------------------------- When using the ``HTMLFrameMojit``, assets are listed in the ``assets`` object in -``application.json.`` The ``assets`` object can contain a ``top`` object and/or a ``bottom`` object. -The assets listed in ``top`` will be inserted into the ``head`` element of the HTML page. The -assets listed in ``bottom`` are inserted at the bottom of the ``body`` element. +``application.json.`` The ``assets`` object can contain a ``top`` object and/or a +``bottom`` object. The assets listed in ``top`` will be inserted into the ``head`` +element of the HTML page. The assets listed in ``bottom`` are inserted at the +bottom of the ``body`` element. -In the example ``application.json`` below, which is taken from this code example, the global asset -``ohhai.css`` is inserted into the ``head`` element of the rendered view. +In the example ``application.json`` below, which is taken from this code example, +the global asset ``ohhai.css`` is inserted into the ``head`` element of the rendered +view. .. code-block:: javascript @@ -131,6 +153,7 @@ In the example ``application.json`` below, which is taken from this code example } ] +.. _code_exs_assets-setup: Setting Up this Example ======================= @@ -144,8 +167,8 @@ To set up and run ``global_assets``: #. Create your mojit. ``$ mojito create mojit OhHai`` -#. To specify that your application use ``HTMLFrameMojit`` with a child mojit, replace the code in - ``application.json`` with the following: +#. To specify that your application use ``HTMLFrameMojit`` with a child mojit, replace the + code in ``application.json`` with the following: .. code-block:: javascript @@ -219,8 +242,8 @@ To set up and run ``global_assets``: }; }, '0.0.1', {requires: ['mojito']}); -#. Modify your ``index`` template to explicitly include the global asset ``sadwalrus.jpeg`` by - replacing the code in ``views/index.hb.html`` with the following: +#. Modify your ``index`` template to explicitly include the global asset ``sadwalrus.jpeg`` + by replacing the code in ``views/index.hb.html`` with the following: .. code-block:: html @@ -234,8 +257,10 @@ To set up and run ``global_assets``: #. To view your application with the sad walrus image, go to the URL: http://localhost:8666 -#. View the source code to see that the global asset ``ohhai.css`` was inserted into the ``head`` - element. +#. View the source code to see that the global asset ``ohhai.css`` was inserted into the + ``head`` element. + +.. _code_exs_assets-src: Source Code =========== diff --git a/docs/dev_guide/code_exs/htmlframe_view.rst b/docs/dev_guide/code_exs/htmlframe_view.rst index ea07f8ff9..f1a5adff4 100644 --- a/docs/dev_guide/code_exs/htmlframe_view.rst +++ b/docs/dev_guide/code_exs/htmlframe_view.rst @@ -6,27 +6,33 @@ Using the HTML Frame Mojit **Difficulty Level:** Intermediate +.. _code_exs_htmlframemojit-summary: + Summary ======= -This example shows how to use the HTML Frame Mojit ( ``HTMLFrameMojit``) to create the skeleton of -an HTML page and embed rendered template into the page. The ``HTMLFrameMojit`` creates the -````, ````, and ```` tags and embeds the rendered templates of the child mojits -into the ```` tag. To be clear, although the name ``HTMLFrameMojit`` contains the string -"frame", the ``HTMLFrameMojit`` does **not** create HTML ``frame`` or ``iframe`` elements. This -example only uses one child mojit, but you can configure the application to use many child mojits. -For more information, see `HTMLFrameMojit <../topics/mojito_framework_mojits.html#htmlframemojit>`_. +This example shows how to use the HTML Frame Mojit ( ``HTMLFrameMojit``) to +create the skeleton of an HTML page and embed rendered template into the +page. The ``HTMLFrameMojit`` creates the ````, ````, and ```` +tags and embeds the rendered templates of the child mojits into the ```` +tag. To be clear, although the name ``HTMLFrameMojit`` contains the string +"frame", the ``HTMLFrameMojit`` does **not** create HTML ``frame`` or ``iframe`` +elements. This example only uses one child mojit, but you can configure the +application to use many child mojits. For more information, see +`HTMLFrameMojit <../topics/mojito_frame_mojits.html#htmlframemojit>`_. The following topics will be covered: - creating the framework for an HTML page - embedding a rendered child mojit's templates into the HTML page +.. _code_exs_htmlframemojit-notes: + Implementation Notes ==================== -The screenshot below shows the page served by your application, where the visible content is created -by the child mojit of ``HTMLFrameMojit``. +The screenshot below shows the page served by your application, where the visible +content is created by the child mojit of ``HTMLFrameMojit``. Tab 2 Selected @@ -34,10 +40,11 @@ Tab 2 Selected :width: 401px :height: 368px -The ``HTMLFrameMojit`` is a reusable component that is available in every Mojito application. To -configure the ``HTMLFrameMojit``, you use the ``application.json`` file. In this example -``application.json``, the ``frame`` object has a ``type`` property that specifies that -``HTMLFrameMojit`` create the HTML framework and embed the rendered view from the ``child`` mojit. +The ``HTMLFrameMojit`` is a reusable component that is available in every Mojito +application. To configure the ``HTMLFrameMojit``, you use the ``application.json`` +file. In this example ``application.json``, the ``frame`` object has a ``type`` +property that specifies that ``HTMLFrameMojit`` create the HTML framework and +embed the rendered view from the ``child`` mojit. .. code-block:: javascript @@ -58,11 +65,11 @@ configure the ``HTMLFrameMojit``, you use the ``application.json`` file. In this } ] -The Mojito server returns the HTML below to the client. The ``HTMLFrameMojit`` is responsible for -the tags that comprise the skeleton of the HTML page and inserting the value of the ``title`` -property in ``application.json`` into the ```` element, and the child mojit creates the -content that is embedded in the ``<body>`` tag. In this example, the child mojit creates the -``<div>`` tag and its content. +The Mojito server returns the HTML below to the client. The ``HTMLFrameMojit`` is +responsible for the tags that comprise the skeleton of the HTML page and inserting the +value of the ``title`` property in ``application.json`` into the ``<title>`` element, +and the child mojit creates the content that is embedded in the ``<body>`` tag. In this +example, the child mojit creates the ``<div>`` tag and its content. .. code-block:: html @@ -95,9 +102,12 @@ content that is embedded in the ``<body>`` tag. In this example, the child mojit </body> </html> -The ``HTMLFrameMojit`` mojit can be used to allow dynamic run-time selection of running on the -client or server. You can also use ``HTMLFrameMojit`` to include assets and control language -defaults. These subjects are discussed in `Internationalizing Your Application <i18n_apps.html>`_. +The ``HTMLFrameMojit`` mojit can be used to allow dynamic run-time selection of running +on the client or server. You can also use ``HTMLFrameMojit`` to include assets and control +language defaults. These subjects are discussed in +`Internationalizing Your Application <i18n_apps.html>`_. + +.. _code_exs_htmlframemojit-setup: Setting Up this Example ======================= @@ -149,23 +159,21 @@ To set up and run ``htmlframe_mojit``: ] #. Change to ``mojits/framed``. -#. Modify the controller of the ``framed`` mojit by replacing the code in ``controller.server.js`` - with the following: +#. Modify the controller of the ``framed`` mojit by replacing the code in + ``controller.server.js`` with the following: .. code-block:: javascript YUI.add('framed', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, index: function(ac) { ac.done({app_name:'Framed Mojit'}); } }; }, '0.0.1', {requires: ['mojito']}); -#. Modify the default template by replacing the code in ``views/index.hb.html`` with the following: +#. Modify the default template by replacing the code in ``views/index.hb.html`` with the + following: .. code-block:: html @@ -189,7 +197,8 @@ To set up and run ``htmlframe_mojit``: ">{{app_name}}</h2> </div> - The HTML fragment in the template above will be embedded in the ``<body>`` tag by ``HTMLFrameMojit``. + The HTML fragment in the template above will be embedded in the ``<body>`` tag by + ``HTMLFrameMojit``. #. From the application directory, run the server. @@ -198,6 +207,8 @@ To set up and run ``htmlframe_mojit``: http://localhost:8666 +.. _code_exs_htmlframemojit-src: + Source Code =========== diff --git a/docs/dev_guide/code_exs/i18n_apps.rst b/docs/dev_guide/code_exs/i18n_apps.rst index 21f1ad233..2c81a2b8e 100644 --- a/docs/dev_guide/code_exs/i18n_apps.rst +++ b/docs/dev_guide/code_exs/i18n_apps.rst @@ -6,33 +6,46 @@ Internationalizing Your Application **Difficulty Level:** Intermediate +.. _code_exs_intl-summary: + Summary ======= -This example shows how to use the i18n support built into Mojito that includes top-level defaults -and the capability to override the default languages of countries. +This example shows how to use the i18n support built into Mojito that includes top-level +defaults and the capability to override the default languages of countries. The following topics will be covered: -- including the `YUI Internationalization utility <http://developer.yahoo.com/yui/3/intl/>`_ in the - mojit controller +- including the `YUI Internationalization utility <http://developer.yahoo.com/yui/3/intl/>`_ + in the mojit controller - using the `Intl addon <../../api/classes/Intl.common.html>`_ -- specifying the `BCP 47 <ftp://ftp.rfc-editor.org/in-notes/bcp/bcp47.txt>`_ language tags. BCP 47 - is currently the combination of `RFC 5646 <http://tools.ietf.org/html/rfc5646>`_ and - `RFC 4647 <http://tools.ietf.org/html/rfc4647>`_ +- specifying the `BCP 47 <ftp://ftp.rfc-editor.org/in-notes/bcp/bcp47.txt>`_ language tags. + BCP 47 is currently the combination of `RFC 5646 <http://tools.ietf.org/html/rfc5646>`_ + and `RFC 4647 <http://tools.ietf.org/html/rfc4647>`_ - specifying the resource bundles for the YUI Internationalization utility +.. _code_exs_intl-notes: + Implementation Notes ==================== +.. _intl_notes-res_bundles: + Resources Bundles for Languages ------------------------------- Mojito uses the `YUI 3 Internationalization <http://developer.yahoo.com/yui/3/intl/#switchingLangs>`_ -utility to support internationalization. To use the YUI Internationalization utility in Mojito, -you create resource bundles in JSON that specify the keys and values for the strings that need -localizing. These resource bundles are JavaScript files that are placed in the ``lang`` directory of -the mojit. +utility to support internationalization. To use the YUI Internationalization utility in +Mojito, you create resource bundles in JSON that specify the keys and values for the +strings that need localizing. + +.. _res_bundles-loc: + +Location of Resource Bundles +############################ + +These resource bundles are JavaScript files that are placed +in the ``lang`` directory of the mojit. This code example has the following three resource bundles in ``lang`` directory of the ``i18n`` mojit: @@ -44,14 +57,39 @@ This code example has the following three resource bundles in ``lang`` directory /i18n_en-AU.js /i18n_fr-FR.js -Notice that the resource bundle files above use the following naming convention: +.. _res_bundles-naming: + +Naming Conventions +################## + +.. _res_bundles_naming-file: + +File Names +********** + +Resource bundle files use the following naming convention: + +``{mojit_name}_{BCP 47 tag}.js`` + +.. _res_bundles_naming-yui: -``{mojit}_{BCP 47 tag}.js`` +YUI Module Name +*************** -From the content of the ``i18n_en-US.js`` resource bundle below, you see that the ``add`` method -specifies the module, the `BCP 47 <ftp://ftp.rfc-editor.org/in-notes/bcp/bcp47.txt>`_ language tag, -and the ``TITLE`` key with its value. The YUI Internationalization utility is included by adding -the string ``'intl'`` to the ``requires`` array. +The YUI module name that is registered in the resource bundle file with ``YUI.add`` +must have the following syntax: ``'lang/{mojit_name}_{BCP 47 Tag}.js'`` + +For example, the YUI module name of the resource bundle for US English +of the mojit ``i18n`` would be ``'lang/i18n_en-US'``. + +Example +####### + +From the content of the ``i18n_en-US.js`` resource bundle below, you see that the ``add`` +method specifies the module, the `BCP 47 <ftp://ftp.rfc-editor.org/in-notes/bcp/bcp47.txt>`_ +language tag, and the ``TITLE`` key with its value. The YUI Internationalization utility +is included by adding the string ``'intl'`` to the ``requires`` array. The +YUI module name also .. code-block:: javascript @@ -66,23 +104,24 @@ the string ``'intl'`` to the ``requires`` array. ); }, "3.1.0", {requires: ['intl']}); +.. _intl_notes-using_addon: + Using the intl Addon -------------------- -In the ``controller.server.js`` file below, the ``intl.lang`` and ``intl.formData`` methods rely on -the YUI Internationalization utility to select the language and format of the title and date. -The YUI Internationalization utility uses the ``Intl.lookupBestLang`` method to determine the best -language based on an application's request and a module's language support. You also need to -include the `Intl addon <../../api/classes/Intl.common.html>`_ by adding the string +In the ``controller.server.js`` file below, the ``intl.lang`` and ``intl.formData`` +methods rely on the YUI Internationalization utility to select the language and format of +the title and date. The YUI Internationalization utility uses the ``Intl.lookupBestLang`` +method to determine the best language based on an application's request and a module's +language support. You also need to include the +`Intl addon <../../api/classes/Intl.common.html>`_ by adding the string 'mojito-intl-addon' to the ``requires`` array. .. code-block:: javascript YUI.add('i18n', function(Y, NAME) {/ Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, + index: function(ac) { // Default. ac.done( @@ -95,22 +134,24 @@ include the `Intl addon <../../api/classes/Intl.common.html>`_ by adding the str }; }, '0.0.1', { requires: ['mojito-intl-addon']}); +.. _intl_notes-run_client: + Configuring a Mojit to Run on Client ------------------------------------ -When trying to deliver HTML pages with the language and date format preferred by the user, it's best -to rely on the user's browser settings. YUI, when running on the client side, can detect the browser -settings to select the default translation and date format. During server-side execution, however, -the preferred language and date format is determined by +When trying to deliver HTML pages with the language and date format preferred by the user, +it's best to rely on the user's browser settings. YUI, when running on the client side, +can detect the browser settings to select the default translation and date format. During +server-side execution, however, the preferred language and date format is determined by the order of languages listed in the mojit controller. -Fortunately, Mojito lets you configure applications to run on either the server or client side. -Because this code example illustrates how to localize your application, we want to -configure Mojito to run the application on the client to improve the chances of serving content in -the user's preferred language and date format. +Fortunately, Mojito lets you configure applications to run on either the server or client +side. Because this code example illustrates how to localize your application, we want to +configure Mojito to run the application on the client to improve the chances of serving +content in the user's preferred language and date format. -To configure Mojito to run on the client, you simply set the ``"deploy"`` property to ``true`` as -seen in the ``application.json`` file below. +To configure Mojito to run on the client, you simply set the ``"deploy"`` property to +``true`` as seen in the ``application.json`` file below. .. code-block:: javascript @@ -131,6 +172,8 @@ seen in the ``application.json`` file below. } ] +.. _code_exs_intl-setup: + Setting Up this Example ======================= @@ -143,8 +186,8 @@ To set up and run ``locale_i18n``: #. Create your mojit. ``$ mojito create mojit i18n`` -#. To configure you application to have the mojit code run on the client, replace the code in - ``application.json`` with the following: +#. To configure you application to have the mojit code run on the client, replace the + code in ``application.json`` with the following: .. code-block:: javascript @@ -165,7 +208,8 @@ To set up and run ``locale_i18n``: } ] -#. To configure routing, replace the code in ``routes.json`` with the following: +#. To configure routing, replace the code in ``routes.json`` with the + following: .. code-block:: javascript @@ -187,9 +231,7 @@ To set up and run ``locale_i18n``: YUI.add('i18n', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, + index: function(ac) { // Default. ac.done( @@ -202,8 +244,8 @@ To set up and run ``locale_i18n``: }; }, '0.0.1', { requires: ['mojito-intl-addon']}); -#. To add the resource bundle for American English, create the file ``lang/i18n_en-US.js`` with the - following: +#. To add the resource bundle for American English, create the file ``lang/i18n_en-US.js`` + with the following: .. code-block:: javascript @@ -218,7 +260,8 @@ To set up and run ``locale_i18n``: ); }, "3.1.0", {requires: ['intl']}); -#. To add the resource bundle for French, create the file ``lang/i18n_fr-FR.js`` with the following: +#. To add the resource bundle for French, create the file ``lang/i18n_fr-FR.js`` with the + following: .. code-block:: javascript @@ -233,8 +276,8 @@ To set up and run ``locale_i18n``: ); }, "3.1.0", {requires: ['intl']}); -#. To add the resource bundle for Australian English, create the file ``lang/i18n_en-AU.js`` with - the following: +#. To add the resource bundle for Australian English, create the file + ``lang/i18n_en-AU.js`` with the following: .. code-block:: javascript @@ -263,16 +306,21 @@ To set up and run ``locale_i18n``: http://localhost:8666 -#. Configure your browser to use French as the default language. To change the language preferences - of Firefox or Chrome, see the `Firefox instructions <http://support.mozilla.com/en-US/kb/Options%20window%20-%20Content%20panel?s=change+preference+language&as=s#w_languages>`_ +#. Configure your browser to use French as the default language. To change the language + preferences of Firefox or Chrome, see the + `Firefox instructions <http://support.mozilla.com/en-US/kb/Options%20window%20-%20Content%20panel?s=change+preference+language&as=s#w_languages>`_ and `Chrome instructions <http://www.google.com/support/chrome/bin/answer.py?hl=en&answer=95416&from=95415&rd=1>`_. -#. Now go to your `application URL <http://localhost:8666>`_ and see the page display French. -#. To force the page to display a specific language and date format, you can also use the query - string parameter ``lang.`` The URL below uses the ``lang`` parameter to display the page in Australian English: +#. Now go to your `application URL <http://localhost:8666>`_ and see the page display + French. +#. To force the page to display a specific language and date format, you can also use the + query string parameter ``lang.`` The URL below uses the ``lang`` parameter to display + the page in Australian English: http://localhost:8666?lang=en-AU +.. _code_exs_intl-src: + Source Code =========== diff --git a/docs/dev_guide/code_exs/index.rst b/docs/dev_guide/code_exs/index.rst index a4f53fbbf..2605c5b55 100644 --- a/docs/dev_guide/code_exs/index.rst +++ b/docs/dev_guide/code_exs/index.rst @@ -1,51 +1,16 @@ -========================= -Overview of Code Examples -========================= - -The code examples intend to provide solutions for common problems. Each example requires creating an -application, but code modifications are minimized for simplification and to isolate the problem that -the code example is solving. To optimize your learning experience, the required time and -skill level are given at the beginning of each example. - -Prerequisites ============= - -Complete the `Getting Started tutorial <../getting_started/>`_. - -Format of Code Examples -======================= - -In general, the code examples have the following format: - -**Time Estimate / Difficulty Level** - approximates the time required to finish the example and the -difficulty level to help you choose examples. - -**Summary** - description of the code example and the topics covered. - -**Implementation Notes** - includes a screenshot of the running application and an explanation of -code snippets that are considered essential to the example. - -**Setting Up This Example** - steps for creating and running the code example. - -**Source Code** - the link to the source code on GitHub for the code example. - - -.. note:: The steps in **Setting Up This Example** assume that you are running the examples on a - local host. Thus, the links provided to the application URL will look like the following: - ``http://localhost:8666/{code_example}``. To view examples running on a remote server, - point your browser to the appropriate application URL. - -Table of Contents -================= +Code Examples +============= .. toctree:: - :maxdepth: 2 - + :maxdepth: 3 + + overview config views assets data - events - inter-mojit + binding_events + intermojit_communication other diff --git a/docs/dev_guide/code_exs/inter-mojit.rst b/docs/dev_guide/code_exs/inter-mojit.rst index 9950dd175..8122ae1c7 100644 --- a/docs/dev_guide/code_exs/inter-mojit.rst +++ b/docs/dev_guide/code_exs/inter-mojit.rst @@ -2,7 +2,8 @@ Inter-Mojit Communication ========================= -This code example shows how a mojit can communicate with the client and other mojits. +This code example shows how a mojit can communicate with the client and +other mojits. .. toctree:: :maxdepth: 1 diff --git a/docs/dev_guide/code_exs/intermojit_communication.rst b/docs/dev_guide/code_exs/intermojit_communication.rst index c6493a696..bf291ac68 100644 --- a/docs/dev_guide/code_exs/intermojit_communication.rst +++ b/docs/dev_guide/code_exs/intermojit_communication.rst @@ -1,22 +1,27 @@ -================================== -Allowing Inter-Mojit Communication -================================== +========================= +Inter-Mojit Communication +========================= **Time Estimate:** 15 minutes **Difficulty Level:** Intermediate +.. _code_exs_intermojit-summary: + Summary ======= -This example shows how to configure mojits to communicate with each other through event binding. +This example shows how to configure mojits to communicate with each other +through event binding. The following topics will be covered: - structuring your mojits for intercommunication - implementing binders for each mojit to listen to and trigger events -- using the `Composite addon <../../api/classes/Composite.common.html>`_ to execute code in child - mojits +- using the `Composite addon <../../api/classes/Composite.common.html>`_ + to execute code in child mojits + +.. _code_exs_intermojit-notes: Implementation Notes ==================== @@ -26,11 +31,13 @@ Implementation Notes Application Configuration ------------------------- -The ``application.json`` for this example defines the hierarchy and relationship between the mojits -of this application and configures the application to run on the client. In the ``application.json`` -below, the ``HTMLFrameMojit`` is the parent of the ``MasterMojit``, which, in turn, is the parent of -the ``SenderMojit`` and ``ReceiverMojit``. The ``"deploy"`` property of the ``"frame"`` object is -assigned the value ``"true"`` to configure Mojito to send code to the client for execution. +The ``application.json`` for this example defines the hierarchy and +relationship between the mojits of this application and configures the +application to run on the client. In the ``application.json`` below, +the ``HTMLFrameMojit`` is the parent of the ``MasterMojit``, +which, in turn, is the parent of the ``SenderMojit`` and ``ReceiverMojit``. +The ``"deploy"`` property of the ``"frame"`` object is assigned the value +``"true"`` to configure Mojito to send code to the client for execution. .. code-block:: javascript @@ -69,15 +76,18 @@ assigned the value ``"true"`` to configure Mojito to send code to the client for } } ] + +.. _impl_notes-routes_config: Routing Configuration --------------------- -In the ``routes.json`` below, two route paths are defined . The route configuration for the root -path specifies that the ``index`` method of the ``frame`` instance of ``HTMLFrameMojit`` be called -when HTTP GET calls are received. Recall that the ``HTMLFrameMojit`` is the parent of the other -mojits. Because the ``HTMLFrameMojit`` has no ``index`` function, the ``index`` function -in the controller of the child mojit ``MasterMojit`` is called instead. +In the ``routes.json`` below, two route paths are defined . The route +configuration for the root path specifies that the ``index`` method of +the ``frame`` instance of ``HTMLFrameMojit`` be called when HTTP GET calls +are received. Recall that the ``HTMLFrameMojit`` is the parent of the other +mojits. Because the ``HTMLFrameMojit`` has no ``index`` function, the ``index`` +function in the controller of the child mojit ``MasterMojit`` is called instead. .. code-block:: javascript @@ -97,43 +107,45 @@ in the controller of the child mojit ``MasterMojit`` is called instead. } ] +.. _impl_notes-master_mojit: + Master Mojit ------------ -The ``MasterMojit`` performs three major functions, each handled by a different file. The controller -executes the ``index`` methods of the children mojits. The binder listens for events and then -broadcasts those events to its children. Lastly, the ``index`` template displays the content created -by the child mojits. We'll now take a look at each of the files to understand how they perform -these three functions. +The ``MasterMojit`` performs three major functions, each handled by a different +file. The controller executes the ``index`` methods of the children mojits. The +binder listens for events and then broadcasts those events to its children. +Lastly, the ``index`` template displays the content created by the child +mojits. We'll now take a look at each of the files to understand how they +perform these three functions. -The ``controller.server.js`` below is very simple because the main purpose is to execute the -``index`` functions of the child mojits. The Action Context object ``actionContext`` is vital -because it gives the ``MasterMojit`` access to the child mojits through addons. The ``MasterMojit`` -can execute the ``index`` functions of the child mojits by calling the ``done`` method from the -``Composite`` addon. +The ``controller.server.js`` below is very simple because the main purpose +is to execute the ``index`` functions of the child mojits. The Action Context +object ``actionContext`` is vital because it gives the ``MasterMojit`` access +to the child mojits through addons. The ``MasterMojit`` can execute the +``index`` functions of the child mojits by calling the ``done`` method from +the ``Composite`` addon. .. code-block:: javascript YUI.add('MasterMojit', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, "index": function(actionContext) { actionContext.composite.done(); } }; - }, '0.0.1', {requires: ['mojito']}); - -The binder for the ``MasterMojit`` listens for events from the ``SenderMojit``. Once an event is -received, the ``MasterMojit`` then broadcasts that event to its child mojits. The child mojit -``ReceiverMojit`` will then intercept the broadcasted events, which we look at later in -:ref:`impl_notes-receiver_mojit`. - -So, how do mojits listen to events from other mojits or broadcast events? On the client, each mojit -binder can use the ``mojitProxy`` object to interact with other mojits on the page. In the -``binders/index.js`` of the ``MasterMojit`` below, the ``mojitProxy`` object is used to listen to -hyperlink events and then to broadcast an event to the child mojits. The first arguments + }, '0.0.1', {requires: ['mojito', 'mojito-composite-addon']}); + +The binder for the ``MasterMojit`` listens for events from the ``SenderMojit``. +Once an event is received, the ``MasterMojit`` then broadcasts that event to +its child mojits. The child mojit ``ReceiverMojit`` will then intercept the +broadcasted events, which we look at later in:ref:`impl_notes-receiver_mojit`. + +So, how do mojits listen to events from other mojits or broadcast events? On +the client, each mojit binder can use the ``mojitProxy`` object to interact +with other mojits on the page. In the ``binders/index.js`` of the +``MasterMojit`` below, the ``mojitProxy`` object is used to listen to hyperlink +events and then to broadcast an event to the child mojits. The first arguments passed to the ``listen`` and ``fire`` methods are the event types. .. code-block:: javascript @@ -164,20 +176,22 @@ passed to the ``listen`` and ``fire`` methods are the event types. }; }, '0.0.1', {requires: ['mojito-client']}); -In the ``application.json`` file discussed in :ref:`impl_notes-app_config`, four mojit instances -were declared: ``frame``, ``child``, ``sender``, and ``receiver``. Because the ``child`` instance -of ``MasterMojit`` is the parent of the ``sender`` and ``receiver`` mojit instances, the controller -can execute the code in the child mojit instances by calling ``actionContext.composite.done()`` -in the controller. As you can see below, the output from the ``sender`` and ``receiver`` instances -can be inserted into the template through Handlebars expressions. +In the ``application.json`` file discussed in :ref:`impl_notes-app_config`, +four mojit instances were declared: ``frame``, ``child``, ``sender``, and +``receiver``. Because the ``child`` instance of ``MasterMojit`` is the parent +of the ``sender`` and ``receiver`` mojit instances, the controller can execute +the code in the child mojit instances by calling ``actionContext.composite.done`` +in the controller. As you can see below, the output from the ``sender`` and +``receiver`` instances can be inserted into the template through Handlebars +expressions. .. code-block:: html <div id="{{mojit_view_id}}" class="mojit"> <div id="header"> - This example demonstrates inter mojit communication on a page. The mojit on the left side - contains a list of image links. The mojit on the right side will display the image whenever a - link in the left mojit is clicked on. + This example demonstrates inter mojit communication on a page. The mojit on the left + side contains a list of image links. The mojit on the right side will display the + image whenever a link in the left mojit is clicked on. </div> <table> <tr> @@ -187,17 +201,20 @@ can be inserted into the template through Handlebars expressions. </table> </div> +.. _impl_notes-sender_mojit: + Sender Mojit ------------ -The ``SenderMojit`` listens for click events and then forwards them and an associated URL to the -``MasterMojit``. Because the controller for the ``SenderMojit`` does little but send some text, -we will only examine the binder and index template. +The ``SenderMojit`` listens for click events and then forwards them and +an associated URL to the ``MasterMojit``. Because the controller for the +``SenderMojit`` does little but send some text, we will only examine the +binder and index template. -The binder for the ``SenderMojit`` binds and attaches event handlers to the DOM. In the -``binders/index.js`` below, the handler for click events uses the ``mojitProxy`` object to fire the -event to the binder for the ``MasterMojit``. The URL of the clicked link is passed to the -``MasterMojit``. +The binder for the ``SenderMojit`` binds and attaches event handlers to the +DOM. In the ``binders/index.js`` below, the handler for click events uses +the ``mojitProxy`` object to fire the event to the binder for the +``MasterMojit``. The URL of the clicked link is passed to the ``MasterMojit``. .. code-block:: javascript @@ -221,9 +238,9 @@ event to the binder for the ``MasterMojit``. The URL of the clicked link is pass }; }, '0.0.1', {requires: ['node','mojito-client']}); -The ``index`` template for the ``SenderMojit`` has an unordered list of links to Flickr photos. As -we saw in the binder, the handler for click events passes the event and the link URL -to the ``MasterMojit``. +The ``index`` template for the ``SenderMojit`` has an unordered list of links +to Flickr photos. As we saw in the binder, the handler for click events passes +the event and the link URL to the ``MasterMojit``. .. code-block:: html @@ -247,20 +264,18 @@ to the ``MasterMojit``. Receiver Mojit -------------- -The ``ReceiverMojit`` is responsible for capturing events that were broadcasted by ``MasterMojit`` -and then displaying the photo associated with the link that was clicked. +The ``ReceiverMojit`` is responsible for capturing events that were broadcasted +by ``MasterMojit`` and then displaying the photo associated with the link that +was clicked. -In the controller for ``ReceiverMojit``, the additional function ``show`` displays a photo based on -the query string parameter ``url`` or a default photo. The ``show`` function gets invoked from the -binder, which we'll look at next. +In the controller for ``ReceiverMojit``, the additional function ``show`` displays +a photo based on the query string parameter ``url`` or a default photo. The ``show`` +function gets invoked from the binder, which we'll look at next. .. code-block:: javascript YUI.add('ReceiverMojit', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, index: function(actionContext) { actionContext.done({title: 'This is the receiver mojit'}); }, @@ -269,12 +284,12 @@ binder, which we'll look at next. actionContext.done({title: 'Image matching the link clicked on the left.', url: url}); } }; - }, '0.0.1', {requires: []}); + }, '0.0.1', {requires: ['mojito-params-addon']}); The binder for the ``ReceiverMojit`` listens for broadcasted link events. In the ``binders/index.js`` below, those broadcasted link events, which are the event type -"broadcast-link", will come from the ``MasterMojit``. When the event is captured, the ``mojitProxy`` -object is used to invoke the ``show`` function and pass the photo URI. +"broadcast-link", will come from the ``MasterMojit``. When the event is captured, the +``mojitProxy`` object is used to invoke the ``show`` function and pass the photo URI. .. code-block:: javascript @@ -309,6 +324,8 @@ object is used to invoke the ``show`` function and pass the photo URI. }; }, '0.0.1', {requires: ['mojito-client']}); +.. _code_exs_intermojit-setup: + Setting Up this Example ======================= @@ -366,8 +383,8 @@ To set up and run ``inter-mojit``: } ] -#. To configure routing for the root path and the path ``/receiver/show``, replace the code in - ``routes.json`` with the following: +#. To configure routing for the root path and the path ``/receiver/show``, replace the + code in ``routes.json`` with the following: .. code-block:: javascript @@ -395,17 +412,14 @@ To set up and run ``inter-mojit``: YUI.add('MasterMojit', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(spec) { - this.spec=spec; - }, "index": function(actionContext) { actionContext.composite.done(); } }; - }, '0.0.1', {requires: []}); + }, '0.0.1', {requires: ['mojito-composite-addon']}); -#. To allow the ``MasterMojit`` to capture events and refire them to its children mojits, replace - the code in ``binders/index.js`` with the following: +#. To allow the ``MasterMojit`` to capture events and refire them to its children mojits, + replace the code in ``binders/index.js`` with the following: .. code-block:: javascript @@ -435,16 +449,18 @@ To set up and run ``inter-mojit``: }; }, '0.0.1', {requires: ['mojito-client']}); -#. Modify the ``index`` template to include output from the ``SenderMojit`` and ``ReceiverMojit`` - by replacing the code in ``views/index.hb.html`` with the following: +#. Modify the ``index`` template to include output from the ``SenderMojit`` and + ``ReceiverMojit`` by replacing the code in ``views/index.hb.html`` with the following: .. code-block:: html <div id="{{mojit_view_id}}" class="mojit"> <div id="header"> - This example demonstrates inter mojit communication on a page. - The mojit on the left side contains a list of image links. - The mojit on the right side will display the image whenever a link in the left mojit is clicked on.</div> + This example demonstrates inter mojit communication on a page. + The mojit on the left side contains a list of image links. + The mojit on the right side will display the image whenever a link in the left + mojit is clicked on. + </div> <table> <tr> <td class="left">{{{sender}}}</td> @@ -462,17 +478,14 @@ To set up and run ``inter-mojit``: YUI.add('SenderMojit', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, index: function(actionContext) { actionContext.done({title: 'List of images for testing'}); } }; }, '0.0.1', {requires: []}); -#. To allow the ``SenderMojit`` to fire an event, replace the code in ``binders/index.js`` with the - following: +#. To allow the ``SenderMojit`` to fire an event, replace the code in ``binders/index.js`` + with the following: .. code-block:: javascript @@ -495,8 +508,8 @@ To set up and run ``inter-mojit``: }; }, '0.0.1', {requires: ['node','mojito-client']}); -#. To provide an unordered list of image links to the ``index`` template of the ``MasterMojit``, - replace the code in ``views/index.hb.html`` with the following: +#. To provide an unordered list of image links to the ``index`` template of the + ``MasterMojit``, replace the code in ``views/index.hb.html`` with the following: .. code-block:: html @@ -518,16 +531,13 @@ To set up and run ``inter-mojit``: #. Change to the ``ReceiverMojit`` directory. ``$ cd ../ReceiverMojit`` -#. To display an image associated with a clicked link, replace the code in ``controller.server.js`` - with the following: +#. To display an image associated with a clicked link, replace the code in + ``controller.server.js`` with the following: .. code-block:: javascript YUI.add('ReceiverMojit', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(spec) { - this.spec = spec; - }, "index": function(actionContext) { actionContext.done({title: 'This is the receiver mojit'}); }, @@ -536,10 +546,10 @@ To set up and run ``inter-mojit``: actionContext.done({title: 'Image matching the link clicked on the left.', url: url}); } }; - }, '0.0.1', {requires: []}); + }, '0.0.1', {requires: ['mojito-params-addon']}); -#. To allow the ``ReceiverMojit`` to capture an event and invoke the ``show`` function in the - controller, replace the code in ``binders/index.js`` with the following: +#. To allow the ``ReceiverMojit`` to capture an event and invoke the ``show`` function in + the controller, replace the code in ``binders/index.js`` with the following: .. code-block:: javascript @@ -601,6 +611,8 @@ To set up and run ``inter-mojit``: http://localhost:8666 +.. _code_exs_intermojit-src: + Source Code =========== diff --git a/docs/dev_guide/code_exs/multiple_mojits.rst b/docs/dev_guide/code_exs/multiple_mojits.rst index 65935540b..66e6ac819 100644 --- a/docs/dev_guide/code_exs/multiple_mojits.rst +++ b/docs/dev_guide/code_exs/multiple_mojits.rst @@ -6,10 +6,13 @@ Using Multiple Mojits **Difficulty Level:** Intermediate +.. _code_exs_multiple_mojits-summary: + Summary ======= -This example shows how to use a parent mojit with multiple child mojits to create an HTML page. +This example shows how to use a parent mojit with multiple child mojits to create an HTML +page. The following topics will be covered: @@ -17,23 +20,27 @@ The following topics will be covered: - including the output from different mojits in one template - embedding the rendered template into the HTML frame -.. tip:: To learn how to use the Mojito built-in mojit ``HTMLFrameMojit`` to aggregate and display -the output from child mojits, see `Using the HTML Frame Mojit <./htmlframe_view.html>`_. +.. tip:: + To learn how to use the Mojito built-in mojit ``HTMLFrameMojit`` to aggregate and + display the output from child mojits, see + `Using the HTML Frame Mojit <./htmlframe_view.html>`_. + +.. _code_exs_multiple_mojits-notes: Implementation Notes ==================== -In the screenshot below, you see an HTML page divided into header, body, and footer sections that -were created by individual mojits. +In the screenshot below, you see an HTML page divided into header, body, and footer +sections that were created by individual mojits. .. image:: images/preview.multiple_mojits.gif :height: 368px :width: 401px -In the ``application.json`` below that is used for this code example, you see that this application -is using the ``frame`` instance of type ``FrameMojit``. The ``FrameMojit`` forms a skeleton page of -``div`` tags that use content created by the child mojits ``HeaderMojit``, ``BodyMojit``, and -``FooterMojit``. +In the ``application.json`` below that is used for this code example, you see that this +application is using the ``frame`` instance of type ``FrameMojit``. The ``FrameMojit`` +forms a skeleton page of ``div`` tags that use content created by the child mojits +``HeaderMojit``, ``BodyMojit``, and ``FooterMojit``. .. code-block:: javascript @@ -62,10 +69,10 @@ is using the ``frame`` instance of type ``FrameMojit``. The ``FrameMojit`` forms } ] -In ``routes.json``, the path set for each mojit is different, but the ``index`` function from each -mojit is called when GET calls are made. What's not obvious here is how the ``frame`` mojit gets -output from the other mojits because that happens in the controller of the ``frame`` mojit and not -in the route configuration. +In ``routes.json``, the path set for each mojit is different, but the ``index`` function +from each mojit is called when GET calls are made. What's not obvious here is how the +``frame`` mojit gets output from the other mojits because that happens in the controller +of the ``frame`` mojit and not in the route configuration. .. code-block:: javascript @@ -95,29 +102,27 @@ in the route configuration. } ] -In ``controller.server.js`` of the ``FrameMojit``, the ``Composite`` addon allows the parent mojit -to execute the child mojits defined in ``application.json`` that we looked at earlier. -After the children mojits are executed, the data that is passed to the ``done`` method in the -children mojits becomes accessible in the ``index.hb.html`` template of ``FrameMojit``, -which we will take a look at next. +In ``controller.server.js`` of the ``FrameMojit``, the ``Composite`` addon allows the +parent mojit to execute the child mojits defined in ``application.json`` that we looked at +earlier. After the children mojits are executed, the data that is passed to the ``done`` +method in the children mojits becomes accessible in the ``index.hb.html`` template of +``FrameMojit``, which we will take a look at next. .. code-block:: javascript YUI.add('FrameMojit', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, + index: function(actionContext) { actionContext.composite.done({template: {title: "Parent Frame"}}); } }; } - }, '0.0.1', {requires: []}); + }, '0.0.1', {requires: ['mojito-composite-addon']}); -The ``index.hb.html`` template of ``FrameMojit``, shown below, has variables from the children -mojits in different ``div`` tags. The variables ``header``, ``body``, and ``footer`` are in triple -braces, which allows you to return unescaped HTML. +The ``index.hb.html`` template of ``FrameMojit``, shown below, has variables from the +children mojits in different ``div`` tags. The variables ``header``, ``body``, and +``footer`` are in triple braces, which allows you to return unescaped HTML. .. code-block:: html @@ -134,6 +139,8 @@ braces, which allows you to return unescaped HTML. </div> </div> +.. _code_exs_multiple_mojits-setup: + Setting Up this Example ======================= @@ -220,17 +227,15 @@ To set up and run ``multiple_mojits``: YUI.add('FrameMojit', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, + index: function(actionContext) { actionContext.composite.done({template: {title: "Parent Frame"}}); } }; - }, '0.0.1', {requires: []}); + }, '0.0.1', {requires: ['mojito-composite-addon']}); -#. Modify the default template to use Handlebars expressions from the child mojits by replacing the - code in ``views/index.hb.html`` with the following: +#. Modify the default template to use Handlebars expressions from the child mojits by + replacing the code in ``views/index.hb.html`` with the following: .. code-block:: javascript @@ -257,9 +262,7 @@ To set up and run ``multiple_mojits``: YUI.add('HeaderMojit', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, + index: function(actionContext) { actionContext.done({title: "Header"}); } @@ -289,9 +292,7 @@ To set up and run ``multiple_mojits``: YUI.add('BodyMojit', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, + index: function(actionContext) { actionContext.done({title: "Body"}); } @@ -306,7 +307,8 @@ To set up and run ``multiple_mojits``: <h4>{{title}}</h4> </div> - This HTML fragment will be included in the body section of the default template of ``FrameMojit``. + This HTML fragment will be included in the body section of the default template of + ``FrameMojit``. #. Change to the ``FooterMojit`` directory. @@ -318,9 +320,7 @@ To set up and run ``multiple_mojits``: YUI.add('FooterMojit', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, + index: function(actionContext) { actionContext.done({title: "Footer"}); } @@ -345,6 +345,7 @@ To set up and run ``multiple_mojits``: http://localhost:8666 +.. _code_exs_multiple_mojits-src: Source Code =========== diff --git a/docs/dev_guide/code_exs/other.rst b/docs/dev_guide/code_exs/other.rst index c203693f9..3ac041440 100644 --- a/docs/dev_guide/code_exs/other.rst +++ b/docs/dev_guide/code_exs/other.rst @@ -2,8 +2,8 @@ Other ===== -These code examples deal with a variety of issues that don't fit into any one category. In the -future, new categories for code examples may be created for some of these examples. +These code examples deal with a variety of issues that don't fit into any one category. +In the future, new categories for code examples may be created for some of these examples. .. toctree:: :maxdepth: 1 diff --git a/docs/dev_guide/code_exs/overview.rst b/docs/dev_guide/code_exs/overview.rst new file mode 100644 index 000000000..99ecec6ab --- /dev/null +++ b/docs/dev_guide/code_exs/overview.rst @@ -0,0 +1,43 @@ +========================= +Overview of Code Examples +========================= + +The code examples intend to provide solutions for common problems. Each example +requires creating an application, but code modifications are minimized for +simplification and to isolate the problem that the code example is solving. +To optimize your learning experience, the required time and +skill level are given at the beginning of each example. + +.. _code_exs_overview-prereqs: + +Prerequisites +------------- + +Complete the `Getting Started tutorial <../getting_started/>`_. + +.. _code_exs_overview-format: + +Format of Code Examples +----------------------- + +In general, the code examples have the following format: + +**Time Estimate / Difficulty Level** - approximates the time required to finish +the example and the difficulty level to help you choose examples. + +**Summary** - description of the code example and the topics covered. + +**Implementation Notes** - includes a screenshot of the running application and +an explanation of code snippets that are considered essential to the example. + +**Setting Up This Example** - steps for creating and running the code example. + +**Source Code** - the link to the source code on GitHub for the code example. + + +.. note:: The steps in **Setting Up This Example** assume that you are running the + examples on a local host. Thus, the links provided to the application URL will + look like the following: ``http://localhost:8666/{code_example}``. To view + examples running on a remote server, point your browser to the appropriate + application URL. + diff --git a/docs/dev_guide/code_exs/query_params.rst b/docs/dev_guide/code_exs/query_params.rst index 6c19db6b9..df2e0cdec 100644 --- a/docs/dev_guide/code_exs/query_params.rst +++ b/docs/dev_guide/code_exs/query_params.rst @@ -6,29 +6,37 @@ Using Query Parameters **Difficulty Level:** Intermediate +.. _code_exs_qp-summary: + Summary ======= -This example shows how to access query parameters from the URL, the POST body, and the routing -configuration of your Mojito application. +This example shows how to access query parameters from the URL, the POST body, +and the routing configuration of your Mojito application. The following topics will be covered: -- using the `Params addon <../../api/classes/Params.common.html>`_ to access parameters +- using the `Params addon <../../api/classes/Params.common.html>`_ to access + parameters - setting and getting parameters from your route configuration +.. _code_exs_qp-notes: + Implementation Notes ==================== -The mojit controller of this code example has four functions, each using methods from the ``Params`` -addon to access different types of parameters. Let's start by learning how -to access the query string parameters in the first function. +The mojit controller of this code example has four functions, each using +methods from the ``Params`` addon to access different types of parameters. +Let's start by learning how to access the query string parameters in the +first function. -The ``example1`` function below gets all of the query string parameters using ``params.getFromUrl``. -To get a specific parameter, just pass a key to ``params.getFromUrl(key)``. In the code below, the -key-value pairs that are fetched by ``params.getFromUrl()`` are wrapped in objects that are pushed -to the array ``paramsArray``. The array is assigned to ``params``, which is then passed to the -``example1`` template. By default, the function sends data to the template with the same name. +The ``example1`` function below gets all of the query string parameters using +``params.getFromUrl``. To get a specific parameter, just pass a key to +``params.getFromUrl(key)``. In the code below, the key-value pairs that are +fetched by ``params.getFromUrl()`` are wrapped in objects that are pushed to +the array ``paramsArray``. The array is assigned to ``params``, which is then +passed to the ``example1`` template. By default, the function sends data to the +template with the same name. .. code-block:: javascript @@ -57,10 +65,12 @@ to the array ``paramsArray``. The array is assigned to ``params``, which is then ); }, ... + }, '0.0.1', {requires: ['dump', 'mojito-params-addon']}); -The ``example2`` function below uses ``params.getFromBody()`` to extract parameters from the POST -body. Once again, the array of objects containing the key-value pairs is passed to the ``example2`` -template, where the array is available through the ``params`` variable. +The ``example2`` function below uses ``params.getFromBody()`` to extract +parameters from the POST body. Once again, the array of objects containing +the key-value pairs is passed to the ``example2`` template, where the array +is available through the ``params`` variable. .. code-block:: javascript @@ -82,9 +92,11 @@ template, where the array is available through the ``params`` variable. ); }, ... + }, '0.0.1', {requires: ['dump', 'mojito-params-addon']}); -The ``example3`` function below uses ``params.getFromRoute()`` to access the parameters that are -specified in ``routes.json``, which we will look at in the next code snippet. +The ``example3`` function below uses ``params.getFromRoute()`` to access the +parameters that are specified in ``routes.json``, which we will look at in +the next code snippet. .. code-block:: javascript @@ -105,11 +117,13 @@ specified in ``routes.json``, which we will look at in the next code snippet. ); }, ... + }, '0.0.1', {requires: ['dump', 'mojito-params-addon']}); -In the ``routes.json`` file below, you see parameters are set for the ``example3`` and ``example4`` -route. Notice that ``example3`` only accepts HTTP GET calls, whereas ``example4`` allows -both HTTP GET and POST calls. Storing parameters in your routing configuration allows you to -associate them with a function, an HTTP method, and a URL path. +In the ``routes.json`` file below, you see parameters are set for the +``example3`` and ``example4`` route. Notice that ``example3`` only accepts +HTTP GET calls, whereas ``example4`` allows both HTTP GET and POST calls. +Storing parameters in your routing configuration allows you to associate +them with a function, an HTTP method, and a URL path. .. code-block:: javascript @@ -148,11 +162,12 @@ associate them with a function, an HTTP method, and a URL path. In the ``example4`` function below, you find the parameters catch-all method -``params.getFromMerged``. Using ``params.getFromMerged``, you can get the query string parameters, -the POST body parameters, and the parameters set in ``routes.json`` at one time. You can also get a -specific parameter by passing a key to ``params.getFromMerged(key)``. For example, -``params.getFromMerged("from")`` would return the value "routing" from the parameters set in the -``routes.json`` shown above. +``params.getFromMerged``. Using ``params.getFromMerged``, you can get the query +string parameters, the POST body parameters, and the parameters set in +``routes.json`` at one time. You can also get a specific parameter by passing +a key to ``params.getFromMerged(key)``. For example, +``params.getFromMerged("from")`` would return the value "routing" from the + parameters set in the ``routes.json`` shown above. .. code-block:: javascript @@ -177,9 +192,12 @@ specific parameter by passing a key to ``params.getFromMerged(key)``. For exampl ); } ... + }, '0.0.1', {requires: ['dump', 'mojito-params-addon']}); -For more information, see the `Params addon <../../api/classes/Params.common.html>`_ in the Mojito -API documentation. +For more information, see the `Params addon <../../api/classes/Params.common.html>`_ in +the Mojito API documentation. + +.. _code_exs_qp-ex: Setting Up this Example ======================= @@ -193,8 +211,8 @@ To set up and run ``using_parameters``: #. Create your mojit. ``$ mojito create mojit QueryMojit`` -#. To specify that your application use ``QueryMojit``, replace the code in ``application.json`` - with the following: +#. To specify that your application use ``QueryMojit``, replace the code in + ``application.json`` with the following: .. code-block:: javascript @@ -209,8 +227,8 @@ To set up and run ``using_parameters``: } ] -#. To configure the routing for your application, replace the code in ``routes.json`` with the - following: +#. To configure the routing for your application, replace the code in ``routes.json`` with + the following: .. code-block:: javascript @@ -255,9 +273,7 @@ To set up and run ``using_parameters``: YUI.add('QueryMojit', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, + index: function(actionContext) { actionContext.done('Mojito is working.'); }, @@ -329,7 +345,7 @@ To set up and run ``using_parameters``: ); } }; - }, '0.0.1', {requires: ['dump']}); + }, '0.0.1', {requires: ['dump', 'mojito-params-addon']}); #. To display the key-value pairs from the query string parameters, create the template ``views/example1.hb.html`` with the following: @@ -346,8 +362,8 @@ To set up and run ``using_parameters``: </ul> </div> -#. To display the key-value pairs from the POST request body parameters, create the template - ``views/example2.hb.html`` with the following: +#. To display the key-value pairs from the POST request body parameters, create the + template ``views/example2.hb.html`` with the following: .. code-block:: html @@ -391,8 +407,8 @@ To set up and run ``using_parameters``: </ul> </div> -#. To display all of the available parameters, create the template ``views/example4.hb.html`` with - the following: +#. To display all of the available parameters, create the template + ``views/example4.hb.html`` with the following: .. code-block:: html @@ -424,22 +440,24 @@ To set up and run ``using_parameters``: #. From the application directory, run the server. ``$ mojito start`` -#. To see the query string parameters fetched by the controller, go to the URL with the query string - below: +#. To see the query string parameters fetched by the controller, go to the URL with the + query string below: http://localhost:8666/example1?foo=bar&bar=foo -#. To see the POST body parameters fetched by the controller, go to the URL below and submit the - form on the page. +#. To see the POST body parameters fetched by the controller, go to the URL below and + submit the form on the page. http://localhost:8666/example2 #. To see the parameters set in ``routes.json``, go to the URL below: http://localhost:8666/example3 -#. To see the query string parameters, the post body parameters, and those set in ``routes.json``, - go to the URL below and submit the form on the page: +#. To see the query string parameters, the post body parameters, and those set in + ``routes.json``, go to the URL below and submit the form on the page: http://localhost:8666/example4?foo=bar&bar=foo +.. _code_exs_qp-src: + Source Code =========== diff --git a/docs/dev_guide/code_exs/route_config.rst b/docs/dev_guide/code_exs/route_config.rst index 0677f777f..6340cd653 100644 --- a/docs/dev_guide/code_exs/route_config.rst +++ b/docs/dev_guide/code_exs/route_config.rst @@ -6,18 +6,23 @@ Configuring Routing **Difficulty Level:** Beginning +.. _code_exs_routing-summary: + Summary ======= -This example shows how to configure routing for your Mojito application. In Mojito, routing is the -mapping of URLs to mojit actions. +This example shows how to configure routing for your Mojito application. +In Mojito, routing is the mapping of URLs to mojit actions. + +.. _code_exs_routing-notes: Implementation Notes ==================== -Before you create routes for your application, you need to specify one or more mojit instances that -can be mapped to URLs. In the ``application.json`` below, the ``mapped_mojit`` instance of -``RoutingMojit`` is created, which can then be associated in a route defined in ``routes.json``. +Before you create routes for your application, you need to specify one or +more mojit instances that can be mapped to URLs. In the ``application.json`` +below, the ``mapped_mojit`` instance of ``RoutingMojit`` is created, which +can then be associated in a route defined in ``routes.json``. .. code-block:: javascript @@ -32,12 +37,13 @@ can be mapped to URLs. In the ``application.json`` below, the ``mapped_mojit`` i } ] -The example ``routes.json`` below associates the ``mapped_mojit`` instance defined in -``application.json`` with a path and explicitly calls the ``index`` action. If the controller for -``RoutingMojit`` had the function ``myFunction``, you would use the following to call it: -``mapped_mojit.myFunction``. Based on the ``custom-route`` route below, when an HTTP GET call is -made on the URL ``http:{domain}:8666/custom-route``, the ``index`` action is called from the -``custom-route`` instance. +The example ``routes.json`` below associates the ``mapped_mojit`` instance +defined in ``application.json`` with a path and explicitly calls the +``index`` action. If the controller for ``RoutingMojit`` had the function +``myFunction``, you would use the following to call it: ``mapped_mojit.myFunction``. +Based on the ``custom-route`` route below, when an HTTP GET call is made on +the URL ``http:{domain}:8666/custom-route``, the ``index`` action is called +from the ``custom-route`` instance. .. code-block:: javascript @@ -52,13 +58,15 @@ made on the URL ``http:{domain}:8666/custom-route``, the ``index`` action is cal } ] -The name of the mojit instance is arbitrary. For example, the mojit instance ``mapped_mojit`` above -could have just as well been called ``mojit-route``. Just remember that the name of the mojit -instance in ``routes.json`` has to be defined and have a mojit type in ``application.json``. +The name of the mojit instance is arbitrary. For example, the mojit instance +``mapped_mojit`` above could have just as well been called ``mojit-route``. +Just remember that the name of the mojit instance in ``routes.json`` has to +be defined and have a mojit type in ``application.json``. -You can also configure multiple routes and use wildcards in ``routes.json``. The modified -``routes.json`` below uses the wildcard to configure a route for handling HTTP POST requests and -calls the method ``post_params`` from the ``post-route`` mojit instance. +You can also configure multiple routes and use wildcards in ``routes.json``. +The modified ``routes.json`` below uses the wildcard to configure a route +for handling HTTP POST requests and calls the method ``post_params`` from the +``post-route`` mojit instance. .. code-block:: javascript @@ -78,13 +86,15 @@ calls the method ``post_params`` from the ``post-route`` mojit instance. } ] -The ``routes.json`` above configures the routes below. Notice that the wildcard used for the path -of ``"another-route"`` configures Mojito to execute ``post_params`` when receiving any HTTP POST -requests. +The ``routes.json`` above configures the routes below. Notice that the wildcard +used for the path of ``"another-route"`` configures Mojito to execute +``post_params`` when receiving any HTTP POST requests. - ``http://localhost:8666/custom-route`` - ``http://localhost:8666/{any_path}`` +.. _code_exs_routing-setup: + Setting Up this Example ======================= @@ -97,8 +107,8 @@ To set up and run ``configure_routing``: #. Create your mojit. ``$ mojito create mojit RoutingMojit`` -#. To create an instance of ``RoutingMojit``, replace the code in ``application.json`` with the - following: +#. To create an instance of ``RoutingMojit``, replace the code in + ``application.json`` with the following: .. code-block:: javascript @@ -114,8 +124,8 @@ To set up and run ``configure_routing``: } ] -#. To map routes to specific actions of the mojit instance, replace the code in ``routes.json`` with - the following: +#. To map routes to specific actions of the mojit instance, replace the + code in ``routes.json`` with the following: .. code-block:: javascript @@ -140,20 +150,19 @@ To set up and run ``configure_routing``: } ] - The ``mapped_mojit`` instance is created in ``application.json`` and configured here to be used - when HTTP GET calls are made on the paths ``/index`` or ``/show``. + The ``mapped_mojit`` instance is created in ``application.json`` and + configured here to be used when HTTP GET calls are made on the paths + ``/index`` or ``/show``. #. Change to ``mojits/RoutingMojit``. -#. Modify your controller to contain the ``index`` and ``show`` actions by replacing the code in - ``controller.server.js`` with the following: +#. Modify your controller to contain the ``index`` and ``show`` actions by + replacing the code in ``controller.server.js`` with the following: .. code-block:: javascript YUI.add('RoutingMojit', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, + index: function(ac) { ac.done(route_info(ac)); }, @@ -162,36 +171,46 @@ To set up and run ``configure_routing``: } }; // Builds object containing route information - function route_info(ac){ - var methods = ""; - var name = ""; - var action = ac.action; - var path = ac.http.getRequest().url; - if(path==="/" && action==="index"){ + function route_info(ac) { + var methods = "", + name = "", + action = ac.action, + path = ac.http.getRequest().url; + ac.url.getRouteMaker(); + if (action === "index" && path === "/") { name = ac.app.routes.root_route.name; Object.keys(ac.app.routes.root_route.verbs).forEach(function(n) { - methods += n + ", "; + methods += n + ", "; }); - } else if(action==="index"){ - path = ac.app.routes.index_route.path; + } else if (action === "index") { name = ac.app.routes.index_route.name; Object.keys(ac.app.routes.index_route.verbs).forEach(function(n) { methods += n + ", "; }); - }else { - path = ac.app.routes.show_route.path; + } else { name = ac.app.routes.show_route.name; - Object.keys(ac.app.routes.show_route.verbs).forEach(function(n) { - methods += n + ", "; + Object.keys(ac.app.routes.show_route.verbs).forEach(function(n) { + methods += n + ", "; }); } return { "path": path, "name": name, - "methods": methods.replace(/, $/,"") + "methods": methods.replace(/, $/, "") }; } - }, '0.0.1', {requires: []}); + Y.namespace('mojito.controllers')[NAME] = { + init: function (config) { + this.config = config; + }, + index: function (ac) { + ac.done(route_info(ac)); + }, + show: function (ac) { + ac.done(route_info(ac)); + } + }; + }, '0.0.1', {requires: ['mojito-url-addon', 'mojito-http-addon']}); #. To display your route information in your ``index`` template, replace the content of ``index.hb.html`` with the following: @@ -204,8 +223,8 @@ To set up and run ``configure_routing``: <b>Route Name:</b> {{name}} </div> -#. To display your route information in your ``show`` template, create the file ``show.hb.html`` - with the following: +#. To display your route information in your ``show`` template, create the file + ``show.hb.html`` with the following: .. code-block:: html @@ -221,6 +240,8 @@ To set up and run ``configure_routing``: http://localhost:8666/show +.. _code_exs_routing-src: + Source Code =========== diff --git a/docs/dev_guide/code_exs/scroll_views.rst b/docs/dev_guide/code_exs/scroll_views.rst index 8e461d51e..4992119ce 100644 --- a/docs/dev_guide/code_exs/scroll_views.rst +++ b/docs/dev_guide/code_exs/scroll_views.rst @@ -6,28 +6,34 @@ Including YUI Modules in Views **Difficulty Level:** Intermediate +.. _code_exs_yui_views-summary: + Summary ======= This example shows how to include the -`YUI ScrollView Module <http://developer.yahoo.com/yui/3/scrollview/>`_ in your mojit's template. +`YUI ScrollView Module <http://developer.yahoo.com/yui/3/scrollview/>`_ +in your mojit's template. The following topics will be covered: - embedding the YUI ScrollView Module in the template - implementing a scrolling content widget +.. _code_exs_yui_views-notes: + Implementation Notes ==================== -The following screenshots show you how the application appears on different devices. +The following screenshots show you how the application appears on different +devices. .. image:: images/scroll_view.preview.gif :height: 368px :width: 401px -In the ``application.json`` file for this code example, the customized CSS are specified in the -``assets`` array as seen below. +In the ``application.json`` file for this code example, the customized +CSS are specified in the ``assets`` array as seen below. .. code-block:: javascript @@ -55,17 +61,16 @@ In the ``application.json`` file for this code example, the customized CSS are s } ] -The mojit controller provides the photo URLs for the scrollable content widget. In the -``controller.server.js`` below, the ``photos`` array that contains the photo URLs and the text for -the image ``alt`` attribute is passed to the ``index`` template. +The mojit controller provides the photo URLs for the scrollable content widget. +In the ``controller.server.js`` below, the ``photos`` array that contains the +photo URLs and the text for the image ``alt`` attribute is passed to the +``index`` template. .. code-block:: javascript YUI.add('scroll', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, + index: function(ac) { // Populate Template ac.done({ @@ -93,11 +98,12 @@ the image ``alt`` attribute is passed to the ``index`` template. }; }, '0.0.1', {requires: ['mojito']}); -In the ``index.hb.html`` below, the YUI ScrollView module is included with ``YUI.use``. To create -the scrolling content widget, you need to create a container, a header, and content frame with -``div`` tags that use YUI-specific IDs and render a ScrollView object. For detailed instructions, -see the `Getting Started <http://developer.yahoo.com/yui/3/scrollview/#start>`_ section on the -YUI 3: ScrollView page. +In the ``index.hb.html`` below, the YUI ScrollView module is included with +``YUI.use``. To create the scrolling content widget, you need to create a +container, a header, and content frame with ``div`` tags that use YUI-specific +IDs and render a ScrollView object. For detailed instructions, see the +`Getting Started <http://developer.yahoo.com/yui/3/scrollview/#start>`_ section +on the YUI 3: ScrollView page. .. code-block:: html @@ -145,8 +151,10 @@ YUI 3: ScrollView page. }, "img");}); </script> +.. _code_exs_yui_views-setup: + Setting Up this Example -####################### +======================= To set up and run ``scroll_views``: @@ -157,7 +165,8 @@ To set up and run ``scroll_views``: #. Create your mojit. ``$ mojito create mojit scroll`` -#. To configure you application, replace the code in ``application.json`` with the following: +#. To configure you application, replace the code in ``application.json`` with the + following: .. code-block:: javascript @@ -185,8 +194,9 @@ To set up and run ``scroll_views``: } ] -#. To configure routing to call the ``index`` action from the instance of the ``HTMLFrameMojit`` - when an HTTP GET call is made on the route path, replace the code in ``routes.json`` with the following: +#. To configure routing to call the ``index`` action from the instance of the + ``HTMLFrameMojit`` when an HTTP GET call is made on the route path, replace + the code in ``routes.json`` with the following: .. code-block:: javascript @@ -202,16 +212,14 @@ To set up and run ``scroll_views``: ] #. Change to ``mojits/scroll``. -#. To have the controller send image data to the template for the scrolling widget, replace the code - in ``controller.server.js`` with the following: +#. To have the controller send image data to the template for the scrolling + widget, replace the code in ``controller.server.js`` with the following: .. code-block:: javascript YUI.add('scroll', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, + index: function(ac) { // Populate Template ac.done({ @@ -239,7 +247,8 @@ To set up and run ``scroll_views``: }; }, '0.0.1', {requires: []}); -#. To modify the ``index`` template, replace the code in ``views/index.hb.html`` with the following: +#. To modify the ``index`` template, replace the code in ``views/index.hb.html`` with the + following: .. code-block:: html @@ -287,8 +296,8 @@ To set up and run ``scroll_views``: }, "img");}); </script> -#. To add CSS for the ``index`` template, replace the contents of ``assets/index.css`` with the - following: +#. To add CSS for the ``index`` template, replace the contents of ``assets/index.css`` + with the following: .. code-block:: css @@ -367,6 +376,8 @@ To set up and run ``scroll_views``: http://localhost:8666 +.. _code_exs_yui_views-src: + Source Code =========== diff --git a/docs/dev_guide/code_exs/simple_logging.rst b/docs/dev_guide/code_exs/simple_logging.rst index 53be984a1..e0aede048 100644 --- a/docs/dev_guide/code_exs/simple_logging.rst +++ b/docs/dev_guide/code_exs/simple_logging.rst @@ -6,10 +6,13 @@ Simple Logging **Difficulty:** Intermediate +.. _code_exs_logging-summary: + Summary ======= -This example shows how to configure the log levels for the client and the server in Mojito. +This example shows how to configure the log levels for the client and the +server in Mojito. The following topics will be covered: @@ -17,15 +20,20 @@ The following topics will be covered: - displaying client-side and server-side logging - using ``Y.log`` to set log levels +.. _code_exs_logging-notes: + Implementation Notes ==================== +.. _logging_notes-config: + Log Configuration ----------------- -Logging is configured in the ``application.json`` file with the ``log`` object. The ``log`` object -can contain a ``client`` object and/or a ``server`` object to configure logging for the client and -server respectively. In the example ``log`` object below, you can see that you can configure the +Logging is configured in the ``application.json`` file with the ``log`` +object. The ``log`` object can contain a ``client`` object and/or a +``server`` object to configure logging for the client and server respectively. +In the example ``log`` object below, you can see that you can configure the levels and some elements of the output for logs. See `Log Defaults <../topics/mojito_logging.html#log-defaults>`_ for the list of configuration properties and their default values. @@ -45,11 +53,13 @@ properties and their default values. } } +.. _logging_notes-levels: + Log Levels ---------- -Mojito has the following five log levels that you configure in ``application.json`` or set -with ``Y.log``. +Mojito has the following five log levels that you configure in +``application.json`` or set with ``Y.log``. - ``DEBUG`` - ``INFO`` @@ -57,27 +67,33 @@ with ``Y.log``. - ``ERROR`` - ``MOJITO`` -Setting a log level of ``WARN`` will filter out all ``DEBUG`` and ``INFO`` messages, while ``WARN``, -``ERROR``, and ``MOJITO`` log messages will be processed. To see all log messages, set the log level -to ``DEBUG``. The ``MOJITO`` log level is for showing Mojito framework-level logging that indicate -important framework events are occurring. +Setting a log level of ``WARN`` will filter out all ``DEBUG`` and ``INFO`` +messages, while ``WARN``, ``ERROR``, and ``MOJITO`` log messages will be processed. +To see all log messages, set the log level to ``DEBUG``. The ``MOJITO`` log level +is for showing Mojito framework-level logging that indicate important framework +events are occurring. + +.. _logging_notes-set_levels: Setting Log Level with Y.log ---------------------------- -The function ``Y.log`` takes two parameters. The first parameter is the log message, and the second -parameter is used to indicate the log level. When the second parameter is omitted, the log message -will be reported at the default or configured log level. +The function ``Y.log`` takes two parameters. The first parameter is the log +message, and the second parameter is used to indicate the log level. When the +second parameter is omitted, the log message will be reported at the default +or configured log level. -For example, the first use of ``Y.log`` below will report the message at the log level that is -configured in ``application.json`` or use the default. The second use of ``Y.log`` will -use the log level ``INFO``. +For example, the first use of ``Y.log`` below will report the message at the +log level that is configured in ``application.json`` or use the default. The +second use of ``Y.log`` will use the log level ``INFO``. .. code-block:: javascript Y.log("This message will be reported at the log level set in application.json or the default level."); Y.log("This log message will be reported at the INFO log level.", "info"); +.. _code_exs_logging-setup: + Setting Up this Example ======================= @@ -90,8 +106,8 @@ To set up and run ``simple_logging``: #. Create your mojit. ``$ mojito create mojit log`` -#. To configure the log levels for the client and server, replace the code in ``application.json`` - with the following: +#. To configure the log levels for the client and server, replace the code in + ``application.json`` with the following: .. code-block:: javascript @@ -140,17 +156,15 @@ To set up and run ``simple_logging``: ] #. Change to ``mojits/log``. -#. Modify your controller so that one log message uses the default log level and one log message has - the log level set by ``Y.log`` by replacing the code in ``controller.server.js`` with the - following: +#. Modify your controller so that one log message uses the default log level and one log + message has the log level set by ``Y.log`` by replacing the code in + ``controller.server.js`` with the following: .. code-block:: javascript YUI.add('log', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, + index: function(ac) { Y.log('[CONTROLLER]: entering into controller index (...)',"info"); var today = new Date(), @@ -183,7 +197,8 @@ To set up and run ``simple_logging``: }; }, '0.0.1', { requires: ['mojito']}); -#. To display your client logging, replace the content of ``binders/index.js`` with the following: +#. To display your client logging, replace the content of ``binders/index.js`` with the + following: .. code-block:: javascript @@ -200,7 +215,8 @@ To set up and run ``simple_logging``: }; }, '0.0.1', {requires: ['mojito-client']}); -#. Modify the default template by replacing the code in ``views/index.hb.html`` with the following: +#. Modify the default template by replacing the code in ``views/index.hb.html`` with the + following: .. code-block:: html @@ -219,14 +235,18 @@ To set up and run ``simple_logging``: #. From the application directory, run the server. ``$ mojito start`` -#. Open the URL below in a browser and look at the output from the Mojito server. You should see the - log messages from the controller that start with the string "\[CONTROLLER]:". Notice that the two - messages have different log levels. +#. Open the URL below in a browser and look at the output from the Mojito + server. You should see the log messages from the controller that start + with the string "\[CONTROLLER]:". Notice that the two messages have + different log levels. http://localhost:8666/ -#. Open your browser's developer console, such as Firebug, and view the console logs. You should see - the client log messages from the binder that start with the string "\[BINDER]". +#. Open your browser's developer console, such as Firebug, and view the console + logs. You should see the client log messages from the binder that start with + the string "\[BINDER]". + +.. _code_exs_logging-src: Source Code =========== diff --git a/docs/dev_guide/code_exs/simple_view_template.rst b/docs/dev_guide/code_exs/simple_view_template.rst index 3bd964202..597c9d8e3 100644 --- a/docs/dev_guide/code_exs/simple_view_template.rst +++ b/docs/dev_guide/code_exs/simple_view_template.rst @@ -6,17 +6,19 @@ Creating a Simple View with Handlebars **Difficulty Level:** Beginning +.. _code_exs_view-summary: + Summary ======= This example shows how to create a simple view for Mojito applications with -`Handlebars <http://handlebarsjs.com/>`_. Note that because Handlebars is a superset -of `Mustache <http://mustache.github.com/>`_, there is an overlap of some syntax and nomenclature. - +`Handlebars <http://handlebarsjs.com/>`_. Note that because Handlebars is a +superset of `Mustache <http://mustache.github.com/>`_, there is an overlap +of some syntax and nomenclature. -Mojito views are template files that are rendered into HTML and served to a device. -These template files are simply called *templates* in this example and throughout the -Mojito documentation. +Mojito views are template files that are rendered into HTML and served to a +device. These template files are simply called *templates* in this example +and throughout the Mojito documentation. The following topics will be covered: @@ -25,20 +27,24 @@ The following topics will be covered: - Handlebars template basics - passing data to the template +.. _code_exs_view-notes: + Implementation Notes ==================== -In the following screenshot, you see the HTML page that was rendered from the template. +In the following screenshot, you see the HTML page that was rendered from +the template. .. image:: images/simple_view_preview.jpg :height: 288px :width: 226px -In Mojito applications, the controller is responsible for passing data to the template. From -the below code snippet taken from ``controller.server.js``, you see the ``index`` function -creating a ``data`` object and passing it to the ``done`` method. The ``done`` method called on -``ac``, the `ActionContext <../../api/classes/ActionContext.html>`_ object, sends the ``data`` -object to the template ``index.hb.html``. +In Mojito applications, the controller is responsible for passing data to +the template. From the below code snippet taken from ``controller.server.js``, +you see the ``index`` function creating a ``data`` object and passing it to the +``done`` method. The ``done`` method called on ``ac``, the +`ActionContext <../../api/classes/ActionContext.html>`_ object, sends the +``data`` object to the template ``index.hb.html``. .. code-block:: javascript @@ -59,16 +65,18 @@ object to the template ``index.hb.html``. }; ... -In the ``index`` template of this code example, the properties of the ``data`` object are -placed in Handlebars expressions that are evaluated by Mojito when the template is rendered. -In Handlebars templates, the property names in double braces, such as ``{{type}}``, are expressions. +In the ``index`` template of this code example, the properties of the ``data`` +object are placed in Handlebars expressions that are evaluated by Mojito when +the template is rendered. In Handlebars templates, the property names in double +braces, such as ``{{type}}``, are expressions. The double braces with a pound are used for lists or conditional -expression, such as ``{{#show}...{{/show}``. Handlebars also has a built-in conditional structure -that allow you to form the same conditional expression in the following way: ``{{#if show}}...{{/if}}`` +expression, such as ``{{#show}...{{/show}``. Handlebars also has a built-in +conditional structure that allow you to form the same conditional expression +in the following way: ``{{#if show}}...{{/if}}`` -You also use double braces with a pound to access properties within an object, which is how the -``hours`` property of the ``time`` object is accessed here. +You also use double braces with a pound to access properties within an object, +which is how the ``hours`` property of the ``time`` object is accessed here. .. code-block:: html @@ -86,8 +94,10 @@ You also use double braces with a pound to access properties within an object, w <div>html: {{{html}}}</div> </div> -See the `Handlebars expressions <http://handlebarsjs.com/expressions.html>`_ in the Handlebars -documentation for more information. +See the `Handlebars expressions <http://handlebarsjs.com/expressions.html>`_ +in the Handlebars documentation for more information. + +.. _code_exs_view-setup: Setting Up This Example ======================= @@ -101,8 +111,8 @@ To set up and run ``simple_view``: #. Create your mojit. ``$ mojito create mojit simple`` -#. To specify that your application use the ``simple`` mojit, replace the code in - ``application.json`` with the following: +#. To specify that your application use the ``simple`` mojit, replace the + code in ``application.json`` with the following: .. code-block:: javascript @@ -117,8 +127,8 @@ To set up and run ``simple_view``: } ] -#. To configure the routing for your application, replace the code in ``routes.json`` with the - following: +#. To configure the routing for your application, replace the code in + ``routes.json`` with the following: .. code-block:: javascript @@ -134,16 +144,14 @@ To set up and run ``simple_view``: ] #. Change to ``mojits/simple``. -#. Modify the mojit controller to pass data to the view by replacing the code in - ``controller.server.js`` with the following: +#. Modify the mojit controller to pass data to the view by replacing the + code in ``controller.server.js`` with the following: .. code-block:: javascript YUI.add('simple', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, + index: function(ac) { var today = new Date(), data = { @@ -160,8 +168,8 @@ To set up and run ``simple_view``: }; }, '0.0.1', {requires: []}); -#. Modify your ``index`` template by replacing the code in ``views/index.hb.html`` with the - following: +#. Modify your ``index`` template by replacing the code in ``views/index.hb.html`` + with the following: .. code-block:: html @@ -185,6 +193,8 @@ To set up and run ``simple_view``: http://localhost:8666 +.. _code_exs_view-src: + Source Code =========== diff --git a/docs/dev_guide/code_exs/view_engines.rst b/docs/dev_guide/code_exs/view_engines.rst index 047dc6c42..998a72288 100644 --- a/docs/dev_guide/code_exs/view_engines.rst +++ b/docs/dev_guide/code_exs/view_engines.rst @@ -6,13 +6,16 @@ Creating and Using a View Engine Addon **Difficulty Level:** Intermediate +.. _code_exs_view_engine_addon-summary: + Summary ======= -This example shows how to install a third-party rendering engine (Embedded Javascript), create a -view engine addon that uses the installed rendering engine, and create a template for the view -engine. Mojito uses the `Handlebars <https://github.com/wycats/handlebars.js/>`_ rendering engine -by default. +This example shows how to install a third-party rendering engine +(Embedded Javascript), create a view engine addon that uses the +installed rendering engine, and create a template for the view engine. +Mojito uses the `Handlebars <https://github.com/wycats/handlebars.js/>`_ +rendering engine by default. The following topics will be covered: @@ -20,41 +23,48 @@ The following topics will be covered: - creating a view engine addon - using Embedded JavaScript (EJS) in the template +.. _code_exs_view_engine_addon-notes: Implementation Notes ==================== -Before you create your application, you should take a look at the following sections to better -understand how the application works. The focus here is to give you a practical example that you can -use to add your own view engines and also to show some of important points of using view engines in -Mojito applications. For more comprehensive but less hands-on documentation, see +Before you create your application, you should take a look at the following +sections to better understand how the application works. The focus here is +to give you a practical example that you can use to add your own view engines +and also to show some of important points of using view engines in Mojito +applications. For more comprehensive but less hands-on documentation, see `Developer Topics: View Engines <../topics/mojito_extensions.html#view-engines>`_. +.. _ve_addon_notes-what: What Is a View Engine? ---------------------- -A view engine is code that applies data returned by the controller to a view. This is most often -done by interpreting the view as a template. View engines in Mojito can function at either the -application or mojit level. This example uses an application-level view engine addon, allowing -multiple mojits to use it although the example only uses one mojit. +A view engine is code that applies data returned by the controller to a view. +This is most often done by interpreting the view as a template. View engines +in Mojito can function at either the application or mojit level. This example +uses an application-level view engine addon, allowing multiple mojits to use it +although the example only uses one mojit. +.. _ve_addon_notes-install: Installing a Rendering Engine ----------------------------- -You could write your own rendering engine or copy code into your Mojito application, but this -example follows the most common use case of installing a rendering engine with ``npm``. We will be -installing the rendering engine `EJS <http://embeddedjs.com/>`_ with ``npm``. +You could write your own rendering engine or copy code into your Mojito +application, but this example follows the most common use case of installing +a rendering engine with ``npm``. We will be installing the rendering engine + `EJS <http://embeddedjs.com/>`_ with ``npm``. -Because your Mojito application is simply a ``npm`` module, you can have a ``node_modules`` -directory for locally installing other modules. Thus, from your application directory, you would -use the following ``npm`` command to install ``ejs``: +Because your Mojito application is simply a ``npm`` module, you can have a +``node_modules`` directory for locally installing other modules. Thus, from +your application directory, you would use the following ``npm`` command to +install ``ejs``: ``{app_dir}/ $ npm install ejs`` -After you have installed ``ejs``, a ``node_modules`` directory will be created with the contents -similar to the following: +After you have installed ``ejs``, a ``node_modules`` directory will be created +with the contents similar to the following: :: @@ -83,21 +93,26 @@ similar to the following: │   └── fixtures/ ... +.. _ve_addon_notes-create: Creating the View Engine Addon ------------------------------ The view engine addon like other addons is simply a YUI module that lives in the -``addons/view-engines`` directory. For the application-level view engine addons that this example -is using, the view engine addon will be in ``{app_dir}/addons/view-engines``. +``addons/view-engines`` directory. For the application-level view engine addons +that this example is using, the view engine addon will be in +``{app_dir}/addons/view-engines``. + +.. _ve_addon_create-req: Requirements ############ The view engine addon must have the following: -- a ``YUI.add`` statement to register the addon. For example, we register the view engine addon with - the name ``addons-viewengine-ejs`` in our code example as seen below. +- a ``YUI.add`` statement to register the addon. For example, we register the + view engine addon with the name ``addons-viewengine-ejs`` in our code example + as seen below. .. code-block:: javascript @@ -107,8 +122,9 @@ The view engine addon must have the following: }, '0.1.0', {requires: []}); -- a prototype of the object has the following two methods ``render`` and ``compiler`` as shown below. - We will look at the ``render`` and ``compile`` methods more closely in the next section. +- a prototype of the object has the following two methods ``render`` and + ``compiler`` as shown below. We will look at the ``render`` and ``compile`` + methods more closely in the next section. .. code-block:: javascript @@ -124,8 +140,8 @@ The view engine addon must have the following: } ... -- an object that is assigned to ``Y.mojito.addons.viewEngines.{view_engine_name}``. In our example, - the constructor ``EjsAdapter`` is assigned to the namespace +- an object that is assigned to ``Y.mojito.addons.viewEngines.{view_engine_name}``. + In our example, the constructor ``EjsAdapter`` is assigned to the namespace ``Y.namespace('mojito.addons.viewEngines').ejs`` or ``Y.mojito.addons.viewEngines.ejs``. .. code-block:: javascript @@ -137,17 +153,19 @@ The view engine addon must have the following: } ... Y.namespace('mojito.addons.viewEngines').ejs = EjsAdapter; - + +.. _ve_addon_create-render_compile: render and compile ################## -The ``render`` method renders the template and sends the output to the methods ``adapter.flush`` or -``adapter.done`` that execute and return the page to the client. +The ``render`` method renders the template and sends the output to the +methods ``adapter.flush`` or ``adapter.done`` that execute and return the +page to the client. -The implementation of how the ``render`` method is up to the developer. You could write code or use -a library to render the template, but in this example we use the instance ``ejs`` to -compile the view. +The implementation of how the ``render`` method is up to the developer. +You could write code or use a library to render the template, but in this +example we use the instance ``ejs`` to compile the view. .. code-block:: javascript @@ -184,9 +202,9 @@ compile the view. }, ... -The ``compile`` method is required to run the command ``mojito compile views``. In our example, -the ``compile`` method also reads the template file and returns a string to ``render`` -so that it can be rendered by ``ejs``. +The ``compile`` method is required to run the command ``mojito compile views``. +In our example, the ``compile`` method also reads the template file and returns +a string to ``render`` so that it can be rendered by ``ejs``. .. code-block:: javascript @@ -197,25 +215,29 @@ so that it can be rendered by ``ejs``. } -In the above code snippet, the ``compile`` method simply returns the template file to the -``render`` method, where the instance of the EJS rendering engine calls ``render`` to render -the template file into a string. The implementation of the ``compile`` method in the -addon could have been written to call ``ejs.render``. +In the above code snippet, the ``compile`` method simply returns the template +file to the ``render`` method, where the instance of the EJS rendering engine +calls ``render`` to render the template file into a string. The implementation +of the ``compile`` method in the addon could have been written to call +``ejs.render``. + +.. _ve_addon_notes-ejs_templates: EJS Templates ------------- -EJS is similar to ``ERB`` that is used by `Ruby on Rails <http://rubyonrails.org/>`_. The embedded -JavaScript is wrapped in ``<%`` and ``%>``. If you want to evaluate code so that -the returned value is inserted into the HTML string, you use ``<%=`` as seen -below, where the variable ``title`` is substituted with a value. +EJS is similar to ``ERB`` that is used by +`Ruby on Rails <http://rubyonrails.org/>`_. The embedded JavaScript is +wrapped in ``<%`` and ``%>``. If you want to evaluate code so that +the returned value is inserted into the HTML string, you use ``<%=`` as +seen below, where the variable ``title`` is substituted with a value. .. code-block:: html <h2> <%= title %></h2> -You can do most of the same things with EJS as you can with JavaScript. For example, -you can iterate through an array in the same way as shown here: +You can do most of the same things with EJS as you can with JavaScript. +For example, you can iterate through an array in the same way as shown here: .. code-block:: html @@ -225,14 +247,15 @@ you can iterate through an array in the same way as shown here: <% } %> </ul> -EJS also has view helpers for creating links and forms, much like ``ERB``. See -`Getting Started with EJS <http://embeddedjs.com/getting_started.html>`_ for more information. +EJS also has view helpers for creating links and forms, much like ``ERB``. +See `Getting Started with EJS <http://embeddedjs.com/getting_started.html>`_ +for more information. +.. _code_exs_view_engine_addon-setup: Setting Up this Example ======================= - To set up and run ``adding_view_engines``: #. Create your application. @@ -243,8 +266,8 @@ To set up and run ``adding_view_engines``: ``$ mojito create mojit myMojit`` -#. To specify that your application use ``myMojit``, replace the code in ``application.json`` with - the following: +#. To specify that your application use ``myMojit``, replace the code in + ``application.json`` with the following: .. code-block:: javascript @@ -260,8 +283,8 @@ To set up and run ``adding_view_engines``: ] -#. To configure routing so controller functions using different templates are used, replace the code - in ``routes.json`` with the following: +#. To configure routing so controller functions using different templates are + used, replace the code in ``routes.json`` with the following: .. code-block:: javascript @@ -335,9 +358,6 @@ To set up and run ``adding_view_engines``: Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, default_ve: function(ac) { ac.done({ "title": "Handlebars at work!", @@ -360,8 +380,8 @@ To set up and run ``adding_view_engines``: }; }, '0.0.1', {requires: ['mojito', 'myMojitModelFoo']}); -#. Create the template ``views/default_ve.hb.html`` that uses Handlebar expressions with the - following: +#. Create the template ``views/default_ve.hb.html`` that uses Handlebar + expressions with the following: .. code-block:: html @@ -382,7 +402,8 @@ To set up and run ``adding_view_engines``: </ul> </div> -#. Create the template ``views/added_ve.ejs.html`` that uses EJS with the following: +#. Create the template ``views/added_ve.ejs.html`` that uses EJS with the + following: .. code-block:: html @@ -399,16 +420,19 @@ To set up and run ``adding_view_engines``: #. From your application directory, start Mojito. ``$ mojito start`` -#. Open the following URL in your browser to see the template rendered by the Handlebars - rendering engine. +#. Open the following URL in your browser to see the template rendered by the + Handlebars rendering engine. `http://localhost:8666/ <http://localhost:8666/>`_ -#. Now see the template rendered by the EJS rendering engine at the following URL: +#. Now see the template rendered by the EJS rendering engine at the following + URL: `http://localhost:8666/ejs <http://localhost:8666/ejs>`_ -#. Great, your application is using two different rendering engines. You should now be ready to add - your own view engine that uses a rendering engine such as Jade. +#. Great, your application is using two different rendering engines. You should + now be ready to add your own view engine that uses a rendering engine such as + Jade. +.. _code_exs_view_engine_addon-src: Source Code =========== diff --git a/docs/dev_guide/code_exs/views.rst b/docs/dev_guide/code_exs/views.rst index 8cbc223a4..da3c04275 100644 --- a/docs/dev_guide/code_exs/views.rst +++ b/docs/dev_guide/code_exs/views.rst @@ -2,8 +2,8 @@ Views ===== -These examples show you how to create templates, use Handlebars expressions, and pass data from the -mojit controller to the templates. +These examples show you how to create templates, use Handlebars expressions, +and pass data from the mojit controller to the templates. .. toctree:: :maxdepth: 1 diff --git a/docs/dev_guide/code_exs/views_multiple_devices.rst b/docs/dev_guide/code_exs/views_multiple_devices.rst index 96be29c2b..46875e5d0 100644 --- a/docs/dev_guide/code_exs/views_multiple_devices.rst +++ b/docs/dev_guide/code_exs/views_multiple_devices.rst @@ -6,12 +6,14 @@ Creating Views for Different Devices **Difficulty Level:** Intermediate +.. _code_exs_device_views-summary: + Summary ======= -This example shows how to create specialized views for different wireless devices, such as iPhones, -BlackBerries, and Android phones. Each device uses different templates but the -same data from the controller. +This example shows how to create specialized views for different wireless +devices, such as iPhones, BlackBerries, and Android phones. Each device +uses different templates but the same data from the controller. The following topics will be covered: @@ -20,10 +22,13 @@ The following topics will be covered: - using query parameters to select the device view - using the user agent to select the device view +.. _code_exs_device_views-notes: + Implementation Notes ==================== -The following screenshots show you how the application appears on different devices. +The following screenshots show you how the application appears on different +devices. .. image:: images/preview.iphone.gif :height: 368px @@ -37,19 +42,24 @@ The following screenshots show you how the application appears on different devi :height: 368px :width: 401px +.. _device_views_notes-config: + Configuring Application to Use Device-Specific Templates -------------------------------------------------------- +.. _device_views-device_contexts: + Context Configurations ###################### -Mojito allows you to map contexts to a set of configurations based on runtime factors. -The context is defined by the ``setting`` property in the JSON configuration files. -The default value for ``setting`` is ``master``. Mojito will first look to see if a base context was -set on the command line with the ``--context`` option, then at the HTTP headers and -query string. In this example, we want contexts defined for different devices, -so, in the ``application.json`` file, we'll define contexts -for Android, Blackberry, and iPhone with the following: +Mojito allows you to map contexts to a set of configurations based on runtime +factors. The context is defined by the ``setting`` property in the JSON +configuration files. The default value for ``setting`` is ``master``. +Mojito will first look to see if a base context wasset on the command line +with the ``--context`` option, then at the HTTP headers and query string. +In this example, we want contexts defined for different devices, so, in +the ``application.json`` file, we'll define contexts for Android, Blackberry, +and iPhone with the following: .. code-block:: javascript @@ -72,16 +82,20 @@ for Android, Blackberry, and iPhone with the following: } ] -You can also have contexts for environment, language, and region configurations, or create -custom contexts. See `Using Context Configurations <../topics/mojito_using_contexts.html>`_. +You can also have contexts for environment, language, and region configurations, +or create custom contexts. +See `Using Context Configurations <../topics/mojito_using_contexts.html>`_. + +.. _device_context-select: selector Property ################# -How does Mojito know which template file to use for a device? Mojito identifies files resources -using the ``selector`` property in configuration files. In the ``application.json`` file, -we can use the contexts for our devices with the ``selector`` property so Mojito knows what -file resources to use for contexts associated with devices. +How does Mojito know which template file to use for a device? Mojito identifies +files resources using the ``selector`` property in configuration files. In the +``application.json`` file, we can use the contexts for our devices with the +``selector`` property so Mojito knows what file resources to use for contexts +associated with devices. .. code-block:: javascript @@ -98,25 +112,30 @@ file resources to use for contexts associated with devices. "selector": "iphone" } -For example, when given the context ``device:iphone``, Mojito will look for file resources that have -the identifier ``iphone``. For more information about the ``selector`` property, +For example, when given the context ``device:iphone``, Mojito will look for +file resources that have the identifier ``iphone``. For more information about +the ``selector`` property, see `Resource Store: selector Property <../topics/mojito_resource_store.html#selector-property>`_. +.. _device_context-determine: + Determining Context ################### -Mojito uses two ways to determine which device is making an HTTP request for a page. The first way -is to use the value assigned to the query string parameter ``device``. For example, if Mojito -received an HTTP GET request on the URL below, it would render the iPhone view into HTML and serve -the page to the device. +Mojito uses two ways to determine which device is making an HTTP request for a +page. The first way is to use the value assigned to the query string parameter +``device``. For example, if Mojito received an HTTP GET request on the URL +below, it would render the iPhone view into HTML and serve the page to the +device. :: http://localhost:8666?device=iphone -Mojito also uses the HTTP User-Agent header field to decide which view to render and serve. In this -example HTTP header, the User-Agent field indicates that the HTTP request is coming from an Android -device, so Mojito would use the Android template and serve the rendered HTML to the device. +Mojito also uses the HTTP User-Agent header field to decide which view to +render and serve. In this example HTTP header, the User-Agent field indicates +that the HTTP request is coming from an Android device, so Mojito would use +the Android template and serve the rendered HTML to the device. :: @@ -128,36 +147,44 @@ device, so Mojito would use the Android template and serve the rendered HTML to Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5 Accept-Charset: utf-8, iso-8859-1, utf-16, *;q=0.7 +.. _device_views_notes-create_templates: Creating Templates for Devices ------------------------------ For each device's customized view, you need to create a template. -This code example uses the templates to create customized views for iPhones, Android phones, and -BlackBerries. +This code example uses the templates to create customized views for +iPhones, Android phones, and BlackBerries. + +.. _device_views_templates-naming: Naming Convention for Templates ############################### -The naming convention for template files has the following syntax, where ``{selector}`` -is the string identifier (defined by the ``selector`` property) of a device, such as "iphone": +The naming convention for template files has the following syntax, where +``{selector}``is the string identifier (defined by the ``selector`` property) +of a device, such as "iphone": ``{action}.{selector}.{rendering_engine}.html`` +.. _device_views_templates-ex: + Templates for This Example ########################## -This code example uses the following template files, where ``hb`` represents -the Handlebars rendering engine: +This code example uses the following template files, where ``hb`` +represents the Handlebars rendering engine: - ``index.iphone.hb.html`` - ``index.android.hb.html`` - ``index.blackberry.hb.html`` -Thus, if an iPhone was making an HTTP GET request on the ``index`` (action) file and the template -was being rendered by the Handlebars rendering engine, Mojito would use ``index.iphone.hb.html`` -and serve the rendered view to the iPhone. +Thus, if an iPhone was making an HTTP GET request on the ``index`` (action) +file and the template was being rendered by the Handlebars rendering engine, +Mojito would use ``index.iphone.hb.html`` and serve the rendered view to the +iPhone. +.. _code_exs_device_views-setup: Setting Up this Example ======================= @@ -172,7 +199,8 @@ To set up and run ``device_views``: ``$ mojito create mojit device`` -#. To configure you application, replace the code in ``application.json`` with the following: +#. To configure you application, replace the code in ``application.json`` + with the following: .. code-block:: javascript @@ -200,7 +228,8 @@ To set up and run ``device_views``: } ] -#. To configure routing, replace the code in ``routes.json`` with the following: +#. To configure routing, replace the code in ``routes.json`` with the + following: .. code-block:: javascript @@ -216,16 +245,13 @@ To set up and run ``device_views``: ] #. Change to ``mojits/device``. - #. Replace the code in ``controller.server.js`` with the following: .. code-block:: javascript YUI.add('device', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, + /* Method corresponding to the 'index' action. * * @param ac {Object} The action context that @@ -237,7 +263,8 @@ To set up and run ``device_views``: }; }, '0.0.1', {requires: []}); -#. To modify the default template, replace the code in ``views/index.hb.html`` with the following: +#. To modify the default template, replace the code in ``views/index.hb.html`` + with the following: .. code-block:: html @@ -269,7 +296,8 @@ To set up and run ``device_views``: </body> </html> -#. For the iPhone view, create the ``views/index.iphone.hb.html`` file with the following: +#. For the iPhone view, create the ``views/index.iphone.hb.html`` file with the + following: .. code-block:: html @@ -301,7 +329,8 @@ To set up and run ``device_views``: </body> </html> -#. For the Android view, create the ``views/index.android.hb.html`` file with the following: +#. For the Android view, create the ``views/index.android.hb.html`` file with + the following: .. code-block:: html @@ -333,7 +362,8 @@ To set up and run ``device_views``: </body> </html> -#. For the BlackBerry view, create the ``views/index.blackberry.hb.html`` file with the following: +#. For the BlackBerry view, create the ``views/index.blackberry.hb.html`` file + with the following: .. code-block:: html @@ -376,6 +406,8 @@ To set up and run ``device_views``: http://localhost:8666?device=iphone +.. _code_exs_device_views-src: + Source Code =========== diff --git a/docs/dev_guide/code_exs/yui_modules.rst b/docs/dev_guide/code_exs/yui_modules.rst index 045aae2f5..74196570e 100644 --- a/docs/dev_guide/code_exs/yui_modules.rst +++ b/docs/dev_guide/code_exs/yui_modules.rst @@ -6,18 +6,24 @@ Including YUI Modules **Difficulty:** Intermediate +.. _code_exs-incl_yui_mods-summary: + Summary ======= -This example shows how to include the YUI module `Storage Lite <http://yuilibrary.com/gallery/show/storage-lite>`_ -in a Mojito application. The example uses the Storage Lite module to create a notepad application. -Any text that you input into the application will persist between page views and browser sessions. +This example shows how to include the YUI module +`Storage Lite <http://yuilibrary.com/gallery/show/storage-lite>`_ in a Mojito +application. The example uses the Storage Lite module to create a notepad +application. Any text that you input into the application will persist between +page views and browser sessions. The following topics will be covered: - adding YUI modules to the ``autoload`` directory - accessing YUI modules from a mojit +.. _code_exs-incl_yui_mods-notes: + Implementation Notes ==================== @@ -26,33 +32,39 @@ Implementation Notes Adding YUI Modules ------------------ +.. _yui_mod_impl_add-loc: + Location ######## -To add YUI modules that all your mojits can access, place the modules in the ``autoload`` directory -under the application directory. For example, YUI modules in the ``hello_world`` application -would be placed in ``hello_world/autoload``. +To add YUI modules that all your mojits can access, place the modules in the +``autoload`` directory under the application directory. For example, YUI +modules in the ``hello_world`` application would be placed in +``hello_world/autoload``. + +.. _yui_mod_impl_add-naming: File Naming Convention ###################### -YUI modules must use the following naming convention, where where ``{module_name}`` is an arbitrary -string for identifying the module and ``{affinity}`` is either ``common``, ``server``, or -``client``. +YUI modules must use the following naming convention, where where +``{module_name}`` is an arbitrary string for identifying the module and +``{affinity}`` is either ``common``, ``server``, or ``client``. ``{module_name}.{affinity}.js`` -In this code example, code is being deployed to the client, so the affinity must be either -``common`` or ``client``. +In this code example, code is being deployed to the client, so the affinity +must be either ``common`` or ``client``. .. _registering_module: Registering Module ################## -To register a module so that it is available to your mojits, pass a string that identifies the -module to the ``YUI.add`` method. From the skeleton of ``storage-lite.client.js`` below, you can see -that ``add`` method registers the module identified by the string ``'gallery-storage-lite'``. +To register a module so that it is available to your mojits, pass a string that +identifies the module to the ``YUI.add`` method. From the skeleton of +``storage-lite.client.js`` below, you can see that ``add`` method registers +the module identified by the string ``'gallery-storage-lite'``. .. code-block:: javascript @@ -60,13 +72,17 @@ that ``add`` method registers the module identified by the string ``'gallery-sto ... }, '1.0.0', { requires: ['event-base', 'event-custom', 'event-custom-complex', 'json']}); + +.. _yui_mod_impl-using: + Using a YUI Module from Mojits ------------------------------ -After registered YUI modules have been added to the ``autoload`` directory, you can load them into -your mojit code by listing them as dependencies in the ``requires`` array. In the binder -``index.js`` below, you can see that the Storage Lite module that we created and registered -in :ref:`registering_module` is listed as a dependency in the ``requires`` array. +After registered YUI modules have been added to the ``autoload`` directory, you +can load them into your mojit code by listing them as dependencies in the +``requires`` array. In the binder ``index.js`` below, you can see that the +Storage Lite module that we created and registered in :ref:`registering_module` +is listed as a dependency in the ``requires`` array. .. code-block:: javascript @@ -82,8 +98,9 @@ in :ref:`registering_module` is listed as a dependency in the ``requires`` array // See autoload/storage-lite.client.js }, '0.0.1', {requires: [ 'gallery-storage-lite' ]}); -In the ``bind`` method, ``Y.StorageLite.getItem`` and ``Y.StorageLite.setItem`` are used to get -and set persistent data. Note that you must use the ``Y`` instance to access the module. +In the ``bind`` method, ``Y.StorageLite.getItem`` and ``Y.StorageLite.setItem`` +are used to get and set persistent data. Note that you must use the ``Y`` +instance to access the module. .. code-block:: javascript @@ -102,6 +119,8 @@ and set persistent data. Note that you must use the ``Y`` instance to access the } ... +.. _code_exs-incl_yui_mods-setup: + Setting Up this Example ======================= @@ -114,8 +133,8 @@ To set up and run ``yui_module``: #. Create your mojit. ``$ mojito create mojit Notepad`` -#. To specify that your application use the ``Notepad`` mojit and be deployed to the client, replace - the code in ``application.json`` with the following: +#. To specify that your application use the ``Notepad`` mojit and be deployed + to the client, replace the code in ``application.json`` with the following: .. code-block:: javascript @@ -137,8 +156,8 @@ To set up and run ``yui_module``: } ] -#. To configure the routing for your application, replace the code in ``routes.json`` with the - following: +#. To configure the routing for your application, replace the code in + ``routes.json`` with the following: .. code-block:: javascript @@ -172,8 +191,8 @@ To set up and run ``yui_module``: }; }, '0.0.1', {requires: ['mojito']}); -#. To create the binder for getting user input and storing it with the Storage Lite module, replace - the code in ``binders/index.js`` with the following: +#. To create the binder for getting user input and storing it with the + Storage Lite module, replace the code in ``binders/index.js`` with the following: .. code-block:: javascript @@ -202,8 +221,8 @@ To set up and run ``yui_module``: // See autoload/storage-lite.client.js }, '0.0.1', {requires: [ 'gallery-storage-lite' ]}); -#. To display a form that allows users to input text, replace the code in ``views/index.hb.html`` - with the following: +#. To display a form that allows users to input text, replace the code in + ``views/index.hb.html`` with the following: .. code-block:: html @@ -211,7 +230,9 @@ To set up and run ``yui_module``: <h1>Storage Lite: Simple Notepad Example</h1> <form> <p>Anything you type in this textarea will - be stored and persisted between page views and browser sessions using the <a href="http://github.com/rgrove/storage-lite/">Storage Lite</a> YUI module by Ryan Grove.</p> + be stored and persisted between page views and browser sessions using the + <a href="http://github.com/rgrove/storage-lite/">Storage Lite</a> YUI module by + Ryan Grove.</p> <p><textarea id="notes" cols="80" rows="8"></textarea> </p> </form> @@ -223,10 +244,13 @@ To set up and run ``yui_module``: #. Go to the application at the URL below and enter some text into the form. http://localhost:8666/ -#. Point to the same URL in a new tab. You should see the same text that you entered in the form - before. -#. Open the same URL in a new browser window. Once again, you should see the same text that you - entered earlier. +#. Point to the same URL in a new tab. You should see the same text that you entered in + the form before. +#. Open the same URL in a new browser window. Once again, you should see the same text + that you entered earlier. + + +.. _code_exs-incl_yui_mods-src: Source Code =========== diff --git a/docs/dev_guide/conf.py b/docs/dev_guide/conf.py new file mode 100644 index 000000000..8eb5c4dc5 --- /dev/null +++ b/docs/dev_guide/conf.py @@ -0,0 +1,220 @@ + +# +# Cocktails documentation build configuration file, created by +# sphinx-quickstart on Wed Oct 12 18:07:15 2011. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.doctest', 'sphinx.ext.ifconfig'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['/home/y/share/htdocs/cocktails/sphinx_rst_ydn/ydn_template/'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +# project = u'Cocktails' +copyright = u'2011, Yahoo! Inc., 2011' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +#version = '0.1.0.178' +# The full version, including alpha/beta/rc tags. +#release = '0.1.0.178' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['.build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +highlight_language = 'javascript' +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'ydntheme' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +html_theme_path = ['/home/y/share/htdocs/cocktails/sphinx_rst_ydn/ydn_template/'] + +# The name for this set of Sphinx documents. If None, it defaults to +# "<project> v<release> documentation". +html_title = 'Mojito API Overview' + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +# html_logo = 'images/molotov-cocktail_logo.png' + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +# html_favicon = 'images/Mojito.ico' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +#html_static_path = ['.static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +# html_sidebars = { +# '**':["other_links.html"] +# } + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +html_show_sourcelink = False + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a <link> tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'MojitoIntro' + + +# -- Options for LaTeX output -------------------------------------------------- + +# The paper size ('letter' or 'a4'). +#latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +#latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'Cocktails.tex', u'Cocktails Documentation', + u'Joe Catera', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'Mojito', u'Mojito Intro', + [u'Joe Catera'], 1) +] + diff --git a/docs/dev_guide/faq/index.rst b/docs/dev_guide/faq/index.rst index 1b4eb4b02..ba4c10e8e 100644 --- a/docs/dev_guide/faq/index.rst +++ b/docs/dev_guide/faq/index.rst @@ -1,6 +1,6 @@ -########### +=========== Mojito: FAQ -########### +=========== This page answers some of the most common questions we get about Mojito. For troubleshooting issues, see @@ -34,7 +34,6 @@ Mojits * :ref:`What is a mojit? <mojit_exp>` * :ref:`Can mojits have child mojits? <moj_children>` * :ref:`How do mojits share objects? <moj_objects>` -* :ref:`How can the same 'Y' instance be shared among mojits? <moj_share_y>` * :ref:`Can mojit instances be dynamically defined and then run? <moj_dynamic_creation>` * :ref:`Is there a way to make all of the resources, such as assets, addons, binders, models, of one mojit available to other mojits? <moj_resources>` * :ref:`Why does Mojito replace hyphens in the names of my mojits with underscores? <moj_names_hyphens>` @@ -180,12 +179,12 @@ General .. _moj_lazyloading: .. topic:: **Does Mojito support lazy loading?** - Yes, the Mojito framework comes with the framework mojit ``LazyLoadMojit`` specifically + Yes, the Mojito framework comes with the frame mojit ``LazyLoadMojit`` specifically for lazy loading. The ``LazyLoadMojit`` allows you to defer the loading of a mojit instance by first dispatching the ``LazyLoadMojit`` as a proxy to the client. From the client, ``LazyLoadMojit`` can then request Mojito to load the proxied mojit. This allows your Mojito application to load the page quickly and then lazily load parts of - the page. See `LazyLoadMojit <../topics/mojito_framework_mojits.html#lazyloadmojit>`_ + the page. See `LazyLoadMojit <../topics/mojito_frame_mojits.html#lazyloadmojit>`_ to learn more. ------------ @@ -207,8 +206,7 @@ General module that selects the best available local storage API supported by the browser it's running in. - `Create an addon <../topics/mojito_extensions.html#creating-new-addons>`_ that - uses a singleton or attaches data to the YUI instance. See the ``shareYUIInstance`` - property in the `configuration object <../intro/mojito_configuring.html#configuration-object>`_. + uses a singleton. - **Server-Side Caching (implementation depends on server)** @@ -228,7 +226,8 @@ General `affinity <../reference/glossary.html#affinity>`_, of the controller, models, addons, etc., that you want to run on both the client and the server. To configure Mojito to deploy application code to the client, you set the ``deploy`` property of the - application configuration to ``true``. See `Configuring Applications to Be Deployed to Client <../intro/mojito_configuring.html#configuring-applications-to-be-deployed-to-client>`_ + application configuration to ``true``. + See `Configuring Applications to Be Deployed to Client <../intro/mojito_configuring.html#configuring-applications-to-be-deployed-to-client>`_ for more information. Mojito determines the client device based on the HTTP header ``User-Agent`` or the @@ -313,7 +312,7 @@ General * Assets and data can be shared through the `template <../reference/glossary.html#view-template>`_ of a parent mojit or through a frame mojit such as - `HTMLFrameMojit <../topics/mojito_framework_mojits.html#htmlframemojit>`_ that + `HTMLFrameMojit <../topics/mojito_frame_mojits.html#htmlframemojit>`_ that creates a parent template. **Rollup/Minify Assets** @@ -344,7 +343,7 @@ General From the client, your Mojito application should lazy load assets as often as possible. For example, the `YUI ImageLoader Utility <http://yuilibrary.com/yui/docs/imageloader/>`_ can be used to help you lazy load images. You can even lazy load a mojit from the client - using the `LazyLoadMojit <../topics/mojito_framework_mojits.html#lazyloadmojit>`_. + using the `LazyLoadMojit <../topics/mojito_frame_mojits.html#lazyloadmojit>`_. ------------ @@ -394,20 +393,11 @@ Mojits See `Configuring Applications to Have Multiple Mojit <../intro/mojito_configuring.html#configuring-applications-to-have-multiple-mojits>`_ and `Composite Mojits <../topics/mojito_composite_mojits.html#composite-mojits>`_. - You can also use framework mojits, such as `HTMLFrameMojit <../topics/mojito_framework_mojits.html#htmlframemojit>`_ + You can also use frame mojits, such as `HTMLFrameMojit <../topics/mojito_frame_mojits.html#htmlframemojit>`_ that can execute one or more child mojits. ------------ -.. _moj_share_y: -.. topic:: **How can the same 'Y' instance be shared among mojits?** - - Mojito creates sandboxes for mojits, thus, each mojit has its own ``Y`` instance. - To allow mojito to share one ``Y`` instance, you set the ``shareYUIInstance: true`` in the - ``application.json`` configuration file. See the `configuration Object <../intro/mojito_configuring.html#configuration-object>`_ - for more information. - ------------- .. _moj_objects: .. topic:: **How do mojits share objects?** @@ -468,9 +458,9 @@ Configuration .. topic:: **How do I configure Mojito to deploy my application to the client?** Binders always get deployed to the client, but to deploy your controller to the - client, you need to use the `HTMLFrameMojit <../topics/mojito_framework_mojits.html#htmlframemojit>`_ + client, you need to use the `HTMLFrameMojit <../topics/mojito_frame_mojits.html#htmlframemojit>`_ and set the ``deploy`` field to ``true`` in the ``application.json`` file. See - `Deploying to Client <../topics/mojito_framework_mojits.html#deploying-to-client>`_ + `Deploying to Client <../topics/mojito_frame_mojits.html#deploying-to-client>`_ for more details. ------------ @@ -510,8 +500,6 @@ Configuration - - Data ---- @@ -715,8 +703,8 @@ Logging/Testing .. _moj_log_level: .. topic:: **How do I change the logging levels for my Mojito application?** - You can set log levels for your application using the ``log`` object in ``application.json``. - You can also set default log levels using the ``log`` object in the ``defaults.json`` + You can set log levels for your application using the ``yui.config`` object in ``application.json``. + You can also set default log levels using the ``yui.config`` object in the ``defaults.json`` at the application or mojit level. See `Logging <../topics/mojito_logging.html>`_ for details and the code example @@ -739,9 +727,12 @@ Logging/Testing .. _moj_client_server_logging: .. topic:: **Can logging be configured to be different for the client and server?** - Yes, the ``application.json`` configuration file can contain a ``log`` object that has - a ``client`` and a ``server`` object that allow you to independently configure logging - for the client and server. See `log Object <../intro/mojito_configuring.html#log-object>`_ + Yes, the ``application.json`` configuration file can contain a ``yui.config`` object that + contain the properties ``logExclude`` and ``logInclude`` to log certain components + of your application. You can also use context configurations to have different + configurations depending on the runtime environment. + + See `config Object <../intro/mojito_configuring.html#yui_config>`_ and the `Log Configuration <../topics/mojito_logging.html#log-configuration>`_ for implementation details. diff --git a/docs/dev_guide/getting_started/index.rst b/docs/dev_guide/getting_started/index.rst index 438debcee..b433fba41 100644 --- a/docs/dev_guide/getting_started/index.rst +++ b/docs/dev_guide/getting_started/index.rst @@ -1,16 +1,13 @@ - -================== +=========================== Getting Started with Mojito -================== - -This chapter offers an introductory tutorial that will show you how to create a simple Mojito application and explain some important features of Mojito. +=========================== +This chapter offers an introductory tutorial that will show you how to create +a simple Mojito application and explain some important features of Mojito. -Table of Contents -################# .. toctree:: :maxdepth: 2 - - mojito_getting_started_requirements + + quickstart mojito_getting_started_tutorial diff --git a/docs/dev_guide/getting_started/mojito_getting_started_requirements.rst b/docs/dev_guide/getting_started/mojito_getting_started_requirements.rst index 093557f21..2afe3da09 100644 --- a/docs/dev_guide/getting_started/mojito_getting_started_requirements.rst +++ b/docs/dev_guide/getting_started/mojito_getting_started_requirements.rst @@ -1,5 +1,3 @@ - - ============ Prerequisite ============ diff --git a/docs/dev_guide/getting_started/mojito_getting_started_tutorial.rst b/docs/dev_guide/getting_started/mojito_getting_started_tutorial.rst index 1a335e00c..08cccebef 100644 --- a/docs/dev_guide/getting_started/mojito_getting_started_tutorial.rst +++ b/docs/dev_guide/getting_started/mojito_getting_started_tutorial.rst @@ -2,7 +2,17 @@ Tutorial: Creating Your First Application ========================================= -In this tutorial, you create a simple application that serves a single page and uses a controller to generate output. +.. _getting_started-prereq: + +Prerequisite +============ + +Complete the `Mojito Quickstart <../quickstart>`_, which instructs you +how to install Mojito and use basic commands for the Mojito command-line tool. + + +In this tutorial, you create a simple application that serves a single page and +uses a controller to generate output. You will learn how to do the following: @@ -12,15 +22,10 @@ You will learn how to do the following: - run an action (method) on the controller - run unit tests for your application +.. _getting_started-make_app: Make the Application -#################### - -#. Create a directory for your app and change to it. - - ``$ mkdir mojito_apps`` - - ``$ cd mojito_apps`` +==================== #. Create the Mojito application ``minty_app``. @@ -30,28 +35,39 @@ Make the Application ``$ cd minty_app`` +.. _getting_started-make_mojit: + Make the Sample Mojit -##################### +===================== + -The name *mojit* is a fusion of the words module and widget. The mojit, however, is neither a module nor a widget. Instead, it is best understood as -a unit of execution used to generate output. Mojits have an MVC structure and consist of two parts: the definition and the instance configuration. +The name *mojit* is a fusion of the words module and widget. The mojit, +however, is neither a module nor a widget. Instead, it is best understood as +a unit of execution used to generate output. Mojits have an MVC structure and +consist of two parts: the definition and the instance configuration. -The definition contains the controller and model code for the mojit, along with the views (and assets) used to render the output. The definition also +The definition contains the controller and model code for the mojit, along with +the views (and assets) used to render the output. The definition also contains unit tests for the code. -The instance configuration is what configures each instance of your mojit. For example, you might have an ``RSSMojit`` which is used to display an -RSS feed. The mojit definition would have the code and views for fetching and rendering a feed, and the instance configuration would have the RSS URL +The instance configuration is what configures each instance of your mojit. For +example, you might have an ``RSSMojit`` which is used to display an RSS feed. +The mojit definition would have the code and views for fetching and rendering a +feed, and the instance configuration would have the RSS URL to fetch, how many items to show, and whether to show thumbnails, etc. -Let's now begin by creating your mojit, but note that you won't be working with models or views in this tutorial. +Let's now begin by creating your mojit, but note that you won't be working with +models or views in this tutorial. #. Create the mojit for your ``minty_app`` application. ``$ mojito create mojit HelloMojit`` - The `Mojito command-line tool <../reference/mojito_cmdline.html>`_ creates a canned mojit definition named ``HelloMojit``. + The `Mojito command-line tool <../reference/mojito_cmdline.html>`_ creates + a canned mojit definition named ``HelloMojit``. -#. To configure your application to use ``HelloMojit``, replace the code in ``application.json`` with the following: +#. To configure your application to use ``HelloMojit``, replace the code in + ``application.json`` with the following: .. code-block:: javascript @@ -67,9 +83,11 @@ Let's now begin by creating your mojit, but note that you won't be working with } ] - Here you have defined the instance ``hello`` of the ``HelloMojit`` mojit, which will allow you to call the functions in the mojit controller. + Here you have defined the instance ``hello`` of the ``HelloMojit`` mojit, + which will allow you to call the functions in the mojit controller. -#. To set up a new route for executing your mojit, create the routing configuration file ``routes.json`` with the following: +#. To set up a new route for executing your mojit, create the routing + configuration file ``routes.json`` with the following: .. code-block:: javascript @@ -84,25 +102,38 @@ Let's now begin by creating your mojit, but note that you won't be working with } ] - This ``routes.json`` file defines the routing paths, the accepted HTTP methods, and what action to take. - The action is what method to call from the mojit instance when a call is made on the defined path. - The ``routes.json`` above configures Mojito to execute the ``index`` method from the ``hello`` - instance (defined in ``application.json``) when receiving HTTP GET calls on the root path. + This ``routes.json`` file defines the routing paths, the accepted HTTP + methods, and what action to take. The action is what method to call from + the mojit instance when a call is made on the defined path. + The ``routes.json`` above configures Mojito to execute the ``index`` method + from the ``hello`` instance (defined in ``application.json``) when receiving + HTTP GET calls on the root path. -#. From the application directory, test your application. You will notice that some tests are deferred. +#. From the application directory, test your application. You will notice that + some tests are deferred. ``$ mojito test app .`` +.. _getting_started-start_server: + Start the Server -################ +================ #. Start the server. ``$ mojito start`` #. Open http://localhost:8666/ in a browser. +#. The Web page should display the following:: + + status + Mojito is working. + data + some: data -#. The Web page should display "Mojito is working.". The text was served by the controller, the ``controller.server.js`` file in the ``minty_app/mojits/HelloMojit`` directory. You will learn more about the controller in `Modify the Sample Mojit`_. + The text was served by the controller, the ``controller.server.js`` file in the + ``minty_app/mojits/HelloMojit`` directory. You will learn more about the controller in + :ref:`Modify the Sample Mojit <first_app-modify_mojit>`. #. Stop the server by going back to your terminal pressing **^C**. @@ -110,13 +141,15 @@ Start the Server .. _first_app-modify_mojit: Modify the Sample Mojit -####################### +======================= -You will now modify the controller, so that the ``index`` function called in the controller outputs different results. +You will now modify the controller, so that the ``index`` function called in the +controller outputs different results. #. Change to ``mojits/HelloMojit``. - -#. Edit ``controller.server.js`` and replace the string 'Just a simple mojit.' in the code with 'Hello World!'. Your ``controller.server.js`` should look similar to the following code: +#. Edit ``controller.server.js`` and replace the string 'Just a simple mojit.' in + the code with 'Hello World!'. Your ``controller.server.js`` should look similar + to the following code: .. code-block:: javascript @@ -136,10 +169,6 @@ You will now modify the controller, so that the ``index`` function called in the */ Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, - /** * Method corresponding to the 'index' action. * @@ -154,7 +183,7 @@ You will now modify the controller, so that the ``index`` function called in the } ac.assets.addCss('./index.css'); ac.done({ - status: 'Hello World!', + status: 'Doing well, thanks.', data: data }); }); @@ -162,39 +191,45 @@ You will now modify the controller, so that the ``index`` function called in the }; }, '0.0.1', {requires: [ 'mojito', - 'mojito-models-addon', + 'mojito-models-addon', + 'mojito-assets-addon', 'HelloMojitModelFoo' ]}); - As you can see the "controllers" are just an array of JavaScript objects, and the "action" is just a method called on the controller object. - The result of the method are communicated back to Mojito through the ``actionContext`` object. + As you can see the "controllers" are just an array of JavaScript objects, + and the "action" is just a method called on the controller object. + The result of the method are communicated back to Mojito through the + ``actionContext`` object. #. Change to the ``tests`` directory. -#. Edit ``controller.server-tests.js`` and replace the string 'Mojito is working.' in the code with 'Hello World!'. Your ``controller.server-tests.js`` should look similar to the following code: +#. Edit ``controller.server-tests.js`` and replace the string 'Mojito is working.' + in the code with 'Hello World!'. Your ``controller.server-tests.js`` should + look similar to the following code: .. code-block:: javascript YUI.add('HelloMojit-tests', function(Y) { var suite = new YUITest.TestSuite('HelloMojit-tests'), - controller = null, - A = YUITest.Assert; + controller = null, + A = YUITest.Assert; suite.add(new YUITest.TestCase({ - + name: 'HelloMojit user tests', - setUp: function() { - controller = Y.mojito.controllers.HelloMojit; + controller = Y.mojito.controllers.HelloMojit; }, tearDown: function() { - controller = null; + controller = null; }, - 'test mojit': function() { - var ac, modelData, assetsResults, doneResults; + var ac, + modelData, + assetsResults, + doneResults; modelData = { x:'y' }; ac = { assets: { @@ -210,34 +245,36 @@ You will now modify the controller, so that the ``index`` function called in the } }; } - }, - done: function(data) { - doneResults = data; - } }; A.isNotNull(controller); A.isFunction(controller.index); controller.index(ac); A.areSame('./index.css', assetsResults); A.isObject(doneResults); - A.areSame('Hello World!', doneResults.status); - A.areSame('{"x":"y"}', doneResults.data); + A.areSame('Doing well, thanks.', doneResults.status); + A.isObject(doneResults.data); + A.isTrue(doneResults.data.hasOwnProperty('x')); + A.areEqual('y', doneResults.data['x']); } - })); - YUITest.TestRunner.add(suite); - }, '0.0.1', {requires: ['mojito-test', 'HelloMojit']}); - Mojito has the unit test given in ``controller.server-tests.js`` confirms that the output from the action index is the same as the + Mojito has the unit test given in ``controller.server-tests.js`` confirms + that the output from the action index is the same as the string given in the assert statement. #. From the application directory, run the application test. ``$ mojito test app .`` +#. Restart the server and reopen http://localhost:8666/ in a browser to see the updated + text:: -#. Restart the server and reopen http://localhost:8666/ in a browser to see the text "Hello World!" + status + Doing well, thanks. + data + some: data -#. Congratulations, now go try our `code examples <../code_exs/>`_ or check out the `Mojito Documentation <../>`_. +#. Congratulations, now go try our `code examples <../code_exs/>`_ or check out + the `Mojito Documentation <../>`_. diff --git a/docs/dev_guide/getting_started/quickstart.rst b/docs/dev_guide/getting_started/quickstart.rst new file mode 100644 index 000000000..4315be074 --- /dev/null +++ b/docs/dev_guide/getting_started/quickstart.rst @@ -0,0 +1,72 @@ +================= +Mojito Quickstart +================= + +.. _mojito_qs-prereqs: + +Prerequisites +============= + +**System:** Unix-based system. + +**Software:** `Node.js (>= 0.6.0 < 0.8) <http://nodejs.org/>`_, `npm (> 1.0.0) <http://npmjs.org/>`_ + +.. _mojito_qs-install: + +Installation Steps +================== + +#. Get Mojito from the npm registry and globally install Mojito so that it can be run from the + command line. You may need to use ``sudo`` if you run into permission errors. + + ``$ npm install mojito -g`` + +#. Confirm that Mojito has been installed by running the help command. + + ``$ mojito help`` + +.. _mojito_qs-create: + +Create a Mojito Application +=========================== + +#. ``$ mojito create app hello_world`` +#. ``$ cd hello_world`` +#. ``$ mojito create mojit myMojit`` + +.. _mojito_qs-modify: + +Modify Your Application +======================= + +To make the application return a string we want, replace the code in ``mojits/myMojit/controller.server.js`` with the following: + +.. code-block:: javascript + + YUI.add('myMojit', function(Y, NAME) { + + Y.namespace('mojito.controllers')[NAME] = { + + index: function(ac) { + ac.done('Hello, world. I have created my first Mojito app at ' + (new Date()) + '.'); + } + + }; + }); + +.. _mojito_qs-running: + +Running the Application +======================= + +#. From the ``hello_world`` application directory, start Mojito: + + ``$ mojito start`` + +#. Go to http://localhost:8666/@myMojit/index to see your application. + +#. Stop your application by pressing **Ctrl-C**. + +For a more in-depth tutorial, please see `Mojito: Getting Started <../getting_started/>`_. To learn more about Mojito, see +the `Mojito Documentation <../>`_. + diff --git a/docs/dev_guide/index.rst b/docs/dev_guide/index.rst new file mode 100644 index 000000000..d702dc3fb --- /dev/null +++ b/docs/dev_guide/index.rst @@ -0,0 +1,75 @@ +==================== +Mojito Documentation +==================== + +.. _mojito_doc_directory-intro: + +Mojito Introduction +=================== + +Learn about Mojito and how it can help you create +HTML5/JavaScript applications for the desktop and mobile platforms. +You'll also get a quick tour of the Mojito ecosystem and architecture +before delving into the details of configuration, client-side code (binders), +and static resources. + +.. _mojito_doc_directory-getting_started: + +Getting Started +=============== + +Ready to start creating apps? You'll start with a quickstart that will show +you how to install Mojito and then get up an running with your first Mojito app. +Once you have Mojito installed and are familiar with the basic steps of creating +an app, you can walk through the `Creating + +.. _mojito_doc_directory-topics: + +Developer Topics +================ + +This chapter is intended for developers who have gotten their feet wet and +intend to get in deeper with Mojito. We'll start the discussion with assets, cookies, +logging, testing and then move onto more advanced topics such as +composite/framework mojits, extending Mojito, and context configuration. + +.. _mojito_doc_directory-code_exs: + +Code Examples +============= + +The code examples are in cookbook format. We took common and very specific problems +faced by developers and created simple applications that isolated and then solved +the problem. Each example has annotated code snippets to emphasize key points and +steps for you to recreate and run the examples. + + +.. _mojito_doc_directory-ref: + +Reference +========= + +The reference includes a glossary for Mojito and Cocktails terminology, a comprehensive +treatment of the Mojito command-line tool, and troubleshooting tips. + +Additional Resources +==================== + +Besides reading documentation and working through code examples, there +are many other ways to learn about Mojito, so we've compiled a page where +you can find presentations, videos, screencasts, articles, and blogs. +You can also find Twitter handles to follow for the latest news and +the YDN forum for Mojito. + +.. toctree:: + :maxdepth: 4 + :hidden: + + intro/index + getting_started/index + code_exs/index + topics/index + api_overview/index + faq/index + reference/index + resources/index diff --git a/docs/dev_guide/intro/index.rst b/docs/dev_guide/intro/index.rst index 06cd0a3a3..ef2708751 100644 --- a/docs/dev_guide/intro/index.rst +++ b/docs/dev_guide/intro/index.rst @@ -1,13 +1,11 @@ - ================== Introducing Mojito ================== -Mojito is a `model-view-controller (MVC) <http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller>`_ application framework built on -YUI 3 that enables agile development of Web applications. The following sections will introduce and discuss the benefits of using Mojito. - -Table of Contents -################# +Mojito is a `model-view-controller (MVC) <http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller>`_ +application framework built on YUI 3 that enables agile development of Web +applications. The following sections will introduce and discuss the benefits of +using Mojito. .. toctree:: :maxdepth: 2 diff --git a/docs/dev_guide/intro/mojito_apps.rst b/docs/dev_guide/intro/mojito_apps.rst index 9ec8c81de..f666596c4 100644 --- a/docs/dev_guide/intro/mojito_apps.rst +++ b/docs/dev_guide/intro/mojito_apps.rst @@ -1,86 +1,123 @@ - - =================== Mojito Applications =================== -Mojito allows developers to use a combination of configuration and an MVC architecture to create applications. Because client and server components -are both written in JavaScript, Mojito can run on the client (browser) or the server (Node.js). The figure below shows the MVC architecture of Mojito +Mojito allows developers to use a combination of configuration and an MVC +architecture to create applications. Because client and server components +are both written in JavaScript, Mojito can run on the client (browser) or +the server (Node.js). The figure below shows the MVC architecture of Mojito and what components can run on the client/server or just the client. .. image:: images/basic_mojit.gif :width: 400px :height: 355px +.. _mojito_apps-overview: + Overview -######## +======== -Mojito applications contains JSON configuration files and directories for storing JavaScript, HTML, and CSS. The configuration files can be used to define relationships -between code components, assets, routing paths, defaults and are available at the application and mojit-level. The directory structure of a Mojito application reflects the -MVC architecture and separates resources, such assets, libraries, middleware, etc. +Mojito applications contains JSON configuration files and directories for +storing JavaScript, HTML, and CSS. The configuration files can be used to +define relationships between code components, assets, routing paths, defaults +and are available at the application and mojit-level. The directory structure +of a Mojito application reflects the MVC architecture and separates resources, +such assets, libraries, middleware, etc. -To create a Mojito application, you use the command-line tool ``mojito``. When the command below is run, Mojito creates a directory -structure with files for configuration and other directories for CSS and mojits. +To create a Mojito application, you use the command-line tool ``mojito``. When +the command below is run, Mojito creates a directory structure with files for +configuration and other directories for CSS and mojits. ``$ mojito create app <mojito_app>`` -Mojito applications can have one or more mojits. Mojits are the basic unit of composition and reuse in a Mojito application. They consist of Javascript and markup, and -follow the MVC pattern. You can think of mojits as the engines that create the rectangular areas of a page. +Mojito applications can have one or more mojits. Mojits are the basic unit of +composition and reuse in a Mojito application. They consist of Javascript and +markup, and follow the MVC pattern. You can think of mojits as the engines +that create the rectangular areas of a page. -To create a mojit, you run the command below from the application directory, which creates another directory structure and files that include the mojit controller, -model, binders, and views. +To create a mojit, you run the command below from the application directory, +which creates another directory structure and files that include the mojit +controller, model, binders, and views. ``$ mojito create mojit <mojito_app>`` +.. _mojito_apps-files_dirs: + Application Files and Directories ================================= -Each Mojito application contains configuration files and directories for mojits and assets. +Each Mojito application contains configuration files and directories for mojits +and assets. -The principal files and directories of a Mojito application are listed below are globally available to all mojits. -Those marked with an asterisk are not created by default. +The principal files and directories of a Mojito application are listed below are +globally available to all mojits. Those marked with an asterisk are not created by default. - ``addons`` - directory containing additional addons for Mojito. - ``assets`` - general directory containing CSS files for all mojits. -- ``application.json`` - application configuration file that lets you specify the port and the mojits used by the application. -- ``autoload`` - directory of JavaScript files that contain YUI modules added with ``YUI.add``. These files aren't actually *auto-loaded*, but are merely automatically included if required by a YUI module. +- ``application.json`` - application configuration file that lets you specify + the port and the mojits used by the application. +- ``autoload`` - directory of JavaScript files that contain YUI modules added + with ``YUI.add``. These files aren't actually *auto-loaded*, but are merely + automatically included if required by a YUI module. - ``default.json`` - file that sets default values for all specifications. -- ``mojits`` - directory storing the mojits. See `Mojit Files and Directories`_ for a description of the directory contents. +- ``mojits`` - directory storing the mojits. See `Mojit Files and Directories`_ + for a description of the directory contents. - ``package.json`` - configuration file for deploying the application. -- ``index.js`` - file providing integration with a cloud-based environment where Mojito applications can run. +- ``index.js`` - file providing integration with a cloud-based environment + where Mojito applications can run. .. _mojito_apps-mojits: Mojits ====== -Mojits are the basic unit of composition and reuse in a Mojito application. This -section covers the details of the files and directories contained in a mojit. -To get an overview about mojits and learn about their architecture and structure, -see `Mojits <mojito_mojits.html>`_. +Mojits are the basic unit of composition and reuse in a Mojito application. +This section covers the details of the files and directories contained in a +mojit. To get an overview about mojits and learn about their architecture and +structure, see `Mojits <mojito_mojits.html>`_. +.. _mojito_apps_mojits-files: Mojit Files and Directories --------------------------- -When you create a mojit, a directory structure containing template files is auto-generated. The template files are for the model, views, controller, tests, and configuration. +When you create a mojit, a directory structure containing template files is +auto-generated. The template files are for the model, views, controller, tests, +and configuration. -The principal directories and template files are listed below with a short description. Those marked with an asterisk are not created by default. The allowed -values for ``{affinity}`` are ``client``, ``common``, and ``server``. The `affinity <../reference/glossary.html>`_ specifies where the resource is available. +The principal directories and template files are listed below with a short +description. Those marked with an asterisk are not created by default. The +allowed values for ``{affinity}`` are ``client``, ``common``, and ``server``. +The `affinity <../reference/glossary.html>`_ specifies where the resource +is available. -- ``actions`` - directory of JavaScript files containing methods to add to the controller. Actions are useful for maintaining large controllers. +- ``actions`` - directory of JavaScript files containing methods to add to the + controller. Actions are useful for maintaining large controllers. - ``assets`` - directory for storing CSS or JavaScript files. -- ``autoload`` - directory containing JavaScript files that contain YUI modules added with ``YUI.add``. These files aren't actually *autoloaded*, but are merely automatically included if required by a YUI module. Both the application directory and mojit directory can have ``autoload`` directories. +- ``autoload`` - directory containing JavaScript files that contain YUI + modules added with ``YUI.add``. These files aren't actually *autoloaded*, + but are merely automatically included if required by a YUI module. Both + the application directory and mojit directory can have ``autoload`` directories. - ``binders`` - directory containing event binding files for the mojit. -- ``controller.server.js`` - the mojit controller that runs on the server. You can also create the file ``controller.client.js`` to have a mojit controller that runs on the client or the file ``controller.common.js`` that can run on the client or server. +- ``controller.server.js`` - the mojit controller that runs on the server. You + can also create the file ``controller.client.js`` to have a mojit controller + that runs on the client or the file ``controller.common.js`` that can run + on the client or server. - ``models`` - directory containing the model scripts. - - ``model.server.js`` - default model that runs on the server. You can also create the file ``model.client.js`` that runs on the client or ``model.common.js`` that can run on either the client or server. Models can be given names as well, such as ``{model_name}.server.js``. -- ``tests`` - directory containing the controller, model, and YUI module unit tests. The structure of ``tests`` mirrors its parent mojit's directory structure. - - - ``controller.{affinity}-tests.js`` - the unit tests for the mojit controllers. - - ``{model_name}.{affinity}-tests.js`` - the unit tests for the mojit models. - - ``{module_name}.{affinity}-tests.js`` - the unit tests for YUI modules, which are located in ``mojits/{mojit_name}/autoload`` directory. + - ``model.server.js`` - default model that runs on the server. You can also + create the file ``model.client.js`` that runs on the client or + ``model.common.js`` that can run on either the client or server. Models + can be given names as well, such as ``{model_name}.server.js``. +- ``tests`` - directory containing the controller, model, and YUI module unit tests. + The structure of ``tests`` mirrors its parent mojit's directory structure. + + - ``controller.{affinity}-tests.js`` - the unit tests for the mojit + controllers. + - ``{model_name}.{affinity}-tests.js`` - the unit tests for the mojit + models. + - ``{module_name}.{affinity}-tests.js`` - the unit tests for YUI modules, + which are located in ``mojits/{mojit_name}/autoload`` directory. - Example of module and corresponding test: - ``{app_name}/mojits/{mojit_name}/autoload/{module_name}.{affinity}.js`` @@ -88,14 +125,21 @@ values for ``{affinity}`` are ``client``, ``common``, and ``server``. The `affin - ``views`` - directory containing the templates. - - ``index.hb.html`` - the default template for the mojit. You can create other templates that get content from functions in the mojit controllers. + - ``index.hb.html`` - the default template for the mojit. You can create other + templates that get content from functions in the mojit controllers. + +.. _mojito_apps-dir_struct: Application Directory Structure =============================== -The following shows the directory structure of a Mojito application that has one mojit. The allowed -values for ``{affinity}`` are ``client``, ``common``, and ``server``. The `affinity <../reference/glossary.html>`_ specifies where the resource is available. The ``{view_engine}`` is the -engine that renders tags used in a templating system. For example, the value ``hb`` in ``index.hb.html`` instructs Mojito to use the Handlebars rendering engine. +The following shows the directory structure of a Mojito application that +has one mojit. The allowed values for ``{affinity}`` are ``client``, +``common``, and ``server``. The `affinity <../reference/glossary.html>`_ +specifies where the resource is available. The ``{view_engine}`` is the +engine that renders tags used in a templating system. For example, the value +``hb`` in ``index.hb.html`` instructs Mojito to use the Handlebars +rendering engine. :: @@ -108,8 +152,6 @@ engine that renders tags used in a templating system. For example, the value ``h |-- index.js |-- mojits/ | `-- [mojit_name] - | |-- actions/ - | | `-- *.{affinity}.js | |-- assets/ | |-- autoload/ | | `-- *.{affinity}.js @@ -125,8 +167,6 @@ engine that renders tags used in a templating system. For example, the value ``h | |-- tests/ | | |-- autoload/ | | | `-- {module_name}.{affinity}-tests.js - | | |-- binders/ - | | | `-- {view_name}.client-tests.js | | |-- controller.{affinity}-tests.js | | `-- models/ | | `-- {model_name}.{affinity}-tests.js diff --git a/docs/dev_guide/intro/mojito_architecture.rst b/docs/dev_guide/intro/mojito_architecture.rst index 3ec82a542..8a4884c5c 100644 --- a/docs/dev_guide/intro/mojito_architecture.rst +++ b/docs/dev_guide/intro/mojito_architecture.rst @@ -13,17 +13,17 @@ debug your Mojito applications more effectively. .. _mj_arch-overview: Overview --------- +======== Before looking at the details of Mojito, let's take a look at how Mojito fits in the Web application world. In the diagram below, the triangular symbol -formed with three circles represents the Mojito MVC core; the circles represent the model, -view, and controller. This common MVC core executes across devices and on both client -and server and is one of the key elements which gives Mojito its power. As the -diagram illustrates, Mojito runs on various clients, uses HTTP for all -client-server communication, and typically accesses data with the Yahoo! Query -Language (YQL) due to its power, ease-of-use, and ability to run from either -the client or server. +formed with three circles represents the Mojito MVC core; the circles represent +the model, view, and controller. This common MVC core executes across devices +and on both client and server and is one of the key elements which gives Mojito +its power. As the diagram illustrates, Mojito runs on various clients, uses +HTTP for all client-server communication, and typically accesses data with the +Yahoo! Query Language (YQL) due to its power, ease-of-use, and ability to run +from either the client or server. .. image:: images/mojito_architecture.png @@ -39,7 +39,7 @@ the client or server. .. _overview-clients: Clients and Runtimes -#################### +==================== Mojito was designed with the goal of running in multiple runtime environments and supporting online and offline experiences. The following are the supported @@ -55,15 +55,15 @@ client/runtime environments: .. _overview-apps: Mojito Applications -################### +=================== -A Mojito application is, quite simply, a set of module/widget components (called mojits) -bound together with one or more JSON configuration files which describe the -application model, view, and controller code used depending on context. A Mojito -application is packaged to be distributed and deployed as a unit, and as such, -it is deployment-independent. The mojits used by an application may be included -directly within the package or may be included by reference from a cloud-based -repository. +A Mojito application is, quite simply, a set of module/widget components +(called mojits) bound together with one or more JSON configuration files +which describe the application model, view, and controller code used depending +on context. A Mojito application is packaged to be distributed and deployed +as a unit, and as such, it is deployment-independent. The mojits used by an +application may be included directly within the package or may be included +by reference from a cloud-based repository. See also `Mojito Applications <./mojito_apps.html>`_ for a more in-depth discussion. @@ -71,7 +71,7 @@ discussion. .. _overview-data: Data -#### +==== To support seamless operation and migration of components between client and server, Mojito application data is typically obtained with YQL. In most cases, @@ -82,7 +82,7 @@ retrieved from YQL. .. _mj_arch-framework: Mojito Framework ----------------- +================ The Mojito Framework consists of both module-level and application-level framing, which taken together provide the infrastructure upon which applications are @@ -102,8 +102,8 @@ elements in the diagram in more detail. .. image:: images/mojito_framework.png :scale: 85 % - :alt: Diagram showing the relationships between the Mojito application, Mojito core, mojits, - and runtime environments. + :alt: Diagram showing the relationships between the Mojito application, + Mojito core, mojits, and runtime environments. :height: 513px :width: 718px :align: center @@ -114,10 +114,10 @@ elements in the diagram in more detail. .. _framework-server_runtime: Mojito Server Runtime -##################### +--------------------- -The base server-only capabilities that support the Mojito Core include, but are -not limited to, the following: +The base server-only capabilities that support the Mojito Core include, +but are not limited to, the following: - HTTP Server - Routing Rules @@ -127,7 +127,7 @@ not limited to, the following: .. _framework-client_runtime: Mojito Client Runtime -##################### +--------------------- The base client-only capabilities that support the Mojito Core include, but are not limited to, the following: @@ -138,7 +138,7 @@ not limited to, the following: .. _framework-core: Mojito Core -########### +----------- The Mojito Core is the common functionality of Mojito that runs on both server and client. The core is initialized (or bootstrapped, if you will) by either the @@ -153,7 +153,7 @@ the Mojito Core include, but are not limited to, the following: .. _framework-mojit_container: Mojit Container -############### +--------------- The Mojit Container is where mojit instances live. This is analogous in a traditional Java Web framework to the servlet container. In Mojito the @@ -165,7 +165,7 @@ container logic includes, but is not limited to, the following: .. _framework-mojit_ac: "API" (Action Context) -###################### +---------------------- The "API" block of the diagram represents the common API object provided to each Mojit. This common API object is properly referred to as the Action Context. @@ -177,7 +177,7 @@ running within the client). .. _framework-mojito_services: Mojito Services -############### +--------------- The Services block is a customizable layer within Mojito, typically created with a combination of custom middleware and ``ActionContext`` addons, which allow your @@ -204,7 +204,7 @@ services to be developed as extensions to the core framework. .. _framework-view_factory: View Factory -############ +------------ The View Factory creates the view instance for a mojit instance based on a mojit's configuration and on its corresponding mojit @@ -218,7 +218,7 @@ capability for a mojit developer to define a custom view type: .. _framework-view_cache: View Cache -########## +---------- The View Cache provides applications with the ability to cache. For example, because of the View Cache, an application can cache partially rendered views to @@ -229,7 +229,7 @@ requested. Mojito Framework Components ---------------------------- +=========================== Box A in the flowchart below represents the Application Container at its most basic level. Here we focus on the servicing of page requests and user @@ -249,7 +249,7 @@ sections. .. _framework_components-dispatcher: Dispatcher/Mapper -################# +----------------- This component processes incoming URLs and determines how to map these to the appropriate application functionality. This is similar in many ways to the front @@ -260,7 +260,7 @@ mojit identifiers. .. _framework_components-nav_manager: Navigation Manager -################## +------------------ In some applications, it is important that certain *destinations* within the application be URL addressable (e.g., to allow the user to create bookmarks to @@ -273,7 +273,7 @@ management, so this component may not be used in such cases. .. _framework_components-mojit_host: Mojit Host -########## +---------- The mojits themselves are not part of the Application Container per se. The container provides the facilities to host, support, and manage mojits while the @@ -283,7 +283,7 @@ mojits collectively provide the functionality of the application. See also .. _framework_components-app_config: Application Configuration -######################### +------------------------- A particular instance of the Application Container is initialized through an externally managed configuration. This configuration includes, among other diff --git a/docs/dev_guide/intro/mojito_binders.rst b/docs/dev_guide/intro/mojito_binders.rst index 9dc873b14..6c65a4d70 100644 --- a/docs/dev_guide/intro/mojito_binders.rst +++ b/docs/dev_guide/intro/mojito_binders.rst @@ -1,57 +1,64 @@ - ============== Mojito Binders ============== +.. _mojito_binders-overview: + Overview -######## +======== -Each mojit you create can have some specific code called binders that is only deployed to the -browser. The code can perform the following three functions: +Each mojit you create can have some specific code called binders that is only +deployed to the browser. The code can perform the following three functions: - allow event handlers to attach to the mojit DOM node - communicate with other mojits on the page - execute actions on the mojit that the binder is attached to -A mojit may have zero, one, or many binders within the ``binders`` directory. Each binder will be -deployed to the browser along with the rest of the mojit code, where the client-side Mojito runtime -will call it appropriately. The view used to generate output determines which binder is used. Thus, -if the ``simple`` view is used, the binder ``simple.js`` is used. This can be overridden by setting +A mojit may have zero, one, or many binders within the ``binders`` directory. +Each binder will be deployed to the browser along with the rest of the mojit +code, where the client-side Mojito runtime will call it appropriately. The view +used to generate output determines which binder is used. Thus, if the ``simple`` +view is used, the binder ``simple.js`` is used. This can be overridden by setting ``view.binder`` in the ``meta`` argument to `ac.done <../../api/classes/ActionContext.html#method_done>`_. If no binder matches the view, then no binder is used. +.. _mojito_binders-app_reqs: Application Requirements for Using Binders -########################################## +========================================== To use binders, your application is required to have the following: -- The top-level mojit instance defined in ``application.json`` is of type ``HTMLFrameMojit`` - or your own frame mojit. See `HTMLFrameMojit <../topics/mojito_framework_mojits.html#htmlframemojit>`_ +- The top-level mojit instance defined in ``application.json`` is of type + ``HTMLFrameMojit`` or your own frame mojit. See + `HTMLFrameMojit <../topics/mojito_frame_mojits.html#htmlframemojit>`_ for an introduction and example configuration. -- Your application is configured to deploy code to the client with the ``deploy`` property in - ``application.json``. See +- Your application is configured to deploy code to the client with the + ``deploy`` property in ``application.json``. See `Configuring Applications to Be Deployed to Client <../intro/mojito_configuring.html #configuring-applications-to-be-deployed-to-client>`_ for more information. - The template files (e.g., ``index.hb.html``) have containers (``div`` elements) that have the ``id`` attribute assigned the value ``{{mojit_view_id}}``. For example: ``<div id={{mojit_view_id}}>``. The - attribute value ``{{mojit_view_id}}`` allows binders to attach themselves to the DOM. + attribute value ``{{mojit_view_id}}`` allows binders to attach themselves to + the DOM. -See `Binding Events <../code_exs/binding_events.html>`_ for a documented example that uses -binders. +See `Binding Events <../code_exs/binding_events.html>`_ for a documented +example that uses binders. + +.. _mojito_binders-anatomy: Anatomy of the Binder -##################### +===================== -A binder essentially has the two essential functions ``init`` and ``bind``. The ``init`` function -initializes the binder and contains the ``mojitProxy`` object. The ``bind`` function allows the -binder to be attached to the DOM. +A binder essentially has the two essential functions ``init`` and ``bind``. +The ``init`` function initializes the binder and contains the ``mojitProxy`` +object. The ``bind`` function allows the binder to be attached to the DOM. -The example binder below shows the basic structure of a binder. The binder is for the ``AwesomeMojit`` -mojit and contains the ``init`` and ``bind`` functions that initialize and allow the binder -code to be attached to the DOM. +The example binder below shows the basic structure of a binder. The binder +is for the ``AwesomeMojit`` mojit and contains the ``init`` and ``bind`` +functions that initialize and allow the binder code to be attached to the DOM. .. code-block:: javascript @@ -65,36 +72,45 @@ code to be attached to the DOM. }; }, '0.0.1', {requires: ['node']}); -An instance of the binder above will be created whenever the ``index`` function of ``AwesomeMojit`` -is executed, and its corresponding DOM node is attached to a client page. Mojito will select that -DOM node and pass it into the ``bind`` function. This allows you to write code to capture UI events -and interact with Mojito or other mojit binders. - -init -==== +An instance of the binder above will be created whenever the ``index`` function +of ``AwesomeMojit`` is executed, and its corresponding DOM node is attached to +a client page. Mojito will select that DOM node and pass it into the ``bind`` +function. This allows you to write code to capture UI events and interact with +Mojito or other mojit binders. -The ``init`` method is called with an instance of a mojit proxy specific for this mojit binder -instance. The mojit proxy can be used at this point to listen for events. It is typical to store the -mojit proxy for later use as well. The mojit proxy is the only gateway back into the Mojito -framework for your binder. +.. _binders_anatomy-init: -bind -==== +init +---- -The ``bind`` method is passed a ``Y.Node`` instance that wraps the DOM node representing this mojit -instance within the DOM. It will be called after all other binders on the page have been constructed -and their ``init`` methods have been called. The mojit proxy can be used at this point to -broadcast events. Users should attach DOM event handlers in ``bind`` to capture user interactions. +The ``init`` method is called with an instance of a mojit proxy specific for +this mojit binder instance. The mojit proxy can be used at this point to listen +for events. It is typical to store the mojit proxy for later use as well. The +mojit proxy is the only gateway back into the Mojito framework for your binder. -For Mojito to reference the DOM node representing the mojit instance and pass it to the ``bind`` -function, the root element of the mojit's template must have the ``id`` attribute with the -Handlebars expression ``{{mojit_view_id}}``. Mojito will render ``{{mojit_view_id}}`` -into a unique ID that can be used to select the DOM node. +.. _binders_anatomy-bind: -For example, the root element ``<div>`` in the template below has the ``id`` attribute with the -value ``{{mojit_view_id}}``. This ``id`` lets Mojito reference the ``Y.Node`` instance wrapping the -DOM node representing the mojit instance within the DOM. If this ``<div>`` element does not have -this ``id`` value, no node will be passed to the ``bind`` function. +bind +---- + +The ``bind`` method is passed a ``Y.Node`` instance that wraps the DOM node +representing this mojit instance within the DOM. It will be called after all +other binders on the page have been constructed and their ``init`` methods +have been called. The mojit proxy can be used at this point to broadcast +events. Users should attach DOM event handlers in ``bind`` to capture user +interactions. + +For Mojito to reference the DOM node representing the mojit instance and pass +it to the ``bind`` function, the root element of the mojit's template must +have the ``id`` attribute with the Handlebars expression ``{{mojit_view_id}}``. +Mojito will render ``{{mojit_view_id}}`` into a unique ID that can be used to +select the DOM node. + +For example, the root element ``<div>`` in the template below has the ``id`` +attribute with the value ``{{mojit_view_id}}``. This ``id`` lets Mojito +reference the ``Y.Node`` instance wrapping the DOM node representing the +mojit instance within the DOM. If this ``<div>`` element does not have this +``id`` value, no node will be passed to the ``bind`` function. .. code-block:: html @@ -112,23 +128,28 @@ this ``id`` value, no node will be passed to the ``bind`` function. </div> </div> +.. _binders_anatomy-mojitProxy: + mojitProxy Object -################# +----------------- -Each binder, when constructed by Mojito on the client, is given a proxy object for interactions with -the mojit it represents as well as with other mojits on the page. This ``mojitProxy`` should be saved -with ``this`` for use in the other parts of the binder. +Each binder, when constructed by Mojito on the client, is given a proxy object +for interactions with the mojit it represents as well as with other mojits on +the page. This ``mojitProxy`` should be saved with ``this`` for use in the +other parts of the binder. -From the ``mojitProxy``, you can access properties that use the interface and provides the -information below: +From the ``mojitProxy``, you can access properties that use the interface and +provides the information below: -**Mojit config** - the instance specification for the mojit linked to the binder and uses the following syntax: +**Mojit config** - the instance specification for the mojit linked to the binder +and uses the following syntax: :: mojitProxy.config -**Mojit context** - environment information such as language, device, region, site, etc. +**Mojit context** - environment information such as language, device, region, +site, etc. :: @@ -146,22 +167,25 @@ information below: mojitProxy.type - +.. _mojito_binders-refresh_views: Refreshing Views ================ -Often all you want your binder to do is to refresh its associated view. From the ``mojitProxy`` -object, you can call the ``refreshView`` method to render a new DOM node for the current mojit and -its children, as well as reattach all of the existing binders to their new nodes within the new -markup. Because all binder instances are retained, state can be stored within a binder's scope. +Often all you want your binder to do is to refresh its associated view. From +the ``mojitProxy`` object, you can call the ``refreshView`` method to render +a new DOM node for the current mojit and its children, as well as reattach +all of the existing binders to their new nodes within the new markup. Because +all binder instances are retained, state can be stored within a binder's scope. + +.. _refresh_views-ex: Example Usage ------------- -The code snippet below shows how to call the ``refreshView`` method with optional parameters. The -``refreshView`` method does not require a callback to manage the markup returned from the action -invocation. +The code snippet below shows how to call the ``refreshView`` method with +optional parameters. The ``refreshView`` method does not require a callback +to manage the markup returned from the action invocation. .. code-block:: javascript @@ -180,22 +204,27 @@ invocation. }); ... +.. _mojito_binders-destroy_child: + Destroying Child Mojits ======================= -A mojit binder can attempt to destroy a child mojit on the page by calling the ``destroyChild`` -method from the ``mojitProxy`` object. The ``destroyChild`` method accepts one parameter that -identifies the child mojit to be destroyed. That parameter can either be the ``slot`` or ``_viewId`` -that identify the child mojit. +A mojit binder can attempt to destroy a child mojit on the page by calling the +``destroyChild`` method from the ``mojitProxy`` object. The ``destroyChild`` +method accepts one parameter that identifies the child mojit to be destroyed. +That parameter can either be the ``slot`` or ``_viewId`` that identify the child +mojit. -After being destroyed, the child's DOM node is detached, destroyed, and its binder -life-cycle events (``unbind``, ``destroy``) are executed. +After being destroyed, the child's DOM node is detached, destroyed, and its +binder life-cycle events (``unbind``, ``destroy``) are executed. + +.. _destroy_child-ex: Example Usage ------------- -The code snippet below uses the ``destroyChild`` method to remove the child nodes based on the -``_viewId``. +The code snippet below uses the ``destroyChild`` method to remove the child +nodes based on the ``_viewId``. .. code-block:: javascript @@ -209,16 +238,21 @@ The code snippet below uses the ``destroyChild`` method to remove the child node } ... +.. _mojito_binders-class_mojitProxy: + Class MojitProxy ================ -See the `Class MojitProxy <../../api/classes/MojitProxy.html>`_ in the Mojito API Reference. +See the `Class MojitProxy <../../api/classes/MojitProxy.html>`_ in the Mojito +API Reference. + +.. _class_mojitProxy-exs: Binder Examples -############### +--------------- -The following example shows a typical binder. To see how to use binders in a working example, see the -`Code Examples: Events <../code_exs/#events>`_. +The following example shows a typical binder. To see how to use binders in a +working example, see the `Code Examples: Events <../code_exs/#events>`_. .. code-block:: javascript diff --git a/docs/dev_guide/intro/mojito_configuring.rst b/docs/dev_guide/intro/mojito_configuring.rst index acfc3e8f1..6be349c3b 100644 --- a/docs/dev_guide/intro/mojito_configuring.rst +++ b/docs/dev_guide/intro/mojito_configuring.rst @@ -1,29 +1,37 @@ - - ================== Configuring Mojito ================== +.. _mojito_configuring-basic: + Basic Information ================= -Mojito can be configured at the framework, application, and mojit levels. Each level is -configured differently, but uses same general file format consisting of JSON. +Mojito can be configured at the framework, application, and mojit levels. +Each level is configured differently, but uses same general file format +consisting of JSON. + +.. _config_basic-file: File Format ----------- -All configuration files in Mojito have a general top-level structure and are in JSON format. -At the top level of each configuration file is an array. Each item of the array is an -object that configures one component of Mojito, such as logging, assets, mojits, static -resources, etc. +.. _config_basic_file-json: -Each configuration object is required to have a ``settings`` property that specifies -conditions for applying the configuration settings. These conditions could be used to -determine the configurations in different environments. +JSON +#### -Below is the skeleton of a configuration file. See `Application Configuration`_ and -`Mojit Configuration`_ for details about specific configuration files. +By default, configuration files in Mojito have a general top-level +structure and are in JSON format. At the top level of each configuration +file is an array. Each item of the array is an object that configures +one component of Mojito, such as logging, assets, mojits, static resources, etc. + +Each configuration object is required to have a ``settings`` property that +specifies conditions for applying the configuration settings. These conditions +could be used to determine the configurations in different environments. + +Below is the skeleton of a configuration file. See `Application Configuration`_ +and `Mojit Configuration`_ for details about specific configuration files. .. code-block:: javascript @@ -38,15 +46,28 @@ Below is the skeleton of a configuration file. See `Application Configuration`_ ... ] +.. _config_basic_file-yaml: + +YAML +#### + +Mojito also supports configuration files in YAML format. The YAML file extension could +be ``.yaml`` or ``.yml``. Mojito allows comments in the YAML files. When both JSON file +and YAML files are present, the YAML file is used and a warning is issued. For the data +types of the YAML elements, please see the JSON configuration tables in +:ref:`Application Configuration <configure_mj-app>`, +:ref:`Routing <configure_mj-routing>`, and :ref:`Mojit Configuration <configure_mj-mojit>`. + .. _configure_mj-app: Application Configuration ========================= -Both the server and client runtimes of an application can be configured. The application -is configured in the ``application.json`` file in the application directory. The file -consists of an array of zero or more ``configuration`` objects. Using the ``configuration`` -object, you can configure the following for your application: +Both the server and client runtimes of an application can be configured. The +application is configured in the ``application.json`` file in the application +directory. The file consists of an array of zero or more ``configuration`` +objects. Using the ``configuration`` object, you can configure the following +for your application: - port number - location of routing files @@ -57,9 +78,9 @@ object, you can configure the following for your application: - logging - static resources -The tables below describe the ``configuration`` object and its properties. Those -properties that have object values have tables below describing their properties as well -except the ``config`` object, which is user defined. +The tables below describe the ``configuration`` object and its properties. +Those properties that have object values have tables below describing their +properties as well except the ``config`` object, which is user defined. .. _app-configuration_obj: @@ -69,6 +90,11 @@ configuration Object +--------------------------------------------------------+----------------------+-------------------+--------------------------------------------------------+ | Property | Data Type | Default Value | Description | +========================================================+======================+===================+========================================================+ +| ``actionTimeout`` | number | 60000 | The number of milliseconds that an action can | +| | | | run without calling ``ac.done`` or ``ac.error`` before | +| | | | Mojito logs a warning and invokes ``ac.error`` with a | +| | | | Timeout error. | ++--------------------------------------------------------+----------------------+-------------------+--------------------------------------------------------+ | ``appPort`` | number | 8666 | The port number (1-65355) that the application | | | | | will use. | +--------------------------------------------------------+----------------------+-------------------+--------------------------------------------------------+ @@ -84,14 +110,6 @@ configuration Object | | | | should generally only be set if you are building | | | | | an offline application. | +--------------------------------------------------------+----------------------+-------------------+--------------------------------------------------------+ -| ``embedJsFilesInHtmlFrame`` | boolean | false | When Mojito is deployed to the client, this property | -| | | | specifies whether the body of the JavaScript files | -| | | | should be embedded in the HTML page. | -+--------------------------------------------------------+----------------------+-------------------+--------------------------------------------------------+ -| ``log`` | object | N/A | Specifies the configuration for the logging | -| | | | subsystem. The configuration is given | -| | | | independently for the two different runtimes. | -+--------------------------------------------------------+----------------------+-------------------+--------------------------------------------------------+ | ``middleware`` | array of strings | [] | A list of paths to the Node.js module that exports | | | | | a Connect middleware function. | +--------------------------------------------------------+----------------------+-------------------+--------------------------------------------------------+ @@ -135,12 +153,6 @@ configuration Object | | | | ``"lang:en"``. See `Using Context Configurations | | | | | <../topics/mojito_using_contexts.html>`_. | +--------------------------------------------------------+----------------------+-------------------+--------------------------------------------------------+ -| ``shareYUIInstance`` | boolean | false | Specifies whether the use of a single shared YUI | -| | | | instance is enabled. Normally, each mojit runs in | -| | | | its own YUI instance. To use the shared YUI | -| | | | instance, each mojit has to be configured to use | -| | | | the shared instance. | -+--------------------------------------------------------+----------------------+-------------------+--------------------------------------------------------+ | `specs <#specs-obj>`_ | object | N/A | Specifies the mojit instances. See the | | | | | :ref:`specs_obj` for details. | +--------------------------------------------------------+----------------------+-------------------+--------------------------------------------------------+ @@ -154,8 +166,9 @@ configuration Object | | | | tunnel from the client back to the server. | +--------------------------------------------------------+----------------------+-------------------+--------------------------------------------------------+ | `yui <#yui-obj>`_ | object | N/A | When Mojito is deployed to client, the | -| | | | :ref:`yui_obj` specifies where | -| | | | and how to obtain YUI 3. | +| | | | :ref:`yui_obj` specifies where and how to obtain | +| | | | YUI 3. The ``yui.config`` object also contains | +| | | | logging configurations. | +--------------------------------------------------------+----------------------+-------------------+--------------------------------------------------------+ @@ -165,18 +178,22 @@ configuration Object builds Object ############# -+-----------------------------+---------------+------------------------------------------------+ -| Property | Data Type | Description | -+=============================+===============+================================================+ -| `html5app <#html5app-obj>`_ | object | Specifies configuration for HTML5 applications | -| | | created with ``$ mojito build html5app``. | -+-----------------------------+---------------+------------------------------------------------+ ++---------------------------------+---------------+--------------------------------------------------------------------------------+ +| Property | Data Type | Description | ++=================================+===============+================================================================================+ +| `html5app <#html5app-obj>`_ | object | Specifies configuration for HTML5 applications | +| | | created with ``$ mojito build html5app``. | ++---------------------------------+---------------+--------------------------------------------------------------------------------+ +| `hybridapp <#hybridapp-obj>`_ | object | Specifies configuration for hybrid applications | +| | | created with the following: | +| | | ``mojito build hybridapp -n <snapshot_name> -t <snapshot_tag> [<build_path>]`` | ++---------------------------------+---------------+--------------------------------------------------------------------------------+ .. _html5app_obj: html5app Object -############### +*************** +------------------------+---------------+-----------+---------------+-------------------------------------------+ | Property | Data Type | Required? | Default Value | Description | @@ -184,6 +201,15 @@ html5app Object | ``attachManifest`` | boolean | no | ``false`` | When ``true``, the ``manifest`` | | | | | | attribute is added to ``<html>``. | +------------------------+---------------+-----------+---------------+-------------------------------------------+ +| ``buildDir`` | string | no | none | The path to the built HTML5 application. | +| | | | | If not specified, the HTML5 application | +| | | | | will be placed in | +| | | | | ``artifacts/build/html5app``. The | +| | | | | specified path for ``buildDir`` will be | +| | | | | overridden if a build path is given to | +| | | | | the following command: | +| | | | | ``mojito build html5app [<build_path>]`` | ++------------------------+---------------+-----------+---------------+-------------------------------------------+ | ``forceRelativePaths`` | boolean | no | ``false`` | When ``true``, the server-relative paths | | | | | | (those starting with "/") are converted | | | | | | into paths relative to the generated | @@ -202,45 +228,44 @@ html5app Object | | | | | ``urls: [ '/view.html']`` | +------------------------+---------------+-----------+---------------+-------------------------------------------+ -log Object -########## - -+----------------+---------------+-------------------------------------------+ -| Property | Data Type | Description | -+================+===============+===========================================+ -| ``client`` | object | The log configuration for the client. | -+----------------+---------------+-------------------------------------------+ -| ``server`` | object | The log configuration for the server. | -+----------------+---------------+-------------------------------------------+ +.. _hybrid_obj: + +hybridapp Object +**************** + +The ``hybridapp`` object is used to specify build information for hybrid applications, +which are created with the command +``mojito build hybridapp -n <snapshot_name> -t <snapshot_tag> [<build_path>]``. +Hybrid applications are HTML5 applications that are designed to work with future +Cocktails components that will enable hybrid applications to use the native features +of mobile devices. Currently, hybrid applications are strictly an experimental feature of +Mojito and Cocktails. + ++------------------------+---------------+-----------+-------------------------------+--------------------------------------------------------------------------------+ +| Property | Data Type | Required? | Default Value | Description | ++========================+===============+===========+===============================+================================================================================+ +| ``buildDir`` | string | no | none | The build path of the hybrid application. If not specified, the hybrid | +| | | | | application will be placed in ``artifacts/build/hybridapp``. The specified | +| | | | | path for ``buildDir`` will be overridden if a build path is given to the | +| | | | | following command: | +| | | | | ``mojito build hybridapp -n <snapshot_name> -t <snapshot_tag> [<build_path>]`` | ++------------------------+---------------+-----------+-------------------------------+--------------------------------------------------------------------------------+ +| ``forceRelativePaths`` | boolean | no | ``false`` | When ``true``, the server-relative paths (those starting with "/") are | +| | | | | converted into paths relative to the generated file. | ++------------------------+---------------+-----------+-------------------------------+--------------------------------------------------------------------------------+ +| ``packages`` | object | yes | none | An object containing key-value pairs that specify dependencies and their | +| | | | | associated versions. When you create a hybrid application with the command | +| | | | | ``mojito build hybridapp``, the dependencies listed in ``packages`` are added | +| | | | | to the ``packages.json`` of the built hybrid application. | ++------------------------+---------------+-----------+-------------------------------+--------------------------------------------------------------------------------+ +| ``urls`` | array of | yes | none | The routing paths to views that be rendered into static pages and then cached | +| | strings | | | so that the page can be viewed offline. For example, if the running | +| | | | | application renders the view ``view.html``, you could configure the | +| | | | | application to statically create and cache ``view.html`` in | +| | | | | ``{app_dir}/artifacts/builds/hybridapp`` (default location) using the | +| | | | | following: ``urls: [ '/view.html']`` | ++------------------------+---------------+-----------+-------------------------------+--------------------------------------------------------------------------------+ -server/client Object -#################### - -+----------------------+---------------+-------------------+-----------------------------------------------------------+ -| Property | Data Type | Default Value | Description | -+======================+===============+===================+===========================================================+ -| ``buffer`` | boolean | false | Determines whether Mojito should buffer log | -| | | | entries (``true``) or output each as they occur | -| | | | (``false``). | -+----------------------+---------------+-------------------+-----------------------------------------------------------+ -| ``defaultLevel`` | string | "info" | Specifies the default log level to log entries. See | -| | | | `Log Levels <../topics/mojito_logging.html#log-levels>`_. | -+----------------------+---------------+-------------------+-----------------------------------------------------------+ -| ``level`` | string | "info" | Specifies the lowest log level to include in th | -| | | | log output. See | -| | | | `Log Levels <../topics/mojito_logging.html#log-levels>`_. | -+----------------------+---------------+-------------------+-----------------------------------------------------------+ -| ``maxBufferSize`` | number | 1024 | If ``buffer`` is set to ``true``, specifies the | -| | | | number of log entries to store before flushing to | -| | | | output. | -+----------------------+---------------+-------------------+-----------------------------------------------------------+ -| ``timestamp`` | boolean | true | Determines whether the timestamp is included in | -| | | | the log output. | -+----------------------+---------------+-------------------+-----------------------------------------------------------+ -| ``yui`` | boolean | false | Determines whether the log entries generated by | -| | | | the YUI framework should be included in the Mojito | -| | | | log output. | -+----------------------+---------------+-------------------+-----------------------------------------------------------+ .. _specs_obj: @@ -271,7 +296,7 @@ specs Object | ``defer`` | boolean | If true and the mojit instance is a child of the ``HTMLFrameMojit``, | | | | an empty node will initially be rendered and then content will be | | | | lazily loaded. See | -| | | `LazyLoadMojit <../topics/mojito_framework_mojits.html#lazyloadmojit>`_ | +| | | `LazyLoadMojit <../topics/mojito_frame_mojits.html#lazyloadmojit>`_ | | | | for more information. | +------------------------------+---------------+-------------------------------------------------------------------------+ | ``proxy`` | object | This is a normal mojit spec to proxy this mojit's execution | @@ -283,11 +308,6 @@ specs Object | | | spec will be attached as a *proxied* object on the proxy mojit's | | | | ``config`` for it to handle as necessary. | +------------------------------+---------------+-------------------------------------------------------------------------+ -| ``shareYUIInstance`` | boolean | Determines whether the mojit should use the single shared YUI | -| | | instance. To use the single shared YUI instance, the | -| | | ``shareYUIInstance`` in ``application.json`` must be set to | -| | | ``true``. The default value is ``false``. | -+------------------------------+---------------+-------------------------------------------------------------------------+ | ``type`` | string | Specifies the mojit type. Either the ``type`` or ``base`` property is | | | | required in the ``specs`` object. | +------------------------------+---------------+-------------------------------------------------------------------------+ @@ -295,7 +315,7 @@ specs Object .. _config_obj: config Object -############# +************* +--------------------------+---------------+--------------------------------------------------------------------------------+ | Property | Data Type | Description | @@ -303,7 +323,7 @@ config Object | ``child`` | object | Contains the ``type`` property that specifies mojit type and may also | | | | contain a ``config`` object. This property can only be used when the mojit | | | | instance is a child of the ``HTMLFrameMojit``. See | -| | | `HTMLFrameMojit <../topics/mojito_framework_mojits.html#htmlframemojit>`_ for | +| | | `HTMLFrameMojit <../topics/mojito_frame_mojits.html#htmlframemojit>`_ for | | | | more information. | +--------------------------+---------------+--------------------------------------------------------------------------------+ | ``children`` | object | Contains one or more mojit instances that specify the mojit type with | @@ -314,9 +334,9 @@ config Object | | | See :ref:`deploy_app` for details. The default value is ``false``. Your | | | | mojit code will only be deployed if it is a child of ``HTMLFrameMojit``. | +--------------------------+---------------+--------------------------------------------------------------------------------+ -| ``title`` | string | If application is using the framework mojit ``HTMLFrameMojit``, | +| ``title`` | string | If application is using the frame mojit ``HTMLFrameMojit``, | | | | the value will be used for the HTML ``<title>`` element. | -| | | See `HTMLFrameMojit <../topics/mojito_framework_mojits.html#htmlframemojit>`_ | +| | | See `HTMLFrameMojit <../topics/mojito_frame_mojits.html#htmlframemojit>`_ | | | | for more information. | +--------------------------+---------------+--------------------------------------------------------------------------------+ | ``{key}`` | any | The ``{key}`` is user defined and can have any type of configuration value. | @@ -371,24 +391,18 @@ staticHandling Object yui Object ########## -See `Example Application Configurations`_ for an example of the ``yui`` object. For -options for the ``config`` object, see the `YUI config Class <http://yuilibrary.com/yui/docs/api/classes/config.html>`_. +See `Example Application Configurations`_ for an example of the ``yui`` object. +--------------------------------+----------------------+------------------------------------------------------------------------+ | Property | Data Type | Description | +================================+======================+========================================================================+ | ``base`` | string | Specifies the prefix from which to load all YUI 3 libraries. | +--------------------------------+----------------------+------------------------------------------------------------------------+ -| ``config`` | object | Used to populate the `YUI_config <http://yuilibrary.com/yui/docs/yui/ | +| :ref:`config <yui_config>` | object | Used to populate the `YUI_config <http://yuilibrary.com/yui/docs/yui/ | | | | #yui_config>`_ global variable that allows you to configure every YUI | | | | instance on the page even before YUI is loaded. For example, you can | -| | | configure YUI not to load its default CSS with the following: | -| | | ``"yui": { "config": { "fetchCSS": false } }`` | -+--------------------------------+----------------------+------------------------------------------------------------------------+ -| ``dependencyCalculations`` | string | Specifies whether the YUI module dependencies are calculated at | -| | | server startup (pre-computed) or deferred until a particular | -| | | module is needed (on demand). The following are the two allowed | -| | | values: ``precomputed``, ``ondemand``, ``precomputed+ondemand`` | +| | | configure logging or YUI not to load its default CSS with the | +| | | following: ``"yui": { "config": { "fetchCSS": false } }`` | +--------------------------------+----------------------+------------------------------------------------------------------------+ | ``extraModules`` | array of strings | Specifies additional YUI library modules that should be added to | | | | the page when Mojito is sent to the client. | @@ -406,6 +420,49 @@ options for the ``config`` object, see the `YUI config Class <http://yuilibrary. +--------------------------------+----------------------+------------------------------------------------------------------------+ +.. _yui_config: + +config Object +************* + +The ``config`` object can be used to configure all the options for the YUI instance. +To see all the options for the ``config`` object, see the +`YUI config Class <http://yuilibrary.com/yui/docs/api/classes/config.html>`_. +Some of the properties of the ``config`` object used for configuring logging are shown below. + + ++----------------------+---------------+-------------------+-----------------------------------------------------------+ +| Property | Data Type | Default Value | Description | ++======================+===============+===================+===========================================================+ +| ``buffer`` | boolean | false | Determines whether Mojito should buffer log | +| | | | entries (``true``) or output each as they occur | +| | | | (``false``). | ++----------------------+---------------+-------------------+-----------------------------------------------------------+ +| ``debug`` | boolean | true | Determines whether ``Y.log`` messages are written to the | +| | | | browser console. | ++----------------------+---------------+-------------------+-----------------------------------------------------------+ +| ``defaultLevel`` | string | "info" | Specifies the default log level to log entries. See | +| | | | `Log Levels <../topics/mojito_logging.html#log-levels>`_. | ++----------------------+---------------+-------------------+-----------------------------------------------------------+ +| ``logExclude`` | object | none | Excludes the logging of the YUI module(s) specified. | +| | | | For example: ``logExclude: { "logModel": true }`` | ++----------------------+---------------+-------------------+-----------------------------------------------------------+ +| ``logInclude`` | object | none | Includes the logging of the YUI module(s) specified. | +| | | | For example: ``logInclude: { "searchMojit": true }`` | ++----------------------+---------------+-------------------+-----------------------------------------------------------+ +| ``logLevel`` | string | "info" | Specifies the lowest log level to include in th | +| | | | log output. See | +| | | | `Log Levels <../topics/mojito_logging.html#log-levels>`_. | ++----------------------+---------------+-------------------+-----------------------------------------------------------+ +| ``maxBufferSize`` | number | 1024 | If ``buffer`` is set to ``true``, specifies the | +| | | | number of log entries to store before flushing to | +| | | | output. | ++----------------------+---------------+-------------------+-----------------------------------------------------------+ +| ``timestamp`` | boolean | true | Determines whether the timestamp is included in | +| | | | the log output. | ++----------------------+---------------+-------------------+-----------------------------------------------------------+ + + .. _config-multiple_mojits: @@ -415,6 +472,8 @@ Configuring Applications to Have Multiple Mojits Applications not only can specify multiple mojit instances in ``application.json``, but mojits can have one or more child mojits as well. +.. _config_mult_mojits-app: + Application With Multiple Mojits ################################ @@ -438,12 +497,14 @@ mojit instances ``sign_in`` and ``sign_out`` are defined: } ] +.. _config_mult_mojits-parent_child: + Parent Mojit With Child Mojit ############################# -A mojit instance can be configured to have a child mojit using the ``child`` object. In -the example ``application.json`` below, the mojit instance ``parent`` of type -``ParentMojit`` has a child mojit of type ``ChildMojit``. +A mojit instance can be configured to have a child mojit using the ``child`` +object. In the example ``application.json`` below, the mojit instance ``parent`` +of type ``ParentMojit`` has a child mojit of type ``ChildMojit``. .. code-block:: javascript @@ -463,12 +524,14 @@ the example ``application.json`` below, the mojit instance ``parent`` of type } ] +.. _config_mult_mojits-parent_children: + Parent Mojit With Children ########################## -A mojit instance can also be configured to have more than one child mojits using the -``children`` object that contains mojit instances. To execute the children, the parent -mojit would use the ``Composite addon``. +A mojit instance can also be configured to have more than one child mojits using +the ``children`` object that contains mojit instances. To execute the children, +the parent mojit would use the ``Composite addon``. See `Composite Mojits <../topics/mojito_composite_mojits.html#composite-mojits>`_ for more information. @@ -498,6 +561,9 @@ In the example ``application.json`` below, the mojit instance ``father`` of type } ] + +.. _config_mult_mojits-child_children: + Child Mojit With Children ######################### @@ -539,7 +605,6 @@ child ``son``, which has the children ``grandson`` and ``granddaughter``. ] - .. _deploy_app: Configuring Applications to Be Deployed to Client @@ -549,6 +614,8 @@ To configure Mojito to deploy code to the client, you must be using the ``HTMLFr as the parent mojit and also set the ``deploy`` property of the :ref:`app-configuration_obj` object to ``true`` in the ``config`` object of your mojit instance. +.. _deploy_app-what: + What Gets Deployed? ################### @@ -562,6 +629,8 @@ affinity, then the controller and its dependencies are deployed to the client as the affinity of the controller is ``server``, the invocation occurs on the server. In either case, the binder is able to seamlessly invoke the controller. +.. _deploy_app-ex: + Example ####### @@ -638,6 +707,9 @@ Although mojit instances are defined at the application level, you configure met defaults for the mojit at the mojit level. The following sections will cover configuration at the mojit level as well as examine the configuration of the mojit instance. + +.. _configure_mojit-metadata: + Configuring Metadata -------------------- @@ -669,6 +741,9 @@ The table below describes the ``configuration`` object in ``definition.json``. | | | | information. | +------------------+----------------------+-------------------+--------------------------------------------------------+ + +.. _configure_mojit-app_level: + Configuring and Using an Application-Level Mojit ------------------------------------------------ @@ -699,43 +774,49 @@ the application-level ``Foo`` mojit, the controller of the Bar mojit would inclu YUI.add('BarMojit', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, index: function(actionContext) { actionContext.done({title: "Body"}); } }; }, '0.0.1', {requires: ['FooMojitModel']}); + +.. _configure_mojit-defaults: + Configuring Defaults for Mojit Instances ---------------------------------------- -The ``defaults.json`` file in the mojit type directory can be used to specify defaults for -each mojit instance of the type. The format is the same as the mojit instance as specified -in the ``specs`` object of ``application.json``. This means that you can specify a default -action, as well as any defaults you might want to put in the ``config`` object. +The ``defaults.json`` file in the mojit type directory can be used to specify +defaults for each mojit instance of the type. The format is the same as the mojit +instance as specified in the ``specs`` object of ``application.json``. This means +that you can specify a default action, as well as any defaults you might want to +put in the ``config`` object. + +.. _configure_mojit-instances: Mojit Instances --------------- -A mojit instance is made entirely of configuration. This configuration specifies which -mojit type to use and configures an instance of that type. The mojit instances are defined -in the ``specs`` object of the ``application.json`` file. +A mojit instance is made entirely of configuration. This configuration specifies +which mojit type to use and configures an instance of that type. The mojit +instances are defined in the ``specs`` object of the ``application.json`` file. + +See :ref:`configure_mj-app` and :ref:`app_config-ex` for details of the ``specs`` +object. -See :ref:`configure_mj-app` and :ref:`app_config-ex` for details of the ``specs`` object. +.. _configure_mojit_instances-using: Using Mojit Instances ##################### -When a mojit instance is defined in ``application.json``, routing paths defined in -``routes.json`` can be associated with an action of that mojit instance. Actions are -references to functions in the mojit controllers. When a client makes an HTTP request on -a defined routing path, the function in the mojit controller that is referenced by the -action from the mojit instance is called. +When a mojit instance is defined in ``application.json``, routing paths defined +in ``routes.json`` can be associated with an action of that mojit instance. +Actions are references to functions in the mojit controllers. When a client +makes an HTTP request on a defined routing path, the function in the mojit +controller that is referenced by the action from the mojit instance is called. -For example, the ``application.json`` below defines the ``foo`` mojit instance of the -mojit type ``Foo``. +For example, the ``application.json`` below defines the ``foo`` mojit instance +of the mojit type ``Foo``. .. code-block:: javascript @@ -753,9 +834,9 @@ mojit type ``Foo``. } ] -The ``routes.json`` below uses the ``foo`` instance to call the ``index`` action when an -HTTP GET request is made on the root path. The ``index`` action references the ``index`` -function in the controller of the ``Foo`` mojit. +The ``routes.json`` below uses the ``foo`` instance to call the ``index`` action +when an HTTP GET request is made on the root path. The ``index`` action references +the ``index`` function in the controller of the ``Foo`` mojit. .. code-block:: javascript @@ -770,19 +851,23 @@ function in the controller of the ``Foo`` mojit. } ] +.. _configure_mj-routing: -Routing +routing ======= -In Mojito, routing is the mapping of URLs to specific mojit actions. This section will -describe the routing configuration file ``routes.json`` and the following two ways to -configure routing: +In Mojito, routing is the mapping of URLs to specific mojit actions. This section +will describe the routing configuration file ``routes.json`` and the following +two ways to configure routing: - Map Routes to Specific Mojit Instances and Actions - Generate URLs from the Controller -See `Code Examples: Configuring Routing <../code_exs/route_config.html>`_ to see an -example of configuring routing in a Mojito application. +See `Code Examples: Configuring Routing <../code_exs/route_config.html>`_ to +see an example of configuring routing in a Mojito application. + + +.. _configure_routing-file: Routing Configuration File -------------------------- @@ -838,20 +923,27 @@ The table below describes the properties of the ``route`` object of ``routes.js | | | | "post" ]`` | +----------------+----------------------+---------------+--------------------------------------------------------+ + +.. _configure_routing-mapping: + Map Routes to Specific Mojit Instances and Actions -------------------------------------------------- -This type of route configuration is the most sophisticated and recommended for production -applications. To map routes to a mojit instance and action, you create the file -``routes.json`` in your application directory. The ``routes.json`` file allows you to -configure a single or multiple routes and specify the HTTP method and action to use for -each route. +This type of route configuration is the most sophisticated and recommended for +production applications. To map routes to a mojit instance and action, you create +the file ``routes.json`` in your application directory. The ``routes.json`` file +allows you to configure a single or multiple routes and specify the HTTP method +and action to use for each route. + + +.. _routing_mapping-single: Single Route ############ -To create a route, you need to create a mojit instance that can be mapped to a path. In the -``application.json`` below, the ``hello`` instance of type ``HelloMojit`` is defined. +To create a route, you need to create a mojit instance that can be mapped to a +path. In the ``application.json`` below, the ``hello`` instance of type +``HelloMojit`` is defined. .. code-block:: javascript @@ -867,9 +959,9 @@ To create a route, you need to create a mojit instance that can be mapped to a p } ] -The ``hello`` instance and a function in the ``HelloMojit`` controller can now be mapped -to a route path in ``routes.json`` file. In the ``routes.json`` below, the ``index`` -function is called when an HTTP GET call is made on the root path. +The ``hello`` instance and a function in the ``HelloMojit`` controller can now +be mapped to a route path in ``routes.json`` file. In the ``routes.json`` below, +the ``index`` function is called when an HTTP GET call is made on the root path. .. code-block:: javascript @@ -884,10 +976,10 @@ function is called when an HTTP GET call is made on the root path. } ] -Instead of using the ``hello`` mojit instance defined in the ``application.json`` shown -above, you can create an anonymous instance of ``HelloMojit`` for mapping an action to a -route path. In the ``routes.json`` below, an anonymous instance of ``HelloMojit`` is made -by prepending "@" to the mojit type. +Instead of using the ``hello`` mojit instance defined in the ``application.json`` +shown above, you can create an anonymous instance of ``HelloMojit`` for mapping +an action to a route path. In the ``routes.json`` below, an anonymous instance +of ``HelloMojit`` is made by prepending "@" to the mojit type. .. code-block:: javascript @@ -903,11 +995,14 @@ by prepending "@" to the mojit type. } ] + +.. _routing_mapping-multiple: + Multiple Routes ############### -To specify multiple routes, you create multiple route objects that contain ``verb``, -``path``, and ``call`` properties in ``routes.json`` as seen here: +To specify multiple routes, you create multiple route objects that contain +``verb``, ``path``, and ``call`` properties in ``routes.json`` as seen here: .. code-block:: javascript @@ -940,10 +1035,11 @@ The ``routes.json`` file above creates the following routes: - ``http://localhost:8666/bar`` - ``http://localhost:8666/anything`` -Notice that the ``routes.json`` above uses the two mojit instances ``foo-1`` and ``bar-1``; -these instances must be defined in the ``application.json`` file before they can be mapped -to a route path. Also, the wildcard used in ``root`` object configures Mojito to call -``foo-1.index`` when HTTP GET calls are made on any undefined path. +Notice that the ``routes.json`` above uses the two mojit instances ``foo-1`` and +``bar-1``; these instances must be defined in the ``application.json`` file before +they can be mapped to a route path. Also, the wildcard used in ``root`` object +configures Mojito to call ``foo-1.index`` when HTTP GET calls are made on any +undefined path. .. _routing_params: @@ -951,13 +1047,13 @@ to a route path. Also, the wildcard used in ``root`` object configures Mojito to Adding Routing Parameters ------------------------- -You can configure a routing path to have routing parameters with the ``params`` property. -Routing parameters are accessible from the ``ActionContext`` object using the -`Params addon <../../api/classes/Params.common.html>`_. +You can configure a routing path to have routing parameters with the ``params`` +property. Routing parameters are accessible from the ``ActionContext`` object +using the `Params addon <../../api/classes/Params.common.html>`_. -In the example ``routes.json`` below, routing parameters are added with an object. To get -the value for the routing parameter ``page`` from a controller, you would use -``ac.params.getFromRoute("page")``. +In the example ``routes.json`` below, routing parameters are added with an object. +To get the value for the routing parameter ``page`` from a controller, you would +use ``ac.params.getFromRoute("page")``. .. code-block:: javascript @@ -985,11 +1081,12 @@ the value for the routing parameter ``page`` from a controller, you would use Using Parameterized Paths to Call a Mojit Action ------------------------------------------------ -Your routing configuration can also use parameterized paths to call mojit actions. In the -``routes.json`` below, the ``path`` property uses parameters to capture a part of the -matched URL and then uses that captured part to replace ``{{mojit-action}}`` in the value -for the ``call``property. Any value can be used for the parameter as long as it is -prepended with a colon (e.g., ``:foo``). After the parameter has been replaced by a value +Your routing configuration can also use parameterized paths to call mojit +actions. In the ``routes.json`` below, the ``path`` property uses parameters +to capture a part of the matched URL and then uses that captured part to +replace ``{{mojit-action}}`` in the value for the ``call``property. Any +value can be used for the parameter as long as it is prepended with a +colon (e.g., ``:foo``). After the parameter has been replaced by a value given in the path, the call to the action should have the following syntax: ``{mojit_instance}.(action}`` @@ -1012,11 +1109,11 @@ given in the path, the call to the action should have the following syntax: } ] -For example, based on the ``routes.json`` above, an HTTP GET call made on the path -``http://localhost:8666/foo/index`` would call the ``index`` function in the controller -because the value of ``:mojit-action`` in the path (``index`` in this case) would be then -replace ``{mojit-action}}`` in the ``call`` property. The following URLs call the -``index`` and ``myAction`` functions in the controller. +For example, based on the ``routes.json`` above, an HTTP GET call made on the +path ``http://localhost:8666/foo/index`` would call the ``index`` function in +the controller because the value of ``:mojit-action`` in the path (``index`` in +this case) would be then replace ``{mojit-action}}`` in the ``call`` property. +The following URLs call the ``index`` and ``myAction`` functions in the controller. - ``http://localhost:8666/foo/index`` @@ -1029,8 +1126,8 @@ replace ``{mojit-action}}`` in the ``call`` property. The following URLs call th Using Regular Expressions to Match Routing Paths ------------------------------------------------ -You can use the ``regex`` property of the ``routing`` object to define a key-value pair -that defines a path parameter and a regular expression. The key is prepended +You can use the ``regex`` property of the ``routing`` object to define a key-value +pair that defines a path parameter and a regular expression. The key is prepended with a colon when represented as a path parameter. For example, the key ``name`` would be represented as ``:name`` as a path parameter: ``"path": "/:name"``. The associated value contains the regular expression that is matched against @@ -1061,6 +1158,11 @@ would call the ``index`` action: - ``http://localhost:8666/1_mojito`` - ``http://localhost:8666/99_Mojitos`` +.. _generate_urls: + +.. _generate_urls: + +.. _generate_urls: Generate URLs from the Controller --------------------------------- @@ -1095,6 +1197,7 @@ with the ``make`` method use the mojit instance and function specified in the The ``index`` function above returns the following URL: ``http://localhost:8666/foo?foo=bar`` +.. _mojito_configuring-access: Accessing Configurations from Mojits ==================================== @@ -1105,24 +1208,65 @@ can also access configuration from other functions through the ``actionContext`` The ``init`` function in the binder instead of a configuration object is passed the ``mojitProxy`` object, which enables you to get the configurations. + +.. _configuring_access-applevel: + Application-Level Configurations -------------------------------- -Only the mojit controller has access to application-level configurations through the -``actionContext`` object. +Only the mojit controller has access to application-level configurations +using the ActionContext ``Config`` addon. + +.. _access-applicationjson: application.json ################ -The controller functions that are passed an ``actionContext`` object can reference the -application configurations in ``application.json`` with ``ac.app.config``. For example, if -you wanted to access the ``specs`` object defined in ``application.json``, -you would use ``ac.app.config.spec``. +The controller functions that are passed an ``actionContext`` object can get the +application configurations in ``application.json`` with the method ``getAppConfig`` +of the ``Config`` addon. + +For example, if you wanted to access the ``specs`` object defined in ``application.json``, +you would use ``ac.config.getAppConfig()`` as shown here: + +.. code-block:: javascript + + YUI.add('myMojit', function(Y, NAME) { + Y.namespace('mojito.controllers')[NAME] = { + index: function(ac) { + // Get the application configuration through + // the Config addon. + var app_config = ac.config.getAppConfig(); + Y.log(app_config); + ac.done({ status: "Showing app config in the log."}); + } + }; + }, '0.0.1', {requires: ['mojito', 'mojito-config-addon']}); + +.. _access-routesjson: routes.json ########### -The routing configuration can be accessed with ``ac.app.routes``. +The routing configuration can be accessed with the method ``getRoutes`` +of the ``Config`` addon. + + +.. code-block:: javascript + + YUI.add('myMojit', function(Y, NAME) { + Y.namespace('mojito.controllers')[NAME] = { + index: function(ac) { + // Get the routing configuration through + // the Config addon. + var route_config = ac.config.getRoutes(); + Y.log(route_config); + ac.done({ status: "Showing routing config in the log."}); + } + }; + }, '0.0.1', {requires: ['mojito', 'mojito-config-addon']}); + +.. _access_configs-context: Application Context ------------------- @@ -1150,6 +1294,7 @@ Below is an example of the ``context`` object: tz: '' } +.. _configuring_access-mojit: Mojit-Level Configurations -------------------------- @@ -1159,6 +1304,9 @@ configurations in the ``config`` object of a mojit instance in ``application.jso default configurations for a mojit in ``mojits/{mojit_name}/defaults.json``. The configurations of ``application.json`` override those in ``defaults.json``. + +.. _access_mojit-controller: + Controller ########## @@ -1169,6 +1317,9 @@ Use ``ac.config.get`` to access configuration values from ``application.json`` a ``defaults.json`` and ``ac.config.getDefinition`` to access definition values from ``definition.json``. + +.. _access_mojit-model: + Model ##### @@ -1177,6 +1328,9 @@ model functions need the configurations, you need to save the configurations to ``this`` reference because no ``actionContext`` object is passed to the model, so your model does not have access to the ``Config`` addon. + +.. _access_mojit-binder: + Binder ###### diff --git a/docs/dev_guide/intro/mojito_mojits.rst b/docs/dev_guide/intro/mojito_mojits.rst index f47bc46c0..3542b41bf 100644 --- a/docs/dev_guide/intro/mojito_mojits.rst +++ b/docs/dev_guide/intro/mojito_mojits.rst @@ -6,13 +6,14 @@ The basic unit of composition and reuse in a Mojito application is a mojit. Visually, you can think of a mojit as the rectangular area of a page that was constructed by a Mojito application. -The following sections explain why we chose the name *mojit* and then examine the -mojit's architecture and structure. This chapter is meant as an overview of mojits and does -not show how to configure, create, or use mojits. For those implementation details, see the -following: +The following sections explain why we chose the name *mojit* and then examine +the mojit's architecture and structure. This chapter is meant as an overview +of mojits and does not show how to configure, create, or use mojits. For those +implementation details, see the following: - `Mojit Configuration <mojito_configuring.html#mojit-configuration>`_ -- `Mojits <mojito_apps.html#mojits>`_ in the `Mojito Applications <mojito_apps.html>`_ chapter +- `Mojits <mojito_apps.html#mojits>`_ in the `Mojito Applications <mojito_apps.html>`_ + chapter - `MVC in Mojito <mojito_mvc.html>`_ - `Mojito API Documentation <../../api>`_ @@ -25,7 +26,8 @@ There are (at least) two very commonly used names given to the basic portions of a page, site, or application, viz. module and widget. Depending upon the context, each of these terms will be interpreted in different ways by different people. In the hope of alleviating misinterpretation, we have chosen -to create our own word: mojit (derived from module + widget and pronounced "mod-jit"). +to create our own word: mojit (derived from module + widget and pronounced +"mod-jit"). .. _mojit-architecture: @@ -36,8 +38,8 @@ From the diagram below, you can see that the mojit has an MVC structure centered around the `Action Context <mojito_architecture.html#api-action-context>`_ and can be deployed to the client or run on the server. Also note that the Mojit Proxy allows client-side code (binders) to communicate with server-side code through the -Action Context. The sections below describe the main components in the diagram that are -shaded in green. +Action Context. The sections below describe the main components in the diagram that +are shaded in green. @@ -48,6 +50,8 @@ shaded in green. :width: 610px :align: center + + .. _mojit_arch-binders: Binders @@ -60,11 +64,12 @@ on the page, and execute actions on the mojit that the binder is attached to. .. _binders-mojitProxy: Mojit Proxy -``````````` +*********** -The Mojit Proxy is the conduit for communication between the binder and the mojit's -``ActionContext`` object and other mojits on the page. In code, the Mojit Proxy is represented by -the `mojitProxy object <mojito_binders.html#mojitproxy-object>`_. +The Mojit Proxy is the conduit for communication between the binder and the +mojit's ``ActionContext`` object and other mojits on the page. In code, the +Mojit Proxy is represented by the +`mojitProxy object <mojito_binders.html#mojitproxy-object>`_. See `Mojito Binders <mojito_binders.html>`_ to learn how binders use the ``mojitProxy`` object to communicate with server-side code. @@ -110,8 +115,8 @@ View Files ########## View files are called templates in Mojito. View templates can contain both HTML -and templating tags/expressions, such as Mustache or Handlebars, and are rendered into markup that is -outputted to the client. +and templating tags/expressions, such as Mustache or Handlebars, and are rendered into +markup that is outputted to the client. See `Views <mojito_mvc.html#views>`_ in the `MVC in Mojito <mojito_mvc.html>`_ chapter for more information. @@ -136,8 +141,9 @@ centralizes the representation and management of the Mojit's data. The Active View provides for presentation and user interaction. See `MVC in Mojito <mojito_mvc.html>`_ for a detailed explanation of how MVC works -in Mojito and `Mojits <mojito_apps.html#mojits>`_ in the `Mojito Applications <mojito_apps.html>`_ -chapter for information about the directory structure and files of a mojit. +in Mojito and `Mojits <mojito_apps.html#mojits>`_ in the +`Mojito Applications <mojito_apps.html>`_ chapter for information about the +directory structure and files of a mojit. .. _structure-active_view: @@ -205,8 +211,8 @@ itself incorporate other mojits in order to fulfill its role. Composite mojits encapsulate their children, such that, for all intents and purposes, an instance of a composite mojit cannot be distinguished from its child mojits. -See the chapter `Composite Mojits <../topics/mojito_composite_mojits.html>`_ for more information -and to learn how to create composite mojits. +See the chapter `Composite Mojits <../topics/mojito_composite_mojits.html>`_ for more +information and to learn how to create composite mojits. .. |---| unicode:: U+2014 .. em dash, trimming surrounding whitespace :trim: diff --git a/docs/dev_guide/intro/mojito_mvc.rst b/docs/dev_guide/intro/mojito_mvc.rst index 5b7c0c958..f0db1ad10 100644 --- a/docs/dev_guide/intro/mojito_mvc.rst +++ b/docs/dev_guide/intro/mojito_mvc.rst @@ -1,39 +1,60 @@ - - ============= MVC in Mojito ============= -The MVC architecture in Mojito incorporates a clear separation of the controller, model, and view. The controller retrieves data from the model and passes it to the view. Client requests for data are sent to the -controller, which in turn fetches data from the model and passes the data to the client. The controller is pivotal in the sense that it controls all interactions in the MVC of Mojito. - -The controller, model, and view are found in the mojit of Mojito. The mojit is a single unit of execution of a Mojito application. An application may have one or more mojits, -which are physically represented by directory structure. The mojit has one controller, any number or no models, and one or more views. When Mojito receives an HTTP request, an application invokes a -mojit controller that can then execute, pass data to the view, or get data from the model. Now that we have described the general characteristics and implementation of the MVC in Mojito, -let's look at each of the components in more detail. +The MVC architecture in Mojito incorporates a clear separation of the +controller, model, and view. The controller retrieves data from the model +and passes it to the view. Client requests for data are sent to the controller, +which in turn fetches data from the model and passes the data to the client. +The controller is pivotal in the sense that it controls all interactions in +the MVC of Mojito. + +The controller, model, and view are found in the mojit of Mojito. The mojit +is a single unit of execution of a Mojito application. An application may +have one or more mojits, which are physically represented by directory +structure. The mojit has one controller, any number or no models, and one +or more views. When Mojito receives an HTTP request, an application invokes +a mojit controller that can then execute, pass data to the view, or get data +from the model. Now that we have described the general characteristics and +implementation of the MVC in Mojito, let's look at each of the components in +more detail. + +.. _mojito_mvc-models: Models -###### +====== -Models are intended to closely represent business logic entities and contain code that accesses and persists data. Mojito lets you create one or more models at the +Models are intended to closely represent business logic entities and contain code that +accesses and persists data. Mojito lets you create one or more models at the application and mojit level that can be accessed from controllers. +.. _mvc_models-loc: + Location -======== +-------- -Models are found in the ``models`` directory of each mojit. For the application ``hello`` with the mojit ``HelloMojit``, the path to the models would -be ``hello/mojits/HelloMojit/models``. +Models are found in the ``models`` directory of each mojit. For the application +``hello`` with the mojit ``HelloMojit``, the path to the models would be +``hello/mojits/HelloMojit/models``. -Naming Conventions -================== +.. _mvc_models-naming: -The name of the model files depend on the affinity, which is the location where a resource is available. Thus, the name of the model file is ``{model_name}.{affinity}.js``, -where ``{affinity}`` can be ``common``, ``server``, or ``client``. +Naming Convention +----------------- + +The name of the model files depend on the affinity, which is the location +where a resource is available. Thus, the name of the model file is +``{model_name}.{affinity}.js``, where ``{affinity}`` can be ``common``, +``server``, or ``client``. -When adding model as a module with ``YUI.add`` in the model file, we suggest you use the following syntax: ``{mojit_name}Model{Model_name}`` -For the default model ``model.server.js``, the suggested convention is ``{mojit_name}Model`` for the module name. +When adding the model as a module with ``YUI.add``, we suggest +you use the following syntax: ``{mojit_name}Model{Model_name}`` -Thus, the ``YUI.add`` statement in ``photos/models/flickr.server.js`` would be the following: +For the default model ``model.server.js``, the suggested convention is +``{mojit_name}Model`` for the module name. + +Thus, the ``YUI.add`` statement in ``photos/models/flickr.server.js`` would +be the following: .. code-block:: javascript @@ -41,8 +62,10 @@ Thus, the ``YUI.add`` statement in ``photos/models/flickr.server.js`` would be t ... } +.. _mvc_models-structure: + Basic Structure -=============== +--------------- A model should have the basic structure shown below. @@ -69,9 +92,10 @@ A model should have the basic structure shown below. }, '0.0.1', { requires:[] }); +.. _mvc_models-objs: Model Objects and Methods -========================= +------------------------- The following objects and methods form the backbone of the model. @@ -80,9 +104,12 @@ The following objects and methods form the backbone of the model. - ``init`` - (optional) gets configuration information -The example model below shows you how the objects and methods are used. The ``galleryModelFlickr`` model is registered with ``YUI.add``, and the namespace for the -model is created with ``Y.namespace('mojito.models')[NAME]``. The ``init`` function stores the date so it can be used by other functions, and the ``requires`` array -instructs Mojito to load the YUI module ``yql`` for getting data. +The example model below shows you how the objects and methods are used. The +``galleryModelFlickr`` model is registered with ``YUI.add``, and the namespace +for the model is created with ``Y.namespace('mojito.models')[NAME]``. The +``init`` function stores the date so it can be used by other functions, and +the ``requires`` array instructs Mojito to load the YUI module ``yql`` for +getting data. .. code-block:: javascript @@ -108,34 +135,25 @@ instructs Mojito to load the YUI module ``yql`` for getting data. } }; }, '0.0.1', {requires: ['yql']}); - -Using Models -============ +.. _mvc_models-using: -The function of the model is to get information and send it to the controller. When calling model functions from a mojit controller, a callback function must be provided to allow for the model -code to run long-term processes for data storage and retrieval. As a matter of best practice, the model should be a YUI module and not include blocking code, although blocking code can be used. +Using Models +------------ -To access a model from the controller, use the syntax ``ac.models.get('{model_name}')`` as seen in the code example below. For a more detailed example, -see `Calling the Model`_ and `Calling YQL from a Mojit <../code_exs/calling_yql.html>`_. +The function of the model is to get information and send it to the controller. +When calling model functions from a mojit controller, a callback function must +be provided to allow for the model code to run long-term processes for data +storage and retrieval. As a matter of best practice, the model should be a YUI +module and not include blocking code, although blocking code can be used. -.. code-block:: javascript +See :ref:`Calling the Model <mvc-controllers-call_model>` to learn how +to call the model from the controller. - YUI.add('{mojit_name}', function(Y, NAME) { - Y.namespace('mojito.controllers')[NAME] = { - index: function(ac) { - // Use ac.models.get('{mojit_name}') if the default model 'model.server.js' is being used. - var model = ac.models.get('{model_name}'); - } - }; - }, '0.0.1', { requires:[ - 'mojito-models-addon', - '{model_name}' - ]}); +.. _mvc_models-ex: Example -======= - +------- .. code-block:: javascript @@ -170,19 +188,66 @@ Example }, '0.0.1', {requires: ['yql']}); +.. _mojito_mvc-controllers: + Controllers -########### +=========== + +After an application has been configured to use a mojit, the mojit controller can either +do all of the work or delegate the work to models and/or views. In the typical case, the +mojit controller requests the model to retrieve data and then the controller serves that +data to the views. -After an application has been configured to use a mojit, the mojit controller can either do all of the work or delegate the work to models and/or views. In the typical case, the mojit controller requests the model to -retrieve data and then the controller serves that data to the views. +Location +-------- + +Controllers are found in the mojit directory. For the application +``hello`` with the mojit ``HelloMojit``, the path to the controller would be +``hello/mojits/HelloMojit/controller.server.js``. + +.. _mvc_controllers-naming: + +Naming Convention +----------------- + +.. _controllers_naming-files: + +Files +##### -A mojit can only use one controller, but may have a different controller for each environment (client vs server). The name of the mojit controllers uses the syntax ``controller.{affinity}.js``, where -the value can be ``common``, ``server``, or ``client``. The affinity is simply the location of the resource, which is important because code can be deployed to the client. +A mojit can only use one controller, but may have a different controller for each +environment (client vs server). The name of the mojit controllers uses the syntax +``controller.{affinity}.js``, where the value can be ``common``, ``server``, or +``client``. The affinity is simply the location of the resource, which is important +because code can be deployed to the client. + +.. _controllers_naming-yui_mod: + +YUI Module +########## + +When registering the controller as a module with ``YUI.add`` in the controller, +you need to use the mojit name, which is also the same as the mojit directory +name: ``YUI.add({mojit_name}, ...);`` + +Thus, the ``YUI.add`` statement in ``mojits/flickr/controller.server.js`` would +be the following: + +.. code-block:: javascript + + YUI.add("flickr", function(Y, NAME) { + ... + }); + +.. _mvc_models-structure: + + +.. _mvc-controllers-structure: Basic Structure -=============== +--------------- -A controller should have the following basic structure: +A controller should have the basic structure shown below. .. code-block:: javascript @@ -190,10 +255,7 @@ A controller should have the following basic structure: // Module name is {mojit-name} // Constructor for the Controller class. Y.namespace('mojito.controllers')[NAME] = { - // The spec configuration is passed to init - init: function(config) { - this.config = config; - }, + /** * Method corresponding to the 'index' action. * @param ac {Object} The ActionContext object @@ -210,40 +272,39 @@ A controller should have the following basic structure: // The requires array lists the YUI module dependencies }, '0.0.1', {requires: []}); +.. _mvc-controllers-objs: Controller Objects and Methods -============================== +------------------------------ Several objects and methods form the backbone of the controller. -- ``YUI.add`` - (required) registers the controller as a YUI module in the Mojito framework. -- ``Y.namespace('mojito.controllers')[NAME]`` - (required) creates a namespace that makes functions available as Mojito - actions. -- ``init`` - (optional) if you provide an ``init`` function on your controller, Mojito will call it - as it creates a controller instance, passing in the mojit specification. You can store the - specification on the ``this`` reference for use within controller functions. -- ``this`` - a reference pointing to an instance of the controller that the function is running - within. This means that you can refer to other functions described within ``Y.namespace('mojito.controllers')[NAME]`` - using ``this.otherFunction``. This is helpful when you've added some utility functions onto your - controller that do not accept an ActionContext object. -- ``requires`` - (optional) an array that lists additional YUI modules needed by the controller. - -The example controller below shows you how the components are used. The ``status`` mojit is -registered with ``YUI.add`` and the ``init`` function stores the date so it can be used by other functions, and -the ``this`` reference allows the ``index`` function to call ``create_status``. Lastly, the -``requires`` array instructs Mojito to load the YUI module ``mojito-intl-addon`` for localizing the -date and title. +- ``YUI.add`` - (required) registers the controller as a YUI module in the Mojito + framework. +- ``Y.namespace('mojito.controllers')[NAME]`` - (required) creates a namespace + that makes functions available as Mojito actions. +- ``this`` - a reference pointing to an instance of the controller that the + function is running within. This means that you can refer to other functions + described within ``Y.namespace('mojito.controllers')[NAME]`` using + ``this.otherFunction``. This is helpful when you've added some utility functions + onto your controller that do not accept an ActionContext object. +- ``requires`` - (optional) an array that lists any addons that are needed + by the controller. + +The example controller below shows you how the components are used. The +``status`` mojit is registered with ``YUI.add`` and the ``init`` function +stores the date so it can be used by other functions, and the ``this`` +reference allows the ``index`` function to call ``create_status``. Lastly, +the ``requires`` array loads the addons ``Intl``, ``Params``, and ``Url`` +that are needed by the controller. .. code-block:: javascript YUI.add('status', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(spec) { - this.spec = spec; - this.date = new Date(); - }, + index: function(ac) { - var dateString = ac.intl.formatDate(this.date); + var dateString = ac.intl.formatDate(new Date()); var status = ac.params.getFromMerged('status'); var user = ac.params.getFromMerged('user'); var status = { @@ -257,17 +318,18 @@ date and title. return user + ': ' + status + ' - ' + time; } }; - }, '0.0.1', {requires: ['mojito-intl-addon']}); + }, '0.0.1', {requires: ['mojito-intl-addon', 'mojito-params-addon', mojito-url-addon']}); +.. _mvc-controllers-actions: Controller Functions as Mojito Actions -====================================== +-------------------------------------- -When mojit instances are created in the application configuration file, you can then call controller -functions as actions that are mapped to route paths. +When mojit instances are created in the application configuration file, you +can then call controller functions as actions that are mapped to route paths. -In the application configure file ``application.json`` below, the mojit instance ``hello`` is -created. +In the application configure file ``application.json`` below, the mojit instance +``hello`` is created. .. code-block:: javascript @@ -283,10 +345,11 @@ created. } ] -The controller for the ``HelloMojit`` mojit has an ``index`` function that we want to call when an -HTTP GET call is made on the root path. To do this, the route configuration file ``routes.json`` -maps the ``hello`` instance and the ``index`` action to the root path with the ``path`` and ``call`` -properties as seen below. +The controller for the ``HelloMojit`` mojit has an ``index`` function that we +want to call when an HTTP GET call is made on the root path. To do this, the +route configuration file ``routes.json`` maps the ``hello`` instance and the +``index`` action to the root path with the ``path`` and ``call`` properties +as seen below. .. code-block:: javascript @@ -300,18 +363,17 @@ properties as seen below. } ] -In the controller, any function that is defined in the ``Y.namespace('mojito.controllers')[NAME]`` is -available as a Mojito action. These functions can only accept the ``ActionContext`` object as an -argument. In the example controller below, the ``index`` and ``greeting`` functions are available as -Mojito actions. +In the controller, any function that is defined in the +``Y.namespace('mojito.controllers')[NAME]`` is available as a Mojito action. +These functions can only accept the ``ActionContext`` object as an argument. +In the example controller below, the ``index`` and ``greeting`` functions +are available as Mojito actions. .. code-block:: javascript YUI.add('Stateful', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, + index: function(ac) { ac.done({id: this.config.id}); }, @@ -322,90 +384,49 @@ Mojito actions. // The requires array list the YUI module dependencies }, '0.0.1', {requires: []}); -Initializing and Referencing a Controller Instance -================================================== -If the controller has an ``init`` function, Mojito will call it as it creates a controller instance. -The ``init`` function is passed the mojit ``config`` object, which is -defined in ``application.json`` or ``defaults.json``. See the -`config Object <./mojito_configuring.html#config-object>`_ for the specifications. -You can also use ``init`` to store other initialization data on ``this`` as seen below: +.. _mvc-controllers-call_model: -.. code-block:: javascript +Calling the Model +----------------- - YUI.add('PlaceFinder', function(Y, NAME) { - Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - this.geo_api = "http://where.yahooapis.com/geocode"; - }, - ... - }; - }, '0.0.1', {requires: []}); +The mojit controller communicates with the model through the +`ActionContext object <../api_overview/mojito_action_context.html>`_ and a +syntax convention. The ``ActionContext`` object allows controller functions +to access framework features such as API methods and addons that extend +functionality. To access the model from the ActionContext object ``ac``, +you use the following syntax: ``ac.models.get('{model_name}').{model_function}`` -Within your controller actions and the ``init`` action, the ``this`` reference points to an instance -of the controller the action is running within. This means that you can refer to other -functions or actions described within ``Y.namespace('mojito.controllers')[NAME]`` using the syntax -``this.{otherFunction}``. This is helpful when you've added some utility functions onto your -controller that do not accept an ActionContext object as the argument, but you wish to use for -several actions. -In the example controller below, the ``health`` function uses ``this`` to call the utility function -``get_bmi``. +The ``{model_name}`` is the YUI module name that is passed to ``YUI.add`` of the +model file, not the model file name. The example controller below shows the +syntax for calling the model from a controller. .. code-block:: javascript - YUI.add('HealthStats', function(Y, NAME) { - Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, + YUI.add('{mojit_name}', function(Y, NAME) { + Y.namespace('mojito.controllers')[NAME] = { index: function(ac) { - ac.done({id: this.config.id}); - }, - health: function(ac) { - var health_stats = ac.params.getAll(); - var weight=health_stats['weight'],height = health_stats['height'], metric=health_stats['metric']; - var bmi = this.get_bmi(weight,height,metric) - ac.done({ bmi: bmi }); - }, - }; - function get_bmi(weight, height, metric){ - var bmi = 0; - if(metric) { - bmi = weight/(height*height); - } else { - bmi = (weight*703)/(height*height); - } - return bmi; + var model = ac.models.get('{model_name}'); } - }, '0.0.1', {requires: []}); - -.. _controllers-calling_models: - -Calling the Model -================= - -The mojit controller communicates with the model through the -`ActionContext object <../api_overview/mojito_action_context.html>`_ and a syntax convention. The -``ActionContext`` object allows controller functions to access framework features such as API -methods and addons that extend functionality. To access the model from the ActionContext object -``ac``, you use the following syntax: ``ac.models.get('{model_name}').{model_function}`` + }; + }, '0.0.1', { requires:[ + 'mojito-models-addon', + '{model_name}' + ]}); -Thus, if you wanted to use the ``photo_search`` function in the model for the ``flickr`` mojit, you -would use the following: ``ac.models.get('flickr').photo_search(args, callback);`` +For example, if you wanted to use the ``photo_search`` function in the model for the +``flickr`` mojit, you would use the following: ``ac.models.get('flickr').photo_search(args, callback);`` -The ``controller.server.js`` below shows a simple example of calling ``get_data`` from the model of -the ``simple`` mojit. +The ``controller.server.js`` below shows a simple example of calling +``get_data`` from the model of the ``simple`` mojit. .. code-block:: javascript YUI.add('simple', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, + index: function(ac) { var model = ac.models.get('simpleModel'); model.get_data (function(data) { @@ -422,17 +443,23 @@ the ``simple`` mojit. 'simpleModel' ]}); +For a more detailed example, see `Calling the Model`_ and +`Calling YQL from a Mojit <../code_exs/calling_yql.html>`_. + +.. _mvc-controllers-pass_data: + Passing Data to the View -======================== +------------------------ -The controller also uses the ActionContext object to send data to the view. Calling the ``done`` -method from the ActionContext object, you can send literal strings or objects, with the latter being -interpolated in template tags that are rendered by the appropriate view engine. The ``done`` method -should only be called once. If neither ``done`` nor ``error`` is called, -your application will hang waiting for output. +The controller also uses the ActionContext object to send data to the view. +Calling the ``done`` method from the ActionContext object, you can send literal +strings or objects, with the latter being interpolated in template tags that are +rendered by the appropriate view engine. The ``done`` method should only be +called once. If neither ``done`` nor ``error`` is called, your application will +hang waiting for output. -In the example ``controller.server.js`` below, the ``index`` function sends the ``user`` object to -the ``index`` template. +In the example ``controller.server.js`` below, the ``index`` function sends the ``user`` +object to the ``index`` template. .. code-block:: javascript @@ -447,9 +474,7 @@ the ``index`` template. * @constructor */ Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, + /** * Method corresponding to the 'index' action. * @param ac {Object} The action context that @@ -462,20 +487,23 @@ the ``index`` template. }; }, '0.0.1', {requires: []}); +.. _mvc-controllers-specify_view: + Specifying the View ------------------- -The default behavior when you pass data from the controller to the view is for the data to be passed -to the view that has the same name as the controller function. For example, if -``ac.done({ "title": "Default View" })`` is invoked in the controller ``index`` function, the data -is sent by default to the ``index`` template. The ``index`` template could be -``index.hb.html``, ``index.iphone.hb.html``, etc., depending on the calling device and rendering -engine. - -To specify the view that receives the data, the controller function passes two parameters to -``ac.done``: The first parameter is the data, and the second parameter specifies the view name in -the object ``{ "view": { "name": "name_of_view_receiving_data" } }``. In the example controller -below, the ``user`` function passes the ``data`` object to the ``profile`` template +The default behavior when you pass data from the controller to the view is for +the data to be passed to the view that has the same name as the controller +function. For example, if ``ac.done({ "title": "Default View" })`` is invoked +in the controller ``index`` function, the data is sent by default to the +``index`` template. The ``index`` template could be ``index.hb.html``, +``index.iphone.hb.html``, etc., depending on the calling device and +rendering engine. + +To specify the view that receives the data, the controller function passes two +parameters to ``ac.done``: The first parameter is the data, and the second +parameter specifies the view name. In the example controller below, the +``user`` function passes the ``data`` object to the ``profile`` template instead of the default ``user`` template. .. code-block:: javascript @@ -491,9 +519,7 @@ instead of the default ``user`` template. * @constructor */ Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, + /** * Method corresponding to the 'index' action. * @param ac {Object} The action context that @@ -505,23 +531,27 @@ instead of the default ``user`` template. }, user: function(ac) { var data = { "title": "Going to profile template." } - ac.done(data, { "view": { "name": "profile" } }); + ac.done(data, "profile"); } }; }, '0.0.1', {requires: []}); +.. _mvc-controllers-report_error: + Reporting Errors -================ +---------------- -The ``ActionContext`` object has an ``error`` method for reporting errors. Like the ``done`` method, -``error`` should only be called once. Also, you cannot call both ``done`` and ``error``. The error -requires an ``Error`` object as a parameter. The ``Error`` object is just the standard JavasScript -``Error`` object that can have a ``code`` property specifying the HTTP response code that -will be used if the error bubbles to the top of the page (i.e., not caught by a parent mojit). +The ``ActionContext`` object has an ``error`` method for reporting errors. +Like the ``done`` method, ``error`` should only be called once. Also, you +cannot call both ``done`` and ``error``. The error requires an ``Error`` +object as a parameter. The ``Error`` object is just the standard JavasScript +``Error`` object that can have a ``code`` property specifying the HTTP response +code that will be used if the error bubbles to the top of the +page (i.e., not caught by a parent mojit). -In the code snippet below from ``controller.server.js``, the model is asked to get a blog post. The -``try-catch`` clause will catch any errors made calling ``getPost``, and the ``error`` method will -display the error message. +In the code snippet below from ``controller.server.js``, the model is asked +to get a blog post. The ``try-catch`` clause will catch any errors made calling +``getPost``, and the ``error`` method will display the error message. .. code-block:: javascript @@ -537,107 +567,67 @@ display the error message. } ... +.. _mvc-controllers-save_state: -Saving State -============ - -You can maintain the state within controllers when they are running on the client because the -client-side Mojito runtime is long-lived. You **cannot** maintain state within server controllers -because the controller is discarded after the page has been generated and served to the client. - -In the example ``controller.client.js`` below, the ``pitch`` function stores the variable ``ball`` -on ``this``. If client code invokes ``pitch``, the ``ball`` parameter it sends will be stored in -controller instance state. If ``catch`` function is invoked, that state variable is retrieved and -sent back in a callback. - -.. code-block:: javascript - - YUI.add('Stateful', function(Y, NAME) { - Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - this.time = new Date().getTime(); - }, - index: function(ac) { - ac.done({id: this.config.id}); - }, - pitch: function(ac) { - this.logit('pitch'); - // Use the Params addon to get the 'ball' parameter. - // getFromMerged() allows you to retrieve routing, - // request, and query string parameters. - this.ball = ac.params.getFromMerged('ball'); - ac.done(); - }, - catch: function(ac) { - // Save a reference to the current object - // for later use. - var me = this; - this.logit('catch'); - ac.models.get('StatefulModel').getData(function(err, data) { - ac.done({ - ball: me.ball, - time: me.time, - model: data.modelId - }); - }); - }, - logit: function(msg) { - Y.log(msg + this.time, 'warn'); - } - }; - }, '0.0.1', {requires: [ - 'mojito-models-addon', - 'StatefulModel' - ]}); +.. _mojito_mvc-views: Views -##### +===== -The views are HTML files that can include templates, such as Handlebars expressions, and are located in -the ``views`` directory. We call these files *templates* to differentiate -them from the rendered views that have substituted values for the template tags. +The views are HTML files that can include templates, such as Handlebars +expressions, and are located in the ``views`` directory. We call these +files *templates* to differentiate them from the rendered views that +have substituted values for the template tags. + +.. _mvc-views-naming: Naming Convention -================= +----------------- -The naming convention of the templates is based on the controller function that supplies data, -the engine that renders the templates, and the device requesting the page. If the calling device is -determined not to be a portable device such as a cell phone, the ``{device}`` element of the syntax -below is omitted. +The naming convention of the templates is based on the controller function +that supplies data, the engine that renders the templates, and the device +requesting the page. If the calling device is determined not to be a portable +device such as a cell phone, the ``{device}`` element of the syntax below +is omitted. **File Naming Convention for Templates:** ``{controller_function}.[{device}].{rendering_engine}.html`` -For example, if the template is receiving data from the ``index`` function of the controller -and has Handlebars expressions that need to be rendered, the name of the template would be -``index.hb.html``. +For example, if the template is receiving data from the ``index`` function +of the controller and has Handlebars expressions that need to be rendered, +the name of the template would be ``index.hb.html``. Here are some other example template names with descriptions: -- ``greeting.hb.html`` - This template gets data from the ``greeting`` function of the - controller and the calling device is determined to be a Web browser. -- ``get_photos.iphone.hb.html`` - This template gets data from the ``get_photos`` function of - the controller and the calling device is an iPhone. -- ``find_friend.android.hb.html`` - This template gets data from the ``find_friend`` function - of the controller and the calling device is Android based. +- ``greeting.hb.html`` - This template gets data from the ``greeting`` + function of the controller and the calling device is determined to + be a Web browser. +- ``get_photos.iphone.hb.html`` - This template gets data from the + ``get_photos`` function + of the controller and the calling device is an iPhone. +- ``find_friend.android.hb.html`` - This template gets data from the + ``find_friend`` function of the controller and the calling device is Android + based. .. note:: Currently, Mojito comes with Handlebars, so the name of templates always contains ``hb``. Users can use other - `view engines <../topics/mojito_extensions.html#view-engines>`_, but the - ``{rendering_engine}`` component of the template name must change. An error will - occur if the file names of different views are the same except the ``{rendering_engine}``. - For example, having the two templates ``index.hb.html`` and - ``index.ejs.html`` (``ejs`` could be `Embedded JavaScript (EJS) <http://embeddedjs.com/>`_) would + `view engines <../topics/mojito_extensions.html#view-engines>`_, + but the ``{rendering_engine}`` component of the template name must + change. An error will occur if the file names of different views + are the same except the ``{rendering_engine}``. For example, having + the two templates ``index.hb.html`` and ``index.ejs.html`` (``ejs`` + could be `Embedded JavaScript (EJS) <http://embeddedjs.com/>`_) would cause an error. +.. _mvc-views-supported_devices: Supported Devices -================= +----------------- -Mojito can examine the HTTP header ``User Agent`` and detect the following devices/browsers: +Mojito can examine the HTTP header ``User Agent`` and detect the following +devices/browsers: +-----------------+---------------------------+ | Device/Browser | Example Template | @@ -659,21 +649,23 @@ Mojito can examine the HTTP header ``User Agent`` and detect the following devic | Blackberry | index.blackberry.hb.html | +-----------------+---------------------------+ - +.. _mvc-views-using_hb: Using Handlebars Expressions -============================ +---------------------------- -Handlebars is a superset of `Mustache <http://mustache.github.com/mustache.5.html>`_, thus, -Handlebars expressions include Mustache tags. Handlebars, however, also has some additional features -such as registering help function and built-in block helpers, iterators, and access to object -properties through the dot operator (i.e, ``{{house.price}}``). We're just going to look at a few +Handlebars is a superset of `Mustache <http://mustache.github.com/mustache.5.html>`_, +thus, Handlebars expressions include Mustache tags. Handlebars, however, also +has some additional features such as registering help function and built-in block +helpers, iterators, and access to object properties through the dot operator +(i.e, ``{{house.price}}``). We're just going to look at a few Handlebars expressions as an introduction. See the -`Handlebars documentation <http://handlebarsjs.com/>`_ for more information examples. +`Handlebars documentation <http://handlebarsjs.com/>`_ for more information +examples. -One of the things that we mentioned already is block helpers, which help you iterate through arrays. -You could use the block helper ``#each`` shown below to iterate through an -array of strings: +One of the things that we mentioned already is block helpers, which help you +iterate through arrays. You could use the block helper ``#each`` shown below +to iterate through an array of strings: .. code-block:: html @@ -683,9 +675,9 @@ array of strings: {{/each}} </ul> -Another interesting block helper used in this example is #with, which will invoke a block when given -a specified context. For example, in the code snippet below, if the ``ul`` object is given, -the property title is evaluated. +Another interesting block helper used in this example is #with, which will +invoke a block when given a specified context. For example, in the code +snippet below, if the ``ul`` object is given, the property title is evaluated. .. code-block:: html @@ -694,27 +686,33 @@ the property title is evaluated. {{/with}} +.. _mvc-views-supplied_data: Mojito-Supplied Data -==================== +-------------------- + +Mojito supplies the following data that can be accessed as template tags in the +template: -Mojito supplies the following data that can be accessed as template tags in the template: +- ``{{mojit_view_id}}`` - a unique ID for the view being rendered. We recommend + that this tag be used as the value for the ``id`` attribute of the a top-level + element (i.e., ``<div>``) of your template because it is used to bind the + binders to the DOM of the view. +- ``{{mojit_assets}}`` - the partial URL to the ``assets`` directory of your + mojit. You can use the value of this tag to point to specific assets. For + example, if your mojit has the image ``assets/spinner.gif``, then you can + point to this image in your template with the following: + ``<img src="{{mojit_assets}}/spinner.gif">`` -- ``{{mojit_view_id}}`` - a unique ID for the view being rendered. We recommend that this tag be - used as the value for the ``id`` attribute of the a top-level element (i.e., ``<div>``) of your - template because it is used to bind the binders to the DOM of the view. -- ``{{mojit_assets}}`` - the partial URL to the ``assets`` directory of your mojit. You can use the - value of this tag to point to specific assets. For example, if your mojit has the - image ``assets/spinner.gif``, then you can point to this image in your template with the - following: ``<img src="{{mojit_assets}}/spinner.gif">`` +.. note:: The prefix ``mojit_`` is reserved for use by Mojito, and thus, + user-defined variables cannot use this prefix in their names. -.. note:: The prefix ``mojit_`` is reserved for use by Mojito, and thus, user-defined variables - cannot use this prefix in their names. +.. _mvc-views-exs: Examples -======== +-------- -See `Code Examples: Views <../code_exs/#views>`_ for annotated code examples, steps to run code, and -source code for Mojito applications. +See `Code Examples: Views <../code_exs/#views>`_ for annotated code examples, +steps to run code, and source code for Mojito applications. diff --git a/docs/dev_guide/intro/mojito_overview.rst b/docs/dev_guide/intro/mojito_overview.rst index f6e4348f6..f16fa8ac4 100644 --- a/docs/dev_guide/intro/mojito_overview.rst +++ b/docs/dev_guide/intro/mojito_overview.rst @@ -1,14 +1,16 @@ - - ======== Overview ======== +.. _mojito_overview-what: + What is Mojito? -############### +=============== -Mojito is a `model-view-controller (MVC) <http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller>`_ application framework built on YUI 3 that enables agile development of -Web applications. Mojito allows developers to use a combination of configuration and an MVC architecture to create applications. Because client and server components are both written in JavaScript, +Mojito is a `model-view-controller (MVC) <http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller>`_ +application framework built on YUI 3 that enables agile development of Web applications. +Mojito allows developers to use a combination of configuration and an MVC architecture to +create applications. Because client and server components are both written in JavaScript, Mojito can run on the client (browser) or the server (Node.js). Mojito offers the following features, some of which are discussed in the next section: @@ -18,62 +20,99 @@ Mojito offers the following features, some of which are discussed in the next se - Integrated unit testing - Device specific presentation (Hero, Mobile, TV, Web, etc.) -Why Mojito? -########### +.. _mojito_overview-why: -The best way to illustrate why you should use Mojito for creating Web applications is to give an example. Suppose you wanted to create a slideboard application that -lets users quickly view news articles from various sources. Your application needs to be available on a variety of devices and use the appropriate UI elements of the device. -For example, the application on the Web should provide rich interaction using the mouse, whereas, the application on a tablet or phone should provide the same rich interaction +Why Mojito? +=========== + +The best way to illustrate why you should use Mojito for creating Web applications is to +give an example. Suppose you wanted to create a slideboard application that +lets users quickly view news articles from various sources. Your application needs to be +available on a variety of devices and use the appropriate UI elements of the device. +For example, the application on the Web should provide rich interaction using the mouse, +whereas, the application on a tablet or phone should provide the same rich interaction using the touchscreen. -You also want people from all over to be able to use your slideboard application, so you will need to support internationalization and localization. Users should be able to see the -application in their local language or choose their preferred language. +You also want people from all over to be able to use your slideboard application, so you +will need to support internationalization and localization. Users should be able to see +the application in their local language or choose their preferred language. + +In the following, we will discuss how Mojito makes it easier to create the slideboard +application. -In the following, we will discuss how Mojito makes it easier to create the slideboard application. +.. _mojito_overview_why-one_lang: One Language -============ +------------ -Your slideboard application will need to fetch articles either through some API or an RSS feed. Most conventional Web applications that need data have both server-side and client-side components. -The server-side script, written in a language such as Python, Perl, Ruby, PHP, or Java, fetches data and then passes the data to client-side script written in JavaScript. The one Web application -would have at least two languages and would need to make at least two network calls: one to the data source, and one between the server-side and client-side code to transmit data. +Your slideboard application will need to fetch articles either through some API or an RSS +feed. Most conventional Web applications that need data have both server-side and +client-side components. The server-side script, written in a language such as Python, +Perl, Ruby, PHP, or Java, fetches data and then passes the data to client-side script +written in JavaScript. The one Web application would have at least two languages and +would need to make at least two network calls: one to the data source, and one between +the server-side and client-side code to transmit data. -Because Mojito is written entirely in JavaScript, your application code can run on the server or be deployed to the client. From the client, you can use the `YUI YQL Utility <http://yuilibrary.com/yui/docs/yql/>`_, -to get all types of Web data, removing the need for the client to fetch data from your server. +Because Mojito is written entirely in JavaScript, your application code can run on the +server or be deployed to the client. From the client, you can use the +`YUI YQL Utility <http://yuilibrary.com/yui/docs/yql/>`_, to get all types of Web data, +removing the need for the client to fetch data from your server. + +.. _mojito_overview_why-two_runtimes: Two Runtimes -============ +------------ + +Your application code can be configured to run on the server or be deployed to the client. +If your application is configured to deploy code to the client, Mojito will determine +whether the client can execute JavaScript before deployment. If the client cannot execute +JavaScript, your application code will instead execute on the server. You write one code +base, configure where code should optimally run, and then let Mojito determine at runtime +to either deploy the code or run it on the server. Whether running on the client or server, +your code can use the `YUI YQL Utility <http://yuilibrary.com/yui/docs/yql/>`_ to get all +types of Web data. -Your application code can be configured to run on the server or be deployed to the client. If your application is configured to deploy code -to the client, Mojito will determine whether the client can execute JavaScript before deployment. If the client cannot execute JavaScript, -your application code will instead execute on the server. You write one code base, configure where code should optimally run, and then let -Mojito determine at runtime to either deploy the code or run it on the server. Whether running on the client or server, your code can -use the `YUI YQL Utility <http://yuilibrary.com/yui/docs/yql/>`_ to get all types of Web data. +.. _mojito_overview_why-device_views: Views for Different Devices -=========================== +--------------------------- -Your slideboard application is a Web application, but we want users to view it on tablets and smart phones as well. Do you create separate versions of your application? Do you write code logic -that serves the correct version? +Your slideboard application is a Web application, but we want users to view it on tablets +and smart phones as well. Do you create separate versions of your application? Do you +write code logic that serves the correct version? -Mojito can identify the calling device by examining the HTTP header ``User-Agent``. You create custom views for different devices, and Mojito will render and serve the correct +Mojito can identify the calling device by examining the HTTP header ``User-Agent``. You +create custom views for different devices, and Mojito will render and serve the correct device-specific views. +.. _mojito_overview_why-prog_enhancement: + Progressive Enhancement -======================= +----------------------- -You want your users to be able to take advantage of the rich features of the device they are using. Users skimming through articles on an iPad should be able to use the touch screen, and Web -users should have features such as mouseovers and right-clicks. Handling these UI events requires JavaScript, so how do you handle them when the client has disabled JavaScript? +You want your users to be able to take advantage of the rich features of the device they +are using. Users skimming through articles on an iPad should be able to use the touch +screen, and Web users should have features such as mouseovers and right-clicks. Handling +these UI events requires JavaScript, so how do you handle them when the client has +disabled JavaScript? -Mojito allows you to serve code to the client, so that your users can use the rich interactive features of their devices, but Mojito also allows you to handle cases when the client has -not enabled JavaScript. You can write HTML and CSS so your page functions without JavaScript sure that your application works with just HTML and CSS. Because Mojito runs on `Node.js <http://nodejs.org/>`_ -the code intended to be deployed to the client can instead run on the server. +Mojito allows you to serve code to the client, so that your users can use the rich +interactive features of their devices, but Mojito also allows you to handle cases when the +client has not enabled JavaScript. You can write HTML and CSS so your page functions +without JavaScript sure that your application works with just HTML and CSS. Because Mojito +runs on `Node.js <http://nodejs.org/>`_ the code intended to be deployed to the client can +instead run on the server. -Localization and Internationalization -===================================== +.. _mojito_overview_why-loc_intl: -Mojito is built on `YUI 3 <http://yuilibrary.com/>`_, which has an internationalization utility that allows you to handle monolingual and multilingual applications. Using the `YUI Internationalization -utility <http://yuilibrary.com/yui/docs/intl/>`_ and `Yahoo! Resource Bundles (YRB) <http://yuilibrary.com/yui/docs/intl/#yrb>`_, your slideboard application could use one language for the UI and serve -the content in a different language. +Localization and Internationalization +------------------------------------- + +Mojito is built on `YUI 3 <http://yuilibrary.com/>`_, which has an internationalization +utility that allows you to handle monolingual and multilingual applications. Using the +`YUI Internationalization utility <http://yuilibrary.com/yui/docs/intl/>`_ and +`Yahoo! Resource Bundles (YRB) <http://yuilibrary.com/yui/docs/intl/#yrb>`_, your +slideboard application could use one language for the UI and serve the content in a +different language. diff --git a/docs/dev_guide/intro/mojito_quicktour.rst b/docs/dev_guide/intro/mojito_quicktour.rst index 700571fbf..798c6c5b7 100644 --- a/docs/dev_guide/intro/mojito_quicktour.rst +++ b/docs/dev_guide/intro/mojito_quicktour.rst @@ -1,46 +1,71 @@ - - ==================== Mojito: A Quick Tour ==================== -Before starting to develop Mojito applications, we would like to introduce some of the main features of Mojito. This simple introduction should give you the bird's eye view of +Before starting to develop Mojito applications, we would like to introduce some of the +main features of Mojito. This simple introduction should give you the bird's eye view of how Mojito works and what it can offer to developers. +.. _mojito_quicktour-node: + Node.js -####### +======= + +`Node.js <http://nodejs.org/>`_ is not a feature of Mojito, but Mojito, as a Node.js +module, greatly benefits from the speed and scalability of Node.js. Mojito also takes +advantage of the core modules of Node.js: The Mojito framework uses the ``http``, ``url``, +and ``querystring`` modules to handle requests and parse URLs, and the Mojito command line +relies heavily on the ``util``, ``fs``, ``path``, and ``vm`` modules. Mojito also +leverages npm packages, such as the `express package <http://expressjs.com/>`_ to create a +server and parse cookies. Mojito application developers also use Node.js core modules and +npm modules to their advantage. For example, your application could use the ``fs`` core +module to cache data or use the ``connect`` package as network middleware. -`Node.js <http://nodejs.org/>`_ is not a feature of Mojito, but Mojito, as a Node.js module, greatly benefits from the speed and scalability of Node.js. Mojito also takes advantage of -the core modules of Node.js: The Mojito framework uses the ``http``, ``url``, and ``querystring`` modules to handle requests and parse URLs, and the Mojito command line relies heavily on -the ``util``, ``fs``, ``path``, and ``vm`` modules. Mojito also leverages npm packages, such as the `express package <http://expressjs.com/>`_ to create a server and parse cookies. -Mojito application developers also use Node.js core modules and npm modules to their advantage. For example, your application could use the ``fs`` core module to cache data or use -the ``connect`` package as network middleware. +.. _mojito_quicktour-framework: Mojito Framework -################ +================ -The Mojito framework offers an extensive API with modules for executing code, making REST calls, handling cookies and assets, accessing parameters and configuration, and more. -The framework can can detect the type of calling devices and serve the appropriate HTML markup. +The Mojito framework offers an extensive API with modules for executing code, making REST +calls, handling cookies and assets, accessing parameters and configuration, and more. +The framework can can detect the type of calling devices and serve the appropriate HTML +markup. -Mojito Command Line Tool -######################## +.. _mojito_quicktour-cmdline: -The Mojito command-line tool, besides being used to create and start applications, also offers developers with a variety of utilities. Developers can use the ``mojito`` command to run unit tests, -create documentation, sanitize code with JSLint, and build projects for iOS and Android applications. +Mojito Command-Line Tool +======================== + +The Mojito command-line tool, besides being used to create and start applications, also +offers developers with a variety of utilities. Developers can use the ``mojito`` command +to run unit tests, create documentation, sanitize code with JSLint, and build projects for +iOS and Android applications. + +.. _mojito_quicktour-yui3: YUI 3 -##### +===== + +YUI 3 forms the backbone of Mojito. The models and controllers in the Mojito MVC use +`Y.Base <http://yuilibrary.com/yui/docs/base/>`_, and the addons, which extend +functionality in Mojito, are based on +`YUI Plugins <http://yuilibrary.com/yui/docs/plugin/>`_. Many important features of +Mojito, such as testing, logging, internationalization, and cookie handling are also +derived from YUI 3. Because of the tight integration of Mojito with YUI 3, developers can +easily extend the functionality of Mojito applications by adding YUI 3 modules. -YUI 3 forms the backbone of Mojito. The models and controllers in the Mojito MVC use `Y.Base <http://yuilibrary.com/yui/docs/base/>`_, and the addons, which extend functionality in Mojito, -are based on `YUI Plugins <http://yuilibrary.com/yui/docs/plugin/>`_. Many important features of Mojito, such as testing, logging, internationalization, and cookie handling are also derived from YUI 3. -Because of the tight integration of Mojito with YUI 3, developers can easily extend the functionality of Mojito applications by adding YUI 3 modules. +.. _mojito_quicktour-apps: Mojito Applications -################### +=================== -Mojito applications are JavaScript applications that fuse configuration and MVC architecture. Because the application code is written in JavaScript, your applications are portable, -being able to move freely from the server to the client or just execute on the server in the Node.js environment. Being on the client does not restrict your application because it can -still communicate with the server through event-driven modules called binders. The binders make it simple to update content or dynamically change the page. Your application can also customize -views for different devices by rendering HTML markup from templating systems such as `Handlebars <http://handlebarsjs.com/>`_. +Mojito applications are JavaScript applications that fuse configuration and MVC +architecture. Because the application code is written in JavaScript, your applications are +portable, being able to move freely from the server to the client or just execute on the +server in the Node.js environment. Being on the client does not restrict your application +because it can still communicate with the server through event-driven modules called +binders. The binders make it simple to update content or dynamically change the page. +Your application can also customize views for different devices by rendering HTML markup +from templating systems such as `Handlebars <http://handlebarsjs.com/>`_. diff --git a/docs/dev_guide/intro/mojito_static_resources.rst b/docs/dev_guide/intro/mojito_static_resources.rst index 0564aaddc..5b86850ba 100644 --- a/docs/dev_guide/intro/mojito_static_resources.rst +++ b/docs/dev_guide/intro/mojito_static_resources.rst @@ -1,39 +1,59 @@ - - ================ Static Resources ================ -Mojito also lets you statically serve files such as controllers, binders, assets (CSS and JavaScript), etc. You can access static resources through a URL that contains the following three components: +Mojito also lets you statically serve files such as controllers, binders, +assets (CSS and JavaScript), etc. You can access static resources through a +URL that contains the following three components: - **prefix** - the basename directory of the static URL. -- **source path** - the directory of either the Mojito framework, the application, or the mojit depending on the level of the resource. +- **source path** - the directory of either the Mojito framework, the + application, or the mojit depending on the level of the resource. - **relative path** - the path relative to the source path. The URL of the static URL has the following syntax: ``/static/{source_path}/{relative_path}`` +.. _static_resources-prefix: + Prefix -###### +====== -The prefix default is ``/static/``, but can be changed through the `staticHandling object <./mojito_configuring.html#app-statichandling-obj>`_ in the ``configuration`` object of ``application.json``. +The prefix default is ``/static/``, but can be changed through the +`staticHandling object <./mojito_configuring.html#app-statichandling-obj>`_ +in the ``configuration`` object of ``application.json``. + +.. _static_resources-src_path: Source Path -########### +=========== -The source path is based on resource level within Mojito. The three resource levels are framework, application, and mojit. +The source path is based on resource level within Mojito. The three resource +levels are framework, application, and mojit. The source paths for the three levels are the following: -- ``/mojito/`` - framework-level resources that are available to the entire framework -- ``/{application_name}/`` - application-level resources where the source path is based on the name of the application. For example, for the ``news`` application, the source path would be ``/news/``. This resource can be accessed by the application or any of its mojits. -- ``/{mojit_name}/`` - mojit-level resources where the source path is based on the name of the mojit. For example, for the ``paging`` mojit, the source path would be ``/paging/``. Only the mojit can access this resource. +- ``/mojito/`` - framework-level resources that are available to the entire + framework +- ``/{application_name}/`` - application-level resources where the source + path is based on the name of the application. For example, for the ``news`` + application, the source path would be ``/news/``. This resource can be + accessed by the application or any of its mojits. +- ``/{mojit_name}/`` - mojit-level resources where the source path is based + on the name of the mojit. For example, for the ``paging`` mojit, the source + path would be ``/paging/``. Only the mojit can access this resource. + +.. _static_resources-rel_path: Relative Path -############# +============= + +The relative path is the path to the resource relative to the source path. +For example, the binder ``index.js`` for the Foo mojit would have the +relative path ``/binders/index.js``. -The relative path is the path to the resource relative to the source path. For example, the binder ``index.js`` for the Foo mojit would have the relative path ``/binders/index.js``. +.. _static_res_rel_path-ex: Examples ######## @@ -58,6 +78,7 @@ Examples ``/app_resources/finance/assets/ticker.css`` - In this example, the default prefix was overridden in the ``staticHandling`` object to be ``app_resources``. + In this example, the default prefix was overridden in the ``staticHandling`` + object to be ``app_resources``. diff --git a/docs/dev_guide/quickstart/index.rst b/docs/dev_guide/quickstart/index.rst index 6eb6b1e96..e450fe8e4 100644 --- a/docs/dev_guide/quickstart/index.rst +++ b/docs/dev_guide/quickstart/index.rst @@ -1,40 +1,47 @@ - - ================= Mojito Quickstart ================= -Prerequisites -############# +.. _mojito_quickstart-reqs: + +Requirements +============ **System:** Unix-based system. -**Software:** `Node.js (>= 0.4.0 < 0.7) <http://nodejs.org/>`_, `npm (> 1.0.0) <http://npmjs.org/>`_ +**Software:** `Node.js (>= 0.6.0 < 0.8) <http://nodejs.org/>`_, `npm (> 1.0.0) <http://npmjs.org/>`_ + +.. _mojito_quickstart-install: Installation Steps -################## +================== -#. Get Mojito from the npm registry and globally install Mojito so that it can be run from the - command line. You may need to use ``sudo`` if you run into permission errors. +#. Get Mojito from the npm registry and globally install Mojito so that + it can be run from the command line. You may need to use ``sudo`` if + you run into permission errors. ``$ npm install mojito -g`` -#. Confirm that Mojito has been installed by running unit tests. +#. Confirm that Mojito has been installed by running the help command. - ``$ mojito test`` + ``$ mojito help`` +.. _mojito_quickstart-create_app: Create a Mojito Application -########################### +=========================== #. ``$ mojito create app hello_world`` #. ``$ cd hello_world`` #. ``$ mojito create mojit myMojit`` +.. _mojito_quickstart-modify_app: + Modify Your Application -####################### +======================= -To make the application return a string we want, replace the code in ``mojits/myMojit/controller.server.js`` with the following: +To make the application return a string we want, replace the code in +``mojits/myMojit/controller.server.js`` with the following: .. code-block:: javascript @@ -49,17 +56,17 @@ To make the application return a string we want, replace the code in ``mojits/my }; }); +.. _mojito_quickstart-run_app: + Running the Application -####################### +======================= #. From the ``hello_world`` application directory, start Mojito: ``$ mojito start`` - #. Go to http://localhost:8666/@myMojit/index to see your application. - #. Stop your application by pressing **Ctrl-C**. -For a more in-depth tutorial, please see `Mojito: Getting Started <../getting_started/>`_. To learn more about Mojito, see -the `Mojito Documentation <../>`_. +For a more in-depth tutorial, please see `Mojito: Getting Started <../getting_started/>`_. +To learn more about Mojito, see the `Mojito Documentation <../>`_. diff --git a/docs/dev_guide/reference/glossary.rst b/docs/dev_guide/reference/glossary.rst index e87212d06..2a6dad1ea 100644 --- a/docs/dev_guide/reference/glossary.rst +++ b/docs/dev_guide/reference/glossary.rst @@ -1,5 +1,3 @@ - - ======== Glossary ======== @@ -7,137 +5,166 @@ Glossary action ------ - A method of a mojit instance (``{mojit_instance}.{action}``) that invokes a call to a function of a mojit controller - when an HTTP request is made on an associated routing path. For example, suppose the root path is associated with the - mojit instance and action ``hello.index``, and the ``hello`` instance is of type ``HelloMojit``. When an HTTP request - is made on the root path, the action ``index`` would invoke the ``index`` function of the ``HelloMojit`` controller. + A method of a mojit instance (``{mojit_instance}.{action}``) that invokes a call to a + function of a mojit controller when an HTTP request is made on an associated routing + path. For example, suppose the root path is associated with the mojit instance and + action ``hello.index``, and the ``hello`` instance is of type ``HelloMojit``. When an + HTTP request is made on the root path, the action ``index`` would invoke the ``index`` + function of the ``HelloMojit`` controller. Action Context -------------- - The Action Context is an essential element of the Mojito framework that gives you access to the frameworks features from within a controller function. - See `Action Context <../api_overview/mojito_action_context.html>`_ in the `Mojito API Overview <../api_overview/>`_ - and the `ActionContext Class <../../api/classes/ActionContext.html>`_ in the `Mojito API documentation <../../api/>`_ - for more detailed information. + The Action Context is an essential element of the Mojito framework that gives you + access to the frameworks features from within a controller function. See + `Action Context <../api_overview/mojito_action_context.html>`_ in the + `Mojito API Overview <../api_overview/>`_ and the + `ActionContext Class <../../api/classes/ActionContext.html>`_ in the + `Mojito API documentation <../../api/>`_ for more detailed information. addon ----- - A namespacing object attached directly to the Action Context object that provides additional functions. The Action Context - object is available in every controller function. See `Mojito API Overview: Addons <../api_overview/mojito_addons.html>`_ for more information. + A namespacing object attached directly to the Action Context object that provides + additional functions. The ``ActionContext`` object is available in every controller + function. See `Mojito API Overview: Addons <../api_overview/mojito_addons.html>`_ for more information. affinity -------- - The location where a resource is available. For example, the value for ``{affinity}`` in the file ``controller.{affinity}.js`` - could be ``server``, ``client``, or ``common``, depending on where the resource is available. The affinity ``common`` means - that the resource is available in both the client and server. If both ``common`` and ``client`` are given, then the ``client`` - file is used on the client and the ``common`` file is used on the server. Likewise, if both ``common`` and ``server`` are given, + The location where a resource is available. For example, the value for ``{affinity}`` + in the file ``controller.{affinity}.js`` could be ``server``, ``client``, or + ``common``, depending on where the resource is available. The affinity ``common`` means + that the resource is available in both the client and server. If both ``common`` and + ``client`` are given, then the ``client`` file is used on the client and the ``common`` + file is used on the server. Likewise, if both ``common`` and ``server`` are given, then the ``common`` file is used on the client and the ``server`` file is used on the server. assets ------ - File resources that are required on the clients. These resources are primarily CSS but can also be JavaScript that is ancillary - to and not a core component of the Mojito application. See `Mojito Developer Topics: Assets <../topics/mojito_assets.html>`_ to + File resources that are required on the clients. These resources are primarily CSS but + can also be JavaScript that is ancillary to and not a core component of the Mojito + application. See `Mojito Developer Topics: Assets <../topics/mojito_assets.html>`_ to learn how to use assets in Mojito applications. autoload -------- - A directory of a Mojito application containing JavaScript files that use YUI modules added with ``YUI.add``. These files aren't - actually *auto-loaded*, but are merely automatically included if required by a YUI module. + A directory of a Mojito application containing JavaScript files that use YUI modules + added with ``YUI.add``. These files aren't actually *auto-loaded*, but are merely + automatically included if required by a YUI module. binder ------ - Mojit code deployed to the browser that can allow event handlers to attach to the mojit DOM node, communicate with other mojits on - the page, and execute actions on the mojit that the binder is attached to. A mojit may have zero, one, or many binders within - the ``binders`` directory. See `Mojito Binders <../intro/mojito_binders.html>`_ for more information. + Mojit code deployed to the browser that can allow event handlers to attach to the mojit + DOM node, communicate with other mojits on the page, and execute actions on the mojit + that the binder is attached to. A mojit may have zero, one, or many binders within + the ``binders`` directory. See `Mojito Binders <../intro/mojito_binders.html>`_ for + more information. composite mojits ---------------- - When a parent mojit controls the execution and layout of child mojits. See `Mojito Developer Topics: Composite Mojits <../topics/mojito_composite_mojits.html>`_ + When a parent mojit controls the execution and layout of child mojits. See + `Mojito Developer Topics: Composite Mojits <../topics/mojito_composite_mojits.html>`_ for more information. controller ---------- - In Mojito, the controller is mojit code that can either do all of the work or delegate the work to models and/or views. - In the typical case, the mojit controller requests the model to retrieve data and then the controller will serve that - data to the views. See `MVC: Controllers <../intro/mojito_mvc.html#controllers>`_ for more information. + In Mojito, the controller is mojit code that can either do all of the work or delegate + the work to models and/or views. In the typical case, the mojit controller requests the + model to retrieve data and then the controller will serve that data to the views. + See `MVC: Controllers <../intro/mojito_mvc.html#controllers>`_ for more information. mojit ----- - The basic unit of composition and reuse in a Mojito application. It typically corresponds to a rectangular area of a page and - is constructed using JavaScript and the `MVC`_. + The basic unit of composition and reuse in a Mojito application. It typically + corresponds to a rectangular area of a page and is constructed using JavaScript and the + `MVC`_. mojitProxy ---------- - The proxy object given to binders that allows them to interact with the mojit it represents as well as with other mojits on the page. - See the `mojitProxy Object <../intro/mojito_binders.html#mojitproxy-object>`_ and the `MojitProxy Class <../../api/classes/MojitProxy.html>`_ + The proxy object given to binders that allows them to interact with the mojit it + represents as well as with other mojits on the page. See the + `mojitProxy Object <../intro/mojito_binders.html#mojitproxy-object>`_ and the + `MojitProxy Class <../../api/classes/MojitProxy.html>`_ for more information. Mojito ------ - A Web framework with which applications are written entirely in JavaScript, using an `MVC`_ - approach and allowing for transportable code between client (browser) and server. The framework addresses the combined needs - of connected devices and desktops, including disconnected application usage. + A Web framework with which applications are written entirely in JavaScript, using an + `MVC`_ approach and allowing for transportable code between client (browser) and + server. The framework addresses the combined needs of connected devices and desktops, + including disconnected application usage. MVC --- - Acronym for Model-View-Controller. A software architecture pattern used in software engineering. The pattern isolates "domain logic" - (the application logic for the user) from the user interface (input and presentation), permitting independent development, testing - and maintenance of each (separation of concerns). See `Mojito Intro: MVC <../intro/mojito_mvc.html>`_ to learn how MVC is used in Mojito. + Acronym for Model-View-Controller. A software architecture pattern used in software + engineering. The pattern isolates "domain logic" (the application logic for the user) + from the user interface (input and presentation), permitting independent development, + testing and maintenance of each (separation of concerns). See + `Mojito Intro: MVC <../intro/mojito_mvc.html>`_ to learn how MVC is used in Mojito. Node.js ------- - An evented I/O framework for the V8 JavaScript engine on Unix-like platforms that is intended for writing scalable network - programs such as Web servers. See `nodejs.org <http://nodejs.org>`_ for more information. + An evented I/O framework for the V8 JavaScript engine on Unix-like platforms that is + intended for writing scalable network programs such as Web servers. See + `nodejs.org <http://nodejs.org>`_ for more information. npm --- - The package manager is for `Node.js`_ and can be used to install and publish code libraries and manage the dependencies among them. + The package manager is for `Node.js`_ and can be used to install and publish code + libraries and manage the dependencies among them. See `npmjs.org <http://npmjs.org>`_ for more information. OAuth ----- - An open standard that enables users to share information stored on one site with another site without giving out the user ID and password. - See the `Yahoo! OAuth Quick Start Guide <http://developer.yahoo.com/oauth/guide/oauth-guide.html>`_ for more information. + An open standard that enables users to share information stored on one site with + another site without giving out the user ID and password. See the + `Yahoo! OAuth Quick Start Guide <http://developer.yahoo.com/oauth/guide/oauth-guide.html>`_ + for more information. view ---- - The display element of Mojito that is served to a device. The view is rendered from the template and consists of HTML and CSS. + The display element of Mojito that is served to a device. The view is rendered from the + template and consists of HTML and CSS. See `MVC: Views <../intro/mojito_mvc.html#views>`_ for more information. view partial ------------ - Also referred to as partials, partial views, and partial collection. View partials are collections that can be iterated through to create a document fragment. - Using a view partial, you can create that document fragment instead of iterating through the collection in the view. + Also referred to as partials, partial views, and partial collection. View partials are + collections that can be iterated through to create a document fragment. Using a view + partial, you can create that document fragment instead of iterating through the + collection in the view. template ------------- - Template files that are rendered into HTML and served to a device. These templates can contain expressions (Handlebars) or tags (Mustache) that - are replaced with values by a view rendering engine. + Template files that are rendered into HTML and served to a device. These templates can + contain expressions (Handlebars) or tags (Mustache) that are replaced with values by a + view rendering engine. YUI --- - Acronym for `Yahoo! User Interface <http://developer.yahoo.com/yui/>`_. A set of utilities, written in JavaScript and CSS, + + Acronym for `Yahoo! User Interface <http://developer.yahoo.com/yui/>`_. A set of + utilities, written in JavaScript and CSS, for building rich, interactive Web applications. diff --git a/docs/dev_guide/reference/index.rst b/docs/dev_guide/reference/index.rst index afbb7c20e..de93cedcd 100644 --- a/docs/dev_guide/reference/index.rst +++ b/docs/dev_guide/reference/index.rst @@ -1,5 +1,3 @@ - - ================ Mojito Reference ================ diff --git a/docs/dev_guide/reference/mojito_cmdline.rst b/docs/dev_guide/reference/mojito_cmdline.rst index a9d3051f8..373610a65 100644 --- a/docs/dev_guide/reference/mojito_cmdline.rst +++ b/docs/dev_guide/reference/mojito_cmdline.rst @@ -1,12 +1,11 @@ - - =================== Mojito Command Line =================== -Mojito comes with a command line tool that provides a number of key capabilities for the developer, -from generating code skeletons, to running tests and test coverage, to cleaning up and documenting -the code base. +Mojito comes with a command line tool that provides a number of key +capabilities for the developer, from generating code skeletons, to +running tests and test coverage, to cleaning up and documenting the +code base. .. _mj_cmdlne-help: @@ -26,42 +25,34 @@ To show help for a specific command: Creating Code from Archetypes ############################# -Archetypes are used to create skeletons for the different types of artifacts in a Mojito application. -The skeletons only contain stripped down boilerplate code that is easier to create using the -command-line tool rather than by hand. +Archetypes are used to create skeletons for the different types of artifacts +in a Mojito application. The skeletons only contain stripped down boilerplate +code that is easier to create using the command-line tool rather than by hand. To create a skeleton for a Mojito application: ``$ mojito create app [<archetype-name>] <app-name>`` -This will create an empty application (i.e. one with no mojits) with the name provided. The -application is created in a directory named ``<app-name>`` within the current directory. If no -archetype name is provided, the default archetype is used. +This will create an empty application (i.e. one with no mojits) with the name +provided. The application is created in a directory named ``<app-name>`` within +the current directory. If no archetype name is provided, the default archetype +is used. -From the application directory, use the following command to create a skeleton for a mojit: +From the application directory, use the following command to create a skeleton +for a mojit: ``$ mojito create mojit [<archetype-name>] <mojit-name>`` -This will create an empty mojit with the name provided. The command assumes it is being executed -within an application directory. Thus, the mojit is created in a directory named ``<mojit-name>`` -within a ``mojits`` subdirectory of the application directory. For example, the mojit ``MyMojit`` -would be created in ``mojits/MyMojit``. - -As with application creation, if no archetype name is provided, the default archetype is used. -Depending upon the archetype, the skeleton may include any or all of controller, model, view, and -binder. +This will create an empty mojit with the name provided. The command assumes it +is being executed within an application directory. Thus, the mojit is created +in a directory named ``<mojit-name>`` within a ``mojits`` subdirectory of the +application directory. For example, the mojit ``MyMojit`` would be created in +``mojits/MyMojit``. -.. ##Note:## Feature not available yet. -.. From an application directory, use the following command to create a project to build a device \ -.. application where ``<archetype-name>`` can be ``android`` or ``xcode``: +As with application creation, if no archetype name is provided, the default +archetype is used. Depending upon the archetype, the skeleton may include any +or all of controller, model, view, and binder. -.. ``$ mojito create project [<archetype-name>] <project-name>`` - -.. The directory ``artifacts/projects/{archetype-name}/{project-name}`` will be created. If -.. ``<archetype-name>`` is ``android``, a project for creating an -.. Android application using the Android SDK is generated. If ``<archetype-name>`` is ``xcode``, -.. a project for creating an iPhone application using the -.. Apple iOS Developer Kit is generated. .. _mj_cmdlne-archetype: @@ -71,26 +62,23 @@ Mojito Archetypes Mojito offers the following three archetypes for applications and mojits. - ``simple`` - The minimal configuration and code needed to run an application. -- ``default`` - This archetype is run if no command line archetype option is specified. It is a - happy medium between ``simple`` and ``full``. -- ``full`` - Provides the most comprehensive configuration and code for applications. +- ``default`` - This archetype is run if no command line archetype option is + specified. It is a happy medium between ``simple`` and ``full``. +- ``full`` - Provides the most comprehensive configuration and code for + applications. +- ``hybrid`` - Creates a hybrid HTML5 application that can be plugged into + a future component of Cocktails that will allow HTML5/JavaScript applications + to access the features of native devices. .. _mj_cmdlne-testing: Testing ####### -Unit tests are run using YUI Test invoked using the Mojito command-line tool. Test output is written -to the console and also to the file ``{CWD}/artifacts/test/result.xml``, where ``{CWD}`` is -the current working directory. Note that it is not (yet) possible to specify an alternative output -location. - -- To run tests for the Mojito framework itself: - - ``$ mojito test`` - - Output is written to ``{CWD}/artifacts/test/result.xml``, where ``{CWD}`` is the current working - directory. +Unit tests are run using YUI Test invoked using the Mojito command-line tool. +Test output is written to the console and also to the file ``{CWD}/artifacts/test/result.xml``, +where ``{CWD}`` is the current working directory. Note that it is not (yet) +possible to specify an alternative output location. - To run tests for an application: @@ -100,24 +88,31 @@ location. ``$ mojito test mojit <mojit-path> [<mojit-module>]`` - If a mojit module (i.e., the YUI module for a portion of the mojit) is specified, only the tests f - or that module will be run. Otherwise all tests for the mojit will be run. + If a mojit module (i.e., the YUI module for a portion of the mojit) is + specified, only the tests for that module will be run. Otherwise all tests + for the mojit will be run. + +To run functional and unit tests for the Mojito framework, +you would use the test framework `Yahoo! Arrow <https://github.com/yahoo/arrow>`_. +Follow the instructions in `Running Mojito’s Built-In Tests <../topics/mojito_testing.html#running-mojito-s-built-in-tests>`_ +to run the framework tests for Mojito. .. _mj_cmdlne-code_coverage: Code Coverage ############# -Code coverage is invoked in the same way as unit testing, but with the added option ``--coverage`` -or ``-c``. To run code coverage tests, you need to have Java installed. +Code coverage is invoked in the same way as unit testing, but with the added +option ``--coverage`` or ``-c``. To run code coverage tests, you need to have +Java installed. Coverage results are written to the console and also to file in the directory -``{CWD}/artifacts/framework/coverage/``. As with unit tests, it is not possible to specify an -alternative output location. +``{CWD}/artifacts/framework/coverage/``. As with unit tests, it is not +possible to specify an alternative output location. -- To run code coverage for the Mojito framework itself: +- To run code coverage for a Mojito application: - ``$ mojito test --coverage`` + ``$ mojito test --coverage app <app-path>`` - To run code coverage for Mojito applications: @@ -136,26 +131,27 @@ Use the following to start the server and run the application. ``$ mojito start [<port>] [--context "key1:value1,key2:value2,key3:value3"]`` -The port number specified in the command above overrides the port number in the application -configuration file, ``application.json``. The default port number is 8666. See -:ref:`Specifying Context <mj_cmdline-context>` to learn how to use the ``--context`` option. +The port number specified in the command above overrides the port number in +the application configuration file, ``application.json``. The default port +number is 8666. See :ref:`Specifying Context <mj_cmdline-context>` to learn +how to use the ``--context`` option. Sanitizing Code ############### -Static code analysis is run using JSLint invoked using the Mojito command-line tool. JSLint output -is written to text files and to a ``jslint.html`` file, making it easier to view the results. The -output file locations are specified below. Note that it is not possible to specify an alternative -output location. +Static code analysis is run using JSLint invoked using the Mojito command-line +tool. JSLint output is written to text files and to a ``jslint.html`` file, +making it easier to view the results. The output file locations are specified +below. Note that it is not possible to specify an alternative output location. - To run JSLint on the Mojito framework code: ``$ mojito jslint`` - Output is written to ``{CWD}/artifacts/framework/jslint/``, where ``{CWD}`` is the current - working directory. + Output is written to ``{CWD}/artifacts/framework/jslint/``, where ``{CWD}`` + is the current working directory. - To run JSLint on an application, including all of its (owned) mojits: @@ -174,26 +170,27 @@ output location. Documenting Code ################ -API documentation is generated using `YUI Doc <http://developer.yahoo.com/yui/yuidoc/>`_, which is -invoked using the Mojito command-line tool. Documentation output is written to files in the -locations specified below. Note that it is not (yet) possible to specify an alternative output -location. +API documentation is generated using `YUI Doc <http://developer.yahoo.com/yui/yuidoc/>`_, +which is invoked using the Mojito command-line tool. Documentation output is +written to files in the locations specified below. Note that it is not (yet) +possible to specify an alternative output location. - To generate documentation for the Mojito framework itself: ``$ mojito docs mojito`` - Output is written to ``{CWD}/artifacts/docs/mojito/``, where ``{CWD}`` is the current working - directory. + Output is written to ``{CWD}/artifacts/docs/mojito/``, where ``{CWD}`` is + the current working directory. -- To generate documentation for an application, including all of its (owned) mojits, run the - following from the application directory: +- To generate documentation for an application, including all of its (owned) + mojits, run the following from the application directory: ``$ mojito docs app`` Output is written to ``{app-dir}/artifacts/docs/``. -- To generate documentation for a specific mojit, run the following from the application directory: +- To generate documentation for a specific mojit, run the following from the + application directory: ``$ mojito docs mojit <mojit-name>`` @@ -208,28 +205,33 @@ Version Information ``$ mojito version`` -- To show the version for an application, run the following from the application directory: +- To show the version for an application, run the following from the + application directory: ``$ mojito version app <app-name>`` -- To show the version for a mojit, run the following from the application directory: +- To show the version for a mojit, run the following from the application + directory: ``$ mojito version mojit <mojit-name>`` - Showing the version of the application and mojit requires that they have a ``package.json`` file. + Showing the version of the application and mojit requires that they have a + ``package.json`` file. .. _mj_cmdlne-build_sys: Build System ############ -Mojito comes with a build command for generating an HTML5 offline Mojito application that runs in -different environments. The command must be run inside of the application you want built. +Mojito comes with a build command for generating an HTML5 offline Mojito +application that runs in different environments. The command must be run inside +of the application you want built. ``$ mojito build <type> [<output-path>] [--context "key1:value1,key2:value2,key3:value3"]`` Output is written to ``{app-dir}/artifacts/builds/{type}`` by default. See -:ref:`Specifying Context <mj_cmdline-context>` to learn about the ``--context`` option. +:ref:`Specifying Context <mj_cmdline-context>` to learn about the ``--context`` +option. .. _build_sys-types: @@ -247,13 +249,36 @@ To build an HTML 5 application, use the the following: ``$ mojito build html5app`` -This generates a HTML5 Offline Application with a ``cache.manifest`` listing all the files that will -be available offline. An ``index.hb.html`` page is generated from the result of calling the Web root -``/`` of the Mojito application that this command was run within. You can build other pages by -specifying the pages in the ``"builds": "html5app"`` object in ``application.json``. The -`html5 <../intro/mojito_configuring.html#html5app-object>`_ object lets you add the ``manifest`` -attribute to the ``html`` element, configure relative paths, and specify a list of URLs to pages -to generate. +This generates a HTML5 Offline Application with a ``cache.manifest`` listing +all the files that will be available offline. An ``index.hb.html`` page is +generated from the result of calling the Web root ``/`` of the Mojito +application that this command was run within. You can build other pages by +specifying the pages in the ``"builds": "html5app"`` object in +``application.json``. The `html5 <../intro/mojito_configuring.html#html5app-object>`_ +object lets you add the ``manifest`` attribute to the ``html`` element, +configure relative paths, and specify a list of URLs to pages to generate. + +.. _build_types-hybridapp: + +hybridapp +######### + + +To build a hybrid application, use either of the following. + +``$ mojito build hybridapp [--context <context>] --snapshotName <snapshot_name> --snapshotTag <snapshot_tag> [<path/to/app/>]`` + +``$ mojito build hybridapp [-c <context>] -n <snapshot_name> -t <snapshot_tag> [<path/to/app/>]`` + + +This generates a HTML5 application that is customized to work with a future +component of the Cocktails suite that will allow you to write HTML5/JavaScript +applications that can access native features of mobile devices. + +The generated application contains Mojito, frame mojits, the default mojit +``top_frame``, your created mojits, and application configuration. The command +will also create a ``snapshot.json`` file that can be used tracking and updating applications. + .. _mj_cmdlne-compile_sys: @@ -268,8 +293,8 @@ production. Syntax ====== -Compile files with the command below where ``<type>`` can have the following values: ``all``, -``inlinecss``, ``views``, ``json``, or ``rollups``. +Compile files with the command below where ``<type>`` can have the following values: +``all``, ``inlinecss``, ``views``, ``json``, or ``rollups``. ``$ mojito compile <options> <type>`` @@ -278,10 +303,12 @@ In addition, the compile command takes the following three options: - ``--app`` or ``-a`` - generates files for application-level files, including files in application-level mojits - ``--clean`` or ``-c`` - cleans up compiled modules -- ``--everything`` or ``-e`` - compiles everything possible and does not require a ``<type>`` +- ``--everything`` or ``-e`` - compiles everything possible and does not require a + ``<type>`` - ``--remove`` or ``-r`` - removes the files that were generated -.. note:: The ``--app`` option is not supported for the ``inlinecss``, ``views``, or ``json`` types. +.. note:: The ``--app`` option is not supported for the ``inlinecss``, ``views``, or + ``json`` types. .. _compile_sys-inline_css: @@ -289,8 +316,8 @@ Compiling Inline CSS ==================== The command below creates files for adding inline CSS to a page. The CSS files in -``/mojits/{mojit_name}/assets/`` will be automatically included as inlined CSS in the rendered -views for mojits that are children of the ``HTMLFrameMojit``. +``/mojits/{mojit_name}/assets/`` will be automatically included as inlined CSS in the +rendered views for mojits that are children of the ``HTMLFrameMojit``. ``$ mojito compile inlinecss`` @@ -300,8 +327,8 @@ Compiling Views =============== The command below pre-compiles the views in ``mojit/{mojit_name}/views`` so that a mojit's -controller and binder are attached to the views, making separate XHR call (back to the server) -unnecessary. +controller and binder are attached to the views, making separate XHR call +(back to the server) unnecessary. ``$ mojito compile views`` @@ -311,8 +338,8 @@ unnecessary. Compiling Configuration ======================= -The command below using the type ``json`` reads the JSON configuration files, such as the specs, -definitions, and defaults, and compiles them into JavaScript. +The command below using the type ``json`` reads the JSON configuration files, such as the +specs, definitions, and defaults, and compiles them into JavaScript. ``$ mojito compile json`` @@ -322,9 +349,10 @@ definitions, and defaults, and compiles them into JavaScript. Compiling Rollups ================= -The command below consolidates the YUI modules in the mojits into a single YUI module, making only -one ``<script>`` tag needed per page. Using the ``--app`` option creates a rollup containing all of -the application-level YUI modules as well as all of the Mojito framework code. +The command below consolidates the YUI modules in the mojits into a single YUI module, +making only one ``<script>`` tag needed per page. Using the ``--app`` option creates a +rollup containing all of the application-level YUI modules as well as all of the Mojito +framework code. ``$ mojito compile rollups`` @@ -344,35 +372,38 @@ The commands below compile inline CSS, views, and YUI modules. Dependency Graphs ################# -The command below generates the Graphviz file ``{CWD}/artifacts/gv/yui.client.dot`` (``{CWD}`` represents -the current working directory) that describes the YUI module dependencies. +The command below generates the Graphviz file ``{CWD}/artifacts/gv/yui.client.dot`` +(``{CWD}`` represents the current working directory) that describes the YUI module +dependencies. ``$ mojito gv`` The ``mojito gv`` command has the following options: -- ``--client`` - inspects the files that have ``client`` and ``common`` as the affinity. The default - is just to inspect files that have ``server`` and ``common`` as the affinity. For example, using - the ``--client`` option, the file ``controller.client.js`` and ``controller.common.js`` will be - inspected. +- ``--client`` - inspects the files that have ``client`` and ``common`` as the affinity. + The default is just to inspect files that have ``server`` and ``common`` as the affinity. + For example, using the ``--client`` option, the file ``controller.client.js`` and + ``controller.common.js`` will be inspected. - ``--framework`` - also inspects the Mojito framework files. -.. note:: To render the Graphviz files into GIF images, you need the `Graphviz - Graph Visualization - Software <http://www.graphviz.org/Download..php>`_. +.. note:: To render the Graphviz files into GIF images, you need the `Graphviz - Graph + Visualization Software <http://www.graphviz.org/Download..php>`_. .. _mj_cmdline-context: Specifying Context ################## -When configuration files are read, a context is applied to determine which values will be used for -a given key. The applied context is a combination of the dynamic context determined for each HTTP -request and a static context specified when the server is started. See -`Using Context Configurations <../topics/mojito_using_contexts.html>`_ for more information. +When configuration files are read, a context is applied to determine which +values will be used for a given key. The applied context is a combination of +the dynamic context determined for each HTTP request and a static context +specified when the server is started. See +`Using Context Configurations <../topics/mojito_using_contexts.html>`_ for +more information. -The static context can be specified by a command-line option whose value is a comma-separated list -of key-value pairs. Each key-value pair is separated by a colon. Try to avoid using whitespace, -commas, and colons in the keys and values. +The static context can be specified by a command-line option whose value +is a comma-separated list of key-value pairs. Each key-value pair is separated +by a colon. Try to avoid using whitespace, commas, and colons in the keys and values. ``$ mojito start --context "key1:value1,key2:value2,key3:value3"`` diff --git a/docs/dev_guide/reference/mojito_troubleshooting.rst b/docs/dev_guide/reference/mojito_troubleshooting.rst index 661cae053..f3fd62cf9 100644 --- a/docs/dev_guide/reference/mojito_troubleshooting.rst +++ b/docs/dev_guide/reference/mojito_troubleshooting.rst @@ -1,22 +1,29 @@ - - =============== Troubleshooting =============== -The following provide answers to common Mojito problems. See also the `Mojito: FAQ <../faq/>`_. +The following provide answers to common Mojito problems. See also the +`Mojito: FAQ <../faq/>`_. Issues ###### -* :ref:`I am trying get config values using "ac.config.get(key)", but Mojito is giving me an error or the value is not found. <moj_config_error>` -* :ref:`I am getting the message that my mojit controller is not an object? What does this mean and how do I fix the problem? <moj_controller_not_obj>` -* :ref:`I am including CSS files in the assets object of "application.json", so why are my CSS files not being inserted into the HTML page? <moj_asset_insertion>` -* :ref:`My binder is getting deployed to the client, so why isn't the "init" function being called? <moj_binder_init>` -* :ref:`I am getting Handlebars rendering errors. Is this a client-side or server-side issue with Handlebars and can it be fixed? <handlebars_rendering_error>` -* :ref:`Why can't my controller access the YUI modules in the "autoload" directory? <controller_access_autoload>` -* :ref:`Why am I getting the error message "EADDRINUSE, Address already in use" when I try to start Mojito? <eaddriuse_err>` -* :ref:`When I execute child mojits with "composite.execute", the views are being rendered, but the binders are not executed. Why? <binder_not_executing>` +* :ref:`I am trying get config values using "ac.config.get(key)", but Mojito is giving me + an error or the value is not found. <moj_config_error>` +* :ref:`I am getting the message that my mojit controller is not an object? What does this + mean and how do I fix the problem? <moj_controller_not_obj>` +* :ref:`I am including CSS files in the assets object of "application.json", so why are my + CSS files not being inserted into the HTML page? <moj_asset_insertion>` +* :ref:`My binder is getting deployed to the client, so why isn't the "init" function being + called? <moj_binder_init>` +* :ref:`I am getting Handlebars rendering errors. Is this a client-side or server-side + issue with Handlebars and can it be fixed? <handlebars_rendering_error>` +* :ref:`Why can't my controller access the YUI modules in the "autoload" directory? + <controller_access_autoload>` +* :ref:`Why am I getting the error message "EADDRINUSE, Address already in use" when I try + to start Mojito? <eaddriuse_err>` +* :ref:`When I execute child mojits with "composite.execute", the views are being rendered, + but the binders are not executed. Why? <binder_not_executing>` Solutions @@ -24,14 +31,17 @@ Solutions .. _moj_config_error: -**Q:** *I am trying get config values using 'ac.config.get(key)', but Mojito is giving me an error or the value is not found.* +**Q:** *I am trying get config values using 'ac.config.get(key)', but Mojito is giving me +an error or the value is not found.* **A:** -Try inspecting the ``spec`` object that is found in the ``ActionContext`` object for the key. If ``ac`` is the ``ActionContext`` object, -you can access the ``specs` object with the following: ``ac.app.config.specs``. +Try inspecting the ``spec`` object that is found in the ``ActionContext`` object for the +key. If ``ac`` is the ``ActionContext`` object, you can access the ``specs` object with the +following: ``ac.app.config.specs``. -If you need to examine the entire ``ActionContext`` object, you can use the ``console.log(ac)`` or the following code: +If you need to examine the entire ``ActionContext`` object, you can use the +``console.log(ac)`` or the following code: .. code-block:: javascript @@ -44,28 +54,40 @@ If you need to examine the entire ``ActionContext`` object, you can use the ``co .. _moj_controller_not_obj: -**Q:** *I am getting the message that my mojit controller is not an object? What does this mean and how do I fix the problem?* +**Q:** *I am getting the message that my mojit controller is not an object? What does this +mean and how do I fix the problem?* **A:** -Usually, this error occurs when one of your controllers has a syntax error. Use the ``mojito`` command with the option ``jslint`` as seen below to check the app and your mojits for errors: +Usually, this error occurs when one of your controllers has a syntax error. Use the +``mojito`` command with the option ``jslint`` as seen below to check the app and your +mojits for errors: :: $ mojito jslint app {app_name} $ mojito jslint mojit {app_name}/mojits/{mojit_name} -The output from the above commands will tell you if you have errors, but not where the errors are. Use your own developer tools or manually check your controllers for errors and then run your application again. +The output from the above commands will tell you if you have errors, but not where the +errors are. Use your own developer tools or manually check your controllers for errors and +then run your application again. ------------ .. _moj_asset_insertion: -**Q:** *I am including CSS files in the assets object of 'application.json', so why are my CSS files not being inserted into the HTML page?* +**Q:** *I am including CSS files in the assets object of 'application.json', so why are my +CSS files not being inserted into the HTML page?* **A:** -To configure Mojito to automatically insert the asset files specified in the ``assets`` object of ``application.json``, you must use the ``HTMLFrameMojit``. The ``HTMLFrameMojit`` will insert the assets into the ``head`` element if you include the assets in the ``top`` array or at the bottom within the ``body`` element if you include the assets in the ``bottom`` array. +To configure Mojito to automatically insert the asset files specified in the ``assets`` +object of ``application.json``, you must use the ``HTMLFrameMojit``. The ``HTMLFrameMojit`` +will insert the assets into the ``head`` element if you include the assets in the ``top`` +array or at the bottom within the ``body`` element if you include the assets in the +``bottom`` array. -In the example ``application.json`` below, the ``simple.css`` file will be included in the ``head`` element of the HTML page. Note that the ``assets`` object is inside the ``frame`` mojit instance. which is of type ``HTMLFrameMojit``. +In the example ``application.json`` below, the ``simple.css`` file will be included in the +``head`` element of the HTML page. Note that the ``assets`` object is inside the ``frame`` +mojit instance. which is of type ``HTMLFrameMojit``. .. code-block:: javascript @@ -96,27 +118,33 @@ In the example ``application.json`` below, the ``simple.css`` file will be inclu .. _moj_binder_init: -**Q:** *My binder is getting deployed to the client, so why isn't the "init" function being called?* +**Q:** *My binder is getting deployed to the client, so why isn't the "init" function +being called?* **A:** -Most likely Mojito was not able to create a ``Y.one`` instance to wrap the DOM nodes that wrap mojit instances because the root element of the mojit's template -didn't have the ``id`` value ``{{mojit_view_id}}``. If your template wraps its content in a ``<div>`` element, assign the value ``{{mojit_view_id}}`` to -the ``id`` attribute of that ``<div>`` element: ``<div id={{mojit_view_id}}>`` +Most likely Mojito was not able to create a ``Y.one`` instance to wrap the DOM nodes that +wrap mojit instances because the root element of the mojit's template didn't have the +``id`` value ``{{mojit_view_id}}``. If your template wraps its content in a ``<div>`` +element, assign the value ``{{mojit_view_id}}`` to the ``id`` attribute of that ``<div>`` + element: ``<div id={{mojit_view_id}}>`` ------------ .. _handlebars_rendering_error: -**Q:** *I am getting Handlebars rendering errors. Is this a client-side or server-side issue with Handlebars and can it be fixed?* +**Q:** *I am getting Handlebars rendering errors. Is this a client-side or server-side +issue with Handlebars and can it be fixed?* **A:** -The issue is not with Handlebars on the client, but with the Handlebars rendering engine on the -server. The Handlebars rendering engine inspects the prototypes during the template processing stage. If you -remove the prototype inspecting, e.g., creating object literals, the Handlebars engine cannot process the data for the templates. +The issue is not with Handlebars on the client, but with the Handlebars rendering engine +on the server. The Handlebars rendering engine inspects the prototypes during the template +processing stage. If you remove the prototype inspecting, e.g., creating object literals, +the Handlebars engine cannot process the data for the templates. -Although not a permanent solution, you can use ``Y.mix`` to ensure that your data has a prototype so that -your templates can be rendered. Try doing the following: ``ac.done(Y.mix({},data));`` +Although not a permanent solution, you can use ``Y.mix`` to ensure that your data has a +prototype so that your templates can be rendered. Try doing the following: +``ac.done(Y.mix({},data));`` ------------ @@ -125,31 +153,38 @@ your templates can be rendered. Try doing the following: ``ac.done(Y.mix({},data **Q:** *Why can't my controller access the YUI modules in the "autoload" directory?* **A:** -A common problem is that the YUI module is missing the `affinity <../reference/glossary.html#affinity>`_ -or that the affinity is incorrect. If your controller has been deployed to the client, your YUI module should -have the ``client`` or ``common`` affinity. If your controller is running on the server, the YUI module should have -the affinity ``server`` or ``common``. Also, confirm that the registered name of the YUI module, i.e., -the string passed to ``YUI.add``, is the same as the string passed to the ``requires`` array. +A common problem is that the YUI module is missing the +`affinity <../reference/glossary.html#affinity>`_ or that the affinity is incorrect. If +your controller has been deployed to the client, your YUI module should have the +``client`` or ``common`` affinity. If your controller is running on the server, the YUI +module should have the affinity ``server`` or ``common``. Also, confirm that the +registered name of the YUI module, i.e., the string passed to ``YUI.add``, is the same as +the string passed to the ``requires`` array. ------------ .. _eaddriuse_err: -**Q:** *Why am I getting the error message "EADDRINUSE, Address already in use" when I try to start Mojito?* +**Q:** *Why am I getting the error message "EADDRINUSE, Address already in use" when I try +to start Mojito?* **A:** -You probably have an instance of mojito already started/running (check the output from ``ps aux | grep mojito``). -Either stop the instance that is running or start a new instance on another port such as in ``mojito start 8667``. +You probably have an instance of mojito already started/running (check the output from +``ps aux | grep mojito``). Either stop the instance that is running or start a new +instance on another port such as in ``mojito start 8667``. ------------ .. _binder_not_executing: -**Q:** *When I execute child mojits with "composite.execute", the views are being rendered, but the binders are not executed. Why?* +**Q:** *When I execute child mojits with "composite.execute", the views are being +rendered, but the binders are not executed. Why?* **A:** -The problem may be that you need to pass the "meta" information to your children as well. This is where the binder metadata *bubbles up* -from the children. Try doing the following: +The problem may be that you need to pass the "meta" information to your children as well. +This is where the binder metadata *bubbles up* from the children. + +Try doing the following: .. code-block:: javascript diff --git a/docs/dev_guide/resources/index.rst b/docs/dev_guide/resources/index.rst index 64086b149..1debb643b 100644 --- a/docs/dev_guide/resources/index.rst +++ b/docs/dev_guide/resources/index.rst @@ -1,5 +1,3 @@ - - ==================== Additional Resources ==================== @@ -15,7 +13,7 @@ The following sections provide supplementary material about Mojito: .. _additional_resources-presentations: Presentations/Slidecasts -######################## +======================== +--------------------------------------------------------+--------------------------------------------------------+--------------------------------------------------------+----------------------+ | Title | Topics Covered | Description | Presenter | @@ -38,7 +36,7 @@ Presentations/Slidecasts .. _additional_resources-screencasts: Screencasts/Videos -################## +================== +--------------------------------------------------------+--------------------------------------------------------+--------------------------------------------------------+--------------------------------------------------------+ | Title | Topics Covered | Description | Narrator | @@ -70,31 +68,37 @@ Screencasts/Videos +--------------------------------------------------------+--------------------------------------------------------+--------------------------------------------------------+--------------------------------------------------------+ - +.. _additional_resources-community: Community -######### +========= + +.. _res_community-forums: Developer Forums -================ +---------------- `YDN: Mojito Forum <http://developer.yahoo.com/forum/Yahoo-Mojito/>`_ +.. _res_community-twitter: + Twitter -======= +------- - `@ydn <https://twitter.com/#!/ydn>`_ - `@rw0 <https://twitter.com/#!/rw0>`_ - `@olympum <https://twitter.com/#!/olympum>`_ - `@add0n <https://twitter.com/#!/add0n>`_ - +.. _res_community-pubs: Publications -############ +------------ + +.. _res_community_pubs-articles: Articles -======== +######## .. raw:: html @@ -173,7 +177,7 @@ Articles <td><a class="ulink" href="http://www.wired.com/wiredenterprise/2012/04/yahoo-open-sources-mojito/" target="_top">Yahoo Open Sources Its Apple App Store Killer</a></td> <td><a class="ulink" href="http://www.wired.com" target="_top">Wired</a></td> <td>2012-04-02</td> - <td><a href="http://www.wired.com/cloudline/members/cademetz/">Cade Metz</a></td> + <td><a href="http://www.wired.com/wiredenterprise/author/cade_metz">Cade Metz</a></td> </tr> <tr> <td><a class="ulink" href="http://cnet.co/FPVLPT" target="_top">Why Ambitious Developers Need More Than Just HTML5</a></td> @@ -221,15 +225,16 @@ Articles <td><a class="ulink" href="http://www.wired.com/wiredenterprise/2011/11/yahoos-manhattan/" target="_top">Yahoo’s ‘Manhattan’ To Rescue Web From the iPad</a></td> <td><a class="ulink" href="http://www.wired.com" target="_top">Wired</a></td> <td>2011-11-02</td> - <td><a href="http://www.wired.com/cloudline/members/cademetz/">Cade Metz</a></td> + <td><a href="http://www.wired.com/wiredenterprise/author/cade_metz">Cade Metz</a></td> </tr> </tbody> </table> </div> +.. _res_community_pubs-blogs: Blogs -===== +##### - `Yahoo! Announces Cocktails – Shaken, Not Stirred <http://developer.yahoo.com/blogs/ydn/posts/2011/11/yahoo-announces-cocktails-%E2%80%93-shaken-not-stirred/>`_ - `How YQL powers Cocktails, the technology behind Livestand <http://developer.yahoo.com/blogs/ydn/posts/2011/11/how-yql-powers-cocktails-the-technology-behind-livestand/>`_ diff --git a/docs/dev_guide/topics/index.rst b/docs/dev_guide/topics/index.rst index b9a4381d2..547627cbb 100644 --- a/docs/dev_guide/topics/index.rst +++ b/docs/dev_guide/topics/index.rst @@ -1,13 +1,12 @@ - -================== +================ Developer Topics -================== +================ -The following sections provide conceptual information and instruction for some of the core components of the Mojito framework. -Although the developer topics are complemented by code snippets, for working code examples, see `Code Examples <../code_exs/>`_. +The following sections provide conceptual information and instruction for some +of the core components of the Mojito framework. Although the developer topics +are complemented by code snippets, for working code examples, see +`Code Examples <../code_exs/>`_. -Table of Contents -################# .. toctree:: :maxdepth: 2 @@ -18,8 +17,9 @@ Table of Contents mojito_testing mojito_composite_mojits mojito_run_dyn_defined_mojits - mojito_framework_mojits + mojito_frame_mojits mojito_extensions mojito_using_contexts mojito_npm mojito_resource_store + mojito_hosting_container_reqs diff --git a/docs/dev_guide/topics/mojito_assets.rst b/docs/dev_guide/topics/mojito_assets.rst index 546694ceb..077e3bab0 100644 --- a/docs/dev_guide/topics/mojito_assets.rst +++ b/docs/dev_guide/topics/mojito_assets.rst @@ -2,39 +2,58 @@ Assets ====== +.. _mojito_assets-intro: + Introduction -############ +============ -Assets are resources that are required on the clients. These resources are primarily CSS but can also be JavaScript that is ancillary to and not a +Assets are resources that are required on the clients. These resources are +primarily CSS but can also be JavaScript that is ancillary to and not a core component of the Mojito application. This topic discusses the following: - location of assets - configuring applications to use assets - accessing assets from controllers and views -To see code examples that demonstrate how to use assets, see `Code Examples: Assets <../code_exs/#assets>`_. +To see code examples that demonstrate how to use assets, see +`Code Examples: Assets <../code_exs/#assets>`_. + +.. _mojito_assets-loc: Location of Asset Files -####################### +======================= + +Assets can be used at the application level and the mojit level. For +application-level assets, CSS and JavaScript files are placed in +the ``{application_name}/assets`` directory. For mojit-level assets, +CSS and JavaScript files are placed in the +``{application_name}/mojits/{mojit_name}/assets`` directory. -Assets can be used at the application level and the mojit level. For application-level assets, CSS and JavaScript files are placed in -the ``{application_name}/assets`` directory. For mojit-level assets, CSS and JavaScript files are placed in the ``{application_name}/mojits/{mojit_name}/assets`` directory. +To better organize your assets, you can create separate directories for CSS and +JavaScript files under the ``assets`` directory. The names of the directories +that you create are arbitrary, but the convention is to create the directories +``css`` for CSS files and ``js`` for JavaScript files. For example, the +application-level CSS assets could be placed in the following directory: +``{application_name}/assets/css`` -To better organize your assets, you can create separate directories for CSS and JavaScript files under the ``assets`` directory. The names of the -directories that you create are arbitrary, but the convention is to create the directories ``css`` for CSS files and ``js`` for JavaScript files. -For example, the application-level CSS assets could be placed in the following directory: ``{application_name}/assets/css`` +.. _mojito_assets-config: Configuration -############# +============= + +You specify the location of your assets in the ``assets`` object specified in +the configuration file ``application.json``. Mojito will read the configuration +file and create a static path to your assets that you can use from your views. -You specify the location of your assets in the ``assets`` object specified in the configuration file ``application.json``. Mojito will read the -configuration file and create a static path to your assets that you can use from your views. +.. _assets_config-assets_obj: assets Object -============= +------------- -In the ``application.json`` file, you use the ``assets`` object to specify the type of asset, the location, and where you would like Mojito to include -the asset in the view. The tables below describe the ``assets`` object and its fields. +In the ``application.json`` file, you use the ``assets`` object to specify the +type of asset, the location, and where you would like Mojito to include +the asset in the view. The tables below describe the ``assets`` object and its +fields. +----------------+----------------------+---------------+------------------------------------------------------------------+------------------------------------------------------------------+ | Property | Data Type | Required? | Example | Description | @@ -54,10 +73,14 @@ the asset in the view. The tables below describe the ``assets`` object and its | | | | ``"/assets/js/whistles.css" ]`` | you want to include JavaScript assets. | +----------------+----------------------+---------------+------------------------------------------------------------------+------------------------------------------------------------------+ + +.. _assets_config-assets_ex: + Examples -======== +-------- -In the ``application.json`` below, the ``assets`` object specifies the paths to the CSS and JavaScript assets: +In the ``application.json`` below, the ``assets`` object specifies the paths to +the CSS and JavaScript assets: .. code-block:: javascript @@ -81,7 +104,9 @@ In the ``application.json`` below, the ``assets`` object specifies the paths to } ] -This ``application.json`` configures Mojito to use the ``HTMLFrameMojit`` that automatically inserts a ``<link>`` tag pointing to ``index.css`` into + +This ``application.json`` configures Mojito to use the ``HTMLFrameMojit`` +that automatically inserts a ``<link>`` tag pointing to ``index.css`` into the ``<head>`` tag of the rendered view. .. code-block:: javascript @@ -109,14 +134,21 @@ the ``<head>`` tag of the rendered view. } ] + +.. _mojito_assets-accessing: + Accessing Assets from an Application -#################################### +==================================== -When specified in ``application.json``, assets can be accessed through a static URL created by Mojito. The static URLs start with ``/static/`` and -point to either the ``assets`` directory under the mojit or application directory, depending on whether the asset is at the application or mojit level. +When specified in ``application.json``, assets can be accessed through a static +URL created by Mojito. The static URLs start with ``/static/`` and point to +either the ``assets`` directory under the mojit or application directory, +depending on whether the asset is at the application or mojit level. + +.. _assets_access-static_url: Syntax for Static URL -===================== +--------------------- For application-level assets, the static URL has the following syntax: @@ -126,10 +158,15 @@ For mojit-level assets, the static URL has the following syntax: ``/static/{mojit_name}/assets/{asset_file}`` + +.. _static_url-refer: + Referring to the Static URL in the Template -=========================================== +------------------------------------------- -Once Mojito has created a static URL to an asset, you can use the ``<link>`` tag in your view to refer to the asset. In the example index template below, the ``<link>`` tag refers to the static URL to the asset ``index.css``. +Once Mojito has created a static URL to an asset, you can use the ``<link>`` +tag in your view to refer to the asset. In the example index template below, +the ``<link>`` tag refers to the static URL to the asset ``index.css``. .. code-block:: html @@ -143,25 +180,28 @@ Once Mojito has created a static URL to an asset, you can use the ``<link>`` tag </ul> </div> -From the static URL, you cannot tell the asset is mojit or application level, but you do know that either the application or the mojit is ``simple``. +From the static URL, you cannot tell the asset is mojit or application level, +but you do know that either the application or the mojit is ``simple``. + +.. _mojito_assets-using: Using the Assets Addon -###################### +====================== -Mojito provides an `Assets addon <../../api/classes/Assets.common.html>`_ that allows you to add inline assets -or links to asset files. Using the ``Assets`` addon, you can dynamically add assets to an HTML page. Two possible use cases would be adding CSS if the -HTTP request is coming from a particular device or adding JavaScript if a user takes a particular action. +Mojito provides an `Assets addon <../../api/classes/Assets.common.html>`_ +that allows you to add inline assets or links to asset files. Using the ``Assets`` +addon, you can dynamically add assets to an HTML page. Two possible use cases would +be adding CSS if the HTTP request is coming from a particular device or adding +JavaScript if a user takes a particular action. -In the mojit controller below, the ``Assets`` addon is used to add metadata and CSS for requests from iPhones. The ``assets.addBlob`` method adds +In the mojit controller below, the ``Assets`` addon is used to add metadata and CSS +for requests from iPhones. The ``assets.addBlob`` method adds the ``<meta>`` tag and the ``addCss`` method adds the device-specific CSS. .. code-block:: javascript YUI.add('device', function(Y, NAME){ Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, index: function(ac) { var device = ac.context.device, css = "./simple"; if (device === 'iphone') { @@ -200,24 +240,28 @@ the ``<meta>`` tag and the ``addCss`` method adds the device-specific CSS. ); } }; - }, '0.0.1', {requires: []}); + }, '0.0.1', {requires: ['mojito-assets-addon']}); + + +.. _mojito_assets-yui_assets: YUI Assets -########## +========== -YUI modules should be placed in the ``autoload`` directory and **not** the ``assets`` directory. When your mojit code wants to use one of the YUI modules -in the ``autoload`` directory, you add the module name in the ``requires`` array, and Mojito will automatically load the module. +YUI modules should be placed in the ``autoload`` directory and **not** +the ``assets`` directory. When your mojit code wants to use one of the YUI +modules in the ``autoload`` directory, you add the module name in the +``requires`` array, and Mojito will automatically load the module. -For example, to use a YUI module called ``substitute`` in your mojit controller, you would place the ``substitute.js`` file in the ``autoload`` directory -and then add the module name in the ``requires`` array as seen in the example mojit controller below. +For example, to use a YUI module called ``substitute`` in your mojit +controller, you would place the ``substitute.js`` file in the +``autoload`` directory and then add the module name in the ``requires`` +array as seen in the example mojit controller below. .. code-block:: javascript YUI.add('textProcessor', function(Y, NAME){ Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, index: function(ac) { // Use the 'substitute' module var name = Y.substitute("Welcome {name}!", {"name":ac.getFromMerged("name")}); @@ -227,20 +271,32 @@ and then add the module name in the ``requires`` array as seen in the example mo }, '0.0.1', {requires: ['substitute']}); -Rolling Up Static Assets -######################## -Mojito lets you `compile views, configuration, and YUI modules <../reference/mojito_cmdline.html#compile-system>`_, but has no native support for rolling up static assets. -Fortunately, you can use the npm module `Shaker <https://github.com/yahoo/mojito-shaker>`_ to roll up static assets for Mojito applications. Shaker lets you -create production rollups at build time, push rollups to a `content delivery network (CDN) <http://en.wikipedia.org/wiki/Content_delivery_network>`_, customize rollups based on `context configurations <../topics/mojito_using_contexts.html>`_, +.. _mojito_assets-rollup: + +Rolling Up Static Assets +======================== + +Mojito lets you `compile views, configuration, and YUI modules <../reference/mojito_cmdline.html#compile-system>`_, +but has no native support for rolling up static assets. Fortunately, you can +use the npm module `Shaker <https://github.com/yahoo/mojito-shaker>`_ to roll +up static assets for Mojito applications. Shaker lets you create production +rollups at build time, push rollups to a `content delivery network (CDN) <http://en.wikipedia.org/wiki/Content_delivery_network>`_, +customize rollups based on `context configurations <../topics/mojito_using_contexts.html>`_, and more. See the `Shaker documentation <../../../shaker/>`_ for more information. +.. _mojito_assets-inline: Inline CSS -########## - -You can use the Mojito command-line tool to compile a mojit's CSS so that the CSS is automatically inlined in rendered views. -The mojit, however, **must** be a child of the `HTMLFrameMojit <../topics/mojito_framework_mojits.html#htmlframemojit>`_. - -When you run ``mojito compile inlinecss``, the CSS files in ``/mojits/{mojit_name}/assets/`` are compiled into the YUI module ``/mojits/{mojit_name}/autoload/compiled/inlinecss.common.js``. -Mojito will use the compiled CSS and insert inline CSS into the ``<head>`` element of the rendered view. See also `Compiling Inline CSS <../reference/mojito_cmdline.html#compiling-inline-css>`_. +========== + +You can use the Mojito command-line tool to compile a mojit's CSS so that the +CSS is automatically inlined in rendered views. The mojit, however, **must** +be a child of the `HTMLFrameMojit <../topics/mojito_frame_mojits.html#htmlframemojit>`_. + +When you run ``mojito compile inlinecss``, the CSS files in +``/mojits/{mojit_name}/assets/`` are compiled into the YUI module +``/mojits/{mojit_name}/autoload/compiled/inlinecss.common.js``. +Mojito will use the compiled CSS and insert inline CSS into the ``<head>`` +element of the rendered view. See also +`Compiling Inline CSS <../reference/mojito_cmdline.html#compiling-inline-css>`_. diff --git a/docs/dev_guide/topics/mojito_composite_mojits.rst b/docs/dev_guide/topics/mojito_composite_mojits.rst index d0eb6e878..c40296a07 100644 --- a/docs/dev_guide/topics/mojito_composite_mojits.rst +++ b/docs/dev_guide/topics/mojito_composite_mojits.rst @@ -2,20 +2,31 @@ Composite Mojits ================ +.. _mojito_composite-intro: + Introduction -############ +============ -A composite mojit is a parent mojit that has child mojits. This parent mojit is responsible for the execution and layout of its children. -The child mojits as subordinates create content and provide functionality for the parent mojit. See `Using Multiple Mojits <../code_exs/multiple_mojits.html>`_ for a working example of composite mojits. +A composite mojit is a parent mojit that has child mojits. This parent mojit is +responsible for the execution and layout of its children. The child mojits as +subordinates create content and provide functionality for the parent mojit. +See `Using Multiple Mojits <../code_exs/multiple_mojits.html>`_ for a working +example of composite mojits. +.. _mojito_composite-parent_child: Creating Parent and Child Mojit Instances -######################################### - -As with any mojit, you need to define a mojit instances in ``application.json``. The parent mojit instance defines its child mojits in the ``children`` object. -In the example ``application.json`` below, the parent mojit instance is ``foo``, which has the child mojit instances ``nav``, ``news``, and ``footer``. -Each mojit instance has a ``type`` that specifies the mojits that are instantiated. Because the parent mojit has children, you cannot use an anonymous -mojit instance in ``routes.json`` to call an action. For example, in ``routes.json``, you could have ``"call": "foo.index"``, but not ``"call": "@MyComp.index"``. +========================================= + +As with any mojit, you need to define a mojit instances in ``application.json``. +The parent mojit instance defines its child mojits in the ``children`` object. +In the example ``application.json`` below, the parent mojit instance is ``foo``, +which has the child mojit instances ``nav``, ``news``, and ``footer``. Each +mojit instance has a ``type`` that specifies the mojits that are instantiated. +Because the parent mojit has children, you cannot use an anonymous +mojit instance in ``routes.json`` to call an action. For example, in +``routes.json``, you could have ``"call": "foo.index"``, but not +``"call": "@MyComp.index"``. .. code-block:: javascript @@ -43,51 +54,61 @@ mojit instance in ``routes.json`` to call an action. For example, in ``routes.js } ] + +.. _mojito_composite-execute_child: + Executing Child Mojits -###################### +====================== -The parent mojit instance defined in ``application.json`` can access the ``config`` object and execute the child mojits from the controller. -The ``init`` function of the controller is passed ``config``, which has the ``children`` object listing the child mojits. +The parent mojit instance defined in ``application.json`` can access the +``config`` object and execute the child mojits from the controller. +The controller methods of the parent mojit can use the ``Config`` addon +to get the application configuration with the method ``getAppConfig``. -In the example controller of ``ParentMojit`` below, the ``init`` function saves and displays the ``children`` object that lists the child mojits. +In the example controller of ``ParentMojit`` below, the ``init`` function saves +and displays the ``children`` object that lists the child mojits. .. code-block:: javascript YUI.add('ParentMojit', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - // Displays the 'children' object that is + index: function(ac) { + var app_config = ac.config.getAppConfig(); + // The app config contains the 'children' object that is // defined in application.json - Y.log(this.config); + Y.log(app_config); }, ... - } - } + }; + }, '0.0.1', {requires: ['mojito', 'mojito-config-addon']}); + -When the controller of the parent mojit calls ``ac.composite.done`` from the ``index`` function, the controllers of the mojit children execute ``ac.done`` -from their ``index`` functions. The rendered views from the child mojits are then available as Handlebars expressions in the ``index`` template of the +When the controller of the parent mojit calls ``ac.composite.done`` from the ``index`` +function, the controllers of the mojit children execute ``ac.done`` from their +``index`` functions. The rendered views from the child mojits are then available +as Handlebars expressions in the ``index`` template of the parent mojit. -For example, in the example controller of the parent mojit below, the ``index`` function calls ``ac.composite.done``, which executes ``ac.done`` in the ``index`` -functions of the child mojits. The rendered ``index`` views for each of the child mojits is then available to as a Handlebars expression, such as ``{{{child_mojit}}}``. -Notice that the ``template`` object allows the parent mojit to send data to the template, so that ``{{title}}`` can be used in the +For example, in the example controller of the parent mojit below, the ``index`` +function calls ``ac.composite.done``, which executes ``ac.done`` in the +``index`` functions of the child mojits. The rendered ``index`` views for +each of the child mojits is then available to as a Handlebars expression, such +as ``{{{child_mojit}}}``. Notice that the ``template`` object allows the parent +mojit to send data to the template, so that ``{{title}}`` can be used in the template. .. code-block:: javascript YUI.add('ParentMojit', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, index: function(ac) { ac.composite.done({ template: { title: 'Recent News'}}); } }; - }, '0.1.0', {requires: []}); + }, '0.1.0', {requires: ['mojito', 'mojito-composite-addon']}); -If ``ParentMojit`` above is the parent of ``ChildMojit``, the controller of ``ChildMojit`` shown below will execute ``ac.done`` in the ``index`` function. +If ``ParentMojit`` above is the parent of ``ChildMojit``, the controller of +``ChildMojit`` shown below will execute ``ac.done`` in the ``index`` function. .. code-block:: javascript @@ -102,15 +123,23 @@ If ``ParentMojit`` above is the parent of ``ChildMojit``, the controller of ``Ch }; }, '0.1.0', {requires: []}); -Displaying Child Mojits in View -############################### -After the controller of the parent mojit calls ``ac.composite.done``, its template then has access to the content created by the child mojits. -The template of the parent mojit can use Handlebars expressions to embed the output from the child mojits. For example, if the child mojit instance -``footer`` was defined in ``application.json``, the template of the parent mojit could use ``{{{footer}}}`` to embed the content created +.. _mojito_composite-child_view: + +Displaying Child Mojits in View +=============================== + +After the controller of the parent mojit calls ``ac.composite.done``, its +template then has access to the content created by the child mojits. The +template of the parent mojit can use Handlebars expressions to embed the +output from the child mojits. For example, if the child mojit instance +``footer`` was defined in ``application.json``, the template of the parent +mojit could use ``{{{footer}}}`` to embed the content created by ``footer``. -In the example ``index`` template of the parent mojit below, the rendered ``index`` templates of the child mojits ``nav``, ``body``, ``footer`` are embedded using Handlebars expressions. +In the example ``index`` template of the parent mojit below, the rendered +``index`` templates of the child mojits ``nav``, ``body``, ``footer`` +are embedded using Handlebars expressions. .. code-block:: html @@ -121,13 +150,19 @@ In the example ``index`` template of the parent mojit below, the rendered ``inde <div class="body" style="border: dashed black 1px; margin: 10px 10px 10px 10px;">{{{body}}}</div> <div class="footer" style="border: dashed black 1px; margin: 10px 10px 10px 10px;">{{{footer}}}</div> </div> + +.. _mojito_composite-dyn_define: Dynamically Defining Child Mojits -################################# - -In some cases, the parent mojit won't know the children specs until runtime. For example, the specs of the children might depend on the results of a -Web service call. In such cases, your controller can generate the equivalent of the ``config`` object and a callback, which are then passed -to ``ac.composite.execute``. Using ``ac.composite.execute`` lets you run dynamically defined child mojits. -See `Running Dynamically Defined Mojit Instances <./mojito_run_dyn_defined_mojits.html>`_ for more information. +================================= + +In some cases, the parent mojit won't know the children specs until runtime. For +example, the specs of the children might depend on the results of a +Web service call. In such cases, your controller can generate the equivalent +of the ``config`` object and a callback, which are then passed to +``ac.composite.execute``. Using ``ac.composite.execute`` lets you run +dynamically defined child mojits. See +`Running Dynamically Defined Mojit Instances <./mojito_run_dyn_defined_mojits.html>`_ +for more information. diff --git a/docs/dev_guide/topics/mojito_data.rst b/docs/dev_guide/topics/mojito_data.rst index 969bf109f..0c0f1a11e 100644 --- a/docs/dev_guide/topics/mojito_data.rst +++ b/docs/dev_guide/topics/mojito_data.rst @@ -1,34 +1,48 @@ - - ============================= Getting Input and Cookie Data ============================= + +.. _mojito_data-intro: + Introduction -############ +============ -Mojito provides addons for accessing data from query string and routing parameters, cookies, and the POST request body. +Mojito provides addons for accessing data from query string and routing +parameters, cookies, and the POST request body. -This section will provide an overview of the following addons that allow you to access data: +This section will provide an overview of the following addons that allow you +to access data: - `Params addon <../../api/classes/Params.common.html>`_ - `Cookies addon <../../api/classes/Cookie.server.html>`_ -To see examples using these addons to get data, see `Using Query Parameters <../code_exs/query_params.html>`_ and `Using Cookies <../code_exs/cookies.html>`_. +To see examples using these addons to get data, see +`Using Query Parameters <../code_exs/query_params.html>`_ and +`Using Cookies <../code_exs/cookies.html>`_. + + +.. _mojito_data-params: Getting Data from Parameters -############################ +============================ -The methods in the Params addon are called from the ``params`` namespace. As a result, the call will have the following syntax where ``ac`` is the +The methods in the Params addon are called from the ``params`` namespace. +As a result, the call will have the following syntax where ``ac`` is the ActionContext object: ``ac.params.*`` +.. _mojito_data-params_get: + GET -=== +--- -The GET parameters are the URL query string parameters. The Params addon creates JSON using the URL query string parameters. The method ``getFromUrl`` -allows you to specify a GET parameter or get all of the GET parameters. You can also use the alias ``url`` to get URL query string parameters. +The GET parameters are the URL query string parameters. The Params addon +creates JSON using the URL query string parameters. The method ``getFromUrl`` +allows you to specify a GET parameter or get all of the GET parameters. You +can also use the alias ``url`` to get URL query string parameters. -For example, for the URL ``http://www.yahoo.com?foo=1&bar=2``, the Params addon would create the following object: +For example, for the URL ``http://www.yahoo.com?foo=1&bar=2``, the Params +addon would create the following object: .. code-block:: javascript @@ -37,20 +51,22 @@ For example, for the URL ``http://www.yahoo.com?foo=1&bar=2``, the Params addon bar: 2 } + +.. _data_params-get_single: + Single Parameter ----------------- +################ -To get the value for a specific parameter, you pass the key to the ``getFromUrl`` method, which returns the associated value. +To get the value for a specific parameter, you pass the key to the ``getFromUrl`` +method, which returns the associated value. -In the example controller below, the value for the ``name`` query string parameter is retrieved: +In the example controller below, the value for the ``name`` query string +parameter is retrieved: .. code-block:: javascript YUI.add('ParamsMojit', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, getNameParam: function(actionContext) { var nameParam = actionContext.params.getFromUrl('name'); actionContext.done( @@ -59,23 +75,24 @@ In the example controller below, the value for the ``name`` query string paramet }, } } - }, '0.0.1', {requires: []}); + }, '0.0.1', {requires: ['mojito-params-addon']}); + + +.. _data_params-get_all: All Parameters --------------- +############## -To get all of the query string parameters, you call ``getFromUrl`` or its alias ``url`` without passing a key as a parameter. +To get all of the query string parameters, you call ``getFromUrl`` or its alias +``url`` without passing a key as a parameter. -In this example controller, all of the query string parameter are stored in the ``qs_params`` array, which ``ac.done`` makes available in -the template. +In this example controller, all of the query string parameter are stored in +the ``qs_params`` array, which ``ac.done`` makes available in the template. .. code-block:: javascript YUI.add('ParamsMojit', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, ... getAllParams: function(actionContext) { var qs_params = []; @@ -89,29 +106,35 @@ the template. }, } } - }, '0.0.1', {requires: []}); + }, '0.0.1', {requires: ['mojito-params-addon']}); + + +.. _mojito_data-params_post: POST -==== +---- + +The POST parameters come from the HTTP POST request body and often consist of +form data. As with query string parameters, the Params addon has the method +``getFromBody`` that allows you to specify a single parameter or get all of +the POST body parameters. -The POST parameters come from the HTTP POST request body and often consist of form data. As with query string parameters, the Params addon has the -method ``getFromBody`` that allows you to specify a single parameter or get all of the POST body parameters. +.. _data_params-post_single: Single ------- +###### -To get a parameter from the POST body, call ``getFromBody`` with the key as the parameter. You can also use the alias ``body`` to get a parameter -from the POST body. +To get a parameter from the POST body, call ``getFromBody`` with the key as the +parameter. You can also use the alias ``body`` to get a parameter from the POST +body. -In the example controller below, the POST body parameter ``name`` is retrieved and then uses the ``done`` method to make it accessible to the template. +In the example controller below, the POST body parameter ``name`` is retrieved +and then uses the ``done`` method to make it accessible to the template. .. code-block:: javascript YUI.add('ParamsMojit', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, getPostName: function(actionContext) { var postName = actionContext.params.getFromBody('name'); actionContext.done( @@ -120,23 +143,25 @@ In the example controller below, the POST body parameter ``name`` is retrieved a }); } } - }, '0.0.1', {requires: []}); + }, '0.0.1', {requires: ['mojito-params-addon']}); + + +.. _data_params-post_all: All ---- +### -To get all of the parameters from the POST body, call ``getFromBody`` or ``body`` without any parameters. +To get all of the parameters from the POST body, call ``getFromBody`` or ``body`` +without any parameters. -In the example controller below, ``getFromBody`` gets all of the POST body parameters, which are then stored in an array and made accessible to the view +In the example controller below, ``getFromBody`` gets all of the POST body +parameters, which are then stored in an array and made accessible to the view template. .. code-block:: javascript YUI.add('ParamsMojit', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, ... getAllParams: function(actionContext) { var post_params = []; @@ -151,23 +176,33 @@ template. ) } } - }, '0.0.1', {requires: []}); + }, '0.0.1', {requires: ['mojito-params-addon']}); + + +.. _mojito_data-routing: Routing ======= -Routing parameters are mapped to routing paths, actions, and HTTP methods. You can use the routing parameters to provide data to mojit actions when +Routing parameters are mapped to routing paths, actions, and HTTP methods. +You can use the routing parameters to provide data to mojit actions when specific routing conditions have been met. +.. _data_routing-set: + Setting Routing Parameters -------------------------- -The routing parameters are set in the routing configuration file ``routes.json``. For each defined route, you can use the ``params`` property to set -routing parameters. Because ``routes.json`` allows you to specify mojit actions for different paths and HTTP methods, you can set routing parameters -based on the routing configuration. +The routing parameters are set in the routing configuration file +``routes.json``. For each defined route, you can use the ``params`` +property to set routing parameters. Because ``routes.json`` allows you +to specify mojit actions for different paths and HTTP methods, you can +set routing parameters based on the routing configuration. -For instance, in the ``routes.json`` below, the routing parameter ``coupon`` is ``true`` when a POST call is made on the ``/coupon/form``, but when a -GET call is made on the same path, ``coupon`` is ``false``. The ``coupon`` parameter could be used by the mojit controller to do something such as give +For instance, in the ``routes.json`` below, the routing parameter ``coupon`` +is ``true`` when a POST call is made on the ``/coupon/form``, but when a +GET call is made on the same path, ``coupon`` is ``false``. The ``coupon`` +parameter could be used by the mojit controller to do something such as give a coupon to a user posting information. .. code-block:: javascript @@ -190,25 +225,32 @@ a coupon to a user posting information. } ] + +.. _data_routing-get: + Getting Routing Parameters -------------------------- -The Params addon has the method ``getFromRoutes`` that allows you to specify a single parameter or get all of the -routing parameters. You can also use the alias ``route`` to get routing parameters. + +The Params addon has the method ``getFromRoutes`` that allows you to specify +a single parameter or get all of the routing parameters. You can also use +the alias ``route`` to get routing parameters. + +.. _data_routing-get_single: Single -~~~~~~ -To get a routing parameter, call ``getFromRoute`` with the key as the parameter. +###### -In the example controller below, the routing parameter ``coupon`` is used to determine whether the user gets a coupon. +To get a routing parameter, call ``getFromRoute`` with the key as the +parameter. + +In the example controller below, the routing parameter ``coupon`` is used +to determine whether the user gets a coupon. .. code-block:: javascript YUI.add('CouponMojit', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, index: function(actionContext) { var sendCoupon = actionContext.params.getFromRoute('coupon'); var name = actionContext.params.getFromBody("name"); @@ -223,22 +265,25 @@ In the example controller below, the routing parameter ``coupon`` is used to det }); } } - }, '0.0.1', {requires: []}); + }, '0.0.1', {requires: ['mojito-params-addon']}); + + + +.. _data_routing-get_all: All -~~~ +### -To get all of the routing parameters, call ``getFromRoute`` or ``route`` without any arguments. +To get all of the routing parameters, call ``getFromRoute`` or ``route`` without +any arguments. -In the example controller below, all of the routing routing parameters to create a URL. +In the example controller below, all of the routing routing parameters to create +a URL. .. code-block:: javascript YUI.add('LinkMojit', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, index: function(actionContext) { var routeParams = actionContext.params.getFromRoute(); var submitUrl = actionContext.url.make("myMojit", 'submit', routeParams); @@ -248,13 +293,17 @@ In the example controller below, all of the routing routing parameters to create }); } } - }, '0.0.1', {requires: []}); + }, '0.0.1', {requires: ['mojito-params-addon', 'mojito-url-addon']}); + +.. _mojito_data-get_all: Getting All Parameters ====================== -The Params addon also has the method ``getFromMerged`` that lets you get one or all of the GET, POST, and routing parameters. Because all of the -parameters are merged into one collection, one parameter might be overridden by another with the same key. You can also use the alias ``merged`` to +The Params addon also has the method ``getFromMerged`` that lets you get one or +all of the GET, POST, and routing parameters. Because all of the parameters are +merged into one collection, one parameter might be overridden by another with +the same key. You can also use the alias ``merged`` to get one or all of the GET, POST, and routing parameters. Thus, the parameter types are given the following priority: @@ -263,22 +312,25 @@ Thus, the parameter types are given the following priority: #. GET parameters #. POST parameters -For example, if each parameter type has a ``foo`` key, the ``foo`` routing parameter will override both the GET and POST ``foo`` parameters. + +For example, if each parameter type has a ``foo`` key, the ``foo`` routing +parameter will override both the GET and POST ``foo`` parameters. + +.. _mojito_data-get_single: Single ------ -To get one of any of the different type of parameters, call ``getFromMerged`` or ``merged`` with the key as the parameter. +To get one of any of the different type of parameters, call ``getFromMerged`` +or ``merged`` with the key as the parameter. -In the example controller below, the ``name`` parameter is obtained using ``getFromMerged``. +In the example controller below, the ``name`` parameter is obtained using +``getFromMerged``. .. code-block:: javascript YUI.add('MergedParamsMojit', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, getPostName: function(actionContext) { var mergedName = actionContext.params.getFromMerged('name'); actionContext.done( @@ -287,20 +339,21 @@ In the example controller below, the ``name`` parameter is obtained using ``getF }); } } - }, '0.0.1', {requires: []}); + }, '0.0.1', {requires: ['mojito-params-addon']}); + + +.. _mojito_data-get_all: All --- -To get all of the GET, POST, and routing parameters, call ``getFromMerged`` or ``merged`` without any arguments. +To get all of the GET, POST, and routing parameters, call ``getFromMerged`` or +``merged`` without any arguments. .. code-block:: javascript YUI.add('MergedParamsMojit', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, ... getAllParams: function(actionContext) { var all_params = []; @@ -315,64 +368,71 @@ To get all of the GET, POST, and routing parameters, call ``getFromMerged`` or ` ) } } - }, '0.0.1', {requires: []}); + }, '0.0.1', {requires: ['mojito-params-addon']}); + + +.. _mojito_data-cookie: Cookies ======= -The `Cookies addon <../../api/classes/Cookie.server.html>`_ offers methods for reading and writing cookies. The API of the Cookie addon is the same as -the `YUI 3 Cookie Utility <http://yuilibrary.com/yui/docs/api/classes/Cookie.html>`_. For a code example showing how to use the Cookies addon, +The `Cookies addon <../../api/classes/Cookie.server.html>`_ offers methods for +reading and writing cookies. The API of the Cookie addon is the same as +the `YUI 3 Cookie Utility <http://yuilibrary.com/yui/docs/api/classes/Cookie.html>`_. +For a code example showing how to use the Cookies addon, see `Using Cookies <../code_exs/cookies.html>`_. +.. _data_cookie-get: + Getting Cookie Data ------------------- -The method ``cookie.get(name)`` is used to get the cookie value associated with ``name``. In the example controller below, the cookie value -for ``'user'`` is obtained and then used to pass user information to the template. +The method ``cookie.get(name)`` is used to get the cookie value associated +with ``name``. In the example controller below, the cookie value +for ``'user'`` is obtained and then used to pass user information to the +template. .. code-block:: javascript YUI.add('CookieMojit', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, - index: function(actionContext) { - var user = actionContext.cookie.get('user'); - actionContext.done( - { - user: user && users[user] ? users[user] : "" - } - ); + index: function(actionContext) { + var user = actionContext.cookie.get('user'); + actionContext.done( + { + user: user && users[user] ? users[user] : "" + } + ); + } } } - }, '0.0.1', {requires: []}); + }, '0.0.1', {requires: ['mojito-cookies-addon']}); + +.. _data_cookies-write: Writing Data to Cookies ----------------------- -The method ``cookie.set(name, value)`` is used to set a cookie with the a given name and value. The following example controller sets a cookie +The method ``cookie.set(name, value)`` is used to set a cookie with the a +given name and value. The following example controller sets a cookie with the name ``'user'`` if one does not exist. .. code-block:: javascript YUI.add('CookieMojit', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, - index: function(actionContext) { - var user = actionContext.cookie.get('user'); - if(!user){ + index: function(actionContext) { + var user = actionContext.cookie.get('user'); + if(!user){ actionContext.cookie.set('user',(new Date).getTime()); - } - actionContext.done( + } + actionContext.done( { user: user } ); - } + } } - }, '0.0.1', {requires: []}); + }, '0.0.1', {requires: ['mojito-cookies-addon']}); diff --git a/docs/dev_guide/topics/mojito_extensions.rst b/docs/dev_guide/topics/mojito_extensions.rst index 380908790..410789840 100644 --- a/docs/dev_guide/topics/mojito_extensions.rst +++ b/docs/dev_guide/topics/mojito_extensions.rst @@ -2,44 +2,58 @@ Extending Mojito ================ +.. _mojito_extending-intro: + Introduction -############ +============ -The Mojito framework lets you add features and extend functionality through addons, libraries, and -middleware. This chapter discusses how to create extensions and where to place files in the Mojito +The Mojito framework lets you add features and extend functionality through +addons, libraries, and middleware. This chapter discusses how to create +extensions and where to place files in the Mojito framework. +.. _mojito_extending-addons: + Addons -###### +====== -In addition to the `Action Context <../../api/classes/ActionContext.html>`_ addons that Mojito -provides, you can create your own addons to add functionality to controller actions. +In addition to the `Action Context <../../api/classes/ActionContext.html>`_ +addons that Mojito provides, you can create your own addons to add functionality +to controller actions. Addons allows you to do the following: - wrap third-party Node.js libraries and YUI libraries - inspect the content of the ``ActionContext`` object -- call methods on the ``ActionContext`` object, which can be references through ``this.get('host')`` +- call methods on the ``ActionContext`` object, which can be references through + ``this.get('host')`` .. _addons-creating: Creating New Addons -=================== +------------------- + +An addon is simply a JavaScript files that contains a YUI module. You can create +addons at the application and mojit level. Application-level addons are +available to all mojits in the application, whereas, mojit-level addons are +only available to its mojit. -An addon is simply a JavaScript files that contains a YUI module. You can create addons at the -application and mojit level. Application-level addons are available to all mojits in the application, -whereas, mojit-level addons are only available to its mojit. +.. _extending_addons-naming: Naming Convention ------------------ +################# -The name of an addon should have the following syntax where ``{addon_name}`` is a unique YUI module -name defined by the user and ``{affinity}`` is ``server``, ``common``, or ``client``. +The name of an addon should have the following syntax where ``{addon_name}`` +is a unique YUI module name defined by the user and ``{affinity}`` is +``server``, ``common``, or ``client``. ``{addon_name}.{affinity}.js`` + +.. _extending_addons-loc: + Location of Addons ------------------- +################## Application-level addons should be placed in the following directory: @@ -49,28 +63,48 @@ Mojit-level addons should be placed in the following directory: ``{mojit_dir}/addons/ac/`` + +.. _extending_addons-namespace: + +Namespace of Addon +################## + +To access addon methods and properties, you use the Action Context object +(i.e., ``ac``) and the addon namespace. The namespace of an addon must be +the same as ``{addon_name}`` in the addon file name ``{addon_name}.{affinity}.js``. +For example, the namespace of the addon ``addons/ac/foo.common.js`` would be +``foo``. Thus, to call the method ``save`` of the ``foo`` addon, you would +use ``ac.foo.save``. + +See :ref:`Using Your Addon <extending_addons-using>` for an example +of how to call methods from the addon namespace. + + +.. _extending_addons-writing: + Writing the Addon ------------------ +################# -The ActionContext is a `YUI Base <http://developer.yahoo.com/yui/3/base/>`_ object, and ActionContext -addons are `YUI Plugins <http://developer.yahoo.com/yui/3/plugin/>`_. To create a new addon, you +The ActionContext is a `YUI Base <http://developer.yahoo.com/yui/3/base/>`_ +object, and ActionContext addons are +`YUI Plugins <http://developer.yahoo.com/yui/3/plugin/>`_. To create a new addon, you write a new YUI Plugin and register it with Mojito. The addon must have the following: - registered plugin name, which is the string passed to ``YUI.add`` - constructor with a ``prototype`` property -- statement assigning the constructor to a namespace of ``Y.mojito.addons.ac``, so Mojito can access - your addon +- statement assigning the constructor to a namespace of ``Y.mojito.addons.ac``, + so Mojito can access your addon **Optional:** ``requires`` array to include other modules. The code snippet below shows the skeleton of an addon with the registered -plugin name (``'addon-ac-cheese'``) and the constructor (``CheeseAcAddon``) with its ``prototype`` -property: +plugin name (``'addon-ac-cheese'``) and the constructor (``CheeseAcAddon``) with its +``prototype`` property: .. code-block:: javascript - // Register the plugin name + // Register the plugin name (must be unique) YUI.add('addon-ac-cheese', function(Y, NAME) { // Constructor for addon function CheeseAcAddon(command, adapter, ac) { @@ -93,12 +127,16 @@ property: // YUI modules if needed. }, '0.0.1', {requires: ['']}); + +.. _extending_addons-writing_ex: + Example Addon -~~~~~~~~~~~~~ +************* -In this example addon, the ``YUI.add`` method registers the ``addon-ac-cheese`` plugin. The addon -has the namespace ``cheese`` and the method ``cheesify``, which is added through the ``prototype`` -property. +In the example addon ``cheese.common.js`` below, the ``YUI.add`` method +registers the ``addon-ac-cheese`` plugin. The addon namespace +is ``cheese``, and the addon has the one method ``cheesify``, which is +added through the ``prototype`` property. .. code-block:: javascript @@ -113,7 +151,9 @@ property. } CheeseAcAddon.prototype = { // The "namespace" is where in the ActionContext - // the user can find this addon. + // the user can find this addon. The namespace + // must be the same as the first part of the addon file. + // Thus, this addon file must be named 'cheese'.{affinity}.js' namespace: 'cheese', cheesify: function(obj) { var n; @@ -136,22 +176,31 @@ property. Y.mojito.addons.ac.cheddar = CheeseAcAddon; }, '0.0.1', {}); + +.. _extending_addons-using: + Using Your Addon ----------------- +################ + +The addon in `Example Addon`_ registered the plugin ``addon-ac-cheese`` and +made its constructor available through the namespace ``cheese``. This namespace +must match the first part of the addon file name, so the addon file name is +``cheese.common.js``. + +Addons are not automatically added to the ActionContext, so to access an +addon, your controller needs to add the YUI plugin name to the ``requires`` +array. The YUI plugin name is the string passed to ``YUI.add`` in the addon. +To invoke the addon methods, you use the Action Context object (``ac``) with +the addon namespace: ``ac.cheese.{addon_method}`` -The addon in `Example Addon`_ registered the plugin ``addon-ac-cheese`` and made its constructor -available through the namespace ``cheese``. The addons are not automatically added to the -ActionContext, but to access an addon, your controller needs to add the YUI plugin name to the -``requires`` array. The YUI plugin name is the string passed to ``YUI.add`` in the addon. To invoke -the addon methods, call the methods from the namespace defined in the ``prototype`` property of the -addon's constructor. In our addon, we defined the namespace ``cheese`` (``"namespace": "cheese"``). .. code-block:: javascript YUI.add('Foo', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { index: function(ac) { - // Use the type 'cheese' and then the + // Use the namespace defined by the + // addon file name ('cheese') with // the addon function 'cheesify' var cheesy = ac.cheese.cheesify({ food: "nachos", @@ -164,23 +213,30 @@ addon's constructor. In our addon, we defined the namespace ``cheese`` (``"names }, '0.0.1', {requires: [ 'mojito', 'addon-ac-cheese']}); +.. _mojito_extending-middleware: + Middleware -########## +========== + +.. _extending_middleware-intro: Introduction -============ +------------ -Middleware is code that can handle (or modify) the HTTP request in the server. Because Mojito -middleware is based on the HTTP middleware `Connect <http://senchalabs.github.com/connect/>`_, the -code must follow the Connect API. Also, because each piece of middleware is a Node.js module, it +Middleware is code that can handle (or modify) the HTTP request in the server. +Because Mojito middleware is based on the HTTP middleware +`Connect <http://senchalabs.github.com/connect/>`_, the code must follow +the Connect API. Also, because each piece of middleware is a Node.js module, it should use ``module.exports`` to create a function to handle incoming requests. +.. _extending_middleware-configure: + Configuring Middleware -====================== +---------------------- -To use middleware, the path to its code must be listed in the ``middleware`` array in -``application.json``. The path can be marked as relative to the application by prefixing -it with "./". +To use middleware, the path to its code must be listed in the ``middleware`` +array in ``application.json``. The path can be marked as relative to the +application by prefixing it with "./". .. code-block:: javascript @@ -198,17 +254,21 @@ it with "./". } ] +.. _extending_middleware-location: + Location of Middleware -====================== +---------------------- -We suggest that middleware be located in the directory ``{app_dir}/middleware/``, but this is only -a convention and not required. The name of the file is not important. +We suggest that middleware be located in the directory ``{app_dir}/middleware/``, +but this is only a convention and not required. The name of the file is not important. + +.. _extending_middleware-example: Example -======= +------- -The simple example below of middleware intercepts an HTTP request and lowercases URLs containing -the string "module_" before the URLs are received by the server. +The simple example below of middleware intercepts an HTTP request and lowercases +URLs containing the string "module_" before the URLs are received by the server. .. code-block:: javascript @@ -220,31 +280,40 @@ the string "module_" before the URLs are received by the server. }; +.. _mojito_extending-libraries: Libraries -######### +========= + +Mojito allows you to use YUI libraries, external libraries, or customized +libraries. To use any library in Mojito, you need to specify the module in +either the ``requires`` array in the controller for YUI libraries or using +the ``require`` method for Node.js modules. -Mojito allows you to use YUI libraries, external libraries, or customized libraries. To use any -library in Mojito, you need to specify the module in either the ``requires`` array in the controller -for YUI libraries or using the ``require`` method for Node.js modules. +.. _extending_libraries-yui: YUI Library -=========== +----------- +YUI libraries can be made available at the application or the mojit level. +Each file can only have one ``YUI.add`` statement. Other components, such +as controllers, models, etc., needing the library should specify the YUI +module name in the ``requires`` array. -YUI libraries can be made available at the application or the mojit level. Each file can only have -one ``YUI.add`` statement. Other components, such as controllers, models, etc., needing the library -should specify the YUI module name in the ``requires`` array. +.. _libraries_yui-naming: File Naming Convention ----------------------- +###################### -The file name of a YUI module should have the following syntax where ``{yui_mod_name}`` is a unique -YUI module name defined by the user and ``{affinity}`` is ``server``, ``common``, or ``client``. +The file name of a YUI module should have the following syntax where +``{yui_mod_name}`` is a unique YUI module name defined by the user and +``{affinity}`` is ``server``, ``common``, or ``client``. ``{yui_mod_name}.{affinity}.js`` +.. _libraries_yui-loc: + Location of YUI Modules ------------------------ +####################### Application-level YUI modules should be placed in the following directory: @@ -254,8 +323,10 @@ Mojit-level YUI modules should be placed in the following directory: ``{mojit_dir}/autoload/`` +.. _libraries_yui-create: + Creating a YUI Module ---------------------- +##################### To create a YUI module, your code needs to have the following: @@ -263,11 +334,14 @@ To create a YUI module, your code needs to have the following: - constructor for the module - methods created through the ``prototype`` object + +.. _yui_create-add: + Adding the Module to YUI -~~~~~~~~~~~~~~~~~~~~~~~~ +************************ -Your YUI module must have a ``YUI.add`` statement that adds the module to YUI. Below is the basic -syntax of the ``YUI.add`` statement: +Your YUI module must have a ``YUI.add`` statement that adds the module to YUI. +Below is the basic syntax of the ``YUI.add`` statement: ``YUI.add('{module-name', function(Y){ ... }`` @@ -275,24 +349,30 @@ For example, the ``send-photos`` YUI module would use the following: ``YUI.add('send-photos', function(Y){ ... }`` +.. _yui_create-constructor: + Constructor -~~~~~~~~~~~ +*********** -The constructor of a YUI module is basically a new namespace that is assigned a function. The new -namespace is created with the following syntax: +The constructor of a YUI module is basically a new namespace that is assigned a +function. The new namespace is created with the following syntax: ``Y.namespace('mojito').{constructor_name} = function() { ... }`` -For example, to create the constructor ``HELLO`` for a YUI module, you would could use the following: +For example, to create the constructor ``HELLO`` for a YUI module, you would +could use the following: ``Y.namespace('mojito').HELLO = function() { this.greeting="hello"; }`` +.. _yui_create-ex: + Example -~~~~~~~ +******* -In the code example below, the ``create_id`` function becomes the constructor for the ``UID`` -namespace. This will let you create an instance, and the ``prototype`` object then allows you to -access the method ``log`` from that instance. +In the code example below, the ``create_id`` function becomes the constructor +for the ``UID`` namespace. This will let you create an instance, and the +``prototype`` object then allows you to access the method ``log`` from that +instance. .. code-block:: javascript @@ -309,38 +389,44 @@ access the method ``log`` from that instance. Y.namespace('mojito').UID = create_id; }); +.. _libraries_yui-using: + Using the YUI Module --------------------- +#################### -In the example mojit controller below, the YUI module ``hello-uid`` is loaded because the module is -in the ``requires`` array. An instance of the module is created and saved in the ``init`` function. -With the saved instance, the ``log`` method from the ``hello-uid`` module can be called: +In the example mojit controller below, the YUI module ``hello-uid`` is loaded +because the module is in the ``requires`` array. An instance of the module +is created and saved in the ``init`` function. With the saved instance, the +``log`` method from the ``hello-uid`` module can be called: .. code-block:: javascript YUI.add('HelloMojit', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - this.uid = new Y.mojito.UID(); - }, index: function(ac) { var user_name = ac.params.getFromMerged("name") || "User"; - this.uid.log(user_name); + var uid = new Y.mojito.UID(); + uid.log(user_name); ac.done('Hello World!'); } }; }, '0.0.1', {requires: ['hello-uid']}); + +.. _extending_libraries-other: + Other Libraries -=============== +--------------- -Non-YUI libraries can also be used at either the application or mojit level. Because Node.js and -**not** Mojito will read the contents of the library files, you need to use ``require()`` to include -the library. Mojito will only confirm that the files exist. +Non-YUI libraries can also be used at either the application or mojit level. +Because Node.js and **not** Mojito will read the contents of the library files, +you need to use ``require()`` to include the library. Mojito will only confirm +that the files exist. + +.. _libraries_other-loc: Location of Non-YUI Libraries ------------------------------ +############################# Application-level libraries should be placed in the following directory: @@ -350,93 +436,120 @@ Mojit-level libraries should be placed in the following directory: ``{mojit_dir}/libs`` + +.. _mojito_extending-ve: + View Engines -############ +============ + +.. _extending_ve-overview: Overview -======== +-------- -A view engine is the piece of code that takes the data returned by a controller and applies it to a -view. This is most often done by interpreting the view as a template. View engines in Mojito can be -at either the application or mojit level. Application-level view engines are available to all mojits. +A view engine is the piece of code that takes the data returned by a controller +and applies it to a view. This is most often done by interpreting the view as +a template. View engines in Mojito can be at either the application or mojit +level. Application-level view engines are available to all mojits. -The view engine consists of an addon that we will refer to as the view engine addon to differentiate -it from other addons. The view engine addon can include code that renders templates -or use a rendering engine, such as `Embedded JavaScript (EJS) http://embeddedjs.com/>`_, -to render templates. In the latter case, the view engine addon acts as an interface between the +The view engine consists of an addon that we will refer to as the view engine +addon to differentiate it from other addons. The view engine addon can include +code that renders templates or use a rendering engine, such as +`Embedded JavaScript (EJS) http://embeddedjs.com/>`_, to render templates. +In the latter case, the view engine addon acts as an interface between the Mojito framework and the rendering engine. -In the following sections, we will be discussing how to create a view engine addon that relies on -a rendering engine, not how to write code that renders templates. +In the following sections, we will be discussing how to create a view engine +addon that relies on a rendering engine, not how to write code that renders + templates. + +.. _ve_overview-term: Terminology ------------ +########### + +The following list may help clarify the meaning of commonly used terms in this +section. -The following list may help clarify the meaning of commonly used terms in this section. +- **view engine** - The code used to apply data to a view. In Mojito, the view + engine consists of a view engine addon. +- **view engine addon** - The Mojito addon that compiles and renders templates. + The addon typically relies on a rendering engine to compile and render templates, + but may include code to do the compilation and rendering. +- **rendering engine** - The rendering engine is typically an off-the-shelf + technology, such as `Dust <http://akdubya.github.com/dustjs>`_, + `Jade <http://jade-lang.com/>`_, or `EJS <http://embeddedjs.com/>`_, that + renders the template into markup for an HTML page. +- **template** - The template file (chosen by the controller) that contains + tags and HTML that is rendered into markup for an HTML page. -- **view engine** - The code used to apply data to a view. In Mojito, the view engine consists of a - view engine addon. -- **view engine addon** - The Mojito addon that compiles and renders templates. The addon typically - relies on a rendering engine to compile and render templates, but may include code to do the - compilation and rendering. -- **rendering engine** - The rendering engine is typically an off-the-shelf technology, such as - `Dust <http://akdubya.github.com/dustjs>`_, `Jade <http://jade-lang.com/>`_, or - `EJS <http://embeddedjs.com/>`_, that renders the template into markup for an HTML page. -- **template** - The template file (chosen by the controller) that contains tags and HTML that - is rendered into markup for an HTML page. +.. _extending_ve-steps: General Steps for Creating View Engines -======================================= +--------------------------------------- -#. Use ``npm`` to install the rendering engine into your Mojito application or copy it into a - directory such as ``{app_dir}/libs``. -#. Create a view engine addon that references the rendering engine with a ``require`` statement and +#. Use ``npm`` to install the rendering engine into your Mojito application or + copy it into a directory such as ``{app_dir}/libs``. +#. Create a view engine addon that references the rendering engine with a + ``require`` statement and meets the :ref:`requirements of the view engine addon <reqs_ve_addon>`. -#. Create templates using the templates for the rendering engine and place them in - ``{mojit_dir}/views``. - +#. Create templates using the templates for the rendering engine and place + them in ``{mojit_dir}/views``. +.. _extending_ve-naming: File Naming Conventions -======================= +----------------------- + +.. _ve_naming-addon: View Engine Addon ------------------ +################# -The name of the addon should have the following syntax where ``{view_engine_name}`` is the view -engine and ``{affinity}`` is ``server``, ``common``, or ``client``. +The name of the addon should have the following syntax where ``{view_engine_name}`` +is the view engine and ``{affinity}`` is ``server``, ``common``, or ``client``. ``{view_engine_name}.{affinity}.js`` +.. _ve_naming-template: + Template --------- +######## -The name of the template should have the following syntax where ``{view_engine_name}`` should -be the same as the ``{view_engine_name}`` in the file name of the view engine addon. +The name of the template should have the following syntax where +``{view_engine_name}`` should be the same as the ``{view_engine_name}`` in +the file name of the view engine addon. ``{view}.{view_engine_name}.html`` +.. _extending_ve-loc: File Locations -============== +-------------- + +.. _ve_loc-app_level: Application-Level View Engine Addons ------------------------------------- +#################################### ``{app_dir}/addons/view-engines`` +.. _ve_loc-mojit_level: Mojit-Level View Engine Addons ------------------------------- +############################## ``{mojit_dir}/addons/view-engines`` +.. _ve_loc-rendering: + Rendering Engines ------------------ +################# -Mojito does not require rendering engines to be in a specific location. The recommended practice is -to use ``npm`` to install rendering engines into the ``node_modules`` directory or copy the -rendering engine into the ``libs`` directory as shown below: +Mojito does not require rendering engines to be in a specific location. The +recommended practice is to use ``npm`` to install rendering engines into +the ``node_modules`` directory or copy the rendering engine into the ``libs`` +directory as shown below: ``{app_dir}/node_modules/{rendering_engine}`` @@ -444,14 +557,15 @@ rendering engine into the ``libs`` directory as shown below: ``{mojit_dir}/libs/{rendering_engine}}`` -.. note:: If you are using mojit-level view engine addons, the rendering engine should be at the - mojit level as well, such as ``{mojit_dir}/libs/{rendering_engine}``. +.. note:: If you are using mojit-level view engine addons, the rendering engine + should be at the mojit level as well, such as + ``{mojit_dir}/libs/{rendering_engine}``. .. _reqs_ve_addon: Requirements of the View Engine Addon -===================================== +------------------------------------- The view engine addon must have the following: @@ -465,7 +579,8 @@ The view engine addon must have the following: }, '0.1.0', {requires: []}); -- an object that is assigned to ``Y.mojito.addons.viewEngines.{view_engine_name}`` as seen below: +- an object that is assigned to ``Y.mojito.addons.viewEngines.{view_engine_name}`` + as seen below: .. code-block:: javascript @@ -477,7 +592,8 @@ The view engine addon must have the following: ... Y.namespace('mojito.addons.viewEngines').ejs = EjsAdapter; -- a prototype of the object has the following two methods ``render`` and ``compiler`` as shown below: +- a prototype of the object has the following two methods ``render`` and ``compiler`` + as shown below: .. code-block:: javascript @@ -492,87 +608,121 @@ The view engine addon must have the following: ... } ... + - +.. _reqs_ve-methods: + Methods for the View Engine Addon -================================= +--------------------------------- + +.. _ve_methods-render: render ------- +###### + +.. _ve_render-desc: Description -~~~~~~~~~~~ +*********** + +Sends a rendered template as the first argument to the methods ``adapter.flush`` +or ``adapter.done``. -Sends a rendered template as the first argument to the methods ``adapter.flush`` or ``adapter.done``. +.. _ve_render-sig: Signature -~~~~~~~~~ +********* ``render(data, mojitType, tmpl, adapter, meta, more)`` +.. _ve_render-params: + Parameters -~~~~~~~~~~ +********** - ``data`` (Object) - the data to render. - ``mojitType`` (String) - the mojit whose view is being rendered. - ``tmpl`` - (String) - path to template to render. - ``adapter`` (Object) - the output adapter to use. -- ``meta`` (Object) - the metadata that should be passed as the second argument to ``adapter.flush`` +- ``meta`` (Object) - the metadata that should be passed as the second argument + to ``adapter.flush`` or ``adapter.done`` -- ``more`` (Boolean) - if ``true``, the addon should call the method ``adapter.flush``, and if - ``false``, call the method ``adapter.done``. +- ``more`` (Boolean) - if ``true``, the addon should call the method + ``adapter.flush``, and if ``false``, call the method ``adapter.done``. + +.. _ve_render-return: Return -~~~~~~ +****** None +.. _ve_methods-compiler: compiler --------- +######## + +.. _ve_compiler-desc: Description -~~~~~~~~~~~ -Returns the compiled template. The ``compiler`` method is only used when you run the following -command: ``mojito compile views`` +********** + +Returns the compiled template. The ``compiler`` method is only used when you +run the following command: ``mojito compile views`` + +.. _ve_compiler-sig: Signature -~~~~~~~~~ +********* ``compile(tmpl)`` +.. _ve_compiler-params: + Parameters -~~~~~~~~~~ +********** - ``tmpl`` (String) - path to the template that is to be rendered + +.. _ve_compiler-return: + Return -~~~~~~ +****** ``String`` - compiled template +.. _ve_engine_view: + View Engine Addon and Its View -============================== +------------------------------ -A naming convention associates a view engine and its templates. For example, the view engine -``{mojit_dir}/addons/view-engines/big_engine.server.js`` will be used to render the template -``{mojit_dir}/views/foo.big_engine.html``. Having two templates that only differ by the view -engine will cause an error because Mojito will not be able to decide which view engine to use +A naming convention associates a view engine and its templates. For example, +the view engine ``{mojit_dir}/addons/view-engines/big_engine.server.js`` will +be used to render the template ``{mojit_dir}/views/foo.big_engine.html``. +Having two templates that only differ by the view engine will cause an error +because Mojito will not be able to decide which view engine to use (which to prioritize above the other) to render the template. +.. _ve_engine_ex: + Example -======= +------- + +.. _ve_engine_ex-ejs: Embedded JavaScript (EJS) -------------------------- +######################### The following example is of the `EJS view engine <http://embeddedjs.com/>`_. +.. _ve_engine_ex-ejs_engine: + EJS Rendering Engine -#################### +******************** -You install ``ejs`` locally with ``npm`` so that the EJS rendering engine is installed in -the ``node_modules`` directory as seen below: +You install ``ejs`` locally with ``npm`` so that the EJS rendering engine is +installed in the ``node_modules`` directory as seen below: :: @@ -592,9 +742,10 @@ the ``node_modules`` directory as seen below: ├── support └── test +.. _ve_engine_ex-ejs_addon: View Engine Addon -################# +***************** ``{app_dir}/addons/view-engines/ejs.server.js`` @@ -635,8 +786,10 @@ View Engine Addon }, '0.1.0', {requires: []}); +.. _ve_engine_ex-ejs_template: + Template -######## +******** ``{app_dir}/mojits/{mojit_name}/views/foo.ejs.html`` diff --git a/docs/dev_guide/topics/mojito_framework_mojits.rst b/docs/dev_guide/topics/mojito_frame_mojits.rst similarity index 58% rename from docs/dev_guide/topics/mojito_framework_mojits.rst rename to docs/dev_guide/topics/mojito_frame_mojits.rst index 2a298344e..b0ec8db7d 100644 --- a/docs/dev_guide/topics/mojito_framework_mojits.rst +++ b/docs/dev_guide/topics/mojito_frame_mojits.rst @@ -1,43 +1,54 @@ -================ -Framework Mojits -================ +============ +Frame Mojits +============ + +.. _mojito_fw_mojits-intro: Introduction -############ +============ + +Mojito comes with the built-in utility mojits that make developing applications easier. +Mojito currently comes with the ``HTMLFrameMojit`` that constructs Web pages from the +skeleton HTML to the styling and content and the ``LazyLoadMojit`` that allows you to +lazily load mojit code. Mojito plans to offer additional frame mojits in the future. -Mojito comes with the built-in utility mojits that make developing applications easier. Mojito -currently comes with the ``HTMLFrameMojit`` that constructs Web pages from the skeleton HTML to the -styling and content and the ``LazyLoadMojit`` that allows you to lazily load mojit code. Mojito -plans to offer additional framework mojits in the future. + +.. _mojito_fw_mojits-htmlframe: HTMLFrameMojit -############## +============== -The ``HTMLFrameMojit`` builds the HTML skeleton of a Web page. When you use ``HTMLFrameMojit`` the -``<html>``, ``<head>``, and ``<body>`` elements are automatically created and the content from child -mojits are inserted into the ``<body>`` element. The ``HTMLFrameMojit`` can also automatically -insert assets such as CSS and JavaScript files into either the ``<head>`` or ``<body>`` elements. +The ``HTMLFrameMojit`` builds the HTML skeleton of a Web page. When you use +``HTMLFrameMojit`` the ``<html>``, ``<head>``, and ``<body>`` elements are automatically +created and the content from child mojits are inserted into the ``<body>`` element. +The ``HTMLFrameMojit`` can also automatically insert assets such as CSS and JavaScript +files into either the ``<head>`` or ``<body>`` elements. -Because it builds the Web page from the framework to the content and styling, the ``HTMLFrameMojit`` -must be the top-level mojit in a Mojito application. As the top-level or parent mojit, the -``HTMLFrameMojit`` may have one or more child mojits. +Because it builds the Web page from the framework to the content and styling, the +``HTMLFrameMojit`` must be the top-level mojit in a Mojito application. As the top-level +or parent mojit, the ``HTMLFrameMojit`` may have one or more child mojits. To create a Mojito application that uses the ``HTMLFrameMojit``, see the code examples `Using the HTML Frame Mojit <../code_exs/htmlframe_view.html>`_ and `Attaching Assets with HTMLFrameMojit <../code_exs/framed_assets.html>`_. +.. _fw_mojits_htmlframe-config: + Configuration -============= +------------- -As with defining instances of other mojit types, you define an instance of the ``HTMLFrameMojit`` in -`configuration object <../intro/mojito_configuring.html#configuration-object>`_ of -``application.json``. Because ``HTMLFrameMojit`` must be the top-level mojit, its instance cannot -have a parent instance, but may have one or more child instances. +As with defining instances of other mojit types, you define an instance of the +``HTMLFrameMojit`` in +`configuration object <../intro/mojito_configuring.html#configuration-object>`_ +of ``application.json``. Because ``HTMLFrameMojit`` must be the top-level mojit, +its instance cannot have a parent instance, but may have one or more child +instances. -In the example ``application.json`` below, ``frame`` is an instance of ``HTMLFrameMojit`` that has -the ``child`` instance of the ``framed`` mojit. After the HTML skeleton is created, -the ``HTMLFrameMojit`` will insert the value of the ``title`` property into the ``<title>`` element -and the content created by the ``frame`` mojit into the ``<body>`` element. +In the example ``application.json`` below, ``frame`` is an instance of +``HTMLFrameMojit`` that has the ``child`` instance of the ``framed`` mojit. +After the HTML skeleton is created, the ``HTMLFrameMojit`` will insert the +value of the ``title`` property into the ``<title>`` element and the content +created by the ``frame`` mojit into the ``<body>`` element. .. code-block:: javascript @@ -58,10 +69,10 @@ and the content created by the ``frame`` mojit into the ``<body>`` element. } ] -To have multiple child instances, the ``HTMLFrameMojit`` instance must use the ``children`` object -to specify the child instances. In this example ``application.json``, the ``page`` instance of -``HTMLFrameMojit`` uses the ``children`` object to specify three child instances that can create -content for the rendered view. +To have multiple child instances, the ``HTMLFrameMojit`` instance must use the +``children`` object to specify the child instances. In this example ``application.json``, +the ``page`` instance of ``HTMLFrameMojit`` uses the ``children`` object to specify three +child instances that can create content for the rendered view. .. code-block:: javascript @@ -96,8 +107,11 @@ content for the rendered view. } ] + +.. _htmlframe_config-deploy: + Deploying to Client -=================== +################### To configure Mojito to deploy code to the client, you set the ``deploy`` property of the `config <../intro/mojito_configuring.html#configuration-object>`_ object to ``true`` @@ -122,31 +136,39 @@ as shown below. } ] + +.. _config_deploy-what: + What Gets Deployed? -------------------- +******************* The following is deployed to the client: - Mojito framework - binders (and their dependencies) -When a binder invokes its controller, if the controller has the ``client`` or ``common`` affinity, -then the controller and its dependencies are deployed to the client as well. If the affinity of the -controller is ``server``, the invocation occurs on the server. In either case, the binder is able to -transparently invoke the controller. +When a binder invokes its controller, if the controller has the ``client`` or ``common`` +affinity, then the controller and its dependencies are deployed to the client as well. If +the affinity of the controller is ``server``, the invocation occurs on the server. In +either case, the binder is able to transparently invoke the controller. + +.. _htmlframemojit-assets: Adding Assets with HTMLFrameMojit -================================= +--------------------------------- -You specify the assets for ``HTMLFrameMojit`` just as you would specify assets for any mojit. The -basic difference is that ``HTMLFrameMojit`` will automatically attach ``<link>`` elements for CSS -and ``<script>`` elements for JavaScript files to the HTML page. When using assets with other mojits, -you have to manually add ``<link>`` elements that refer to assets to templates. See -`Assets <./mojito_assets.html>`_ for general information about using assets in Mojito. +You specify the assets for ``HTMLFrameMojit`` just as you would specify assets +for any mojit. The basic difference is that ``HTMLFrameMojit`` will +automatically attach ``<link>`` elements for CSS and ``<script>`` elements +for JavaScript files to the HTML page. When using assets with other mojits, +you have to manually add ``<link>`` elements that refer to assets to templates. +See `Assets <./mojito_assets.html>`_ for general information about using +assets in Mojito. -In the example ``application.json`` below, the ``HTMLFrameMojit`` instance ``frame`` has one child -mojit with a CSS asset. Because the assets are listed in the ``top`` object, the ``HTMLFrameMojit`` -will attach the ``<link>`` element pointing to ``index.css`` to the ``<head>`` element. +In the example ``application.json`` below, the ``HTMLFrameMojit`` instance +``frame`` has one child mojit with a CSS asset. Because the assets are +listed in the ``top`` object, the ``HTMLFrameMojit`` will attach the ``<link>`` +element pointing to ``index.css`` to the ``<head>`` element. .. code-block:: javascript @@ -173,8 +195,8 @@ will attach the ``<link>`` element pointing to ``index.css`` to the ``<head>`` e } ] -The rendered view that was constructed by the ``HTMLFrameMojit`` should look similar to the HTML -below. +The rendered view that was constructed by the ``HTMLFrameMojit`` should look +similar to the HTML below. .. code-block:: html @@ -193,25 +215,41 @@ below. </body> </html> + +.. _mojito_fw_mojits-lazyloadmojit: + +LazyLoadMojit +============= + +``LazyLoadMojit`` allows you to defer the loading of a mojit instance by first +dispatching the ``LazyLoadMoit`` as a proxy to the client. From the client, +``LazyLoadMojit`` can then request Mojito to load the proxied mojit. This allows +your Mojito application to load the page quickly and then lazily load parts of +the page. + +.. _mojito_fw_mojits-lazyload: + LazyLoadMojit -############# +============= + -``LazyLoadMojit`` allows you to defer the loading of a mojit instance by first dispatching the -``LazyLoadMoit`` as a proxy to the client. From the client, ``LazyLoadMojit`` can then request -Mojito to load the proxied mojit. This allows your Mojito application to load the page quickly and -then lazily load parts of the page. +.. _fw_mojits_lazyload-how: How Does It Work? -================= +----------------- -The ``LazyLoadMojit`` is really a proxy mojit that dispatches its binder and an empty DOM node to -the client. From the client, the binder sends a request to the controller to execute the code of -the proxied (original) mojit. The output from the executed mojit is then returned to the binder of -the ``LazyLoadMojit``, which attaches the output to the empty DOM node. The binder of -``LazyLoadMojit`` destroys itself, leaving the DOM intact with the new content. +The ``LazyLoadMojit`` is really a proxy mojit that dispatches its binder and an +empty DOM node to the client. From the client, the binder sends a request to the +controller to execute the code of the proxied (original) mojit. The output from +the executed mojit is then returned to the binder of the ``LazyLoadMojit``, which +attaches the output to the empty DOM node. The binder of ``LazyLoadMojit`` destroys +itself, leaving the DOM intact with the new content. + + +.. _fw_mojits_lazyload-config: Configuring Lazy Loading -======================== +------------------------ To use the ``LazyLoadMojit``, the ``application.json`` must do the following: @@ -220,9 +258,9 @@ To use the ``LazyLoadMojit``, the ``application.json`` must do the following: - create a container mojit that has children mojit instances (``"children": { ... }``) - defer the dispatch of the mojit instance that will be lazily loaded (``"defer": true``) -In the example ``application.json`` below, the child mojit instance ``myLazyMojit`` is configured to -be lazily loaded. The action (``hello``) of the proxied mojit is also configured to be executed -after lazy loading is complete. +In the example ``application.json`` below, the child mojit instance ``myLazyMojit`` is +configured to be lazily loaded. The action (``hello``) of the proxied mojit is also +configured to be executed after lazy loading is complete. .. code-block:: javascript @@ -255,31 +293,40 @@ after lazy loading is complete. } ] + +.. _fw_mojits_lazyload-ex: + Example -======= +------- -This example shows you application configuration as well as the code for the parent mojit and the -child mojit that is lazy loaded. If you were to run this lazy load example, you would see the -content of the parent mojit first and then see the child mojit's output loaded in the page. +This example shows you application configuration as well as the code for the +parent mojit and the child mojit that is lazy loaded. If you were to run +this lazy load example, you would see the content of the parent mojit first +and then see the child mojit's output loaded in the page. + + +.. _lazyload_ex-app_config: Application Configuration -------------------------- +######################### -The application configuration for this example (shown below) meets the requirements for using -``LazyLoadMojit``: +The application configuration for this example (shown below) meets the +requirements for using ``LazyLoadMojit``: - creates the ``frame`` mojit instance of type ``HTMLFrameMojit`` -- sets ``"deploy"`` to ``true`` for ``frame`` so that the code is deployed to the client -- creates the ``child`` mojit instance that has the ``children`` object specifying child mojit - instance -- configures the ``myLazyMojit`` instance to defer being dispatched, which causes it to be lazily - loaded by ``LazyLoadMojit`` - -In this ``application.json``, the ``parent`` mojit instance has the one child ``myLazyMojit``. -The ``myLazyMojit`` mojit instance of type ``LazyChild`` is the mojit that will be lazily loaded by -``LazyLoadMojit``. In a production application, you could configure the application to have many -child instances that are lazily loaded after the parent mojit instance is already loaded onto the -page. +- sets ``"deploy"`` to ``true`` for ``frame`` so that the code is deployed + to the client +- creates the ``child`` mojit instance that has the ``children`` object + specifying child mojit instance +- configures the ``myLazyMojit`` instance to defer being dispatched, which + causes it to be lazily loaded by ``LazyLoadMojit`` + +In this ``application.json``, the ``parent`` mojit instance has the one child +``myLazyMojit``. The ``myLazyMojit`` mojit instance of type ``LazyChild`` is +the mojit that will be lazily loaded by ``LazyLoadMojit``. In a production +application, you could configure the application to have many child instances +that are lazily loaded after the parent mojit instance is already loaded onto +the page. .. code-block:: javascript @@ -309,8 +356,11 @@ page. } ] + +.. _lazyload_ex-container_mojit: + Container Mojit ---------------- +############### The ``Container`` mojit uses ``ac.composite.done`` to execute its child mojits. @@ -328,11 +378,12 @@ The ``Container`` mojit uses ``ac.composite.done`` to execute its child mojits. ac.composite.done(); } }; - }, '0.0.1', {requires: ['mojito']}); + }, '0.0.1', {requires: ['mojito', 'mojito-composite-addon']}); -Instead of waiting for the child mojit to execute, the partially rendered view of the ``Container`` -mojit is immediately sent to the client. After the child mojit is lazily loaded, the content of the -executed child replaces the Handlebars expression ``{{{myLazyMojit}}}``. +Instead of waiting for the child mojit to execute, the partially rendered view +of the ``Container`` mojit is immediately sent to the client. After the child +mojit is lazily loaded, the content of the executed child replaces the Handlebars +expression ``{{{myLazyMojit}}}``. .. code-block:: html @@ -343,12 +394,15 @@ executed child replaces the Handlebars expression ``{{{myLazyMojit}}}``. <hr/> </div> + +.. _lazyload_ex-lazychild_mojit: + LazyChild Mojit ---------------- +############### -The ``LazyLoadMojit`` in the ``application.json`` is configured to lazily load the mojit instance -``myLazyMojit`` and then call the action ``hello``. Thus, the ``index`` function in the -``LazyChild`` mojit below is never called. +The ``LazyLoadMojit`` in the ``application.json`` is configured to lazily load +the mojit instance ``myLazyMojit`` and then call the action ``hello``. Thus, +the ``index`` function in the ``LazyChild`` mojit below is never called. .. code-block:: javascript @@ -363,7 +417,8 @@ The ``LazyLoadMojit`` in the ``application.json`` is configured to lazily load t }; }, '0.0.1', {requires: ['mojito']}); -The template ``hello.hb.html`` is rendered on the server and then lazily loaded to the client. +The template ``hello.hb.html`` is rendered on the server and then lazily loaded +to the client. .. code-block:: html diff --git a/docs/dev_guide/topics/mojito_hosting_container_reqs.rst b/docs/dev_guide/topics/mojito_hosting_container_reqs.rst new file mode 100644 index 000000000..bcf8fd61d --- /dev/null +++ b/docs/dev_guide/topics/mojito_hosting_container_reqs.rst @@ -0,0 +1,261 @@ +======================================================= +Startup Requirements for Mojito in Hosting Environments +======================================================= + +This chapter discusses the startup files needed to launch Mojito applications +in a hosting environment. Because different versions of Mojito use different +startup files, you may need to modify or even remove certain startup files in +your applications, so that they may be launched in hosting environments. We +will look at what startup files are required for each version and provide +code examples. + +.. _startup_reqs-v0.4.5: + +Mojito v0.4.5 and Earlier Versions +================================== + +Version 0.4.5 and earlier versions rely exclusively on ``mojito start`` to +run a new Mojito server instance, which means the ``index.js`` and ``server.js`` +files are both required. Applications using Mojito version 0.4.5 and prior +versions should use ``index.js`` and ``server.js`` files matching those +below. + +.. _startup_reqs_v0.4.5-index: + +index.js +-------- + +.. code-block:: javascript + + /* + * Copyright (c) 2011-2012, Yahoo! Inc. All rights reserved. + * Copyrights licensed under the New BSD License. + * See the accompanying LICENSE file for terms. + */ + + + /*jslint anon:true, sloppy:true, nomen:true, node:true*/ + + process.chdir(__dirname); + + /** + * @param {object} config The configuration object containing processing params. + * @param {object} token Token used to identify the application. + */ + module.exports = function(config, token) { + var app = require('./server.js'); + + // Signal the application is ready, providing the token and app references. + process.emit('application-ready', token, app); + }; + +.. _startup_reqs_v0.4.5-server: + +server.js +--------- + + +.. code-block:: javascript + + /* + * Copyright (c) 2011-2012, Yahoo! Inc. All rights reserved. + * Copyrights licensed under the New BSD License. + * See the accompanying LICENSE file for terms. + */ + + /*jslint anon:true, sloppy:true*/ + + /** + * Returns a new Mojito server instance. + */ + module.exports = require('mojito').createServer(); + +.. _startup_reqs-v0.4.6: + +Mojito v0.4.6 +============= + +**NOT RECOMMENDED** + +Version 0.4.6 has been found not to work with at least one hosting container +due to changes in how a Mojito server instance is created and the +API of that instances. See :ref:`server.js <startup_reqs_v0.4.6-server>` +for details of the changes. We **recommend** using version 0.4.7 or +greater. + +.. _startup_reqs_v0.4.6-index: + +index.js +-------- + +The ``index.js`` file does not change for version 0.4.6. + + +.. _startup_reqs_v0.4.6-server: + +server.js +--------- + +For version 0.4.6, the ``server.js`` file changes due to changes in how a Mojito +server instance is created and the API of that instance. In this version of +Mojito, there is a ``start`` method on the Mojito server that is used to launch a +new server. Unfortunately, while this approach works, it retains some limitations +and created an issue with at least one hosting container. + +.. code-block:: javascript + + /* + * Copyright (c) 2011-2012, Yahoo! Inc. All rights reserved. + * Copyrights licensed under the New BSD License. + * See the accompanying LICENSE file for terms. + */ + + /*jslint anon:true, sloppy:true*/ + + /** + * Create and start a new Mojito server/application. + */ + + var Mojito = require('mojito'); + var app = Mojito.createServer(); + + module.exports = app.start(); + + +.. _startup_reqs_v0.4.6-npm: + +npm start +--------- + +Version 0.4.6 is the first version of Mojito to support ``npm start`` in addition +to ``mojito start`` as a means for starting up a new Mojito server instance. + +Mojito version 0.4.6 alters Mojito's startup logic to support ``npm start`` as a +common startup mechanism and expands the number of hosting containers Mojito was +compatible with. + + + +.. _startup_reqs-v0.4.7: + +Mojito v0.4.7 +============= + + +Version 0.4.7 repaired an issue with a specific hosting container and replaced +the ``start`` method with a ``listen`` wrapper method and a ``getHttpServer`` method +to provide access to the Node.js ``http.Server`` instance being used. This +approach makes it possible for Mojito to support an even broader range of +hosting containers and startup requirements. + +Applications running version 0.4.7 or greater no longer require an ``index.js`` +file, although one is still provided. Such applications must use the ``server.js`` +file shown below. + +.. _startup_reqs_v0.4.7-index: + +index.js +-------- + +**OBSOLETE** + +Version 0.4.7 still creates the ``index.js`` file, but is not used. Remove the +``index.js`` file from any applications using version 0.4.7 or greater. + +.. _startup_reqs_v0.4.7-server: + +server.js +--------- + +.. code-block:: javascript + + /* + * Copyright (c) 2011-2012, Yahoo! Inc. All rights reserved. + * Copyrights licensed under the New BSD License. + * See the accompanying LICENSE file for terms. + */ + + /*jslint anon:true, sloppy:true, nomen:true*/ + + process.chdir(__dirname); + + /* + * Create the MojitoServer instance we'll interact with. Options can be passed + * using an object with the desired key/value pairs. + */ + var Mojito = require('mojito'); + var app = Mojito.createServer(); + + // --------------------------------------------------------------------------- + // Different hosting environments require different approaches to starting the + // server. Adjust below to match the requirements of your hosting environment. + // --------------------------------------------------------------------------- + + /* + * Manhattan + * + module.exports = function(config, token) { + process.emit('application-ready', token, app.getHttpServer()); + }; + */ + + /* + * Localhost and others where the default port/host combinations work. + * You can provide port, host, callback parameters as needed. + */ + module.exports = app.listen(); + +.. _startup_reqs-v0.4.8: + +Mojito v0.4.8 and Later +======================= + +Version 0.4.8 solidifies the changes made in version 0.4.7, removing the +``index.js`` file from any application archetypes (the files used to create +new applications) and the unnecessary commented-out code in the ``server.js`` +file. As with applications created by version 0.4.7, you should remove +the ``index.js`` file from any applications using version 0.4.8 or later +versions and update your ``server.js`` file to match the one provided below. + +.. _startup_reqs_v0.4.8-index: + +index.js +-------- + +**OBSOLETE** + +The ``index.js`` file is not created by version 0.4.7. Remove from any +applicationsthat are using versions 0.4.7 or greater. + +.. _startup_reqs_v0.4.8-server: + +server.js +--------- + +.. code-block:: javascript + + /* + * Copyright (c) 2011-2012, Yahoo! Inc. All rights reserved. + * Copyrights licensed under the New BSD License. + * See the accompanying LICENSE file for terms. + */ + + /*jslint anon:true, sloppy:true, nomen:true*/ + + process.chdir(__dirname); + + /* + * Create the MojitoServer instance we'll interact with. Options can be passed + * using an object with the desired key/value pairs. + */ + var Mojito = require('mojito'); + var app = Mojito.createServer(); + + // --------------------------------------------------------------------------- + // Different hosting environments require different approaches to starting the + // server. Adjust below to match the requirements of your hosting environment. + // --------------------------------------------------------------------------- + + module.exports = app.listen(); + + diff --git a/docs/dev_guide/topics/mojito_logging.rst b/docs/dev_guide/topics/mojito_logging.rst index 4572fcb62..13e1dbe30 100644 --- a/docs/dev_guide/topics/mojito_logging.rst +++ b/docs/dev_guide/topics/mojito_logging.rst @@ -1,13 +1,13 @@ - - ======= Logging ======= -Mojito has its own logging system. When you call ``Y.log`` from within your mojits, your log -messages are intercepted and processed by Mojito. This allows you to create your own log formatting, -writing, and publishing functions for both your client and server runtimes. It also allows you to -enable log buffering, so performance during crucial runtime periods is not adversely affected. +Mojito has its own logging system. When you call ``Y.log`` from within your mojits, your +log messages are intercepted and processed by Mojito. You can set logging levels +to control the degree of detail in your log reports. You can also configure Mojito to enable +log buffering, so performance during crucial runtime periods is not adversely affected. + +.. _mojito_logging-levels: Log Levels ========== @@ -20,176 +20,102 @@ Mojito has the following five log levels: - ``ERROR`` - ``MOJITO`` -All of them should be familiar except the last, which are framework-level messages that indicate that -an important framework event is occurring (one that users might want to track). +All of them should be familiar except the last, which are framework-level messages that +indicate that an important framework event is occurring (one that users might want to +track). -Setting a log level of ``WARN`` will filter out all ``DEBUG`` and ``INFO`` messages, while ``WARN``, -``ERROR``, and ``MOJITO`` log messages will be processed. To see all +Setting a log level of ``WARN`` will filter out all ``DEBUG`` and ``INFO`` messages, while +``WARN``, ``ERROR``, and ``MOJITO`` log messages will be processed. To see all log messages, set the log level to ``DEBUG``. -YUI Library Logs -================ - -By default, all log messages generated by the YUI library itself are processed. The log level filter -is also applied to these messages, but within the Mojito log output, a "YUI-" identifier is added to -them. So when YUI emits a ``WARN`` level log message, the Mojito logs will display a ``YUI-WARN`` -log level. This helps differentiate between application messages and YUI framework messages. - -YUI logs can be turned on and off for both server and client within an application's log configuration -(see below). +.. _mojito_logging-defaults: Log Defaults ============ The server and client log settings have the following default values: -- ``level:`` ``DEBUG`` - log level filter. +- ``logLevel:`` ``DEBUG`` - log level filter. - ``yui:`` ``true`` - determines whether YUI library logs are displayed. - ``buffer:`` ``false`` - determines whether logs are buffered. - ``maxBufferSize: 1024`` - the number of logs the buffer holds before auto-flushing. - ``timestamp: true`` - log statements are given a timestamp if value is true. -- ``defaultLevel: 'info'`` - if ``Y.log`` is called without a log level, this is the default. +- ``defaultLevel: 'info'`` - if ``Y.log`` is called without a log level, this is the + default. + +.. _mojito_logging-config: Log Configuration ================= All the values above are configurable through the -`log object <../intro/mojito_configuring.html#log-object>`_ in the ``application.json`` file. In the -example ``application.json`` below, the ``log`` object has both ``client`` and ``server`` objects -that override the defaults for ``level`` and ``yui``. +`yui.config object <../intro/mojito_configuring.html#yui_config>`_ in the ``application.json`` +file. In the example ``application.json`` below, the ``yui.config`` object +overrides the defaults for ``logLevel`` and ``buffer``. .. code-block:: javascript [ { "settings": [ "master" ], - "log": { - "client": { + "yui": { + "config": { "level": "error", - "yui": false - }, - "server": { - "level": "info", - "yui": false + "buffer": true + } + }, + ... + } + ] + +.. _logging_config-prod: + +Recommended Logging Configuration for Production +------------------------------------------------ + +For production, we recommend that you use the ``environment:production`` +context with the log configuration shown below: + +.. code-block:: javascript + + [ + { + "settings": [ "environment:production" ], + "yui": { + "config": { + "debug": false, + "logLevel": "none" } }, ... } ] -.. Commenting out Mutator Log Function documentation because as of 10/03/12, you -.. cannot create log mutator functions. - - Mutator Log Functions - ===================== - - You can create different write function to change the format of log messages and control where the - logs are written. The logger has functions for formatting, writing, and publishing log messages that - can be provided by a Mojito application. The function names are defined by users. For example, you - could name the log formatter either ``formatLogs`` or ``log_formatter``. - - Custom Log Formatter - -------------------- - - The log formatter function accepts the log message, the log level, a string identifying the source - of the log (usually the YUI module name emitting the log), a timestamp, and the complete - ``logOptions`` object. The function returns a string, which is passed to the log writer. - - .. code-block:: javascript - - function {log_formatter_name}(message, logLevel, source, timestamp, logOptions) { - return "formatted message"; - } - - Custom Log Writer - ----------------- - - The log writer function accepts a string and does something with it. You can provide a function that - does whatever you want with the log string. The default log writer calls ``console.log``. - - .. code-block:: javascript - - function {log_writer_name}(logMessage[s]) {} - - .. note:: Your log writer function must be able to handle a string or an array of strings. If you - have set buffered logging, it may be sent an array of formatted log messages. - - Custom Log Publisher - -------------------- - - If a log publisher function is provided, it is expected to format and write logs. Thus, a log - publisher function takes the place of the log formatter and the log writer functions - and accepts the same parameters as the log formatter function. - - .. code-block:: javascript - - function {log_publisher_name}(message, logLevel, source, timestamp, logOptions) { - - Custom Log Functions on the Client - ---------------------------------- - - To provide custom log function on the client, you add the log function to a JavaScript asset that - your application will load. - - In the example JavaScript asset below, the log function ``formatter`` is first defined and then set - as the log formatter function. - - .. code-block:: javascript - - function formatter(msg, lvl, src, ts, opts) { - return "LOG MSG: " + msg.toLowerCase() + " -[" + lvl.toUpperCase() + "]- (" + ts + ")"; - } - YUI._mojito.logger.set('formatter', formatter); - - Using the ``formatter`` function above, the log messages will have the following format: - - ``>LOG MSG: dispatcher loaded and waiting to rock! -[INFO]- (1305666208939)`` - - Custom Log Functions on the Server - ---------------------------------- - - On the server, you must add log mutator functions to ``server.js``, so that Mojito will set them as - the log functions before starting the server. - - In this example ``server.js``, ``writeLog`` writes logs to the file system. - - .. code-block:: javascript - - var mojito = require('mojito'), fs = require('fs'), logPath = "/tmp/mojitolog.txt"; - function writeLog(msg) { - fs.writeFile(logPath, msg, 'utf-8'); - } - // You can access log formatter, writer, or - // publisher for the server here. - mojito.setLogWriter(function(logMessage) { - writeLog(logMessage + '\n'); - }); - module.exports = mojito.createServer(); +.. _mojito_logging-buffering: Log Buffering ============= -To avoid performance issues caused by logging, you can enable buffering, which will configure Mojito -to cache all logs in memory. You can force Mojito to flush the logs with the ``Y.log`` function or -setting the maximum buffer size. The following sections show you how to enable buffering and force -Mojito to flush the cached logs. +To avoid performance issues caused by logging, you can enable buffering, which will +configure Mojito to cache all logs in memory. You can force Mojito to flush the logs with +the ``Y.log`` function or setting the maximum buffer size. The following sections show you +how to enable buffering and force Mojito to flush the cached logs. + +.. _logging_buffering-enable: Enable Buffering ---------------- -To configure Mojito to buffer your logs, set the ``buffer`` property to ``true`` in the ``log`` -object as shown in the example ``application.json`` below. +To configure Mojito to buffer your logs, set the ``buffer`` property to ``true`` in the +``yui.config`` object as shown in the example ``application.json`` below. .. code-block:: javascript [ { "settings": [ "master" ], - "log": { - "client": { - "buffer": true - }, - "server": { + "yui": { + "config": { "buffer": true } }, @@ -197,29 +123,27 @@ object as shown in the example ``application.json`` below. } ] +.. _logging_buffering-flush: + Flush Cached Logs ----------------- -Mojito provides you with two ways to forcefully flush cached logs. When you have buffering enabled, -you can force Mojito to flush the cached logs with ``Y.log(({flush: true})``. You can also set the -maximum buffer size, so that Mojito will flush cached logs after the cache has reached the maximum -buffer size. +Mojito provides you with two ways to forcefully flush cached logs. When you have buffering +enabled, you can force Mojito to flush the cached logs with ``Y.log(({flush: true})``. +You can also set the maximum buffer size, so that Mojito will flush cached logs after the +cache has reached the maximum buffer size. -In the example ``application.json`` below, the maximum buffer size is set to be 4096 bytes. Once the -log cache reaches this size, the logs are then flushed. The default size of the log cache is 1024 -bytes. +In the example ``application.json`` below, the maximum buffer size is set to be 4096 bytes. +Once the log cache reaches this size, the logs are then flushed. The default size of the +log cache is 1024 bytes. .. code-block:: javascript [ { "settings": [ "master" ], - "log": { - "client": { - "buffer": true, - "maxBufferSize": 4096 - }, - "server": { + "yui": { + "config": { "buffer": true, "maxBufferSize": 4096 } @@ -228,4 +152,80 @@ bytes. } ] +.. _mojito_logging-custom: + +Customizing Logging +=================== + +.. _logging_custom-rt_context: + +Client and Server Logging +------------------------- + +You can use the ``runtime:client`` and ``runtime:server`` contexts to create different logging +settings for the client and server. + +In the ``application.json`` file, create two configuration +objects that use the ``runtime:client`` and ``runtime:server`` +contexts as shown below. + +.. code-block:: javascript + + [ + { + "settings": [ "runtime:client" ], + }, + { + "settings": [ "runtime:server" ], + } + ] + +For each context, configure your logging with +the ``yui.config`` object. + +.. code-block:: javascript + + [ + { + "settings": [ "runtime:client" ], + ... + "yui": { + "config": { + "logLevel": "WARN" + } + } + }, + { + "settings": [ "runtime:server" ], + ... + "yui": { + "config": { + "logLevel": "INFO" + } + } + } + ] + + +.. _logging_custom-include_exclude_src: + +Including and Excluding Modules From Logging +-------------------------------------------- + +You can use the ``logExclude`` and ``logInclude`` properties +of the ``yui.config`` object to include or exclude logging +from YUI modules of your application. + +The configuration below excludes logging from the YUI module +``FinanceModel``: + +.. code-block:: javascript + + "yui": { + "config": { + "logLevel": "INFO", + "buffer": true, + "logExclude": { "FinanceModel": true } + } + } diff --git a/docs/dev_guide/topics/mojito_npm.rst b/docs/dev_guide/topics/mojito_npm.rst index 473b312f0..d432f4403 100644 --- a/docs/dev_guide/topics/mojito_npm.rst +++ b/docs/dev_guide/topics/mojito_npm.rst @@ -2,34 +2,43 @@ Mojito and npm Packaging ======================== +.. _mojito_package-overview: + Overview ======== -Having installed Mojito with npm 1.0, you already understand that Mojito is an npm package. What may not be as clear -is that Mojito applications are also npm packages. Being an npm package, Mojito applications can -have their own dependencies that are installed using npm. For example, after you create a Mojito application, -you can use npm to install a local copy of the Mojito framework in the ``node_modules`` directory. If you -deployed your application to a cloud server that has a Node.js runtime environment, your application could -be run by this locally installed copy of the Mojito framework. - -Your Mojito application can also install other npm modules, even those that contain Mojito resources, such as -mojits or middleware. Conversely, you can create npm modules that contain Mojito resources, so other developers can +Having installed Mojito with npm 1.0, you already understand that Mojito is an +npm package. What may not be as clear is that Mojito applications are also npm +packages. Being an npm package, Mojito applications can have their own +dependencies that are installed using npm. For example, after you create a +Mojito application, you can use npm to install a local copy of the Mojito +framework in the ``node_modules`` directory. If you deployed your application +to a cloud server that has a Node.js runtime environment, your application +could be run by this locally installed copy of the Mojito +framework. + +Your Mojito application can also install other npm modules, even those that +contain Mojito resources, such as mojits or middleware. Conversely, you can +create npm modules that contain Mojito resources, so other developers can reuse your code. -Because npm allows you to use other modules or create your own, this chapter is divided into two sections -to meet the needs of the following two audiences: +Because npm allows you to use other modules or create your own, this chapter +is divided into two sections to meet the needs of the following two audiences: - :ref:`developers using shared mojits <using_shared_mojits>` - :ref:`authors creating npm modules that contain shared Mojito resources <author_npm_mod_shared_mojito_resource>` +.. _package_overview-resource: Mojito Resources ---------------- -A *Mojito resource* is a piece of code or functionality used by Mojito. These resources can be installed with npm or -live directly in the Mojito application. Examples of Mojito resources could be shared mojits and middleware. Developers using -shared mojits and those authoring npm modules that contain code used by Mojito should be familiar with the meaning of *Mojito resource* as it will -be used throughout this chapter. +A *Mojito resource* is a piece of code or functionality used by Mojito. These +resources can be installed with npm or live directly in the Mojito application. +Examples of Mojito resources could be shared mojits and middleware. Developers +using shared mojits and those authoring npm modules that contain code used by +Mojito should be familiar with the meaning of *Mojito resource* as it will be +used throughout this chapter. .. _using_shared_mojits: @@ -37,36 +46,42 @@ be used throughout this chapter. Using Shared Mojits =================== -Mojito applications can have any number of different resources installed with npm. -Each of these resources should be specified in the package descriptor file ``package.json`` of the -Mojito application. When users run ``npm install`` in the application directory, npm modules -containing Mojito resources and those not containing Mojito resources will be installed into -the ``node_modules`` directory. Your Mojito application will have access to all of the installed npm modules as -soon as the application starts. +Mojito applications can have any number of different resources installed with +npm. Each of these resources should be specified in the package descriptor +file ``package.json`` of the Mojito application. When users run ``npm install`` +in the application directory, npm modules containing Mojito resources and +those not containing Mojito resources will be installed into the +``node_modules`` directory. Your Mojito application will have access to +all of the installed npm modules as soon as the application starts. -For details about npm packages, see the `npm's package.json handling <http://npmjs.org/doc/json.html>`_. +For details about npm packages, see the +`npm's package.json handling <http://npmjs.org/doc/json.html>`_. .. _process_spec_install_dependencies: General Process of Using Shared Mojits -------------------------------------- -The following steps are just a guideline and not definitive instructions. Your application -may not need to install any npm modules. +The following steps are just a guideline and not definitive instructions. +Your application may not need to install any npm modules. #. Create a Mojito application. #. Add any needed dependencies to ``dependencies`` object in ``package.json``. #. Install dependencies with npm. ``{app_dir}$ npm install`` -#. When Mojito starts, your application will have access to the installed npm modules. +#. When Mojito starts, your application will have access to the installed + npm modules. + +.. _process_spec_install_dependencies_ex: Example package.json -```````````````````` +#################### -The dependencies include Mojito, the ``async`` module, and the shared mojit ``form_mojit`` (example) that will be -installed in ``node_modules`` when you run ``npm install`` from the Mojito application directory. +The dependencies include Mojito, the ``async`` module, and the shared mojit +``form_mojit`` (example) that will be installed in ``node_modules`` when you +run ``npm install`` from the Mojito application directory. .. code-block:: javascript @@ -103,9 +118,10 @@ installed in ``node_modules`` when you run ``npm install`` from the Mojito appli Authoring an npm Module Containing Shared Mojito Resources ========================================================== -Developers who have created Mojito resources that they would like to share with others can package the -Mojito resources in an npm module. The npm module is simply a container for the Mojito resource(s). -The npm module must specify that it contains a Mojito resource in its ``package.json``. +Developers who have created Mojito resources that they would like to share with +others can package the Mojito resources in an npm module. The npm module is +simply a container for the Mojito resource(s). The npm module must specify that +it contains a Mojito resource in its ``package.json``. @@ -115,7 +131,8 @@ General Process of Authoring an npm Module Containing Shared Mojito Resources ----------------------------------------------------------------------------- #. Create your Mojito resource. -#. Specify that the npm module contains Mojito resources in ``package.json``. See :ref:`Resource Definition Metadata <resource_def_metadata>` to learn how. +#. Specify that the npm module contains Mojito resources in ``package.json``. + See :ref:`Resource Definition Metadata <resource_def_metadata>` to learn how. #. Publish the module to the `npm registry <http://npmjs.org/doc/registry.html>`_. @@ -124,10 +141,11 @@ General Process of Authoring an npm Module Containing Shared Mojito Resources Resource Definition Metadata ---------------------------- -The npm module containing a Mojito resource is specified by the ``mojito`` object in ``package.json``. -The ``mojito`` object, a property of the ``yahoo`` object, defines the type and location of the resource as well as the required version -of Mojito to use the resource as shown in the example below. -See :ref:`moj_object` for details about the properties of the ``mojito`` object. +The npm module containing a Mojito resource is specified by the ``mojito`` object +in ``package.json``. The ``mojito`` object, a property of the ``yahoo`` object, +defines the type and location of the resource as well as the required version of +Mojito to use the resource as shown in the example below. See :ref:`moj_object` +for details about the properties of the ``mojito`` object. .. code-block:: javascript @@ -144,7 +162,7 @@ See :ref:`moj_object` for details about the properties of the ``mojito`` object. .. _moj_object: mojito object -````````````` +############# The following table describes the properties of the ``mojito`` object that specifies the resource type and location. @@ -173,34 +191,37 @@ specifies the resource type and location. +--------------+----------------+-----------+----------------------------+ - - .. _res_types: Mojito Package Types -```````````````````` +#################### -Currently, Mojito packages can be of type ``mojit`` or ``bundle``. See the sections below for more details. +Currently, Mojito packages can be of type ``mojit`` or ``bundle``. See the +sections below for more details. .. _mojit_type: mojit -..... +***** -The ``mojit`` type specifies that the npm module contains a mojit. The resources in the mojit (controller, views, etc.) will be looked for at -the location specified by the ``"location"`` field of the ``mojito`` object. For example, the controller will be looked for -in the following location, where ``{name}`` is the name of the npm package: ``{location}/controller.{affinity}.{selector}.js`` +The ``mojit`` type specifies that the npm module contains a mojit. The +resources in the mojit (controller, views, etc.) will be looked for at +the location specified by the ``"location"`` field of the ``mojito`` object. +For example, the controller will be looked for in the following location, +where ``{name}`` is the name of the npm package: +``{location}/controller.{affinity}.{selector}.js`` .. _bundle_type: bundle -...... +****** The ``bundle`` type specifies that the npm module contains several resources. -The following table shows where Mojito will automatically search for the different resources. -The ``{location}`` is the location specified by the ``location`` property of the ``mojito`` object. +The following table shows where Mojito will automatically search for the different +resources. The ``{location}`` is the location specified by the ``location`` property of +the ``mojito`` object. +--------------------+---------------------------------------+----------------------------------+ @@ -230,14 +251,16 @@ The ``{location}`` is the location specified by the ``location`` property of the +--------------------+---------------------------------------+----------------------------------+ +.. _resource_def_examples: + Examples -------- **package.json** - -The example ``package.json`` has the ``yahoo`` object that specifies that this npm module contains a Mojito resource. +The example ``package.json`` has the ``yahoo`` object that specifies that this +npm module contains a Mojito resource. .. code-block:: javascript diff --git a/docs/dev_guide/topics/mojito_resource_store.rst b/docs/dev_guide/topics/mojito_resource_store.rst index f73d0dad0..691101694 100644 --- a/docs/dev_guide/topics/mojito_resource_store.rst +++ b/docs/dev_guide/topics/mojito_resource_store.rst @@ -7,26 +7,31 @@ Resource Store Overview ======== -The Resource Store (RS) is the Mojito subsystem that manages metadata about the files in your -Mojito applications. Thus, it is responsible for finding and classifying code and configuration -files. When you start a Mojito application, Mojito can find, track, and resolve versions of files -in your application, such as mojits, configuration files, binders, views, assets, addons, etc., -because of the |RS|. - +The Resource Store (RS) is the Mojito subsystem that manages metadata about +the files in your Mojito applications. Thus, it is responsible for finding +and classifying code and configuration files. When you start a Mojito +application, Mojito can find, track, and resolve versions of files in your +application, such as mojits, configuration files, binders, views, assets, +addons, etc., because of the |RS|. .. _intro-who: Intended Audience ----------------- -Only advanced Mojito application developers needing finer grain control over the management -of resources or to extend the functionality of the resource store should read this documentation. +Only advanced Mojito application developers needing finer grain control over +the management of resources or to extend the functionality of the resource +store should read this documentation. + +.. _intro-prereqs: + +.. _intro-prereqs: Prerequisites ------------- -In addition to being an advanced Mojito user, you should also understand the following before -using the |RS|: +In addition to being an advanced Mojito user, you should also understand the following +before using the |RS|: - |YUIPlugin|_ - `Mojito addons <../topics/mojito_extensions.html#addons>`_ @@ -37,34 +42,41 @@ using the |RS|: How Can the Resource Store Help Developers? ------------------------------------------- +.. _intro_how-reflection: + Reflection ########## The |RS| API has methods that can be used (as-is, no addons -required) to query for details about an application. For example, when you run the commands -``mojito compile`` and ``mojito gv``, the |RS| API methods ``getResources`` and -``getResourceVersions`` are called to get information about your application. - +required) to query for details about an application. For example, when you +run the commands ``mojito compile`` and ``mojito gv``, the |RS| API methods +``getResources`` and ``getResourceVersions`` are called to get information +about your application. +.. _intro_how-define_types: Define/Register New Resource Types ################################## You can write custom |RS| addons using the aspect-oriented features of -the |RS| to define resource types. The |RS| has aspect-oriented features because it is -implemented as an extension of `Y.Base <http://yuilibrary.com/yui/docs/base/>`_, and the -|RS| addons are implemented as `YUI Plugins <http://yuilibrary.com/yui/docs/plugin/>`_. +the |RS| to define resource types. The |RS| has aspect-oriented features +because it is implemented as an extension of +`Y.Base <http://yuilibrary.com/yui/docs/base/>`_, and the |RS| addons are +implemented as `YUI Plugins <http://yuilibrary.com/yui/docs/plugin/>`_. + +For example, you could write your own |RS| addon so that the Mojito +command-line tool will register files and resources for your application. -For example, you could write your own |RS| addon so that the Mojito command-line tool -will register files and resources for your application. +.. _intro_how-extend: Extend/Modify Functionality of the |RS| ####################################### -You can also write addons or create custom versions of built-in |RS| addons to modify -how the resource store works. Your addon could map contexts to -:ref:`selectors <resolution-selectors>`, track new file types, augment the information that the -|RS| stores about files or code, or augment/replace the information returned by the |RS|. +You can also write addons or create custom versions of built-in |RS| addons to +modify how the resource store works. Your addon could map contexts to +:ref:`selectors <resolution-selectors>`, track new file types, augment the +information that the |RS| stores about files or code, or augment/replace the +information returned by the |RS|. .. _rs-resources: @@ -77,29 +89,31 @@ Resources What is a Resource? ------------------- -In Mojito, the meaning of the term **resource** is different depending on the context. -Before we discuss the |RS| in more detail, let's differentiate and define the definition of -resource in the contexts of Mojito and the |RS|. +In Mojito, the meaning of the term **resource** is different depending on the +context. Before we discuss the |RS| in more detail, let's differentiate and +define the definition of resource in the contexts of Mojito and the |RS|. .. _what-to_mojito: To Mojito ######### -The Mojito framework primarily views a **resource** as something useful found on the filesystem. +The Mojito framework primarily views a **resource** as something useful found +on the filesystem. .. _what-to_rs: To the Resource Store ##################### -The |RS| primarily cares about the *metadata* about each resource, so it sees the -metadata as the *resource*. To the |RS|, the **resource** is just a JavaScript object containing -metadata. The |RS| defines certain keys with specific meanings. The |RS| addons -can add, remove, or modify those keys/values as they see fit. -For example, the YUI |RS| addon adds, for resources that are YUI modules, the ``yui`` -property with metadata about the YUI module aspect of the resource. -The |RS| itself, however, doesn't populate the ``yui`` key of each resource. +The |RS| primarily cares about the *metadata* about each resource, so it +sees the metadata as the *resource*. To the |RS|, the **resource** is just +a JavaScript object containing metadata. The |RS| defines certain keys with +specific meanings. The |RS| addons can add, remove, or modify those +keys/values as they see fit. For example, the YUI |RS| addon adds, for +resources that are YUI modules, the ``yui`` property with metadata about +the YUI module aspect of the resource. The |RS| itself, however, doesn't +populate the ``yui`` key of each resource. .. _resources-versions: @@ -107,16 +121,18 @@ The |RS| itself, however, doesn't populate the ``yui`` key of each resource. Resource Versions ----------------- -Because there can be multiple files which are all conceptually different versions of the -same thing (e.g., ``views/index.hb.html`` and ``views/index.iphone.hb.html``), the |RS| defines -**resource version** as the metadata about each file and resource as the metadata -about the file chosen among the possible choices. +Because there can be multiple files which are all conceptually different +versions of the same thing (e.g., ``views/index.hb.html`` and +``views/index.iphone.hb.html``), the |RS| defines **resource version** +as the metadata about each file and resource as the metadata about the file +chosen among the possible choices. -The process of choosing which version of a resource to use is called *resolution* (or -"resolving the resource"). This act is one of the primary responsibilities of the |RS|. +The process of choosing which version of a resource to use is called +*resolution* (or "resolving the resource"). This act is one of the +primary responsibilities of the |RS|. -See :ref:`Resolution and Priorities <how-resolution>` to learn how the |RS| resolves -different versions of the same resource. +See :ref:`Resolution and Priorities <how-resolution>` to learn how the +|RS| resolves different versions of the same resource. .. _resources-scope: @@ -129,8 +145,8 @@ Application-Level Resources ########################### Application-level resources are truly global to the application. -At the application level, resources include archetypes, commands, configuration files, and -middleware. +At the application level, resources include archetypes, commands, +configuration files, and middleware. .. _scope-mojit: @@ -138,29 +154,29 @@ middleware. Mojit-Level Resources ##################### -At the mojit level, resources include controllers, models, binders, configuration files, and views. -These resources are limited in scope to a mojit. +At the mojit level, resources include controllers, models, binders, +configuration files, and views. These resources are limited in scope to a mojit. .. _scope-shared: Shared Resources ################ -Some resources (and resource versions) are *shared*, meaning that they are included in **all** -mojits. Most resource types that are mojit level can also be shared. Examples of mojit-level -resource types that can't be shared are controllers, configuration files (such as -``definition.json``), and YUI language bundles. +Some resources (and resource versions) are *shared*, meaning that they are +included in **all** mojits. Most resource types that are mojit level can also +be shared. Examples of mojit-level resource types that can't be shared are +controllers, configuration files (such as ``definition.json``), and YUI language +bundles. .. _resources-types: Resource Types -------------- -The resource type is defined by the ``type`` property in the metadata for a given resource. -See :ref:`Types of Resources <metadata-types>` for descriptions of the resource -types. Developers can also create their own types of resources to fit the need of their -applications. - +The resource type is defined by the ``type`` property in the metadata for a +given resource. See :ref:`Types of Resources <metadata-types>` for descriptions +of the resource types. Developers can also create their own types of resources +to fit the need of their applications. .. _rs-metadata: @@ -173,9 +189,10 @@ Resource Metadata Intro ----- -The RS uses metadata to track information about each resource. This metadata is used by the rest of -Mojito to find, load, and parse the resources. The metadata is generated by the |RS| or by |RS| -addons |---| it has no representation on the filesystem. +The RS uses metadata to track information about each resource. This metadata +is used by the rest of Mojito to find, load, and parse the resources. The +metadata is generated by the |RS| or by |RS| addons |---| it has no +representation on the filesystem. .. _metadata-obj: @@ -244,12 +261,11 @@ Metadata Object .. admonition:: Note About Default Values - Some values for the properties of the metadata object do have defaults, but it depends on - the value of the ``type`` property and/or comes from the file name of the resource being - represented. For example, the affinity of views is ``common`` (because views are used - on both client and server); however, the affinity for controllers comes - from the file name, so there is no default. - + Some values for the properties of the metadata object do have defaults, but + it depends on the value of the ``type`` property and/or comes from the file + name of the resource being represented. For example, the affinity of views + is ``common`` (because views are used on both client and server); however, + the affinity for controllers comes from the file name, so there is no default. .. _src_obj: @@ -339,10 +355,11 @@ view Object yui Object ########## -The ``yui`` property of the ``metadata`` object is created by the ``yui`` |RS| addon. The -``yui`` property can be any data type, but in general, it is an object -containing metadata about YUI modules. You can think of the ``yui`` object as a container for the -arguments to the ``YUI.add`` method that is used to register reusable YUI modules. +The ``yui`` property of the ``metadata`` object is created by the ``yui`` |RS| addon. +The ``yui`` property can be any data type, but in general, it is an object +containing metadata about YUI modules. You can think of the ``yui`` object as +a container for the arguments to the ``YUI.add`` method that is used to register +reusable YUI modules. The following table lists the typical properties that are part of the ``yui`` object. @@ -366,7 +383,8 @@ part of the ``yui`` object. Types of Resources ------------------ -The ``type`` property of the ``metadata`` object can have any of the following values: +The ``type`` property of the ``metadata`` object can have any of the following +values: - ``config`` - a piece of configuration, sometimes for another resource - ``controller`` - the controller for a mojit @@ -375,7 +393,8 @@ The ``type`` property of the ``metadata`` object can have any of the following v - ``binder`` - a binder for a mojit - ``asset`` - an asset (css, js, image, etc.) - ``addon`` - an addon to the mojito system -- ``archetype`` - the commands to create resources as described in the output from ``mojito help create`` +- ``archetype`` - the commands to create resources as described in the output from + ``mojito help create`` - ``spec`` - the configuration for a mojit instance - ``yui-lang`` - a YUI 3 language bundle - ``yui-module`` - a YUI 3 module (that isn't one of the above) @@ -386,8 +405,9 @@ Subtypes ######## You can use a subtype to specify types of a ``type``. For example, a -resource of ``type:addon`` might have subtypes, such as ``subtype:ac`` for AC addons, -``subtype:view-engine`` for view engines, or ``subtype:rs`` for |RS| addons. +resource of ``type:addon`` might have subtypes, such as ``subtype:ac`` +for AC addons, ``subtype:view-engine`` for view engines, or ``subtype:rs`` +for |RS| addons. For ``type:archetype``, the subtypes could be ``subtype:app`` or ``subtype:mojit``. The subtype specifies what archetype Mojito should create, @@ -400,29 +420,33 @@ selector Property ----------------- The **selector** is an arbitrary user-defined string, which is used to -*select* which version of each resource to use. The selector is defined in the -``application.json`` with the ``selector`` property. Because the selector is a global -entity, you cannot define it at the mojit level. For example, you cannot define the selector -in the ``defaults.json`` of a mojit. +*select* which version of each resource to use. The selector is defined in +the ``application.json`` with the ``selector`` property. Because the selector +is a global entity, you cannot define it at the mojit level. For example, you +cannot define the selector in the ``defaults.json`` of a mojit. The value of the ``selector`` property is a string that must not have a -period (``'.'``) or slash (``'/'``) in it. In practice, it's suggested to use alphanumeric and -hyphen ('-') characters only. +period (``'.'``) or slash (``'/'``) in it. In practice, it's suggested to use +alphanumeric and hyphen ('-') characters only. Only one selector can be used in each configuration object identified by the -``setting`` property, which defines the context. The specified selectors must match the selector -found in the resource file names. So, for example, the template ``views/index.iphone.hb.html`` has -the selector ``iphone``. +``setting`` property, which defines the context. The specified selectors +must match the selector found in the resource file names. So, for example, +the template ``views/index.iphone.hb.html`` has the selector ``iphone``. + +.. _sel_prop-ex: + +.. _sel_prop-ex: Example ####### -The selector is typically used in conjunction with a context to specify a resource -for a particular device. In the example ``application.json`` below, the selector -``ipad`` is defined when the context is ``device:ipad``. If an application -is running in the ``device:ipad`` context, Mojito will select resources -with ``ipad`` identifier. Thus, Mojito might render the template ``index.ipad.hb.html`` -and **not** ``index.iphone.hb.html``. +The selector is typically used in conjunction with a context to specify a +resource for a particular device. In the example ``application.json`` below, +the selector``ipad`` is defined when the context is ``device:ipad``. If an +application is running in the ``device:ipad`` context, Mojito will select +resources with ``ipad`` identifier. Thus, Mojito might render the template +``index.ipad.hb.html`` and **not** ``index.iphone.hb.html``. .. code-block:: javascript @@ -444,19 +468,20 @@ and **not** ``index.iphone.hb.html``. - .. _metatdata-versions: Resource Versions ----------------- Resources can have many versions that are identified by the -:ref:`selector property <sel_prop>` and the affinity. The selector is defined by the user and -indicates the version of the resource and the affinity is defined by the resource itself. +:ref:`selector property <sel_prop>` and the affinity. The selector is defined +by the user and indicates the version of the resource and the affinity is +defined by the resource itself. -For example, developer might decide to use the selector ``selector: iphone`` for the -iPhone version and ``selector: android`` for the Android version of a resource. Using these two -selectors, you could have the following two versions of the ``index`` resource of type ``view``: +For example, developer might decide to use the selector ``selector: iphone`` +for the iPhone version and ``selector: android`` for the Android version of a +resource. Using these two selectors, you could have the following two versions +of the ``index`` resource of type ``view``: - ``index.iphone.hb.html`` - ``index.android.hb.html`` @@ -502,7 +527,7 @@ Example } -.. _rs-how: +.. _rs-how_work: How Does the Resource Store Work? ================================= @@ -510,39 +535,45 @@ How Does the Resource Store Work? Understanding the |RS| will allow you to debug your application and write |RS| addons to customize how it works. +.. _how_work-overview: Overview -------- In short, the resource store walks through the application-level, -mojit-level, and ``npm`` module files (in that order) of a Mojito application, determines what type -of resource each file is, creates metadata about the resource, and then registers the resource. +mojit-level, and ``npm`` module files (in that order) of a Mojito application, +determines what type of resource each file is, creates metadata about the resource, +and then registers the resource. During this process, the resource store also does the following: -- pre-calculates ("resolves") which resource versions are used for each version of the mojit. -- also keeps track of application-level resources (archetypes, commands, config files, - and middleware). +- pre-calculates ("resolves") which resource versions are used for each version + of the mojit. +- also keeps track of application-level resources (archetypes, commands, + config files, and middleware). - provides methods and events, including those specialized for AOP. -- explicitly uses the addons :ref:`selector <intro-selector>` and :ref:`config <intro-config>`. +- explicitly uses the addons :ref:`selector <intro-selector>` and + :ref:`config <intro-config>`. -In the following sections, we'll look at the process in a little more details. To see the code for -the resource store, see the |SS|_ file. +In the following sections, we'll look at the process in a little more details. +To see the code for the resource store, see the |SS|_ file. .. _how-walk_fs: Walking the Filesystem ---------------------- -Resource versions are discovered by the |RS| at server-start time. The |RS| method ``preload`` -first walks all the files in the application, excluding the ``node_modules`` directory. Next, all -the files in the packages in ``node_modules`` are walked. The packages are walked in breadth-first -fashion, so that *shallower* packages have precedence over *deeper* ones. (Not all the packages -are used: only those that have declared themselves as extensions to Mojito.) Finally, -if Mojito wasn't found in ``node_modules``, the globally-installed version of Mojito is walked. +Resource versions are discovered by the |RS| at server-start time. The |RS| +method ``preload`` first walks all the files in the application, excluding the +``node_modules`` directory. Next, all the files in the packages in ``node_modules`` +are walked. The packages are walked in breadth-first fashion, so that *shallower* +packages have precedence over *deeper* ones. (Not all the packages are used: only +those that have declared themselves as extensions to Mojito.) Finally, if Mojito +wasn't found in ``node_modules``, the globally-installed version of Mojito is walked. -After all that, the |RS| knows about all the resource versions. Then it resolves those versions -into the resources as described in :ref:`Resolution and Priorities <how-resolution>`. +After all that, the |RS| knows about all the resource versions. Then it resolves +those versions into the resources as described in +:ref:`Resolution and Priorities <how-resolution>`. .. _how-resolution: @@ -550,19 +581,20 @@ Resolution and Priorities ------------------------- The resolving of resource version happens in the |RS| ``preload`` method as well. -The act of resolving the resource versions is really just resolving the affinities and selectors. -See :ref:`Resource Versions <metatdata-versions>` for a brief explanation about how affinities -and selectors determine different versions of a resource. The following sections discuss what the -|RS| uses to resolve versions and create a **priority-ordered selector list (POSL)**. +The act of resolving the resource versions is really just resolving the +affinities and selectors. See :ref:`Resource Versions <metatdata-versions>` +for a brief explanation about how affinities and selectors determine different +versions of a resource. The following sections discuss what the |RS| uses to +resolve versions and create a **priority-ordered selector list (POSL)**. .. _resolution-affinities: Affinities ########## -The choice of a resource version depends on the affinity. If we're resolving versions -for the server, versions with ``affinity:server`` will have higher priority than -``affinity:common``, and ``affinity:client`` will be completely ignored. +The choice of a resource version depends on the affinity. If we're resolving +versions for the server, versions with ``affinity:server`` will have higher +priority than ``affinity:common``, and ``affinity:client`` will be completely ignored. .. _resolution-selectors: @@ -579,26 +611,28 @@ Suppose an application has the following resources: - ``controller.server.phone.js`` In this application, the POSL for context ``{device:browser}`` might -be ``['*']``, but the POSL for the context ``{device:iphone}`` might be ``['iphone','*']``. -We need to use a (prioritized) list of selectors instead of just a "selector that matches the -context" because not all versions might exist for all selectors. In the example above, if -``controller.server.iphone.js`` didn't exist, we should still do the right thing for context -``{device:iphone}``. +be ``['*']``, but the POSL for the context ``{device:iphone}`` might be +``['iphone','*']``. We need to use a (prioritized) list of selectors instead of +just a "selector that matches the context" because not all versions might exist +for all selectors. In the example above, if ``controller.server.iphone.js`` +didn't exist, we should still do the right thing for context ``{device:iphone}``. .. _resolution-sources: Sources ####### -The final consideration for priority is the source. Mojit-level versions have higher priority -than shared versions. Let's take a different application with the following resources: +The final consideration for priority is the source. Mojit-level versions have +higher priority than shared versions. Let's take a different application with +the following resources: - ``mojits/Foo/models/bar.common.js`` - ``models/bar.common.js`` -In this application, the second resource is shared with all mojits. The mojit ``Foo``, however, has -defined its own version of the same resource (``id: model--bar``), and so that should have higher -priority than the shared one. +In this application, the second resource is shared with all mojits. The mojit +``Foo``, however, has defined its own version of the same resource +(``id: model--bar``), and so that should have higher priority than the shared +one. .. _resolution-relationships: @@ -611,40 +645,43 @@ Finally, there's a relationship between the different types of priority. #. The selector has the next highest priority. #. The affinity has the least highest priority. -That means that if there exists, for example, both a ``controller.server.js`` and -``controller.common.iphone.js``, for the server and context ``{device:iphone}``, the second version -will be used because its selector is a higher priority match than its affinity. +That means that if there exists, for example, both a ``controller.server.js`` +and ``controller.common.iphone.js``, for the server and context +``{device:iphone}``, the second version will be used because its selector +is a higher priority match than its affinity. -All this is pre-calculated for each resource and for each possible runtime configuration (client or -server, and every appropriate runtime context). +All this is pre-calculated for each resource and for each possible runtime +configuration (client or server, and every appropriate runtime context). .. _how-get_data: Getting Data from the Resource Store ------------------------------------ -Besides the standard ways that Mojito uses the resource store, there are two generic methods for -getting resources and resource versions from the |RS|. +Besides the standard ways that Mojito uses the resource store, there are two +generic methods for getting resources and resource versions from the |RS|. - ``getResourceVersions(filter)`` - ``getResources(env, ctx, filter)`` -The APIs are intentionally similar. Both return an array of resources, and the ``filter`` argument -can be used to restrict the returned resources (or versions). The ``filter`` is an object -whose keys and values must match the returned resources (or versions). Think of it as a *template* -or *partial resource* that all resources must match. For example, a filter of ``{type:'view'}`` -will return all the views. +The APIs are intentionally similar. Both return an array of resources, and the +``filter`` argument can be used to restrict the returned resources +(or versions). The ``filter`` is an object whose keys and values must match +the returned resources (or versions). Think of it as a *template* or *partial +resource* that all resources must match. For example, a filter of +``{type:'view'}`` will return all the views. -For mojit-level resources or resource versions, specify the mojit name in the filter. For example, -filter ``{mojit:'Foo'}`` will return all resources (or versions) in the ``Foo`` mojit. +For mojit-level resources or resource versions, specify the mojit name in the +filter. For example, filter ``{mojit:'Foo'}`` will return all resources +(or versions) in the ``Foo`` mojit. -.. note:: Because of the resolution process, the resources returned for filter ``{mojit:'Foo'}`` - might contain shared resources. +.. note:: Because of the resolution process, the resources returned for filter + ``{mojit:'Foo'}`` might contain shared resources. -To get mojit-level resources (or versions) from multiple mojits, you'll have to call -the method ``getResourceVersions`` or ``getResources`` for each mojit. You can call -``listAllMojits`` to get a list of all mojits. +To get mojit-level resources (or versions) from multiple mojits, you'll have to c +all the method ``getResourceVersions`` or ``getResources`` for each mojit. +You can call ``listAllMojits`` to get a list of all mojits. .. _rs-creating_rs_addons: @@ -657,17 +694,19 @@ Creating Your Own Resource Store Addons Intro ----- -In this section, we will discuss the key methods, events, and give a simple example of a custom -|RS| addon. By using the provided example as a model and referring to the |RSC|_ in the API -documentation, you should be able to create your own custom |RS| addons. +In this section, we will discuss the key methods, events, and give a simple +example of a custom |RS| addon. By using the provided example as a model +and referring to the |RSC|_ in the API documentation, you should be able to +create your own custom |RS| addons. .. _creating_rs_addons-anatomy: Anatomy of a |RS| Addon ----------------------- -The resource store addons are implemented using the |YUIPlugin|_ mechanism. In essence, a Mojito -addon is a YUI plugin, so the skeleton of a |RS| addon will be the same as a YUI Plugin. +The resource store addons are implemented using the |YUIPlugin|_ mechanism. +In essence, a Mojito addon is a YUI plugin, so the skeleton of a |RS| addon +will be the same as a YUI Plugin. See the |RSC|_ for the parameters and return values for the |RS| methods. @@ -680,8 +719,9 @@ Key Methods .. js:function:: initialize(config) - This method sets the paths to find the application, Mojito, and |RS| files. Addons should hook - into |RS| methods (using AOP) or events fired by the |RS| in this method. + This method sets the paths to find the application, Mojito, and |RS| files. + Addons should hook into |RS| methods (using AOP) or events fired by the |RS| + in this method. The following host methods are called: @@ -710,9 +750,9 @@ Key Methods .. js:function:: preloadResourceVersions() - The |RS| walks the filesystem in this method. Before ``preloadResourceVersions`` is called, - not much is known, though the static application configuration is available using the - method ``getStaticAppConfig``. + The |RS| walks the filesystem in this method. Before ``preloadResourceVersions`` is + called, not much is known, though the static application configuration is available + using the method ``getStaticAppConfig``. Within the ``preloadResourceVersions`` method, the following host methods are called: @@ -730,9 +770,9 @@ Key Methods .. js:function:: findResourceVersionByConvention() - This method is called on each directory or file being walked and is used to decide if the - path is a resource version. The return value can be a bit confusing, so read the API - documentation carefully and feel free to post any questions that you have to the + This method is called on each directory or file being walked and is used to decide if + the path is a resource version. The return value can be a bit confusing, so read the + API documentation carefully and feel free to post any questions that you have to the `Yahoo! Mojito Forum <http://developer.yahoo.com/forum/Yahoo-Mojito/>`_. Typically, you would hook into this method with the ``afterHostMethod`` method to register @@ -741,30 +781,35 @@ Key Methods .. js:function:: parseResourceVersion() - This method creates an actual resource version. Typically, you would hook into this method - with the ``beforeHostMethod`` method to create your own resource versions. This should work - together with your own version of the :js:func:`findResourceVersionByConvention` method. + This method creates an actual resource version. Typically, you would hook into this + method with the ``beforeHostMethod`` method to create your own resource versions. This + should work together with your own version of the + :js:func:`findResourceVersionByConvention` method. .. js:function:: addResourceVersion() - This method is called to save the resource version into the |RS|. Typically, if you want to - modify/augment an existing resource version, hook into this with the + This method is called to save the resource version into the |RS|. Typically, if you + want to modify/augment an existing resource version, hook into this with the ``beforeHostMethod`` method. .. js:function:: resolveResourceVersions() - This method resolves the resource versions into resources. As a resource version is resolved, - the ``mojitResourcesResolved`` event is called. After the method has been executed, all - resource versions have been resolved. + This method resolves the resource versions into resources. As a resource version is + resolved, the ``mojitResourcesResolved`` event is called. After the method has been + executed, all resource versions have been resolved. .. js:function:: serializeClientStore() - This method is called during runtime as Mojito creates the configuration for the client-side - Mojito. + This method is called during runtime as Mojito creates the configuration for the + client-side Mojito. + +.. _key_methods-access: + +.. _key_methods-access: Accessing the Resource Store -```````````````````````````` +**************************** To access the |RS|, you call ``this.get('host')``. The method returns the |RS|. @@ -777,16 +822,17 @@ Key Events .. _key_events-mojitResourcesResolved: mojitResourcesResolved -`````````````````````` +********************** This event is called when the resources in a mojit are resolved. .. _key_events-getMojitTypeDetails: getMojitTypeDetails -``````````````````` +******************* -This event is called during runtime as Mojito creates an *instance* used to dispatch a mojit. +This event is called during runtime as Mojito creates an *instance* used to +dispatch a mojit. .. _creating_rs_addons-ex: @@ -798,7 +844,8 @@ Example |RS| Addon ########## -The following |RS| addon registers the new resource type ``text`` for text files. +The following |RS| addon registers the new resource type ``text`` for text +files. ``addons/rs/text.server.js`` @@ -890,9 +937,10 @@ The following |RS| addon registers the new resource type ``text`` for text files Text ActionContext Addon ######################## -The Text Addon provides accessors so that the controller can access resources of type ``text``. -You could use this example addon as a model for writing an addon that allows a controller -to access other resource types such as ``xml`` or ``yaml``. +The Text Addon provides accessors so that the controller can access resources +of type ``text``. You could use this example addon as a model for writing an +addon that allows a controller to access other resource types such as ``xml`` +or ``yaml``. ``addons/ac/text.server.js`` @@ -950,9 +998,6 @@ Controller YUI.add('Viewer', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, index: function(ac) { var chosen; // TODO: use form input to choose a text file @@ -970,7 +1015,7 @@ Controller }); } }; - }, '1.0.1', {requires: ['mojito', 'addon-ac-text']}); + }, '1.0.1', {requires: ['mojito', 'mojito-assets-addon', 'addon-ac-text']}); .. _rs-addons: @@ -984,9 +1029,9 @@ Intro ----- Mojito comes with built-in resource store addons that are used by the |RS| -and the Mojito framework. These resource store addons are required by the |RS| and -the Mojito framework. Thus, particular care must be taken when creating custom versions -of them. +and the Mojito framework. These resource store addons are required by the |RS| +and the Mojito framework. Thus, particular care must be taken when creating +custom versions of them. The |RS| comes with the following four built-in addons: @@ -1004,14 +1049,15 @@ The |RS| comes with the following four built-in addons: - stores the URL in the ``url`` key of the resource - calculates the asset URL base for each mojit - ``yui`` - - registers new resource type ``yui-module`` found in the directories ``autoload`` - or ``yui_modules`` + - registers new resource type ``yui-module`` found in the directories + ``autoload`` or ``yui_modules`` - registers new resource type ``yui-lang`` found in the ``lang`` directory - calculates the ``yui`` metadata for resource versions that are YUI modules - - pre-calculates corresponding YUI module dependencies when resources are resolved - for each version of each mojit - - appends the pre-calculated YUI module dependencies for the controller and binders when - Mojito queries the |RS| for the details of a mojit (``getMojitTypeDetails`` method) + - pre-calculates corresponding YUI module dependencies when resources are + resolved for each version of each mojit + - appends the pre-calculated YUI module dependencies for the controller and + binders when Mojito queries the |RS| for the details of a mojit + (``getMojitTypeDetails`` method) - provides methods used by Mojito to configure its YUI instances @@ -1020,11 +1066,13 @@ The |RS| comes with the following four built-in addons: Creating Custom Versions of Built-In |RS| Addons ------------------------------------------------ -We will be examining the ``selector`` and ``url`` addons to help you create custom versions of -those addons. We do not recommend that you create custom versions of the -``config`` or ``yui`` addons, so we will not be looking at those addons. Also, this documentation -explains what the |RS| expects the addon to do, so you can create your own version of the addons. -To learn what the |RS| built-in addons do, please refer to the |RSC|_ in the API documentation. +We will be examining the ``selector`` and ``url`` addons to help you create +custom versions of those addons. We do not recommend that you create custom +versions of the ``config`` or ``yui`` addons, so we will not be looking at +those addons. Also, this documentation explains what the |RS| expects the +addon to do, so you can create your own version of the addons. To learn what +the |RS| built-in addons do, please refer to the |RSC|_ in the API +documentation. .. _custom-selector: @@ -1035,27 +1083,27 @@ selector .. _selector-desc: Description -``````````` +*********** -If you wish to use a different algorithm for to determine the selectors to use, -you can implement your own version of this |RS| addon in the +If you wish to use a different algorithm for to determine the selectors +to use, you can implement your own version of this |RS| addon in the ``addons/rs/selector.server.js`` file of your application. .. _selector-reqs: Requirements -```````````` +************ -Because the ``selector`` addon is used directly by the the resource store, all implementations -need to provide the following method: +Because the ``selector`` addon is used directly by the the resource store, all +implementations need to provide the following method: - :js:func:`getPOSLFromContext(ctx)` .. _selector-methods: Methods -``````` +******* .. js:function:: getPOSLFromContext(ctx) @@ -1078,24 +1126,25 @@ url .. _url-desc: Description -``````````` +*********** The ``url`` addon calculates and manages the static handler URLs for resources. -The addon is not used by resource store core, but used by the static handler middleware. +The addon is not used by resource store core, but used by the static handler +middleware. If you wish to use a different algorithm to determine the URLs, you can implement your own version of this |RS| addon in the ``addons/rs/url.server.js`` file of your application. -After the method ``preloadResourceVersions`` sets ``res.url`` to the static handler URL -for the resource, the method ``getMojitTypeDetails`` sets the mojit's ``assetsRoot``. -The static handler URL can be a rollup URL. +After the method ``preloadResourceVersions`` sets ``res.url`` to the static +handler URL for the resource, the method ``getMojitTypeDetails`` sets the +mojit's ``assetsRoot``. The static handler URL can be a rollup URL. .. _url-reqs: Requirements -```````````` +************ Your addon is required to do the following: diff --git a/docs/dev_guide/topics/mojito_run_dyn_defined_mojits.rst b/docs/dev_guide/topics/mojito_run_dyn_defined_mojits.rst index ea5d1c981..d411f7fa6 100644 --- a/docs/dev_guide/topics/mojito_run_dyn_defined_mojits.rst +++ b/docs/dev_guide/topics/mojito_run_dyn_defined_mojits.rst @@ -1,73 +1,102 @@ - - =========================================== Running Dynamically Defined Mojit Instances =========================================== +.. _dyn_defined_mojits-intro: + Introduction ============ -Mojito allows developer to statically or dynamically define child mojit instances. In the simplest case, -your parent and its child mojit instances will be statically defined in ``application.json``. -The parent mojit will run its child mojits and then attach their rendered output to its own template. -In more complex cases, your application may need to run a mojit instance or pass data to another mojit instance because of -some condition, such as a user event or an environment variable. Being self-contained units of execution, mojits can only pass data -or run mojits that have been defined as children in configuration. If you have not statically defined the child instances -that you want to run or receive data, you can still dynamically define those child instances in configuration objects at runtime. - -The dynamically defined child instances, however, are only useful if the parent mojit can run them. -Thus, the Mojito API provides the two methods ``ac.composite.execute`` or ``ac._dispatch`` that parent mojits can use to run -dynamically defined child mojit instances. The parent mojit passes configuration objects that define the child mojit -instances and any data you want to pass to one of the two methods. Although both the ``ac.composite.execute`` and ``ac._dispatch`` -methods allow a parent mojit to run a dynamically defined child instance and pass data to that child instance, -the two methods do have some distinct differences, which are discussed in `Should I Use ac.composite.execute or ac._dispatch?`_. - +Mojito allows developer to statically or dynamically define child mojit +instances. In the simplest case, your parent and its child mojit instances +will be statically defined in ``application.json``. The parent mojit will +run its child mojits and then attach their rendered output to its own +template. In more complex cases, your application may need to run a mojit +instance or pass data to another mojit instance because of some condition, +such as a user event or an environment variable. Being self-contained units +of execution, mojits can only pass data or run mojits that have been defined +as children in configuration. If you have not statically defined the child +instances that you want to run or receive data, you can still dynamically +define those child instances in configuration objects at runtime. + +The dynamically defined child instances, however, are only useful if the +parent mojit can run them. Thus, the Mojito API provides the two methods +``ac.composite.execute`` or ``ac._dispatch`` that parent mojits can use to +run dynamically defined child mojit instances. The parent mojit passes +configuration objects that define the child mojit instances and any data +you want to pass to one of the two methods. Although both the +``ac.composite.execute`` and ``ac._dispatch`` methods allow a parent mojit +to run a dynamically defined child instance and pass data to that child +instance, the two methods do have some distinct differences, which are +discussed in `Should I Use ac.composite.execute or ac._dispatch?`_. + +.. _dyn_defined_mojits_intro-execute: ac.composite.execute -------------------- -The `Composite addon <../../api/classes/Composite.common.html>`_ includes the ``execute`` method that allows parents to run -one or more dynamically defined children mojits by passing the ``children`` object. The ``execute`` method is different than the ``done`` method -of the ``Composite`` addon in that the ``done`` method runs child mojit instances that are defined in ``application.json``. -See `Composite Mojits <./mojito_composite_mojits.html>`_ to learn how to use the ``done`` method of the ``Composite`` addon. +The `Composite addon <../../api/classes/Composite.common.html>`_ includes +the ``execute`` method that allows parents to run one or more dynamically +defined children mojits by passing the ``children`` object. The ``execute`` +method is different than the ``done`` method of the ``Composite`` addon in +that the ``done`` method runs child mojit instances that are defined in +``application.json``. See `Composite Mojits <./mojito_composite_mojits.html>`_ +to learn how to use the ``done`` method of the ``Composite`` addon. +.. _dyn_defined_mojits_intro-dispatch: ac._dispatch ------------ -Mojito also provides the ``dispatch`` method that can be called from the ``ActionContext`` object to run a dynamically defined child mojit. -The ``dispatch`` method also allows you to define your own ``flush``, ``done``, and ``error`` functions -for the child mojit instance. +Mojito also provides the ``dispatch`` method that can be called from the +``ActionContext`` object to run a dynamically defined child mojit. The +``dispatch`` method also allows you to define your own ``flush``, +``done``, and ``error`` functions for the child mojit instance. + +.. _dyn_defined_mojits-use_cases: Use Cases ========= - A mojit needs to pass data to another mojit. -- A mojit wants to attach the rendered view of the dynamically defined mojit to its template. -- A mojit binder invokes the controller to run an instance of another mojit. The mojit renders its view, which is then returned it to the binder. +- A mojit wants to attach the rendered view of the dynamically defined mojit + to its template. +- A mojit binder invokes the controller to run an instance of another mojit. + The mojit renders its view, which is then returned it to the binder. + +.. _dyn_defined_mojits-exec_v_dispatch: Should I Use ac.composite.execute or ac._dispatch? ================================================= -If you need fine-grained control over your child instances, you will want to use ``ac._dispatch``. -In most other cases, and particularly when dynamically defining and running more than one child instance, you will most likely want -to use ``ac.composite.execute`` because it is easier to use. Also, in the case of running multiple child instances, ``ac.composite.execute`` -keeps track of the configuration and metadata for your child instances; whereas, your parent mojit will need to manage its children -if ``ac._dispatch`` was used. +If you need fine-grained control over your child instances, you will want to +use ``ac._dispatch``. In most other cases, and particularly when dynamically +defining and running more than one child instance, you will most likely want +to use ``ac.composite.execute`` because it is easier to use. Also, in the case +of running multiple child instances, ``ac.composite.execute`` keeps track of +the configuration and metadata for your child instances; whereas, your parent +mojit will need to manage its children if ``ac._dispatch`` was used. + +.. _dyn_defined_mojits-composite: Using the Composite Addon ========================= -For a mojit to run dynamically defined mojit instances using the ``Composite`` addon, you need to pass a configuration object to ``ac.composite.execute``. -The next sections will look at the configuration object, the controller code, and then the template of the parent mojit. +For a mojit to run dynamically defined mojit instances using the ``Composite`` +addon, you need to pass a configuration object to ``ac.composite.execute``. +The next sections will look at the configuration object, the controller code, +and then the template of the parent mojit. +.. _dyn_defined_mojits_comp-child: Configuring Child Instances --------------------------- -The configuration object passed to ``ac.composite.execute`` must have the ``children`` object to defines one or more mojit instances. -In the ``cfg`` object below, the child mojit instances ``news`` and ``sidebar`` are defined. You can also specify the action to -execute and pass configuration information that includes parameters and assets. +The configuration object passed to ``ac.composite.execute`` must have the + ``children`` object to defines one or more mojit instances. In the ``cfg`` +object below, the child mojit instances ``news`` and ``sidebar`` are defined. +You can also specify the action to execute and pass configuration information +that includes parameters and assets. .. code-block:: javascript @@ -96,48 +125,60 @@ execute and pass configuration information that includes parameters and assets. } } +.. _dyn_defined_mojits-run_mojits: + Running Mojit Instances ----------------------- -The ``ac.composite.execute`` takes two parameters. The first parameter is the configuration object -discussed in `Configuring Child Instances`_ that define the child mojit instance or instances. The second parameter is a callback -that is returned an object containing the rendered data from the child mojit instances and an optional object containing the metadata of -the children. The metadata contains information about the children's binders, assets, configuration, and HTTP headers -and is required for binders to execute and attach content to the DOM. +The ``ac.composite.execute`` takes two parameters. The first parameter is the +configuration object discussed in `Configuring Child Instances`_ that define +the child mojit instance or instances. The second parameter is a callback that +is returned an object containing the rendered data from the child mojit +instances and an optional object containing the metadata of the children. +The metadata contains information about the children's binders, assets, +configuration, and HTTP headers and is required for binders to execute and +attach content to the DOM. -In the example controller below, the child instances ``header``, ``body``, and ``footer`` are dynamically defined in ``cfg`` and then run with -``actionContext.composite.execute``. The rendered views of the child mojits are returned in the callback and then made available to the mojit's template. +In the example controller below, the child instances ``header``, ``body``, +and ``footer`` are dynamically defined in ``cfg`` and then run with +``actionContext.composite.execute``. The rendered views of the child mojits +are returned in the callback and then made available to the mojit's template. .. code-block:: javascript YUI.add('FrameMojit', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, index: function(actionContext) { - var cfg = { view: "index", children: { header: { type: "HeaderMojit", action: "index"}, body: { type: "BodyMojit", action: "index" }, footer: { type: "FooterMojit", action: "index" }}}; - // The 'meta' object containing metadata about the children's binders, assets, configuration, and HTTP header - // info is passed to the callback. This 'meta' object is required for binders to execute and attach content to the DOM. + var cfg = { view: "index", + children: { + header: { type: "HeaderMojit", action: "index"}, + body: { type: "BodyMojit", action: "index" }, + footer: { type: "FooterMojit", action: "index" } + } + }; + // The 'meta' object containing metadata about the children's binders, assets, + // configuration, and HTTP header info is passed to the callback. This 'meta' + // object is required for binders to execute and attach content to the DOM. actionContext.composite.execute(cfg,function(data, meta){ actionContext.done(data, meta); }); } } - ;}, '0.0.1', {requires: []}); - + ;}, '0.0.1', {requires: ['mojito-composite-addon']}); +.. _dyn_defined_mojits-templates: Templates --------- -The rendered output from each of the dynamically defined child mojit instances can be injected into -the template of the parent mojit using Handlebars expressions. If the child mojit instances ``header``, ``footer``, -and ``body`` were defined in the configuration object passed to ``ac.composite.execute``, you -could add the rendered content from those child mojit instances to the parent mojit's template with -the Handlebars expressions ``{{{header}}}``, ``{{{footer}}}``, and ``{{{body}}}`` as shown in the -example template below. The Handlebars expressions using triple braces insert unescaped HTML into the page. +The rendered output from each of the dynamically defined child mojit instances can be +injected into the template of the parent mojit using Handlebars expressions. If the child +mojit instances ``header``, ``footer``, and ``body`` were defined in the configuration +object passed to ``ac.composite.execute``, you could add the rendered content from those +child mojit instances to the parent mojit's template with the Handlebars expressions +``{{{header}}}``, ``{{{footer}}}``, and ``{{{body}}}`` as shown in the example template +below. The Handlebars expressions using triple braces insert unescaped HTML into the page. .. code-block:: html @@ -147,12 +188,18 @@ example template below. The Handlebars expressions using triple braces insert un {{{footer}}} </div> +.. _dyn_defined_mojits-exs: + Example ------- +.. _dyn_defined_mojits_exs-controllers: + Controllers ########### +.. _dyn_controllers-parentmojit: + ParentMojit *********** @@ -172,15 +219,17 @@ ParentMojit } }; ac.composite.execute(cfg,function(data, meta){ - // The 'meta' object containing metadata about the children's binders, assets, configuration, and HTTP header - // info is passed to the callback. This 'meta' object is required for binders to execute and attach content to the DOM. + // The 'meta' object containing metadata about the children's binders, + // assets, configuration, and HTTP header info is passed to the callback. + // This 'meta' object is required for binders to execute and attach content + // to the DOM. ac.done(data, meta); }); } }; - }, '0.0.1', {requires: ['mojito']}); - + }, '0.0.1', {requires: ['mojito', 'mojito-composite-addon']}); +.. _dyn_controllers-dynchild: DynamicChildMojit ***************** @@ -199,33 +248,39 @@ DynamicChildMojit } } }; - }, '0.0.1', {requires: ['mojito']}); + }, '0.0.1', {requires: ['mojito', 'mojito-config-addon']}); +.. _dyn_defined_mojits_exs-templates: Templates ######### -DynamicChildMojit -***************** +.. _dyn_templates-parentmojit: + +ParentMojit +*********** .. code-block:: html <div id="{{mojit_view_id}}"> - {{{content}}} + {{{dynamic_child}}} </div> +.. _dyn_templates-dynchild: -ParentMojit -*********** +DynamicChildMojit +***************** .. code-block:: html <div id="{{mojit_view_id}}"> - {{{dynamic_child}}} + {{{content}}} </div> +.. _dyn_defined_mojits_exs-rendered_views: + Rendered Views ############## @@ -241,32 +296,40 @@ Rendered Views I was called directly and have no parent. +.. _dyn_defined_mojits-dispatch: Using ac._dispatch ================== -Using ``ac._dispatch`` not only allows you to run a dynamically defined child mojit instance like -``ac.composite.execute``, but you also have more fine-grained control over how the child mojit instance runs. -The content from the child mojit's controller may be passed to its template or the child mojit's rendered template -is passed to the parent mojit. +Using ``ac._dispatch`` not only allows you to run a dynamically defined child +mojit instance like ``ac.composite.execute``, but you also have more +fine-grained control over how the child mojit instance runs. The content from +the child mojit's controller may be passed to its template or the child mojit's +rendered template is passed to the parent mojit. +.. _dyn_dispatch-config: Configuring a Child Instance ---------------------------- -Two configuration objects are passed to ``ac._dispatch``, each having a different function. The ``command`` -object defines the instance, the action to execute, the context, and any parameters. This lets the -parent mojit have greater control over its child instances. The ``adapter`` object lets you define custom ``flush``, -``done``, and ``error`` functions for the child mojit instances. +Two configuration objects are passed to ``ac._dispatch``, each having a +different function. The ``command`` object defines the instance, the action +to execute, the context, and any parameters. This lets the parent mojit have +greater control over its child instances. The ``adapter`` object lets you +define custom ``flush``, ``done``, and ``error`` functions for the child mojit +instances. -Although you can also pass the ``ActionContext`` object as the ``adapter`` to use the default ``flush``, ``done``, and ``error`` functions, -it is not recommended because the ``ActionContext`` object contains both parent and child mojit metadata, which could cause unexpected results. +Although you can also pass the ``ActionContext`` object as the ``adapter`` to +use the default ``flush``, ``done``, and ``error`` functions, it is not +recommended because the ``ActionContext`` object contains both parent and child +mojit metadata, which could cause unexpected results. Command Object ############## -In the ``command`` object below, a mojit instance of type ``MessengerMojit`` and the action to execute are -specified. The new mojit instance is also passed parameters. +In the ``command`` object below, a mojit instance of type ``MessengerMojit`` and +the action to execute are specified. The new mojit instance is also passed +parameters. .. code-block:: javascript @@ -283,13 +346,15 @@ specified. The new mojit instance is also passed parameters. } }; - + +.. _dyn_dispatch-adapter: + Adapter Object ############## -In the ``adapter`` object below, the ``ac.done``, ``ac.flush``, or ``ac.error`` are -defined and will override those functions in the child mojit instance. See `Adapter Functions`_ -for more information. +In the ``adapter`` object below, the ``ac.done``, ``ac.flush``, or ``ac.error`` +are defined and will override those functions in the child mojit instance. +See `Adapter Functions`_ for more information. .. code-block:: javascript @@ -302,23 +367,26 @@ for more information. }, error: function(err){ Y.log(err); } }; - + +.. _dyn_dispatch-adapter_funcs: Adapter Functions ################# The functions ``ac.done``, ``ac.flush``, and ``ac.error`` defined in the ``adapter`` -object are actually implemented by the Mojito framework. For example, before ``adapter.done`` -is executed, Mojito runs the ``done`` function defined in +object are actually implemented by the Mojito framework. For example, before +``adapter.done`` is executed, Mojito runs the ``done`` function defined in `output-adapter.common.js <https://github.com/yahoo/mojito/blob/develop/lib/app/addons/ac/output-adapter.common.js>`_, which collects metadata and configuration. +.. _dyn_dispatch-controller: Controller ---------- -The controller of the mojit that is dynamically creating mojit instances defines the mojit -instance and passes custom versions of ``done``, ``flush``, and ``error``. +The controller of the mojit that is dynamically creating mojit instances +defines the mojit instance and passes custom versions of ``done``, ``flush``, +and ``error``. .. code-block:: javascript @@ -350,26 +418,37 @@ instance and passes custom versions of ``done``, ``flush``, and ``error``. }; }, '0.0.1', {requires: ['mojito']}); +.. _dyn_dispatch-templates: Templates --------- -The template that is rendered depends on the ``adapter`` object passed to ``ac._dispatch``. -If you pass the ``ac`` object as the ``adapter`` parameter, as in ``ac._dispatch(command,ac)``, -the ``ac.done`` in the dynamically defined mojit will execute and its template will be -rendered. If you pass a custom ``adapter`` object defining ``done``, you can call ``ac.done`` -inside your defined ``done`` method to pass data to the parent mojit and render its template. +The template that is rendered depends on the ``adapter`` object passed to +``ac._dispatch``. If you pass the ``ac`` object as the ``adapter`` parameter, +as in ``ac._dispatch(command,ac)``, the ``ac.done`` in the dynamically defined +mojit will execute and its template will be rendered. If you pass a custom +``adapter`` object defining ``done``, you can call ``ac.done`` inside your +defined ``done`` method to pass data to the parent mojit and render its +template. + +.. _dyn_dispatch_templates-exs: Examples ######## +.. _dyn_dispatch-templates_ex_one: + Example One *********** -In this example, the mojit ``CreatorMojit`` dynamically creates the child mojit instance of -type ``SpawnedMojit``. The child mojit instance gets data from its parent mojit and then renders its template. -The rendered template is returned to the parent mojit, which inserts the content into its -own template. +In this example, the mojit ``CreatorMojit`` dynamically creates the child +mojit instance of type ``SpawnedMojit``. The child mojit instance gets data +from its parent mojit and then renders its template. The rendered template +is returned to the parent mojit, which inserts the content into its own +template. + + +.. _dyn_dispatch-templates_exs-app_config: Application Configuration ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -387,10 +466,13 @@ Application Configuration } ] +.. _dyn_dispatch-templates_exs-controllers: Controllers ^^^^^^^^^^^ +.. _templates_exs_controllers-creatormojit: + CreatorMojit ```````````` @@ -435,6 +517,7 @@ CreatorMojit }; }, '0.0.1', {requires: ['mojito']}); +.. _templates_exs_controllers-spawnedmojit: SpawnedMojit ```````````` @@ -446,15 +529,22 @@ SpawnedMojit Y.namespace('mojito.controllers')[NAME] = { "index": function(ac) { - ac.done({ "route": ac.params.route('name'), "url": ac.params.url('path'), "body": ac.params.body("message") }); + ac.done({ "route": ac.params.route('name'), + "url": ac.params.url('path'), + "body": ac.params.body("message") + }); } }; }, '0.0.1', {requires: ['mojito']}); +.. _dyn_dispatch-templates_exs-templates: + Templates ^^^^^^^^^ +.. _templates_exs-templates_spawnedmojit: + SpawnedMojit ```````````` @@ -469,6 +559,7 @@ SpawnedMojit </ul> </div> +.. _templates_exs-templates_creatormojit: CreatorMojit ```````````` @@ -480,15 +571,16 @@ CreatorMojit {{{child_slot}}} </div> - +.. _dyn_dispatch-templates_ex_two: Example Two *********** -In this example, the binder invokes its controller to dynamically define an instance of another mojit. -The dynamically defined mojit instance renders its view, which is then sent to the binder to -be attached to the DOM. +In this example, the binder invokes its controller to dynamically define an +instance of another mojit. The dynamically defined mojit instance renders its +view, which is then sent to the binder to be attached to the DOM. +.. _templates_ex_two-app_config: Application Configuration ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -530,10 +622,13 @@ Application Configuration } ] +.. _templates_ex_two-controllers: Controllers ^^^^^^^^^^^ +.. _templates_ex_two-controllers_parentmojit: + ParentMojit ``````````` @@ -556,8 +651,10 @@ ParentMojit ac._dispatch(command, ac); } }; - }, '0.0.1', {requires: ['mojito']}); + }, '0.0.1', {requires: ['mojito', 'mojito-assets-addon']}); + +.. _templates_ex_two-controllers_childmojit: ChildMojit `````````` @@ -572,12 +669,15 @@ ChildMojit ac.done({ "random_content" : content }); } }; - }, '0.0.1', {requires: ['mojito']}); + }, '0.0.1', {requires: ['mojito', 'mojito-assets-addon']}); +.. _templates_ex_two-binders: Binders ^^^^^^^ +.. _templates_ex_two-binders_parentmojit: + ParentMojit ``````````` @@ -604,6 +704,7 @@ ParentMojit }; }, '0.0.1', {requires: ['mojito-client']}); +.. _templates_ex_two-binders_childmojit: ChildMojit `````````` @@ -626,10 +727,14 @@ ChildMojit }; }, '0.0.1', {requires: ['mojito-client']}); + +.. _templates_ex_two-templates: Templates ^^^^^^^^^ +.. _templates_ex_two-templates_parentmojit: + ParentMojit ``````````` @@ -642,6 +747,7 @@ ParentMojit <div id="output"></div> </div> +.. _templates_ex_two-templates_childmojit: ChildMojit `````````` @@ -653,27 +759,40 @@ ChildMojit {{random_content}} </div> +.. _dyn_defined_mojits-execute: Using ac._dispatch with ac.composite.execute ============================================ -You can combine both methods to dynamically define and run a more complex set of mojits. The mojit that initiates the process -uses ``ac._dispatch`` to define and run a parent mojit instance that uses ``ac.composite.execute`` in its controller to define and run child mojit -instances. This chain of running dynamically defined mojit instances can be extended even further if one or more of the child mojit instances -is using ``ac._dispatch`` or ``ac.composite.execute``. When running a set of dynamically defined mojits, you should be aware that -you may run into memory issues. +You can combine both methods to dynamically define and run a more complex +set of mojits. The mojit that initiates the process uses ``ac._dispatch`` to +define and run a parent mojit instance that uses ``ac.composite.execute`` in +its controller to define and run child mojit instances. This chain of running +dynamically defined mojit instances can be extended even further if one or more +of the child mojit instances is using ``ac._dispatch`` or +``ac.composite.execute``. When running a set of dynamically defined mojits, +you should be aware that you may run into memory issues. + +Because the configuration, controllers, and templates are the same when using +``ac._dispatch`` and ``ac.composite.execute`` independently or together, please +see `Using the Composite Addon`_ and `Using ac._dispatch`_ for implementation details. -Because the configuration, controllers, and templates are the same when using ``ac._dispatch`` and ``ac.composite.execute`` -independently or together, please see `Using the Composite Addon`_ and `Using ac._dispatch`_ -for implementation details. + +.. _dyn_defined_mojits-execute_ex: Example ------- -In this example, the ``GrandparentMojit`` uses ``ac._dispatch`` to create a child mojit instance of type ``ParentMojit``, which in -turn creates a child mojit instance of type ``GrandchildMojit``. The child instance of type ``GrandchildMojit`` is executed and its -rendered view is returned to its parent mojit instance of type ``ParentMojit``. The content is then attached -to the parent mojit instance's template, which gets rendered and returned as the response. +In this example, the ``GrandparentMojit`` uses ``ac._dispatch`` to create a +child mojit instance of type ``ParentMojit``, which in turn creates a child +mojit instance of type ``GrandchildMojit``. The child instance of type +``GrandchildMojit`` is executed and its rendered view is returned to its +parent mojit instance of type ``ParentMojit``. The content is then attached +to the parent mojit instance's template, which gets rendered and returned as +the response. + + +.. _execute_ex-app_config: Application Configuration ######################### @@ -721,10 +840,13 @@ Application Configuration } ] +.. _execute_ex-controllers: Controllers ########### +.. _execute_ex-controllers_grandparentmojit: + GrandparentMojit **************** @@ -751,7 +873,7 @@ GrandparentMojit }; }, '0.0.1', {requires: ['mojito']}); - +.. _execute_ex-controllers_parentmojit: ParentMojit *********** @@ -780,8 +902,9 @@ ParentMojit }); } }; - }, '0.0.1', {requires: ['mojito']}); + }, '0.0.1', {requires: ['mojito', 'mojito-composite-addon']}); +.. _execute_ex-controllers_grandchildmojit: GrandchildMojit *************** @@ -798,10 +921,13 @@ GrandchildMojit }, '0.0.1', {requires: ['mojito']}); +.. _execute_ex-templates: Templates ######### +.. _execute_ex-templates_grandchildmojit: + GrandchildMojit *************** @@ -811,6 +937,7 @@ GrandchildMojit <h3>I am the {{whoami}} dynamically defined and run by {{creator}}.</h3> </div> +.. _execute_ex-templates_parentmojit: ParentMojit *********** diff --git a/docs/dev_guide/topics/mojito_testing.rst b/docs/dev_guide/topics/mojito_testing.rst index e68149bc0..d8015a7ef 100644 --- a/docs/dev_guide/topics/mojito_testing.rst +++ b/docs/dev_guide/topics/mojito_testing.rst @@ -3,10 +3,13 @@ Testing ======= Mojito provides a testing framework based on `YUI Test`_ that -allows you to run unit tests for the framework, modules, applications, mojit controllers, mojit -models, and mojit binders. +allows you to run unit tests for modules, applications, mojit controllers, +mojit models, and mojit binders. -The next few sections show you how to run specific tests with the ``mojito`` command. +The next few sections show you how to run specific tests with the ``mojito`` +command. + +.. _mojito_testing-conventions: Conventions =========== @@ -15,60 +18,61 @@ Conventions - ``{app_name}/tests`` - application tests - ``{app_name}/mojits/{mojit_name}/tests`` - mojit tests - - ``{app_name}/autoload/{yui_module}/tests`` - tests for application-level YUI modules - - ``{app_name}/mojits/{mojit_name}/autoload/{yui_module}/tests`` - tests for mojit-level YUI modules + - ``{app_name}/autoload/{yui_module}/tests`` - tests for + application-level YUI modules + - ``{app_name}/mojits/{mojit_name}/autoload/{yui_module}/tests`` - tests for + mojit-level YUI modules - Syntax for the name of the test file: ``{yui_module}.{affinity}-tests.js`` - For example, the name of the unit test YUI module for the ``HelloMojit`` mojit with the ``server`` - affinity would be ``HelloMojit-tests.server.js``. + For example, the name of the unit test YUI module for the ``HelloMojit`` mojit + with the ``server`` affinity would be ``HelloMojit-tests.server.js``. -- The unit test YUI module should include the target module and the ``mojito-test`` module in the - ``requires`` array. The requires array includes the ``mojito-test`` module and the target module ``HelloMojit``: +- The unit test YUI module should include the target module and the ``mojito-test`` + module in the ``requires`` array. The requires array includes the ``mojito-test`` + module and the target module ``HelloMojit``: .. code-block:: javascript { requires: [ 'mojito-test', 'HelloMojit' ] } -.. note:: Test files that are **not** in a ``tests`` directory may be found by Mojito as long as the - file name has the suffix ``-tests``. The suggested practice though is to place all test - files in the ``tests`` directories shown above. - -Framework Tests -=============== - -After you have installed Mojito, you should run the framework test to confirm that Mojito installed -correctly and that Node.js has been given permission to access the file system. - -To test the Mojito framework, run the following: +.. note:: Test files that are **not** in a ``tests`` directory may be found by + Mojito as long as the file name has the suffix ``-tests``. The + suggested practice though is to place all test files in the ``tests`` + directories shown above. -``$ mojito test`` +.. _mojito_testing-application: Application Tests ================= -Running applications tests is much like running the framework tests above. The following command runs -tests for all of the mojits of a Mojito application. +The following command runs tests for all of the mojits of a Mojito application. ``$ mojito test app {path-to-app}/{application-name}`` -To run one specific test in your application, use the following where ``[test-name]`` is either the -YUI module or the module to be tested. +To run one specific test in your application, use the following where ``[test-name]`` is +either the YUI module or the module to be tested. ``$ mojito test app {path-to-app}/{application-name} [test-name]`` +.. _mojito_testing-mojit: + Mojit Tests =========== -You create unit tests for your mojits and execute them also using the ``mojito`` command. Mojit tests -must require (included in the YUI ``require`` array) the module undergoing testing and the Mojito -Test module ``mojito-test``. For example, if the ``Foo`` module was being tested, the ``requires`` -array would include the ``Foo`` and ``mojit-test`` modules as seen -here: ``requires: [ 'Foo', 'mojit-test']`` +You create unit tests for your mojits and execute them also using the ``mojito`` +command. Mojit tests must require (included in the YUI ``require`` array) the +module undergoing testing and the Mojito Test module ``mojito-test``. For +example, if the ``Foo`` module was being tested, the ``requires`` array would +include the ``Foo`` and ``mojit-test`` modules as seen here: +``requires: [ 'Foo', 'mojit-test']`` + +By default, Mojito uses the `YUI Test <http://yuilibrary.com/yuitest/>`_ +framework for the `test harness <http://en.wikipedia.org/wiki/Test_harness>`_ +and assertion functions. Each mojit test will be executed within a YUI +instance along with its required dependencies, so you can be assured to only +have properly scoped values. -By default, Mojito uses the `YUI Test <http://yuilibrary.com/yuitest/>`_ framework for the -`test harness <http://en.wikipedia.org/wiki/Test_harness>`_ and assertion functions. -Each mojit test will be executed within a YUI instance along with its required dependencies, so you -can be assured to only have properly scoped values. +.. _mojit_testing-types: Types of Mojit Tests -------------------- @@ -79,14 +83,17 @@ The following three types of mojit tests exist: - controller tests - model tests +.. _mojito_testing-standards: + Testing Standards ================= -To use the Mojito test harness, you are required to name files and testing modules according to -certain rules. The name of the test file must have the same `affinity <../reference/glossary.html>`_ -as the file being tested and have the string ``-tests`` appended to the affinity. For example, the -mojit controller with the ``common`` affinity would be ``controller.common.js``, so the name of the -test file must be ``controller.common-tests.js``. +To use the Mojito test harness, you are required to name files and testing +modules according to certain rules. The name of the test file must have the +same `affinity <../reference/glossary.html>`_ as the file being tested and +have the string ``-tests`` appended to the affinity. For example, the mojit +controller with the ``common`` affinity would be ``controller.common.js``, +so the name of the test file must be ``controller.common-tests.js``. The ``controller.common.js`` below requires the ``Foo`` module. @@ -96,8 +103,8 @@ The ``controller.common.js`` below requires the ``Foo`` module. ... }); -To test the ``Foo``, module, the the test file ``controller.common-tests.js`` would require the -``Foo-tests`` module as seen below. +To test the ``Foo``, module, the the test file ``controller.common-tests.js`` would +require the ``Foo-tests`` module as seen below. .. code-block:: javascript @@ -105,13 +112,18 @@ To test the ``Foo``, module, the the test file ``controller.common-tests.js`` wo ... }, 'VERSION', {requires: ['mojito-test', 'Foo']}); +.. _mojito_testing-binders: + Binder Tests ============ -You can create multiple binder tests and place them in the ``tests/binders`` directory. For example, -if your binder is ``binders/index.js``, the test file would be -``tests/binders/index.common-test.js``. Notice that the affinity is ``common``, which can be used -for binders on the client or server and is also the default binder test file. +You can create multiple binder tests and place them in the ``tests/binders`` +directory. For example, if your binder is ``binders/index.js``, the test file +would be ``tests/binders/index.common-test.js``. Notice that the affinity is +``common``, which can be used for binders on the client or server and is also +the default binder test file. + +.. _binders_test-ex: Example ------- @@ -146,8 +158,9 @@ Below is the binder ``index.js`` that includes the ``FooBinderIndex`` module: }; }, '0.0.1', {requires: []}); -The test binder file ``tests/binders/index-common-tests.js`` below includes the module -``FooBinderIndex-tests`` and the requires ``array`` includes the ``FooBinderIndex`` module: +The test binder file ``tests/binders/index-common-tests.js`` below includes the +module ``FooBinderIndex-tests`` and the requires ``array`` includes the +``FooBinderIndex`` module: .. code-block:: javascript @@ -178,14 +191,18 @@ The test binder file ``tests/binders/index-common-tests.js`` below includes the }, '0.0.1', {requires: ['mojito-test', 'node', 'FooBinderIndex']}); +.. _mojito_testing-controller: Controller Tests ================ -A mojit can have one or more controllers that have different affinities. For each controller, you -can create create a test controller with the same affinity or use ``controller.common-tests.js``, -which tests controllers with any affinity. For example, ``controller.server.js`` can be tested with -``controller.server-tests.js`` or ``controller.common-tests.js``. +A mojit can have one or more controllers that have different affinities. For each +controller, you can create create a test controller with the same affinity or use +``controller.common-tests.js``, which tests controllers with any affinity. For example, +``controller.server.js`` can be tested with ``controller.server-tests.js`` or +``controller.common-tests.js``. + +.. _controller_tests-ex: Example ------- @@ -196,19 +213,16 @@ The ``controller.server.js`` below requires the ``Foo`` module. YUI.add('Foo', function(Y, NAME) { Y.namespace('mojito.controllers')[NAME] = { - init: function(mojitSpec) { - this.spec = mojitSpec; - }, index: function(ac) { ac.done(); } }; }, '0.0.1', {requires: []}); -To test the controller of the ``Foo`` mojit, create a file in the tests directory called -``controller.common-tests.js`` that includes the ``Foo-tests`` module as seen below. Note that the -reference to the controller is gotten using ``Y.mojito.controller`` or -``Y.mojito.controllers[NAME]``. +To test the controller of the ``Foo`` mojit, create a file in the tests +directory called ``controller.common-tests.js`` that includes the ``Foo-tests`` +module as seen below. Note that the reference to the controller is gotten +using ``Y.mojito.controller`` or ``Y.mojito.controllers[NAME]``. .. code-block:: javascript @@ -241,20 +255,26 @@ reference to the controller is gotten using ``Y.mojito.controller`` or YUITest.TestRunner.add(suite); }, '0.0.1', {requires: ['mojito-test', 'Foo']}); +.. _mojito_testing-mockactioncontext: + Testing with the MockActionContext Object ========================================= -The ``mojito-test`` YUI module allows you to create the mock object ``MockActionContext`` to test -without dependencies. Using the ``MockActionContext`` object, you can easily build an -``ActionContext`` for your controller, addon, and model tests. To learn more information about using -YUI to create mock objects, see +The ``mojito-test`` YUI module allows you to create the mock object +``MockActionContext`` to test without dependencies. Using the +``MockActionContext`` object, you can easily build an ``ActionContext`` +for your controller, addon, and model tests. To learn more information +about using YUI to create mock objects, see `YUI Test Standalone Library: Mock Objects <http://yuilibrary.com/yuitest/#mockobjects>`_. +.. _mockactioncontext_testing-using: + Using the Mock ActionContext ---------------------------- -The following sections will explain the below example code that creates a simple ``MockActionContext`` -that tests the ``done`` function and verifies it was called correctly. +The following sections will explain the below example code that creates a +simple ``MockActionContext`` that tests the ``done`` function and verifies +it was called correctly. .. code-block:: javascript @@ -271,26 +291,33 @@ that tests the ``done`` function and verifies it was called correctly. Y.mojito.controller.actionUnderTest(ac); ac.verify(); + +.. _mockactioncontext_testing-creating: + Creating the MockActionContext Object ##################################### -To mock the ``ActionContext``, the ``mojito-test`` YUI module provides the ``MockActionContext`` -constructor that returns a mocked ``ActionContext`` as shown below: +To mock the ``ActionContext``, the ``mojito-test`` YUI module provides the +``MockActionContext`` constructor that returns a mocked ``ActionContext`` +as shown below: .. code-block:: javascript var ac = new Y.mojito.MockActionContext(); +.. _mockactioncontext_testing-expectations: + Setting Test Expectations ######################### -To test with the ``MockActionContext`` object, you use the ``expect`` method and pass it an -``expectation`` object containing the properties ``method``, ``args``, and ``run``. -These properties, in turn, contain the controller method to test, the function parameters, and the -test function. +To test with the ``MockActionContext`` object, you use the ``expect`` method +and pass it an ``expectation`` object containing the properties ``method``, +``args``, and ``run``. These properties, in turn, contain the controller +method to test, the function parameters, and the test function. -In the code snippet below, the ``expect`` method creates a test for the controller method ``done``, -using the ``YUITest`` module to perform an assertion on the function's return value. +In the code snippet below, the ``expect`` method creates a test for the +controller method ``done``, using the ``YUITest`` module to perform an +assertion on the function's return value. .. code-block:: javascript @@ -302,35 +329,46 @@ using the ``YUITest`` module to perform an assertion on the function's return va } }); +.. _mockactioncontext_testing-configure: + Configuring Mojito to Test MockActionContext Object ################################################### -To configure Mojito to use your ``MockActionContext`` object to run test, use the following: +To configure Mojito to use your ``MockActionContext`` object to run test, +use the following: .. code-block:: javascript Y.mojito.controller.actionUnderTest(ac); -If ``actionUnderTest`` function fails to call the ``done`` function, calls it more than one time, or -calls it with the wrong parameters, the test will fail. +If ``actionUnderTest`` function fails to call the ``done`` function, calls +it more than one time, or calls it with the wrong parameters, the test will +fail. + +.. _mockactioncontext_testing-run: Running the Test -~~~~~~~~~~~~~~~~ +**************** -Finally, run the expectation by call the ``verify`` method from the ``MockActionContext`` object as -seen here: +Finally, run the expectation by call the ``verify`` method from the +``MockActionContext`` object as seen here: .. code-block:: javascript ac.verify(); -.. note:: Expectations for addons, models, and extras will be be verified automatically when you - call the main ``verify`` function from the ``MockActionContext`` object. +.. note:: Expectations for addons, models, and extras will be be verified + automatically when you call the main ``verify`` function from the + ``MockActionContext`` object. + +.. _mockac_testing_expectations-ex: Example Expectations -------------------- +.. _testing_expectations_ex-pass_objs: + Passing Multiple expectation Objects #################################### @@ -358,6 +396,8 @@ You can pass many ``expectation`` objects to the ``expect`` method: } ); +.. _testing_expectations_ex-chain_methods: + Chaining expect Methods ####################### @@ -383,11 +423,13 @@ You can also chain ``expect`` methods: args: ['thepath'] }); +.. _mock_addons: + Mocking Addons -------------- -To use the MockActionContext object to test different addons, you specify the namespaces of the -addons within the ``MockActionContext`` constructor: +To use the MockActionContext object to test different addons, you specify +the namespaces of the addons within the ``MockActionContext`` constructor: .. code-block:: javascript @@ -400,18 +442,20 @@ addons within the ``MockActionContext`` constructor: returns: 'updating, yo' }); +.. _mock_custom_addons: + Mocking Custom Addons ##################### -To create a custom addon that contains functions within a property, you might have an addon that is -used in the following way: +To create a custom addon that contains functions within a property, you might +have an addon that is used in the following way: .. code-block:: javascript ac.customAddon.params.get('key'); -To test the addon, you pass the ``addons`` array with a list of the addons you want to test to the -``MockActionContext`` constructor as seen below: +To test the addon, you pass the ``addons`` array with a list of the addons +you want to test to the ``MockActionContext`` constructor as seen below: .. code-block:: javascript @@ -422,13 +466,16 @@ To test the addon, you pass the ``addons`` array with a list of the addons you w } ); -This will give you a mock object at ``ac.customAddon.params`` from which you can call ``expect``. +This will give you a mock object at ``ac.customAddon.params`` from which you can +call ``expect``. + +.. _mock_models: Mocking Models ############## -To test models with the ``MockActionContext`` object, you pass the ``models`` array with the model -YUI modules as is done with addons: +To test models with the ``MockActionContext`` object, you pass the ``models`` +array with the model YUI modules as is done with addons: .. code-block:: javascript @@ -449,12 +496,17 @@ YUI modules as is done with addons: } ); +.. _mojito_testing-models: + Model Tests =========== -Model tests are largely the same as controller tests, except there can be many of them. The model -tests are placed in the ``tests/models`` directory. You can create multiple model tests or use -``models.common-tests.js`` to test both server and client models. +Model tests are largely the same as controller tests, except there can be +many of them. The model tests are placed in the ``tests/models`` directory. +You can create multiple model tests or use ``models.common-tests.js`` to test +both server and client models. + +.. _mojito_testing_models-ex: Example ------- @@ -471,8 +523,9 @@ The ``model.server.js`` below includes the ``FooModel`` module. }; }, '0.0.1', {requires: []}); -The ``tests/models/models.common-tests.js`` test below includes the ``FooModel-tests`` module and -the ``requires`` array contains the ``FooModel`` module. +The ``tests/models/models.common-tests.js`` test below includes the +``FooModel-tests`` module and the ``requires`` array contains the ``FooModel`` +module. .. code-block:: javascript @@ -496,18 +549,6 @@ the ``requires`` array contains the ``FooModel`` module. YUITest.TestRunner.add(suite); }, '0.0.1', {requires: ['mojito-test', 'FooModel']}); -Module Tests -############ - -You can run specific unit tests for modules of the Mojito framework. When you test a module, Mojito -will look for framework tests found in ``path-to-node/node/mojito/tests``. - -You can provide either the YUI module name of the test or the class it is testing. For example, to -test the module ``foo`` with the test called ``foo-test``, use either of -the following commands: - -- ``$ mojito test foo`` -- ``$ mojito test foo-test`` .. _moj_tests-func_unit: @@ -515,15 +556,16 @@ Functional/Unit Tests ===================== Mojito comes with functional tests that you can run with the npm module -`Arrow <https://github.com/yahoo/arrow/>`_, a testing framework that fuses together JavaScript, -Node.js, PhantomJS, and Selenium. Arrow lets you write tests in -`YUI Test`_ that can be executed on the client or server. -You can also write your own functional/unit tests with Arrow. Mojito recommends that contributors -write Arrow functional/unit tests for their code to accelerate the process of merging pull requests. +`Arrow <https://github.com/yahoo/arrow/>`_, a testing framework that fuses +together JavaScript, Node.js, PhantomJS, and Selenium. Arrow lets you write +tests in `YUI Test`_ that can be executed on the client or server. +You can also write your own functional/unit tests with Arrow. Mojito recommends +that contributors write Arrow functional/unit tests for their code to accelerate +the process of merging pull requests. -The following sections show you how to set up your environment and run the unit and -functional tests that come with Mojito. In the future, we will also provide you with instructions -for writing Arrow tests for your code contributions. +The following sections show you how to set up your environment and run the unit +and functional tests that come with Mojito. In the future, we will also provide +you with instructions for writing Arrow tests for your code contributions. .. _func_unit-builtin: @@ -547,7 +589,7 @@ Macs .. _func_unit-macs_setup: Setting Up -~~~~~~~~~~ +********** #. `Download PhantomJS <http://www.doctor46.com/phantomjs>`_. #. Copy the phantomjs binary to ``/usr/local/bin/``. @@ -569,7 +611,7 @@ Linux .. _func_unit-linux_setup: Setting Up -~~~~~~~~~~ +********** #. Follow the `installation instructions for PhantomJS <http://www.doctor46.com/phantomjs>`_. #. Copy the phantomjs binary to ``/usr/local/bin/``. @@ -606,11 +648,12 @@ Running Tests .. _func_unit_run-batch: Running Batch Tests -~~~~~~~~~~~~~~~~~~~ +******************* -The following instructions show you how to run Arrow tests with the wrapper script ``run.js``, -which allows you to run batch tests. For example, you can use ``run.js`` to run all of the Mojito -functional or unit tests with one command. +The following instructions show you how to run Arrow tests with the +wrapper script ``run.js``, which allows you to run batch tests. For +example, you can use ``run.js`` to run all of the Mojito functional +or unit tests with one command. #. Clone the Mojito repository. @@ -627,8 +670,9 @@ functional or unit tests with one command. #. Run the unit tests for the framework and client: ``$ ./run.js test -u --path unit --group fw,client,server`` -#. You can also run all the functional tests with the below command. The functional tests - may take some time to complete, so you may want to terminate the tests with **Ctl-C**. +#. You can also run all the functional tests with the below command. The + functional tests may take some time to complete, so you may want to + terminate the tests with **Ctl-C**. ``$ ./run.js test -f --path func`` #. To view the test reports (in JSON or XML) in the following directories: @@ -636,15 +680,16 @@ functional or unit tests with one command. - ``$ ./unit/artifacts/arrowreport/`` - ``$ ./func/artifacts/arrowreport/`` -.. note:: You will not get a report if you terminated any tests before they completed. - Also, Selenium will display the error message ``SeleniumDriver - Failed to collect the - test report`` if a previously generated report exists. +.. note:: You will not get a report if you terminated any tests before they + completed. Also, Selenium will display the error message + ``SeleniumDriver - Failed to collect the test report`` if a + previously generated report exists. .. _func_unit_run-arrow: Using Arrow to Run Tests -~~~~~~~~~~~~~~~~~~~~~~~~ +************************ You can also separately run unit and functional tests directly with the ``arrow`` command. You pass Arrow a test descriptor, which @@ -652,12 +697,12 @@ is a JSON configuration file that describes and organizes your tests. For an overview of Arrow and the command-line options, see the `Arrow README <https://github.com/yahoo/arrow/blob/master/README.md>`_. -In the following steps, you'll start a routing application, run a test with Arrow, -and then look at the test reports. Afterward, you should be able to +In the following steps, you'll start a routing application, run a test with +Arrow, and then look at the test reports. Afterward, you should be able to run some of the other tests included with Mojito. -#. Start Selenium in the background if it is not running already. You can confirm that it's running - by going to http://127.0.0.1:4444/wd/hub/static/resource/hub.html. +#. Start Selenium in the background if it is not running already. You can + confirm that it's running by going to http://127.0.0.1:4444/wd/hub/static/resource/hub.html. #. Change to the directory containing the routing test application. ``$ cd mojito/tests/func/applications/frameworkapp/routing`` @@ -670,11 +715,13 @@ run some of the other tests included with Mojito. #. Launch Firefox with ``arrow_selenium``. ``$ arrow_selenium --open=firefox`` -#. After Firefox has launched, run the functional routing tests with Arrow with the ``arrow`` command, - the test descriptor, and the option ``--browser=reuse``: +#. After Firefox has launched, run the functional routing tests with Arrow + with the ``arrow`` command, the test descriptor, and the option + ``--browser=reuse``: ``$ arrow routingtest_descriptor.json --browser=reuse`` -#. You should see the functional tests running in Firefox testing different routing paths. +#. You should see the functional tests running in Firefox testing different + routing paths. #. As with running the ``run.js`` script, Arrow will generate reports containing the results of the tests, but the report names will match the name of the test descriptor and be located in the current working directory. Thus, diff --git a/docs/dev_guide/topics/mojito_using_contexts.rst b/docs/dev_guide/topics/mojito_using_contexts.rst index 48401a85a..e312c1035 100644 --- a/docs/dev_guide/topics/mojito_using_contexts.rst +++ b/docs/dev_guide/topics/mojito_using_contexts.rst @@ -1,43 +1,53 @@ - - ============================ Using Context Configurations ============================ +.. _context_configs-intro: + Introduction ============ -Context configurations are how Mojito enables different configurations to be used based on various -runtime factors. Many factors are predefined such as language and device, but you can -create custom ones as well. These runtime factors are called **contexts** in Mojito -and are mapped to user-defined configurations. For example, you could set the configuration -``logLevel`` to ``ERROR`` in the production context and set it to ``INFO`` in the development -context. +Context configurations are how Mojito enables different configurations to be +used based on various runtime factors. Many factors are predefined such as +language and device, but you can create custom ones as well. These runtime +factors are called **contexts** in Mojito and are mapped to user-defined +configurations. For example, you could set the configuration +``logLevel`` to ``ERROR`` in the production context and set it to ``INFO`` +in the development context. +.. _context_configs_intro-why: Why Use Context Configurations? ------------------------------- Context configurations make it possible to do the following: -- Create sets of configurations associated with environments without affecting the application - running with the *master* configurations ``"setting: ["master"]``. -- Customize content for users: Applications can dynamically apply language and device - configurations by determining the user's language preferences and the device making the HTTP request. +- Create sets of configurations associated with environments without affecting + the application running with the *master* configurations + ``"setting: ["master"]``. +- Customize content for users: Applications can dynamically apply language and + device configurations by determining the user's language preferences and the + device making the HTTP request. +.. _context_configs-what: What is a Context? ================== -The context is the runtime parameters that are either statically set (base context) -on the command line or dynamically set (request context) in the HTTP headers and/or the -request query string. The configurations for request contexts override those of the base context. +The context is the runtime parameters that are either statically set +(base context) on the command line or dynamically set (request context) +in the HTTP headers and/or the request query string. The configurations +for request contexts override those of the base context. +.. _context_configs_what-base: Base Context ------------ -The base context is statically set with the ``--context`` option when you start an application. +The base context is statically set with the ``--context`` option when you +start an application. + +.. _context_base-syntax: Syntax ###### @@ -46,47 +56,64 @@ The base context has the following syntax: ``"key1:value1[,key2:value2]"`` +.. _context_base-ex: + Example ####### -The following starts the application with the base context ``environment:production``: +The following starts the application with the base context +``environment:production``: ``$ mojito start --context "environment:production"`` +.. _context_configs_what-request: + Request Contexts ---------------- -Contexts that are dynamically invoked by HTTP requests are called request contexts. When Mojito -receives an HTTP request that specifies a context, the configurations mapped to that context will be -dynamically applied. The contexts can be specified in HTTP request as a parameter in the query +Contexts that are dynamically invoked by HTTP requests are called request +contexts. When Mojito receives an HTTP request that specifies a context, +the configurations mapped to that context will be dynamically applied. +The contexts can be specified in HTTP request as a parameter in the query string or in the HTTP header. +.. _context_request-headers: + Request Headers ############### -The contexts for languages can be requested using the HTTP header ``Accept-Language``. After -starting an application with the context ``"environment:testing"``, you can dynamically apply the -configurations for the context ``"environment:testing,lang:fr"`` by sending the HTTP header -``"Accept-Language: fr"``. In the same way, the contexts for devices can be requested using the HTTP -header ``User-Agent``. The configurations for the context "device:android" could be requested with -the HTTP header ``"User-Agent: Mozilla/5.0 (Linux; U; Android 2.3; en-us)"``. +The contexts for languages can be requested using the HTTP header +``Accept-Language``. After starting an application with the context +``"environment:testing"``, you can dynamically apply the configurations +for the context ``"environment:testing,lang:fr"`` by sending the HTTP +header ``"Accept-Language: fr"``. In the same way, the contexts for +devices can be requested using the HTTP header ``User-Agent``. The +configurations for the context "device:android" could be requested +with the HTTP header ``"User-Agent: Mozilla/5.0 (Linux; U; Android 2.3; en-us)"``. + +.. _context_request-query_str: Query String Parameters ####################### The key and value pairs in the context are dynamically set by the query string. +.. _request_query_str-syntax: + Syntax `````` ``?key1=value1,key2=value2`` +.. _request_query_str-ex: + Example ``````` -For example, if an application is started with the base context ``"environment:testing"`` and you -want to dynamically apply the context ``"environment:testing,device:iphone"``, you could append the -following query string to the application URL: +For example, if an application is started with the base context +``"environment:testing"`` and you want to dynamically apply the context +``"environment:testing,device:iphone"``, you could append the following +query string to the application URL: ``?device=iphone`` @@ -97,10 +124,11 @@ following query string to the application URL: Mojito Predefined Contexts -------------------------- -The following lists the contexts that are defined by Mojito. You can define configurations for these -predefined contexts. You can combine multiple contexts to form a compound context as well. For -example, if you wanted a context to map to configurations for Android devices in a testing -environment, you could use the following compound context: ``"environment:test,device:android"`` +The following lists the contexts that are defined by Mojito. You can define +configurations for these predefined contexts. You can combine multiple contexts +to form a compound context as well. For example, if you wanted a context to map +to configurations for Android devices in a testing environment, you could use +the following compound context: ``"environment:test,device:android"`` - ``environment:development`` - ``environment:production`` @@ -120,27 +148,29 @@ environment, you could use the following compound context: ``"environment:test,d You can view the supported BCP 47 language tags and default contexts in the -`dimensions.json <https://github.com/yahoo/mojito/blob/develop/lib/dimensions.json>`_ file -of Mojito. You can also :ref:`create custom contexts <context_configs-custom>` if the Mojito -default contexts don't meet the needs of your application. +`dimensions.json <https://github.com/yahoo/mojito/blob/develop/lib/dimensions.json>`_ +file of Mojito. You can also :ref:`create custom contexts <context_configs-custom>` +if the Mojito default contexts don't meet the needs of your application. +.. _context_configs-resolultion: How Does Mojito Resolve Context Configurations? =============================================== When a request is made to a Mojito application, Mojito has to resolve -configurations, defined contexts (``dimensions.json``), and the base/requested contexts -before the correct context configurations can be applied. +configurations, defined contexts (``dimensions.json``), and the base/requested +contexts before the correct context configurations can be applied. The following are the steps taken by Mojito to apply the correct context configurations: #. **Determines Valid Contexts:** - Mojito looks for a local ``dimensions.json``. If one is found, Mojito replaces - Mojito's ``dimensions.json`` with it. Mojito then uses ``dimensions.json`` to determine - which contexts are valid. Contexts defined earlier in ``dimensions.json`` override - contexts defined later in the file. + Mojito looks for a local ``dimensions.json``. If one is found, Mojito + replaces Mojito's ``dimensions.json`` with it. Mojito then uses + ``dimensions.json`` to determine which contexts are valid. Contexts + defined earlier in ``dimensions.json`` override contexts defined later + in the file. #. **Merges Configurations** @@ -151,68 +181,80 @@ configurations: #. **Determines Context** - - Mojito checks if a base context was specified (statically) on the command line with - the ``--context`` option. + - Mojito checks if a base context was specified (statically) on the command + line with the ``--context`` option. - When Mojito receives an HTTP request, it looks for a request context in - the query string, HTTP headers, or through the execution of a child mojit with configuration - information. - - Mojito merges the base context (if any) with the request context (if any). For example, - if the base context is ``"environment:develop``" and the request context found in the query string - is ``"?lang=de"``, then the compound context in the ``setting`` array in - configuration files would be ``["environment:development", "lang:de"]``. - - If no base or request context is found, Mojito then uses the default context ``master``. + the query string, HTTP headers, or through the execution of a child mojit + with configuration information. + - Mojito merges the base context (if any) with the request context (if any). + For example, if the base context is ``"environment:develop``" and the + request context found in the query string is ``"?lang=de"``, then the + compound context in the ``setting`` array in configuration files would + be ``["environment:development", "lang:de"]``. + - If no base or request context is found, Mojito then uses the default + context ``master``. #. **Resolves Context Configurations** - Mojito then searches for configurations associated with the determined context. - The contexts are found in the ``setting`` object in configuration files. - Mojito will use the more qualified contexts if present over more general contexts. - For example, if the merged base and request context is ``"environment:prod, device:iphone"``, - then Mojito will use it over either ``"device:iphone"`` or ``"env:prod"``. If - ``"environment:prod, device:iphone"`` is not present, Mojito will use the request context - over the base context as the resolved context. + Mojito then searches for configurations associated with the determined + context. The contexts are found in the ``setting`` object in configuration + files. Mojito will use the more qualified contexts if present over more + general contexts. For example, if the merged base and request context is + ``"environment:prod, device:iphone"``, then Mojito will use it over either + ``"device:iphone"`` or ``"env:prod"``. If ``"environment:prod, device:iphone"`` + is not present, Mojito will use the request context over the base context + as the resolved context. - #. **Applies Context Configuration** Mojito applies the configurations associated with the resolved context. - +.. _context_configs-define: Defining Configurations for Contexts ==================================== -Configurations for contexts are defined in the application configuration file ``application.json``. -Routing configurations for contexts are defined in the routing configuration file ``routes.json``. -Default configurations are defined in the ``defaults.json`` file of a mojit. All configurations are -merged when an application starts. The configuration values in ``application.json`` override those -in ``defaults.json``. +Configurations for contexts are defined in the application configuration file +``application.json``. Routing configurations for contexts are defined in the +routing configuration file ``routes.json``. Default configurations are defined +in the ``defaults.json`` file of a mojit. All configurations are merged when an +application starts. The configuration values in ``application.json`` override +those in ``defaults.json``. + +.. _context_configs_define-obj: Configuration Objects --------------------- -The ``application.json`` and ``routes.json`` files in the application directory and the -``defaults.json`` file in a mojit's directory consist of an array of configuration objects. The -configuration object has a ``settings`` array that specifies the context. The configuration objects -in ``application.json`` also have a ``specs`` object containing mojit instances, which may also have -a ``config`` object that has data in the form of key-value pairs. The configuration objects in -``defaults.json`` do not have a ``specs`` object because they do not define mojits, but do have a -``config`` object for storing key-value pairs. The ``routes.json`` file specifies routing -configuration such as the path, HTTP methods, actions, and routing parameters, but does not contain -a ``specs`` or a ``config`` object. +The ``application.json`` and ``routes.json`` files in the application directory +and the ``defaults.json`` file in a mojit's directory consist of an array of +configuration objects. The configuration object has a ``settings`` array that +specifies the context. The configuration objects in ``application.json`` also +have a ``specs`` object containing mojit instances, which may also have a +``config`` object that has data in the form of key-value pairs. The configuration +objects in ``defaults.json`` do not have a ``specs`` object because they do not +define mojits, but do have a ``config`` object for storing key-value pairs. The +``routes.json`` file specifies routing configuration such as the path, HTTP +methods, actions, and routing parameters, but does not contain a ``specs`` or +a ``config`` object. + +.. _context_configs_obj-setting: setting ####### -The ``settings`` array specifies the context or the default ("master") that is then mapped to -configurations. +The ``settings`` array specifies the context or the default ("master") that is +then mapped to configurations. + +.. _context_obj_setting-default: Default Configurations `````````````````````` -Default configurations are used when no context is given. These configurations are found in the -object where the settings array has the string "master" as seen below. +Default configurations are used when no context is given. These configurations +are found in the object where the settings array has the string "master" as +seen below. .. code-block:: javascript @@ -226,6 +268,8 @@ object where the settings array has the string "master" as seen below. ... ] +.. _context_obj_setting-simple: + Simple Context Configuration ```````````````````````````` @@ -244,11 +288,13 @@ The context is specified in the ``settings`` array of the configuration object. ... ] +.. _context_obj_setting-compound: + Compound Context Configuration `````````````````````````````` -Compound contexts are specified in the settings array as a series of contexts separated by commas -as seen below. +Compound contexts are specified in the settings array as a series of contexts +separated by commas as seen below. .. code-block:: javascript @@ -262,6 +308,8 @@ as seen below. }, ... ] + +.. _context_obj_setting-routing: Routing Context Configuration ````````````````````````````` @@ -283,6 +331,7 @@ Routing Context Configuration } ] +.. _context_configs_obj-specs: specs ##### @@ -304,6 +353,8 @@ The ``specs`` object contains the mojit instances associated with a context. ... ] +.. _context_configs_obj-config: + config ###### @@ -327,15 +378,20 @@ The ``config`` object stores configuration for a mojit that is mapped to the con ... ] +.. _context_configs_define-exs: + Examples -------- +.. _context_configs_exs-applicationjson: + application.json ################ -The configuration objects in ``application.json`` below define default configurations and three -context configurations. The last context configuration contains two strings containing key-value -pairs and is, thus, called a compound context configuration. +The configuration objects in ``application.json`` below define default +configurations and three context configurations. The last context configuration +contains two strings containing key-value pairs and is, thus, called a compound +context configuration. .. code-block:: javascript @@ -391,8 +447,8 @@ pairs and is, thus, called a compound context configuration. defaults.json ############# -The configuration ``gamma`` in the example ``defaults.json`` below is mapped to contexts for -languages. +The configuration ``gamma`` in the example ``defaults.json`` below is mapped +to contexts for languages. .. code-block:: javascript @@ -418,7 +474,9 @@ languages. } } ] - + +.. _context_configs_exs-routesjson: + routes.json ########### @@ -443,22 +501,24 @@ routes.json } ] - +.. _context_configs-dynamic: Dynamically Changing Configurations =================================== -You may dynamically change the configurations for any context by having a parent mojit execute a -child mojit with new configurations. This is different than getting different configurations by -requesting a new context or specifying a different base context. Regardless of the context being -used, you can use the same context and change the configurations by executing a child mojit with new -configurations. The parent mojit uses the ``execute`` method of the -`Composite addon <../../api/classes/Composite.common.html>`_ to execute the child mojit. -Let's look at an example to see how it works. +You may dynamically change the configurations for any context by having a parent +mojit execute a child mojit with new configurations. This is different than +getting different configurations by requesting a new context or specifying a +different base context. Regardless of the context being used, you can use the +same context and change the configurations by executing a child mojit with +new configurations. The parent mojit uses the ``execute`` method of the +`Composite addon <../../api/classes/Composite.common.html>`_ to execute the +child mojit. Let's look at an example to see how it works. -In the example controller below, if the ``child`` parameter is found in the routing, query string, -or request body, a child instance with its own configuration is executed, allowing the application -to add new or change configurations of the current context. +In the example controller below, if the ``child`` parameter is found in the +routing, query string, or request body, a child instance with its own +configuration is executed, allowing the application to add new or change +configurations of the current context. .. code-block:: javascript @@ -471,8 +531,9 @@ to add new or change configurations of the current context. "type": "Child", "action": "index", "config": { - "alpha": "Creating a new 'alpha' key or replacing the value of the alpha key mapped - to the context being used. The context, however, does not change." + "alpha": "Creating a new 'alpha' key or replacing the value of the alpha + key mapped to the context being used. The context, however, does + not change." } } } @@ -489,7 +550,7 @@ to add new or change configurations of the current context. } } }; - }, '0.0.1', {requires: ['mojito']}); + }, '0.0.1', {requires: ['mojito', 'mojito-config-addon', 'mojito-params-addon', mojito-composite-addon']}); .. _context_configs-custom: @@ -497,39 +558,50 @@ to add new or change configurations of the current context. Creating Custom Contexts ======================== -The Mojito framework defines default contexts that developers can map configurations to. These -default contexts are defined in the file ``dimensions.json <https://github.com/yahoo/mojito/blob/develop/source/lib/dimensions.json>`_ -found in the Mojito source code. Developers can create an application-level ``dimensions.json`` to -define custom contexts that can be mapped to configurations as well. +The Mojito framework defines default contexts that developers can map +configurations to. These default contexts are defined in the file +``dimensions.json <https://github.com/yahoo/mojito/blob/develop/source/lib/dimensions.json>`_ +found in the Mojito source code. Developers can create an application-level +``dimensions.json`` to define custom contexts that can be mapped to configurations +as well. + +The local ``dimensions.json`` replaces the Mojito's ``dimensions.json``, so to +create custom contexts, you will need to copy Mojito's ``dimension.json`` to +your application directory and then add your custom contexts to the file. +Defining and applying configurations for custom contexts is done in the same +way as for default contexts. -The local ``dimensions.json`` replaces the Mojito's ``dimensions.json``, so to create custom -contexts, you will need to copy Mojito's ``dimension.json`` to your application directory and -then add your custom contexts to the file. Defining and applying configurations for custom contexts -is done in the same way as for default contexts. +.. _context_configs_custom-create: Who Should Create Custom Contexts? ---------------------------------- -Developers who create applications that require a degree of personalization that extends beyond -language and device would be good candidates to create custom contexts. Before beginning to create -your own ``dimensions.json`` file, you should review the :ref:`contexts-predefined` to make sure that -you truly need custom contexts. +Developers who create applications that require a degree of personalization +that extends beyond language and device would be good candidates to create +custom contexts. Before beginning to create your own ``dimensions.json`` file, +you should review the :ref:`contexts-predefined` to make sure that you truly +need custom contexts. + +.. _context_configs_custom-dimensions: Dimensions File --------------- -The key-value pairs of the context are defined in the ``dimensions.json`` file in the application -directory. Once contexts are defined in the ``dimensions.file``, you can then map configurations to -those contexts. If your application has configurations for a context that has not been defined by -Mojito or at the application level in ``dimensions.json``, an error will prevent you from starting -the application. +The key-value pairs of the context are defined in the ``dimensions.json`` +file in the application directory. Once contexts are defined in the +``dimensions.file``, you can then map configurations to those contexts. +If your application has configurations for a context that has not been +defined by Mojito or at the application level in ``dimensions.json``, +an error will prevent you from starting the application. + +.. _dimensions-syntax: Syntax for JavaScript Object ############################ -In the ``dimension.json`` file, the ``dimensions`` array contains JavaScript objects that define the -contexts. The keys of the context are the names of the objects, -and the values are the object's properties as seen below. +In the ``dimension.json`` file, the ``dimensions`` array contains JavaScript +objects that define the contexts. The keys of the context are the names of +the objects, and the values are the object's properties as seen below. .. code-block:: javascript @@ -547,10 +619,13 @@ and the values are the object's properties as seen below. } } +.. _dimensions-ex: + Example dimensions.js -````````````````````` +##################### -Based on the example ``dimensions.json`` below, the following are valid contexts: +Based on the example ``dimensions.json`` below, the following are +valid contexts: - ``"account_type:basic"`` - ``"account_type:premium"`` diff --git a/lib/app/addons/ac/deploy.server.js b/lib/app/addons/ac/deploy.server.js index 1f8278fbc..cdab14b12 100644 --- a/lib/app/addons/ac/deploy.server.js +++ b/lib/app/addons/ac/deploy.server.js @@ -16,8 +16,6 @@ YUI.add('mojito-deploy-addon', function(Y, NAME) { 'use strict'; - var fs = require('fs'); - /** * <strong>Access point:</strong> <em>ac.deploy.*</em> * Provides ability to create client runtime deployment HTML @@ -59,7 +57,11 @@ YUI.add('mojito-deploy-addon', function(Y, NAME) { var store = this.rs, contextServer = this.ac.context, + appConfigServer = store.getAppConfig(contextServer), + appGroupConfig = store.yui.getAppGroupConfig(), + seedFiles = store.yui.getAppSeedFiles(contextServer), + contextClient, appConfigClient, yuiConfig = {}, @@ -90,23 +92,43 @@ YUI.add('mojito-deploy-addon', function(Y, NAME) { // a fragment to prepend to the path attribute when // when building combo urls root: Y.version + '/build/', - seed: "/static/combo?yui-base.js&loader-base.js&" + - "loader-yui3.js&loader-app-base{langPath}.js&loader.js" + groups: { + app: appGroupConfig + } }, ((appConfigClient.yui && appConfigClient.yui.config) || {}), { lang: contextServer.lang // same as contextClient.lang }); + // yui.config goes through a different channel (yuiConfig), + // so we should remove it from the appConfigClient. + if (appConfigClient.yui.config) { + delete appConfigClient.yui.config; + } + - // adjusting the seed based on {langPath} to facilitate - // the customization of the combo url. - yuiConfig.seed = yuiConfig.seed.replace('{langPath}', - (contextServer.lang ? '_' + contextServer.lang : '')); + // Set the YUI seed file to use on the client. This has to be done + // before any other scripts are added and can be controlled through + // application.json->yui->config->seed in a form of + // a array with the list of modules or fullpath urls. + if (appGroupConfig.combine === false) { - clientConfig.store = store.serializeClientStore(contextClient); + // if the combo is disabled, then we need to insert one by one + // this is useful for offline and hybrid apps where the combo + // does not work. + for (i = 0; i < seedFiles.length; i += 1) { + assetHandler.addAsset('js', 'top', appGroupConfig.base + + appGroupConfig.root + seedFiles[i]); + } + + } else { + + // using combo for the seed files + assetHandler.addAsset('js', 'top', appGroupConfig.comboBase + + appGroupConfig.root + seedFiles.join(appGroupConfig.comboSep + + appGroupConfig.root)); + + } - // Set the YUI URL to use on the client (This has to be done - // before any other scripts are added) - assetHandler.addAsset('js', 'top', yuiConfig.seed); // adding the default module for the Y.use statement in the client initialModuleList['mojito-client'] = true; @@ -122,10 +144,8 @@ YUI.add('mojito-deploy-addon', function(Y, NAME) { clientConfig.binderMap = binderMap; - // TODO: [Issue 65] Split the app config in to server client - // sections. // we need the app config on the client for log levels (at least) - clientConfig.appConfig = clientConfig.store.appConfig; + clientConfig.appConfig = appConfigClient; // this is mainly used by html5app pathToRoot = this.ac.http.getHeader('x-mojito-build-path-to-root'); @@ -134,7 +154,6 @@ YUI.add('mojito-deploy-addon', function(Y, NAME) { } clientConfig.routes = this.ac.url.getRouteMaker().getComputedRoutes(); - delete clientConfig.store; // Unicode escape the various strings in the config data to help // fight against possible script injection attacks. diff --git a/lib/app/addons/ac/intl.common.js b/lib/app/addons/ac/intl.common.js index aac9e71e1..530084368 100644 --- a/lib/app/addons/ac/intl.common.js +++ b/lib/app/addons/ac/intl.common.js @@ -36,8 +36,11 @@ YUI.add('mojito-intl-addon', function(Y, NAME) { * @return {string|Object} translated string for label or if no label was provided an object containing all resources. */ lang: function(label, args) { - Y.Intl.setLang(this.ac.type, this.ac.context.lang); - var string = Y.Intl.get(this.ac.type, label); + var lang, string; + lang = Y.mojito.util.findClosestLang(this.ac.context.lang, this.ac.instance.langs) || + this.ac.instance.defaultLang || 'en'; + Y.Intl.setLang(this.ac.instance.controller, lang); + string = Y.Intl.get(this.ac.instance.controller, label); if (string && args) { // simple string substitution return Y.Lang.sub(string, Y.Lang.isString(args) ? [args] : args); @@ -53,8 +56,11 @@ YUI.add('mojito-intl-addon', function(Y, NAME) { * @return {string} formatted data for language. */ formatDate: function(date) { + var lang = Y.mojito.util.findClosestLang(this.ac.context.lang, this.ac.instance.langs) || + this.ac.instance.defaultLang || 'en'; //Y.log('Formatting date (' + date + ') in lang "' + - // this.ac.context.lang + '"', 'debug', NAME); + // lang + '"', 'debug', NAME); + Y.Intl.setLang('datatype-date-format', lang); return Y.DataType.Date.format(date, {format: '%x'}); } }; @@ -65,6 +71,7 @@ YUI.add('mojito-intl-addon', function(Y, NAME) { 'intl', 'datatype-date', 'mojito', + 'mojito-util', 'mojito-config-addon' ]}); diff --git a/lib/app/addons/rs/url.js b/lib/app/addons/rs/url.js index dde417fc0..4ddd9fa5f 100644 --- a/lib/app/addons/rs/url.js +++ b/lib/app/addons/rs/url.js @@ -4,7 +4,7 @@ * See the accompanying LICENSE file for terms. */ -/*jslint anon:true, sloppy:true, nomen:true, stupid:true*/ +/*jslint anon:true, sloppy:true, nomen:true, stupid:true, node:true */ /*global YUI*/ @@ -18,10 +18,19 @@ */ YUI.add('addon-rs-url', function(Y, NAME) { - var libfs = require('fs'), + 'use strict'; + + var libfs = require('fs'), + liburl = require('url'), libpath = require('path'), existsSync = libfs.existsSync || libpath.existsSync, - URL_PARTS = ['frameworkName', 'appName', 'prefix']; + URL_PARTS = ['frameworkName', 'appName', 'prefix'], + // TODO: needs a more future-proof way to do this + SHARED_STATIC_URLS = { + 'asset-ico-favicon': '/favicon.ico', + 'asset-txt-robots': '/robots.txt', + 'asset-xml-crossdomain': '/crossdomain.xml' + }; function RSAddonUrl() { RSAddonUrl.superclass.constructor.apply(this, arguments); @@ -61,9 +70,6 @@ YUI.add('addon-rs-url', function(Y, NAME) { this.config[part] = defaults[part] || ''; } } - - // FUTURE: deprecate appConfig.assumeRollups - this.assumeRollups = this.config.assumeRollups || appConfig.assumeRollups; }, @@ -98,10 +104,7 @@ YUI.add('addon-rs-url', function(Y, NAME) { } // Server-only framework mojits like DaliProxy and HTMLFrameMojit - // should never have URLs associated with them. This never used - // to be an issue until we added the "assumeRollups" feature to - // preload JSON specs for specific mojits during the compile step - // (`mojito compile json`) for Livestand. + // should never have URLs associated with them. if ('shared' !== mojit && 'mojito' === mojitRes.source.pkg.name) { mojitControllerRess = store.getResourceVersions({mojit: mojit, id: 'controller--controller'}); if (mojitControllerRess.length === 1 && @@ -124,7 +127,6 @@ YUI.add('addon-rs-url', function(Y, NAME) { ress = store.getResourceVersions({mojit: mojit}); for (r = 0; r < ress.length; r += 1) { res = ress[r]; - skip = false; if ('config' === res.type) { skip = true; @@ -177,21 +179,32 @@ YUI.add('addon-rs-url', function(Y, NAME) { _calcResourceURL: function(res, mojitRes) { var fs = res.source.fs, relativePath = fs.fullPath.substr(fs.rootDir.length + 1), - urlParts = [], - rollupParts = [], - rollupFsPath; + urlParts = [liburl.resolve('/', (this.config.prefix || 'static'))]; - // Don't clobber a URL calculated by another RS addon. - if (res.hasOwnProperty('url')) { + // Don't clobber a URL calculated by another RS addon, or bother to + // proceed for server affinity resources that don't need uris + if (res.hasOwnProperty('url') || ('server' === res.affinity.affinity)) { return; } - if (this.config.prefix) { - urlParts.push(this.config.prefix); - rollupParts.push(this.config.prefix); + if (res.yui && res.yui.name) { + // any yui module in our app will have a fixed path + // that has to be really short to optimize combo + // urls as much as possible. This url will also work + // in conjuntion with "base", "comboBase" and "root" + // from application.json->yui->config + urlParts.push(res.yui.name + '.js'); + res.url = urlParts.join('/'); + return; } + // FUTURE: routes.json can specify URLs for static resources + if ('shared' === res.mojit) { + if (SHARED_STATIC_URLS[res.id]) { + res.url = SHARED_STATIC_URLS[res.id]; + return; + } if ('mojito' === res.source.pkg.name) { if (this.config.frameworkName) { urlParts.push(this.config.frameworkName); @@ -201,42 +214,23 @@ YUI.add('addon-rs-url', function(Y, NAME) { urlParts.push(this.config.appName); } } - // fw resources are also put into the app-level rollup - if (res.yui && res.yui.name) { - if (this.config.appName) { - rollupParts.push(this.config.appName); - } - rollupParts.push('rollup.client.js'); - rollupFsPath = libpath.join(this.appRoot, 'rollup.client.js'); - } } else { if ('mojit' === res.type) { urlParts.push(res.name); } else { urlParts.push(res.mojit); } - if (res.yui && res.yui.name) { - rollupParts.push(res.mojit); - rollupParts.push('rollup.client.js'); - rollupFsPath = libpath.join(mojitRes.source.fs.fullPath, 'rollup.client.js'); - } } if ('mojit' === res.type) { if ('shared' !== res.name) { - res.url = '/' + urlParts.join('/'); + res.url = urlParts.join('/'); } return; } urlParts.push(relativePath); - - if (rollupFsPath && (this.assumeRollups || existsSync(rollupFsPath))) { - res.url = '/' + rollupParts.join('/'); - fs.rollupPath = rollupFsPath; - } else { - res.url = '/' + urlParts.join('/'); - } + res.url = urlParts.join('/'); } diff --git a/lib/app/addons/rs/yui.js b/lib/app/addons/rs/yui.js index 3ee275cd0..aec21906f 100644 --- a/lib/app/addons/rs/yui.js +++ b/lib/app/addons/rs/yui.js @@ -4,7 +4,7 @@ * See the accompanying LICENSE file for terms. */ -/*jslint anon:true, sloppy:true, nomen:true, stupid:true*/ +/*jslint anon:true, nomen:true, stupid:true, continue:true, node:true*/ /*global YUI*/ @@ -19,16 +19,102 @@ */ YUI.add('addon-rs-yui', function(Y, NAME) { + 'use strict'; + var libfs = require('fs'), libpath = require('path'), libvm = require('vm'), + libmime = require('mime'), + WARN_SERVER_MODULES = /\b(dom-[\w\-]+|node-[\w\-]+|io-upload-iframe)/ig, MODULE_SUBDIRS = { autoload: true, tests: true, yui_modules: true }, - intlPath; + + yuiSandboxFactory = require(libpath.join(__dirname, '..', '..', '..', 'yui-sandbox.js')), + syntheticStat = null, + + MODULE_META_ENTRIES = ['path', 'requires', 'use', 'optional', 'skinnable', 'after', 'condition'], + // TODO: revisit this list with @davglass + MODULE_META_PRIVATE_ENTRIES = ['after', 'expanded', 'supersedes', 'ext', '_parsed', '_inspected', 'skinCache', 'langCache'], + + REGEX_LANG_TOKEN = /\"\{langToken\}\"/g, + REGEX_LANG_PATH = /\{langPath\}/g, + REGEX_LOCALE = /\_([a-z]{2}(-[A-Z]{2})?)$/, + + MODULE_PER_LANG = ['loader-app-base', 'loader-app-resolved', 'loader-yui3-base', 'loader-yui3-resolved'], + MODULE_TEMPLATES = { + /* + * This is a replacement of the original loader to include loader-app + * module, which represents the meta of the app. + */ + 'loader-app': + 'YUI.add("loader",function(Y){' + + '},"",{requires:["loader-base","loader-yui3","loader-app"]});', + + /* + * Use this module when you want to rely on the loader to do recursive + * computations to resolve combo urls for app yui modules in the client + * runtime. + * Note: This is the default config used by YUI. + */ + 'loader-app-base': + 'YUI.add("loader-app",function(Y){' + + 'Y.applyConfig({groups:{app:Y.merge(' + + '((Y.config.groups&&Y.config.groups.app)||{}),' + + '{modules:{app-base}}' + + ')}});' + + '},"",{requires:["loader-base"]});', + + /* + * Use this module when you want to precompute the loader metadata to + * avoid doing recursive computations to resolve combo urls for app yui modules + * in the client runtime. + * Note: Keep in mind that this meta is considerable bigger than "loader-app-base". + */ + 'loader-app-resolved': + 'YUI.add("loader-app",function(Y){' + + 'Y.applyConfig({groups:{app:Y.merge(' + + '((Y.config.groups&&Y.config.groups.app)||{}),' + + '{modules:{app-resolved}}' + + ')}});' + + '},"",{requires:["loader-base"]});', + + /* + * Use this module when you want to rely on the loader to do recursive + * computations to resolve combo urls for yui core modules in the client + * runtime. + * Note: This is a more restrictive configuration than the default + * meta bundle with yui, but it is considerable smaller, which helps + * with performance. + */ + 'loader-yui3-base': + 'YUI.add("loader-yui3",function(Y){' + + // TODO: we should use YUI.applyConfig() instead of the internal + // YUI.Env API, but that's pending due a bug in YUI: + // http://yuilibrary.com/projects/yui3/ticket/2532854 + 'YUI.Env[Y.version].modules=YUI.Env[Y.version].modules||' + + '{yui-base};' + + '},"",{requires:["loader-base"]});', + + /* + * Use this module when you want to precompute the loader metadata to + * avoid doing recursive computations to resolve combo urls for yui core + * modules in the client runtime. + * Note: Keep in mind that this meta is considerable bigger than "loader-yui3-base". + */ + 'loader-yui3-resolved': + 'YUI.add("loader-yui3",function(Y){' + + // TODO: we should use YUI.applyConfig() instead of the internal + // YUI.Env API, but that's pending due a bug in YUI: + // http://yuilibrary.com/projects/yui3/ticket/2532854 + 'YUI.Env[Y.version].modules=YUI.Env[Y.version].modules||' + + '{yui-resolved};' + + '},"",{requires:["loader-base"]});' + }; + function RSAddonYUI() { RSAddonYUI.superclass.constructor.apply(this, arguments); @@ -46,14 +132,25 @@ YUI.add('addon-rs-yui', function(Y, NAME) { initializer: function(config) { this.appRoot = config.appRoot; this.mojitoRoot = config.mojitoRoot; + + // for all synthetic files, since we don't have an actual file, we need to + // create a stat object, in this case we use the mojito folder stat as + // a replacement. We make it syncronous since it is meant to be executed + // once during the preload process. + syntheticStat = libfs.statSync(libpath.join(__dirname, '../../../..')); + this.afterHostMethod('findResourceVersionByConvention', this.findResourceVersionByConvention, this); this.beforeHostMethod('parseResourceVersion', this.parseResourceVersion, this); this.beforeHostMethod('addResourceVersion', this.addResourceVersion, this); this.beforeHostMethod('makeResourceVersions', this.makeResourceVersions, this); + this.afterHostMethod('resolveResourceVersions', this.resolveResourceVersions, this); this.beforeHostMethod('getResourceContent', this.getResourceContent, this); - this.yuiConfig = config.host.getStaticAppConfig().yui; - + this.staticAppConfig = config.host.getStaticAppConfig() || {}; + this.yuiConfig = (this.staticAppConfig.yui && this.staticAppConfig.yui.config) || {}; this.langs = {}; // keys are list of languages in the app, values are simply "true" + this.resContents = {}; // res.id: contents + this.appModulesRess = {}; // res.yui.name: module ress + this.yuiModulesRess = {}; // res.yui.name: fake ress }, @@ -125,6 +222,88 @@ YUI.add('addon-rs-yui', function(Y, NAME) { }, + /** + * Hook to allow other RS addons to control the combo + * handler configuration for group "app". By default, + * the `yui.config.groups.app` will allow customization + * of the combo handler when needed from application.json + * @method getAppGroupConfig + * @return {object} yui configuration for group "app" + */ + getAppGroupConfig: function() { + return Y.merge({ + combine: (this.yuiConfig.combine === false) ? false : true, + maxURLLength: 1024, + base: "/.", + comboBase: "/combo~", + comboSep: "~", + root: "" + }, ((this.yuiConfig.groups && this.yuiConfig.groups.app) || {})); + }, + + + /** + * Produce the YUI seed files. This can be controlled through + * application.json->yui->config->seed in a form of + * a array with the list of full paths for all seed files. + * @method getAppSeedFiles + * @param {object} ctx the context + * @return {array} list of seed files + */ + getAppSeedFiles: function(ctx) { + var closestLang = Y.mojito.util.findClosestLang(ctx.lang, this.langs), + files = [], + seed = this.yuiConfig.seed ? Y.Array(this.yuiConfig.seed) : [ + 'yui-base', + 'loader-base', + 'loader-yui3', + 'loader-app', + 'loader-app-base{langPath}' + ], + i; + + // adjusting lang just to be url friendly + closestLang = closestLang ? '_' + closestLang : ''; + + // The seed files collection is lang aware, hence we should adjust + // is on runtime. + for (i = 0; i < seed.length; i += 1) { + // adjusting the seed based on {langToken} to facilitate + // the customization of the seed file url per lang. + files[i] = seed[i].replace(REGEX_LANG_PATH, closestLang); + // verifying if the file is actually a synthetic or yui module + if (this.yuiModulesRess.hasOwnProperty(files[i])) { + files[i] = this.yuiModulesRess[files[i]].url; + } else if (this.appModulesRess.hasOwnProperty(files[i])) { + files[i] = this.appModulesRess[files[i]].url; + } + } + + return files; + }, + + + /* + * Aggregate all yui core files + * using the path of as the hash. + * + * @method getYUIURLResources + * @return {object} yui core resources by url + * @api private + */ + getYUIURLResources: function () { + var name, + urls = {}; + + for (name in this.yuiModulesRess) { + if (this.yuiModulesRess.hasOwnProperty(name)) { + urls[this.yuiModulesRess[name].url] = this.yuiModulesRess[name]; + } + } + return urls; + }, + + /** * Using AOP, this is called after the ResourceStore's version. * @method findResourceVersionByConvention @@ -196,6 +375,11 @@ YUI.add('addon-rs-yui', function(Y, NAME) { res.name = res.yui.name; res.id = [res.type, res.subtype, res.name].join('-'); this.langs[res.yui.lang] = true; + // caching the res + this.appModulesRess[res.yui.name] = res; + if (res.yui.name === 'lang/' + res.yui.langFor) { + res.yui.isRootLang = true; + } return new Y.Do.Halt(null, res); } @@ -221,6 +405,8 @@ YUI.add('addon-rs-yui', function(Y, NAME) { this._captureYUIModuleDetails(res); res.name = res.yui.name; res.id = [res.type, res.subtype, res.name].join('-'); + // caching the res + this.appModulesRess[res.yui.name] = res; return new Y.Do.Halt(null, res); } }, @@ -255,8 +441,8 @@ YUI.add('addon-rs-yui', function(Y, NAME) { /** * Using AOP, this is called before the ResourceStore's version. - * We precompute the YUI configurations, and register some fake resource - * versions that represent these configurations. + * We register some fake resource versions that represent the YUI + * configurations. * @method addResourceVersion * @param {object} res resource version metadata * @return {nothing} @@ -264,24 +450,90 @@ YUI.add('addon-rs-yui', function(Y, NAME) { makeResourceVersions: function() { //console.log('------------------------------------------- YUI makeResourceVersions'); var store = this.get('host'), - langs = Object.keys(this.langs), l, - lang; + i, + lang, + name, + langExt, + langs = Object.keys(this.langs), + res; // we always want to make the no-lang version if (!this.langs['']) { langs.push(''); } - // For the server we'll load all the language bundles so we can just - // precompute the no-lang config. - this._precomputeConfigApp('server', 'base', ''); + res = { + source: {}, + mojit: 'shared', + type: 'yui-module', + subtype: 'synthetic', + name: 'loader-app', + affinity: 'client', + selector: '*', + yui: { + name: 'loader-app' + } + }; + res.id = [res.type, res.subtype, res.name].join('-'); + res.source.pkg = store.getAppPkgMeta(); + res.source.fs = store.makeResourceFSMeta(this.appRoot, 'app', '.', 'loader-app.js', true); + // adding res to cache + this.appModulesRess['loader-app'] = res; + store.addResourceVersion(res); for (l = 0; l < langs.length; l += 1) { lang = langs[l]; - this._precomputeConfigApp('client', 'base', lang); - this._precomputeConfigApp('client', 'full', lang); + langExt = lang ? '_' + lang : ''; + + for (i = 0; i < MODULE_PER_LANG.length; i += 1) { + + name = MODULE_PER_LANG[i]; + + res = { + source: {}, + mojit: 'shared', + type: 'yui-module', + subtype: 'synthetic', + name: [name, lang].join('-'), + affinity: 'client', + selector: '*', + yui: { + name: name + langExt + } + }; + res.id = [res.type, res.subtype, res.name].join('-'); + res.source.pkg = store.getAppPkgMeta(); + res.source.fs = store.makeResourceFSMeta(this.appRoot, 'app', '.', + name + langExt + '.js', true); + // adding res to cache + this.appModulesRess[name + langExt] = res; + store.addResourceVersion(res); + + } + + } + + // we can also make some fake resources for all yui + // modules that we might want to serve. + this._precalcYUIResources(); + }, + + + /** + * Using AOP, this is called after the ResourceStore's version. + * We precompute the YUI configurations. + * @method resolveResourceVersions + * @return {nothing} + */ + resolveResourceVersions: function() { + //console.log('------------------------------------------- YUI resolveResourceVersions'); + var langs = Object.keys(this.langs); + // we always want to make the no-lang version + if (!this.langs['']) { + langs.push(''); } + this._precalcLoaderMeta(langs); }, @@ -299,51 +551,296 @@ YUI.add('addon-rs-yui', function(Y, NAME) { * @return {undefined} nothing is returned, the results are returned via the callback */ getResourceContent: function(res, callback) { - //console.log('------------------------------------------- YUI getResourceContent -- ' + res.id); - var content; - /* TODO - if ('yui-meta' === res.type) { - content = ... use results of _precomputeConfigApp ... - store.processResourceContent(res, content, null, callback); + var contents = res.name && this.resContents[res.name]; + if (contents) { + callback(null, new Buffer(contents, 'utf8'), syntheticStat); return new Y.Do.Halt(null, null); } - */ }, /** - * Precomputes the YUI configuration for the whole app, for different situations. - * Also registers a fake resource in the resource store. - * - * @param {string} env runtime environment (either `client`, or `server`) - * @param {string} lang which language. If "lang" is empty string ("") then all - * the language bundles will be included. - * @param {string} type either "base" or "full". If "full" all the dependencies will be resolved as well. - * @return {nothing} computed data stashed for later retrieval + * Precomputes YUI modules resources, so that we don't have to at runtime. + * @private + * @method _precalcYUIResources + * @return {nothing} + */ + _precalcYUIResources: function() { + var name, + modules, + mimetype, + charset, + fullpath, + staticHandling = this.staticAppConfig.staticHandling || {}, + Ysandbox = yuiSandboxFactory + .getYUI(this.yuiConfig.filter)(Y.merge(this.yuiConfig)); + + // used to find the the modules in YUI itself + Ysandbox.use('loader'); + modules = (new Ysandbox.Loader(Ysandbox.config)).moduleInfo || {}; + + for (name in modules) { + if (modules.hasOwnProperty(name)) { + // faking a RS object for the sake of simplicity + fullpath = libpath.join(__dirname, + '../../../../node_modules/yui', modules[name].path); + mimetype = libmime.lookup(fullpath); + charset = libmime.charsets.lookup(mimetype); + + modules[name] = { + url: libpath.join('/', (staticHandling.prefix || 'static'), + 'yui', modules[name].path), + source: { + fs: { + isFile: true, + fullPath: fullpath + } + }, + mime: { + type: mimetype, + charset: charset + } + }; + } + } + this.yuiModulesRess = modules; + }, + + /** + * Precomputes YUI loader metadata, so that we don't have to at runtime. + * @private + * @method _precalcLoaderMeta + * @param {array} langs array of languages for which to compute YUI loader metadata + * @return {nothing} */ - _precomputeConfigApp: function(env, type, lang) { + _precalcLoaderMeta: function(langs) { + //console.log('------------------------------------------- YUI _precalcLoaderMeta'); var store = this.get('host'), - langExt = lang ? '-' + lang : '', - res; + lang, + mojits, + shared, + Ysandbox, + modules_config, + Ysanbdox, + loader, + resolved, + appMetaData = { + base: {}, + full: {} + }, + yuiMetaData = { + base: {}, + full: {} + }, + expanded_modules = {}, // expanded meta (including fullpaths) + modules = {}, // regular meta (a la loader-yui3) + conditions = {}, // hash to store conditional functions + name, + i, + l; + + // TODO: inline these calls, and optimize + mojits = this.getConfigAllMojits('client', {}); + shared = this.getConfigShared('client', {}); + + Ysandbox = yuiSandboxFactory + .getYUI(this.yuiConfig.filter)(Y.merge(this.yuiConfig)); + + modules_config = Ysandbox.merge((mojits.modules || {}), (shared.modules || {})); + Ysandbox.applyConfig({ + modules: Ysandbox.merge({}, modules_config), + useSync: true + }); + Ysandbox.use('loader'); + + // using the loader at the server side to compute the loader metadata + // to avoid loading the whole thing on demand. + loader = new Ysandbox.Loader(Ysandbox.merge(Ysandbox.config, { + require: Ysandbox.Object.keys(modules_config) + })); + resolved = loader.resolve(true); + + // we need to copy, otherwise the datastructures that Y.loader holds + // onto get mixed with our changes, and Y.loader gets confused + resolved = Y.mojito.util.copy(resolved); + + this._processMeta(resolved.jsMods, modules, expanded_modules, conditions); + this._processMeta(resolved.cssMods, modules, expanded_modules, conditions); + + for (i = 0; i < langs.length; i += 1) { + lang = langs[i] || '*'; + + appMetaData.base[lang] = {}; + appMetaData.full[lang] = {}; + yuiMetaData.base[lang] = {}; + yuiMetaData.full[lang] = {}; + + for (name in expanded_modules) { + if (expanded_modules.hasOwnProperty(name)) { + if (expanded_modules[name].owner && + !expanded_modules[expanded_modules[name].owner]) { + // if there is not a module corresponding with the lang pack + // that means the controller doesn't have client affinity, + // in that case, we don't need to ship it. + continue; + } + if ((lang === '*') || + (expanded_modules[name].langPack === '*') || + (!expanded_modules[name].langPack) || + (lang === expanded_modules[name].langPack)) { + + // we want to separate modules into different buckets + // to be able to support groups in loader config + if (modules_config[name]) { + appMetaData.base[lang][name] = modules[name]; + appMetaData.full[lang][name] = expanded_modules[name]; + } else { + yuiMetaData.base[lang][name] = modules[name]; + yuiMetaData.full[lang][name] = expanded_modules[name]; + } + } + } + } - // FUTURE: Check first if resource exists on disk (since it - // might be made by a build step) and preread it instead. - res = { - source: {}, - type: 'yui-meta', - subtype: type, - name: lang, - affinity: env, - selector: '*' - }; - res.id = [res.type, res.subtype, res.name].join('-'); - res.source.pkg = store.getAppPkgMeta(); - res.source.fs = store.makeResourceFSMeta(this.appRoot, 'app', '.', 'loader-meta-' + type + langExt + '.js', true); - store.addResourceVersion(res); + appMetaData.base[lang] = JSON.stringify(appMetaData.base[lang]); + appMetaData.full[lang] = JSON.stringify(appMetaData.full[lang]); + yuiMetaData.base[lang] = JSON.stringify(yuiMetaData.base[lang]); + yuiMetaData.full[lang] = JSON.stringify(yuiMetaData.full[lang]); + + for (name in conditions) { + if (conditions.hasOwnProperty(name)) { + appMetaData.base[lang] = appMetaData.base[lang] + .replace('"{' + name + '}"', conditions[name]); + appMetaData.full[lang] = appMetaData.full[lang] + .replace('"{' + name + '}"', conditions[name]); + yuiMetaData.base[lang] = yuiMetaData.base[lang] + .replace('"{' + name + '}"', conditions[name]); + yuiMetaData.full[lang] = yuiMetaData.full[lang] + .replace('"{' + name + '}"', conditions[name]); + } + } + } // for each lang + + this.resContents['loader-app'] = MODULE_TEMPLATES['loader-app']; - // TODO -- move precompute code here from middleware/combo-handler - // if type===base just walk through the resources and gather res.yui - // if type===full do that, plus use Y.Loader to precompute everything + for (l = 0; l < langs.length; l += 1) { + lang = langs[l] || ''; + + for (i = 0; i < MODULE_PER_LANG.length; i += 1) { + + name = MODULE_PER_LANG[i]; + // populating the internal cache using name+lang as the key + this.resContents[([name, lang].join('-'))] = + this._produceMeta(name, lang || '*', appMetaData, yuiMetaData); + + } + + } + }, + + + /** + * @private + * @method _processMeta + * @param {object} resolvedMods resolved module metadata, from Y.Loader.resolve() + * @param {object} modules regular YUI module metadata (ala loader-yui3) + * @param {object} expanded_modules YUI module metadata that include details such as fullpaths. This parameter is populated by this method. + * @param {object} conditions store of conditional functions. This parameter is populated by this method. + * @return {nothing} + */ + _processMeta: function(resolvedMods, modules, expanded_modules, conditions) { + var m, + l, + i, + module, + name, + mod, + lang, + bundle; + + for (m in resolvedMods) { + if (resolvedMods.hasOwnProperty(m)) { + module = resolvedMods[m]; + + mod = name = module.name; + bundle = name.indexOf('lang/') === 0; + lang = bundle && REGEX_LOCALE.exec(name); + + if (lang) { + mod = mod.slice(0, lang.index); // eg. lang/foo_en-US -> lang/foo + lang = lang[1]; + // TODO: validate lang + } + mod = bundle ? mod.slice(5) : mod; // eg. lang/foo -> foo + + // language manipulation + // TODO: this routine is very restrictive, and we might want to + // make it optional later on. + if (module.lang) { + module.lang = ['{langToken}']; + } + if (bundle) { + module.owner = mod; + // applying some extra optimizations + module.langPack = lang || '*'; + module.intl = true; + delete module.expanded_map; + } + + if (module.condition && module.condition.test) { + conditions[module.name] = module.condition.test.toString(); + module.condition.test = "{" + module.name + "}"; + } + + modules[module.name] = {}; + if (module.type === 'css') { + modules[module.name].type = 'css'; + } + for (i = 0; i < MODULE_META_ENTRIES.length; i += 1) { + if (module[MODULE_META_ENTRIES[i]]) { + modules[module.name][MODULE_META_ENTRIES[i]] = + module[MODULE_META_ENTRIES[i]]; + } + } + + expanded_modules[module.name] = module; + for (i = 0; i < MODULE_META_PRIVATE_ENTRIES.length; i += 1) { + delete module[MODULE_META_PRIVATE_ENTRIES[i]]; + } + } + } + }, + + + /** + * Generates the final YUI metadata. + * @private + * @method _produceMeta + * @param {string} name type of YUI metadata to return + * @param {string} lang which language the metadata should be customized for + * @param {object} appMetaData gathered YUI metadata for the application + * @param {object} yuiMetaData gathered YUI metadata for YUI itself + * @return {string} the requested YUI metadata + */ + _produceMeta: function(name, lang, appMetaData, yuiMetaData) { + var token = '', + path = ''; + + if (lang) { + token = '"' + lang + '"'; + path = '_' + lang; + } else { + lang = '*'; + } + + // module definition definitions + return MODULE_TEMPLATES[name] + .replace('{app-base}', appMetaData.base[lang] || appMetaData.base['*']) + .replace('{app-resolved}', appMetaData.full[lang] || appMetaData.full['*']) + .replace('{yui-base}', yuiMetaData.base[lang] || yuiMetaData.base['*']) + .replace('{yui-resolved}', yuiMetaData.full[lang] || yuiMetaData.full['*']) + .replace(REGEX_LANG_TOKEN, token) + .replace(REGEX_LANG_PATH, path); }, @@ -396,7 +893,7 @@ YUI.add('addon-rs-yui', function(Y, NAME) { // Use ignoreRegistered here instead of the old `delete YUI.Env._renderedMods` hack loader = new Y.Loader({ ignoreRegistered: true }); // Only override the default if it's required - if (this.yuiConfig && this.yuiConfig.base) { + if (this.yuiConfig.base) { loader.base = this.yuiConfig.base; } @@ -443,9 +940,14 @@ YUI.add('addon-rs-yui', function(Y, NAME) { */ _makeYUIModuleConfig: function(env, res) { var config = { - fullpath: ('client' === env) ? res.url : res.source.fs.fullPath, requires: (res.yui.meta && res.yui.meta.requires) || [] }; + if ('client' === env) { + // using relative path since the loader will do the rest + config.path = res.url; + } else { + config.fullpath = res.source.fs.fullPath; + } return config; }, @@ -503,37 +1005,6 @@ YUI.add('addon-rs-yui', function(Y, NAME) { if (yui) { res.yui = Y.merge(res.yui || {}, yui); } - }, - - - /** - * Augments the results of _precomputeYUIDependencies() with the details - * about the language. - * - * @private - * @method _addLangsToSorted - * @param {string} env runtime environment (either `client`, or `server`) - * @param {object} sorted results of _precomputeYUIDependencies() - * @param {string} langName which language to add - * @param {object} langRess resources representing the language bundle - * @return {nothing} results are added to the `sorted` parameter - */ - _addLangsToSorted: function(env, sorted, langName, langRess) { - var modName, - langRes; - for (modName in langRess) { - if (langRess.hasOwnProperty(modName)) { - langRes = langRess[modName][langName] || langRess[modName]['']; - if (langRes) { - sorted.sorted.push(langRes.yui.name); - sorted.paths[langRes.yui.name] = ('client' === env) ? langRes.url : langRes.source.fs.fullPath; - } - } - } - if (!sorted.paths.intl) { - sorted.sorted.unshift('intl'); - sorted.paths.intl = intlPath; - } } diff --git a/lib/app/archetypes/app/full/application.json b/lib/app/archetypes/app/full/application.json index 506b4a064..109be1417 100644 --- a/lib/app/archetypes/app/full/application.json +++ b/lib/app/archetypes/app/full/application.json @@ -4,30 +4,19 @@ "appPort": 8666, "cacheViewTemplates": true, - "deferAllOptionalAutoload": false, - "embedJsFilesInHtmlFrame": false, - "log": { - "client": { - "level": "info", - "yui": false - }, - "server": { - "level": "info", - "yui": false - } - }, - "shareYUIInstance": false, "staticHandling": { "appName": "app", - "cache": false, "frameworkName": "mojito", + "cache": true, "maxAge": 600000, - "prefix": "static", - "useRollups": false + "prefix": "static" }, "tunnelPrefix": "/tunnel/", "yui": { - "dependencyCalculations": "precomputed" + "config": { + "debug": true, + "logLevel": "info" + } }, "specs": { @@ -46,6 +35,7 @@ { "settings": [ "environment:development" ], "staticHandling": { + "cache": false, "forceUpdate": true } } diff --git a/lib/app/archetypes/mojit/default/controller.server.js.hb b/lib/app/archetypes/mojit/default/controller.server.js.hb index 3347a414a..d97e4eab0 100644 --- a/lib/app/archetypes/mojit/default/controller.server.js.hb +++ b/lib/app/archetypes/mojit/default/controller.server.js.hb @@ -18,10 +18,6 @@ YUI.add('{{name}}', function(Y, NAME) { */ Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, - /** * Method corresponding to the 'index' action. * diff --git a/lib/app/archetypes/mojit/default/tests/models/foo.server-tests.js.hb b/lib/app/archetypes/mojit/default/tests/models/foo.server-tests.js.hb index 7b21a3a74..de1e8a28a 100644 --- a/lib/app/archetypes/mojit/default/tests/models/foo.server-tests.js.hb +++ b/lib/app/archetypes/mojit/default/tests/models/foo.server-tests.js.hb @@ -20,8 +20,15 @@ YUI.add('{{name}}ModelFoo-tests', function(Y, NAME) { }, 'test mojit model': function() { - var called = false; + var called = false, + cfg = { color: 'red' }; + A.isNotNull(model); + + A.isFunction(model.init); + model.init(cfg); + A.areSame(cfg, model.config); + A.isFunction(model.getData); model.getData(function(err, data) { called = true; diff --git a/lib/app/archetypes/mojit/full/controller.server.js.hb b/lib/app/archetypes/mojit/full/controller.server.js.hb index 3347a414a..d97e4eab0 100644 --- a/lib/app/archetypes/mojit/full/controller.server.js.hb +++ b/lib/app/archetypes/mojit/full/controller.server.js.hb @@ -18,10 +18,6 @@ YUI.add('{{name}}', function(Y, NAME) { */ Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, - /** * Method corresponding to the 'index' action. * diff --git a/lib/app/archetypes/mojit/full/tests/models/model.server-tests.js.hb b/lib/app/archetypes/mojit/full/tests/models/foo.server-tests.js.hb similarity index 51% rename from lib/app/archetypes/mojit/full/tests/models/model.server-tests.js.hb rename to lib/app/archetypes/mojit/full/tests/models/foo.server-tests.js.hb index b3e749417..de1e8a28a 100644 --- a/lib/app/archetypes/mojit/full/tests/models/model.server-tests.js.hb +++ b/lib/app/archetypes/mojit/full/tests/models/foo.server-tests.js.hb @@ -2,15 +2,15 @@ * Copyright (c) 2012 Yahoo! Inc. All rights reserved. */ -YUI.add('{{name}}Model-tests', function(Y) { +YUI.add('{{name}}ModelFoo-tests', function(Y, NAME) { - var suite = new YUITest.TestSuite('{{name}}Model-tests'), + var suite = new YUITest.TestSuite(NAME), model = null, A = YUITest.Assert; suite.add(new YUITest.TestCase({ - name: '{{name}} model user tests', + name: '{{name}}ModelFoo user tests', setUp: function() { model = Y.mojito.models.{{name}}ModelFoo; @@ -20,8 +20,23 @@ YUI.add('{{name}}Model-tests', function(Y) { }, 'test mojit model': function() { + var called = false, + cfg = { color: 'red' }; + A.isNotNull(model); + + A.isFunction(model.init); + model.init(cfg); + A.areSame(cfg, model.config); + A.isFunction(model.getData); + model.getData(function(err, data) { + called = true; + A.isTrue(!err); + A.isObject(data); + A.areSame('data', data.some); + }); + A.isTrue(called); } })); diff --git a/lib/app/autoload/action-context.common.js b/lib/app/autoload/action-context.common.js index f8086492c..ed7e42322 100644 --- a/lib/app/autoload/action-context.common.js +++ b/lib/app/autoload/action-context.common.js @@ -334,6 +334,30 @@ YUI.add('mojito-action-context', function(Y, NAME) { perf = Y.mojito.perf.timeline('mojito', 'action:call', 'the initial syncronous part of the action', command); + // Reap the request/ac process within the timeout. If ac.done or + // ac.error is invoked by user code prior to the time limit this + // timer will be cleared. + if (this.staticAppConfig.actionTimeout) { + this._timer = setTimeout(function() { + var err, + msg = 'Killing potential zombie context for Mojit type: ' + + my.instance.type + + ', controller: ' + my.instance.controller + + ', action: ' + actionFunction; + + // Clear the timer reference so our invocation of error() + // doesn't try to clear it. + my._timer = null; + + // Create an HTTP Timeout error with controller/action info. + err = new Error(msg); + err.code = 408; + + my.error(err); + + }, this.staticAppConfig.actionTimeout); + } + controller[actionFunction](this); perf.done(); // closing the 'action:call' timeline @@ -359,6 +383,12 @@ YUI.add('mojito-action-context', function(Y, NAME) { */ done: function(data, meta, more) { + // If we have an active timer clear it immediately. + if (this._timer) { + clearTimeout(this._timer); + this._timer = null; + } + var callbackFunc = more ? 'flush' : 'done', instance = this.command.instance, config = instance.config || {}, @@ -426,6 +456,9 @@ YUI.add('mojito-action-context', function(Y, NAME) { if (mojitView && meta.view.engine) { mojitView.engine = meta.view.engine; } + if (mojitView && mojitView.assets) { + meta.assets = Y.mojito.util.metaMerge(meta.assets, mojitView.assets); + } meta.assets = Y.mojito.util.metaMerge(meta.assets, config.assets || {}); // Here we ask each "thing" attached to the AC if it wants to add view @@ -534,6 +567,11 @@ YUI.add('mojito-action-context', function(Y, NAME) { * "This does not exist in my app". */ error: function(err) { + // If we have an active timer clear it immediately. + if (this._timer) { + clearTimeout(this._timer); + this._timer = null; + } this._adapter.error(err); } }; diff --git a/lib/app/autoload/dispatch.common.js b/lib/app/autoload/dispatch.common.js index 9fc102f99..c18da3eeb 100644 --- a/lib/app/autoload/dispatch.common.js +++ b/lib/app/autoload/dispatch.common.js @@ -4,27 +4,31 @@ * See the accompanying LICENSE file for terms. */ -/*jslint anon:true, sloppy:true, nomen:true*/ +/*jslint anon:true, nomen:true*/ /*global YUI*/ /** * This object is responsible for running mojits. * @class MojitoDispatcher - * @constructor - * @param {ServerStore} resourceStore the store to use. - * @private + * @static + * @public */ -YUI.add('mojito-dispatcher', function(Y, NAME) { +YUI.add('mojito-dispatcher', function (Y, NAME) { + + 'use strict'; Y.namespace('mojito').Dispatcher = { /** * Initializes the dispatcher instance. - * @param {Y.mojito.ResourceStore} resourceStore + * @method init + * @public + * @param {Y.mojito.ResourceStore} resourceStore the store to use. + * @param {Y.mojito.TunnelClient} rpcTunnel optional tunnel client for RPC calls * @return {Y.mojito.Dispatcher} */ - init: function(resourceStore) { + init: function (resourceStore, rpcTunnel) { if (!resourceStore) { throw new Error( @@ -35,16 +39,128 @@ YUI.add('mojito-dispatcher', function(Y, NAME) { // Cache parameters as instance variables for the dispatch() call to // reference. this.store = resourceStore; - - this.CACHE = {}; + this.tunnel = rpcTunnel; Y.log('Dispatcher created', 'debug', NAME); return this; }, - /* See docs for the dispatch function in action-context.common.js */ - dispatch: function(command, adapter) { + /** + * Attaches requirements to dispatch the current mojit when + * position. This is usually needed when running in the + * client side and loading mojits on demand. + * @method _useController + * @protected + * @param {object} command the command to dispatch + * @param {OutputAdapter} adapter the output adapter + */ + _useController: function (command, adapter) { + var my = this, + instance = command.instance, + modules = []; + + // For performance reasons we don't want to support + // this ondemand "use" in the server side since + // all the requirements are already in place. + if (command.context.runtime === 'server') { + adapter.error(new Error('Invalid controller name [' + + instance.controller + '] for mojit [' + + instance.type + '].')); + return; + } + + // attach controller to Y ondemand + modules.push(instance.controller); + + // TODO: this is a hack to attach the correct engine, the problem + // here is that we are assuming too many things, action might not + // be defined, or engine might not even be needed. + modules.push('mojito-' + ((instance.views && instance.views[command.action] && + instance.views[command.action].engine) || 'hb')); + + // use statement callback + modules.push(function () { + if (Y.mojito.controllers[command.instance.controller]) { + // continue with the workflow + my._createActionContext(command, adapter); + } else { + // the controller was not found, we should halt + adapter.error(new Error('Invalid controller name [' + + instance.controller + '] for mojit [' + + instance.type + '].')); + } + }); + + // TODO: view engine should not be attached here. + Y.use.apply(Y, modules); + }, + + /** + * Create AC object for a particular controller. + * @method _createActionContext + * @protected + * @param {object} command the command to dispatch + * @param {OutputAdapter} adapter the output adapter + */ + _createActionContext: function (command, adapter) { + var ac, + controller = Y.mojito.controllers[command.instance.controller], + perf = Y.mojito.perf.timeline('mojito', 'ac:ctor', + 'create ControllerContext', command); + + // Note that creation of an ActionContext current causes + // immediate invocation of the dispatch() call. + try { + ac = new Y.mojito.ActionContext({ + command: command, + controller: Y.mojito.util.heir(controller), + dispatcher: this, // NOTE passing dispatcher. + adapter: adapter, + store: this.store + }); + } catch (e) { + Y.log('Error from dispatch on instance \'' + + (command.instance.id || '@' + command.instance.type) + + '\':', 'error', NAME); + Y.log(e.message, 'error', NAME); + Y.log(e.stack, 'error', NAME); + adapter.error(e); + } + perf.done(); // closing the 'ac:ctor' timeline + }, + + /** + * Executes a command in a remote runtime if possible. + * @method rpc + * @public + * @param {object} command the command to dispatch + * @param {OutputAdapter} adapter the output adapter + */ + rpc: function (command, adapter) { + if (this.tunnel) { + + Y.log('Dispatching instance "' + (command.instance.base || '@' + + command.instance.type) + '" through RPC tunnel.', 'info', NAME); + this.tunnel.rpc(command, adapter); + + } else { + + adapter.error(new Error('RPC tunnel is not available in the ' + + command.context.runtime + ' runtime.')); + + } + }, + + /** + * Dispatch a command in the current runtime, or fallback + * to a remote runtime when posible. + * @method dispatch + * @public + * @param {object} command the command to dispatch + * @param {OutputAdapter} adapter the output adapter + */ + dispatch: function (command, adapter) { var my = this, store = this.store, @@ -54,61 +170,45 @@ YUI.add('mojito-dispatcher', function(Y, NAME) { store.validateContext(command.context); - store.expandInstance(command.instance, command.context, - function(err, instance) { - var controller, - ac; + if (command.rpc) { + // forcing to dispatch command through RPC tunnel + this.rpc(command, adapter); + return; + } - if (err) { - throw new Error(err); - } + store.expandInstance(command.instance, command.context, + function (err, instance) { perf.done(); // closing 'dispatch:expandInstance' timeline + if (err || !instance || !instance.controller) { + + // error expanding the instance, potentially + // a remote instance that can't be expanded in the + // current runtime and should be dispatched through RPC + Y.log('Cannot expand instance "' + (command.instance.base || '@' + + command.instance.type) + '". Trying with the tunnel in case ' + + 'it is a remote mojit.', 'info', NAME); + + my.rpc(command, adapter); + return; + + } + // We replace the given instance with the expanded instance. command.instance = instance; - Y.mojito.perf.mark('mojito', 'core_dispatch_start', - 'dispatching an instance', command); - - // Ensure there's a createController method we can call - // that will always return a viable controller. By - // wrapping in a function we allow tests and other code - // to provide mocks etc. - // TODO: instance.createController should be part of the - // expandInstance routine rather than dispatch. - instance.createController = instance.createController || - function() { - // returning a controller instance instead of - // a singleton to avoid leaks. - return Y.mojito.util.heir(Y.mojito.controllers[ - instance.controller - ]); - }; - controller = instance.createController(); - - perf = Y.mojito.perf.timeline('mojito', 'ac:ctor', - 'create ControllerContext', command); - - // Note that creation of an ActionContext current causes - // immediate invocation of the dispatch() call. - try { - ac = new Y.mojito.ActionContext({ - command: command, - controller: controller, - dispatcher: my, // NOTE passing dispatcher. - adapter: adapter, - store: store - }); - } catch (e) { - Y.log('Error from dispatch on instance \'' + - (instance.id || '@' + instance.type) + - '\':', 'error', NAME); - Y.log(e.message, 'error', NAME); - Y.log(e.stack, 'error', NAME); - adapter.error(e); + // if this controller does not exist yet, we should try + // to require it along with it depedencies. + if (!Y.mojito.controllers[instance.controller]) { + // requiring the controller and its dependencies + // before dispatching AC + my._useController(command, adapter); + } else { + // dispatching AC + my._createActionContext(command, adapter); } - perf.done(); // closing the 'ac:ctor' timeline + }); } diff --git a/lib/app/autoload/mojito-client.client.js b/lib/app/autoload/mojito-client.client.js index 592b8b927..ad58244d5 100644 --- a/lib/app/autoload/mojito-client.client.js +++ b/lib/app/autoload/mojito-client.client.js @@ -347,7 +347,7 @@ YUI.add('mojito-client', function(Y, NAME) { // Note this is the client-store, not the server-store. this.resourceStore = new Y.mojito.ResourceStore(config); - this.dispatcher = Y.mojito.Dispatcher.init(this.resourceStore); + this.dispatcher = Y.mojito.Dispatcher.init(this.resourceStore, this.tunnel); // request context from server this.context = config.context; @@ -622,62 +622,25 @@ YUI.add('mojito-client', function(Y, NAME) { * @private */ executeAction: function(command, viewId, cb) { - var my = this; + var outputHandler; // Sending a command to dispatcher that defines our action execution Y.log('Executing "' + (command.instance.base || '@' + command.instance.type) + '/' + command.action + '" on the client.', 'mojito', NAME); - this.resourceStore.expandInstanceForEnv('client', - command.instance, this.context, function(err, details) { + command.context = this.context; - if (err) { - if (typeof cb === 'function') { - cb(new Error(err)); - return; - } - throw new Error(err); - } + outputHandler = new Y.mojito.OutputHandler(viewId, cb, this); - var controllerModule = details && details.controller, - // TODO: this is a hack to attach the correct engine, the problem - // here is that we are assuming too many things, action might not - // be defined, or engine might not even be needed. - engine = (details && details.views && details.views[command.action] && - details.views[command.action].engine) || 'hb'; - - command.context = my.context; - - // if this controller does not exist here, or we have been - // instructed to force an RPC call, we'll go to the server - if (!controllerModule || command.rpc) { - // Make the call via a fixed RPC channel - my.tunnel.rpc(command, - new Y.mojito.OutputHandler(viewId, cb, my)); - } else if (controllerModule && !Y.mojito.controllers[controllerModule]) { - // attach controller to Y ondemand - // attach view engine ondemand - - // TODO: view engine should not be attached here. - Y.use(controllerModule, 'mojito-' + engine, function () { - // Dispatch locally - my.dispatcher.dispatch(command, - new Y.mojito.OutputHandler(viewId, cb, my)); - }); - } else { - // Dispatch locally - my.dispatcher.dispatch(command, - new Y.mojito.OutputHandler(viewId, cb, my)); - } - }); + this.dispatcher.dispatch(command, outputHandler); }, doRender: function(mp, data, view, cb) { var viewEngine = this.appConfig.viewEngine; if (!mp._views || !mp._assetsRoot) { - this.resourceStore.expandInstanceForEnv('client', {type: mp.type}, mp.context, + this.resourceStore.expandInstance({type: mp.type}, mp.context, function(err, typeInfo) { if (err) { cb(new Error( diff --git a/lib/app/autoload/store.server.js b/lib/app/autoload/store.server.js index 2aca076d4..ec078cac5 100644 --- a/lib/app/autoload/store.server.js +++ b/lib/app/autoload/store.server.js @@ -528,11 +528,12 @@ YUI.add('mojito-resource-store', function(Y, NAME) { */ expandInstanceForEnv: function(env, instance, ctx, cb) { + // TODO: fakeInstance could be even more optimized, where + // type has priority over base, and only one of them is really + // needed. var fakeInstance = { base: instance.base, - action: instance.action, - type: instance.type, - id: instance.id + type: instance.type }, posl = this.selector.getPOSLFromContext(ctx), // We need to include the lang, since it's a part of the context @@ -561,7 +562,6 @@ YUI.add('mojito-resource-store', function(Y, NAME) { return cb(err); } spec.config = spec.config || {}; - spec.action = spec.action || 'index'; if (!spec.instanceId) { spec.instanceId = Y.guid(); } @@ -637,6 +637,9 @@ YUI.add('mojito-resource-store', function(Y, NAME) { if (!dest.binders) { dest.binders = {}; } + if (!dest.langs) { + dest.langs = {}; + } if (!dest.models) { dest.models = {}; } @@ -675,6 +678,13 @@ YUI.add('mojito-resource-store', function(Y, NAME) { dest.controller = res.yui.name; } + if (res.type === 'yui-lang') { + dest.langs[res.yui.lang] = true; + if (res.yui.isRootLang) { + dest.defaultLang = res.yui.lang; + } + } + if (res.type === 'model') { dest.models[res.name] = res.yui.name; } @@ -699,6 +709,8 @@ YUI.add('mojito-resource-store', function(Y, NAME) { } else { dest.views[res.name] = template; } + dest.views[res.name].assets = res.view.assets; + dest.views[res.name].engine = res.view.engine; engines[res.view.engine] = true; } } @@ -816,61 +828,13 @@ YUI.add('mojito-resource-store', function(Y, NAME) { for (r = 0; r < ress.length; r += 1) { res = ress[r]; if (res.url) { - urls[res.url] = res.source.fs.rollupPath || res.source.fs.fullPath; + urls[res.url] = res.source.fs.fullPath; } } } return urls; }, - /** - * Sugar method that returns all "url" metadata of all resources that - * can be accessed from the client runtime, that includes "common" and - * "client" affinity, where "client" has the priority. - * @method getAllModulesURLs - * @return {object} for all resources with a "url" metadatum, the key is - * that URL and the value the filesystem path - */ - getAllModulesURLs: function() { - var r, - res, - ress, - m, - mojit, - mojits, - urls = { - client: {}, - common: {} - }, - affinity; - mojits = this.listAllMojits(); - mojits.push('shared'); - for (m = 0; m < mojits.length; m += 1) { - mojit = mojits[m]; - ress = this.getResourceVersions({mojit: mojit}); - for (r = 0; r < ress.length; r += 1) { - res = ress[r]; - // exposing YUI modules with common or client affinity. - // TODO: this should be handled differently, asking for - // the specific resources with the correct affinity. - affinity = res.affinity && res.affinity.affinity; - if (res.yui && res.yui.name && - (affinity === 'common' || affinity === 'client')) { - - if (urls[affinity] && urls[affinity][res.yui.name]) { - Y.log('YUI Modules should have unique name per affinity. ' + - 'Module [' + res.yui.name + '] has a duplicated ' + - 'definition for ' + affinity, 'error', NAME); - } - urls[affinity][res.yui.name] = - res.source.fs.rollupPath || res.source.fs.fullPath; - - } - } - } - return Y.merge(urls.common, urls.client); - }, - /** * Sugar method that returns a hash table with the urls and the @@ -903,11 +867,6 @@ YUI.add('mojito-resource-store', function(Y, NAME) { res = ress[r]; if (res.url && res.source.fs.isFile) { urls[res.url] = res; - // rewrite for the favicon.ico - // /favicon.ico is sent to ./my_app_folder/assets/favicon.ico - if (/\/assets\/favicon\.ico$/.test(res.url) && (res.source.fs.rootType === 'app')) { - urls['/favicon.ico'] = res; - } } } } @@ -932,7 +891,7 @@ YUI.add('mojito-resource-store', function(Y, NAME) { filename; if (res && res.source && res.source.fs && res.source.fs.isFile) { - filename = res.source.fs.rollupPath || res.source.fs.fullPath; + filename = res.source.fs.fullPath; // FUTURE [Issue 89] stat cache? store._libs.fs.stat(filename, function(err, stat) { diff --git a/lib/app/autoload/tunnel.client-optional.js b/lib/app/autoload/tunnel.common.js similarity index 99% rename from lib/app/autoload/tunnel.client-optional.js rename to lib/app/autoload/tunnel.common.js index 75093f318..cd0138971 100644 --- a/lib/app/autoload/tunnel.client-optional.js +++ b/lib/app/autoload/tunnel.common.js @@ -68,7 +68,7 @@ YUI.add('mojito-tunnel-client', function(Y, NAME) { }, '0.1.0', {requires: [ 'mojito', - 'io', + 'io-base', 'json-stringify', 'json-parse' ]}); diff --git a/lib/app/autoload/util.common.js b/lib/app/autoload/util.common.js index 4aea2f76e..9b8032f41 100644 --- a/lib/app/autoload/util.common.js +++ b/lib/app/autoload/util.common.js @@ -493,6 +493,26 @@ YUI.add('mojito-util', function(Y) { key = Y.guid(); } return key; + }, + + /** + * @method findClosestLang + * @param {string} want the desired language code + * @param {object} have an object whose keys are available language codes and whose values are true (for all keys that exist) + * @return {string} closest matching language code, or an empty string if none match + */ + findClosestLang: function(want, have) { + var p, + parts, + test; + parts = want ? want.split('-') : []; + for (p = want.length; p > 0; p -= 1) { + test = parts.slice(0, p).join('-'); + if (have[test]) { + return test; + } + } + return ''; } }; diff --git a/lib/app/commands/build.js b/lib/app/commands/build.js index ee37bb7f4..908c787fa 100644 --- a/lib/app/commands/build.js +++ b/lib/app/commands/build.js @@ -10,6 +10,7 @@ var libpath = require('path'), + libwrench = require('wrench'), utils = require(libpath.join(__dirname, '../../management/utils')), Mojito = require(libpath.join(__dirname, '../../mojito')), Store = require(libpath.join(__dirname, '../../store')), @@ -20,6 +21,7 @@ var libpath = require('path'), mkdirP, rmdirR, writeWebPagesToFiles, + findYUIDir, YUI = require('yui').YUI, Y = YUI({useSync: true}).use('json-parse', 'json-stringify', 'escape'); @@ -167,7 +169,9 @@ exports.buildhtml5app = function(cmdOptions, store, config, destination, sr, specRes, specRess, - id; + id, + sourceYUI, + destinationYUI; if (typeof cmdOptions.context === 'string') { // Parse the context into an object @@ -256,6 +260,11 @@ exports.buildhtml5app = function(cmdOptions, store, config, destination, } } + sourceYUI = findYUIDir(); + destinationYUI = libpath.join(destination, 'static', 'yui'); + mkdirP(destinationYUI, MODE_755); + libwrench.copyDirSyncRecursive(sourceYUI, destinationYUI); + // Write the "cache.manifest" file fs.writeFileSync(libpath.join(destination, 'cache.manifest'), manifest, 'utf8'); @@ -268,6 +277,15 @@ exports.buildhtml5app = function(cmdOptions, store, config, destination, }; +/** + * Locate the directory where YUI is installed within "mojito" module + */ +findYUIDir = function() { + var source; + source = libpath.join(__dirname, '../../../node_modules', 'yui'); + return source; +}; + getSpecURL = function(appConfig, id) { var prefix = '/static', parts = id.split(':'), @@ -405,6 +423,20 @@ function attachManifest(root, relativePath, content, force) { return content; } +function attachCharset(root, relativePath, content, force) { + var extname = libpath.extname(relativePath); + + if (force || '.html' === extname) { + if (/meta charset="UTF-8/i.test(content)) { + return content; + } + if (/meta [^>]*charset=.*>/i.test(content)) { + return content; + } + content = content.replace(/<head>/, '<head>\n <meta charset="UTF-8">'); + } + return content; +} /** * Changes server-relative paths to file-relative paths. @@ -472,7 +504,6 @@ writeWebPagesToFiles = function(destination, urls, config, callback) { return; } need = Object.keys(urls).length; - Object.keys(urls).forEach(function(u) { var opts = { headers: { @@ -481,6 +512,7 @@ writeWebPagesToFiles = function(destination, urls, config, callback) { libpath.dirname(urls[u])) } }; + app.getWebPage(u, opts, function(err, url, content) { var dest; @@ -495,6 +527,11 @@ writeWebPagesToFiles = function(destination, urls, config, callback) { content, userPages[url]); } + // TODO: add config.attachCharset + config.attachCharset = true; + if (config.attachCharset) { + content = attachCharset(destination, urls[url], content, userPages[url]); + } if (config.forceRelativePaths) { content = forceRelativePaths(destination, urls[url], content, diff --git a/lib/app/commands/test.js b/lib/app/commands/test.js index 03dddcb37..5fdc915a6 100644 --- a/lib/app/commands/test.js +++ b/lib/app/commands/test.js @@ -25,7 +25,10 @@ var libpath = require('path'), mojitoTmp = '/tmp/mojitotmp', mojitoInstrumentedDir = '/tmp/mojito-lib-inst', - fwTestsRoot = libpath.join(targetMojitoPath, 'lib/tests'), + yuiTestCoverageJar = libpath.join(targetMojitoPath, + 'tests/harness/lib/yuitest/java/build/yuitest-coverage.jar'), + yuiTestCoverageReportJar = libpath.join(targetMojitoPath, + 'tests/harness/lib/yuitest/java/build/yuitest-coverage-report.jar'), resultsDir = 'artifacts/test', resultsFile = libpath.join(resultsDir, 'result.xml'), @@ -34,6 +37,8 @@ var libpath = require('path'), Store = require(libpath.join(__dirname, '../../store')), + RX_TESTS = /-tests$/, + YUI = require('yui').YUI, YUITest = require('yuitest').YUITest, TestRunner = YUITest.TestRunner, @@ -370,9 +375,8 @@ function processResults() { utils.log('Creating coverage report...'); // generate coverage reports in html // TODO: find home for fixed path string - exec('java -jar ' + fwTestsRoot + - '/harness/lib/yuitest/java/build/yuitest-coverage-report.jar ' + - '--format LCOV -o ' + coverageDir + ' ' + coverageFile, + exec('java -jar ' + yuiTestCoverageReportJar + + ' --format LCOV -o ' + coverageDir + ' ' + coverageFile, function(error, stdout, stderr) { if (inputOptions.verbose) { utils.log('stdout: ' + stdout); @@ -468,12 +472,10 @@ function executeTestsWithinY(tests, cb) { function instrumentDirectory(from, verbose, testType, callback) { utils.log('Instrumenting "' + from + '" for test coverage\n\t(this will take a while).'); - var converter = libfs.realpathSync(fwTestsRoot + - '/harness/lib/yuitest/java/build/yuitest-coverage.jar'), - opts = verbose ? ' -v' : '', + var opts = verbose ? ' -v' : '', realPathFrom = libfs.realpathSync(from), - cmd = 'java -jar ' + converter + opts + ' -d -o ' + - mojitoInstrumentedDir + ' ' + mojitoTmp, + cmd = 'java -jar ' + yuiTestCoverageJar + ' ' + opts + + ' -d -o ' + mojitoInstrumentedDir + ' ' + mojitoTmp, allMatcher, instrumentableJsMatcher; @@ -706,8 +708,7 @@ runTests = function(opts) { } } - } else if (name.indexOf('-tests') === - name.length - '-tests'.length) { + } else if (RX_TESTS.test(name)) { testModuleNames.push(name); } }); @@ -727,7 +728,7 @@ runTests = function(opts) { // execute each test within new sandbox testModuleNames.forEach(function(name) { // only run tests, and not the frame mojit tests - if (/-tests$/.test(name) && name !== 'HTMLFrameMojit-tests') { + if (RX_TESTS.test(name) && name !== 'HTMLFrameMojit-tests') { testQueue.push(name); } }); diff --git a/lib/app/middleware/mojito-combo-handler.js b/lib/app/middleware/mojito-combo-handler.js deleted file mode 100644 index a48673c1f..000000000 --- a/lib/app/middleware/mojito-combo-handler.js +++ /dev/null @@ -1,593 +0,0 @@ -/* - * Ext JS Connect - * Copyright(c) 2010 Sencha Inc. - * MIT Licensed - * - * Modified by Yahoo! - * Copyright (c) 2011-2012, Yahoo! Inc. All rights reserved. - * Yahoo! Copyrights licensed under the New BSD License. - * See the accompanying LICENSE file for terms. - */ - -/* - * Connect staticProvider middleware adapted for Mojito * - ******************************************************** - * This was modified to allow load all combo urls from the - * Mojito development environment instead of one static - * directory. - ******************************************************** - */ - - -/*jslint anon:true, sloppy:true, nomen:true, stupid:true, node: true*/ - -'use strict'; - -/* -DECLAIMER: this is VERY experimental, and the purpose of this -middleware is to provide an easy way to load yui modules by their -names rather than the real static path. - from: @caridy -*/ - - -/* - * Module dependencies. - */ -var libfs = require('fs'), - mime = require('mime'), - libpath = require('path'), - YUI = require(libpath.join(__dirname, '..', '..', 'yui-sandbox.js')).getYUI(), - parseUrl = require('url').parse, - logger, - NAME = 'ComboHandler', - - MODULE_META_ENTRIES = ['requires', 'use', 'optional', 'skinnable', 'after', 'condition'], - // TODO: revisit this list with @davglass - MODULE_META_PRIVATE_ENTRIES = ['after', 'expanded', 'supersedes', 'ext', '_parsed', '_inspected', - 'skinCache', 'langCache'], - - REGEX_LANG_TOKEN = /\"\{langToken\}\"/g, - REGEX_LANG_PATH = /\{langPath\}/g, - REGEX_LOCALE = /\_([a-z]{2}(-[A-Z]{2})?)$/, - - DEFAULT_HEADERS = { - '.js': { - 'Content-Type': 'application/javascript; charset=utf-8' - }, - '.css': { - 'Content-Type': 'text/css; charset=utf-8' - } - }, - - MODULE_TEMPLATES = { - 'loader-app-base': - 'YUI.add("loader-app-base",function(Y){' + - 'Y.applyConfig({groups:{app:{' + - 'combine:true,' + - 'maxURLLength:1024,' + - 'base:"/static/",' + - 'comboBase:"/static/combo?",' + - 'root:"",' + - 'modules:{app-base}' + - '}}});' + - '},"",{requires:["loader-base"]});', - 'loader-app-full': - 'YUI.add("loader-app-full",function(Y){' + - 'Y.applyConfig({groups:{app:{' + - 'combine:true,' + - 'maxURLLength:1024,' + - 'base:"/static/",' + - 'comboBase:"/static/combo?",' + - 'root:"",' + - 'modules:{app-full}' + - '}}});' + - '},"",{requires:["loader-base"]});', - - 'loader': - 'YUI.add("loader",function(Y){' + - '},"",{requires:["loader-base","loader-yui3","loader-app-base"]});', - - 'loader-lock': - 'YUI.add("loader",function(Y){' + - // TODO: we should use YUI.applyConfig() instead of the internal - // YUI.Env API, but that's pending due a bug in YUI: - // http://yuilibrary.com/projects/yui3/ticket/2532854 - 'YUI.Env[Y.version].modules=YUI.Env[Y.version].modules||' + - '{yui-base};' + - '},"",{requires:["loader-base","loader-app-base"]});', - - 'loader-full': - 'YUI.add("loader",function(Y){' + - // TODO: we should use YUI.applyConfig() instead of the internal - // YUI.Env API, but that's pending due a bug in YUI: - // http://yuilibrary.com/projects/yui3/ticket/2532854 - 'YUI.Env[Y.version].modules=YUI.Env[Y.version].modules||' + - '{yui-full};' + - '},"",{requires:["loader-base","loader-app-full"]});' - }; - -/* - * Check if `req` and response `headers`. - * - * @param {IncomingMessage} req - * @param {Object} headers - * @return {Boolean} - * @api private - */ -function modified(req, headers) { - var modifiedSince = req.headers['if-modified-since'], - lastModified = headers['Last-Modified'], - noneMatch = req.headers['if-none-match'], - etag = headers.ETag; - - // Check If-None-Match - if (noneMatch && etag && noneMatch === etag) { - return false; - } - - // Check If-Modified-Since - if (modifiedSince && lastModified) { - modifiedSince = new Date(modifiedSince); - lastModified = new Date(lastModified); - // Ignore invalid dates - if (!isNaN(modifiedSince.getTime())) { - if (lastModified <= modifiedSince) { - return false; - } - } - } - - return true; -} - -/* - * Check if `req` is a conditional GET request. - * - * @method conditionalGET - * @param {IncomingMessage} req - * @return {Boolean} - * @api private - */ -function conditionalGET(req) { - return req.headers['if-modified-since'] || - req.headers['if-none-match']; -} - -/* - * Return an ETag in the form of size-mtime. - * - * @method etag - * @param {Object} stat - * @return {String} - * @api private - */ -function etag(stat) { - return stat.size + '-' + Number(stat.mtime); -} - -/* - * Respond with 304 "Not Modified". - * - * @method notModified - * @param {ServerResponse} res - * @param {Object} headers - * @api private - */ -function notModified(res, headers) { - // Strip Content-* headers - Object.keys(headers).forEach(function(field) { - if (0 === field.indexOf('Content')) { - delete headers[field]; - } - }); - res.writeHead(304, headers); - res.end(); -} - -/* - * Respond with 403 "Forbidden". - * - * @method forbidden - * @param {ServerResponse} res - * @api private - */ -function forbidden(res) { - var body = 'Forbidden'; - res.writeHead(403, { - 'Content-Type': 'text/plain', - 'Content-Length': body.length - }); - res.end(body); -} - - -function processMeta(resolvedMods, modules, expanded_modules, langs, conditions) { - var m, - l, - i, - module, - name, - mod, - lang, - bundle; - - for (m in resolvedMods) { - if (resolvedMods.hasOwnProperty(m)) { - module = resolvedMods[m]; - - mod = name = module.name; - bundle = name.indexOf('lang/') === 0; - lang = bundle && REGEX_LOCALE.exec(name); - - if (lang) { - mod = mod.slice(0, lang.index); // eg. lang/foo_en-US -> lang/foo - lang = lang[1]; - // TODO: validate lang - langs.push(lang); // eg. en-US - } - mod = bundle ? mod.slice(5) : mod; // eg. lang/foo -> foo - - // language manipulation - // TODO: this routine is very restrictive, and we might want to - // make it optional later on. - if (module.lang) { - module.lang = ['{langToken}']; - } - if (bundle) { - module.owner = mod; - // applying some extra optimizations - module.langPack = lang || '*'; - module.intl = true; - delete module.expanded_map; - } - - if (module.condition && module.condition.test) { - conditions[module.name] = module.condition.test.toString(); - module.condition.test = "{" + module.name + "}"; - } - - modules[module.name] = {}; - if (module.type === 'css') { - modules[module.name].type = 'css'; - } - for (i = 0; i < MODULE_META_ENTRIES.length; i += 1) { - if (module[MODULE_META_ENTRIES[i]]) { - modules[module.name][MODULE_META_ENTRIES[i]] = - module[MODULE_META_ENTRIES[i]]; - } - } - - expanded_modules[module.name] = module; - for (i = 0; i < MODULE_META_PRIVATE_ENTRIES.length; i += 1) { - delete module[MODULE_META_PRIVATE_ENTRIES[i]]; - } - } - } -} - - -/* - * Static file server. - * - * Options: - * - * - `root` Root path from which to serve static files. - * - `maxAge` Browser cache maxAge in milliseconds, defaults to 0 - * - `cache` When true cache files in memory indefinitely, - * until invalidated by a conditional GET request. - * When given, maxAge will be derived from this value. - * - * @param {Object} options - * @return {Function} - * @api public - */ -function staticProvider(store, globalLogger) { - var appConfig = store.getAppConfig(store.getStaticContext()), - options = appConfig.staticHandling || {}, - cache = options.cache, - maxAge = options.maxAge, - urls = store.getAllModulesURLs(), - lang, - - // collecting client side metadata - mojits = store.yui.getConfigAllMojits('client', {}), - shared = store.yui.getConfigShared('client', {}, false), - modules_config, - Y, - loader, - resolved, - - appMetaData = { - base: {}, - full: {} - }, - yuiMetaData = { - base: {}, - full: {} - }, - - // other structures - langs = ['*'], // language wildcard - expanded_modules = {}, // expanded meta (including fullpaths) - modules = {}, // regular meta (a la loader-yui3) - conditions = {}, // hash to store conditional functions - name, - i; - - logger = globalLogger; - - Y = YUI({ - fetchCSS: true, - combine: true, - base: "/static/combo?", - comboBase: "/static/combo?", - root: "" - }, ((appConfig.yui && appConfig.yui.config && appConfig.yui.config.config) || {})); - - modules_config = Y.merge((mojits.modules || {}), (shared.modules || {})); - Y.applyConfig({ - modules: Y.merge({}, modules_config), - useSync: true - }); - Y.use('loader'); - - // using the loader at the server side to compute the loader metadata - // to avoid loading the whole thing on demand. - loader = new Y.Loader({ - require: Y.Object.keys(modules_config) - }); - resolved = loader.resolve(true); - - if (cache && !maxAge) { - maxAge = cache; - } - maxAge = maxAge || 0; - - processMeta(resolved.jsMods, modules, expanded_modules, langs, conditions); - processMeta(resolved.cssMods, modules, expanded_modules, langs, conditions); - - for (i = 0; i < langs.length; i += 1) { - lang = langs[i]; - - appMetaData.base[lang] = {}; - appMetaData.full[lang] = {}; - yuiMetaData.base[lang] = {}; - yuiMetaData.full[lang] = {}; - - for (name in expanded_modules) { - if (expanded_modules.hasOwnProperty(name)) { - if (expanded_modules[name].owner && - !expanded_modules[expanded_modules[name].owner]) { - // if there is not a module corresponding with the lang pack - // that means the controller doesn't have client affinity, - // in that case, we don't need to ship it. - continue; - } - if ((lang === '*') || - (expanded_modules[name].langPack === '*') || - (!expanded_modules[name].langPack) || - (lang === expanded_modules[name].langPack)) { - - // we want to separate modules into different buckets - // to be able to support groups in loader config - if (modules_config[name]) { - appMetaData.base[lang][name] = modules[name]; - appMetaData.full[lang][name] = expanded_modules[name]; - } else { - yuiMetaData.base[lang][name] = modules[name]; - yuiMetaData.full[lang][name] = expanded_modules[name]; - } - - } - } - } - - appMetaData.base[lang] = JSON.stringify(appMetaData.base[lang]); - appMetaData.full[lang] = JSON.stringify(appMetaData.full[lang]); - yuiMetaData.base[lang] = JSON.stringify(yuiMetaData.base[lang]); - yuiMetaData.full[lang] = JSON.stringify(yuiMetaData.full[lang]); - - for (name in conditions) { - if (conditions.hasOwnProperty(name)) { - appMetaData.base[lang] = appMetaData.base[lang] - .replace('"{' + name + '}"', conditions[name]); - appMetaData.full[lang] = appMetaData.full[lang] - .replace('"{' + name + '}"', conditions[name]); - yuiMetaData.base[lang] = yuiMetaData.base[lang] - .replace('"{' + name + '}"', conditions[name]); - yuiMetaData.full[lang] = yuiMetaData.full[lang] - .replace('"{' + name + '}"', conditions[name]); - } - } - - } - - - function produceMeta(name, lang) { - var token = '', - path = ''; - - if (lang) { - token = '"' + lang + '"'; - path = '_' + lang; - } else { - lang = '*'; - } - - // module definition definitions - return MODULE_TEMPLATES[name] - .replace('{app-base}', appMetaData.base[lang] || appMetaData.base['*']) - .replace('{app-full}', appMetaData.full[lang] || appMetaData.full['*']) - .replace('{yui-base}', yuiMetaData.base[lang] || yuiMetaData.base['*']) - .replace('{yui-full}', yuiMetaData.full[lang] || yuiMetaData.full['*']) - .replace(REGEX_LANG_TOKEN, token) - .replace(REGEX_LANG_PATH, path); - } - - - return function(req, res, next) { - if (req.method !== 'GET' && req.method !== 'HEAD') { - return next(); - } - - var url = parseUrl(req.url), - files = [], - filename = '', - module = '', - basemodule = '', - lang = '', - ext = '', - result = [], - counter = 0, - i = 0, - hit, - head = (req.method === 'HEAD'); - - // only combo requests are allow here - if (libpath.basename(url.pathname) !== 'combo' || !url.query) { - return next(); - } - - logger.log('serving combo url: ' + url.query, 'debug', NAME); - - // YIV might be messing around with the querystring params - // trying to formalize them by adding = and transforming / - // so we need to revert back to / and remove the = - // TODO: this might not work in Windows - files = url.query.replace(/[=]/g, '').replace(/%2F/g, '/').split('&'); - - function readHandler(index, filename) { - return function (err, data) { - var headers, - content = '', - i; - - counter += 1; - if (err) { - logger.log('NOT FOUND: ' + filename, 'warn', NAME); - } else { - result[index].content = data; - } - if (counter === files.length) { - - for (i = 0; i < counter; i += 1) { - content += result[i].content; - } - headers = Y.merge({ - 'Content-Length': content.length, - 'Last-Modified': new Date().toUTCString(), - 'Cache-Control': 'public max-age=' + (maxAge / 1000) - }, (DEFAULT_HEADERS[ext] || {})); - - res.writeHead(200, headers); - res.end(head ? undefined : content); - - } - }; - } - - if (files.length === 0) { - // probably an empty /combo? request - res.writeHead(400); - res.end(undefined); - return; - } - - // combo response's content-type should be - // resolved from the first file in the list - ext = libpath.extname(files[0]); - - if (!ext || !DEFAULT_HEADERS.hasOwnProperty(ext)) { - // probably an invalid request - res.writeHead(400); - res.end(undefined); - return; - } - - // validating all files before doing anything else - // so errors can be found early on. - for (i = 0; i < files.length; i += 1) { - - // something like: - // - foo/bar-min.js becomes "bar" - // - foo/lang/bar_en-US.js becomes "lang/bar_en-US" - module = (files[i].indexOf('/lang/') >= 0 ? 'lang/' : '') + - libpath.basename(files[i], ext).replace(/\-(min|debug)$/, ''); - - lang = REGEX_LOCALE.exec(module); - - if (lang) { - basemodule = module.slice(0, lang.index); // eg. lang/foo_en-US -> lang/foo - lang = lang[1]; - } else { - basemodule = module; - } - - // at this point, we should have: - // module == lang/foo_en-US or lang/foo or foo - // basemodule == lang/foo or foo - - if (MODULE_TEMPLATES[basemodule]) { - // getting a synthetic module - result[i] = { - fullpath: module, - content: produceMeta(basemodule, lang) - }; - } else if (urls[module]) { - // geting an app module - result[i] = { - fullpath: urls[module], - content: '' - }; - } else if (loader.moduleInfo[module] && loader.moduleInfo[module].path) { - // getting a yui module - result[i] = { - fullpath: libpath.join(__dirname, - '../../../node_modules/yui', loader.moduleInfo[module].path), - content: '' - }; - } else { - logger.log('Invalid module name: ' + module, 'warn', NAME); - res.writeHead(400); - res.end(undefined); - break; - } - - } - - // async queue implementation - if (files.length > 0 && (result.length === files.length)) { - for (i = 0; i < result.length; i += 1) { - filename = result[i].fullpath; - if (result[i].content) { - // if we already have content in memory, let's - // just use it directly. - readHandler(i, filename)(null, result[i].content); - } else { - try { - libfs.readFile(filename, readHandler(i, filename)); - } catch (err) { - logger.log('Error reading: ' + filename, 'error', NAME); - // this should never happen, if the module is in loader - // meta, it should be in disk as well. - res.writeHead(400); - res.end(undefined); - break; - } - } - } - } - - }; -} - - -/** - * Export function to create the static handler. - * @param {Object} config The configuration data for the handler. - * @return {Object} A static handler. - */ -module.exports = function(config) { - return staticProvider(config.store, config.logger, config.Y); -}; diff --git a/lib/app/middleware/mojito-handler-static.js b/lib/app/middleware/mojito-handler-static.js index 8e5e78413..70f7d1ae4 100644 --- a/lib/app/middleware/mojito-handler-static.js +++ b/lib/app/middleware/mojito-handler-static.js @@ -19,14 +19,16 @@ */ -/*jslint node:true, anon:true, sloppy:true, nomen:true */ +/*jslint node:true, nomen:true */ +'use strict'; /* * Module dependencies. */ -var parseUrl = require('url').parse, - logger, +var liburl = require('url'), + libpath = require('path'), + NAME = 'StaticHandler'; /* @@ -68,19 +70,6 @@ function modified(req, headers) { return true; } -/* - * Check if `req` is a conditional GET request. - * - * @method conditionalGET - * @param {IncomingMessage} req - * @return {Boolean} - * @api private - */ -function conditionalGET(req) { - return req.headers['if-modified-since'] || - req.headers['if-none-match']; -} - /* * Return an ETag in the form of size-mtime. * @@ -103,13 +92,16 @@ function etag(data, stat) { * @param {Object} headers * @api private */ -function notModified(res, headers) { - // Strip Content-* headers - Object.keys(headers).forEach(function(field) { - if (0 === field.indexOf('Content')) { - delete headers[field]; +function notModified(res, originalHeaders) { + var headers = {}, + field; + // skip Content-* headers + // making a copy of the original to avoid currupting the cache + for (field in originalHeaders) { + if (originalHeaders.hasOwnProperty(field) && (0 !== field.indexOf('Content'))) { + headers[field] = originalHeaders[field]; } - }); + } res.writeHead(304, headers); res.end(); } @@ -161,12 +153,19 @@ function clearCache(key) { } } - -function makeContentTypeHeader(res) { - return res.mime.type + (res.mime.charset ? '; charset=' + res.mime.charset : ''); +/* + * Make the appropiated content type based on a resource. + * + * @method makeContentTypeHeader + * @param {object} resource Resource from RS. + * @return {string} content-type + * @api private + */ +function makeContentTypeHeader(resource) { + return resource.mime.type + (resource.mime.charset ? '; charset=' + + resource.mime.charset : ''); } - /* * Static file server. * @@ -182,12 +181,17 @@ function makeContentTypeHeader(res) { * @return {Function} * @api public */ -function staticProvider(store, logger) { - var appConfig = store.getStaticAppConfig(), +function staticProvider(store, logger, Y) { + + var appConfig = store.getStaticAppConfig(), + yuiRess = store.yui.getYUIURLResources(), + staticRess = store.getAllURLResources(), + options = appConfig.staticHandling || {}, - cache = options.cache, - maxAge = options.maxAge, - urls = store.getAllURLResources(); + cache = options.cache, + maxAge = options.maxAge, + staticPath = liburl.resolve('/', (options.prefix || 'static') + '/'), + comboPath = '/combo~'; if (cache && !maxAge) { maxAge = cache; @@ -204,87 +208,186 @@ function staticProvider(store, logger) { } } - return function(req, res, next) { + return function (req, res, next) { if (req.method !== 'GET' && req.method !== 'HEAD') { - return next(); + next(); + return; } - var url = parseUrl(req.url), + var url = liburl.parse(req.url), path = url.pathname, + files = [], + file, + result = [], + failures = 0, + counter = 0, resource, - hit; + i; + + function tryToFlush() { + var headers, + len, + content, + j; + + if (counter < files.length) { + return; + } + if (counter === files.length) { + if (failures) { + notFound(res); + return; + } + } + + // first pass computes total length, so we can make a buffer of the + // correct size + len = 0; + for (j = 0; j < counter; j += 1) { + len += result[j].content.length; + } + content = new Buffer(len); + + // second pass actually fills the buffer + len = 0; + for (j = 0; j < counter; j += 1) { + result[j].content.copy(content, len); + len += result[j].content.length; + } + + // Serve the content of the file using buffers + // Response headers + headers = { + 'Content-Type': makeContentTypeHeader(result[0].res), + 'Content-Length': content.length, + 'Last-Modified': (options.forceUpdate || !result[0].stat) ? + new Date().toUTCString() : result[0].stat.ctime.toUTCString(), + 'Cache-Control': 'public max-age=' + (maxAge / 1000), + // Return an ETag in the form of size-mtime. + 'ETag': etag(content, result[0].stat) + }; + + done(req, res, { + path: path, + headers: headers, + body: content + }); + // adding guard in case tryToFlush is called twice. + counter = 0; + + } + + + function readHandler(index, path) { + // using the clousure to preserve the binding between + // the index, path and the actual result + return function (err, data, stat) { + + counter += 1; + if (err) { + logger.log('failed to read ' + path + ' because: ' + err.message, 'error', NAME); + notFound(res); + } else { + logger.log(path + ' was read from disk', 'debug', NAME); + result[index].content = data; + result[index].stat = stat; + // Cache support + if (cache) { + _cache[path] = result[index]; + } + // in case we have everything ready + tryToFlush(); + } + + }; + } + // TODO: [Issue 87] we should be able to just remove this, because // Mojito closes all bad URLs down. // Potentially malicious path if (path.indexOf('..') !== -1) { - return forbidden(res); + forbidden(res); + return; } - logger.log('serving static path: ' + path, 'debug', 'static-handler'); + // combo urls are allow as well in a form of: + // - /combo~foo.js~bar.js + if (path.indexOf(comboPath) === 0) { + // no spaces allowed + files = url.pathname.split('~'); + files.shift(); // removing te first element from the list + } else if (path.indexOf(staticPath) === 0 || staticRess[path]) { + files = [path]; + } else { + // this is not a static file + next(); + return; + } - resource = urls[path]; + for (i = 0; i < files.length; i += 1) { - // TODO: these adjustments should really be done by addons/rs/url - if (!resource && (path === '/favicon.ico')) { - resource = store.getResources('client', {}, {mojit: 'shared', id: 'asset-ico-favicon'}); - resource = resource[0]; - } - if (!resource && (path === '/robots.txt')) { - resource = store.getResources('client', {}, {mojit: 'shared', id: 'asset-txt-robots'}); - resource = resource[0]; - } - if (!resource && (path === '/crossdomain.xml')) { - resource = store.getResources('client', {}, {mojit: 'shared', id: 'asset-xml-crossdomain'}); - resource = resource[0]; - } + file = files[i]; - if (!resource) { - return next(); - } + if (cache && _cache[file]) { + + // Cache hit + logger.log(file + ' was read from cache', 'debug', NAME); + result[i] = _cache[file]; + + } else if (staticRess[file]) { + + // geting an static file + result[i] = { + path: file, + res: staticRess[file] + }; - // Cache hit - //if (cache && !conditionalGET(req) && (hit = _cache[path])) { - // logger.log(path + ' was read from cache', 'debug', NAME); - // return done(req, res, _cache[path]); - //} + } else if (yuiRess[file]) { - store.getResourceContent(resource, function (err, data, stat) { - var headers; + // getting a yui library file + result[i] = { + path: file, + res: yuiRess[file] + }; + + } else if (i < (files.length - 1)) { + + // Note: we are tolerants with the last file in the combo + // because some proxies/routers might cut the url, and + // the loader should be able to recover from that if we send + // a partial response. + notFound(res); + return; - if (err) { - logger.log('failed to read ' + path + ' because: ' + err.message, 'error', NAME); - return notFound(res); } - // Serve the content of the file using buffers + } - // Response headers - headers = { - 'Content-Type': makeContentTypeHeader(resource), - 'Content-Length': data.length, - 'Last-Modified': options.forceUpdate ? new Date().toUTCString() : stat.ctime.toUTCString(), - 'Cache-Control': 'public max-age=' + (maxAge / 1000), - // Return an ETag in the form of size-mtime. - 'ETag': etag(data, stat) - }; + // async queue implementation + if (result.length > 0) { - hit = { - path: path, - headers: headers, - body: data - }; + logger.log('serving ' + (result.length > 1 ? 'combo' : 'static') + ' path: ' + + path, 'debug', 'static-handler'); - // TODO: [Issue 92] This is not even being used here... - // remove and revisit (the _cache use is commented out below) - // Cache support - if (cache) { - _cache[path] = hit; + for (i = 0; i < result.length; i += 1) { + if (!result[i].content) { + store.getResourceContent(result[i].res, readHandler(i, result[i].path)); + } else { + counter += 1; + } } - logger.log(path + ' was read from disk', 'debug', NAME); - done(req, res, hit); - }); + // in case we get everything from cache + tryToFlush(); + + } else { + + next(); + return; + + } + }; } @@ -294,6 +397,6 @@ function staticProvider(store, logger) { * @param {Object} config The configuration data for the handler. * @return {Object} A static handler. */ -module.exports = function(config) { - return staticProvider(config.store, config.logger); +module.exports = function (config) { + return staticProvider(config.store, config.logger, config.Y); }; diff --git a/lib/config.json b/lib/config.json index f00aedae8..e9bd9186e 100644 --- a/lib/config.json +++ b/lib/config.json @@ -2,6 +2,7 @@ "###": "Copyright (c) 2012 Yahoo! Inc. All rights reserved.", "appConfigBase": { + "actionTimeout": 60000, "mojitsDirs": [ "mojits" ], "routesFiles": [ "routes.json" ], "tunnelPrefix": "/tunnel", @@ -10,6 +11,9 @@ "tunnelProxy": { "type": "TunnelProxy" } + }, + "staticHandling": { + "prefix": "static" } }, "defaultRoutes" : { diff --git a/lib/management/utils.js b/lib/management/utils.js index 0c83c7a13..437cbd1d4 100644 --- a/lib/management/utils.js +++ b/lib/management/utils.js @@ -5,55 +5,44 @@ */ -/*jslint anon:true, sloppy:true, nomen:true, stupid:true, regexp:true*/ - +/*jslint stupid:true, node:true, nomen:true*/ +'use strict'; var fs = require('fs'), util = require('util'), path = require('path'), existsSync = fs.existsSync || path.existsSync, hb = require('yui/handlebars').Handlebars, - tty = require('tty'), + tty = require('tty'), // use process.stdin/err.isTTY instead archetypes_dir = path.join(__dirname, '../app/archetypes'), - isatty = tty.isatty(1) && tty.isatty(2); + colors = require('colors'); -if (!isatty) { - // fake out the getters that the "color" library would have added - (['bold', 'underline', 'italic', 'inverse', 'grey', 'yellow', 'red', - 'green', 'blue', 'white', 'cyan', 'magenta']).forEach(function(style) { - try { - Object.defineProperty(String.prototype, style, { - get: function() { - return this; - } - }); - } catch (e) { - // just ignore - } - }); -} else { - require('colors'); -} +colors.mode = (tty.isatty(1) && tty.isatty(2)) ? 'console' : 'none'; function log(message) { - console.log(message.cyan); + console.log(message.cyan.toString()); } - function error(message, usage, die) { + var msgs = []; + if (message instanceof Error) { - console.log(('✖ ' + message.message).red.bold); + msgs.push(('✖ ' + message.message).red.bold); if (message.stack) { - console.log(('\t' + message.stack).red.bold); + msgs.push('\n' + message.stack.red); } } else { - console.log(('✖ ' + message).red.bold); + msgs.push(('✖ ' + message).red.bold); } + if (usage) { - console.log('usage:\n' + usage.grey); + msgs.push('\nusage: ' + usage.grey); } + + console.error(msgs.join(' ')); + if (die) { process.exit(-1); } @@ -61,12 +50,12 @@ function error(message, usage, die) { function success(message) { - console.log(('✔ ' + message).green.bold); + console.log(('✔ ' + message).green.bold.toString()); } function warn(message) { - console.log(('⚠ ' + message).yellow); + console.warn(('⚠ ' + message).yellow.toString()); } @@ -86,7 +75,8 @@ function heir(o) { */ // FUTURE: find a node module that can do this well function decodeHTMLEntities(txt) { - txt = txt.replace(/(&[^;]+;)/g, function(all, ent) { + /*jslint regexp:true, unparam: true */ + txt = txt.replace(/(&[^;]+;)/g, function (all, ent) { if ('&#x' === ent.substr(0, 3)) { return String.fromCharCode(parseInt(ent.substring(3, ent.length - 1), 16)); } @@ -105,7 +95,6 @@ function process_template(archetype_path, file, mojit_dir, template) { var archetype_file = path.join(archetype_path, file), new_file = path.join(mojit_dir, file.substring(0, file.length - 3)), - buffer = '', stat, tmpl, compiled, @@ -135,7 +124,7 @@ function process_file(archetype_path, file, mojit_dir, template) { util.pump( fs.createReadStream(path.join(archetype_path, file)), fs.createWriteStream(path.join(mojit_dir, file)), - function(err) { + function (err) { if (err) { warn('Failed to copy file: ' + file); } @@ -171,7 +160,7 @@ function process_directory(archetype_path, dir, mojit_dir, template, force) { // console.log('reading dir: ' + path.join(archetype_path, dir)); files = fs.readdirSync(path.join(archetype_path, dir)); - files.forEach(function(f) { + files.forEach(function (f) { var s = fs.statSync(path.join(archetype_path, '/', dir, '/', f)); if (f.charAt(0) === '.') { @@ -196,7 +185,7 @@ function validate_archetype(archetype_type, archetype_name) { try { curdir = path.join(archetypes_dir, archetype_type); files = fs.readdirSync(curdir); - files.forEach(function(archetype) { + files.forEach(function (archetype) { var s = fs.statSync(path.join(curdir, archetype)); if (s.isDirectory()) { @@ -460,10 +449,9 @@ function isMojitoApp(dir, usage, die) { // - the file above must require('mojito') var requiresMojito = /require\s*\(\s*'mojito'\s*\)/, - isMojito = false, - checker; + isMojito = false; - checker = function(file) { + function checker(file) { var filepath, contents; @@ -473,7 +461,7 @@ function isMojitoApp(dir, usage, die) { contents = fs.readFileSync(filepath, 'utf-8'); isMojito = requiresMojito.test(contents); } - }; + } ['server.js', 'index.js'].forEach(checker); diff --git a/lib/mojito.js b/lib/mojito.js index 5121d3f9a..0a07d1ada 100644 --- a/lib/mojito.js +++ b/lib/mojito.js @@ -83,7 +83,6 @@ function MojitoServer(options) { * @type {Array.<string>} */ MojitoServer.MOJITO_MIDDLEWARE = [ - 'mojito-combo-handler', 'mojito-handler-static', 'mojito-parser-body', 'mojito-parser-cookies', @@ -193,13 +192,11 @@ MojitoServer.prototype._configureAppInstance = function(app, options) { appConfig = store.getAppConfig(store.getStaticContext()); yuiConfig = (appConfig.yui && appConfig.yui.config) || {}; - YUI = YUIFactory.getYUI(yuiConfig.filter); - Y = YUI({ - useSync: true - }); - - this._configureLogger(Y, yuiConfig); - this._configureYUI(Y, store, modules); + // redefining "combine" and/or "base" in the server side have side effects + // and might try to load yui from CDN, so we bypass them. + // TODO: report bug. + delete yuiConfig.combine; + delete yuiConfig.base; // in case we want to collect some performance metrics, // we can do that by defining the "perf" object in: @@ -211,9 +208,14 @@ MojitoServer.prototype._configureAppInstance = function(app, options) { yuiConfig.perf.logFile = options.perf; } - + YUI = YUIFactory.getYUI(yuiConfig.filter); // applying the default configuration from application.json->yui-config - Y.applyConfig(yuiConfig); + Y = YUI(yuiConfig, { + useSync: true + }); + + this._configureLogger(Y, yuiConfig); + this._configureYUI(Y, store, modules); // attaching all modules available for this application for the server side Y.applyConfig({ useSync: true }); @@ -400,7 +402,8 @@ MojitoServer.prototype._configureYUI = function(Y, store, load) { var mojits = store.yui.getConfigAllMojits('server', {}), shared = store.yui.getConfigShared('server', {}, false), modules, - module; + module, + lang; modules = Y.merge((mojits.modules || {}), (shared.modules || {})); @@ -414,6 +417,14 @@ MojitoServer.prototype._configureYUI = function(Y, store, load) { load.push(module); } } + + // NOTE: Not all of these module names are guaranteed to be valid, + // but the loader tolerates them anyways. + for (lang in store.yui.langs) { + if (store.yui.langs.hasOwnProperty(lang) && lang) { + load.push('lang/datatype-date-format_' + lang); + } + } }; diff --git a/lib/store.js b/lib/store.js index 7bd3deb0e..ea365e462 100644 --- a/lib/store.js +++ b/lib/store.js @@ -28,7 +28,7 @@ var Store = {}; /** * Creates a new server-side resource store instance and returns it. * @method createStore - * @param {{dir: string, + * @param {{root: string, * context: Object, * appConfig: Object, * verbose: boolean}} options An object containing store options. @@ -43,8 +43,8 @@ Store.createStore = function(options) { if (!options) { options = {}; } - if (!options.dir) { - options.dir = process.cwd(); + if (!options.root) { + options.root = process.cwd(); } if (!options.context) { options.context = {}; @@ -74,7 +74,7 @@ Store.createStore = function(options) { Y.use('mojito-resource-store'); store = new Y.mojito.ResourceStore({ - root: options.dir, + root: options.root, context: options.context, appConfig: options.appConfig }); diff --git a/lib/yui-sandbox.js b/lib/yui-sandbox.js index 5a66e7ca0..4fd6428c1 100644 --- a/lib/yui-sandbox.js +++ b/lib/yui-sandbox.js @@ -38,6 +38,9 @@ exports.getYUI = function (filter) { module: module, setTimeout: setTimeout, setInterval: setInterval, + clearTimeout: clearTimeout, + clearInterval: clearInterval, + JSON: JSON, __filename: __filename, __dirname: path.join(__dirname, '..', 'node_modules', 'yui', 'yui-nodejs'), exports: {} diff --git a/package.json b/package.json index 50d0991b4..b789141ad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mojito", - "version": "0.5.0pr2", + "version": "0.5.0pr4", "description": "Mojito provides an architecture, components and tools for developers to build complex web applications faster.", "preferGlobal": true, "author": "Drew Folta <folta@yahoo-inc.com>", @@ -49,9 +49,8 @@ "npm": ">1.0" }, "devDependencies": { - "ejs": "*", "node-static": "~0.6.1", - "yahoo-arrow": "0.0.59" + "yahoo-arrow": "0.0.64" }, "homepage": "http://developer.yahoo.com/cocktails/mojito/", "repository": { diff --git a/tests/base/mojito-test.js b/tests/base/mojito-test.js index 2315b7717..6e7f86178 100644 --- a/tests/base/mojito-test.js +++ b/tests/base/mojito-test.js @@ -13,12 +13,38 @@ * Baseline Mojito client testing harness. */ YUI.add('mojito', function(Y, NAME) { + + // TODO: why does need to be in sync with autoload/mojito.common.js + // why don't we just attach that to every test? + Y.namespace('mojito'); + Y.namespace('mojito.trans'); + Y.namespace('mojito.actions'); + Y.namespace('mojito.binders'); + Y.namespace('mojito.controllers'); + Y.namespace('mojito.models'); + Y.namespace('mojito.addons'); Y.namespace('mojito.addons.ac'); - Y.namespace('mojito').perf = { - mark: function () {}, - timeline: function () { return { done: function() {} }; } + Y.namespace('mojito.addons.viewEngines'); + + // this is a facade for the real implementation from mojito-perf module + // that will have to be plugged manually to get the metrics in the + // console or a log file. + Y.mojito.perf = { + timeline: function () { + return { + done: function () {} + }; + }, + mark: function () {} }; + + // internal mojito framework cache (this is probably legacy) + YUI.namespace('_mojito._cache'); + + // setting the stage for all data-scopes implementation + YUI.namespace('Env.mojito'); + }); /* AC ADDONS */ diff --git a/tests/func/applications/frameworkapp/common/application.json b/tests/func/applications/frameworkapp/common/application.json index 4ab5e5307..2c2133c2c 100644 --- a/tests/func/applications/frameworkapp/common/application.json +++ b/tests/func/applications/frameworkapp/common/application.json @@ -1,400 +1,396 @@ -[{ - "settings": [ "master" ], - "config": { - "myconfig0": "This is myconfig0 from top application.json", - "myconfig1": "This is myconfig1 from top application.json", - "myconfig2": "This is myconfig2 from top application.json", - "myconfig3": "This is myconfig3 from top application.json" - }, - "appPort": 4081, - "log": { - "client": { - "level": "debug", - "yui": false - }, - "server": { - "level": "mojito", - "yui": false - } - }, - "builds": { - "html5app": { - "urls": [ - "/mytestpath/index.html" - ] - } +[ + { + "settings": [ "master" ], + "config": { + "myconfig0": "This is myconfig0 from top application.json", + "myconfig1": "This is myconfig1 from top application.json", + "myconfig2": "This is myconfig2 from top application.json", + "myconfig3": "This is myconfig3 from top application.json" + }, + "appPort": 4081, + "yui": { + "config": { + "debug": true, + "logLevel": "debug", + "root": "static/yui", + "combine": false + } + }, + "builds": { + "html5app": { + "forceRelativePaths": true, + "urls": [ + "/mytestpath/index.html" + ] + } + }, + "mojitsDirs": [ + "mojits/", + "mojits_subdir1/*/", + "mojits_subdir2/*/", + "../test_files/mojits/" + ], + "mojitDirs": [ + "just_one_mojit/one_level_in/Binders", + "mojits_params/*" + ], + "staticHandling": { + "appName": "myNewAppName" }, - "mojitsDirs": [ - "mojits/", - "mojits_subdir1/*/", - "mojits_subdir2/*/", - "../test_files/mojits/" - ], - "mojitDirs": [ - "just_one_mojit/one_level_in/Binders", - "mojits_params/*" - ], - "staticHandling": { - "useRollups": true, - "appName": "myNewAppName" - }, - "yui": { - "extraModules": ["plugin","jsonp"] - }, "specs": { - "frame": { - "type": "HTMLFrameMojit", + "frame": { + "type": "HTMLFrameMojit", + "config": { + "deploy": true, + "child": { + "type": "TestsLayout", "config": { - "deploy": true, - "child": { - "type": "TestsLayout", - "config": { - "children": { - "SimpleModel": { - "base": "SimpleModel", - "config": { - "myUrls": [ - "/SimpleModel/simpleModel", - "/SimpleModel/configModel" - ] - } - }, - "ACMojit": { - "base": "ACMojit", - "config": { - "myUrls": [ - "/ACMojit/acMojit?test=done1" - ] - } - }, - "GetParams": { - "base": "GetParams", - "config": { - "myUrls": [ - "/GetParams/allParams?foo=123&bar=2&test=no", - "/GetParams/paramsByValue?foo=abc", - "/GetParams/allParamsSimple?foo=123&bar=2&test=no", - "/GetParams/paramsByValueSimple?foo=abc" - ] - } - }, - "PostParams": { - "base": "PostParams", - "config": { - "myUrls": [ - "/Poster/index" - ] - } - }, - "MergeParams": { - "base": "MergeParams", - "config": { - "myUrls": [ - "/MergePoster/index" - ] - } - }, - "RouteParams": { - "type": "RouteParams", - "config": { - "myUrls": [ - "/RouteParams", - "/RouteParamsSimple" - ] - } - }, - "ClientCookie": { - "type": "ClientCookie", - "config": { - "myUrls": [ - "/@ClientCookie/setCookies", - "/@ClientCookie/catch" - ] - } - }, - "ControllerCaching": { - "base": "ControllerCaching" - }, - "MojitContainer": { - "type": "MojitContainer", - "config": { - "children": { - "a": { - "base": "ControllerCaching" - }, - "b": { - "base": "ControllerCaching" - } - } - } - }, - "BroadCast": { - "type": "BroadCast", - "config": { - "children": { - "devil": { - "type": "RedChild" - }, - "socks": { - "type": "RedChild" - }, - "jeans": { - "type": "BlueChild" - }, - "jay": { - "type": "BlueChild" - }, - "happy": { - "type": "GreenChild" - }, - "sad": { - "type": "GreenChild" - } - } - } - }, - "MojitProxyMojit": { - "type": "MojitProxyMojit" - }, - "PartialMojit": { - "type": "PartialMojit", - "config": { - "myUrls": [ - "/@PartialMojit/mytest", - "/@PartialMojit/myinvoke" - ] - } - }, - "DepCheckParent": { - "type": "DepCheckParent", - "config": { - "children": { - "meta": { - "type": "MetaChild" - } - } - } - }, - "LazyParent": { - "type": "LazyParent", - "config": { - "children": { - "LazyDefer": { - "type": "LazyChild", - "action": "hello", - "defer": true - } - } - } - }, - "CompositeMojit": { - "base": "CM_Layout" - }, - "SimpleBinders": { - "base": "Binders", - "config": { - "myUrls": [ - "/binderframe" - ] - } - }, - "Configs": { - "base": "MyConfig", - "config": { - "myUrls": [ - "/MyConfig/myIndex" - ] - } - }, - "Assets": { - "base": "MyAssets", - "config": { - "myUrls": [ - "/assetsframeloc", - "/assetsframedefault" - ] - } - }, - "Coverage_Client": { - "base": "CoverageClient" - } - } + "children": { + "SimpleModel": { + "base": "SimpleModel", + "config": { + "myUrls": [ + "/SimpleModel/simpleModel", + "/SimpleModel/configModel" + ] + } + }, + "ACMojit": { + "base": "ACMojit", + "config": { + "myUrls": [ + "/ACMojit/acMojit?test=done1" + ] + } + }, + "GetParams": { + "base": "GetParams", + "config": { + "myUrls": [ + "/GetParams/allParams?foo=123&bar=2&test=no", + "/GetParams/paramsByValue?foo=abc", + "/GetParams/allParamsSimple?foo=123&bar=2&test=no", + "/GetParams/paramsByValueSimple?foo=abc" + ] + } + }, + "PostParams": { + "base": "PostParams", + "config": { + "myUrls": [ + "/Poster/index" + ] + } + }, + "MergeParams": { + "base": "MergeParams", + "config": { + "myUrls": [ + "/MergePoster/index" + ] + } + }, + "RouteParams": { + "type": "RouteParams", + "config": { + "myUrls": [ + "/RouteParams", + "/RouteParamsSimple" + ] + } + }, + "ClientCookie": { + "type": "ClientCookie", + "config": { + "myUrls": [ + "/@ClientCookie/setCookies", + "/@ClientCookie/catch" + ] + } + }, + "ControllerCaching": { + "base": "ControllerCaching" + }, + "MojitContainer": { + "type": "MojitContainer", + "config": { + "children": { + "a": { + "base": "ControllerCaching" + }, + "b": { + "base": "ControllerCaching" + } } + } }, - "assets": { - "top": { - "css": [ - "/static/TestsLayout/assets/index.css" - ] - }, - "bottom": { - "js": [ - "/static/TestsLayout/assets/main.js" - ] - } - } - } - }, - "SimpleModel": { - "type": "SimpleModel", - "config": { - "myconfig0": "This is myconfig0 from application.json", - "myconfig1": "This is myconfig1 from application.json" - } - }, - "ACMojit": { - "type": "ACMojit" - }, - "GetParams": { - "type": "GetParams" - }, - "Poster": { - "type": "Poster" - }, - "PostParams": { - "type": "PostParams" - }, - "MergePoster": { - "type": "MergePoster" - }, - "MergeParams": { - "type": "MergeParams" - }, - "RouteParams": { - "type": "RouteParams" - - }, - "SearchMojit":{ - "type": "HTMLFrameMojit", - "config": { - "deploy": true, - "title": "Search Mojit", - "child": { - "type": "SearchMojit" - } - } - }, - "ControllerCaching": { - "type": "Stateful" - }, - "Binders_HTMLFrame": { - "type": "HTMLFrameMojit", - "config": { - "deploy": true, - "child": { - "base": "Binders" - } - } - }, - "Binders": { - "type": "Binders", - "config": { - "id": "myId", - "config_data": "This is the data coming from the config", - "extra": "This is extra text from the config file" - } - }, - "CM_Footer": { - "type": "CM_Footer" - }, - "CM_Layout_HTMLFrame": { - "type": "HTMLFrameMojit", - "config": { - "deploy": true, - "child": { - "base": "CM_Layout" - } - } - }, - "CM_Layout" :{ - "type": "CM_Layout", - "config": { - "id": "layout", - "children": { - "nav": { - "type": "CM_Nav", - "config":{ - "id": "nav" - } - }, - "news": { - "type": "CM_News", - "config":{ - "id": "news" - } - }, - "footer": { - "base": "CM_Footer", - "config":{ - "id": "footer_id" - } + "BroadCast": { + "type": "BroadCast", + "config": { + "children": { + "devil": { + "type": "RedChild" + }, + "socks": { + "type": "RedChild" + }, + "jeans": { + "type": "BlueChild" + }, + "jay": { + "type": "BlueChild" + }, + "happy": { + "type": "GreenChild" + }, + "sad": { + "type": "GreenChild" + } + } + } + }, + "MojitProxyMojit": { + "type": "MojitProxyMojit" + }, + "PartialMojit": { + "type": "PartialMojit", + "config": { + "myUrls": [ + "/@PartialMojit/mytest", + "/@PartialMojit/myinvoke" + ] + } + }, + "DepCheckParent": { + "type": "DepCheckParent", + "config": { + "children": { + "meta": { + "type": "MetaChild" + } + } + } + }, + "LazyParent": { + "type": "LazyParent", + "config": { + "children": { + "LazyDefer": { + "type": "LazyChild", + "action": "hello", + "defer": true + } } + } + }, + "CompositeMojit": { + "base": "CM_Layout" + }, + "SimpleBinders": { + "base": "Binders", + "config": { + "myUrls": [ + "/binderframe" + ] + } + }, + "Configs": { + "base": "MyConfig", + "config": { + "myUrls": [ + "/MyConfig/myIndex" + ] + } + }, + "Assets": { + "base": "MyAssets", + "config": { + "myUrls": [ + "/assetsframeloc", + "/assetsframedefault" + ] + } + }, + "Coverage_Client": { + "base": "CoverageClient" } + } } - }, - "CM_Footer": { - "type": "CM_Footer" - }, - "MyConfig": { - "type": "ConfigMojit", - "config":{ - "config1": "This is the config for config1 in application.yaml", - "commonKey1": "Value of commonKey1 in application.yaml", - "configArray1":[ - "configArray1Value1", - "configArray1Value2", - "configArray1Value3" - ], - "config2":{ - "config2Key1": "config2Key1 value from application.yaml", - "config2Key2": { - "config2Key2Key1": "It gets complicated here- config2Key2Key1 value in application.yaml", - "config2Key2Key2": "config2Key2Key2 value in application.yaml" - }, - "config2Key3Array1": [ - "config2Key3Array1Value1", - "config2Key3Array1Value2", - "config2Key3Array1Value3" - ] - } + }, + "assets": { + "top": { + "css": [ + "/static/TestsLayout/assets/index.css" + ] + }, + "bottom": { + "js": [ + "/static/TestsLayout/assets/main.js" + ] } - }, - "MyAssets_HTMLFrame": { - "type": "HTMLFrameMojit", - "config": { - "deploy": true, - "child": { - "base": "MyAssets" - } + } + } + }, + "SimpleModel": { + "type": "SimpleModel", + "config": { + "myconfig0": "This is myconfig0 from application.json", + "myconfig1": "This is myconfig1 from application.json" + } + }, + "ACMojit": { + "type": "ACMojit" + }, + "GetParams": { + "type": "GetParams" + }, + "Poster": { + "type": "Poster" + }, + "PostParams": { + "type": "PostParams" + }, + "MergePoster": { + "type": "MergePoster" + }, + "MergeParams": { + "type": "MergeParams" + }, + "RouteParams": { + "type": "RouteParams" + + }, + "SearchMojit":{ + "type": "HTMLFrameMojit", + "config": { + "deploy": true, + "title": "Search Mojit", + "child": { + "type": "SearchMojit" + } + } + }, + "ControllerCaching": { + "type": "Stateful" + }, + "Binders_HTMLFrame": { + "type": "HTMLFrameMojit", + "config": { + "deploy": true, + "child": { + "base": "Binders" + } + } + }, + "Binders": { + "type": "Binders", + "config": { + "id": "myId", + "config_data": "This is the data coming from the config", + "extra": "This is extra text from the config file" + } + }, + "CM_Footer": { + "type": "CM_Footer" + }, + "CM_Layout_HTMLFrame": { + "type": "HTMLFrameMojit", + "config": { + "deploy": true, + "child": { + "base": "CM_Layout" + } + } + }, + "CM_Layout" :{ + "type": "CM_Layout", + "config": { + "id": "layout", + "children": { + "nav": { + "type": "CM_Nav", + "config":{ + "id": "nav" + } + }, + "news": { + "type": "CM_News", + "config":{ + "id": "news" + } + }, + "footer": { + "base": "CM_Footer", + "config":{ + "id": "footer_id" + } } - }, - "MyAssets": { - "type": "AssetsMojit", - "config": { - } - }, - "AppLevelMojit": { - "type": "GlobalMojit" - }, - "GetAppLevelInfo": { - "type": "AccessGlobalMojit" - }, - "mobiledevices": { - "type": "MobileDevices" - }, - "Coverage": { - "type": "Coverage" - }, - "CoverageClient": { - "type": "CoverageClient" + } + } + }, + "CM_Footer": { + "type": "CM_Footer" + }, + "MyConfig": { + "type": "ConfigMojit", + "config":{ + "config1": "This is the config for config1 in application.yaml", + "commonKey1": "Value of commonKey1 in application.yaml", + "configArray1":[ + "configArray1Value1", + "configArray1Value2", + "configArray1Value3" + ], + "config2":{ + "config2Key1": "config2Key1 value from application.yaml", + "config2Key2": { + "config2Key2Key1": "It gets complicated here- config2Key2Key1 value in application.yaml", + "config2Key2Key2": "config2Key2Key2 value in application.yaml" + }, + "config2Key3Array1": [ + "config2Key3Array1Value1", + "config2Key3Array1Value2", + "config2Key3Array1Value3" + ] + } } + }, + "MyAssets_HTMLFrame": { + "type": "HTMLFrameMojit", + "config": { + "deploy": true, + "child": { + "base": "MyAssets" + } + } + }, + "MyAssets": { + "type": "AssetsMojit", + "config": { + } + }, + "AppLevelMojit": { + "type": "GlobalMojit" + }, + "GetAppLevelInfo": { + "type": "AccessGlobalMojit" + }, + "mobiledevices": { + "type": "MobileDevices" + }, + "Coverage": { + "type": "Coverage" + }, + "CoverageClient": { + "type": "CoverageClient" + } } - }, - { "settings": [ "device:android" ], "selector": "android" }, - { "settings": [ "device:blackberry" ], "selector": "blackberry" }, - { "settings": [ "device:iemobile" ], "selector": "iemobile" }, - { "settings": [ "device:iphone" ], "selector": "iphone" }, - { "settings": [ "device:kindle" ], "selector": "kindle" }, - { "settings": [ "device:opera-mini" ], "selector": "opera-mini" }, - { "settings": [ "device:palm" ], "selector": "palm" } + }, + { "settings": [ "device:android" ], "selector": "android" }, + { "settings": [ "device:blackberry" ], "selector": "blackberry" }, + { "settings": [ "device:iemobile" ], "selector": "iemobile" }, + { "settings": [ "device:iphone" ], "selector": "iphone" }, + { "settings": [ "device:kindle" ], "selector": "kindle" }, + { "settings": [ "device:opera-mini" ], "selector": "opera-mini" }, + { "settings": [ "device:palm" ], "selector": "palm" } ] diff --git a/tests/func/applications/frameworkapp/common/mojits/BroadCast/controller.common.js b/tests/func/applications/frameworkapp/common/mojits/BroadCast/controller.common.js index 4f934944f..dced48e3e 100644 --- a/tests/func/applications/frameworkapp/common/mojits/BroadCast/controller.common.js +++ b/tests/func/applications/frameworkapp/common/mojits/BroadCast/controller.common.js @@ -125,4 +125,8 @@ YUI.add('BroadCast', function(Y, NAME) { }; -}, '0.0.1', {requires: ['mojito']}); +}, '0.0.1', {requires: [ + 'mojito', + 'mojito-composite-addon', + 'mojito-assets-addon' +]}); diff --git a/tests/func/applications/frameworkapp/common/mojits/CM_Nav/binders/index.js b/tests/func/applications/frameworkapp/common/mojits/CM_Nav/binders/index.js index 1c98df013..fdadd9c9b 100644 --- a/tests/func/applications/frameworkapp/common/mojits/CM_Nav/binders/index.js +++ b/tests/func/applications/frameworkapp/common/mojits/CM_Nav/binders/index.js @@ -63,4 +63,4 @@ YUI.add('CM_NavBinderIndex', function(Y, NAME) { }; -}, '0.0.1', {requires: []}); +}, '0.0.1', {requires: ['mojito-client', 'node']}); diff --git a/tests/func/applications/frameworkapp/common/mojits/CM_Nav/controller.common.js b/tests/func/applications/frameworkapp/common/mojits/CM_Nav/controller.common.js index 5ac93ca1e..473a71804 100644 --- a/tests/func/applications/frameworkapp/common/mojits/CM_Nav/controller.common.js +++ b/tests/func/applications/frameworkapp/common/mojits/CM_Nav/controller.common.js @@ -17,10 +17,6 @@ YUI.add('CM_Nav', function(Y, NAME) { */ Y.namespace('mojito.controllers')[NAME] = { - init: function(config) { - this.config = config; - }, - /** * Method corresponding to the 'index' action. * diff --git a/tests/func/applications/frameworkapp/common/mojits/CM_Nav/views/index.mu.html b/tests/func/applications/frameworkapp/common/mojits/CM_Nav/views/index.mu.html index 24d10bf1b..04ca9bc09 100644 --- a/tests/func/applications/frameworkapp/common/mojits/CM_Nav/views/index.mu.html +++ b/tests/func/applications/frameworkapp/common/mojits/CM_Nav/views/index.mu.html @@ -1,4 +1,4 @@ <div id="{{mojit_view_id}}" class="mojit"> <h3 id="nav_h3">{{title}}</h3> - <a id="nav_a" href="#"><u>ALERT - Click on me to raise an alert !!</u></a> -</div> \ No newline at end of file + <a id="nav_a" href="#"><u>ALERT - Click on me to raise an alert !!</u></a> +</div> diff --git a/tests/func/common/html5apptest_descriptor.json b/tests/func/common/html5apptest_descriptor.json index ed40010f0..12e53fe67 100755 --- a/tests/func/common/html5apptest_descriptor.json +++ b/tests/func/common/html5apptest_descriptor.json @@ -4,8 +4,6 @@ "name" : "html5app", - "commonlib" : "../../base/yui-test.js", - "config" :{ "baseUrl" : "http://localhost:4084" }, diff --git a/tests/func/common/testacmojitdone1client.js b/tests/func/common/testacmojitdone1client.js index 97cce25c5..4fa90983d 100644 --- a/tests/func/common/testacmojitdone1client.js +++ b/tests/func/common/testacmojitdone1client.js @@ -1,27 +1,23 @@ /* * This is a basic func test for a Common application. */ -YUI({ - useConsoleOutput: true, - useBrowserConsole: true, - logInclude: { TestRunner: true } -}).use('node', 'node-event-simulate', 'test', 'console', function (Y) { - +YUI.add('acmojitdone1client-tests', function (Y) { + var suite = new Y.Test.Suite("Common: acmojitdone1client"); suite.add(new Y.Test.Case({ - - "test acmojitdone1client": function() { - var that = this; - Y.one('#testcase > option[value="done1"]').set('selected','selected'); - Y.one('#acMojitButton').simulate('click'); - that.wait(function(){ - Y.Assert.areEqual('Hello Action Context Testing', Y.one('#ACMojitTest').get('innerHTML').match(/Hello Action Context Testing/gi)); - }, 2000); - } - })); + "test acmojitdone1client": function() { + var that = this; + Y.one('#testcase > option[value="done1"]').set('selected','selected'); + Y.one('#acMojitButton').simulate('click'); + that.wait(function(){ + Y.Assert.areEqual('Hello Action Context Testing', Y.one('#ACMojitTest').get('innerHTML').match(/Hello Action Context Testing/gi)); + }, 2000); + } + + })); - Y.Test.Runner.add(suite); + Y.Test.Runner.add(suite); -}); \ No newline at end of file +}, '0.0.1', { requires: ['test', 'node', 'node-event-simulate', 'console']}); diff --git a/tests/func/common/testcompositemojit1client.js b/tests/func/common/testcompositemojit1client.js index ca3c30c9b..8a2327a8c 100644 --- a/tests/func/common/testcompositemojit1client.js +++ b/tests/func/common/testcompositemojit1client.js @@ -1,11 +1,7 @@ /* * This is a basic func test for a Common application. */ -YUI({ - useConsoleOutput: true, - useBrowserConsole: true, - logInclude: { TestRunner: true } -}).use('node', 'node-event-simulate', 'test', 'console', function (Y) { +YUI.add('compositemojit1client-tests', function (Y) { var suite = new Y.Test.Suite("Common: compositemojit1client"); @@ -32,4 +28,4 @@ YUI({ Y.Test.Runner.add(suite); -}); \ No newline at end of file +}, '0.0.1', { requires: ['node', 'test', 'node-event-simulate', 'console']}); \ No newline at end of file diff --git a/tests/func/common/testconfiginappfilecompclient.js b/tests/func/common/testconfiginappfilecompclient.js index 664a8b092..e18d69150 100644 --- a/tests/func/common/testconfiginappfilecompclient.js +++ b/tests/func/common/testconfiginappfilecompclient.js @@ -15,8 +15,18 @@ YUI({ var that = this; Y.one('#config_button').simulate('click'); that.wait(function(){ - Y.Assert.areEqual('ac.config.get\(\) -', Y.one('#completeConfig').get('innerHTML').match(/ac.config.get\(\) -/gi)); - Y.Assert.areEqual('\{\"key1\":\"This is the value from the default.yaml for key1\",\"key2\":\"This is the value from the default.yaml for key2\",\"commonKey1\":\"Value of commonKey1 in application.yaml\",\"defaultArray\":\[\"defaultArrayValue1\",\"defaultArrayValue2\"\],\"nestedConfig\":\{\"subConfig1\":\"SubConfig from defaults.yaml\",\"subConfig2\":\{\"subsubConfig1\":\"SubSubConfig1 from defaults.yaml\",\"subsubConfig2\":\"SubSubConfig2 from defaults.yaml\"\}\},\"config1\":\"This is the config for config1 in application.yaml\",\"configArray1\":\[\"configArray1Value1\",\"configArray1Value2\",\"configArray1Value3\"\],\"config2\":\{\"config2Key1\":\"config2Key1 value from application.yaml\",\"config2Key2\":\{\"config2Key2Key1\":\"It gets complicated here- config2Key2Key1 value in application.yaml\",\"config2Key2Key2\":\"config2Key2Key2 value in application.yaml\"\},\"config2Key3Array1\":\[\"config2Key3Array1Value1\",\"config2Key3Array1Value2\",\"config2Key3Array1Value3\"\]\},\"myUrls\":\[\"\/MyConfig\/myIndex\"\]\}', Y.one('#completeConfig').get('innerHTML').match(/\{\"key1\":\"This is the value from the default.yaml for key1\",\"key2\":\"This is the value from the default.yaml for key2\",\"commonKey1\":\"Value of commonKey1 in application.yaml\",\"defaultArray\":\[\"defaultArrayValue1\",\"defaultArrayValue2\"\],\"nestedConfig\":\{\"subConfig1\":\"SubConfig from defaults.yaml\",\"subConfig2\":\{\"subsubConfig1\":\"SubSubConfig1 from defaults.yaml\",\"subsubConfig2\":\"SubSubConfig2 from defaults.yaml\"\}\},\"config1\":\"This is the config for config1 in application.yaml\",\"configArray1\":\[\"configArray1Value1\",\"configArray1Value2\",\"configArray1Value3\"\],\"config2\":\{\"config2Key1\":\"config2Key1 value from application.yaml\",\"config2Key2\":\{\"config2Key2Key1\":\"It gets complicated here- config2Key2Key1 value in application.yaml\",\"config2Key2Key2\":\"config2Key2Key2 value in application.yaml\"\},\"config2Key3Array1\":\[\"config2Key3Array1Value1\",\"config2Key3Array1Value2\",\"config2Key3Array1Value3\"\]\},\"myUrls\":\[\"\/MyConfig\/myIndex\"\]\}/gi)); + Y.Assert.areEqual( + 'ac.config.get\(\) -', + Y.one('#completeConfig').get('innerHTML').match(/ac.config.get\(\) -/gi) + ); + console.log(Y.one('#completeConfig').get('innerHTML')); + Y.Assert.areEqual( + '\"key1\":\"This is the value from the default.yaml for key1\"', + Y.one('#completeConfig').get('innerHTML').match(/\"key1\":\"This is the value from the default.yaml for key1\"/gi) + ); + Y.Assert.areEqual('\"key1\":\"This is the value from the default.yaml for key1\",\"key2\":\"This is the value from the default.yaml for key2\",\"defaultArray\":\[\"defaultArrayValue1\",\"defaultArrayValue2\"\],\"nestedConfig\":\{\"subConfig1\":\"SubConfig from defaults.yaml\",\"subConfig2\":\{\"subsubConfig1\":\"SubSubConfig1 from defaults.yaml\",\"subsubConfig2\":\"SubSubConfig2 from defaults.yaml\"\}\},\"myUrls\":\[\"\/MyConfig\/myIndex\"\]', Y.one('#completeConfig').get('innerHTML').match(/\"key1\":\"This is the value from the default.yaml for key1\",\"key2\":\"This is the value from the default.yaml for key2\",\"defaultArray\":\[\"defaultArrayValue1\",\"defaultArrayValue2\"\],\"nestedConfig\":\{\"subConfig1\":\"SubConfig from defaults.yaml\",\"subConfig2\":\{\"subsubConfig1\":\"SubSubConfig1 from defaults.yaml\",\"subsubConfig2\":\"SubSubConfig2 from defaults.yaml\"\}\},\"myUrls\":\[\"\/MyConfig\/myIndex\"\]/gi)); + // That's the original assert; not sure why the text is different from the results ?? + // Y.Assert.areEqual('\{\"key1\":\"This is the value from the default.yaml for key1\",\"key2\":\"This is the value from the default.yaml for key2\",\"commonKey1\":\"Value of commonKey1 in application.yaml\",\"defaultArray\":\[\"defaultArrayValue1\",\"defaultArrayValue2\"\],\"nestedConfig\":\{\"subConfig1\":\"SubConfig from defaults.yaml\",\"subConfig2\":\{\"subsubConfig1\":\"SubSubConfig1 from defaults.yaml\",\"subsubConfig2\":\"SubSubConfig2 from defaults.yaml\"\}\},\"config1\":\"This is the config for config1 in application.yaml\",\"configArray1\":\[\"configArray1Value1\",\"configArray1Value2\",\"configArray1Value3\"\],\"config2\":\{\"config2Key1\":\"config2Key1 value from application.yaml\",\"config2Key2\":\{\"config2Key2Key1\":\"It gets complicated here- config2Key2Key1 value in application.yaml\",\"config2Key2Key2\":\"config2Key2Key2 value in application.yaml\"\},\"config2Key3Array1\":\[\"config2Key3Array1Value1\",\"config2Key3Array1Value2\",\"config2Key3Array1Value3\"\]\},\"myUrls\":\[\"\/MyConfig\/myIndex\"\]\}', Y.one('#completeConfig').get('innerHTML').match(/\{\"key1\":\"This is the value from the default.yaml for key1\",\"key2\":\"This is the value from the default.yaml for key2\",\"commonKey1\":\"Value of commonKey1 in application.yaml\",\"defaultArray\":\[\"defaultArrayValue1\",\"defaultArrayValue2\"\],\"nestedConfig\":\{\"subConfig1\":\"SubConfig from defaults.yaml\",\"subConfig2\":\{\"subsubConfig1\":\"SubSubConfig1 from defaults.yaml\",\"subsubConfig2\":\"SubSubConfig2 from defaults.yaml\"\}\},\"config1\":\"This is the config for config1 in application.yaml\",\"configArray1\":\[\"configArray1Value1\",\"configArray1Value2\",\"configArray1Value3\"\],\"config2\":\{\"config2Key1\":\"config2Key1 value from application.yaml\",\"config2Key2\":\{\"config2Key2Key1\":\"It gets complicated here- config2Key2Key1 value in application.yaml\",\"config2Key2Key2\":\"config2Key2Key2 value in application.yaml\"\},\"config2Key3Array1\":\[\"config2Key3Array1Value1\",\"config2Key3Array1Value2\",\"config2Key3Array1Value3\"\]\},\"myUrls\":\[\"\/MyConfig\/myIndex\"\]\}/gi)); }, 2000); } diff --git a/tests/func/common/testconfiginappfilecompserver.js b/tests/func/common/testconfiginappfilecompserver.js index dab670211..ac3da6cd9 100644 --- a/tests/func/common/testconfiginappfilecompserver.js +++ b/tests/func/common/testconfiginappfilecompserver.js @@ -12,10 +12,25 @@ YUI({ suite.add(new Y.Test.Case({ "test configinappfilecompserver": function() { - Y.Assert.areEqual('ac.config.get\(\) -', Y.one('#completeConfig').get('innerHTML').match(/ac.config.get\(\) -/gi)); - Y.Assert.areEqual('\{\"key1\":\"This is the value from the default.yaml for key1\",\"key2\":\"This is the value from the default.yaml for key2\",\"commonKey1\":\"Value of commonKey1 in application.yaml\",\"defaultArray\":\[\"defaultArrayValue1\",\"defaultArrayValue2\"\],\"nestedConfig\":\{\"subConfig1\":\"SubConfig from defaults.yaml\",\"subConfig2\":\{\"subsubConfig1\":\"SubSubConfig1 from defaults.yaml\",\"subsubConfig2\":\"SubSubConfig2 from defaults.yaml\"\}\},\"config1\":\"This is the config for config1 in application.yaml\",\"configArray1\":\[\"configArray1Value1\",\"configArray1Value2\",\"configArray1Value3\"\],\"config2\":\{\"config2Key1\":\"config2Key1 value from application.yaml\",\"config2Key2\":\{\"config2Key2Key1\":\"It gets complicated here- config2Key2Key1 value in application.yaml\",\"config2Key2Key2\":\"config2Key2Key2 value in application.yaml\"\},\"config2Key3Array1\":\[\"config2Key3Array1Value1\",\"config2Key3Array1Value2\",\"config2Key3Array1Value3\"\]\}', Y.one('#completeConfig').get('innerHTML').match(/\{\"key1\":\"This is the value from the default.yaml for key1\",\"key2\":\"This is the value from the default.yaml for key2\",\"commonKey1\":\"Value of commonKey1 in application.yaml\",\"defaultArray\":\[\"defaultArrayValue1\",\"defaultArrayValue2\"\],\"nestedConfig\":\{\"subConfig1\":\"SubConfig from defaults.yaml\",\"subConfig2\":\{\"subsubConfig1\":\"SubSubConfig1 from defaults.yaml\",\"subsubConfig2\":\"SubSubConfig2 from defaults.yaml\"\}\},\"config1\":\"This is the config for config1 in application.yaml\",\"configArray1\":\[\"configArray1Value1\",\"configArray1Value2\",\"configArray1Value3\"\],\"config2\":\{\"config2Key1\":\"config2Key1 value from application.yaml\",\"config2Key2\":\{\"config2Key2Key1\":\"It gets complicated here- config2Key2Key1 value in application.yaml\",\"config2Key2Key2\":\"config2Key2Key2 value in application.yaml\"\},\"config2Key3Array1\":\[\"config2Key3Array1Value1\",\"config2Key3Array1Value2\",\"config2Key3Array1Value3\"\]\}/gi)); + Y.Assert.areEqual('ac.config.get\(\) -', Y.one('#completeConfig').get('innerHTML').match(/ac.config.get\(\) -/gi)); + var matches = Y.one('#completeConfig').get('innerHTML').match(/({.+})/); + var json; + try { + json = JSON.parse(matches[1]); + } catch(e) { + } + Y.Assert.isObject(json); + Y.Assert.areEqual(8, Object.keys(json).length); + Y.Assert.areSame('Value of commonKey1 in application.yaml', json.commonKey1); + Y.Assert.areSame('This is the config for config1 in application.yaml', json.config1); + Y.Assert.areSame('This is the value from the default.yaml for key1', json.key1); + Y.Assert.areSame('This is the value from the default.yaml for key2', json.key2); + Y.Assert.areSame('["configArray1Value1","configArray1Value2","configArray1Value3"]', JSON.stringify(json.configArray1)) + Y.Assert.areSame('["defaultArrayValue1","defaultArrayValue2"]', JSON.stringify(json.defaultArray)); + Y.Assert.areSame('{"config2Key1":"config2Key1 value from application.yaml","config2Key2":{"config2Key2Key1":"It gets complicated here- config2Key2Key1 value in application.yaml","config2Key2Key2":"config2Key2Key2 value in application.yaml"},"config2Key3Array1":["config2Key3Array1Value1","config2Key3Array1Value2","config2Key3Array1Value3"]}', JSON.stringify(json.config2)); + Y.Assert.areSame('{"subConfig1":"SubConfig from defaults.yaml","subConfig2":{"subsubConfig1":"SubSubConfig1 from defaults.yaml","subsubConfig2":"SubSubConfig2 from defaults.yaml"}}', JSON.stringify(json.nestedConfig)); } })); Y.Test.Runner.add(suite); -}); \ No newline at end of file +}); diff --git a/tests/func/common/testdependencyclient.js b/tests/func/common/testdependencyclient.js index c2d1b438d..3626c111d 100644 --- a/tests/func/common/testdependencyclient.js +++ b/tests/func/common/testdependencyclient.js @@ -1,11 +1,7 @@ /* * This is a basic func test for a Common application. */ -YUI({ - useConsoleOutput: true, - useBrowserConsole: true, - logInclude: { TestRunner: true } -}).use('node', 'node-event-simulate', 'test', 'console', function (Y) { +YUI.add('testdependencyclient-tests', function (Y) { var suite = new Y.Test.Suite("Common: dependencyclient"); @@ -23,4 +19,4 @@ YUI({ Y.Test.Runner.add(suite); -}); \ No newline at end of file +}, '0.0.1', { requires: ['node', 'node-event-simulate', 'test']}); \ No newline at end of file diff --git a/tests/func/common/testmojitproxybroadcaststaticlisten2.js b/tests/func/common/testmojitproxybroadcaststaticlisten2.js index 85c2d32bf..68c0504fb 100644 --- a/tests/func/common/testmojitproxybroadcaststaticlisten2.js +++ b/tests/func/common/testmojitproxybroadcaststaticlisten2.js @@ -40,6 +40,4 @@ YUI({ })); Y.Test.Runner.add(suite); - } - -}); \ No newline at end of file +}); diff --git a/tests/func/examples/developerguide/test_yuimodule.js b/tests/func/examples/developerguide/test_yuimodule.js index 5615fd292..c6b2eae3e 100644 --- a/tests/func/examples/developerguide/test_yuimodule.js +++ b/tests/func/examples/developerguide/test_yuimodule.js @@ -14,7 +14,8 @@ YUI({ "test yuimodule": function() { Y.Assert.areEqual("Storage Lite: Simple Notepad Example", Y.one('h1').get('innerHTML')); Y.Assert.areEqual("Storage Lite", Y.one('a').get('innerHTML')); - Y.Assert.areEqual("\"/static/yui_module/autoload/storage-lite.client.js\"", Y.one('body').get('innerHTML').match(/"\/static\/yui_module\/autoload\/storage-lite.client.js"/gi).[0]); + Y.Assert.areEqual("\"/static/yui_module/autoload/storage-lite.client.js\"", + Y.one('body').get('innerHTML').match(/"\/static\/yui_module\/autoload\/storage-lite.client.js"/gi)[0]); } })); diff --git a/tests/harness/lib/yuitest/LICENSE b/tests/harness/lib/yuitest/LICENSE new file mode 100644 index 000000000..15aeb6de7 --- /dev/null +++ b/tests/harness/lib/yuitest/LICENSE @@ -0,0 +1,28 @@ +YUI Test +Author: Nicholas C. Zakas <nzakas@yahoo-inc.com> +Copyright (c) 2009, Yahoo! Inc. All rights reserved. +Yahoo! source code licensed under the BSD License: + http://developer.yahoo.net/yui/license.txt + +Some portions Copyright (c) 2002 JSON.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +The Software shall be used for Good, not Evil. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/tests/harness/lib/yuitest/README b/tests/harness/lib/yuitest/README new file mode 100644 index 000000000..1f224e8a1 --- /dev/null +++ b/tests/harness/lib/yuitest/README @@ -0,0 +1 @@ +YUITest diff --git a/tests/harness/lib/yuitest/java/README b/tests/harness/lib/yuitest/java/README new file mode 100644 index 000000000..437ad5312 --- /dev/null +++ b/tests/harness/lib/yuitest/java/README @@ -0,0 +1,33 @@ +YUI Test Java Tools +Author: Nicholas C. Zakas <nzakas@yahoo-inc.com> + www.nczonline.net +Copyright (c) 2009, Yahoo! Inc. All rights reserved. + +Code licensed under the BSD License: + http://developer.yahoo.net/yui/license.txt + + +This software contains an ANTLR grammar based off of work from a couple of sources: + +The original ES3 grammar is available under a BSD License. +Copyright (c) 2008-2009 Xebic Reasearch BV. Original work by Patrick Hulsmeijer. + +The modified ES3 grammar contains portions from JsTestDriver +(http://code.google.com/p/js-test-driver/) and is available under an +Apache License, Version 2.0. Copyright (c) 2009 Google Inc. + + +This software also requires access to software from the following sources: + +The Jargs Library v 1.0 ( http://jargs.sourceforge.net/ ) is available +under a BSD License. Copyright (c) 2001-2003 Steve Purcell, +Copyright (c) 2002 Vidar Holen, Copyright (c) 2002 Michal Ceresna and +Copyright (c) 2005 Ewan Mellor. + +The Antlr Library v3.2 (http://www.antlr.org) is available +under a BSD License (http://www.antlr.org/license.html). +Copyright (c) 2003-2008 Terrence Parr. + +The Selenium Java Client Driver Library v1.0.1 (http://www.seleniumhq.org) +is available an Apache License (http://seleniumhq.org/about/license.html). + diff --git a/tests/harness/lib/yuitest/java/ant.properties b/tests/harness/lib/yuitest/java/ant.properties new file mode 100644 index 000000000..95c8c0418 --- /dev/null +++ b/tests/harness/lib/yuitest/java/ant.properties @@ -0,0 +1,39 @@ +#Target JVM for the compilation +target.jvm.version = 1.5 + +#Directories +src.dir = src +lib.dir = lib +doc.dir = doc +build.dir = build +tmp.dir = tmp +tests.dir = tests + +#Version information +version.number = 0.6.4 + +#Code paths +codepath.root.dir = com/yahoo/platform/yuitest + +#Libraries +antlr.jar = antlr-3.2.jar +antlr2.jar = antlr-2.7.7.jar +antlr-runtime.jar = antlr-runtime-3.2.jar +stringtemplate.jar = stringtemplate-3.2.1.jar +jargs.jar = jargs-1.0.jar +junit.jar = junit-4.1.jar +selenium.jar = selenium-java-client-driver.jar + +#Coverage info +coverage.name = yuitest-coverage +coverage.jar.name = ${coverage.name}.jar +coverage.root.dir = ${codepath.root.dir}/coverage + +coverage-report.name = yuitest-coverage-report +coverage-report.jar.name = ${coverage-report.name}.jar +coverage-report.root.dir = ${codepath.root.dir}/coverage + +#Selenium Driver info +selenium-driver.name = yuitest-selenium-driver +selenium-driver.jar.name = ${selenium-driver.name}.jar +selenium-driver.root.dir = ${codepath.root.dir}/selenium \ No newline at end of file diff --git a/tests/harness/lib/yuitest/java/build.xml b/tests/harness/lib/yuitest/java/build.xml new file mode 100644 index 000000000..d017685d2 --- /dev/null +++ b/tests/harness/lib/yuitest/java/build.xml @@ -0,0 +1,243 @@ +<?xml version="1.0" encoding="iso-8859-1"?> +<project name="YUITest" default="build.all.jars" basedir=""> + + <!-- ================================================================== --> + <!-- Property Loading --> + <!-- ================================================================== --> + + <target name="-load.properties"> + <property file="ant.properties"/> + </target> + + <!-- ================================================================== --> + <!-- Clean and Init --> + <!-- ================================================================== --> + + <target name="clean" depends="-load.properties"> + <delete dir="${build.dir}" quiet="true"/> + <delete dir="${tmp.dir}" quiet="true"/> + </target> + + <target name="-init" depends="-load.properties"> + <mkdir dir="${build.dir}"/> + <mkdir dir="${tmp.dir}"/> + </target> + + <!-- ================================================================== --> + <!-- Build Classes --> + <!-- ================================================================== --> + + <target name="build.parsers" depends="-init"> + <antlr-gen dir="${src.dir}/${codepath.root.dir}/coverage/grammar/" grammar="ES3YUITest.g"/> + </target> + + <target name="build.classes" depends="-init"> + <mkdir dir="${tmp.dir}/classes"/> + <javac srcdir="${src.dir}" + destdir="${tmp.dir}/classes" + includes="**/*.java" + deprecation="off" + debug="on" + target="${target.jvm.version}" + source="${target.jvm.version}"> + <classpath> + <pathelement location="${lib.dir}/${jargs.jar}"/> + <pathelement location="${lib.dir}/${antlr.jar}"/> + <pathelement location="${lib.dir}/${selenium.jar}"/> + </classpath> + </javac> + + </target> + + <!-- ================================================================== --> + <!-- Build Coverage JAR --> + <!-- ================================================================== --> + + <target name="build.coverage.jar" depends="build.parsers,build.classes"> + <mkdir dir="${tmp.dir}/coverage/jar"/> + <unjar src="${lib.dir}/${jargs.jar}" dest="${tmp.dir}/coverage/jar"/> + <unjar src="${lib.dir}/${antlr-runtime.jar}" dest="${tmp.dir}/coverage/jar"/> + <unjar src="${lib.dir}/${antlr2.jar}" dest="${tmp.dir}/coverage/jar"/> + <unjar src="${lib.dir}/${stringtemplate.jar}" dest="${tmp.dir}/coverage/jar"/> + <copy todir="${tmp.dir}/coverage/jar"> + <fileset dir="${tmp.dir}/classes" includes="**/coverage/*.class"/> + <fileset dir="${tmp.dir}/classes" includes="**/json/*.class"/> + </copy> + <!-- copy string template files to JAR --> + <copy todir="${tmp.dir}/coverage/jar/${coverage.root.dir}"> + <fileset dir="${src.dir}/${coverage.root.dir}" includes="**/*.stg"/> + </copy> + <jar destfile="${build.dir}/${coverage.jar.name}" basedir="${tmp.dir}/coverage/jar"> + <manifest> + <attribute name="Main-Class" value="com.yahoo.platform.yuitest.coverage.YUITestCoverage"/> + </manifest> + </jar> + + </target> + + <!-- ================================================================== --> + <!-- Build Coverage Report JAR --> + <!-- ================================================================== --> + + <target name="build.coverage-report.jar" depends="build.classes"> + <mkdir dir="${tmp.dir}/coverage-report/jar"/> + <unjar src="${lib.dir}/${jargs.jar}" dest="${tmp.dir}/coverage-report/jar"/> + <unjar src="${lib.dir}/${antlr2.jar}" dest="${tmp.dir}/coverage-report/jar"/> + <unjar src="${lib.dir}/${stringtemplate.jar}" dest="${tmp.dir}/coverage-report/jar"/> + <copy todir="${tmp.dir}/coverage-report/jar"> + <fileset dir="${tmp.dir}/classes" includes="**/coverage/**/*.class"/> + <fileset dir="${tmp.dir}/classes" includes="**/json/*.class"/> + <fileset dir="${tmp.dir}/classes" includes="**/writers/*.class"/> + </copy> + <!-- copy string template files to JAR --> + <copy todir="${tmp.dir}/coverage-report/jar/${codepath.root.dir}"> + <fileset dir="${src.dir}/${codepath.root.dir}" includes="**/*.stg"/> + </copy> + <jar destfile="${build.dir}/${coverage-report.jar.name}" basedir="${tmp.dir}/coverage-report/jar"> + <manifest> + <attribute name="Main-Class" value="com.yahoo.platform.yuitest.coverage.report.YUITestCoverageReport"/> + </manifest> + </jar> + + </target> + + <!-- ================================================================== --> + <!-- Build Selenium Driver JAR --> + <!-- ================================================================== --> + + <target name="build.selenium-driver.jar" depends="build.classes"> + <mkdir dir="${tmp.dir}/selenium-driver/jar"/> + <unjar src="${lib.dir}/${jargs.jar}" dest="${tmp.dir}/selenium-driver/jar"/> + <unjar src="${lib.dir}/${antlr2.jar}" dest="${tmp.dir}/selenium-driver/jar"/> + <unjar src="${lib.dir}/${stringtemplate.jar}" dest="${tmp.dir}/selenium-driver/jar"/> + <copy todir="${tmp.dir}/selenium-driver/jar"> + <fileset dir="${tmp.dir}/classes" includes="**/*.class"/> +<!-- + <fileset dir="${tmp.dir}/classes" includes="**/config/*.class"/> + <fileset dir="${tmp.dir}/classes" includes="**/selenium/*.class"/> + <fileset dir="${tmp.dir}/classes" includes="**/results/*.class"/> + <fileset dir="${tmp.dir}/classes" includes="**/writers/*.class"/> +--> + </copy> + + <copy todir="${tmp.dir}/selenium-driver/jar/${selenium-driver.root.dir}"> + <fileset dir="${src.dir}/${selenium-driver.root.dir}" includes="**/*.properties"/> + </copy> + + <copy todir="${tmp.dir}/selenium-driver/jar/${codepath.root.dir}"> + <fileset dir="${src.dir}/${codepath.root.dir}" includes="**/*.stg"/> + </copy> + + <jar destfile="${build.dir}/${selenium-driver.jar.name}" basedir="${tmp.dir}/selenium-driver/jar"> + <manifest> + <attribute name="Main-Class" value="com.yahoo.platform.yuitest.selenium.YUITestSeleniumDriver"/> + </manifest> + </jar> + + </target> + + + <!-- ================================================================== --> + <!-- Build All JARs --> + <!-- ================================================================== --> + + <target name="build.all.jars" depends="build.coverage.jar,build.coverage-report.jar,build.selenium-driver.jar"> + </target> + + <!-- ================================================================== --> + <!-- Testing --> + <!-- ================================================================== --> + + <target name="build.tests" depends="-init"> + <mkdir dir="${tmp.dir}/testclasses"/> + <javac srcdir="${tests.dir}" + destdir="${tmp.dir}/testclasses" + includes="**/*.java" + deprecation="off" + debug="on" + source="${target.jvm.version}" + target="${target.jvm.version}"> + <classpath> + <pathelement location="${lib.dir}/${jargs.jar}"/> + <pathelement location="${lib.dir}/${antlr.jar}"/> + <pathelement location="${lib.dir}/${selenium.jar}"/> + <pathelement location="${lib.dir}/${junit.jar}"/> + <pathelement location="${tmp.dir}/classes"/> + </classpath> + </javac> + + <!-- copy over XML/JSON files for testing--> + <copy todir="${tmp.dir}/testclasses/${codepath.root.dir}"> + <fileset dir="${tests.dir}/${codepath.root.dir}" includes="**/*.xml"/> + <fileset dir="${tests.dir}/${codepath.root.dir}" includes="**/*.json"/> + </copy> + </target> + + <target name="test.classes" depends="build.classes,build.tests"> + <junit printsummary="yes" fork="yes" errorproperty="junit.failure" failureproperty="junit.failure" showoutput="true"> + + <jvmarg value="-Duser.dir=${res.dir}"/> + + <!-- classpath must include all jar dependencies and classes --> + <classpath> + <pathelement location="${tmp.dir}/classes"/> + <pathelement location="${tmp.dir}/testclasses"/> + <pathelement location="${lib.dir}/${jargs.jar}"/> + <pathelement location="${lib.dir}/${selenium.jar}"/> + <pathelement location="${lib.dir}/${junit.jar}"/> + </classpath> + + <!-- formatter to use for output --> + <formatter type="plain" usefile="false"/> + + <!-- fully qualified classname of testsuite --> + <batchtest> + <fileset dir="${tmp.dir}/testclasses"> + <include name="**/*Test.class" /> + </fileset> + </batchtest> + </junit> + </target> + + <!-- TODO + <target name="build.dist.package" depends="build.jar"> + <mkdir dir="${build.dir}/${dist.package.name}"/> + <mkdir dir="${build.dir}/${dist.package.name}/build"/> + <copy file="${build.dir}/${jar.name}" todir="${build.dir}/${dist.package.name}/build"/> + <copy todir="${build.dir}/${dist.package.name}"> + <fileset dir="."> + <include name="*.properties"/> + <include name="build.xml"/> + <include name="doc/**/*"/> + <include name="lib/**/*"/> + <include name="src/**/*"/> + <exclude name="**/.git"/> + </fileset> + </copy> + <zip destfile="${build.dir}/${dist.package.name}.zip" + basedir="${build.dir}" + includes="${dist.package.name}/**/*"/> + </target> + --> + + <!-- ================================================================== --> + <!-- Macros --> + <!-- ================================================================== --> + + <!-- macro to create antlr parser --> + <macrodef name="antlr-gen"> + <attribute name="dir"/> + <attribute name="grammar"/> + <sequential> + <echo>Creating parser from @{grammar}</echo> + <apply executable="java" parallel="true" failonerror="true"> + <arg line="-jar"/> + <arg path="${lib.dir}/${antlr.jar}"/> + <srcfile/> + <arg line="-o ${src.dir}/${coverage.root.dir}"/> + <fileset dir="@{dir}" includes="@{grammar}" /> + </apply> + </sequential> + </macrodef> + +</project> \ No newline at end of file diff --git a/tests/harness/lib/yuitest/java/build/yuitest-coverage-report.jar b/tests/harness/lib/yuitest/java/build/yuitest-coverage-report.jar new file mode 100644 index 000000000..7046bc0b6 Binary files /dev/null and b/tests/harness/lib/yuitest/java/build/yuitest-coverage-report.jar differ diff --git a/tests/harness/lib/yuitest/java/build/yuitest-coverage.jar b/tests/harness/lib/yuitest/java/build/yuitest-coverage.jar new file mode 100644 index 000000000..7e0526e82 Binary files /dev/null and b/tests/harness/lib/yuitest/java/build/yuitest-coverage.jar differ diff --git a/tests/harness/lib/yuitest/java/build/yuitest-selenium-driver.jar b/tests/harness/lib/yuitest/java/build/yuitest-selenium-driver.jar new file mode 100644 index 000000000..3c72ffdcd Binary files /dev/null and b/tests/harness/lib/yuitest/java/build/yuitest-selenium-driver.jar differ diff --git a/tests/harness/lib/yuitest/java/lib/antlr-2.7.7.jar b/tests/harness/lib/yuitest/java/lib/antlr-2.7.7.jar new file mode 100644 index 000000000..5e5f14b35 Binary files /dev/null and b/tests/harness/lib/yuitest/java/lib/antlr-2.7.7.jar differ diff --git a/tests/harness/lib/yuitest/java/lib/antlr-3.2.jar b/tests/harness/lib/yuitest/java/lib/antlr-3.2.jar new file mode 100644 index 000000000..fdd167d44 Binary files /dev/null and b/tests/harness/lib/yuitest/java/lib/antlr-3.2.jar differ diff --git a/tests/harness/lib/yuitest/java/lib/antlr-runtime-3.2.jar b/tests/harness/lib/yuitest/java/lib/antlr-runtime-3.2.jar new file mode 100644 index 000000000..52a052860 Binary files /dev/null and b/tests/harness/lib/yuitest/java/lib/antlr-runtime-3.2.jar differ diff --git a/tests/harness/lib/yuitest/java/lib/jargs-1.0.jar b/tests/harness/lib/yuitest/java/lib/jargs-1.0.jar new file mode 100644 index 000000000..cdbc80bb3 Binary files /dev/null and b/tests/harness/lib/yuitest/java/lib/jargs-1.0.jar differ diff --git a/tests/harness/lib/yuitest/java/lib/junit-4.1.jar b/tests/harness/lib/yuitest/java/lib/junit-4.1.jar new file mode 100644 index 000000000..30e5ec3c1 Binary files /dev/null and b/tests/harness/lib/yuitest/java/lib/junit-4.1.jar differ diff --git a/tests/harness/lib/yuitest/java/lib/selenium-java-client-driver.jar b/tests/harness/lib/yuitest/java/lib/selenium-java-client-driver.jar new file mode 100644 index 000000000..4a1269853 Binary files /dev/null and b/tests/harness/lib/yuitest/java/lib/selenium-java-client-driver.jar differ diff --git a/tests/harness/lib/yuitest/java/lib/stringtemplate-3.2.1.jar b/tests/harness/lib/yuitest/java/lib/stringtemplate-3.2.1.jar new file mode 100644 index 000000000..8a309ccb5 Binary files /dev/null and b/tests/harness/lib/yuitest/java/lib/stringtemplate-3.2.1.jar differ diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/config/TestConfig.java b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/config/TestConfig.java new file mode 100644 index 000000000..3a3116a8b --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/config/TestConfig.java @@ -0,0 +1,114 @@ +/* + * YUI Test + * Author: Nicholas C. Zakas <nzakas@yahoo-inc.com> + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ + +package com.yahoo.platform.yuitest.config; + +import com.yahoo.platform.yuitest.selenium.*; +import java.io.IOException; +import java.io.InputStream; +import java.util.LinkedList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +/** + * Represents all tests that need to be executed. + * @author Nicholas C. Zakas + */ +public class TestConfig { + + private TestPageGroup[] groups; + + /** + * Creates a new instance. + */ + public TestConfig(){ + } + + /** + * Loads test configuration information from the specific input stream. + * @param in The stream to read the data from. + * @throws SAXException When there's an XML parse error. + * @throws IOException When the input stream can't be read. + */ + public void load(InputStream in) throws SAXException, IOException { + SAXParserFactory spf = SAXParserFactory.newInstance(); + SAXParser parser = null; + final List<TestPageGroup> groupsList = new LinkedList<TestPageGroup>(); + + try { + parser = spf.newSAXParser(); + parser.parse(in, new DefaultHandler(){ + + private int version = 4; + private TestPageGroup currentGroup = null; + private TestPage currentPage = null; + + @Override + public void characters(char[] ch, int start, int length) throws SAXException { + if (currentPage != null){ + currentPage.setPath(currentPage.getPath() + new String(ch, start, length)); + } + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + if (qName.equals("yuitest")){ + String ver = attributes.getValue("version"); + if (ver != null){ + version = Integer.parseInt(ver); + } + } else if (qName.equals("tests")){ + String ver = attributes.getValue("version"); + String timeout = attributes.getValue("timeout"); + currentGroup = new TestPageGroup(attributes.getValue("base"), (ver == null ? version : Integer.parseInt(ver)), (timeout == null ? -1 : Integer.parseInt(timeout))); + groupsList.add(currentGroup); + } else if (qName.equals("url")){ + + //make sure it's inside of a group + if (currentGroup == null){ + throw new SAXException("<url> must be within <tests>"); + } + String ver = attributes.getValue("version"); + String timeout = attributes.getValue("timeout"); + currentPage = new TestPage("", (ver == null ? currentGroup.getVersion() : Integer.parseInt(ver)), (timeout == null ? currentGroup.getTimeout() : Integer.parseInt(timeout))); + currentGroup.add(currentPage); + } + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + if (qName.equals("tests")){ + currentGroup = null; + } else if (qName.equals("url")){ + currentPage = null; + } + } + + }); + } catch (ParserConfigurationException ex) { + Logger.getLogger(TestConfig.class.getName()).log(Level.SEVERE, null, ex); + } + + groups = groupsList.toArray(new TestPageGroup[groupsList.size()]); + } + + /** + * Returns all groups. + * @return All groups. + */ + public TestPageGroup[] getGroups(){ + return groups; + } +} diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/config/TestPage.java b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/config/TestPage.java new file mode 100644 index 000000000..55c4e6a41 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/config/TestPage.java @@ -0,0 +1,114 @@ +/* + * YUI Test Selenium Driver + * Author: Nicholas C. Zakas <nzakas@yahoo-inc.com> + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ + +package com.yahoo.platform.yuitest.config; + +/** + * Represents a single test page and its settings. + * @author Nicholas C. Zakas + */ +public class TestPage { + + private TestPageGroup owner = null; + private String path = null; + private int timeout = 10000; + private int version = 4; + + //-------------------------------------------------------------------------- + // Constructors + //-------------------------------------------------------------------------- + + /** + * Creates a new instance based on a path. Timeout and version are defaulted + * to 10000 and 2, respectively. + * @param path The path of the test URL. + */ + public TestPage(String path){ + this.path = path; + } + + /** + * Creates a new instance. Timeout is defaulted to 10000. + * @param path The path of the test URL. + * @param version The YUI Test version of the test. + */ + public TestPage(String path, int version){ + this.path = path; + this.version = version; + } + + /** + * Creates a new instance. + * @param path The path of the test URL. + * @param version The YUI Test version of the test. + * @param timeout The timeout for the page. + */ + public TestPage(String path, int version, int timeout){ + this.path = path; + this.version = version; + this.timeout = timeout; + } + + //-------------------------------------------------------------------------- + // Getters and Setters + //-------------------------------------------------------------------------- + + /** + * Returns the test path as specified originally. + * @return The test path as specified originally. + */ + public String getPath() { + return path; + } + + /** + * Sets the path for the TestPage. + * @param path The path for the test. + */ + public void setPath(String path){ + this.path = path; + } + + /** + * Sets a TestPageGroup as the owner for this TestPage. + * @param owner The TestPageGroup that the TestPage should be a part of. + */ + protected void setOwner(TestPageGroup owner){ + this.owner = owner; + } + + /** + * Returns the timeout for the test. + * @return The timeout for the test. + */ + public int getTimeout() { + return timeout; + } + + /** + * Returns the YUI Test version for the test. + * @return The YUI Test version for the test. + */ + public int getVersion(){ + return version; + } + + /** + * Returns the absolute path for the test, prepending the base of its owner + * TestPageGroup if necessary. + * @return The absolute path for the test. + */ + public String getAbsolutePath(){ + if (owner != null){ + return owner.getBase() + path; + } else { + return path; + } + + } +} diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/config/TestPageGroup.java b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/config/TestPageGroup.java new file mode 100644 index 000000000..02f2d3656 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/config/TestPageGroup.java @@ -0,0 +1,119 @@ +/* + * YUI Test Selenium Driver + * Author: Nicholas C. Zakas <nzakas@yahoo-inc.com> + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ + +package com.yahoo.platform.yuitest.config; + +import java.util.LinkedList; +import java.util.List; + +/** + * Represents a group of test URLs with common properties. + * @author Nicholas C. Zakas + */ +public class TestPageGroup { + + private String base = ""; + private int version = 4; + private List<TestPage> testPages = null; + private int timeout = 10000; + + //-------------------------------------------------------------------------- + // Constructors + //-------------------------------------------------------------------------- + + /** + * Creates a new instance using default values for base (empty string), + * version (2), and timeout (10000). + */ + public TestPageGroup(){ + this.testPages = new LinkedList<TestPage>(); + } + + /** + * Creates a new instance for the given base. The values for version and + * timeout are defaulted to 2 and 10000, respectively. + * @param base The base of the URLs in this group. + */ + public TestPageGroup(String base){ + this(); + this.base = base; + } + + /** + * Creates a new instance for the given base and YUI Test version. The + * timeout value defaults to 10000. + * @param base The base of the URLs in this group. + * @param version The YUI Test version used for URLs in this group. + */ + public TestPageGroup(String base, int version){ + this(); + this.base = base; + this.version = version; + } + + /** + * Creates a new instance for the given base, YUI Test version, and timeout. + * @param base The base of the URLs in this group. + * @param version The YUI Test version used for URLs in this group. + * @param timeout The default timeout, in milliseconds, for the tests. + */ + public TestPageGroup(String base, int version, int timeout){ + this(); + this.base = base; + this.version = version; + this.timeout = timeout; + } + + //-------------------------------------------------------------------------- + // Methods + //-------------------------------------------------------------------------- + + /** + * Adds a TestPage to the group. + * @param testPage The TestPage to add. + */ + public void add(TestPage testPage){ + testPages.add(testPage); + testPage.setOwner(this); + } + + /** + * Returns an array of all TestPages in the group. + * @return An array of all TestPages in the group. + */ + public TestPage[] getTestPages(){ + TestPage[] result = new TestPage[testPages.size()]; + testPages.toArray(result); + return result; + } + + /** + * Returns the base URL for all tests in the group. + * @return The base URL for all tests in the group. + */ + public String getBase(){ + return base; + } + + /** + * Returns the YUI Test version for tests in this group. + * @return The YUI Test version for tests in this group. + */ + public int getVersion(){ + return version; + } + + /** + * Returns the default timeout, in milliseconds, for tests in this group. + * @return The default timeout, in milliseconds, for tests in this group. + */ + public int getTimeout(){ + return timeout; + } + +} diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/DirectoryInstrumenter.java b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/DirectoryInstrumenter.java new file mode 100644 index 000000000..78387fdef --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/DirectoryInstrumenter.java @@ -0,0 +1,108 @@ +/* + * YUI Test Coverage + * Author: Nicholas C. Zakas <nzakas@yahoo-inc.com> + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ +package com.yahoo.platform.yuitest.coverage; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.LinkedList; +import java.util.List; +import org.antlr.runtime.RecognitionException; + + +/** + * Encapsulates instrumenting all files in a inputDir. + * @author Nicholas C. Zakas + */ +public class DirectoryInstrumenter { + + private static boolean verbose = false; + + public static boolean isVerbose() { + return verbose; + } + + public static void setVerbose(boolean verbose) { + DirectoryInstrumenter.verbose = verbose; + } + + public static void instrument(String inputDir, String outputDir) + throws FileNotFoundException, UnsupportedEncodingException, + IOException, RecognitionException { + + //normalize + if (!inputDir.endsWith(File.separator)){ + inputDir = inputDir + File.separator; + } + if (!outputDir.endsWith(File.separator)){ + outputDir = outputDir + File.separator; + } + + List<String> filenames = getFilenames(inputDir); + + for (int i=0; i < filenames.size(); i++){ + String inputFilename = filenames.get(i); + String outputFilename = outputDir + inputFilename.substring(inputFilename.indexOf(inputDir) + inputDir.length()); + + //create the directories if necessary + File dir = new File(outputFilename.substring(0, outputFilename.lastIndexOf(File.separator))); + if (!dir.exists()){ + + if (verbose){ + System.err.println("[INFO] Creating directory " + dir.getPath()); + } + + dir.mkdirs(); + } + + FileInstrumenter.setVerbose(verbose); + FileInstrumenter.instrument(inputFilename, outputFilename); + } + + } + + + + /** + * Retrieves a recursive list of all JavaScript files in the inputDir. + * @param inputDir The inputDir to search. + * @return List of all JavaScript files in the inputDir and subdirectories. + * @throws IllegalArgumentException When the inputDir cannot be read. + * @throws FileNotFoundException When the inputDir doesn't exist. + */ + private static List<String> getFilenames(String directory) throws IllegalArgumentException, FileNotFoundException { + + File dir = new File(directory); + + //validate the inputDir first + if (!dir.isDirectory()){ + throw new FileNotFoundException("'" + directory + "' is not a valid directory."); + } + if (!dir.canRead()){ + throw new IllegalArgumentException("'" + directory + "' cannot be read."); + } + + List<String> filenames = new LinkedList<String>(); + + //TODO: Gotta be a better way... + File[] files = dir.listFiles(); + for (int i=0; i < files.length; i++){ + if (files[i].isFile() && files[i].getName().endsWith(".js")){ + filenames.add(files[i].getPath()); + } else if (files[i].isDirectory()){ + filenames.addAll(getFilenames(files[i].getPath())); + } + } + + return filenames; + + } + + +} diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/ES3YUITest.tokens b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/ES3YUITest.tokens new file mode 100644 index 000000000..8e46f2754 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/ES3YUITest.tokens @@ -0,0 +1,274 @@ +VT=134 +LOR=95 +FUNCTION=17 +PACKAGE=52 +SHR=87 +RegularExpressionChar=170 +LT=72 +WHILE=30 +MOD=83 +SHL=86 +CONST=37 +BackslashSequence=168 +LS=142 +CASE=8 +CHAR=35 +NEW=21 +DQUOTE=131 +DO=13 +NOT=92 +DecimalDigit=152 +BYFIELD=114 +CEXPR=117 +BREAK=7 +Identifier=148 +DIVASS=110 +BYINDEX=115 +FORSTEP=120 +FINAL=43 +RPAREN=66 +INC=84 +IMPORT=47 +EOL=145 +POS=129 +OctalDigit=156 +THIS=24 +RETURN=22 +ExponentPart=157 +ARGS=111 +DOUBLE=39 +WhiteSpace=139 +VAR=28 +EXPORT=41 +VOID=29 +LABELLED=122 +SUPER=58 +GOTO=45 +EQ=76 +XORASS=108 +ADDASS=99 +ARRAY=112 +SHU=88 +RBRACK=68 +RBRACE=64 +PRIVATE=53 +STATIC=57 +INV=93 +SWITCH=23 +NULL=4 +ELSE=14 +NATIVE=51 +THROWS=60 +INT=48 +DELETE=12 +MUL=82 +IdentifierStartASCII=151 +TRY=26 +FF=135 +SHLASS=103 +OctalEscapeSequence=164 +USP=138 +RegularExpressionFirstChar=169 +ANDASS=106 +TYPEOF=27 +IdentifierNameASCIIStart=154 +QUE=96 +OR=90 +DEBUGGER=38 +GT=73 +PDEC=127 +CALL=116 +CharacterEscapeSequence=162 +CATCH=9 +FALSE=6 +EscapeSequence=167 +LAND=94 +MULASS=101 +THROW=25 +PINC=128 +OctalIntegerLiteral=160 +PROTECTED=54 +DEC=85 +CLASS=36 +LBRACK=67 +HexEscapeSequence=165 +ORASS=107 +SingleLineComment=147 +NAMEDVALUE=123 +LBRACE=63 +GTE=75 +FOR=16 +RegularExpressionLiteral=155 +SUB=81 +FLOAT=44 +ABSTRACT=32 +AND=89 +DecimalIntegerLiteral=158 +HexDigit=150 +LTE=74 +LPAREN=65 +IF=18 +SUBASS=100 +EXPR=118 +BOOLEAN=33 +SYNCHRONIZED=59 +IN=19 +IMPLEMENTS=46 +OBJECT=125 +CONTINUE=10 +COMMA=71 +FORITER=119 +TRANSIENT=61 +SHRASS=104 +MODASS=102 +PS=143 +DOT=69 +IdentifierPart=153 +MultiLineComment=146 +WITH=31 +ADD=80 +BYTE=34 +XOR=91 +ZeroToThree=163 +ITEM=121 +VOLATILE=62 +UnicodeEscapeSequence=166 +SHUASS=105 +DEFAULT=11 +NSAME=79 +TAB=133 +SHORT=56 +INSTANCEOF=20 +SQUOTE=132 +DecimalLiteral=159 +TRUE=5 +SAME=78 +StringLiteral=149 +COLON=97 +PAREXPR=126 +NEQ=77 +ENUM=40 +FINALLY=15 +HexIntegerLiteral=161 +NBSP=137 +SP=136 +BLOCK=113 +LineTerminator=144 +NEG=124 +ASSIGN=98 +INTERFACE=49 +DIV=109 +SEMIC=70 +CR=141 +LONG=50 +EXTENDS=42 +PUBLIC=55 +BSLASH=130 +LF=140 +'>='=75 +'=='=76 +'implements'=46 +'with'=31 +'this'=24 +'volatile'=62 +';'=70 +'return'=22 +'==='=78 +'for'=16 +'protected'=54 +'debugger'=38 +'^'=91 +'>>'=87 +'static'=57 +'catch'=9 +'extends'=42 +'{'=63 +'package'=52 +'try'=26 +'var'=28 +'&='=106 +'('=65 +':'=97 +'synchronized'=59 +'default'=11 +'public'=55 +'<<='=103 +']'=68 +'>>>'=88 +'enum'=40 +'transient'=61 +'finally'=15 +'new'=21 +'|='=107 +'throws'=60 +'const'=37 +'export'=41 +'='=98 +'%'=83 +'super'=58 +'case'=8 +'boolean'=33 +'<<'=86 +'<='=74 +'!='=77 +'!=='=79 +'continue'=10 +'<'=72 +'--'=85 +'['=67 +'&'=89 +'instanceof'=20 +'~'=93 +'/'=109 +'/='=110 +'switch'=23 +'%='=102 +'>'=73 +'||'=95 +'&&'=94 +'+'=80 +'function'=17 +'.'=69 +'byte'=34 +'delete'=12 +'import'=47 +'++'=84 +'true'=5 +'else'=14 +'final'=43 +'^='=108 +'native'=51 +'+='=99 +'break'=7 +'>>='=104 +'>>>='=105 +'void'=29 +'?'=96 +'private'=53 +'int'=48 +'if'=18 +'while'=30 +'-'=81 +','=71 +'in'=19 +'-='=100 +'short'=56 +'long'=50 +'!'=92 +'|'=90 +'class'=36 +'null'=4 +'typeof'=27 +'goto'=45 +'throw'=25 +')'=66 +'*='=101 +'do'=13 +'char'=35 +'float'=44 +'}'=64 +'abstract'=32 +'double'=39 +'false'=6 +'*'=82 +'interface'=49 diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/ES3YUITestTemplates.stg b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/ES3YUITestTemplates.stg new file mode 100644 index 000000000..b8aff1f81 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/ES3YUITestTemplates.stg @@ -0,0 +1,53 @@ +group ES3YUITestTemplates; + +cover_line(src,code,line) ::= << +_yuitest_coverline("<src>", <line>); <code> +>> + +cover_func(src,code,name,line) ::= << +_yuitest_coverfunc("<src>", "<name>", <line>); +<code> +>> + +ignore(code) ::= << +<code> +>> + +file_header(src,path) ::= << +if (typeof _yuitest_coverage == "undefined"){ + _yuitest_coverage = {}; + _yuitest_coverline = function(src, line){ + var coverage = _yuitest_coverage[src]; + if (!coverage.lines[line]){ + coverage.calledLines++; + } + coverage.lines[line]++; + }; + _yuitest_coverfunc = function(src, name, line){ + var coverage = _yuitest_coverage[src], + funcId = name + ":" + line; + if (!coverage.functions[funcId]){ + coverage.calledFunctions++; + } + coverage.functions[funcId]++; + }; +} +_yuitest_coverage["<src>"] = { + lines: {}, + functions: {}, + coveredLines: 0, + calledLines: 0, + coveredFunctions: 0, + calledFunctions: 0, + path: "<path>", + code: [] +}; +>> + +cover_file(src,code,lines,funcs,lineCount,funcCount) ::= << +_yuitest_coverage["<src>"].lines = <lines>; +_yuitest_coverage["<src>"].functions = <funcs>; +_yuitest_coverage["<src>"].coveredLines = <lineCount>; +_yuitest_coverage["<src>"].coveredFunctions = <funcCount>; +<code> +>> \ No newline at end of file diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/FileInstrumenter.java b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/FileInstrumenter.java new file mode 100644 index 000000000..a820b101e --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/FileInstrumenter.java @@ -0,0 +1,114 @@ +/* + * YUI Test Coverage + * Author: Nicholas C. Zakas <nzakas@yahoo-inc.com> + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ + +package com.yahoo.platform.yuitest.coverage; + +import java.io.File; +import org.antlr.runtime.RecognitionException; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.UnsupportedEncodingException; +import java.io.Writer; + +/** + * Handles instrumenting a single file. + * @author Nicholas C. Zakas + */ +public class FileInstrumenter { + + private static boolean verbose = false; + + /** + * Determines if the class is in verbose mode. + * @return + */ + public static boolean isVerbose() { + return verbose; + } + + /** + * Sets the verbose flag, which outputs debugging information. + * @param verbose The new value for the verbose flag. + */ + public static void setVerbose(boolean verbose) { + FileInstrumenter.verbose = verbose; + } + + /** + * Instruments a single file. + * @param inputFilename The filename to instrument. + * @param outputFilename The output file. + * @throws FileNotFoundException If the input file cannot be read. + * @throws UnsupportedEncodingException If charset is invalid. + * @throws IOException If an error occurs during writing. + * @throws RecognitionException If the file has a syntax error. + */ + public static void instrument(String inputFilename, String outputFilename) + throws FileNotFoundException, UnsupportedEncodingException, + IOException, RecognitionException { + + instrument(inputFilename, outputFilename, "UTF-8"); + } + + /** + * Instruments a single file. + * @param inputFilename The filename to instrument. + * @param outputFilename The output file. + * @param charset The character set to use. + * @throws FileNotFoundException If the input file cannot be read. + * @throws UnsupportedEncodingException If charset is invalid. + * @throws IOException If an error occurs during writing. + * @throws RecognitionException If the file has a syntax error. + */ + public static void instrument(String inputFilename, String outputFilename, + String charset) throws FileNotFoundException, + UnsupportedEncodingException, IOException, RecognitionException { + + if (verbose) { + System.err.println("\n[INFO] Preparing to instrument JavaScript file " + inputFilename + "."); + System.err.println("\n[INFO] Output file will be " + outputFilename + "."); + } + + Reader in = null; + Writer out = null; + + try { + + File inputFile = new File(inputFilename); + + in = new InputStreamReader(new FileInputStream(inputFilename), charset); + out = new OutputStreamWriter(new FileOutputStream(outputFilename), charset); + + //if the file is empty, don't bother instrumenting + //if (inputFile.length() > 0){ + //strip out relative paths - that just messes up coverage report writing + JavaScriptInstrumenter instrumenter = new JavaScriptInstrumenter(in, inputFilename.replaceAll("\\.\\./", ""), (new File(inputFilename)).getCanonicalPath()); + instrumenter.instrument(out, verbose); + //} else { + // out.write(""); + //} + } catch (IOException ex){ + in.close(); + if (out != null){ + out.close(); + } + throw ex; + } + + if (verbose) { + System.err.println("\n[INFO] Created file " + outputFilename + "."); + } + + } + +} diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/JavaScriptInstrumenter.java b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/JavaScriptInstrumenter.java new file mode 100644 index 000000000..98cc19084 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/JavaScriptInstrumenter.java @@ -0,0 +1,123 @@ +/* + * YUI Test + * Author: Nicholas C. Zakas <nzakas@yahoo-inc.com> + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ + +package com.yahoo.platform.yuitest.coverage; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringReader; +import java.io.Writer; +import org.antlr.runtime.ANTLRReaderStream; +import org.antlr.runtime.TokenRewriteStream; +import org.antlr.runtime.RecognitionException; +import org.antlr.stringtemplate.StringTemplate; +import org.antlr.stringtemplate.StringTemplateGroup; + +/** + * + * @author Nicholas C. Zakas + */ +public class JavaScriptInstrumenter { + + private Reader in; + private String name; + private String path; + + public JavaScriptInstrumenter(Reader in, String name){ + this(in, name, name); + } + + public JavaScriptInstrumenter(Reader in, String name, String path){ + this.in = in; + this.name = name; + this.path = path; + } + + public void instrument(Writer out, boolean verbose) throws IOException, RecognitionException { + + //get string headerTemplate group + InputStream stgstream = JavaScriptInstrumenter.class.getResourceAsStream("ES3YUITestTemplates.stg"); + InputStreamReader reader = new InputStreamReader(stgstream); + StringTemplateGroup group = new StringTemplateGroup(reader); + reader.close(); + + //get headerTemplate for file header + StringTemplate headerTemplate = group.getInstanceOf("file_header"); + headerTemplate.setAttribute("src", name); + headerTemplate.setAttribute("path", path.replace("\\", "\\\\")); + + //read lines for later usage + BufferedReader lineReader = new BufferedReader(in); + StringBuilder codeLines = new StringBuilder(); + StringBuilder code = new StringBuilder(); + String line = null; + codeLines.append("_yuitest_coverage[\""); + codeLines.append(name); + codeLines.append("\"].code=["); + + while((line = lineReader.readLine()) != null){ + + //build up array of lines + codeLines.append("\""); + codeLines.append(line.replace("\\", "\\\\").replace("\"", "\\\"")); + codeLines.append("\","); + + //build up source code + code.append(line); + code.append("\n"); + } + + + switch (codeLines.charAt(codeLines.length()-1)){ + case ',': //if there's a dangling comma, replace it + codeLines.setCharAt(codeLines.length()-1, ']'); + break; + case '[': //empty file + codeLines.append("]"); + break; + //no default + } + codeLines.append(";"); + + //output full path + + //setup parser + ANTLRReaderStream stream = new ANTLRReaderStream(new StringReader(code.toString())); + stream.name = name; + ES3YUITestLexer lexer = new ES3YUITestLexer(stream); + TokenRewriteStream tokens = new TokenRewriteStream(lexer); + ES3YUITestParser parser = new ES3YUITestParser(tokens); + parser.setTemplateLib(group); + //parser.setVerbose(verbose); + + String result = ""; + + //an empty string will cause the parser to explode + if (code.toString().trim().length() > 0){ + parser.program(); + result = tokens.toString(); + } + + //close input stream in case writing to the same place + in.close(); in = null; + + //output the resulting file + out.write(headerTemplate.toString()); + out.write("\n"); + out.write(codeLines.toString()); + out.write("\n"); + out.flush(); + out.write(result); + out.flush(); + } + + +} diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/YUITestCoverage.java b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/YUITestCoverage.java new file mode 100644 index 000000000..e8040e894 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/YUITestCoverage.java @@ -0,0 +1,152 @@ +/* + * YUI Test + * Author: Nicholas C. Zakas <nzakas@yahoo-inc.com> + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ +package com.yahoo.platform.yuitest.coverage; + +import jargs.gnu.CmdLineParser; +import java.io.*; +import java.nio.charset.Charset; + +/** + * Main YUI Test Coverage class. + * @author Nicholas C. Zakas + */ +public class YUITestCoverage { + + public static void main(String args[]) { + + //---------------------------------------------------------------------- + // Initialize command line parser + //---------------------------------------------------------------------- + CmdLineParser parser = new CmdLineParser(); + CmdLineParser.Option verboseOpt = parser.addBooleanOption('v', "verbose"); + CmdLineParser.Option helpOpt = parser.addBooleanOption('h', "help"); + CmdLineParser.Option charsetOpt = parser.addStringOption("charset"); + CmdLineParser.Option outputLocationOpt = parser.addStringOption('o', "output"); + CmdLineParser.Option directoryOpt = parser.addBooleanOption('d', "dir"); + + Reader in = null; + Writer out = null; + + try { + + + parser.parse(args); + + //Help option + Boolean help = (Boolean) parser.getOptionValue(helpOpt); + if (help != null && help.booleanValue()) { + usage(); + System.exit(0); + } + + //Verbose option + boolean verbose = parser.getOptionValue(verboseOpt) != null; + + //Charset option + String charset = (String) parser.getOptionValue(charsetOpt); + if (charset == null || !Charset.isSupported(charset)) { + charset = System.getProperty("file.encoding"); + if (charset == null) { + charset = "UTF-8"; + } + if (verbose) { + System.err.println("\n[INFO] Using charset " + charset); + } + } + + //get the files to operate on + String[] fileArgs = parser.getRemainingArgs(); + + if (fileArgs.length == 0) { + usage(); + System.exit(1); + } + + String outputLocation = (String) parser.getOptionValue(outputLocationOpt); + Boolean directories = parser.getOptionValue(directoryOpt) != null; + + if (outputLocation == null){ + if (directories){ + throw new Exception("-o option is required with -d option."); + } + if (verbose) { + System.err.println("\n[INFO] Preparing to instrument JavaScript file " + fileArgs[0] + "."); + } + + in = new InputStreamReader(new FileInputStream(fileArgs[0]), charset); + JavaScriptInstrumenter instrumenter = new JavaScriptInstrumenter(in, fileArgs[0]); + out = new OutputStreamWriter(System.out, charset); + instrumenter.instrument(out, verbose); + } else{ + + if (directories){ + DirectoryInstrumenter.setVerbose(verbose); + + //in this case fileArgs[0] and outputLocation are directories + DirectoryInstrumenter.instrument(fileArgs[0], outputLocation); + } else { + FileInstrumenter.setVerbose(verbose); + + //in this case fileArgs[0] and outputLocation are files + FileInstrumenter.instrument(fileArgs[0], outputLocation); + } + } + + + + + } catch (CmdLineParser.OptionException e) { + + usage(); + System.exit(1); + + } catch (IOException e) { + + e.printStackTrace(); + System.exit(1); + + } catch (Exception e) { + + e.printStackTrace(); + // Return a special error code used specifically by the web front-end. + System.exit(2); + + } finally { + + if (in != null) { + try { + in.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + if (out != null) { + try { + out.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + private static void usage() { + System.out.println( + "\nUsage: java -jar yuitest-coverage-x.y.z.jar [options] [file|dir]\n\n" + + + "Global Options\n" + + " -h, --help Displays this information.\n" + + " --charset <charset> Read the input file using <charset>.\n" + + " -d, --dir Input and output (-o) are both directories.\n" + + " -v, --verbose Display informational messages and warnings.\n" + + " -o <file|dir> Place the output into <file|dir>. Defaults to stdout.\n\n"); + } + + +} diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/grammar/ES3.g b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/grammar/ES3.g new file mode 100644 index 000000000..f5b24593c --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/grammar/ES3.g @@ -0,0 +1,1536 @@ +/* + +Copyrights 2008-2009 Xebic Reasearch BV. All rights reserved (see license.txt). +Original work by Patrick Hulsmeijer. + +This ANTLR 3 LL(*) grammar is based on Ecma-262 3rd edition (JavaScript 1.5, JScript 5.5). +The annotations refer to the "A Grammar Summary" section (e.g. A.1 Lexical Grammar) and the numbers in parenthesis to the paragraph numbers (e.g. (7.8) ). +This document is best viewed with ANTLRWorks (www.antlr.org). + + +The major challenges faced in defining this grammar were: + +-1- Ambiguity surrounding the DIV sign in relation to the multiplicative expression and the regular expression literal. +This is solved with some lexer driven magic: a gated semantical predicate turns the recognition of regular expressions on or off, based on the +value of the RegularExpressionsEnabled property. When regular expressions are enabled they take precedence over division expressions. The decision whether +regular expressions are enabled is based on the heuristics that the previous token can be considered as last token of a left-hand-side operand of a division. + +-2- Automatic semicolon insertion. +This is solved within the parser. The semicolons are not physically inserted but the situations in which they should are recognized and treated as if they were. +The physical insertion of semicolons would be undesirable because of several reasons: +- performance degration because of how ANTLR handles tokens in token streams +- the alteration of the input, which we need to have unchanged +- it is superfluous being of no interest to AST construction + +-3- Unicode identifiers +Because ANTLR couldn't handle the unicode tables defined in the specification well and for performance reasons unicode identifiers are implemented as an action +driven alternative to ASCII identifiers. First the ASCII version is tried that is defined in detail in this grammar and then the unicode alternative is tried action driven. +Because of the fact that the ASCII version is defined in detail the mTokens switch generation in the lexer can predict identifiers appropriately. +For details see the identifier rules. + + +The minor challenges were related to converting the grammar to an ANTLR LL(*) grammar: +- Resolving the ambiguity between functionDeclaration vs functionExpression and block vs objectLiteral stemming from the expressionStatement production. +- Left recursive nature of the left hand side expressions. +- The assignmentExpression production. +- The forStatement production. +The grammar was kept as close as possible to the grammar in the "A Grammar Summary" section of Ecma-262. + +*/ + + +grammar ES3 ; + +options +{ + output = AST ; + language = Java ; +} + +tokens +{ +// Reserved words + NULL = 'null' ; + TRUE = 'true' ; + FALSE = 'false' ; + +// Keywords + BREAK = 'break' ; + CASE = 'case' ; + CATCH = 'catch' ; + CONTINUE = 'continue' ; + DEFAULT = 'default' ; + DELETE = 'delete' ; + DO = 'do' ; + ELSE = 'else' ; + FINALLY = 'finally' ; + FOR = 'for' ; + FUNCTION = 'function' ; + IF = 'if' ; + IN = 'in' ; + INSTANCEOF = 'instanceof' ; + NEW = 'new' ; + RETURN = 'return' ; + SWITCH = 'switch' ; + THIS = 'this' ; + THROW = 'throw' ; + TRY = 'try' ; + TYPEOF = 'typeof' ; + VAR = 'var' ; + VOID = 'void' ; + WHILE = 'while' ; + WITH = 'with' ; + +// Future reserved words + ABSTRACT = 'abstract' ; + BOOLEAN = 'boolean' ; + BYTE = 'byte' ; + CHAR = 'char' ; + CLASS = 'class' ; + CONST = 'const' ; + DEBUGGER = 'debugger' ; + DOUBLE = 'double' ; + ENUM = 'enum' ; + EXPORT = 'export' ; + EXTENDS = 'extends' ; + FINAL = 'final' ; + FLOAT = 'float' ; + GOTO = 'goto' ; + IMPLEMENTS = 'implements' ; + IMPORT = 'import' ; + INT = 'int' ; + INTERFACE = 'interface' ; + LONG = 'long' ; + NATIVE = 'native' ; + PACKAGE = 'package' ; + PRIVATE = 'private' ; + PROTECTED = 'protected' ; + PUBLIC = 'public' ; + SHORT = 'short' ; + STATIC = 'static' ; + SUPER = 'super' ; + SYNCHRONIZED = 'synchronized' ; + THROWS = 'throws' ; + TRANSIENT = 'transient' ; + VOLATILE = 'volatile' ; + +// Punctuators + LBRACE = '{' ; + RBRACE = '}' ; + LPAREN = '(' ; + RPAREN = ')' ; + LBRACK = '[' ; + RBRACK = ']' ; + DOT = '.' ; + SEMIC = ';' ; + COMMA = ',' ; + LT = '<' ; + GT = '>' ; + LTE = '<=' ; + GTE = '>=' ; + EQ = '==' ; + NEQ = '!=' ; + SAME = '===' ; + NSAME = '!==' ; + ADD = '+' ; + SUB = '-' ; + MUL = '*' ; + MOD = '%' ; + INC = '++' ; + DEC = '--' ; + SHL = '<<' ; + SHR = '>>' ; + SHU = '>>>' ; + AND = '&' ; + OR = '|' ; + XOR = '^' ; + NOT = '!' ; + INV = '~' ; + LAND = '&&' ; + LOR = '||' ; + QUE = '?' ; + COLON = ':' ; + ASSIGN = '=' ; + ADDASS = '+=' ; + SUBASS = '-=' ; + MULASS = '*=' ; + MODASS = '%=' ; + SHLASS = '<<=' ; + SHRASS = '>>=' ; + SHUASS = '>>>=' ; + ANDASS = '&=' ; + ORASS = '|=' ; + XORASS = '^=' ; + DIV = '/' ; + DIVASS = '/=' ; + +// Imaginary + ARGS ; + ARRAY ; + BLOCK ; + BYFIELD ; + BYINDEX ; + CALL ; + CEXPR ; + EXPR ; + FORITER ; + FORSTEP ; + ITEM ; + LABELLED ; + NAMEDVALUE ; + NEG ; + OBJECT ; + PAREXPR ; + PDEC ; + PINC ; + POS ; +} + +@lexer::header {package com.google.jstestdriver.coverage.es3;} + +@lexer::members +{ +private Token last; + +private final boolean areRegularExpressionsEnabled() +{ + if (last == null) + { + return true; + } + switch (last.getType()) + { + // identifier + case Identifier: + // literals + case NULL: + case TRUE: + case FALSE: + case THIS: + case OctalIntegerLiteral: + case DecimalLiteral: + case HexIntegerLiteral: + case StringLiteral: + // member access ending + case RBRACK: + // function call or nested expression ending + case RPAREN: + return false; + // otherwise OK + default: + return true; + } +} + +private final void consumeIdentifierUnicodeStart() throws RecognitionException, NoViableAltException +{ + int ch = input.LA(1); + if (isIdentifierStartUnicode(ch)) + { + matchAny(); + do + { + ch = input.LA(1); + if (ch == '$' || (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || ch == '\\' || ch == '_' || (ch >= 'a' && ch <= 'z') || isIdentifierPartUnicode(ch)) + { + mIdentifierPart(); + } + else + { + return; + } + } + while (true); + } + else + { + throw new NoViableAltException(); + } +} + +private final boolean isIdentifierPartUnicode(int ch) +{ + return Character.isJavaIdentifierPart(ch); +} + +private final boolean isIdentifierStartUnicode(int ch) +{ + return Character.isJavaIdentifierStart(ch); +} + +public Token nextToken() +{ + Token result = super.nextToken(); + if (result.getChannel() == Token.DEFAULT_CHANNEL) + { + last = result; + } + return result; +} +} +@parser::header {package com.google.jstestdriver.coverage.es3;} +@parser::members +{ +private final boolean isLeftHandSideAssign(RuleReturnScope lhs, Object[] cached) +{ + if (cached[0] != null) + { + return ((Boolean)cached[0]).booleanValue(); + } + + boolean result; + if (isLeftHandSideExpression(lhs)) + { + switch (input.LA(1)) + { + case ASSIGN: + case MULASS: + case DIVASS: + case MODASS: + case ADDASS: + case SUBASS: + case SHLASS: + case SHRASS: + case SHUASS: + case ANDASS: + case XORASS: + case ORASS: + result = true; + break; + default: + result = false; + break; + } + } + else + { + result = false; + } + + cached[0] = new Boolean(result); + return result; +} + +private final static boolean isLeftHandSideExpression(RuleReturnScope lhs) +{ + if (lhs.getTree() == null) // e.g. during backtracking + { + return true; + } + else + { + switch (((Tree)lhs.getTree()).getType()) + { + // primaryExpression + case THIS: + case Identifier: + case NULL: + case TRUE: + case FALSE: + case DecimalLiteral: + case OctalIntegerLiteral: + case HexIntegerLiteral: + case StringLiteral: + case RegularExpressionLiteral: + case ARRAY: + case OBJECT: + case PAREXPR: + // functionExpression + case FUNCTION: + // newExpression + case NEW: + // leftHandSideExpression + case CALL: + case BYFIELD: + case BYINDEX: + return true; + + default: + return false; + } + } +} + +private final boolean isLeftHandSideIn(RuleReturnScope lhs, Object[] cached) +{ + if (cached[0] != null) + { + return ((Boolean)cached[0]).booleanValue(); + } + + boolean result = isLeftHandSideExpression(lhs) && (input.LA(1) == IN); + cached[0] = new Boolean(result); + return result; +} + +private final void promoteEOL(ParserRuleReturnScope rule) +{ + // Get current token and its type (the possibly offending token). + Token lt = input.LT(1); + int la = lt.getType(); + + // We only need to promote an EOL when the current token is offending (not a SEMIC, EOF, RBRACE, EOL or MultiLineComment). + // EOL and MultiLineComment are not offending as they're already promoted in a previous call to this method. + // Promoting an EOL means switching it from off channel to on channel. + // A MultiLineComment gets promoted when it contains an EOL. + if (!(la == SEMIC || la == EOF || la == RBRACE || la == EOL || la == MultiLineComment)) + { + // Start on the possition before the current token and scan backwards off channel tokens until the previous on channel token. + for (int ix = lt.getTokenIndex() - 1; ix > 0; ix--) + { + lt = input.get(ix); + if (lt.getChannel() == Token.DEFAULT_CHANNEL) + { + // On channel token found: stop scanning. + break; + } + else if (lt.getType() == EOL || (lt.getType() == MultiLineComment && lt.getText().matches("/.*\r\n|\r|\n"))) + { + // We found our EOL: promote the token to on channel, position the input on it and reset the rule start. + lt.setChannel(Token.DEFAULT_CHANNEL); + input.seek(lt.getTokenIndex()); + if (rule != null) + { + rule.start = lt; + } + break; + } + } + } +} +} + +// +// $< A.1 Lexical Grammar (7) +// + +// Added for lexing purposes + +fragment BSLASH + : '\\' + ; + +fragment DQUOTE + : '"' + ; + +fragment SQUOTE + : '\'' + ; + +// $< Whitespace (7.2) + +fragment TAB + : '\u0009' + ; + +fragment VT // Vertical TAB + : '\u000b' + ; + +fragment FF // Form Feed + : '\u000c' + ; + +fragment SP // Space + : '\u0020' + ; + +fragment NBSP // Non-Breaking Space + : '\u00a0' + ; + +fragment USP // Unicode Space Separator (rest of Unicode category Zs) + : '\u1680' // OGHAM SPACE MARK + | '\u180E' // MONGOLIAN VOWEL SEPARATOR + | '\u2000' // EN QUAD + | '\u2001' // EM QUAD + | '\u2002' // EN SPACE + | '\u2003' // EM SPACE + | '\u2004' // THREE-PER-EM SPACE + | '\u2005' // FOUR-PER-EM SPACE + | '\u2006' // SIX-PER-EM SPACE + | '\u2007' // FIGURE SPACE + | '\u2008' // PUNCTUATION SPACE + | '\u2009' // THIN SPACE + | '\u200A' // HAIR SPACE + | '\u202F' // NARROW NO-BREAK SPACE + | '\u205F' // MEDIUM MATHEMATICAL SPACE + | '\u3000' // IDEOGRAPHIC SPACE + ; + +WhiteSpace + : ( TAB | VT | FF | SP | NBSP | USP )+ { $channel = HIDDEN; } + ; + +// $> + +// $< Line terminators (7.3) + +fragment LF // Line Feed + : '\n' + ; + +fragment CR // Carriage Return + : '\r' + ; + +fragment LS // Line Separator + : '\u2028' + ; + +fragment PS // Paragraph Separator + : '\u2029' + ; + +fragment LineTerminator + : CR | LF | LS | PS + ; + +EOL + : ( ( CR LF? ) | LF | LS | PS ) { $channel = HIDDEN; } + ; +// $> + +// $< Comments (7.4) + +MultiLineComment + : '/*' ( options { greedy = false; } : . )* '*/' + ; + +SingleLineComment + : '//' ( ~( LineTerminator ) )* { $channel = HIDDEN; } + ; + +// $> + +// $< Tokens (7.5) + +token + : reservedWord + | Identifier + | punctuator + | numericLiteral + | StringLiteral + ; + +// $< Reserved words (7.5.1) + +reservedWord + : keyword + | futureReservedWord + | NULL + | booleanLiteral + ; + +// $> + +// $< Keywords (7.5.2) + +keyword + : BREAK + | CASE + | CATCH + | CONTINUE + | DEFAULT + | DELETE + | DO + | ELSE + | FINALLY + | FOR + | FUNCTION + | IF + | IN + | INSTANCEOF + | NEW + | RETURN + | SWITCH + | THIS + | THROW + | TRY + | TYPEOF + | VAR + | VOID + | WHILE + | WITH + ; + +// $> + +// $< Future reserved words (7.5.3) + +futureReservedWord + : ABSTRACT + | BOOLEAN + | BYTE + | CHAR + | CLASS + | CONST + | DEBUGGER + | DOUBLE + | ENUM + | EXPORT + | EXTENDS + | FINAL + | FLOAT + | GOTO + | IMPLEMENTS + | IMPORT + | INT + | INTERFACE + | LONG + | NATIVE + | PACKAGE + | PRIVATE + | PROTECTED + | PUBLIC + | SHORT + | STATIC + | SUPER + | SYNCHRONIZED + | THROWS + | TRANSIENT + | VOLATILE + ; + +// $> + +// $> + +// $< Identifiers (7.6) + +fragment IdentifierStartASCII + : 'a'..'z' | 'A'..'Z' + | '$' + | '_' + | BSLASH 'u' HexDigit HexDigit HexDigit HexDigit // UnicodeEscapeSequence + ; + +/* +The first two alternatives define how ANTLR can match ASCII characters which can be considered as part of an identifier. +The last alternative matches other characters in the unicode range that can be sonsidered as part of an identifier. +*/ +fragment IdentifierPart + : DecimalDigit + | IdentifierStartASCII + | { isIdentifierPartUnicode(input.LA(1)) }? { matchAny(); } + ; + +fragment IdentifierNameASCIIStart + : IdentifierStartASCII IdentifierPart* + ; + +/* +The second alternative acts as an action driven fallback to evaluate other characters in the unicode range than the ones in the ASCII subset. +Due to the first alternative this grammar defines enough so that ANTLR can generate a lexer that correctly predicts identifiers with characters in the ASCII range. +In that way keywords, other reserved words and ASCII identifiers are recognized with standard ANTLR driven logic. When the first character for an identifier fails to +match this ASCII definition, the lexer calls consumeIdentifierUnicodeStart because of the action in the alternative. This method checks whether the character matches +as first character in ranges other than ASCII and consumes further characters belonging to the identifier with help of mIdentifierPart generated out of the +IdentifierPart rule above. +*/ +Identifier + : IdentifierNameASCIIStart + | { consumeIdentifierUnicodeStart(); } + ; + +// $> + +// $< Punctuators (7.7) + +punctuator + : LBRACE + | RBRACE + | LPAREN + | RPAREN + | LBRACK + | RBRACK + | DOT + | SEMIC + | COMMA + | LT + | GT + | LTE + | GTE + | EQ + | NEQ + | SAME + | NSAME + | ADD + | SUB + | MUL + | MOD + | INC + | DEC + | SHL + | SHR + | SHU + | AND + | OR + | XOR + | NOT + | INV + | LAND + | LOR + | QUE + | COLON + | ASSIGN + | ADDASS + | SUBASS + | MULASS + | MODASS + | SHLASS + | SHRASS + | SHUASS + | ANDASS + | ORASS + | XORASS + | DIV + | DIVASS + ; + +// $> + +// $< Literals (7.8) + +literal + : NULL + | booleanLiteral + | numericLiteral + | StringLiteral + | RegularExpressionLiteral + ; + +booleanLiteral + : TRUE + | FALSE + ; + +// $< Numeric literals (7.8.3) + +/* +Note: octal literals are described in the B Compatibility section. +These are removed from the standards but are here for backwards compatibility with earlier ECMAScript definitions. +*/ + +fragment DecimalDigit + : '0'..'9' + ; + +fragment HexDigit + : DecimalDigit | 'a'..'f' | 'A'..'F' + ; + +fragment OctalDigit + : '0'..'7' + ; + +fragment ExponentPart + : ( 'e' | 'E' ) ( '+' | '-' )? DecimalDigit+ + ; + +fragment DecimalIntegerLiteral + : '0' + | '1'..'9' DecimalDigit* + ; + +DecimalLiteral + : DecimalIntegerLiteral '.' DecimalDigit* ExponentPart? + | '.' DecimalDigit+ ExponentPart? + | DecimalIntegerLiteral ExponentPart? + ; + +OctalIntegerLiteral + : '0' OctalDigit+ + ; + +HexIntegerLiteral + : ( '0x' | '0X' ) HexDigit+ + ; + +numericLiteral + : DecimalLiteral + | OctalIntegerLiteral + | HexIntegerLiteral + ; + +// $> + +// $< String literals (7.8.4) + +/* +Note: octal escape sequences are described in the B Compatibility section. +These are removed from the standards but are here for backwards compatibility with earlier ECMAScript definitions. +*/ + +fragment CharacterEscapeSequence + : ~( DecimalDigit | 'x' | 'u' | LineTerminator ) // Concatenation of SingleEscapeCharacter and NonEscapeCharacter + ; + +fragment ZeroToThree + : '0'..'3' + ; + +fragment OctalEscapeSequence + : OctalDigit + | ZeroToThree OctalDigit + | '4'..'7' OctalDigit + | ZeroToThree OctalDigit OctalDigit + ; + +fragment HexEscapeSequence + : 'x' HexDigit HexDigit + ; + +fragment UnicodeEscapeSequence + : 'u' HexDigit HexDigit HexDigit HexDigit + ; + +fragment EscapeSequence + : + BSLASH + ( + CharacterEscapeSequence + | OctalEscapeSequence + | HexEscapeSequence + | UnicodeEscapeSequence + ) + ; + +StringLiteral + : SQUOTE ( ~( SQUOTE | BSLASH | LineTerminator ) | EscapeSequence )* SQUOTE + | DQUOTE ( ~( DQUOTE | BSLASH | LineTerminator ) | EscapeSequence )* DQUOTE + ; + +// $> + +// $< Regular expression literals (7.8.5) + +fragment BackslashSequence + : BSLASH ~( LineTerminator ) + ; + +fragment RegularExpressionFirstChar + : ~ ( LineTerminator | MUL | BSLASH | DIV ) + | BackslashSequence + ; + +fragment RegularExpressionChar + : ~ ( LineTerminator | BSLASH | DIV ) + | BackslashSequence + ; + +RegularExpressionLiteral + : { areRegularExpressionsEnabled() }?=> DIV RegularExpressionFirstChar RegularExpressionChar* DIV IdentifierPart* + ; + +// $> + +// $> + +// $> + +// +// $< A.3 Expressions (11) +// + +// $<Primary expressions (11.1) + +primaryExpression + : THIS + | Identifier + | literal + | arrayLiteral + | objectLiteral + | lpar=LPAREN expression RPAREN -> ^( PAREXPR[$lpar, "PAREXPR"] expression ) + ; + +arrayLiteral + : lb=LBRACK ( arrayItem ( COMMA arrayItem )* )? RBRACK + -> ^( ARRAY[$lb, "ARRAY"] arrayItem* ) + ; + +arrayItem + : ( expr=assignmentExpression | { input.LA(1) == COMMA }? ) + -> ^( ITEM $expr? ) + ; + +objectLiteral + : lb=LBRACE ( nameValuePair ( COMMA nameValuePair )* )? RBRACE + -> ^( OBJECT[$lb, "OBJECT"] nameValuePair* ) + ; + +nameValuePair + : propertyName COLON assignmentExpression + -> ^( NAMEDVALUE propertyName assignmentExpression ) + ; + +propertyName + : Identifier + | StringLiteral + | numericLiteral + ; + +// $> + +// $<Left-hand-side expressions (11.2) + +/* +Refactored some rules to make them LL(*) compliant: +all the expressions surrounding member selection and calls have been moved to leftHandSideExpression to make them right recursive +*/ + +memberExpression + : primaryExpression + | functionExpression + | newExpression + ; + +newExpression + : NEW^ primaryExpression + ; + +multiLineComment + : MultiLineComment + ; + +arguments + : LPAREN ( assignmentExpression ( COMMA assignmentExpression )* )? RPAREN + -> ^( ARGS assignmentExpression* ) + ; + +leftHandSideExpression + : + ( + memberExpression -> memberExpression + ) + ( + arguments -> ^( CALL $leftHandSideExpression arguments ) + | LBRACK expression RBRACK -> ^( BYINDEX $leftHandSideExpression expression ) + | DOT Identifier -> ^( BYFIELD $leftHandSideExpression Identifier ) + )* + ; + +// $> + +// $<Postfix expressions (11.3) + +/* +The specification states that there are no line terminators allowed before the postfix operators. +This is enforced by the call to promoteEOL in the action before ( INC | DEC ). +We only must promote EOLs when the la is INC or DEC because this production is chained as all expression rules. +In other words: only promote EOL when we are really in a postfix expression. A check on the la will ensure this. +*/ +postfixExpression + : leftHandSideExpression { if (input.LA(1) == INC || input.LA(1) == DEC) promoteEOL(null); } ( postfixOperator^ )? + ; + +postfixOperator + : op=INC { $op.setType(PINC); } + | op=DEC { $op.setType(PDEC); } + ; + +// $> + +// $<Unary operators (11.4) + +unaryExpression + : postfixExpression + | unaryOperator^ unaryExpression + ; + +unaryOperator + : DELETE + | VOID + | TYPEOF + | INC + | DEC + | op=ADD { $op.setType(POS); } + | op=SUB { $op.setType(NEG); } + | INV + | NOT + ; + +// $> + +// $<Multiplicative operators (11.5) + +multiplicativeExpression + : unaryExpression ( ( MUL | DIV | MOD )^ unaryExpression )* + ; + +// $> + +// $<Additive operators (11.6) + +additiveExpression + : multiplicativeExpression ( ( ADD | SUB )^ multiplicativeExpression )* + ; + +// $> + +// $<Bitwise shift operators (11.7) + +shiftExpression + : additiveExpression ( ( SHL | SHR | SHU )^ additiveExpression )* + ; + +// $> + +// $<Relational operators (11.8) + +relationalExpression + : shiftExpression ( ( LT | GT | LTE | GTE | INSTANCEOF | IN )^ shiftExpression )* + ; + +relationalExpressionNoIn + : shiftExpression ( ( LT | GT | LTE | GTE | INSTANCEOF )^ shiftExpression )* + ; + +// $> + +// $<Equality operators (11.9) + +equalityExpression + : relationalExpression ( ( EQ | NEQ | SAME | NSAME )^ relationalExpression )* + ; + +equalityExpressionNoIn + : relationalExpressionNoIn ( ( EQ | NEQ | SAME | NSAME )^ relationalExpressionNoIn )* + ; + +// $> + +// $<Binary bitwise operators (11.10) + +bitwiseANDExpression + : equalityExpression ( AND^ equalityExpression )* + ; + +bitwiseANDExpressionNoIn + : equalityExpressionNoIn ( AND^ equalityExpressionNoIn )* + ; + +bitwiseXORExpression + : bitwiseANDExpression ( XOR^ bitwiseANDExpression )* + ; + +bitwiseXORExpressionNoIn + : bitwiseANDExpressionNoIn ( XOR^ bitwiseANDExpressionNoIn )* + ; + +bitwiseORExpression + : bitwiseXORExpression ( OR^ bitwiseXORExpression )* + ; + +bitwiseORExpressionNoIn + : bitwiseXORExpressionNoIn ( OR^ bitwiseXORExpressionNoIn )* + ; + +// $> + +// $<Binary logical operators (11.11) + +logicalANDExpression + : bitwiseORExpression ( LAND^ bitwiseORExpression )* + ; + +logicalANDExpressionNoIn + : bitwiseORExpressionNoIn ( LAND^ bitwiseORExpressionNoIn )* + ; + +logicalORExpression + : logicalANDExpression ( LOR^ logicalANDExpression )* + ; + +logicalORExpressionNoIn + : logicalANDExpressionNoIn ( LOR^ logicalANDExpressionNoIn )* + ; + +// $> + +// $<Conditional operator (11.12) + +conditionalExpression + : logicalORExpression ( QUE^ assignmentExpression COLON! assignmentExpression )? + ; + +conditionalExpressionNoIn + : logicalORExpressionNoIn ( QUE^ assignmentExpressionNoIn COLON! assignmentExpressionNoIn )? + ; + +// $> + +// $<Assignment operators (11.13) + +/* +The specification defines the AssignmentExpression rule as follows: +AssignmentExpression : + ConditionalExpression + LeftHandSideExpression AssignmentOperator AssignmentExpression +This rule has a LL(*) conflict. Resolving this with a syntactical predicate will yield something like this: + +assignmentExpression + : ( leftHandSideExpression assignmentOperator )=> leftHandSideExpression assignmentOperator^ assignmentExpression + | conditionalExpression + ; +assignmentOperator + : ASSIGN | MULASS | DIVASS | MODASS | ADDASS | SUBASS | SHLASS | SHRASS | SHUASS | ANDASS | XORASS | ORASS + ; + +But that didn't seem to work. Terence Par writes in his book that LL(*) conflicts in general can best be solved with auto backtracking. But that would be +a performance killer for such a heavy used rule. +The solution I came up with is to always invoke the conditionalExpression first and than decide what to do based on the result of that rule. +When the rule results in a Tree that can't be coming from a left hand side expression, then we're done. +When it results in a Tree that is coming from a left hand side expression and the LA(1) is an assignment operator then parse the assignment operator +followed by the right recursive call. +*/ +assignmentExpression +@init +{ + Object[] isLhs = new Object[1]; +} + : lhs=conditionalExpression + ( { isLeftHandSideAssign(lhs, isLhs) }? assignmentOperator^ assignmentExpression )? + ; + +assignmentOperator + : ASSIGN | MULASS | DIVASS | MODASS | ADDASS | SUBASS | SHLASS | SHRASS | SHUASS | ANDASS | XORASS | ORASS + ; + +assignmentExpressionNoIn +@init +{ + Object[] isLhs = new Object[1]; +} + : lhs=conditionalExpressionNoIn + ( { isLeftHandSideAssign(lhs, isLhs) }? assignmentOperator^ assignmentExpressionNoIn )? + ; + +// $> + +// $<Comma operator (11.14) + +expression + : exprs+=assignmentExpression ( COMMA exprs+=assignmentExpression )* + -> { $exprs.size() > 1 }? ^( CEXPR $exprs+ ) + -> $exprs + ; + +expressionNoIn + : exprs+=assignmentExpressionNoIn ( COMMA exprs+=assignmentExpressionNoIn )* + -> { $exprs.size() > 1 }? ^( CEXPR $exprs+ ) + -> $exprs + ; + +// $> + +// $> + +// +// $< A.4 Statements (12) +// + +/* +This rule handles semicolons reported by the lexer and situations where the ECMA 3 specification states there should be semicolons automaticly inserted. +The auto semicolons are not actually inserted but this rule behaves as if they were. + +In the following situations an ECMA 3 parser should auto insert absent but grammaticly required semicolons: +- the current token is a right brace +- the current token is the end of file (EOF) token +- there is at least one end of line (EOL) token between the current token and the previous token. + +The RBRACE is handled by matching it but not consuming it. +The EOF needs no further handling because it is not consumed by default. +The EOL situation is handled by promoting the EOL or MultiLineComment with an EOL present from off channel to on channel +and thus making it parseable instead of handling it as white space. This promoting is done in the action promoteEOL. +*/ +semic +@init +{ + // Mark current position so we can unconsume a RBRACE. + int marker = input.mark(); + // Promote EOL if appropriate + promoteEOL(retval); +} + : SEMIC + | EOF + | RBRACE { input.rewind(marker); } + | EOL | MultiLineComment // (with EOL in it) + ; + +/* +To solve the ambiguity between block and objectLiteral via expressionStatement all but the block alternatives have been moved to statementTail. +Now when k = 1 and a semantical predicate is defined ANTLR generates code that always will prefer block when the LA(1) is a LBRACE. +This will result in the same behaviour that is described in the specification under 12.4 on the expressionStatement rule. +*/ +statement +options +{ + k = 1 ; +} + : { input.LA(1) == LBRACE }? block + | statementTail + ; + +statementTail + : variableStatement + | emptyStatement + | expressionStatement + | ifStatement + | iterationStatement + | continueStatement + | breakStatement + | returnStatement + | withStatement + | labelledStatement + | switchStatement + | throwStatement + | tryStatement + ; + +// $<Block (12.1) + +block + : lb=LBRACE statement* RBRACE + -> ^( BLOCK[$lb, "BLOCK"] statement* ) + ; + +// $> + +// $<Variable statement 12.2) + +variableStatement + : VAR variableDeclaration ( COMMA variableDeclaration )* semic + -> ^( VAR variableDeclaration+ ) + ; + +variableDeclaration + : Identifier ( ASSIGN^ assignmentExpression )? + ; + +variableDeclarationNoIn + : Identifier ( ASSIGN^ assignmentExpressionNoIn )? + ; + +// $> + +// $<Empty statement (12.3) + +emptyStatement + : SEMIC! + ; + +// $> + +// $<Expression statement (12.4) + +/* +The look ahead check on LBRACE and FUNCTION the specification mentions has been left out and its function, resolving the ambiguity between: +- functionExpression and functionDeclaration +- block and objectLiteral +are moved to the statement and sourceElement rules. +*/ +expressionStatement + : expression semic! + ; + +// $> + +// $<The if statement (12.5) + +ifStatement +// The predicate is there just to get rid of the warning. ANTLR will handle the dangling else just fine. + : IF LPAREN expression RPAREN statement ( { input.LA(1) == ELSE }? ELSE statement )? + -> ^( IF expression statement (ELSE statement)? ) + ; + +// $> + + +// $<Iteration statements (12.6) + +iterationStatement + : doStatement + | whileStatement + | forStatement + ; + +doStatement + : DO statement WHILE LPAREN expression RPAREN semic + -> ^( DO statement expression ) + ; + +whileStatement + : WHILE^ LPAREN! expression RPAREN! statement + ; + +/* +The forStatement production is refactored considerably as the specification contains a very none LL(*) compliant definition. +The initial version was like this: + +forStatement + : FOR^ LPAREN! forControl RPAREN! statement + ; +forControl +options +{ + backtrack = true ; + //k = 3 ; +} + : stepClause + | iterationClause + ; +stepClause +options +{ + memoize = true ; +} + : ( ex1=expressionNoIn | var=VAR variableDeclarationNoIn ( COMMA variableDeclarationNoIn )* )? SEMIC ex2=expression? SEMIC ex3=expression? + -> { $var != null }? ^( FORSTEP ^( VAR[$var] variableDeclarationNoIn+ ) ^( EXPR $ex2? ) ^( EXPR $ex3? ) ) + -> ^( FORSTEP ^( EXPR $ex1? ) ^( EXPR $ex2? ) ^( EXPR $ex3? ) ) + ; +iterationClause +options +{ + memoize = true ; +} + : ( leftHandSideExpression | var=VAR variableDeclarationNoIn ) IN expression + -> { $var != null }? ^( FORITER ^( VAR[$var] variableDeclarationNoIn ) ^( EXPR expression ) ) + -> ^( FORITER ^( EXPR leftHandSideExpression ) ^( EXPR expression ) ) + ; + +But this completely relies on the backtrack feature and capabilities of ANTLR. +Furthermore backtracking seemed to have 3 major drawbacks: +- the performance cost of backtracking is considerably +- didn't seem to work well with ANTLRWorks +- when introducing a k value to optimize the backtracking away, ANTLR runs out of heap space +*/ +forStatement + : FOR^ LPAREN! forControl RPAREN! statement + ; + +forControl + : forControlVar + | forControlExpression + | forControlSemic + ; + +forControlVar + : VAR variableDeclarationNoIn + ( + ( + IN expression + -> ^( FORITER ^( VAR variableDeclarationNoIn ) ^( EXPR expression ) ) + ) + | + ( + ( COMMA variableDeclarationNoIn )* SEMIC ex1=expression? SEMIC ex2=expression? + -> ^( FORSTEP ^( VAR variableDeclarationNoIn+ ) ^( EXPR $ex1? ) ^( EXPR $ex2? ) ) + ) + ) + ; + +forControlExpression +@init +{ + Object[] isLhs = new Object[1]; +} + : ex1=expressionNoIn + ( + { isLeftHandSideIn(ex1, isLhs) }? ( + IN ex2=expression + -> ^( FORITER ^( EXPR $ex1 ) ^( EXPR $ex2 ) ) + ) + | + ( + SEMIC ex2=expression? SEMIC ex3=expression? + -> ^( FORSTEP ^( EXPR $ex1 ) ^( EXPR $ex2? ) ^( EXPR $ex3? ) ) + ) + ) + ; + +forControlSemic + : SEMIC ex1=expression? SEMIC ex2=expression? + -> ^( FORSTEP ^( EXPR ) ^( EXPR $ex1? ) ^( EXPR $ex2? ) ) + ; + +// $> + +// $<The continue statement (12.7) + +/* +The action with the call to promoteEOL after CONTINUE is to enforce the semicolon insertion rule of the specification that there are +no line terminators allowed beween CONTINUE and the optional identifier. +As an optimization we check the la first to decide whether there is an identier following. +*/ +continueStatement + : CONTINUE^ { if (input.LA(1) == Identifier) promoteEOL(null); } Identifier? semic! + ; + +// $> + +// $<The break statement (12.8) + +/* +The action with the call to promoteEOL after BREAK is to enforce the semicolon insertion rule of the specification that there are +no line terminators allowed beween BREAK and the optional identifier. +As an optimization we check the la first to decide whether there is an identier following. +*/ +breakStatement + : BREAK^ { if (input.LA(1) == Identifier) promoteEOL(null); } Identifier? semic! + ; + +// $> + +// $<The return statement (12.9) + +/* +The action calling promoteEOL after RETURN ensures that there are no line terminators between RETURN and the optional expression as the specification states. +When there are these get promoted to on channel and thus virtual semicolon wannabees. +So the folowing code: + +return +1 + +will be parsed as: + +return; +1; +*/ +returnStatement + : RETURN^ { promoteEOL(null); } expression? semic! + ; + +// $> + +// $<The with statement (12.10) + +withStatement + : WITH^ LPAREN! expression RPAREN! statement + ; + +// $> + +// $<The switch statement (12.11) + +switchStatement +@init +{ + int defaultClauseCount = 0; +} + : SWITCH LPAREN expression RPAREN LBRACE ( { defaultClauseCount == 0 }?=> defaultClause { defaultClauseCount++; } | caseClause )* RBRACE + -> ^( SWITCH expression defaultClause? caseClause* ) + ; + +caseClause + : CASE^ expression COLON! statement* + ; + +defaultClause + : DEFAULT^ COLON! statement* + ; + +// $> + +// $<Labelled statements (12.12) + +labelledStatement + : Identifier COLON statement + -> ^( LABELLED Identifier statement ) + ; + +// $> + +// $<The throw statement (12.13) + +/* +The action calling promoteEOL after THROW ensures that there are no line terminators between THROW and the expression as the specification states. +When there are line terminators these get promoted to on channel and thus to virtual semicolon wannabees. +So the folowing code: + +throw +new Error() + +will be parsed as: + +throw; +new Error(); + +which will yield a recognition exception! +*/ +throwStatement + : THROW^ { promoteEOL(null); } expression semic! + ; + +// $> + +// $<The try statement (12.14) + +tryStatement + : TRY^ block ( catchClause finallyClause? | finallyClause ) + ; + +catchClause + : CATCH^ LPAREN! Identifier RPAREN! block + ; + +finallyClause + : FINALLY^ block + ; + +// $> + +// $> + +// +// $< A.5 Functions and Programs (13, 14) +// + +// $< Function Definition (13) + +functionDeclaration + : FUNCTION name=Identifier formalParameterList functionBody + -> ^( FUNCTION $name formalParameterList functionBody ) + ; + +functionExpression + : FUNCTION name=Identifier? formalParameterList functionBody + -> ^( FUNCTION $name? formalParameterList functionBody ) + ; + +formalParameterList + : LPAREN ( Identifier ( COMMA Identifier )* )? RPAREN + -> ^( ARGS Identifier* ) + ; + +functionBody + : lb=LBRACE sourceElement* RBRACE + -> ^( BLOCK[$lb, "BLOCK"] sourceElement* ) + ; + +// $> + +// $< Program (14) + +program + : sourceElement* + ; + +/* +By setting k to 1 for this rule and adding the semantical predicate ANTRL will generate code that will always prefer functionDeclararion over functionExpression +here and therefor remove the ambiguity between these to production. +This will result in the same behaviour that is described in the specification under 12.4 on the expressionStatement rule. +*/ +sourceElement +options +{ + k = 1 ; +} + : { input.LA(1) == FUNCTION }? functionDeclaration + | statement + | multiLineComment + ; + +// $> + +// $> diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/grammar/ES3Instrument.g b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/grammar/ES3Instrument.g new file mode 100644 index 000000000..c17e937c1 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/grammar/ES3Instrument.g @@ -0,0 +1,1612 @@ +/* + +Copyrights 2008-2009 Xebic Reasearch BV. All rights reserved (see license.txt). +Original work by Patrick Hulsmeijer. + +This ANTLR 3 LL(*) grammar is based on Ecma-262 3rd edition (JavaScript 1.5, JScript 5.5). +The annotations refer to the "A Grammar Summary" section (e.g. A.1 Lexical Grammar) and the numbers in parenthesis to the paragraph numbers (e.g. (7.8) ). +This document is best viewed with ANTLRWorks (www.antlr.org). + + +The major challenges faced in defining this grammar were: + +-1- Ambiguity surrounding the DIV sign in relation to the multiplicative expression and the regular expression literal. +This is solved with some lexer driven magic: a gated semantical predicate turns the recognition of regular expressions on or off, based on the +value of the RegularExpressionsEnabled property. When regular expressions are enabled they take precedence over division expressions. The decision whether +regular expressions are enabled is based on the heuristics that the previous token can be considered as last token of a left-hand-side operand of a division. + +-2- Automatic semicolon insertion. +This is solved within the parser. The semicolons are not physically inserted but the situations in which they should are recognized and treated as if they were. +The physical insertion of semicolons would be undesirable because of several reasons: +- performance degration because of how ANTLR handles tokens in token streams +- the alteration of the input, which we need to have unchanged +- it is superfluous being of no interest to AST construction + +-3- Unicode identifiers +Because ANTLR couldn't handle the unicode tables defined in the specification well and for performance reasons unicode identifiers are implemented as an action +driven alternative to ASCII identifiers. First the ASCII version is tried that is defined in detail in this grammar and then the unicode alternative is tried action driven. +Because of the fact that the ASCII version is defined in detail the mTokens switch generation in the lexer can predict identifiers appropriately. +For details see the identifier rules. + + +The minor challenges were related to converting the grammar to an ANTLR LL(*) grammar: +- Resolving the ambiguity between functionDeclaration vs functionExpression and block vs objectLiteral stemming from the expressionStatement production. +- Left recursive nature of the left hand side expressions. +- The assignmentExpression production. +- The forStatement production. +The grammar was kept as close as possible to the grammar in the "A Grammar Summary" section of Ecma-262. + +*/ +/* This file is from JsTestDriver but is not used in this project. + It is included for fulfillment of license obligations. */ + +grammar ES3Instrument ; + +options +{ + output = template ; + rewrite = true; + language = Java ; +} + +tokens +{ +// Reserved words + NULL = 'null' ; + TRUE = 'true' ; + FALSE = 'false' ; + +// Keywords + BREAK = 'break' ; + CASE = 'case' ; + CATCH = 'catch' ; + CONTINUE = 'continue' ; + DEFAULT = 'default' ; + DELETE = 'delete' ; + DO = 'do' ; + ELSE = 'else' ; + FINALLY = 'finally' ; + FOR = 'for' ; + FUNCTION = 'function' ; + IF = 'if' ; + IN = 'in' ; + INSTANCEOF = 'instanceof' ; + NEW = 'new' ; + RETURN = 'return' ; + SWITCH = 'switch' ; + THIS = 'this' ; + THROW = 'throw' ; + TRY = 'try' ; + TYPEOF = 'typeof' ; + VAR = 'var' ; + VOID = 'void' ; + WHILE = 'while' ; + WITH = 'with' ; + +// Future reserved words + ABSTRACT = 'abstract' ; + BOOLEAN = 'boolean' ; + BYTE = 'byte' ; + CHAR = 'char' ; + CLASS = 'class' ; + CONST = 'const' ; + DEBUGGER = 'debugger' ; + DOUBLE = 'double' ; + ENUM = 'enum' ; + EXPORT = 'export' ; + EXTENDS = 'extends' ; + FINAL = 'final' ; + FLOAT = 'float' ; + GOTO = 'goto' ; + IMPLEMENTS = 'implements' ; + IMPORT = 'import' ; + INT = 'int' ; + INTERFACE = 'interface' ; + LONG = 'long' ; + NATIVE = 'native' ; + PACKAGE = 'package' ; + PRIVATE = 'private' ; + PROTECTED = 'protected' ; + PUBLIC = 'public' ; + SHORT = 'short' ; + STATIC = 'static' ; + SUPER = 'super' ; + SYNCHRONIZED = 'synchronized' ; + THROWS = 'throws' ; + TRANSIENT = 'transient' ; + VOLATILE = 'volatile' ; + +// Punctuators + LBRACE = '{' ; + RBRACE = '}' ; + LPAREN = '(' ; + RPAREN = ')' ; + LBRACK = '[' ; + RBRACK = ']' ; + DOT = '.' ; + SEMIC = ';' ; + COMMA = ',' ; + LT = '<' ; + GT = '>' ; + LTE = '<=' ; + GTE = '>=' ; + EQ = '==' ; + NEQ = '!=' ; + SAME = '===' ; + NSAME = '!==' ; + ADD = '+' ; + SUB = '-' ; + MUL = '*' ; + MOD = '%' ; + INC = '++' ; + DEC = '--' ; + SHL = '<<' ; + SHR = '>>' ; + SHU = '>>>' ; + AND = '&' ; + OR = '|' ; + XOR = '^' ; + NOT = '!' ; + INV = '~' ; + LAND = '&&' ; + LOR = '||' ; + QUE = '?' ; + COLON = ':' ; + ASSIGN = '=' ; + ADDASS = '+=' ; + SUBASS = '-=' ; + MULASS = '*=' ; + MODASS = '%=' ; + SHLASS = '<<=' ; + SHRASS = '>>=' ; + SHUASS = '>>>=' ; + ANDASS = '&=' ; + ORASS = '|=' ; + XORASS = '^=' ; + DIV = '/' ; + DIVASS = '/=' ; + +// Imaginary + ARGS ; + ARRAY ; + BLOCK ; + BYFIELD ; + BYINDEX ; + CALL ; + CEXPR ; + EXPR ; + FORITER ; + FORSTEP ; + ITEM ; + LABELLED ; + NAMEDVALUE ; + NEG ; + OBJECT ; + PAREXPR ; + PDEC ; + PINC ; + POS ; +} + +@parser::header { + package com.google.jstestdriver.coverage.es3; + import org.antlr.runtime.tree.Tree; +} +@lexer::header { + package com.google.jstestdriver.coverage.es3; +} +@lexer::members +{ +private Token last; + +private final boolean areRegularExpressionsEnabled() +{ + if (last == null) + { + return true; + } + switch (last.getType()) + { + // identifier + case Identifier: + // literals + case NULL: + case TRUE: + case FALSE: + case THIS: + case OctalIntegerLiteral: + case DecimalLiteral: + case HexIntegerLiteral: + case StringLiteral: + // member access ending + case RBRACK: + // function call or nested expression ending + case RPAREN: + return false; + // otherwise OK + default: + return true; + } +} + +private final void consumeIdentifierUnicodeStart() throws RecognitionException, NoViableAltException +{ + int ch = input.LA(1); + if (isIdentifierStartUnicode(ch)) + { + matchAny(); + do + { + ch = input.LA(1); + if (ch == '$' || (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || ch == '\\' || ch == '_' || (ch >= 'a' && ch <= 'z') || isIdentifierPartUnicode(ch)) + { + mIdentifierPart(); + } + else + { + return; + } + } + while (true); + } + else + { + throw new NoViableAltException(); + } +} + +private final boolean isIdentifierPartUnicode(int ch) +{ + return Character.isJavaIdentifierPart(ch); +} + +private final boolean isIdentifierStartUnicode(int ch) +{ + return Character.isJavaIdentifierStart(ch); +} + +public Token nextToken() +{ + Token result = super.nextToken(); + if (result.getChannel() == Token.DEFAULT_CHANNEL) + { + last = result; + } + return result; +} +} + +@parser::members +{ +private final boolean isLeftHandSideAssign(RuleReturnScope lhs, Object[] cached) +{ + if (cached[0] != null) + { + return ((Boolean)cached[0]).booleanValue(); + } + + boolean result; + if (isLeftHandSideExpression(lhs)) + { + switch (input.LA(1)) + { + case ASSIGN: + case MULASS: + case DIVASS: + case MODASS: + case ADDASS: + case SUBASS: + case SHLASS: + case SHRASS: + case SHUASS: + case ANDASS: + case XORASS: + case ORASS: + result = true; + break; + default: + result = false; + break; + } + } + else + { + result = false; + } + + cached[0] = new Boolean(result); + return result; +} + +@SuppressWarnings("unused") +private final static String wrapInBraces(Token start, Token stop, TokenStream tokens) { + if (start == null || stop == null) { + return null; + } + if ("{".equals(start.getText())) { + return tokens.toString(start, stop); + } + return "{" + tokens.toString(start, stop) + "}"; +} + +private final static boolean isLeftHandSideExpression(RuleReturnScope lhs) +{ + if (lhs.getTree() == null) // e.g. during backtracking + { + return true; + } + else + { + switch (((Tree)lhs.getTree()).getType()) + { + // primaryExpression + case THIS: + case Identifier: + case NULL: + case TRUE: + case FALSE: + case DecimalLiteral: + case OctalIntegerLiteral: + case HexIntegerLiteral: + case StringLiteral: + case RegularExpressionLiteral: + case ARRAY: + case OBJECT: + case PAREXPR: + // functionExpression + case FUNCTION: + // newExpression + case NEW: + // leftHandSideExpression + case CALL: + case BYFIELD: + case BYINDEX: + return true; + + default: + return false; + } + } +} + +private final boolean isLeftHandSideIn(RuleReturnScope lhs, Object[] cached) +{ + if (cached[0] != null) + { + return ((Boolean)cached[0]).booleanValue(); + } + + boolean result = isLeftHandSideExpression(lhs) && (input.LA(1) == IN); + cached[0] = new Boolean(result); + return result; +} + +private final void promoteEOL(ParserRuleReturnScope rule) +{ + // Get current token and its type (the possibly offending token). + Token lt = input.LT(1); + int la = lt.getType(); + + // We only need to promote an EOL when the current token is offending (not a SEMIC, EOF, RBRACE, EOL or MultiLineComment). + // EOL and MultiLineComment are not offending as they're already promoted in a previous call to this method. + // Promoting an EOL means switching it from off channel to on channel. + // A MultiLineComment gets promoted when it contains an EOL. + if (!(la == SEMIC || la == EOF || la == RBRACE || la == EOL || la == MultiLineComment)) + { + // Start on the possition before the current token and scan backwards off channel tokens until the previous on channel token. + for (int ix = lt.getTokenIndex() - 1; ix > 0; ix--) + { + lt = input.get(ix); + if (lt.getChannel() == Token.DEFAULT_CHANNEL) + { + // On channel token found: stop scanning. + break; + } + else if (lt.getType() == EOL || (lt.getType() == MultiLineComment && lt.getText().matches("/.*\r\n|\r|\n"))) + { + // We found our EOL: promote the token to on channel, position the input on it and reset the rule start. + lt.setChannel(Token.DEFAULT_CHANNEL); + input.seek(lt.getTokenIndex()); + if (rule != null) + { + rule.start = lt; + } + break; + } + } + } +} +} + +// +// $< A.1 Lexical Grammar (7) +// + +// Added for lexing purposes + +fragment BSLASH + : '\\' + ; + +fragment DQUOTE + : '"' + ; + +fragment SQUOTE + : '\'' + ; + +// $< Whitespace (7.2) + +fragment TAB + : '\u0009' + ; + +fragment VT // Vertical TAB + : '\u000b' + ; + +fragment FF // Form Feed + : '\u000c' + ; + +fragment SP // Space + : '\u0020' + ; + +fragment NBSP // Non-Breaking Space + : '\u00a0' + ; + +fragment USP // Unicode Space Separator (rest of Unicode category Zs) + : '\u1680' // OGHAM SPACE MARK + | '\u180E' // MONGOLIAN VOWEL SEPARATOR + | '\u2000' // EN QUAD + | '\u2001' // EM QUAD + | '\u2002' // EN SPACE + | '\u2003' // EM SPACE + | '\u2004' // THREE-PER-EM SPACE + | '\u2005' // FOUR-PER-EM SPACE + | '\u2006' // SIX-PER-EM SPACE + | '\u2007' // FIGURE SPACE + | '\u2008' // PUNCTUATION SPACE + | '\u2009' // THIN SPACE + | '\u200A' // HAIR SPACE + | '\u202F' // NARROW NO-BREAK SPACE + | '\u205F' // MEDIUM MATHEMATICAL SPACE + | '\u3000' // IDEOGRAPHIC SPACE + ; + +WhiteSpace + : ( TAB | VT | FF | SP | NBSP | USP )+ { $channel = HIDDEN; } + ; + +// $> + +// $< Line terminators (7.3) + +fragment LF // Line Feed + : '\n' + ; + +fragment CR // Carriage Return + : '\r' + ; + +fragment LS // Line Separator + : '\u2028' + ; + +fragment PS // Paragraph Separator + : '\u2029' + ; + +fragment LineTerminator + : CR | LF | LS | PS + ; + +EOL + : ( ( CR LF? ) | LF | LS | PS ) { $channel = HIDDEN; } + ; +// $> + +// $< Comments (7.4) + +MultiLineComment + : '/*' ( options { greedy = false; } : . )* '*/' { $channel = HIDDEN; } + ; + +SingleLineComment + : '//' ( ~( LineTerminator ) )* { $channel = HIDDEN; } + ; + +// $> + +// $< Tokens (7.5) + +token + : reservedWord + | Identifier + | punctuator + | numericLiteral + | StringLiteral + ; + +// $< Reserved words (7.5.1) + +reservedWord + : keyword + | futureReservedWord + | NULL + | booleanLiteral + ; + +// $> + +// $< Keywords (7.5.2) + +keyword + : BREAK + | CASE + | CATCH + | CONTINUE + | DEFAULT + | DELETE + | DO + | ELSE + | FINALLY + | FOR + | FUNCTION + | IF + | IN + | INSTANCEOF + | NEW + | RETURN + | SWITCH + | THIS + | THROW + | TRY + | TYPEOF + | VAR + | VOID + | WHILE + | WITH + ; + +// $> + +// $< Future reserved words (7.5.3) + +futureReservedWord + : ABSTRACT + | BOOLEAN + | BYTE + | CHAR + | CLASS + | CONST + | DEBUGGER + | DOUBLE + | ENUM + | EXPORT + | EXTENDS + | FINAL + | FLOAT + | GOTO + | IMPLEMENTS + | IMPORT + | INT + | INTERFACE + | LONG + | NATIVE + | PACKAGE + | PRIVATE + | PROTECTED + | PUBLIC + | SHORT + | STATIC + | SUPER + | SYNCHRONIZED + | THROWS + | TRANSIENT + | VOLATILE + ; + +// $> + +// $> + +// $< Identifiers (7.6) + +fragment IdentifierStartASCII + : 'a'..'z' | 'A'..'Z' + | '$' + | '_' + | BSLASH 'u' HexDigit HexDigit HexDigit HexDigit // UnicodeEscapeSequence + ; + +/* +The first two alternatives define how ANTLR can match ASCII characters which can be considered as part of an identifier. +The last alternative matches other characters in the unicode range that can be sonsidered as part of an identifier. +*/ +fragment IdentifierPart + : DecimalDigit + | IdentifierStartASCII + | { isIdentifierPartUnicode(input.LA(1)) }? { matchAny(); } + ; + +fragment IdentifierNameASCIIStart + : IdentifierStartASCII IdentifierPart* + ; + +/* +The second alternative acts as an action driven fallback to evaluate other characters in the unicode range than the ones in the ASCII subset. +Due to the first alternative this grammar defines enough so that ANTLR can generate a lexer that correctly predicts identifiers with characters in the ASCII range. +In that way keywords, other reserved words and ASCII identifiers are recognized with standard ANTLR driven logic. When the first character for an identifier fails to +match this ASCII definition, the lexer calls consumeIdentifierUnicodeStart because of the action in the alternative. This method checks whether the character matches +as first character in ranges other than ASCII and consumes further characters belonging to the identifier with help of mIdentifierPart generated out of the +IdentifierPart rule above. +*/ +Identifier + : IdentifierNameASCIIStart + | { consumeIdentifierUnicodeStart(); } + ; + +// $> + +// $< Punctuators (7.7) + +punctuator + : LBRACE + | RBRACE + | LPAREN + | RPAREN + | LBRACK + | RBRACK + | DOT + | SEMIC + | COMMA + | LT + | GT + | LTE + | GTE + | EQ + | NEQ + | SAME + | NSAME + | ADD + | SUB + | MUL + | MOD + | INC + | DEC + | SHL + | SHR + | SHU + | AND + | OR + | XOR + | NOT + | INV + | LAND + | LOR + | QUE + | COLON + | ASSIGN + | ADDASS + | SUBASS + | MULASS + | MODASS + | SHLASS + | SHRASS + | SHUASS + | ANDASS + | ORASS + | XORASS + | DIV + | DIVASS + ; + +// $> + +// $< Literals (7.8) + +literal + : NULL + | booleanLiteral + | numericLiteral + | StringLiteral + | RegularExpressionLiteral + ; + +booleanLiteral + : TRUE + | FALSE + ; + +// $< Numeric literals (7.8.3) + +/* +Note: octal literals are described in the B Compatibility section. +These are removed from the standards but are here for backwards compatibility with earlier ECMAScript definitions. +*/ + +fragment DecimalDigit + : '0'..'9' + ; + +fragment HexDigit + : DecimalDigit | 'a'..'f' | 'A'..'F' + ; + +fragment OctalDigit + : '0'..'7' + ; + +fragment ExponentPart + : ( 'e' | 'E' ) ( '+' | '-' )? DecimalDigit+ + ; + +fragment DecimalIntegerLiteral + : '0' + | '1'..'9' DecimalDigit* + ; + +DecimalLiteral + : DecimalIntegerLiteral '.' DecimalDigit* ExponentPart? + | '.' DecimalDigit+ ExponentPart? + | DecimalIntegerLiteral ExponentPart? + ; + +OctalIntegerLiteral + : '0' OctalDigit+ + ; + +HexIntegerLiteral + : ( '0x' | '0X' ) HexDigit+ + ; + +numericLiteral + : DecimalLiteral + | OctalIntegerLiteral + | HexIntegerLiteral + ; + +// $> + +// $< String literals (7.8.4) + +/* +Note: octal escape sequences are described in the B Compatibility section. +These are removed from the standards but are here for backwards compatibility with earlier ECMAScript definitions. +*/ + +fragment CharacterEscapeSequence + : ~( DecimalDigit | 'x' | 'u' | LineTerminator ) // Concatenation of SingleEscapeCharacter and NonEscapeCharacter + ; + +fragment ZeroToThree + : '0'..'3' + ; + +fragment OctalEscapeSequence + : OctalDigit + | ZeroToThree OctalDigit + | '4'..'7' OctalDigit + | ZeroToThree OctalDigit OctalDigit + ; + +fragment HexEscapeSequence + : 'x' HexDigit HexDigit + ; + +fragment UnicodeEscapeSequence + : 'u' HexDigit HexDigit HexDigit HexDigit + ; + +fragment EscapeSequence + : + BSLASH + ( + CharacterEscapeSequence + | OctalEscapeSequence + | HexEscapeSequence + | UnicodeEscapeSequence + ) + ; + +StringLiteral + : SQUOTE ( ~( SQUOTE | BSLASH | LineTerminator ) | EscapeSequence )* SQUOTE + | DQUOTE ( ~( DQUOTE | BSLASH | LineTerminator ) | EscapeSequence )* DQUOTE + ; + +// $> + +// $< Regular expression literals (7.8.5) + +fragment BackslashSequence + : BSLASH ~( LineTerminator ) + ; + +fragment RegularExpressionFirstChar + : ~ ( LineTerminator | MUL | BSLASH | DIV ) + | BackslashSequence + ; + +fragment RegularExpressionChar + : ~ ( LineTerminator | BSLASH | DIV ) + | BackslashSequence + ; + +RegularExpressionLiteral + : { areRegularExpressionsEnabled() }?=> DIV RegularExpressionFirstChar RegularExpressionChar* DIV IdentifierPart* + ; + +// $> + +// $> + +// $> + +// +// $< A.3 Expressions (11) +// + +// $<Primary expressions (11.1) + +primaryExpression + : THIS + | Identifier + | literal + | arrayLiteral + | objectLiteral + | lpar=LPAREN expression RPAREN //-> ^( PAREXPR[$lpar, "PAREXPR"] expression ) + ; + +arrayLiteral + : lb=LBRACK ( arrayItem ( COMMA arrayItem )* )? RBRACK + //-> ^( ARRAY[$lb, "ARRAY"] arrayItem* ) + ; + +arrayItem + : ( expr=assignmentExpression | { input.LA(1) == COMMA }? ) + //-> ^( ITEM $expr? ) + ; + +objectLiteral + : lb=LBRACE ( nameValuePair ( COMMA nameValuePair )* )? RBRACE + //-> ^( OBJECT[$lb, "OBJECT"] nameValuePair* ) + ; + +nameValuePair + : propertyName COLON assignmentExpression + //-> ^( NAMEDVALUE propertyName assignmentExpression ) + ; + +propertyName + : Identifier + | StringLiteral + | numericLiteral + ; + +// $> + +// $<Left-hand-side expressions (11.2) + +/* +Refactored some rules to make them LL(*) compliant: +all the expressions surrounding member selection and calls have been moved to leftHandSideExpression to make them right recursive +*/ + +memberExpression + : primaryExpression + | functionExpression + | newExpression + ; + +newExpression + : NEW primaryExpression + ; + + +arguments + : LPAREN ( assignmentExpression ( COMMA assignmentExpression )* )? RPAREN + //-> ^( ARGS assignmentExpression* ) + ; + +leftHandSideExpression + : + ( + memberExpression //-> memberExpression + ) + ( + arguments //-> ^( CALL $leftHandSideExpression arguments ) + | LBRACK expression RBRACK //-> ^( BYINDEX $leftHandSideExpression expression ) + | DOT Identifier //-> ^( BYFIELD $leftHandSideExpression Identifier ) + )* + ; + +// $> + +// $<Postfix expressions (11.3) + +/* +The specification states that there are no line terminators allowed before the postfix operators. +This is enforced by the call to promoteEOL in the action before ( INC | DEC ). +We only must promote EOLs when the la is INC or DEC because this production is chained as all expression rules. +In other words: only promote EOL when we are really in a postfix expression. A check on the la will ensure this. +*/ +postfixExpression + : leftHandSideExpression { if (input.LA(1) == INC || input.LA(1) == DEC) promoteEOL(null); } ( postfixOperator )? + ; + +postfixOperator + : op=INC { $op.setType(PINC); } + | op=DEC { $op.setType(PDEC); } + ; + +// $> + +// $<Unary operators (11.4) + +unaryExpression + : postfixExpression + | unaryOperator unaryExpression + ; + +unaryOperator + : DELETE + | VOID + | TYPEOF + | INC + | DEC + | op=ADD { $op.setType(POS); } + | op=SUB { $op.setType(NEG); } + | INV + | NOT + ; + +// $> + +// $<Multiplicative operators (11.5) + +multiplicativeExpression + : unaryExpression ( ( MUL | DIV | MOD ) unaryExpression )* + ; + +// $> + +// $<Additive operators (11.6) + +additiveExpression + : multiplicativeExpression ( ( ADD | SUB ) multiplicativeExpression )* + ; + +// $> + +// $<Bitwise shift operators (11.7) + +shiftExpression + : additiveExpression ( ( SHL | SHR | SHU ) additiveExpression )* + ; + +// $> + +// $<Relational operators (11.8) + +relationalExpression + : shiftExpression ( ( LT | GT | LTE | GTE | INSTANCEOF | IN ) shiftExpression )* + ; + +relationalExpressionNoIn + : shiftExpression ( ( LT | GT | LTE | GTE | INSTANCEOF ) shiftExpression )* + ; + +// $> + +// $<Equality operators (11.9) + +equalityExpression + : relationalExpression ( ( EQ | NEQ | SAME | NSAME ) relationalExpression )* + ; + +equalityExpressionNoIn + : relationalExpressionNoIn ( ( EQ | NEQ | SAME | NSAME ) relationalExpressionNoIn )* + ; + +// $> + +// $<Binary bitwise operators (11.10) + +bitwiseANDExpression + : equalityExpression ( AND equalityExpression )* + ; + +bitwiseANDExpressionNoIn + : equalityExpressionNoIn ( AND equalityExpressionNoIn )* + ; + +bitwiseXORExpression + : bitwiseANDExpression ( XOR bitwiseANDExpression )* + ; + +bitwiseXORExpressionNoIn + : bitwiseANDExpressionNoIn ( XOR bitwiseANDExpressionNoIn )* + ; + +bitwiseORExpression + : bitwiseXORExpression ( OR bitwiseXORExpression )* + ; + +bitwiseORExpressionNoIn + : bitwiseXORExpressionNoIn ( OR bitwiseXORExpressionNoIn )* + ; + +// $> + +// $<Binary logical operators (11.11) + +logicalANDExpression + : bitwiseORExpression ( LAND bitwiseORExpression )* + ; + +logicalANDExpressionNoIn + : bitwiseORExpressionNoIn ( LAND bitwiseORExpressionNoIn )* + ; + +logicalORExpression + : logicalANDExpression ( LOR logicalANDExpression )* + ; + +logicalORExpressionNoIn + : logicalANDExpressionNoIn ( LOR logicalANDExpressionNoIn )* + ; + +// $> + +// $<Conditional operator (11.12) + +conditionalExpression + : logicalORExpression ( QUE assignmentExpression COLON assignmentExpression )? + ; + +conditionalExpressionNoIn + : logicalORExpressionNoIn ( QUE assignmentExpressionNoIn COLON assignmentExpressionNoIn )? + ; + +// $> + +// $<Assignment operators (11.13) + +/* +The specification defines the AssignmentExpression rule as follows: +AssignmentExpression : + ConditionalExpression + LeftHandSideExpression AssignmentOperator AssignmentExpression +This rule has a LL(*) conflict. Resolving this with a syntactical predicate will yield something like this: + +assignmentExpression + : ( leftHandSideExpression assignmentOperator )=> leftHandSideExpression assignmentOperator^ assignmentExpression + | conditionalExpression + ; +assignmentOperator + : ASSIGN | MULASS | DIVASS | MODASS | ADDASS | SUBASS | SHLASS | SHRASS | SHUASS | ANDASS | XORASS | ORASS + ; + +But that didn't seem to work. Terence Par writes in his book that LL(*) conflicts in general can best be solved with auto backtracking. But that would be +a performance killer for such a heavy used rule. +The solution I came up with is to always invoke the conditionalExpression first and than decide what to do based on the result of that rule. +When the rule results in a Tree that can't be coming from a left hand side expression, then we're done. +When it results in a Tree that is coming from a left hand side expression and the LA(1) is an assignment operator then parse the assignment operator +followed by the right recursive call. +*/ +assignmentExpression +@init +{ + Object[] isLhs = new Object[1]; +} + : lhs=conditionalExpression + ( { isLeftHandSideAssign(lhs, isLhs) }? assignmentOperator assignmentExpression )? + ; + +assignmentOperator + : ASSIGN | MULASS | DIVASS | MODASS | ADDASS | SUBASS | SHLASS | SHRASS | SHUASS | ANDASS | XORASS | ORASS + ; + +assignmentExpressionNoIn +@init +{ + Object[] isLhs = new Object[1]; +} + : lhs=conditionalExpressionNoIn + ( { isLeftHandSideAssign(lhs, isLhs) }? assignmentOperator assignmentExpressionNoIn )? + ; + +// $> + +// $<Comma operator (11.14) + +expression + : exprs+=assignmentExpression ( COMMA exprs+=assignmentExpression )* + //-> { $exprs.size() > 1 }? ^( CEXPR $exprs+ ) + //-> $exprs + ; + +expressionNoIn + : exprs+=assignmentExpressionNoIn ( COMMA exprs+=assignmentExpressionNoIn )* + //-> { $exprs.size() > 1 }? ^( CEXPR $exprs+ ) + //-> $exprs + ; + +// $> + +// $> + +// +// $< A.4 Statements (12) +// + +/* +This rule handles semicolons reported by the lexer and situations where the ECMA 3 specification states there should be semicolons automaticly inserted. +The auto semicolons are not actually inserted but this rule behaves as if they were. + +In the following situations an ECMA 3 parser should auto insert absent but grammaticly required semicolons: +- the current token is a right brace +- the current token is the end of file (EOF) token +- there is at least one end of line (EOL) token between the current token and the previous token. + +The RBRACE is handled by matching it but not consuming it. +The EOF needs no further handling because it is not consumed by default. +The EOL situation is handled by promoting the EOL or MultiLineComment with an EOL present from off channel to on channel +and thus making it parseable instead of handling it as white space. This promoting is done in the action promoteEOL. +*/ +semic +@init +{ + // Mark current position so we can unconsume a RBRACE. + int marker = input.mark(); + // Promote EOL if appropriate + promoteEOL(retval); +} + : SEMIC + | EOF + | RBRACE { input.rewind(marker); } + | EOL | MultiLineComment // (with EOL in it) + ; + +/* +To solve the ambiguity between block and objectLiteral via expressionStatement all but the block alternatives have been moved to statementTail. +Now when k = 1 and a semantical predicate is defined ANTLR generates code that always will prefer block when the LA(1) is a LBRACE. +This will result in the same behaviour that is described in the specification under 12.4 on the expressionStatement rule. +*/ +statement +options +{ + k = 1 ; +} +scope { + boolean isBlock; +} +@init{ + boolean instrument = false; + + if ($start.getLine() > $program::stopLine) { + $program::stopLine = $start.getLine(); + instrument = true; + } +} +@after { + if (instrument && !$statement::isBlock) { + $program::executableLines.add($start.getLine()); + } +} + : ({ $statement::isBlock = input.LA(1) == LBRACE }? block | statementTail) + -> {instrument && !$statement::isBlock}? instrument(stmt={$text}, hash = {$program::hash}, ln = {$start.getLine()}) + -> pass(stmt={$text}) + ; + +statementTail + : variableStatement + | emptyStatement + | expressionStatement + | ifStatement + | iterationStatement + | continueStatement + | breakStatement + | returnStatement + | withStatement + | labelledStatement + | switchStatement + | throwStatement + | tryStatement + ; + +// $<Block (12.1) + +block + : lb=LBRACE statement* RBRACE + //-> ^( BLOCK[$lb, "BLOCK"] statement* ) + ; + +// $> + +// $<Variable statement 12.2) + +variableStatement + : VAR variableDeclaration ( COMMA variableDeclaration )* semic + //-> ^( VAR variableDeclaration+ ) + ; + +variableDeclaration + : Identifier ( ASSIGN assignmentExpression )? + ; + +variableDeclarationNoIn + : Identifier ( ASSIGN assignmentExpressionNoIn )? + ; + +// $> + +// $<Empty statement (12.3) + +emptyStatement + : SEMIC + ; + +// $> + +// $<Expression statement (12.4) + +/* +The look ahead check on LBRACE and FUNCTION the specification mentions has been left out and its function, resolving the ambiguity between: +- functionExpression and functionDeclarationstatement +- block and objectLiteral +are moved to the statement and sourceElement rules. +*/ +expressionStatement + : expression semic + ; + +// $> + +// $<The if statement (12.5) + + +ifStatement +// The predicate is there just to get rid of the warning. ANTLR will handle the dangling else just fine. + : IF LPAREN expression RPAREN statement ( { input.LA(1) == ELSE }? elseStatement)? + //push the block wrap to the statement? + -> template(p = {input.toString($start.getTokenIndex(), $statement.start.getTokenIndex() - 1)}, + body = {wrapInBraces($statement.start, $statement.stop, input)}, + elseClause = { + $elseStatement.stop != null ? input.toString($statement.stop.getTokenIndex()+1, $elseStatement.stop.getTokenIndex() ) : null}) "<p><body><elseClause>" + ; + +elseStatement + : ELSE statement + -> template(prefix = {input.toString($start.getTokenIndex(), $statement.start.getTokenIndex() - 1)}, stmt = {wrapInBraces($statement.start, $statement.stop, input)}) "<prefix><stmt>" + ; + +// $> + +// $<Iteration statements (12.6) + +iterationStatement + : doStatement + | whileStatement + | forStatement + ; + +doStatement + : DO statement WHILE LPAREN expression RPAREN semic + -> template(pre = {input.toString($start.getTokenIndex(), $statement.start.getTokenIndex() - 1)}, + stmt = {wrapInBraces($statement.start, $statement.stop, input)}, + post = {input.toString($WHILE, $RPAREN)}, + end = {$semic.text}) "<pre><stmt><post><end>" + ; + +whileStatement + : WHILE LPAREN expression RPAREN statement + -> template(pre = {input.toString($start.getTokenIndex(), $statement.start.getTokenIndex() - 1)}, + stmt = {wrapInBraces($statement.start, $statement.stop, input)} + ) "<pre><stmt>" + ; + +/* +The forStatement production is refactored considerably as the specification contains a very none LL(*) compliant definition. +The initial version was like this: + +forStatement + : FOR^ LPAREN! forControl RPAREN! statement + ; +forControl +options +{ + backtrack = true ; + //k = 3 ; +} + : stepClause + | iterationClause + ; +stepClause +options +{ + memoize = true ; +} + : ( ex1=expressionNoIn | var=VAR variableDeclarationNoIn ( COMMA variableDeclarationNoIn )* )? SEMIC ex2=expression? SEMIC ex3=expression? + -> { $var != null }? ^( FORSTEP ^( VAR[$var] variableDeclarationNoIn+ ) ^( EXPR $ex2? ) ^( EXPR $ex3? ) ) + -> ^( FORSTEP ^( EXPR $ex1? ) ^( EXPR $ex2? ) ^( EXPR $ex3? ) ) + ; +iterationClause +options +{ + memoize = true ; +} + : ( leftHandSideExpression | var=VAR variableDeclarationNoIn ) IN expression + -> { $var != null }? ^( FORITER ^( VAR[$var] variableDeclarationNoIn ) ^( EXPR expression ) ) + -> ^( FORITER ^( EXPR leftHandSideExpression ) ^( EXPR expression ) ) + ; + +But this completely relies on the backtrack feature and capabilities of ANTLR. +Furthermore backtracking seemed to have 3 major drawbacks: +- the performance cost of backtracking is considerably +- didn't seem to work well with ANTLRWorks +- when introducing a k value to optimize the backtracking away, ANTLR runs out of heap space +*/ +forStatement + : FOR LPAREN forControl RPAREN statement + -> template(pre = {input.toString($start.getTokenIndex(), $statement.start.getTokenIndex() - 1)}, + stmt = {wrapInBraces($statement.start, $statement.stop, input)} + ) "<pre><stmt>" + ; + +forControl + : forControlVar + | forControlExpression + | forControlSemic + ; + +forControlVar + : VAR variableDeclarationNoIn + ( + ( + IN expression + //-> ^( FORITER ^( VAR variableDeclarationNoIn ) ^( EXPR expression ) ) + ) + | + ( + ( COMMA variableDeclarationNoIn )* SEMIC ex1=expression? SEMIC ex2=expression? + //-> ^( FORSTEP ^( VAR variableDeclarationNoIn+ ) ^( EXPR $ex1? ) ^( EXPR $ex2? ) ) + ) + ) + ; + +forControlExpression +@init +{ + Object[] isLhs = new Object[1]; +} + : ex1=expressionNoIn + ( + { isLeftHandSideIn(ex1, isLhs) }? ( + IN ex2=expression + //-> ^( FORITER ^( EXPR $ex1 ) ^( EXPR $ex2 ) ) + ) + | + ( + SEMIC ex2=expression? SEMIC ex3=expression? + //-> ^( FORSTEP ^( EXPR $ex1 ) ^( EXPR $ex2? ) ^( EXPR $ex3? ) ) + ) + ) + ; + +forControlSemic + : SEMIC ex1=expression? SEMIC ex2=expression? + //-> ^( FORSTEP ^( EXPR ) ^( EXPR $ex1? ) ^( EXPR $ex2? ) ) + ; + +// $> + +// $<The continue statement (12.7) + +/* +The action with the call to promoteEOL after CONTINUE is to enforce the semicolon insertion rule of the specification that there are +no line terminators allowed beween CONTINUE and the optional identifier. +As an optimization we check the la first to decide whether there is an identier following. +*/ +continueStatement + : CONTINUE { if (input.LA(1) == Identifier) promoteEOL(null); } Identifier? semic + ; + +// $> + +// $<The break statement (12.8) + +/* +The action with the call to promoteEOL after BREAK is to enforce the semicolon insertion rule of the specification that there are +no line terminators allowed beween BREAK and the optional identifier. +As an optimization we check the la first to decide whether there is an identier following. +*/ +breakStatement + : BREAK { if (input.LA(1) == Identifier) promoteEOL(null); } Identifier? semic + ; + +// $> + +// $<The return statement (12.9) + +/* +The action calling promoteEOL after RETURN ensures that there are no line terminators between RETURN and the optional expression as the specification states. +When there are these get promoted to on channel and thus virtual semicolon wannabees. +So the folowing code: + +return +1 + +will be parsed as: + +return; +1; +*/ +returnStatement + : RETURN { promoteEOL(null); } expression? semic + ; + +// $> + +// $<The with statement (12.10) + +withStatement + : WITH LPAREN expression RPAREN statement + -> template(pre = {input.toString($start.getTokenIndex(), $statement.start.getTokenIndex() - 1)}, + stmt = {wrapInBraces($statement.start, $statement.stop, input)} + ) "<pre><stmt>" + ; + +// $> + +// $<The switch statement (12.11) + +switchStatement +@init +{ + int defaultClauseCount = 0; +} + : SWITCH LPAREN expression RPAREN LBRACE ( { defaultClauseCount == 0 }?=> defaultClause { defaultClauseCount++; } | caseClause )* RBRACE + //-> ^( SWITCH expression defaultClause? caseClause* ) + ; + +caseClause + : CASE expression COLON statement* + ; + +defaultClause + : DEFAULT COLON statement* + ; + +// $> + +// $<Labelled statements (12.12) + +labelledStatement + : Identifier COLON statement + //-> ^( LABELLED Identifier statement ) + ; + +// $> + +// $<The throw statement (12.13) + +/* +The action calling promoteEOL after THROW ensures that there are no line terminators between THROW and the expression as the specification states. +When there are line terminators these get promoted to on channel and thus to virtual semicolon wannabees. +So the folowing code: + +throw +new Error() + +will be parsed as: + +throw; +new Error(); + +which will yield a recognition exception! +*/ +throwStatement + : THROW { promoteEOL(null); } expression semic + ; + +// $> + +// $<The try statement (12.14) + +tryStatement + : TRY block ( catchClause finallyClause? | finallyClause ) + ; + +catchClause + : CATCH LPAREN Identifier RPAREN block + ; + +finallyClause + : FINALLY block + ; + +// $> + +// $> + +// +// $< A.5 Functions and Programs (13, 14) +// + +// $< Function Definition (13) + + +functionDeclaration +@init{ + boolean instrument = false; + if ($start.getLine() > $program::stopLine) { + $program::executableLines.add($start.getLine()); + $program::stopLine = $start.getLine(); + instrument = true; + } +} + : FUNCTION name=Identifier formalParameterList functionBody + -> {instrument}? instrument(stmt={$text}, ln={$start.getLine()}, hash = {$program::hash}) + -> pass(stmt={$text}) + ; + +functionExpression + : FUNCTION name=Identifier? formalParameterList functionBody + // -> ( FUNCTION $name? formalParameterList functionBody ) + ; + +formalParameterList + : LPAREN ( Identifier ( COMMA Identifier )* )? RPAREN + //-> ^( ARGS Identifier* ) + ; + +functionBody + : lb=LBRACE sourceElement* RBRACE + //-> ^( BLOCK[$lb, "BLOCK"] sourceElement* ) + ; + +// $> + +// $< Program (14) + +program +scope { + String name; + String hash; + java.util.List<Integer> executableLines; + int stopLine; +} +@init { + String name = getSourceName(); + $program::name = name; + $program::hash = Integer.toString(Math.abs(name.hashCode()), Character.MAX_RADIX); + $program::executableLines = new java.util.LinkedList(); + $program::stopLine = 0; +} + : (sourceElement*) {java.util.Collections.sort($program::executableLines);} + -> init_instrument(stmt = {$text}, hash = {$program::hash}, name = {name}, lines = {$program::executableLines.toString()}) + ; + +/* +By setting k to 1 for this rule and adding the semantical predicate ANTRL will generate code that will always prefer functionDeclararion over functionExpression +here and therefor remove the ambiguity between these to production. +This will result in the same behaviour that is described in the specification under 12.4 on the expressionStatement rule. +*/ +sourceElement +options +{ + k = 1 ; +} + : { input.LA(1) == FUNCTION }? functionDeclaration + | statement + ; + +// $> + +// $> diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/grammar/ES3Instrument_license.txt b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/grammar/ES3Instrument_license.txt new file mode 100644 index 000000000..1914f856a --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/grammar/ES3Instrument_license.txt @@ -0,0 +1,55 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + 1. You must give any other recipients of the Work or Derivative Works a copy of this License; and + + 2. You must cause any modified files to carry prominent notices stating that You changed the files; and + + 3. You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + 4. If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + +You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/grammar/ES3YUITest.g b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/grammar/ES3YUITest.g new file mode 100644 index 000000000..94348067e --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/grammar/ES3YUITest.g @@ -0,0 +1,1751 @@ +/* + +Copyrights 2008-2009 Xebic Reasearch BV. All rights reserved (see license.txt). +Original work by Patrick Hulsmeijer. + +This ANTLR 3 LL(*) grammar is based on Ecma-262 3rd edition (JavaScript 1.5, JScript 5.5). +The annotations refer to the "A Grammar Summary" section (e.g. A.1 Lexical Grammar) and the numbers in parenthesis to the paragraph numbers (e.g. (7.8) ). +This document is best viewed with ANTLRWorks (www.antlr.org). + + +The major challenges faced in defining this grammar were: + +-1- Ambiguity surrounding the DIV sign in relation to the multiplicative expression and the regular expression literal. +This is solved with some lexer driven magic: a gated semantical predicate turns the recognition of regular expressions on or off, based on the +value of the RegularExpressionsEnabled property. When regular expressions are enabled they take precedence over division expressions. The decision whether +regular expressions are enabled is based on the heuristics that the previous token can be considered as last token of a left-hand-side operand of a division. + +-2- Automatic semicolon insertion. +This is solved within the parser. The semicolons are not physically inserted but the situations in which they should are recognized and treated as if they were. +The physical insertion of semicolons would be undesirable because of several reasons: +- performance degration because of how ANTLR handles tokens in token streams +- the alteration of the input, which we need to have unchanged +- it is superfluous being of no interest to AST construction + +-3- Unicode identifiers +Because ANTLR couldn't handle the unicode tables defined in the specification well and for performance reasons unicode identifiers are implemented as an action +driven alternative to ASCII identifiers. First the ASCII version is tried that is defined in detail in this grammar and then the unicode alternative is tried action driven. +Because of the fact that the ASCII version is defined in detail the mTokens switch generation in the lexer can predict identifiers appropriately. +For details see the identifier rules. + + +The minor challenges were related to converting the grammar to an ANTLR LL(*) grammar: +- Resolving the ambiguity between functionDeclaration vs functionExpression and block vs objectLiteral stemming from the expressionStatement production. +- Left recursive nature of the left hand side expressions. +- The assignmentExpression production. +- The forStatement production. +The grammar was kept as close as possible to the grammar in the "A Grammar Summary" section of Ecma-262. + +*/ +/* +This file is based on ES3Instrument.g, which is part of JsTestDriver. +The code in this file has been modified from the original for the purposes +of this project. +*/ + +grammar ES3YUITest ; + +options +{ + output = template ; + rewrite = true; + language = Java ; +} + +tokens +{ +// Reserved words + NULL = 'null' ; + TRUE = 'true' ; + FALSE = 'false' ; + +// Keywords + BREAK = 'break' ; + CASE = 'case' ; + CATCH = 'catch' ; + CONTINUE = 'continue' ; + DEFAULT = 'default' ; + DELETE = 'delete' ; + DO = 'do' ; + ELSE = 'else' ; + FINALLY = 'finally' ; + FOR = 'for' ; + FUNCTION = 'function' ; + IF = 'if' ; + IN = 'in' ; + INSTANCEOF = 'instanceof' ; + NEW = 'new' ; + RETURN = 'return' ; + SWITCH = 'switch' ; + THIS = 'this' ; + THROW = 'throw' ; + TRY = 'try' ; + TYPEOF = 'typeof' ; + VAR = 'var' ; + VOID = 'void' ; + WHILE = 'while' ; + WITH = 'with' ; + +// Future reserved words + ABSTRACT = 'abstract' ; + BOOLEAN = 'boolean' ; + BYTE = 'byte' ; + CHAR = 'char' ; + CLASS = 'class' ; + CONST = 'const' ; + DEBUGGER = 'debugger' ; + DOUBLE = 'double' ; + ENUM = 'enum' ; + EXPORT = 'export' ; + EXTENDS = 'extends' ; + FINAL = 'final' ; + FLOAT = 'float' ; + GOTO = 'goto' ; + IMPLEMENTS = 'implements' ; + IMPORT = 'import' ; + INT = 'int' ; + INTERFACE = 'interface' ; + LONG = 'long' ; + NATIVE = 'native' ; + PACKAGE = 'package' ; + PRIVATE = 'private' ; + PROTECTED = 'protected' ; + PUBLIC = 'public' ; + SHORT = 'short' ; + STATIC = 'static' ; + SUPER = 'super' ; + SYNCHRONIZED = 'synchronized' ; + THROWS = 'throws' ; + TRANSIENT = 'transient' ; + VOLATILE = 'volatile' ; + +// Punctuators + LBRACE = '{' ; + RBRACE = '}' ; + LPAREN = '(' ; + RPAREN = ')' ; + LBRACK = '[' ; + RBRACK = ']' ; + DOT = '.' ; + SEMIC = ';' ; + COMMA = ',' ; + LT = '<' ; + GT = '>' ; + LTE = '<=' ; + GTE = '>=' ; + EQ = '==' ; + NEQ = '!=' ; + SAME = '===' ; + NSAME = '!==' ; + ADD = '+' ; + SUB = '-' ; + MUL = '*' ; + MOD = '%' ; + INC = '++' ; + DEC = '--' ; + SHL = '<<' ; + SHR = '>>' ; + SHU = '>>>' ; + AND = '&' ; + OR = '|' ; + XOR = '^' ; + NOT = '!' ; + INV = '~' ; + LAND = '&&' ; + LOR = '||' ; + QUE = '?' ; + COLON = ':' ; + ASSIGN = '=' ; + ADDASS = '+=' ; + SUBASS = '-=' ; + MULASS = '*=' ; + MODASS = '%=' ; + SHLASS = '<<=' ; + SHRASS = '>>=' ; + SHUASS = '>>>=' ; + ANDASS = '&=' ; + ORASS = '|=' ; + XORASS = '^=' ; + DIV = '/' ; + DIVASS = '/=' ; + +// Imaginary + ARGS ; + ARRAY ; + BLOCK ; + BYFIELD ; + BYINDEX ; + CALL ; + CEXPR ; + EXPR ; + FORITER ; + FORSTEP ; + ITEM ; + LABELLED ; + NAMEDVALUE ; + NEG ; + OBJECT ; + PAREXPR ; + PDEC ; + PINC ; + POS ; +} + +@parser::header { +/* + * YUI Test Coverage + * Author: Nicholas C. Zakas <nzakas@yahoo-inc.com> + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ +package com.yahoo.platform.yuitest.coverage; +import org.antlr.runtime.tree.Tree; +} + +@lexer::header { +/* + * YUI Test Coverage + * Author: Nicholas C. Zakas <nzakas@yahoo-inc.com> + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ +package com.yahoo.platform.yuitest.coverage; +} +@lexer::members +{ +private Token last; + + +private final boolean areRegularExpressionsEnabled() +{ + if (last == null) + { + return true; + } + switch (last.getType()) + { + // identifier + case Identifier: + // literals + case NULL: + case TRUE: + case FALSE: + case THIS: + case OctalIntegerLiteral: + case DecimalLiteral: + case HexIntegerLiteral: + case StringLiteral: + // member access ending + case RBRACK: + // function call or nested expression ending + case RPAREN: + return false; + // otherwise OK + default: + return true; + } +} + +private final void consumeIdentifierUnicodeStart() throws RecognitionException, NoViableAltException +{ + int ch = input.LA(1); + if (isIdentifierStartUnicode(ch)) + { + matchAny(); + do + { + ch = input.LA(1); + if (ch == '$' || (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || ch == '\\' || ch == '_' || (ch >= 'a' && ch <= 'z') || isIdentifierPartUnicode(ch)) + { + mIdentifierPart(); + } + else + { + return; + } + } + while (true); + } + else + { + throw new NoViableAltException(); + } +} + +private final boolean isIdentifierPartUnicode(int ch) +{ + return Character.isJavaIdentifierPart(ch); +} + +private final boolean isIdentifierStartUnicode(int ch) +{ + return Character.isJavaIdentifierStart(ch); +} + +public Token nextToken() +{ + Token result = super.nextToken(); + if (result.getChannel() == Token.DEFAULT_CHANNEL) + { + last = result; + } + return result; +} +} + +@parser::members +{ + +private boolean verbose = false; + +public void setVerbose(boolean newVerbose){ + verbose = newVerbose; +} + +private final boolean isLeftHandSideAssign(RuleReturnScope lhs, Object[] cached) +{ + if (cached[0] != null) + { + return ((Boolean)cached[0]).booleanValue(); + } + + boolean result; + if (isLeftHandSideExpression(lhs)) + { + switch (input.LA(1)) + { + case ASSIGN: + case MULASS: + case DIVASS: + case MODASS: + case ADDASS: + case SUBASS: + case SHLASS: + case SHRASS: + case SHUASS: + case ANDASS: + case XORASS: + case ORASS: + result = true; + break; + default: + result = false; + break; + } + } + else + { + result = false; + } + + cached[0] = new Boolean(result); + return result; +} + +@SuppressWarnings("unused") + +private final String wrapInBraces(Token start, Token stop, TokenStream tokens) { + if (start == null || stop == null) { + return null; + } + if ("{".equals(start.getText())) { + return tokens.toString(start, stop); + } + + if (verbose){ + System.err.println("\n[INFO] Adding braces around statement at line " + start.getLine()); + } + return "{" + tokens.toString(start, stop) + "}"; +} + +private final static String toObjectLiteral(List list, boolean numbers){ + StringBuilder builder = new StringBuilder(); + builder.append("{"); + for (int i=0; i < list.size(); i++){ + + if (i > 0){ + builder.append(","); + } + + if (numbers){ + builder.append('"'); + builder.append(list.get(i)); + builder.append("\":0"); + } else { + builder.append(list.get(i)); + builder.append(":0"); + } + } + builder.append("}"); + return builder.toString(); +} + +private final static boolean isLeftHandSideExpression(RuleReturnScope lhs) +{ + if (lhs.getTree() == null) // e.g. during backtracking + { + return true; + } + else + { + switch (((Tree)lhs.getTree()).getType()) + { + // primaryExpression + case THIS: + case Identifier: + case NULL: + case TRUE: + case FALSE: + case DecimalLiteral: + case OctalIntegerLiteral: + case HexIntegerLiteral: + case StringLiteral: + case RegularExpressionLiteral: + case ARRAY: + case OBJECT: + case PAREXPR: + // functionExpression + case FUNCTION: + // newExpression + case NEW: + // leftHandSideExpression + case CALL: + case BYFIELD: + case BYINDEX: + return true; + + default: + return false; + } + } +} + +private final boolean isLeftHandSideIn(RuleReturnScope lhs, Object[] cached) +{ + if (cached[0] != null) + { + return ((Boolean)cached[0]).booleanValue(); + } + + boolean result = isLeftHandSideExpression(lhs) && (input.LA(1) == IN); + cached[0] = new Boolean(result); + return result; +} + +private final void promoteEOL(ParserRuleReturnScope rule) +{ + // Get current token and its type (the possibly offending token). + Token lt = input.LT(1); + int la = lt.getType(); + + // We only need to promote an EOL when the current token is offending (not a SEMIC, EOF, RBRACE, EOL or MultiLineComment). + // EOL and MultiLineComment are not offending as they're already promoted in a previous call to this method. + // Promoting an EOL means switching it from off channel to on channel. + // A MultiLineComment gets promoted when it contains an EOL. + if (!(la == SEMIC || la == EOF || la == RBRACE || la == EOL || la == MultiLineComment)) + { + // Start on the possition before the current token and scan backwards off channel tokens until the previous on channel token. + for (int ix = lt.getTokenIndex() - 1; ix > 0; ix--) + { + lt = input.get(ix); + if (lt.getChannel() == Token.DEFAULT_CHANNEL) + { + // On channel token found: stop scanning. + break; + } + else if (lt.getType() == EOL || (lt.getType() == MultiLineComment && lt.getText().matches("/.*\r\n|\r|\n"))) + { + // We found our EOL: promote the token to on channel, position the input on it and reset the rule start. + lt.setChannel(Token.DEFAULT_CHANNEL); + input.seek(lt.getTokenIndex()); + if (rule != null) + { + rule.start = lt; + } + break; + } + } + } +} +} + +// +// $< A.1 Lexical Grammar (7) +// + +// Added for lexing purposes + +fragment BSLASH + : '\\' + ; + +fragment DQUOTE + : '"' + ; + +fragment SQUOTE + : '\'' + ; + +// $< Whitespace (7.2) + +fragment TAB + : '\u0009' + ; + +fragment VT // Vertical TAB + : '\u000b' + ; + +fragment FF // Form Feed + : '\u000c' + ; + +fragment SP // Space + : '\u0020' + ; + +fragment NBSP // Non-Breaking Space + : '\u00a0' + ; + +fragment USP // Unicode Space Separator (rest of Unicode category Zs) + : '\u1680' // OGHAM SPACE MARK + | '\u180E' // MONGOLIAN VOWEL SEPARATOR + | '\u2000' // EN QUAD + | '\u2001' // EM QUAD + | '\u2002' // EN SPACE + | '\u2003' // EM SPACE + | '\u2004' // THREE-PER-EM SPACE + | '\u2005' // FOUR-PER-EM SPACE + | '\u2006' // SIX-PER-EM SPACE + | '\u2007' // FIGURE SPACE + | '\u2008' // PUNCTUATION SPACE + | '\u2009' // THIN SPACE + | '\u200A' // HAIR SPACE + | '\u202F' // NARROW NO-BREAK SPACE + | '\u205F' // MEDIUM MATHEMATICAL SPACE + | '\u3000' // IDEOGRAPHIC SPACE + ; + +WhiteSpace + : ( TAB | VT | FF | SP | NBSP | USP )+ { $channel = HIDDEN; } + ; + +// $> + +// $< Line terminators (7.3) + +fragment LF // Line Feed + : '\n' + ; + +fragment CR // Carriage Return + : '\r' + ; + +fragment LS // Line Separator + : '\u2028' + ; + +fragment PS // Paragraph Separator + : '\u2029' + ; + +fragment LineTerminator + : CR | LF | LS | PS + ; + +EOL + : ( ( CR LF? ) | LF | LS | PS ) { $channel = HIDDEN; } + ; +// $> + +// $< Comments (7.4) + +MultiLineComment + : '/*' ( options { greedy = false; } : . )* '*/' { $channel = HIDDEN; } + ; + +SingleLineComment + : '//' ( ~( LineTerminator ) )* { $channel = HIDDEN; } + ; + +// $> + +// $< Tokens (7.5) + +token + : reservedWord + | Identifier + | punctuator + | numericLiteral + | StringLiteral + ; + +// $< Reserved words (7.5.1) + +reservedWord + : keyword + | futureReservedWord + | NULL + | booleanLiteral + ; + +// $> + +// $< Keywords (7.5.2) + +keyword + : BREAK + | CASE + | CATCH + | CONTINUE + | DEFAULT + | DELETE + | DO + | ELSE + | FINALLY + | FOR + | FUNCTION + | IF + | IN + | INSTANCEOF + | NEW + | RETURN + | SWITCH + | THIS + | THROW + | TRY + | TYPEOF + | VAR + | VOID + | WHILE + | WITH + ; + +// $> + +// $< Future reserved words (7.5.3) + +futureReservedWord + : ABSTRACT + | BOOLEAN + | BYTE + | CHAR + | CLASS + | CONST + | DEBUGGER + | DOUBLE + | ENUM + | EXPORT + | EXTENDS + | FINAL + | FLOAT + | GOTO + | IMPLEMENTS + | IMPORT + | INT + | INTERFACE + | LONG + | NATIVE + | PACKAGE + | PRIVATE + | PROTECTED + | PUBLIC + | SHORT + | STATIC + | SUPER + | SYNCHRONIZED + | THROWS + | TRANSIENT + | VOLATILE + ; + +// $> + +// $> + +// $< Identifiers (7.6) + +fragment IdentifierStartASCII + : 'a'..'z' | 'A'..'Z' + | '$' + | '_' + | BSLASH 'u' HexDigit HexDigit HexDigit HexDigit // UnicodeEscapeSequence + ; + +/* +The first two alternatives define how ANTLR can match ASCII characters which can be considered as part of an identifier. +The last alternative matches other characters in the unicode range that can be sonsidered as part of an identifier. +*/ +fragment IdentifierPart + : DecimalDigit + | IdentifierStartASCII + | { isIdentifierPartUnicode(input.LA(1)) }? { matchAny(); } + ; + +fragment IdentifierNameASCIIStart + : IdentifierStartASCII IdentifierPart* + ; + +/* +The second alternative acts as an action driven fallback to evaluate other characters in the unicode range than the ones in the ASCII subset. +Due to the first alternative this grammar defines enough so that ANTLR can generate a lexer that correctly predicts identifiers with characters in the ASCII range. +In that way keywords, other reserved words and ASCII identifiers are recognized with standard ANTLR driven logic. When the first character for an identifier fails to +match this ASCII definition, the lexer calls consumeIdentifierUnicodeStart because of the action in the alternative. This method checks whether the character matches +as first character in ranges other than ASCII and consumes further characters belonging to the identifier with help of mIdentifierPart generated out of the +IdentifierPart rule above. +*/ +Identifier + : IdentifierNameASCIIStart + | { consumeIdentifierUnicodeStart(); } + ; + +// $> + +// $< Punctuators (7.7) + +punctuator + : LBRACE + | RBRACE + | LPAREN + | RPAREN + | LBRACK + | RBRACK + | DOT + | SEMIC + | COMMA + | LT + | GT + | LTE + | GTE + | EQ + | NEQ + | SAME + | NSAME + | ADD + | SUB + | MUL + | MOD + | INC + | DEC + | SHL + | SHR + | SHU + | AND + | OR + | XOR + | NOT + | INV + | LAND + | LOR + | QUE + | COLON + | ASSIGN + | ADDASS + | SUBASS + | MULASS + | MODASS + | SHLASS + | SHRASS + | SHUASS + | ANDASS + | ORASS + | XORASS + | DIV + | DIVASS + ; + +// $> + +// $< Literals (7.8) + +literal + : NULL + | booleanLiteral + | numericLiteral + | StringLiteral + | RegularExpressionLiteral + ; + +booleanLiteral + : TRUE + | FALSE + ; + +// $< Numeric literals (7.8.3) + +/* +Note: octal literals are described in the B Compatibility section. +These are removed from the standards but are here for backwards compatibility with earlier ECMAScript definitions. +*/ + +fragment DecimalDigit + : '0'..'9' + ; + +fragment HexDigit + : DecimalDigit | 'a'..'f' | 'A'..'F' + ; + +fragment OctalDigit + : '0'..'7' + ; + +fragment ExponentPart + : ( 'e' | 'E' ) ( '+' | '-' )? DecimalDigit+ + ; + +fragment DecimalIntegerLiteral + : '0' + | '1'..'9' DecimalDigit* + ; + +DecimalLiteral + : DecimalIntegerLiteral '.' DecimalDigit* ExponentPart? + | '.' DecimalDigit+ ExponentPart? + | DecimalIntegerLiteral ExponentPart? + ; + +OctalIntegerLiteral + : '0' OctalDigit+ + ; + +HexIntegerLiteral + : ( '0x' | '0X' ) HexDigit+ + ; + +numericLiteral + : DecimalLiteral + | OctalIntegerLiteral + | HexIntegerLiteral + ; + +// $> + +// $< String literals (7.8.4) + +/* +Note: octal escape sequences are described in the B Compatibility section. +These are removed from the standards but are here for backwards compatibility with earlier ECMAScript definitions. +*/ + +fragment CharacterEscapeSequence + : ~( DecimalDigit | 'x' | 'u' | LineTerminator ) // Concatenation of SingleEscapeCharacter and NonEscapeCharacter + ; + +fragment ZeroToThree + : '0'..'3' + ; + +fragment OctalEscapeSequence + : OctalDigit + | ZeroToThree OctalDigit + | '4'..'7' OctalDigit + | ZeroToThree OctalDigit OctalDigit + ; + +fragment HexEscapeSequence + : 'x' HexDigit HexDigit + ; + +fragment UnicodeEscapeSequence + : 'u' HexDigit HexDigit HexDigit HexDigit + ; + +fragment EscapeSequence + : + BSLASH + ( + CharacterEscapeSequence + | OctalEscapeSequence + | HexEscapeSequence + | UnicodeEscapeSequence + ) + ; + +StringLiteral + : SQUOTE ( ~( SQUOTE | BSLASH | LineTerminator ) | EscapeSequence )* SQUOTE + | DQUOTE ( ~( DQUOTE | BSLASH | LineTerminator ) | EscapeSequence )* DQUOTE + ; + +// $> + +// $< Regular expression literals (7.8.5) + +fragment BackslashSequence + : BSLASH ~( LineTerminator ) + ; + +fragment RegularExpressionFirstChar + : ~ ( LineTerminator | MUL | BSLASH | DIV ) + | BackslashSequence + ; + +fragment RegularExpressionChar + : ~ ( LineTerminator | BSLASH | DIV ) + | BackslashSequence + ; + +RegularExpressionLiteral + : { areRegularExpressionsEnabled() }?=> DIV RegularExpressionFirstChar RegularExpressionChar* DIV IdentifierPart* + ; + +// $> + +// $> + +// $> + +// +// $< A.3 Expressions (11) +// + +// $<Primary expressions (11.1) + +primaryExpression + : THIS + | Identifier + | literal + | arrayLiteral + | objectLiteral + | lpar=LPAREN expression RPAREN //-> ^( PAREXPR[$lpar, "PAREXPR"] expression ) + ; + +/* + * Added ? for arrayItem. Original grammer didn't handle the case of [ foo, ] + */ +arrayLiteral + : lb=LBRACK ( arrayItem ( COMMA arrayItem? )* )? RBRACK + //-> ^( ARRAY[$lb, "ARRAY"] arrayItem* ) + ; + +arrayItem + : ( expr=assignmentExpression | { input.LA(1) == COMMA }? ) + //-> ^( ITEM $expr? ) + ; + +objectLiteral + : lb=LBRACE ( nameValuePair ( COMMA nameValuePair )* )? RBRACE + //-> ^( OBJECT[$lb, "OBJECT"] nameValuePair* ) + ; + +/* + * In order to get the name of a function expression in an object literal, + * need to keep track of the property name for later usage. + */ +nameValuePair + : propertyName COLON assignmentExpression + //-> ^( NAMEDVALUE propertyName assignmentExpression ) + ; + +propertyName + : Identifier + | StringLiteral + | numericLiteral + ; + +// $> + +// $<Left-hand-side expressions (11.2) + +/* +Refactored some rules to make them LL(*) compliant: +all the expressions surrounding member selection and calls have been moved to leftHandSideExpression to make them right recursive +*/ + +memberExpression + : primaryExpression + | functionExpression + | newExpression + ; + +newExpression + : NEW primaryExpression + | NEW functionExpression //not per spec, but needed to support old coding patterns + ; + + +arguments + : LPAREN ( assignmentExpression ( COMMA assignmentExpression )* )? RPAREN + //-> ^( ARGS assignmentExpression* ) + ; + +leftHandSideExpression + : + ( + memberExpression //-> memberExpression + ) + ( + arguments //-> ^( CALL $leftHandSideExpression arguments ) + | LBRACK expression RBRACK //-> ^( BYINDEX $leftHandSideExpression expression ) + | DOT Identifier //-> ^( BYFIELD $leftHandSideExpression Identifier ) + )* + ; + +// $> + +// $<Postfix expressions (11.3) + +/* +The specification states that there are no line terminators allowed before the postfix operators. +This is enforced by the call to promoteEOL in the action before ( INC | DEC ). +We only must promote EOLs when the la is INC or DEC because this production is chained as all expression rules. +In other words: only promote EOL when we are really in a postfix expression. A check on the la will ensure this. +*/ +postfixExpression + : leftHandSideExpression { if (input.LA(1) == INC || input.LA(1) == DEC) promoteEOL(null); } ( postfixOperator )? + ; + +postfixOperator + : op=INC { $op.setType(PINC); } + | op=DEC { $op.setType(PDEC); } + ; + +// $> + +// $<Unary operators (11.4) + +unaryExpression + : postfixExpression + | unaryOperator unaryExpression + ; + +unaryOperator + : DELETE + | VOID + | TYPEOF + | INC + | DEC + | op=ADD { $op.setType(POS); } + | op=SUB { $op.setType(NEG); } + | INV + | NOT + ; + +// $> + +// $<Multiplicative operators (11.5) + +multiplicativeExpression + : unaryExpression ( ( MUL | DIV | MOD ) unaryExpression )* + ; + +// $> + +// $<Additive operators (11.6) + +additiveExpression + : multiplicativeExpression ( ( ADD | SUB ) multiplicativeExpression )* + ; + +// $> + +// $<Bitwise shift operators (11.7) + +shiftExpression + : additiveExpression ( ( SHL | SHR | SHU ) additiveExpression )* + ; + +// $> + +// $<Relational operators (11.8) + +relationalExpression + : shiftExpression ( ( LT | GT | LTE | GTE | INSTANCEOF | IN ) shiftExpression )* + ; + +relationalExpressionNoIn + : shiftExpression ( ( LT | GT | LTE | GTE | INSTANCEOF ) shiftExpression )* + ; + +// $> + +// $<Equality operators (11.9) + +equalityExpression + : relationalExpression ( ( EQ | NEQ | SAME | NSAME ) relationalExpression )* + ; + +equalityExpressionNoIn + : relationalExpressionNoIn ( ( EQ | NEQ | SAME | NSAME ) relationalExpressionNoIn )* + ; + +// $> + +// $<Binary bitwise operators (11.10) + +bitwiseANDExpression + : equalityExpression ( AND equalityExpression )* + ; + +bitwiseANDExpressionNoIn + : equalityExpressionNoIn ( AND equalityExpressionNoIn )* + ; + +bitwiseXORExpression + : bitwiseANDExpression ( XOR bitwiseANDExpression )* + ; + +bitwiseXORExpressionNoIn + : bitwiseANDExpressionNoIn ( XOR bitwiseANDExpressionNoIn )* + ; + +bitwiseORExpression + : bitwiseXORExpression ( OR bitwiseXORExpression )* + ; + +bitwiseORExpressionNoIn + : bitwiseXORExpressionNoIn ( OR bitwiseXORExpressionNoIn )* + ; + +// $> + +// $<Binary logical operators (11.11) + +logicalANDExpression + : bitwiseORExpression ( LAND bitwiseORExpression )* + ; + +logicalANDExpressionNoIn + : bitwiseORExpressionNoIn ( LAND bitwiseORExpressionNoIn )* + ; + +logicalORExpression + : logicalANDExpression ( LOR logicalANDExpression )* + ; + +logicalORExpressionNoIn + : logicalANDExpressionNoIn ( LOR logicalANDExpressionNoIn )* + ; + +// $> + +// $<Conditional operator (11.12) + +conditionalExpression + : logicalORExpression ( QUE assignmentExpression COLON assignmentExpression )? + ; + +conditionalExpressionNoIn + : logicalORExpressionNoIn ( QUE assignmentExpressionNoIn COLON assignmentExpressionNoIn )? + ; + +// $> + +// $<Assignment operators (11.13) + +/* +The specification defines the AssignmentExpression rule as follows: +AssignmentExpression : + ConditionalExpression + LeftHandSideExpression AssignmentOperator AssignmentExpression +This rule has a LL(*) conflict. Resolving this with a syntactical predicate will yield something like this: + +assignmentExpression + : ( leftHandSideExpression assignmentOperator )=> leftHandSideExpression assignmentOperator^ assignmentExpression + | conditionalExpression + ; +assignmentOperator + : ASSIGN | MULASS | DIVASS | MODASS | ADDASS | SUBASS | SHLASS | SHRASS | SHUASS | ANDASS | XORASS | ORASS + ; + +But that didn't seem to work. Terence Par writes in his book that LL(*) conflicts in general can best be solved with auto backtracking. But that would be +a performance killer for such a heavy used rule. +The solution I came up with is to always invoke the conditionalExpression first and than decide what to do based on the result of that rule. +When the rule results in a Tree that can't be coming from a left hand side expression, then we're done. +When it results in a Tree that is coming from a left hand side expression and the LA(1) is an assignment operator then parse the assignment operator +followed by the right recursive call. +*/ +assignmentExpression +@init +{ + Object[] isLhs = new Object[1]; +} + : lhs=conditionalExpression + ( { isLeftHandSideAssign(lhs, isLhs) }? assignmentOperator assignmentExpression )? + ; + +assignmentOperator + : ASSIGN | MULASS | DIVASS | MODASS | ADDASS | SUBASS | SHLASS | SHRASS | SHUASS | ANDASS | XORASS | ORASS + ; + +assignmentExpressionNoIn +@init +{ + Object[] isLhs = new Object[1]; +} + : lhs=conditionalExpressionNoIn + ( { isLeftHandSideAssign(lhs, isLhs) }? assignmentOperator assignmentExpressionNoIn )? + ; + +// $> + +// $<Comma operator (11.14) + +expression + : exprs+=assignmentExpression ( COMMA exprs+=assignmentExpression )* + //-> { $exprs.size() > 1 }? ^( CEXPR $exprs+ ) + //-> $exprs + ; + +expressionNoIn + : exprs+=assignmentExpressionNoIn ( COMMA exprs+=assignmentExpressionNoIn )* + //-> { $exprs.size() > 1 }? ^( CEXPR $exprs+ ) + //-> $exprs + ; + +// $> + +// $> + +// +// $< A.4 Statements (12) +// + +/* +This rule handles semicolons reported by the lexer and situations where the ECMA 3 specification states there should be semicolons automaticly inserted. +The auto semicolons are not actually inserted but this rule behaves as if they were. + +In the following situations an ECMA 3 parser should auto insert absent but grammaticly required semicolons: +- the current token is a right brace +- the current token is the end of file (EOF) token +- there is at least one end of line (EOL) token between the current token and the previous token. + +The RBRACE is handled by matching it but not consuming it. +The EOF needs no further handling because it is not consumed by default. +The EOL situation is handled by promoting the EOL or MultiLineComment with an EOL present from off channel to on channel +and thus making it parseable instead of handling it as white space. This promoting is done in the action promoteEOL. +*/ +semic +@init +{ + // Mark current position so we can unconsume a RBRACE. + int marker = input.mark(); + // Promote EOL if appropriate + promoteEOL(retval); +} + : SEMIC + | EOF + | RBRACE { input.rewind(marker); } + | EOL | MultiLineComment // (with EOL in it) + ; + +/* +To solve the ambiguity between block and objectLiteral via expressionStatement all but the block alternatives have been moved to statementTail. +Now when k = 1 and a semantical predicate is defined ANTLR generates code that always will prefer block when the LA(1) is a LBRACE. +This will result in the same behaviour that is described in the specification under 12.4 on the expressionStatement rule. +*/ +statement +options +{ + k = 1 ; +} +scope { + boolean isBlock; +} +@init{ + boolean instrument = false; + + if ($start.getLine() > $program::stopLine) { + $program::stopLine = $start.getLine(); + instrument = true; + } +} +@after { + if (instrument && !$statement::isBlock) { + $program::executableLines.add($start.getLine()); + } + if (verbose){ + System.err.println("\n[INFO] Instrumenting statement on line " + $start.getLine()); + } +} + : ({ $statement::isBlock = input.LA(1) == LBRACE }? block | statementTail) + -> {instrument && !$statement::isBlock}? cover_line(src={$program::name}, code={$text},line={$start.getLine()}) + -> ignore(code={$text}) + ; + +statementTail + : variableStatement + | emptyStatement + | expressionStatement + | ifStatement + | iterationStatement + | continueStatement + | breakStatement + | returnStatement + | withStatement + | labelledStatement + | switchStatement + | throwStatement + | tryStatement + ; + +// $<Block (12.1) + +block + : lb=LBRACE statement* RBRACE + //-> ^( BLOCK[$lb, "BLOCK"] statement* ) + ; + +// $> + +// $<Variable statement 12.2) + +variableStatement + : VAR variableDeclaration ( COMMA variableDeclaration )* semic + //-> ^( VAR variableDeclaration+ ) + ; + +variableDeclaration + : Identifier ( ASSIGN assignmentExpression )? + ; + +variableDeclarationNoIn + : Identifier ( ASSIGN assignmentExpressionNoIn )? + ; + +// $> + +// $<Empty statement (12.3) + +emptyStatement + : SEMIC + ; + +// $> + +// $<Expression statement (12.4) + +/* +The look ahead check on LBRACE and FUNCTION the specification mentions has been left out and its function, resolving the ambiguity between: +- functionExpression and functionDeclarationstatement +- block and objectLiteral +are moved to the statement and sourceElement rules. +*/ +expressionStatement + : expression semic + ; + +// $> + +// $<The if statement (12.5) + + +ifStatement +// The predicate is there just to get rid of the warning. ANTLR will handle the dangling else just fine. + : IF LPAREN expression RPAREN statement ( { input.LA(1) == ELSE }? elseStatement)? + //push the block wrap to the statement? + -> template(p = {input.toString($start.getTokenIndex(), $statement.start.getTokenIndex() - 1)}, + body = {wrapInBraces($statement.start, $statement.stop, input)}, + elseClause = { + $elseStatement.stop != null ? input.toString($statement.stop.getTokenIndex()+1, $elseStatement.stop.getTokenIndex() ) : null}) "<p><body><elseClause>" + ; + +elseStatement + : ELSE statement + -> template(prefix = {input.toString($start.getTokenIndex(), $statement.start.getTokenIndex() - 1)}, stmt = {wrapInBraces($statement.start, $statement.stop, input)}) "<prefix><stmt>" + ; + +// $> + +// $<Iteration statements (12.6) + +iterationStatement + : doStatement + | whileStatement + | forStatement + ; + +doStatement + : DO statement WHILE LPAREN expression RPAREN semic + -> template(pre = {input.toString($start.getTokenIndex(), $statement.start.getTokenIndex() - 1)}, + stmt = {wrapInBraces($statement.start, $statement.stop, input)}, + post = {input.toString($WHILE, $RPAREN)}, + end = {$semic.text}) "<pre><stmt><post><end>" + ; + +whileStatement + : WHILE LPAREN expression RPAREN statement + -> template(pre = {input.toString($start.getTokenIndex(), $statement.start.getTokenIndex() - 1)}, + stmt = {wrapInBraces($statement.start, $statement.stop, input)} + ) "<pre><stmt>" + ; + +/* +The forStatement production is refactored considerably as the specification contains a very none LL(*) compliant definition. +The initial version was like this: + +forStatement + : FOR^ LPAREN! forControl RPAREN! statement + ; +forControl +options +{ + backtrack = true ; + //k = 3 ; +} + : stepClause + | iterationClause + ; +stepClause +options +{ + memoize = true ; +} + : ( ex1=expressionNoIn | var=VAR variableDeclarationNoIn ( COMMA variableDeclarationNoIn )* )? SEMIC ex2=expression? SEMIC ex3=expression? + -> { $var != null }? ^( FORSTEP ^( VAR[$var] variableDeclarationNoIn+ ) ^( EXPR $ex2? ) ^( EXPR $ex3? ) ) + -> ^( FORSTEP ^( EXPR $ex1? ) ^( EXPR $ex2? ) ^( EXPR $ex3? ) ) + ; +iterationClause +options +{ + memoize = true ; +} + : ( leftHandSideExpression | var=VAR variableDeclarationNoIn ) IN expression + -> { $var != null }? ^( FORITER ^( VAR[$var] variableDeclarationNoIn ) ^( EXPR expression ) ) + -> ^( FORITER ^( EXPR leftHandSideExpression ) ^( EXPR expression ) ) + ; + +But this completely relies on the backtrack feature and capabilities of ANTLR. +Furthermore backtracking seemed to have 3 major drawbacks: +- the performance cost of backtracking is considerably +- didn't seem to work well with ANTLRWorks +- when introducing a k value to optimize the backtracking away, ANTLR runs out of heap space +*/ +forStatement + : FOR LPAREN forControl RPAREN statement + -> template(pre = {input.toString($start.getTokenIndex(), $statement.start.getTokenIndex() - 1)}, + stmt = {wrapInBraces($statement.start, $statement.stop, input)} + ) "<pre><stmt>" + ; + +forControl + : forControlVar + | forControlExpression + | forControlSemic + ; + +forControlVar + : VAR variableDeclarationNoIn + ( + ( + IN expression + //-> ^( FORITER ^( VAR variableDeclarationNoIn ) ^( EXPR expression ) ) + ) + | + ( + ( COMMA variableDeclarationNoIn )* SEMIC ex1=expression? SEMIC ex2=expression? + //-> ^( FORSTEP ^( VAR variableDeclarationNoIn+ ) ^( EXPR $ex1? ) ^( EXPR $ex2? ) ) + ) + ) + ; + +forControlExpression +@init +{ + Object[] isLhs = new Object[1]; +} + : ex1=expressionNoIn + ( + { isLeftHandSideIn(ex1, isLhs) }? ( + IN ex2=expression + //-> ^( FORITER ^( EXPR $ex1 ) ^( EXPR $ex2 ) ) + ) + | + ( + SEMIC ex2=expression? SEMIC ex3=expression? + //-> ^( FORSTEP ^( EXPR $ex1 ) ^( EXPR $ex2? ) ^( EXPR $ex3? ) ) + ) + ) + ; + +forControlSemic + : SEMIC ex1=expression? SEMIC ex2=expression? + //-> ^( FORSTEP ^( EXPR ) ^( EXPR $ex1? ) ^( EXPR $ex2? ) ) + ; + +// $> + +// $<The continue statement (12.7) + +/* +The action with the call to promoteEOL after CONTINUE is to enforce the semicolon insertion rule of the specification that there are +no line terminators allowed beween CONTINUE and the optional identifier. +As an optimization we check the la first to decide whether there is an identier following. +*/ +continueStatement + : CONTINUE { if (input.LA(1) == Identifier) promoteEOL(null); } Identifier? semic + ; + +// $> + +// $<The break statement (12.8) + +/* +The action with the call to promoteEOL after BREAK is to enforce the semicolon insertion rule of the specification that there are +no line terminators allowed beween BREAK and the optional identifier. +As an optimization we check the la first to decide whether there is an identier following. +*/ +breakStatement + : BREAK { if (input.LA(1) == Identifier) promoteEOL(null); } Identifier? semic + ; + +// $> + +// $<The return statement (12.9) + +/* +The action calling promoteEOL after RETURN ensures that there are no line terminators between RETURN and the optional expression as the specification states. +When there are these get promoted to on channel and thus virtual semicolon wannabees. +So the folowing code: + +return +1 + +will be parsed as: + +return; +1; +*/ +returnStatement + : RETURN { promoteEOL(null); } expression? semic + ; + +// $> + +// $<The with statement (12.10) + +withStatement + : WITH LPAREN expression RPAREN statement + -> template(pre = {input.toString($start.getTokenIndex(), $statement.start.getTokenIndex() - 1)}, + stmt = {wrapInBraces($statement.start, $statement.stop, input)} + ) "<pre><stmt>" + ; + +// $> + +// $<The switch statement (12.11) + +switchStatement +@init +{ + int defaultClauseCount = 0; +} + : SWITCH LPAREN expression RPAREN LBRACE ( { defaultClauseCount == 0 }?=> defaultClause { defaultClauseCount++; } | caseClause )* RBRACE + //-> ^( SWITCH expression defaultClause? caseClause* ) + ; + +caseClause + : CASE expression COLON statement* + ; + +defaultClause + : DEFAULT COLON statement* + ; + +// $> + +// $<Labelled statements (12.12) + +labelledStatement + : Identifier COLON statement + //-> ^( LABELLED Identifier statement ) + ; + +// $> + +// $<The throw statement (12.13) + +/* +The action calling promoteEOL after THROW ensures that there are no line terminators between THROW and the expression as the specification states. +When there are line terminators these get promoted to on channel and thus to virtual semicolon wannabees. +So the folowing code: + +throw +new Error() + +will be parsed as: + +throw; +new Error(); + +which will yield a recognition exception! +*/ +throwStatement + : THROW { promoteEOL(null); } expression semic + ; + +// $> + +// $<The try statement (12.14) + +tryStatement + : TRY block ( catchClause finallyClause? | finallyClause ) + ; + +catchClause + : CATCH LPAREN Identifier RPAREN block + ; + +finallyClause + : FINALLY block + ; + +// $> + +// $> + +// +// $< A.5 Functions and Programs (13, 14) +// + +// $< Function Definition (13) + + +functionDeclaration +scope { + String funcName; + Integer funcLine; +} +@init{ + + boolean instrument = false; + if ($start.getLine() > $program::stopLine) { + $program::executableLines.add($start.getLine()); + $program::stopLine = $start.getLine(); + instrument = true; + } + $functionDeclaration::funcLine = $start.getLine(); +} +@after { + $program::functions.add("\"" + $functionDeclaration::funcName + ":" + $start.getLine() + "\""); + if (verbose){ + System.err.println("\n[INFO] Instrumenting function " + $functionDeclaration::funcName + " on line " + $start.getLine()); + } +} + + : FUNCTION name=Identifier {$functionDeclaration::funcName=$Identifier.text;} formalParameterList functionDeclarationBody + -> {instrument}? cover_line(src={$program::name}, code={$text}, line={$start.getLine()}) + -> ignore(code={$text}) + ; + +functionExpression +scope{ + String funcName; + Integer funcLine; +} +@init { + $functionExpression::funcLine=$start.getLine(); + + /* + * The function expression might have an identifier, and if so, use that as + * the name. + * + * This might be a function that's a method in an object literal. If so, + * the previous token will be a colon and the one prior to that will be the + * identifier. + * + * Function may also be assigned to a variable. In that case, the previous + * token will be the equals sign (=) and the token prior to that is the + * variable/property. + * + * Even after all that, the function expression might have a declared name + * as if it were a function declaration. If so, the declared function name + * takes precendence over any object literal or variable assignment. + */ + int lastTT = input.LA(-1); //look for = or : + int nextTT = input.LA(2); //look for an identifer + + if (nextTT == Identifier){ + $functionExpression::funcName = input.LT(2).getText(); + } else if (lastTT == COLON || lastTT == ASSIGN) { + $functionExpression::funcName = input.LT(-2).getText().replace("\"", "\\\"").replace("'", "\\'"); + + //TODO: Continue walking back in case the identifier is object.name + //right now, I end up just with name. + } else { + $functionExpression::funcName = "(anonymous " + (++$program::anonymousFunctionCount) + ")"; + } + +} + : FUNCTION Identifier? formalParameterList functionExpressionBody + ; + +formalParameterList + : LPAREN ( Identifier ( COMMA Identifier )* )? RPAREN + ; + +functionDeclarationBody + : lb=LBRACE functionDeclarationBodyWithoutBraces? RBRACE + ; + +functionExpressionBody + : lb=LBRACE functionExpressionBodyWithoutBraces? RBRACE + ; + +//Jumping through hoops to get the function body without braces. There's gotta be an easier way. +functionExpressionBodyWithoutBraces +@after { + //favor the function expression's declared name, otherwise assign an anonymous function name + $program::functions.add("\"" + $functionExpression::funcName + ":" + $functionExpression::funcLine + "\""); + + if (verbose){ + System.err.println("\n[INFO] Instrumenting function expression '" + $functionExpression::funcName + "' on line " + $functionExpression::funcLine); + + } + +} + : sourceElement sourceElement* + { + + } + -> {$functionExpression::funcName!=null}? cover_func(src={$program::name}, code={$text}, name={$functionExpression::funcName}, line={$functionExpression::funcLine}) + -> cover_func(src={$program::name}, code={$text}, name={$functionDeclaration::funcName}, line={$functionDeclaration::funcLine}) + ; + +functionDeclarationBodyWithoutBraces + : sourceElement sourceElement* + -> cover_func(src={$program::name}, code={$text}, name={$functionDeclaration::funcName}, line={$functionDeclaration::funcLine}) + ; + +// $> + +// $< Program (14) + +program +scope { + java.util.List<Integer> executableLines; + java.util.List<String> functions; + int stopLine; + String name; + int anonymousFunctionCount; +} +@init { + $program::executableLines = new java.util.LinkedList(); + $program::functions = new java.util.LinkedList(); + $program::stopLine = 0; + $program::name = getSourceName(); + $program::anonymousFunctionCount = 0; +} + : (sourceElement*) {java.util.Collections.sort($program::executableLines);} + -> cover_file(src={$program::name}, code = {$text}, lines = {toObjectLiteral($program::executableLines, true)}, funcs={toObjectLiteral($program::functions, false)}, lineCount={$program::executableLines.size()}, funcCount={$program::functions.size()}) + ; + +/* +By setting k to 1 for this rule and adding the semantical predicate ANTRL will generate code that will always prefer functionDeclararion over functionExpression +here and therefor remove the ambiguity between these to production. +This will result in the same behaviour that is described in the specification under 12.4 on the expressionStatement rule. +*/ +sourceElement +options +{ + k = 1 ; +} + : { input.LA(1) == FUNCTION }? functionDeclaration + | statement + ; + diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/grammar/ES3_license.txt b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/grammar/ES3_license.txt new file mode 100644 index 000000000..4a3a5c178 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/grammar/ES3_license.txt @@ -0,0 +1,30 @@ +Software License Agreement (BSD License) + +Copyright (c) 2008-2009, Xebic Research B.V. +All rights reserved. + +Redistribution and use of this software in source and binary forms, with or without modification, are +permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of Xebic Research B.V. nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of Xebic Research B.V. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/report/CoverageReportGenerator.java b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/report/CoverageReportGenerator.java new file mode 100644 index 000000000..fa7ae0a08 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/report/CoverageReportGenerator.java @@ -0,0 +1,22 @@ +/* + * YUI Test + * Author: Nicholas C. Zakas <nzakas@yahoo-inc.com> + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ + +package com.yahoo.platform.yuitest.coverage.report; + +import com.yahoo.platform.yuitest.coverage.results.SummaryCoverageReport; +import java.io.IOException; +import java.util.Date; + +/** + * + * @author Nicholas C. Zakas + */ +public interface CoverageReportGenerator { + public void generate(SummaryCoverageReport report) throws IOException; + public void generate(SummaryCoverageReport report, Date timestamp) throws IOException; +} diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/report/CoverageReportGeneratorFactory.java b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/report/CoverageReportGeneratorFactory.java new file mode 100644 index 000000000..94dc71a02 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/report/CoverageReportGeneratorFactory.java @@ -0,0 +1,28 @@ +/* + * YUI Test + * Author: Nicholas C. Zakas <nzakas@yahoo-inc.com> + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ + +package com.yahoo.platform.yuitest.coverage.report; + +/** + * + * @author Nicholas C. Zakas + */ +public class CoverageReportGeneratorFactory { + + public static CoverageReportGenerator getGenerator(String format, String outputDirectory, boolean verbose){ + if (format.equalsIgnoreCase("html")){ + return new HTMLReportGenerator(outputDirectory, verbose); + } else if (format.equalsIgnoreCase("lcov")){ + return new LCOVReportGenerator(outputDirectory, verbose); + } else if (format.equalsIgnoreCase("gcov")){ + return new GCOVReportGenerator(outputDirectory, verbose); + } else { + throw new IllegalArgumentException("Unsupported format '" + format + "'."); + } + } +} diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/report/GCOVReportGenerator.java b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/report/GCOVReportGenerator.java new file mode 100644 index 000000000..ecfac7220 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/report/GCOVReportGenerator.java @@ -0,0 +1,112 @@ +/* + * YUI Test + * Author: Nicholas C. Zakas <nzakas@yahoo-inc.com> + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ + +package com.yahoo.platform.yuitest.coverage.report; + +import com.yahoo.platform.yuitest.coverage.results.DirectoryCoverageReport; +import com.yahoo.platform.yuitest.coverage.results.FileCoverageReport; +import com.yahoo.platform.yuitest.coverage.results.SummaryCoverageReport; +import com.yahoo.platform.yuitest.writers.ReportWriter; +import com.yahoo.platform.yuitest.writers.ReportWriterFactory; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.util.Date; + +/** + * + * @author Nicholas C. Zakas + */ +public class GCOVReportGenerator implements CoverageReportGenerator { + + private File outputdir = null; + private boolean verbose = false; + + /** + * Creates a new GCOVReportGenerator + * @param outputdir The output directory for the GCOV report. + * @param verbose True to output additional information to the console. + */ + public GCOVReportGenerator(String outputdir, boolean verbose){ + this.outputdir = new File(outputdir); + this.verbose = verbose; + } + + /** + * Generates report files for the given coverage report. + * @param report The report to generate files for. + * @throws IOException When the files cannot be written. + */ + public void generate(SummaryCoverageReport report) throws IOException { + generate(report, new Date()); + } + + /** + * Generates report files for the given coverage report. + * @param report The report to generate files for. + * @param timestamp The timestamp to apply to the files. + * @throws IOException When the files cannot be written. + */ + public void generate(SummaryCoverageReport report, Date timestamp) throws IOException { + //create the report directory now + if (!outputdir.exists()){ + outputdir.mkdirs(); + if (verbose){ + System.err.println("[INFO] Creating " + outputdir.getCanonicalPath()); + } + } + + DirectoryCoverageReport[] reports = report.getDirectoryReports(); + for (int i=0; i < reports.length; i++){ + generateDirectories(reports[i], timestamp); + } + } + + /** + * Generates a report page for each file in the coverage information. + * @param report The coverage information to generate reports for. + * @param date The date associated with the report. + * @throws IOException When a file cannot be written to. + */ + private void generateDirectories(DirectoryCoverageReport report, Date date) throws IOException { + + FileCoverageReport[] fileReports = report.getFileReports(); + + //make the directory to mimic the source file + String parentDir = fileReports[0].getFile().getParent(); + File parent = new File(outputdir.getAbsolutePath() + File.separator + parentDir); + if (!parent.exists()){ + parent.mkdirs(); + } + + for (int i=0; i < fileReports.length; i++){ + generateGCOVFile(fileReports[i], date, parent); + } + } + + /** + * Generates a GCOV file for the file coverage information. + * @param report The coverage information to generate files for. + * @param date The date associated with the report. + * @throws IOException When a file cannot be written to. + */ + private void generateGCOVFile(FileCoverageReport report, Date date, File parent) throws IOException { + String outputFile = parent.getAbsolutePath() + File.separator + report.getFile().getName() + ".gcov"; + + if (verbose){ + System.err.println("[INFO] Outputting " + outputFile); + } + + Writer out = new OutputStreamWriter(new FileOutputStream(outputFile)); + ReportWriter reportWriter = (new ReportWriterFactory<FileCoverageReport>()).getWriter(out, "GCOVFileReport"); + reportWriter.write(report, date); + out.close(); + } +} diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/report/HTMLReportGenerator.java b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/report/HTMLReportGenerator.java new file mode 100644 index 000000000..863f07288 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/report/HTMLReportGenerator.java @@ -0,0 +1,120 @@ +/* + * YUI Test + * Author: Nicholas C. Zakas <nzakas@yahoo-inc.com> + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ + +package com.yahoo.platform.yuitest.coverage.report; + +import com.yahoo.platform.yuitest.coverage.results.FileCoverageReport; +import com.yahoo.platform.yuitest.coverage.results.SummaryCoverageReport; +import com.yahoo.platform.yuitest.writers.ReportWriter; +import com.yahoo.platform.yuitest.writers.ReportWriterFactory; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.util.Date; + +/** + * + * @author Nicholas C. Zakas + */ +public class HTMLReportGenerator implements CoverageReportGenerator { + + private File outputdir = null; + private boolean verbose = false; + + /** + * Creates a new HTMLReportGenerator + * @param outputdir The output directory for the HTML report. + * @param verbose True to output additional information to the console. + */ + public HTMLReportGenerator(String outputdir, boolean verbose){ + this.outputdir = new File(outputdir); + this.verbose = verbose; + + //create directory if not already there + if (!this.outputdir.exists()){ + this.outputdir.mkdirs(); + } + } + + /** + * Generates report files for the given coverage report. + * @param report The report to generate files for. + * @throws IOException When the files cannot be written. + */ + public void generate(SummaryCoverageReport report) throws IOException { + generate(report, new Date()); + } + + /** + * Generates report files for the given coverage report. + * @param report The report to generate files for. + * @param timestamp The timestamp to apply to the files. + * @throws IOException When the files cannot be written. + */ + + public void generate(SummaryCoverageReport report, Date timestamp) throws IOException { + generateSummaryPage(report, timestamp); + generateFilePages(report, timestamp); + } + + /** + * Generates the index.html page for the HTML report. + * @param report The coverage information to generate a report for. + * @param date The date associated with the report. + * @throws IOException When a file cannot be written to. + */ + private void generateSummaryPage(SummaryCoverageReport report, Date date) throws IOException { + + String outputFile = outputdir.getAbsolutePath() + File.separator + "index.html"; + Writer out = new OutputStreamWriter(new FileOutputStream(outputFile)); + + if (verbose){ + System.err.println("[INFO] Outputting " + outputFile); + } + + ReportWriter reportWriter = (new ReportWriterFactory<SummaryCoverageReport>()).getWriter(out, "CoverageSummaryReportHTML"); + reportWriter.write(report, date); + out.close(); + } + + /** + * Generates a report page for each file in the coverage information. + * @param report The coverage information to generate reports for. + * @param date The date associated with the report. + * @throws IOException When a file cannot be written to. + */ + private void generateFilePages(SummaryCoverageReport report, Date date) throws IOException { + + FileCoverageReport[] fileReports = report.getFileReports(); + + for (int i=0; i < fileReports.length; i++){ + generateFilePage(fileReports[i], date); + } + } + + /** + * Generates a report page for the file coverage information. + * @param report The coverage information to generate reports for. + * @param date The date associated with the report. + * @throws IOException When a file cannot be written to. + */ + private void generateFilePage(FileCoverageReport report, Date date) throws IOException { + String outputFile = outputdir.getAbsolutePath() + File.separator + report.getReportName() + ".html"; + + if (verbose){ + System.err.println("[INFO] Outputting " + outputFile); + } + + Writer out = new OutputStreamWriter(new FileOutputStream(outputFile)); + ReportWriter reportWriter = (new ReportWriterFactory<FileCoverageReport>()).getWriter(out, "CoverageFileReportHTML"); + reportWriter.write(report, date); + out.close(); + } +} diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/report/LCOVReportGenerator.java b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/report/LCOVReportGenerator.java new file mode 100644 index 000000000..69eb87042 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/report/LCOVReportGenerator.java @@ -0,0 +1,209 @@ +/* + * YUI Test + * Author: Nicholas C. Zakas <nzakas@yahoo-inc.com> + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ + +package com.yahoo.platform.yuitest.coverage.report; + +import com.yahoo.platform.yuitest.coverage.results.DirectoryCoverageReport; +import com.yahoo.platform.yuitest.coverage.results.FileCoverageReport; +import com.yahoo.platform.yuitest.coverage.results.SummaryCoverageReport; +import com.yahoo.platform.yuitest.writers.ReportWriter; +import com.yahoo.platform.yuitest.writers.ReportWriterFactory; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.util.Date; + +/** + * + * @author Nicholas C. Zakas + */ +public class LCOVReportGenerator implements CoverageReportGenerator { + + private File outputdir = null; + private boolean verbose = false; + private File reportdir = null; + + /** + * Creates a new LCOVReportGenerator + * @param outputdir The output directory for the LCOV report. + * @param verbose True to output additional information to the console. + */ + public LCOVReportGenerator(String outputdir, boolean verbose){ + this.outputdir = new File(outputdir); + this.verbose = verbose; + this.reportdir = new File(outputdir + File.separator + "lcov-report"); + + //create directories if not already there + if (!this.outputdir.exists()){ + this.outputdir.mkdirs(); + } + + } + + /** + * Generates report files for the given coverage report. + * @param report The report to generate files for. + * @throws IOException When the files cannot be written. + */ + public void generate(SummaryCoverageReport report) throws IOException { + generate(report, new Date()); + } + + /** + * Generates report files for the given coverage report. + * @param report The report to generate files for. + * @param timestamp The timestamp to tie to the files. + * @throws IOException When the files cannot be written. + */ + public void generate(SummaryCoverageReport report, Date timestamp) throws IOException { + generateLCOVInfo(report, timestamp); + + //create the report directory timestamp + if (!this.reportdir.exists()){ + this.reportdir.mkdirs(); + if (verbose){ + System.err.println("[INFO] Creating " + reportdir.getCanonicalPath()); + } + } + + generateIndexPage(report, timestamp); + DirectoryCoverageReport[] reports = report.getDirectoryReports(); + for (int i=0; i < reports.length; i++){ + generateDirectoryPages(reports[i], timestamp); + } + } + + /** + * Generates the lcov.info file for the coverage information. + * @param report The coverage information to generate a report for. + * @param date The date associated with the report. + * @throws IOException When a file cannot be written to. + */ + private void generateLCOVInfo(SummaryCoverageReport report, Date date) throws IOException { + + String outputFile = outputdir.getAbsolutePath() + File.separator + "lcov.info"; + Writer out = new OutputStreamWriter(new FileOutputStream(outputFile)); + + if (verbose){ + System.err.println("[INFO] Outputting " + outputFile); + } + + ReportWriter reportWriter = (new ReportWriterFactory<SummaryCoverageReport>()).getWriter(out, "CoverageSummaryReportLCOV"); + reportWriter.write(report, date); + out.close(); + } + + /** + * Generates a report page for each file in the coverage information. + * @param report The coverage information to generate reports for. + * @param date The date associated with the report. + * @throws IOException When a file cannot be written to. + */ + private void generateDirectoryPages(DirectoryCoverageReport report, Date date) throws IOException { + + FileCoverageReport[] fileReports = report.getFileReports(); + + //make the directory to mimic the source file + String parentDir = fileReports[0].getFile().getParent(); + File parent = new File(reportdir.getAbsolutePath() + File.separator + parentDir); + if (!parent.exists()){ + parent.mkdirs(); + } + + generateDirectoryPage(report, date, parent); + + for (int i=0; i < fileReports.length; i++){ + generateFilePage(fileReports[i], date, parent); + generateFunctionPage(fileReports[i], date, parent); + } + } + + /** + * Generates a report page for the directory coverage information. + * @param report The coverage information to generate reports for. + * @param date The date associated with the report. + * @throws IOException When a file cannot be written to. + */ + private void generateDirectoryPage(DirectoryCoverageReport report, Date date, File parent) throws IOException { + String outputFile = parent.getAbsolutePath() + File.separator + "index.html"; + + if (verbose){ + System.err.println("[INFO] Outputting " + outputFile); + } + + Writer out = new OutputStreamWriter(new FileOutputStream(outputFile)); + ReportWriter reportWriter = (new ReportWriterFactory<FileCoverageReport>()).getWriter(out, "LCOVHTMLDirectoryReport"); + reportWriter.write(report, date); + out.close(); + + + } + + /** + * Generates an index page for the file coverage information. + * @param report The coverage information to generate reports for. + * @param date The date associated with the report. + * @throws IOException When a file cannot be written to. + */ + private void generateIndexPage(SummaryCoverageReport report, Date date) throws IOException { + String outputFile = this.reportdir.getAbsolutePath() + File.separator + "index.html"; + + if (verbose){ + System.err.println("[INFO] Outputting " + outputFile); + } + + Writer out = new OutputStreamWriter(new FileOutputStream(outputFile)); + ReportWriter reportWriter = (new ReportWriterFactory<SummaryCoverageReport>()).getWriter(out, "LCOVHTMLIndexReport"); + reportWriter.write(report, date); + out.close(); + + + } + + /** + * Generates a report page for the file coverage information. + * @param report The coverage information to generate reports for. + * @param date The date associated with the report. + * @throws IOException When a file cannot be written to. + */ + private void generateFilePage(FileCoverageReport report, Date date, File parent) throws IOException { + String outputFile = parent.getAbsolutePath() + File.separator + report.getFile().getName() + ".gcov.html"; + + if (verbose){ + System.err.println("[INFO] Outputting " + outputFile); + } + + Writer out = new OutputStreamWriter(new FileOutputStream(outputFile)); + ReportWriter reportWriter = (new ReportWriterFactory<FileCoverageReport>()).getWriter(out, "LCOVHTMLFileReport"); + reportWriter.write(report, date); + out.close(); + } + + /** + * Generates a report page for the function coverage information. + * @param report The coverage information to generate reports for. + * @param date The date associated with the report. + * @throws IOException When a file cannot be written to. + */ + private void generateFunctionPage(FileCoverageReport report, Date date, File parent) throws IOException { + String outputFile = parent.getAbsolutePath() + File.separator + report.getFile().getName() + ".func.html"; + + if (verbose){ + System.err.println("[INFO] Outputting " + outputFile); + } + + Writer out = new OutputStreamWriter(new FileOutputStream(outputFile)); + ReportWriter reportWriter = (new ReportWriterFactory<FileCoverageReport>()).getWriter(out, "LCOVHTMLFunctionReport"); + reportWriter.write(report, date); + out.close(); + } + + +} diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/report/YUITestCoverageReport.java b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/report/YUITestCoverageReport.java new file mode 100644 index 000000000..cf525fc09 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/report/YUITestCoverageReport.java @@ -0,0 +1,141 @@ +/* + * YUI Test + * Author: Nicholas C. Zakas <nzakas@yahoo-inc.com> + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ +package com.yahoo.platform.yuitest.coverage.report; + +import com.yahoo.platform.yuitest.coverage.results.SummaryCoverageReport; +import jargs.gnu.CmdLineParser; +import java.io.*; + +/** + * Main YUI Test Coverage class. + * @author Nicholas C. Zakas + */ +public class YUITestCoverageReport { + + public static void main(String args[]) { + + //---------------------------------------------------------------------- + // Initialize command line parser + //---------------------------------------------------------------------- + CmdLineParser parser = new CmdLineParser(); + CmdLineParser.Option verboseOpt = parser.addBooleanOption('v', "verbose"); + CmdLineParser.Option helpOpt = parser.addBooleanOption('h', "help"); + CmdLineParser.Option formatOpt = parser.addStringOption("format"); + CmdLineParser.Option outputLocationOpt = parser.addStringOption('o', "output"); + + Reader in = null; + Writer out = null; + + try { + + parser.parse(args); + + //Help option + Boolean help = (Boolean) parser.getOptionValue(helpOpt); + if (help != null && help.booleanValue()) { + usage(); + System.exit(0); + } + + //Verbose option + boolean verbose = parser.getOptionValue(verboseOpt) != null; + + //format option + String format = (String) parser.getOptionValue(formatOpt); + if (format == null) { + format = "HTML"; + } + + if (verbose) { + System.err.println("\n[INFO] Using format '" + format + "'."); + } + + //report option + String outputLocation = (String) parser.getOptionValue(outputLocationOpt); + if (outputLocation == null){ + outputLocation = "."; + } + + if (verbose) { + System.err.println("\n[INFO] Using output directory '" + outputLocation + "'."); + } + + //get the files to operate on + String[] fileArgs = parser.getRemainingArgs(); + + if (fileArgs.length == 0) { + usage(); + System.exit(1); + } + + if (verbose) { + System.err.println("\n[INFO] Preparing to generate coverage reports."); + } + + in = new InputStreamReader(new FileInputStream(fileArgs[0])); + SummaryCoverageReport fullReport = new SummaryCoverageReport(in); + in.close(); + + for (int i=1; i < fileArgs.length; i++){ + in = new InputStreamReader(new FileInputStream(fileArgs[i])); + fullReport.merge(new SummaryCoverageReport(in)); + in.close(); + } + + CoverageReportGenerator generator = CoverageReportGeneratorFactory.getGenerator(format, outputLocation, verbose); + generator.generate(fullReport); + + } catch (CmdLineParser.OptionException e) { + + usage(); + System.exit(1); + + } catch (IOException e) { + + e.printStackTrace(); + System.exit(1); + + } catch (Exception e) { + + e.printStackTrace(); + // Return a special error code used specifically by the web front-end. + System.exit(2); + + } finally { + + if (in != null) { + try { + in.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + if (out != null) { + try { + out.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + private static void usage() { + System.out.println( + "\nUsage: java -jar yuitest-coverage-report-x.y.z.jar [options] [file]\n\n" + + + "Global Options\n" + + " -h, --help Displays this information.\n" + + " --format <format> Output reports in <format>. Defaults to HTML.\n" + + " -v, --verbose Display informational messages and warnings.\n" + + " -o <file|dir> Place the output into <file|dir>.\n\n"); + } + + +} diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/results/DirectoryCoverageReport.java b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/results/DirectoryCoverageReport.java new file mode 100644 index 000000000..722a4e752 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/results/DirectoryCoverageReport.java @@ -0,0 +1,150 @@ +/* + * YUI Test + * Author: Nicholas C. Zakas <nzakas@yahoo-inc.com> + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ + +package com.yahoo.platform.yuitest.coverage.results; + +import java.text.DecimalFormat; +import java.util.LinkedList; +import java.util.List; +import org.json.JSONException; + +/** + * + * @author Nicholas C. Zakas + */ +public class DirectoryCoverageReport { + + private List<FileCoverageReport> fileReports; + private String directory = ""; + + + public DirectoryCoverageReport(String directory){ + this.directory = directory; + this.fileReports = new LinkedList<FileCoverageReport>(); + } + + public String getDirectory(){ + return directory; + } + +// public String getAbsolutePath(){ +// return directory; //TODO +// } + + protected void addFileReport(FileCoverageReport report){ + fileReports.add(report); + } + + public FileCoverageReport[] getFileReports(){ + return fileReports.toArray(new FileCoverageReport[fileReports.size()]); + } + + /** + * Returns the total number of lines tracked. + * @return The total number of lines tracked. + * @throws org.json.JSONException + */ + public int getCoveredLineCount() throws JSONException { + int sum = 0; + for (int i=0; i < fileReports.size(); i++){ + sum += fileReports.get(i).getCoveredLineCount(); + } + return sum; + } + + /** + * Returns the number of called lines. + * @return The number of called lines. + * @throws org.json.JSONException + */ + public int getCalledLineCount() throws JSONException { + int sum = 0; + for (int i=0; i < fileReports.size(); i++){ + sum += fileReports.get(i).getCalledLineCount(); + } + return sum; } + + /** + * Returns the percentage of lines called. + * @return The percentage of lines called. + * @throws org.json.JSONException + */ + public double getCalledLinePercentage() throws JSONException { + DecimalFormat twoDForm = new DecimalFormat("#.##"); + return Double.valueOf(twoDForm.format(((double) getCalledLineCount() / (double) getCoveredLineCount()) * 100)); + } + + /** + * Returns a string indicating the coverage level for lines in the file. + * This string is suitable for use in generating HTML reports. + * @return A string value of "high", "med", "low" depending on the coverage. + * @throws JSONException + */ + public String getCalledLinePercentageName() throws JSONException { + double percentage = getCalledLinePercentage(); + if (percentage >= 50){ + return "high"; + } else if (percentage <= 15){ + return "low"; + } else { + return "med"; + } + } + + /** + * Returns the total number of functions tracked. + * @return The total number of functions tracked. + * @throws org.json.JSONException + */ + public int getCoveredFunctionCount() throws JSONException { + int sum = 0; + for (int i=0; i < fileReports.size(); i++){ + sum += fileReports.get(i).getCoveredFunctionCount(); + } + return sum; } + + /** + * Returns the number of functions that were called. + * @return The number of functions that were called. + * @throws org.json.JSONException + */ + public int getCalledFunctionCount() throws JSONException { + int sum = 0; + for (int i=0; i < fileReports.size(); i++){ + sum += fileReports.get(i).getCalledFunctionCount(); + } + return sum; + } + + /** + * Returns the percentage of functions called. + * @return The percentage of functions called. + * @throws org.json.JSONException + */ + public double getCalledFunctionPercentage() throws JSONException { + DecimalFormat twoDForm = new DecimalFormat("#.##"); + return Double.valueOf(twoDForm.format(((double) getCalledFunctionCount() / (double) getCoveredFunctionCount()) * 100)); + } + + /** + * Returns a string indicating the coverage level for lines in the file. + * This string is suitable for use in generating HTML reports. + * @return A string value of "high", "med", "low" depending on the coverage. + * @throws JSONException + */ + public String getCalledFunctionPercentageName() throws JSONException { + double percentage = getCalledFunctionPercentage(); + if (percentage >= 90){ + return "high"; + } else if (percentage <= 75){ + return "low"; + } else { + return "med"; + } + } +} diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/results/FileCoverageReport.java b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/results/FileCoverageReport.java new file mode 100644 index 000000000..9d5d39d26 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/results/FileCoverageReport.java @@ -0,0 +1,322 @@ +/* + * YUI Test + * Author: Nicholas C. Zakas <nzakas@yahoo-inc.com> + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ + +package com.yahoo.platform.yuitest.coverage.results; + +import java.io.File; +import java.text.DecimalFormat; +import java.util.Arrays; +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Represents a single item on a report. + * @author Nicholas C. Zakas + */ +public class FileCoverageReport { + + private JSONObject report1; + private String filename; + private FileLine[] lines; + private FileFunction[] functions; + private String path; + + /** + * Creates a new FileCoverageReport for the given filename. + * @param filename The filename of the item. + * @param report1 The associated coverage report1. + */ + protected FileCoverageReport(String filename, JSONObject data) throws JSONException { + this.filename = filename; + this.report1 = data; + this.path = data.getString("path"); + createFileLines(); + createFileFunctions(); + } + + /** + * Creates the FileLine objects for the file. + * @throws org.json.JSONException + */ + private void createFileLines() throws JSONException { + int count = report1.getJSONArray("code").length(); + lines = new FileLine[count]; + + for (int i=0; i < count; i++){ + lines[i] = new FileLine(i+1, report1.getJSONArray("code").getString(i), report1.getJSONObject("lines").optInt(String.valueOf(i+1), -1)); + } + } + + /** + * Creates the FileFunction objects for the file. + * @throws org.json.JSONException + */ + private void createFileFunctions() throws JSONException { + JSONObject functionData = report1.getJSONObject("functions"); + String[] keys = JSONObject.getNames(functionData); + functions = new FileFunction[keys.length]; + + for (int i=0; i < keys.length; i++){ + functions[i] = new FileFunction(keys[i], functionData.optInt(keys[i], -1)); + } + + Arrays.sort(functions, new FileFunctionComparator()); + } + + /** + * Returns the filename for this item. + * @return The filename for this item. + */ + public String getFilename(){ + return filename; + } + + public File getFile(){ + return new File(filename); + } + + /** + * Returns the file path for this item. + * @return The file path for this item. + */ + public String getAbsolutePath(){ + return path; + } + + public String getFileParent(){ + String parent = getFile().getParent(); + if (parent != null){ + return parent.replace("\\", "/"); + } else { + return "base"; + } + } + + /** + * Returns the total number of lines tracked. + * @return The total number of lines tracked. + * @throws org.json.JSONException + */ + public int getCoveredLineCount() throws JSONException { + return report1.getInt("coveredLines"); + } + + /** + * Returns the number of called lines. + * @return The number of called lines. + * @throws org.json.JSONException + */ + public int getCalledLineCount() throws JSONException { + return report1.getInt("calledLines"); + } + + /** + * Returns the percentage of lines called. + * @return The percentage of lines called. + * @throws org.json.JSONException + */ + public double getCalledLinePercentage() throws JSONException { + DecimalFormat twoDForm = new DecimalFormat("#.##"); + return Double.valueOf(twoDForm.format(((double) getCalledLineCount() / (double) getCoveredLineCount()) * 100)); + } + + /** + * Returns a string indicating the coverage level for lines in the file. + * This string is suitable for use in generating HTML reports. + * @return A string value of "high", "med", "low" depending on the coverage. + * @throws JSONException + */ + public String getCalledLinePercentageName() throws JSONException { + double percentage = getCalledLinePercentage(); + if (percentage >= 50){ + return "high"; + } else if (percentage <= 15){ + return "low"; + } else { + return "med"; + } + } + + /** + * Returns the total number of functions tracked. + * @return The total number of functions tracked. + * @throws org.json.JSONException + */ + public int getCoveredFunctionCount() throws JSONException { + return report1.getInt("coveredFunctions"); + } + + /** + * Returns the number of functions that were called. + * @return The number of functions that were called. + * @throws org.json.JSONException + */ + public int getCalledFunctionCount() throws JSONException { + return report1.getInt("calledFunctions"); + } + + /** + * Returns the percentage of functions called. + * @return The percentage of functions called. + * @throws org.json.JSONException + */ + public double getCalledFunctionPercentage() throws JSONException { + DecimalFormat twoDForm = new DecimalFormat("#.##"); + return Double.valueOf(twoDForm.format(((double) getCalledFunctionCount() / (double) getCoveredFunctionCount()) * 100)); + } + + /** + * Returns a string indicating the coverage level for lines in the file. + * This string is suitable for use in generating HTML reports. + * @return A string value of "high", "med", "low" depending on the coverage. + * @throws JSONException + */ + public String getCalledFunctionPercentageName() throws JSONException { + double percentage = getCalledFunctionPercentage(); + if (percentage >= 90){ + return "high"; + } else if (percentage <= 75){ + return "low"; + } else { + return "med"; + } + } + + /** + * Returns all information about a given line. + * @param line The one-based number of the line to retrieve. + * @return A FileLine for the specified line. + * @throws org.json.JSONException + */ + public FileLine getLine(int line) throws JSONException{ + return lines[line-1]; + } + + /** + * Returns all information about all lines. + * @return An array of lines for the file. + * @throws org.json.JSONException + */ + public FileLine[] getLines() throws JSONException { + return lines; + } + + /** + * Returns all information about all functions. + * @return An array of functions for the file. + * @throws org.json.JSONException + */ + public FileFunction[] getFunctions() throws JSONException { + return functions; + } + + /** + * Returns the number of times that a given line was called. + * @param line The line number to check. + * @return The number of times that the lines was called. + * @throws org.json.JSONException + */ + public int getLineCallCount(int line) throws JSONException{ + + //error for uncovered lines + try { + return report1.getJSONObject("lines").getInt(String.valueOf(line)); + } catch (Exception ex){ + return -1; + } + } + + /** + * Returns the number of times a given function was called. + * @param functionName The name of the function. This is the function + * name followed by a colon followed by the line number. + * @return The number of times that the function was called. + * @throws org.json.JSONException + */ + public int getFunctionCallCount(String functionName) throws JSONException { + return report1.getJSONObject("functions").getInt(functionName); + } + + /** + * Returns all function names stored in the report item. + * @return All function names stored in the report item. + * @throws org.json.JSONException + */ + public String[] getFunctionNames() throws JSONException { + return JSONObject.getNames(report1.getJSONObject("functions")); + } + + /** + * Returns the JSONObject associated with the report item. + * @return The JSONObject associated with the report item. + */ + public JSONObject toJSONObject() { + return report1; + } + + /** + * Returns a name suitable for use as a filename in which the report can + * be saved. + * @return A name containing only A-Z,0-9, and _. + */ + public String getReportName(){ + return filename.replaceAll("[^A-Za-z0-9\\.]", "_"); + } + + /** + * Returns the JSON string representing the item. + * @return The JSON string representing the item. + */ + @Override + public String toString(){ + return report1.toString(); + } + + /** + * Merges the report1 in another report with this report. + * @param report The report to merge report1 from. + */ + public void merge(FileCoverageReport report) throws JSONException { + + //make sure the file is the same + if (this.getAbsolutePath().equals(report.getAbsolutePath())){ + + //update calledFunctions + if (this.getCalledFunctionCount() < report.getCalledFunctionCount()){ + report1.put("calledFunctions", report.getCalledFunctionCount()); + } + + //update calledLines + if (this.getCalledLineCount() < report.getCalledLineCount()){ + report1.put("calledLines", report.getCalledLineCount()); + } + + //update line calls + for (int i=0; i < lines.length; i++){ + report1.getJSONObject("lines").put(String.valueOf(lines[i].getLineNumber()), + (lines[i].getCallCount() + report.getLineCallCount(lines[i].getLineNumber()))); + + } + + //update function calls + String[] functionNames = getFunctionNames(); + for (int i=0; i < functionNames.length; i++){ + report1.getJSONObject("functions").put(functionNames[i], + (getFunctionCallCount(functionNames[i]) + report.getFunctionCallCount(functionNames[i]))); + + } + + //re-create file lines and functions + createFileLines(); + createFileFunctions(); + } else { + throw new IllegalArgumentException("Expected a report for " + this.getAbsolutePath()); + } + + } +} diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/results/FileFunction.java b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/results/FileFunction.java new file mode 100644 index 000000000..9ccae0b91 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/results/FileFunction.java @@ -0,0 +1,47 @@ +/* + * YUI Test + * Author: Nicholas C. Zakas <nzakas@yahoo-inc.com> + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ + +package com.yahoo.platform.yuitest.coverage.results; + +/** + * Represents a single function in the file. + * @author Nicholas C. Zakas + */ +public class FileFunction { + private String name; + private int lineNumber; + private int callCount; + + protected FileFunction(String name, int callCount){ + this.lineNumber = Integer.parseInt(name.substring(name.lastIndexOf(":")+1)); + this.name = name.substring(0, name.lastIndexOf(":")); + this.callCount = callCount; + } + + public int getCallCount() { + return callCount; + } + + protected void setCallCount(int callCount){ + this.callCount = callCount; + } + + public int getLineNumber() { + return lineNumber; + } + + public String getName() { + return name; + } + + public boolean isCalled(){ + return callCount > 0; + } + + +} diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/results/FileFunctionComparator.java b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/results/FileFunctionComparator.java new file mode 100644 index 000000000..baa8ba856 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/results/FileFunctionComparator.java @@ -0,0 +1,23 @@ +/* + * YUI Test + * Author: Nicholas C. Zakas <nzakas@yahoo-inc.com> + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ + +package com.yahoo.platform.yuitest.coverage.results; + +import java.util.Comparator; + +/** + * + * @author Nicholas C. Zakas + */ +public class FileFunctionComparator implements Comparator<FileFunction> { + + public int compare(FileFunction o1, FileFunction o2) { + return o1.getName().compareToIgnoreCase(o2.getName()); + } + +} diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/results/FileLine.java b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/results/FileLine.java new file mode 100644 index 000000000..c91b652f2 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/results/FileLine.java @@ -0,0 +1,51 @@ +/* + * YUI Test Coverage + * Author: Nicholas C. Zakas <nzakas@yahoo-inc.com> + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ + +package com.yahoo.platform.yuitest.coverage.results; + +/** + * Represents a single line in a file. + * @author Nicholas C. Zakas + */ +public class FileLine { + + private String text; + private int lineNumber; + private int callCount; + + protected FileLine(int lineNumber, String text, int callCount){ + this.lineNumber = lineNumber; + this.text = text; + this.callCount = callCount; + } + + public int getCallCount() { + return callCount; + } + + protected void setCallCount(int callCount){ + this.callCount = callCount; + } + + public int getLineNumber() { + return lineNumber; + } + + public String getText() { + return text; + } + + public boolean isCovered(){ + return callCount > -1; + } + + public boolean isCalled(){ + return callCount > 0; + } + +} diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/results/SummaryCoverageReport.java b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/results/SummaryCoverageReport.java new file mode 100644 index 000000000..51b51ed18 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/results/SummaryCoverageReport.java @@ -0,0 +1,299 @@ +/* + * YUI Test + * Author: Nicholas C. Zakas <nzakas@yahoo-inc.com> + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ + +package com.yahoo.platform.yuitest.coverage.results; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.text.DecimalFormat; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Represents a code coverage report. + * @author Nicholas C. Zakas + */ +public class SummaryCoverageReport { + + private JSONObject data; + private FileCoverageReport[] files; + private HashMap<String,DirectoryCoverageReport> directories; + + //-------------------------------------------------------------------------- + // Constructors + //-------------------------------------------------------------------------- + + /** + * Creates a new report object from data in a file. + * @param file The file from which to read the JSON data. + * @throws java.io.IOException + * @throws org.json.JSONException + */ + public SummaryCoverageReport(File file) throws IOException, JSONException { + this(new InputStreamReader(new FileInputStream(file))); + } + + /** + * Creates a new report object from data in multiple files. + * @param file The file from which to read the JSON data. + * @throws java.io.IOException + * @throws org.json.JSONException + */ + public SummaryCoverageReport(File[] files) throws IOException, JSONException { + + //start with the first file + this(files[0]); + + //merge the others + for (int i=1; i < files.length; i++){ + merge(new SummaryCoverageReport(files[i])); + } + } + + /** + * Creates a new report object from a reader. + * @param in The reader containing JSON information. + * @throws java.io.IOException + * @throws org.json.JSONException + */ + public SummaryCoverageReport(Reader in) throws IOException, JSONException { + StringBuilder builder = new StringBuilder(); + int c; + + while((c = in.read()) != -1){ + builder.append((char)c); + } + + this.data = new JSONObject(builder.toString()); + this.directories = new HashMap<String,DirectoryCoverageReport>(); + generateFileReports(); + } + + /** + * Creates a new report object from a JSON object. + * @param data The JSON object containing coverage data. + */ + public SummaryCoverageReport(JSONObject data) throws JSONException{ + this.data = data; + generateFileReports(); + } + + /** + * Generates FileCoverageReport objects for every file in the report. + */ + private void generateFileReports() throws JSONException{ + String[] filenames = getFilenames(); + Arrays.sort(filenames); + files = new FileCoverageReport[filenames.length]; + directories.clear(); + + for (int i=0; i < filenames.length; i++){ + files[i] = new FileCoverageReport(filenames[i], data.getJSONObject(filenames[i])); + if (!directories.containsKey(files[i].getFileParent())){ + directories.put(files[i].getFileParent(), new DirectoryCoverageReport(files[i].getFileParent())); + } + directories.get(files[i].getFileParent()).addFileReport(files[i]); + } + } + + /** + * Returns the filenames tracked in the report. + * @return The filenames tracked in the report. + */ + public String[] getFilenames(){ + String[] filenames = JSONObject.getNames(data); + Arrays.sort(filenames); + return filenames; + } + + public DirectoryCoverageReport[] getDirectoryReports(){ + //return directories.values().toArray(new DirectoryCoverageReport[directories.size()]); + DirectoryCoverageReport[] reports = directories.values().toArray( + new DirectoryCoverageReport[directories.size()]); + Arrays.sort(reports, new Comparator<DirectoryCoverageReport>() { + public int compare(DirectoryCoverageReport o1, + DirectoryCoverageReport o2) { + return o1.getDirectory().compareTo(o2.getDirectory()); + } + }); + return reports; + } + + /** + * Returns all FileCoverageReport objects in the report. + * @return All FileCoverageReport objects in the report. + */ + public FileCoverageReport[] getFileReports(){ + return files; + } + + /** + * Returns the FileCoverageReport in the given position in the report. + * @param index The position in the report to retrieve. + * @return The FileCoverageReport for the position. + */ + public FileCoverageReport getFileReport(int index){ + return files[index]; + } + + /** + * Returns the FileCoverageReport associated with a given filename. + * @param filename The filename to retrieve. + * @return The FileCoverageReport if found or null if not found. + */ + public FileCoverageReport getFileReport(String filename){ + for (int i=0; i < files.length; i++){ + if (files[i].getFilename().equals(filename)){ + return files[i]; + } + } + return null; + } + + /** + * Returns the total number of lines tracked. + * @return The total number of lines tracked. + * @throws org.json.JSONException + */ + public int getCoveredLineCount() throws JSONException { + int sum = 0; + for (int i=0; i < files.length; i++){ + sum += files[i].getCoveredLineCount(); + } + return sum; + } + + /** + * Returns the number of called lines. + * @return The number of called lines. + * @throws org.json.JSONException + */ + public int getCalledLineCount() throws JSONException { + int sum = 0; + for (int i=0; i < files.length; i++){ + sum += files[i].getCalledLineCount(); + } + return sum; } + + /** + * Returns the percentage of lines called. + * @return The percentage of lines called. + * @throws org.json.JSONException + */ + public double getCalledLinePercentage() throws JSONException { + DecimalFormat twoDForm = new DecimalFormat("#.##"); + return Double.valueOf(twoDForm.format(((double) getCalledLineCount() / (double) getCoveredLineCount()) * 100)); + } + + /** + * Returns a string indicating the coverage level for lines in the file. + * This string is suitable for use in generating HTML reports. + * @return A string value of "high", "med", "low" depending on the coverage. + * @throws JSONException + */ + public String getCalledLinePercentageName() throws JSONException { + double percentage = getCalledLinePercentage(); + if (percentage >= 50){ + return "high"; + } else if (percentage <= 15){ + return "low"; + } else { + return "med"; + } + } + + /** + * Returns the total number of functions tracked. + * @return The total number of functions tracked. + * @throws org.json.JSONException + */ + public int getCoveredFunctionCount() throws JSONException { + int sum = 0; + for (int i=0; i < files.length; i++){ + sum += files[i].getCoveredFunctionCount(); + } + return sum; } + + /** + * Returns the number of functions that were called. + * @return The number of functions that were called. + * @throws org.json.JSONException + */ + public int getCalledFunctionCount() throws JSONException { + int sum = 0; + for (int i=0; i < files.length; i++){ + sum += files[i].getCalledFunctionCount(); + } + return sum; + } + + /** + * Returns the percentage of functions called. + * @return The percentage of functions called. + * @throws org.json.JSONException + */ + public double getCalledFunctionPercentage() throws JSONException { + DecimalFormat twoDForm = new DecimalFormat("#.##"); + return Double.valueOf(twoDForm.format(((double) getCalledFunctionCount() / (double) getCoveredFunctionCount()) * 100)); + } + + /** + * Returns a string indicating the coverage level for lines in the file. + * This string is suitable for use in generating HTML reports. + * @return A string value of "high", "med", "low" depending on the coverage. + * @throws JSONException + */ + public String getCalledFunctionPercentageName() throws JSONException { + double percentage = getCalledFunctionPercentage(); + if (percentage >= 90){ + return "high"; + } else if (percentage <= 75){ + return "low"; + } else { + return "med"; + } + } + + public JSONObject toJSONObject(){ + return data; + } + + /** + * Include another report's data in this report. + * @param otherReport The other report to merge. + */ + public void merge(SummaryCoverageReport otherReport) throws JSONException{ + + FileCoverageReport[] reports = otherReport.getFileReports(); + + boolean needsRegeneration = false; + + for (int i=0; i < reports.length; i++){ + FileCoverageReport fileReport = getFileReport(reports[i].getFilename()); + if (fileReport != null){ + fileReport.merge(reports[i]); + } else { + //need to add to the JSON object + data.put(reports[i].getFilename(), otherReport.toJSONObject().getJSONObject(reports[i].getFilename())); + needsRegeneration = true; + } + } + + //regenerate file reports if necessary + if (needsRegeneration){ + generateFileReports(); + } + } + +} diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/writers/AbstractStringTemplateReportWriter.java b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/writers/AbstractStringTemplateReportWriter.java new file mode 100644 index 000000000..637256a0e --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/writers/AbstractStringTemplateReportWriter.java @@ -0,0 +1,66 @@ +/* + * YUI Test + * Author: Nicholas C. Zakas <nzakas@yahoo-inc.com> + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ + +package com.yahoo.platform.yuitest.coverage.writers; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Writer; +import org.antlr.stringtemplate.StringTemplate; + +/** + * Provides basic string template loading for writers. + * @author Nicholas C. Zakas + */ +public abstract class AbstractStringTemplateReportWriter { + + protected Writer out; + protected String templateName; + protected StringTemplate template; + + protected AbstractStringTemplateReportWriter(Writer out, String templateName){ + this.out = out; + this.templateName = templateName; + this.template = getStringTemplate(); + } + + /** + * Closes the writer. + * @throws IOException + */ + public void close() throws IOException { + out.close(); + } + + /** + * Retrieves a StringTemplate with the given name from the JAR. + * @param name The name of the StringTemplate to retrieve. + * @return A StringTemplate object. + */ + protected StringTemplate getStringTemplate(){ + InputStream stream = null; + try { + stream = AbstractStringTemplateReportWriter.class.getResource(templateName + ".st").openStream(); + StringBuilder builder = new StringBuilder(); + int c; + while ((c = stream.read()) != -1) { + builder.append((char) c); + } + return new StringTemplate(builder.toString()); + + } catch (IOException ex) { + throw new IllegalArgumentException("Couldn't open " + templateName); + } finally { + try { + stream.close(); + } catch (IOException ex) { + //ignore + } + } + } +} diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/writers/FileReportWriter.java b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/writers/FileReportWriter.java new file mode 100644 index 000000000..a8d4eb928 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/writers/FileReportWriter.java @@ -0,0 +1,23 @@ +/* + * YUI Test + * Author: Nicholas C. Zakas <nzakas@yahoo-inc.com> + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ + +package com.yahoo.platform.yuitest.coverage.writers; + +import com.yahoo.platform.yuitest.coverage.results.FileCoverageReport; +import java.io.IOException; +import java.util.Date; + +/** + * + * @author Nicholas C. Zakas + */ +public interface FileReportWriter { + public void write(FileCoverageReport report) throws IOException; + public void write(FileCoverageReport report, Date date) throws IOException; + public void close() throws IOException; +} diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/writers/FileReportWriterFactory.java b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/writers/FileReportWriterFactory.java new file mode 100644 index 000000000..3d8300c0c --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/writers/FileReportWriterFactory.java @@ -0,0 +1,27 @@ +/* + * YUI Test + * Author: Nicholas C. Zakas <nzakas@yahoo-inc.com> + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ + +package com.yahoo.platform.yuitest.coverage.writers; + +import java.io.Writer; + +/** + * + * @author Nicholas C. Zakas + */ +public class FileReportWriterFactory { + + public static FileReportWriter getWriter(Writer out, String format) throws Exception { + try { + return new StringTemplateFileReportWriter(out, format); + } catch(Exception ex){ + throw new Exception(String.format("No writer for '%s' found.", format)); + } + } + +} diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/writers/GCOVFileReportTemplates.stg b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/writers/GCOVFileReportTemplates.stg new file mode 100644 index 000000000..afa268abb --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/writers/GCOVFileReportTemplates.stg @@ -0,0 +1,34 @@ +group GCOVFileReportTemplates; + +report(report,date) ::= << + -: 0:Source:$report.filename$ +$report.lines:line()$ +>> + +line() ::= << +$if(!it.covered)$ +$it:uncoveredline()$ +$elseif(it.called)$ +$it:calledline()$ +$else$ +$it:uncalledline()$ +$endif$ +>> + +uncoveredline() ::= << + -:$it:linedetail()$ +>> + +uncalledline() ::= << +#####:$it:linedetail()$ + +>> + +calledline() ::= << +$it.callCount;format="padLeft5"$:$it:linedetail()$ + +>> + +linedetail() ::= << +$it.lineNumber;format="padLeft5"$:$it.text$ +>> diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/writers/HTMLFileReportTemplates.stg b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/writers/HTMLFileReportTemplates.stg new file mode 100644 index 000000000..66dae9d69 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/writers/HTMLFileReportTemplates.stg @@ -0,0 +1,179 @@ +group HTMLFileReportTemplates; + +report(report,date) ::= << +<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" + "http://www.w3.org/TR/html4/strict.dtd"> +<html lang="en-US"> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> + <title>$report.filename$ Report + + + + +
+ +
+ +

$report.filename$ Report

+

Date Generated: $date$

+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
CategoryCalledTotalCoverage
Lines$report.calledLineCount$$report.totalLineCount$$report.calledLinePercentage$%
Functions$report.calledFunctionCount$$report.totalFunctionCount$$report.calledFunctionPercentage$%
+ + +
+ + $report.lines:line()$ +
+
+
+ + +
+

Code coverage for $report.filename$ generated on $date$ by YUI Test.

+
+
+ + + +>> + +line() ::= << + + $it.lineNumber$$it:callcount()$$it.text; format="htmlEscapeSpace"$ + +>> + +rowclass() ::= << +$if(!it.covered)$ +uncovered +$elseif(it.called)$ +called +$else$ +not-called +$endif$ +>> + +callcount() ::= << +$if(!it.covered)$ +  +$else$ +$it.callCount$ +$endif$ +>> \ No newline at end of file diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/writers/HTMLSummaryReportTemplates.stg b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/writers/HTMLSummaryReportTemplates.stg new file mode 100644 index 000000000..ddbdfb117 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/writers/HTMLSummaryReportTemplates.stg @@ -0,0 +1,115 @@ +group HTMLFileReportTemplates; + +report(report,date) ::= << + + + + + Code Coverage Report + + + + +
+ +
+ +

Code Coverage Report

+

Date Generated: $date$

+
+ +
+ + + + + + + + + + + + $report.fileReports:file()$ + +
FilenameLinesFunctions
+
+ + +
+

Code coverage report generated on $date$ by YUI Test.

+
+
+ + + +>> + +file() ::= << + + $it.filename$ + $it.calledLineCount$/$it.totalLineCount$ ($it.calledLinePercentage$%) + $it.calledFunctionCount$/$it.totalFunctionCount$ ($it.calledFunctionPercentage$%) + +>> \ No newline at end of file diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/writers/HTMLSummaryReportWriter.java b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/writers/HTMLSummaryReportWriter.java new file mode 100644 index 000000000..57255158d --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/writers/HTMLSummaryReportWriter.java @@ -0,0 +1,21 @@ +/* + * YUI Test + * Author: Nicholas C. Zakas + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ + +package com.yahoo.platform.yuitest.coverage.writers; + +import java.io.Writer; + +/** + * + * @author Nicholas C. Zakas + */ +public class HTMLSummaryReportWriter extends StringTemplateSummaryReportWriter { + public HTMLSummaryReportWriter(Writer out) { + super(out, "HTML"); + } +} diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/writers/StringTemplateFileReportWriter.java b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/writers/StringTemplateFileReportWriter.java new file mode 100644 index 000000000..e6995f2df --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/writers/StringTemplateFileReportWriter.java @@ -0,0 +1,114 @@ +/* + * YUI Test + * Author: Nicholas C. Zakas + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ + +package com.yahoo.platform.yuitest.coverage.writers; + +import com.yahoo.platform.yuitest.coverage.results.FileCoverageReport; +import com.yahoo.platform.yuitest.results.TestReport; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Writer; +import java.util.Date; +import org.antlr.stringtemplate.AttributeRenderer; +import org.antlr.stringtemplate.StringTemplate; +import org.antlr.stringtemplate.StringTemplateGroup; +import org.antlr.stringtemplate.language.DefaultTemplateLexer; + +/** + * + * @author Nicholas C. Zakas + */ +public class StringTemplateFileReportWriter implements FileReportWriter { + + protected Writer out; + protected StringTemplateGroup templateGroup; + + public StringTemplateFileReportWriter(Writer out, String format) throws IOException { + this.out = out; + this.templateGroup = getStringTemplateGroup(format); + } + + private StringTemplateGroup getStringTemplateGroup(String format) throws IOException{ + //get string template group + InputStream stgstream = StringTemplateFileReportWriter.class.getResourceAsStream(format + "FileReportTemplates.stg"); + InputStreamReader reader = new InputStreamReader(stgstream); + StringTemplateGroup group = new StringTemplateGroup(reader, DefaultTemplateLexer.class); + reader.close(); + return group; + } + + public void write(FileCoverageReport report) throws IOException { + write(report, new Date()); + } + + public void write(FileCoverageReport report, Date date) throws IOException { + StringTemplate template = templateGroup.getInstanceOf("report"); + template.setAttribute("report", report); + template.setAttribute("date", date); + + //renderer for strings + template.registerRenderer(String.class, new AttributeRenderer(){ + + public String toString(Object o) { + return o.toString(); + } + + public String toString(Object o, String format) { + if (format.equals("classname")){ + return o.toString().replace(TestReport.PATH_SEPARATOR, ".").replaceAll("[^a-zA-Z0-9\\\\.]", ""); + } else if (format.equals("xmlEscape")){ + return o.toString().replace("&", "&").replace(">", ">").replace("<", "<").replace("\"", """).replace("'", "'"); + } else if (format.equals("htmlEscape")){ + return o.toString().replace("&", "&").replace("\"", """).replace("<", "<").replace(">", ">"); + } else if (format.equals("htmlEscapeSpace")){ + return o.toString().replace("&", "&").replace("\"", """).replace("<", "<").replace(">", ">").replace(" ", " "); + } else { + return o.toString(); + } + } + }); + + //renderer for numbers + template.registerRenderer(Integer.class, new AttributeRenderer(){ + + public String toString(Object o) { + return o.toString(); + } + + public String toString(Object o, String format) { + if (format.equals("ms_to_s")){ + return String.valueOf(Double.parseDouble(o.toString()) / 1000); + } else if (format.equals("padLeft5")){ + int value = Integer.parseInt(o.toString()); + String result = o.toString(); + if (value > 9999){ + return result; + } else if (value > 999){ + return " " + result; + } else if (value > 99){ + return " " + result; + } else if (value > 9){ + return " " + result; + } else { + return " " + result; + } + } else { + return o.toString(); + } + } + }); + + + out.write(template.toString()); + } + + public void close() throws IOException { + out.close(); + } +} diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/writers/StringTemplateSummaryReportWriter.java b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/writers/StringTemplateSummaryReportWriter.java new file mode 100644 index 000000000..6047752cb --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/writers/StringTemplateSummaryReportWriter.java @@ -0,0 +1,48 @@ +/* + * YUI Test + * Author: Nicholas C. Zakas + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ + +package com.yahoo.platform.yuitest.coverage.writers; + +import com.yahoo.platform.yuitest.coverage.results.SummaryCoverageReport; +import java.io.IOException; +import java.io.Writer; +import java.util.Date; + +/** + * Provides basic string template loading for writers. + * @author Nicholas C. Zakas + */ +public class StringTemplateSummaryReportWriter extends AbstractStringTemplateReportWriter + implements SummaryReportWriter { + + public StringTemplateSummaryReportWriter(Writer out, String templateName){ + super(out, templateName + "SummaryReportTemplate"); + } + + /** + * Passthrough to overloaded write() with a date representing now. + * @param report The file report to write + * @throws IOException + */ + public void write(SummaryCoverageReport report) throws IOException { + write(report, new Date()); + } + + /** + * Writes a report out to the writer. + * @param report The report to write out. + * @param date The date to specify in the report. + * @throws IOException + */ + public void write(SummaryCoverageReport report, Date date) throws IOException { + template.setAttribute("report", report); + template.setAttribute("date", date); + out.write(template.toString()); + } + +} diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/writers/SummaryReportWriter.java b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/writers/SummaryReportWriter.java new file mode 100644 index 000000000..7485dbdc3 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/coverage/writers/SummaryReportWriter.java @@ -0,0 +1,23 @@ +/* + * YUI Test Coverage + * Author: Nicholas C. Zakas + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ + +package com.yahoo.platform.yuitest.coverage.writers; + +import com.yahoo.platform.yuitest.coverage.results.SummaryCoverageReport; +import java.io.IOException; +import java.util.Date; + +/** + * + * @author Nicholas C. Zakas + */ +public interface SummaryReportWriter { + public void write(SummaryCoverageReport report) throws IOException; + public void write(SummaryCoverageReport report, Date date) throws IOException; + public void close() throws IOException; +} diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/results/Test.java b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/results/Test.java new file mode 100644 index 000000000..b2a65401a --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/results/Test.java @@ -0,0 +1,111 @@ +/* + * YUI Test + * Author: Nicholas C. Zakas + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ + +package com.yahoo.platform.yuitest.results; + +/** + * Represents a single test result. + * @author Nicholas C. Zakas + */ +public class Test { + + public static final int PASS = 0; + public static final int FAIL = 1; + public static final int IGNORE = 2; + + private String name; + private int result; + private String message; + private String stackTrace; + private int duration; + private TestCase parent; + + protected Test(String name, int duration, int result, String message) { + this.name = name; + this.result = result; + this.message = message; + this.duration = duration; + } + + public String getMessage() { + return message; + } + + public String getName() { + return name; + } + + public int getResult() { + return result; + } + + public String getResultText(){ + switch(result){ + case PASS: return "pass"; + case FAIL: return "fail"; + default: return "ignore"; + } + } + + public String getStackTrace() { + return stackTrace; + } + + public int getDuration(){ + return duration; + } + + protected void setStackTrace(String stackTrace){ + this.stackTrace = stackTrace; + } + + public TestCase getParent(){ + return parent; + } + + protected void setParent(TestCase parent){ + this.parent = parent; + } + + public String getPath(){ + return getPath(TestReport.PATH_SEPARATOR); + } + + public String getPath(String separator){ + String path = ""; + if (parent != null){ + path = parent.getFullPath(); + } + return path; + } + + public String getFullPath(){ + return getFullPath(TestReport.PATH_SEPARATOR); + } + + public String getFullPath(String separator){ + String fullPath = getPath(separator); + if (fullPath.length() > 0){ + fullPath += separator; + } + fullPath += name; + return fullPath; + } + + public boolean isFailed(){ + return result == FAIL; + } + + public boolean isIgnored(){ + return result == IGNORE; + } + + public boolean isPassed(){ + return result == PASS; + } +} diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/results/TestCase.java b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/results/TestCase.java new file mode 100644 index 000000000..5ef368a61 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/results/TestCase.java @@ -0,0 +1,123 @@ +/* + * YUI Test + * Author: Nicholas C. Zakas + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ + +package com.yahoo.platform.yuitest.results; + +import java.util.LinkedList; +import java.util.List; + +/** + * + * @author Nicholas C. Zakas + */ +public class TestCase { + + private String name; + private int duration; + private List tests; + private int passed = 0; + private int failed = 0; + private int ignored = 0; + private TestSuite parent; + + protected TestCase(String name, int duration, int passed, int failed, int ignored){ + this.name = name; + this.duration = duration; + this.passed = passed; + this.failed = failed; + this.ignored = ignored; + this.tests = new LinkedList(); + } + + protected void addTest(Test test){ + tests.add(test); + test.setParent(this); + } + + public int getDuration() { + return duration; + } + + public int getFailed() { + return failed; + } + + public int getIgnored() { + return ignored; + } + + public String getName() { + return name; + } + + public int getPassed() { + return passed; + } + + public Test[] getTests(){ + return tests.toArray(new Test[tests.size()]); + } + + public TestSuite getParent(){ + return parent; + } + + protected void setParent(TestSuite parent){ + this.parent = parent; + } + + public int getTotal(){ + return passed + failed; + } + + public int getTotalIncludingIgnored(){ + return getTotal() + ignored; + } + + public String getPath(){ + return getPath(TestReport.PATH_SEPARATOR); + } + + + public String getPath(String separator){ + String path = ""; + if (parent != null){ + path = parent.getFullPath(); + } + return path; + } + + public String getFullPath(){ + return getFullPath(TestReport.PATH_SEPARATOR); + } + + public String getFullPath(String separator){ + String fullPath = getPath(separator); + if (fullPath.length() > 0){ + fullPath += separator; + } + fullPath += name; + return fullPath; + } + + public String[] getFailureMessages(){ + List messages = new LinkedList(); + for (int i=0; i < tests.size(); i++){ + if (tests.get(i).getResult() == Test.FAIL){ + messages.add(String.format("%s: %s", tests.get(i).getName(), + tests.get(i).getMessage())); + } + } + return messages.toArray(new String[messages.size()]); + } + + public boolean hasResults(){ + return getTotal() > 0; + } + +} diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/results/TestReport.java b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/results/TestReport.java new file mode 100644 index 000000000..3ecfd6baf --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/results/TestReport.java @@ -0,0 +1,91 @@ +/* + * YUI Test + * Author: Nicholas C. Zakas + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ + +package com.yahoo.platform.yuitest.results; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import org.xml.sax.InputSource; + +/** + * + * @author Nicholas C. Zakas + */ +public class TestReport extends TestSuite { + + public static final String PATH_SEPARATOR = "\\"; + + private String browser = ""; + + protected TestReport(String name, int duration, int passed, int failed, int ignored) { + super(name, duration, passed, failed, ignored); + } + + protected void setBrowser(String browser){ + this.browser = browser; + } + + public String getBrowser(){ + return browser; + } + + @Override + public String getPath(String separator){ + return "YUITest" + (!browser.equals("") ? separator + browser : ""); + } + + public static TestReport load(File file) throws IOException { + return load(file, ""); + } + + public static TestReport load(File file, String browser) throws IOException { + return load(new FileInputStream(file), browser); + } + + public static TestReport load(InputStream in) throws IOException { + return load(in, ""); + } + + public static TestReport load(InputStream in, String browser) throws IOException { + return load(new InputSource(in), browser); + } + + public static TestReport load(Reader in) throws IOException { + return load(in, ""); + } + + public static TestReport load(Reader in, String browser) throws IOException { + return load(new InputSource(in), browser); + } + + public static TestReport load(InputSource in) throws IOException { + return load(in, ""); + } + + public static TestReport load(InputSource in, String browser) throws IOException { + SAXParserFactory spf = SAXParserFactory.newInstance(); + SAXParser parser = null; + TestReportXMLHandler handler = new TestReportXMLHandler(browser); + + try { + parser = spf.newSAXParser(); + parser.parse(in, handler); + } catch (Exception ex) { + throw new IOException("XML could not be parsed."); + } + + return handler.getTestReport(); + + } + +} diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/results/TestReportXMLHandler.java b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/results/TestReportXMLHandler.java new file mode 100644 index 000000000..4d98bdafa --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/results/TestReportXMLHandler.java @@ -0,0 +1,101 @@ +/* + * YUI Test + * Author: Nicholas C. Zakas + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ + +package com.yahoo.platform.yuitest.results; + +import java.util.Stack; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +/** + * + * @author Nicholas C. Zakas + */ +public class TestReportXMLHandler extends DefaultHandler { + + private TestReport report = null; + private Stack suites = null; + private TestCase curTestCase = null; + private String browser=""; + + public TestReportXMLHandler(){ + suites = new Stack(); + } + + public TestReportXMLHandler(String browser) { + this(); + this.browser = browser; + } + + public TestReport getTestReport(){ + return report; + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + if (qName.equals("testsuite")){ + suites.pop(); + } + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + if (qName.equals("report")){ + report = new TestReport(attributes.getValue("name"), + Integer.parseInt(attributes.getValue("duration")), + Integer.parseInt(attributes.getValue("passed")), + Integer.parseInt(attributes.getValue("failed")), + Integer.parseInt(attributes.getValue("ignored"))); + report.setBrowser(browser); + suites.push(report); + } else if (qName.equals("testsuite")){ + TestSuite suite = new TestSuite(attributes.getValue("name"), + Integer.parseInt(attributes.getValue("duration")), + Integer.parseInt(attributes.getValue("passed")), + Integer.parseInt(attributes.getValue("failed")), + Integer.parseInt(attributes.getValue("ignored"))); + + //if there's another suite, add as a child + suites.peek().addTestSuite(suite); + suites.push(suite); + } else if (qName.equals("testcase")){ + TestCase testCase = new TestCase(attributes.getValue("name"), + Integer.parseInt(attributes.getValue("duration")), + Integer.parseInt(attributes.getValue("passed")), + Integer.parseInt(attributes.getValue("failed")), + Integer.parseInt(attributes.getValue("ignored"))); + + //if there's another suite, add as a child + suites.peek().addTestCase(testCase); + curTestCase = testCase; + } else if (qName.equals("test")){ + + //figure out the result + String xmlResult = attributes.getValue("result"); + int result = Test.PASS; + if (xmlResult.equals("fail")){ + result = Test.FAIL; + } else if (xmlResult.equals("ignore")){ + result = Test.IGNORE; + } + + int duration = 0; + if (attributes.getValue("duration") != null){ + duration = Integer.parseInt(attributes.getValue("duration")); + } + + Test test = new Test(attributes.getValue("name"), + duration, result, attributes.getValue("message")); + + curTestCase.addTest(test); + } + } + + +} diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/results/TestSuite.java b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/results/TestSuite.java new file mode 100644 index 000000000..9bc94171f --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/results/TestSuite.java @@ -0,0 +1,137 @@ +/* + * YUI Test + * Author: Nicholas C. Zakas + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ + +package com.yahoo.platform.yuitest.results; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +/** + * + * @author Nicholas C. Zakas + */ +public class TestSuite { + + private String name; + private int duration; + private int passed; + private int failed; + private int ignored; + private List testCases; + private List testSuites; + private TestSuite parent; + + protected TestSuite(String name, int duration, int passed, int failed, int ignored) { + this.name = name; + this.duration = duration; + this.passed = passed; + this.failed = failed; + this.ignored = ignored; + this.testSuites = new LinkedList(); + this.testCases = new LinkedList(); + } + + protected void addTestSuite(TestSuite suite){ + testSuites.add(suite); + suite.setParent(this); + } + + protected void addTestCase(TestCase testCase){ + testCases.add(testCase); + testCase.setParent(this); + } + + public int getDuration() { + return duration; + } + + public int getFailed() { + return failed; + } + + public String getName() { + return name; + } + + public int getPassed() { + return passed; + } + + public int getIgnored() { + return ignored; + } + + public TestSuite[] getTestSuites(){ + return testSuites.toArray(new TestSuite[testSuites.size()]); + } + + public TestCase[] getTestCases(){ + return testCases.toArray(new TestCase[testCases.size()]); + } + + public TestSuite getParent(){ + return parent; + } + + protected void setParent(TestSuite parent){ + this.parent = parent; + } + + public int getTotal(){ + return passed + failed; + } + + public int getTotalIncludingIgnored(){ + return getTotal() + ignored; + } + + public String getPath(){ + return getPath(TestReport.PATH_SEPARATOR); + } + + public String getPath(String separator){ + String path = ""; + if (parent != null){ + path = parent.getFullPath(); + } + return path; + } + + public String getFullPath(){ + return getFullPath(TestReport.PATH_SEPARATOR); + } + + public String getFullPath(String separator){ + String fullPath = getPath(separator); + if (fullPath.length() > 0){ + fullPath += separator; + } + fullPath += name; + return fullPath; + } + + public String[] getFailureMessages(){ + List messages = new LinkedList(); + + for (int i=0; i < testSuites.size(); i++){ + messages.addAll(Arrays.asList(testSuites.get(i).getFailureMessages())); + } + + for (int i=0; i < testCases.size(); i++){ + messages.addAll(Arrays.asList(testCases.get(i).getFailureMessages())); + } + + return messages.toArray(new String[messages.size()]); + } + + public boolean hasResults(){ + return getTotal() > 0; + } + +} diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/selenium/RawTestResultsParser.java b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/selenium/RawTestResultsParser.java new file mode 100644 index 000000000..31259cbba --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/selenium/RawTestResultsParser.java @@ -0,0 +1,63 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package com.yahoo.platform.yuitest.selenium; + +import java.io.InputStream; +import java.util.LinkedList; +import java.util.List; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +/** + * + * @author Nicholas C. Zakas + */ +public class RawTestResultsParser { + + /** + * Parses a raw YUI Test XML results stream and returns a SessionResult object. + * @param in The stream to read the results from. + * @throws Exception If a parsing error occurs. + */ + public static SessionResult parse(InputStream in, final SessionResult result) throws Exception { + + SAXParserFactory spf = SAXParserFactory.newInstance(); + SAXParser parser = null; + final List messages = new LinkedList(); + + try { + parser = spf.newSAXParser(); + parser.parse(in, new DefaultHandler(){ + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { +// if (qName.equals("test")){ +// String testResult = attributes.getValue("result"); +// if (testResult.equals("fail")){ +// messages.add(String.format("%s: %s", attributes.getValue("name"), attributes.getValue("message"))); +// } +// } else if (qName.equals("report")){ +// result.setFailed(Integer.parseInt(attributes.getValue("failed"))); +// result.setPassed(Integer.parseInt(attributes.getValue("passed"))); +// result.setIgnored(Integer.parseInt(attributes.getValue("ignored"))); +// } + + } + + }); + result.setMessages(messages.toArray(new String[messages.size()])); + return result; + } catch (ParserConfigurationException ex) { + throw ex; + } + + } + +} diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/selenium/SeleniumDriver.java b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/selenium/SeleniumDriver.java new file mode 100644 index 000000000..d1022267f --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/selenium/SeleniumDriver.java @@ -0,0 +1,470 @@ +/* + * YUI Test Selenium Driver + * Author: Nicholas C. Zakas + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ + +package com.yahoo.platform.yuitest.selenium; + +import com.yahoo.platform.yuitest.config.TestPageGroup; +import com.yahoo.platform.yuitest.config.TestPage; +import com.yahoo.platform.yuitest.config.TestConfig; +import com.thoughtworks.selenium.DefaultSelenium; +import com.thoughtworks.selenium.Selenium; +import com.thoughtworks.selenium.SeleniumException; +import com.yahoo.platform.yuitest.coverage.results.SummaryCoverageReport; +import com.yahoo.platform.yuitest.results.TestReport; +import java.io.StringReader; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Properties; + +/** + * Controls Selenium to extract YUI Test information. + * @author Nicholas C. Zakas + */ +public class SeleniumDriver { + + //-------------------------------------------------------------------------- + // Constants + //-------------------------------------------------------------------------- + + public static final String YUITEST_VERSION = "yuitest.version"; + public static final String YUITEST_TIMEOUT = "yuitest.timeout"; + public static final String YUITEST_TESTS_FILE = "yuitest.tests"; + + public static final String COVERAGE_OUTPUTDIR = "coverage.outputdir"; + public static final String COVERAGE_FORMAT = "coverage.format"; + + public static final String RESULTS_OUTPUTDIR = "results.outputdir"; + public static final String RESULTS_FILENAME = "results.filename"; + public static final String RESULTS_FORMAT = "results.format"; + + + public static final String SELENIUM_HOST = "selenium.host"; + public static final String SELENIUM_PORT = "selenium.port"; + public static final String SELENIUM_BROWSERS = "selenium.browsers"; + + public static final String SELENIUM_WAIT_FOR_LOAD = "selenium.waitforload"; + public static final String ERROR_ON_FAIL = "console.erroronfail"; + + public static final String CONSOLE_MODE = "console.enabled"; + + //-------------------------------------------------------------------------- + // Private Static + //-------------------------------------------------------------------------- + + private static HashMap testRunners = + new HashMap(); + private static HashMap testFormats = + new HashMap(); + private static HashMap coverageFormats = + new HashMap(); + private static final String jsWindow = "selenium.browserbot.getCurrentWindow()"; + + static { + testRunners.put("2", "YAHOO.tool.TestRunner"); + testRunners.put("3", "Y.Test.Runner"); + testRunners.put("4", "YUITest.TestRunner"); + + testFormats.put("2", "YAHOO.tool.TestFormat"); + testFormats.put("3", "Y.Test.Format"); + testFormats.put("4", "YUITest.TestFormat"); + + coverageFormats.put("2", "YAHOO.tool.CoverageFormat"); + coverageFormats.put("3", "Y.Coverage.Format"); + coverageFormats.put("4", "YUITest.CoverageFormat"); + } + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * Configuration properties for the instance. + */ + private Properties properties; + + /** + * Indicates if additional information should be output to the console. + */ + private boolean verbose = false; + + /** + * The list of Selenium browsers to test. + */ + private String[] browsers; + + /** + * Collection of error messages. + */ + private LinkedList errors = new LinkedList(); + + //-------------------------------------------------------------------------- + // Constructors + //-------------------------------------------------------------------------- + + /** + * Creates a new instance of the Selenium driver based on the given + * properties. + * @param properties Properties defining how the driver should act. + */ + public SeleniumDriver(Properties properties) throws Exception { + this.properties = properties; + getBrowserList(); + } + + /** + * Creates a new instance of the Selenium driver based on the given + * properties. + * @param properties Properties defining how the driver should act. + * @param verbose Indicates if additional information should be output to + * the console. + */ + public SeleniumDriver(Properties properties, boolean verbose) throws Exception { + this.properties = properties; + this.verbose = verbose; + getBrowserList(); + } + + /** + * Returns a list of error messages. + */ + public String[] getErrors(){ + return errors.toArray(new String[errors.size()]); + } + + //-------------------------------------------------------------------------- + // Methods to run tests + //-------------------------------------------------------------------------- + + /** + * Runs all tests contained in the config object. + * @param config Information about tests to run. + * @return An array of test results based on the tests that were run. + * @throws Exception When a test cannot be run. + */ + public SessionResult[] runTests(TestConfig config) throws Exception { + return runTests(config.getGroups()); + } + + /** + * Runs all tests contained in the TestPageGroup objects. + * @param groups Information about tests to run. + * @return An array of test results based on the tests that were run. + * @throws Exception When a test cannot be run. + */ + public SessionResult[] runTests(TestPageGroup[] groups) throws Exception { + + List results = new LinkedList(); + + //do the tests + for(int i=0; i < groups.length; i++){ + for (int j=0; j < browsers.length; j++){ + results.addAll(runTestGroup(browsers[j], groups[i])); + } + } + + return results.toArray(new SessionResult[results.size()]); + } + + /** + * Runs all tests contained in the TestPageGroup object. + * @param groups Information about tests to run. + * @return An array of test results based on the tests that were run. + * @throws Exception When a test cannot be run. + */ + public SessionResult[] runTests(TestPageGroup group) throws Exception { + List results = new LinkedList(); + + for (int j=0; j < browsers.length; j++){ + results.addAll(runTestGroup(browsers[j], group)); + } + + return results.toArray(new SessionResult[results.size()]); + } + + //-------------------------------------------------------------------------- + // Run a test page group + //-------------------------------------------------------------------------- + + /** + * Runs all tests in a test group on the given browser. First, determines + * the most optimal way to run the tests, either by using a single Selenium + * instance or by creating multiple Selenium instances. + * @param browser The Selenium browser name to run the tests on. + * @param group The TestPageGroup containing tests to run. + * @return A list of SessionResult objects. + * @throws Exception If any of the tests error out. + */ + private List runTestGroup(String browser, TestPageGroup group) + throws Exception { + + //if there's a common base, try to optimize + if (group.getBase().length() > 0){ + return runTestGroupOpt(browser, group); + } else { + return runTestGroupUnopt(browser, group); + } + } + + /** + * Runs all tests in a test group using a single Selenium instance. + * @param browser The Selenium browser name to run the tests on. + * @param group The TestPageGroup containing tests to run. + * @return A list of SessionResult objects. + * @throws Exception If any of the tests error out. + */ + private List runTestGroupOpt(String browser, TestPageGroup group) + throws Exception { + + TestPage[] testpages = group.getTestPages(); + List results = new LinkedList(); + Selenium selenium = startBrowser(browser, group.getBase()); + + + for (int i=0; i < testpages.length; i++){ + try { + results.add(runTestPage(selenium, browser, testpages[i])); + } catch (Exception ex){ + errors.add(ex.getMessage()); + } + } + + + selenium.stop(); + + return results; + } + + /** + * Runs all tests in a test group using multiple Selenium instances. + * @param browser The Selenium browser name to run the tests on. + * @param group The TestPageGroup containing tests to run. + * @return A list of SessionResult objects. + * @throws Exception If any of the tests error out. + */ + private List runTestGroupUnopt(String browser, TestPageGroup group) + throws Exception { + + TestPage[] testpages = group.getTestPages(); + List results = new LinkedList(); + Selenium selenium = null; + + try { + for (int i=0; i < testpages.length; i++){ + selenium = startBrowser(browser, testpages[i].getAbsolutePath()); + results.add(runTestPage(selenium, browser, testpages[i])); + } + } catch (Exception ex){ + throw ex; + } finally { + if (selenium != null){ + selenium.stop(); + } + } + + return results; + } + + //-------------------------------------------------------------------------- + // Run a single test page + //-------------------------------------------------------------------------- + + /** + * Runs a single test page in a given browser. It first creates a selenium + * instance for the given browser and then runs the tests. + * @param browser The Selenium name of the browser being run. + * @param page The test page to run. + * @return The results of the test being run. + * @throws Exception If there's an error while running the test. + */ + public SessionResult runTestPage(String browser, TestPage page) throws Exception { + + Selenium selenium = null; + String url = page.getAbsolutePath(); + + try { + selenium = new DefaultSelenium(properties.getProperty(SELENIUM_HOST), + Integer.parseInt(properties.getProperty(SELENIUM_PORT)), browser, url); + + selenium.start(); + + return runTestPage(selenium, browser, page); + + } catch (Exception ex){ + //TODO: What should happen here? Default file generation? + throw ex; + } finally { + if (selenium != null){ + selenium.stop(); + } + } + + } + + /** + * Runs a single test page in a given browser. + * @param selenium The Selenium object to use to run the test. + * @param browser The Selenium name of the browser being run. + * @param page The test page to run. + * @return The results of the test being run. + * @throws Exception If there's an error while running the test. + */ + private SessionResult runTestPage(Selenium selenium, String browser, + TestPage page) throws Exception { + + //basic YUI Test info + String yuitestVersion = String.valueOf(page.getVersion()); + String testRunner = jsWindow + "." + testRunners.get(yuitestVersion); + String testFormat = jsWindow + "." + testFormats.get(yuitestVersion); + String coverageFormat = jsWindow + "." + coverageFormats.get(yuitestVersion); + + //JS strings to use + String testRunnerIsNotRunning = "!" + testRunner + ".isRunning()"; + String testRawResults = testRunner + ".getResults(" + testFormat + ".XML);"; + String testCoverage = testRunner + ".getCoverage(" + coverageFormat + ".JSON);"; + String testName = testRunner + ".getName();"; + String rawResults = ""; + String coverageResults = ""; + String name = ""; + + //page info + String url = page.getAbsolutePath(); + String pageTimeout = String.valueOf(page.getTimeout()); + if (pageTimeout.equals("-1")){ + pageTimeout = properties.getProperty(YUITEST_TIMEOUT, "10000"); + } + + //run the tests + try { + + selenium.open(url); + + if (!isSilent()){ + System.out.printf("\nRunning %s\n", url); + } + + if (verbose){ + System.err.println("[INFO] Navigating to '" + url + "'"); + } + + selenium.waitForPageToLoad(properties.getProperty(SELENIUM_WAIT_FOR_LOAD, "10000")); + + if (verbose){ + System.err.println("[INFO] Page is loaded."); + } + + selenium.waitForCondition("(function(){ try { return " + testRunnerIsNotRunning + "}catch(ex){return false}})()", pageTimeout); + + if (verbose){ + System.err.println("[INFO] Test complete."); + } + + rawResults = selenium.getEval(testRawResults); + if (rawResults.equals("null")){ + rawResults = null; + } + + coverageResults = selenium.getEval(testCoverage); + if (coverageResults.equals("null")){ + coverageResults = null; + } + + name = selenium.getEval(testName); + + //some basic error checking, make sure we have some results! + if (rawResults == null){ + throw new Exception("Couldn't retrieve test results. Please double-check that the test (" + url + ") is running correctly."); + } + + TestReport testReport = TestReport.load(new StringReader(rawResults), browser.replace("*", "")); + SessionResult result = new SessionResult(name, browser.replace("*", ""), url); + result.setTestReport(testReport); + + SummaryCoverageReport coverageReport = null; + if (coverageResults != null){ + coverageReport = new SummaryCoverageReport(new StringReader(coverageResults)); + result.setCoverageReport(coverageReport); + } + + //output results detail + if (!isSilent()){ + + System.out.printf("Testsuite: %s on %s\n", result.getName(), result.getBrowser()); + System.out.printf("Tests run: %d, Failures: %d, Ignored: %d\n", result.getTotal(), result.getFailed(), result.getIgnored()); + + if (result.getTotal() == 0){ + System.out.printf("Warning: No tests were run. Check the test page '%s'.\n", result.getName()); + } + + String messages[] = testReport.getFailureMessages(); + if (messages.length > 0){ + System.out.println(); + for (int i=0; i < messages.length; i++){ + System.out.println(messages[i]); + } + } + } + + //determine if a failure should throw an error + if (!properties.getProperty(ERROR_ON_FAIL).equals("0") && result.getFailed() > 0){ + throw new Exception(String.format("There were %d failures in %s on %s", result.getFailed(), result.getName(), result.getBrowser())); + } + + return result; + + } catch (SeleniumException ex){ + + //probably not a valid page + throw new Exception("Selenium failed with message: " + ex.getMessage() + ". Check the test URL " + url + " to ensure it is valid.", ex); + + } catch (Exception ex){ + throw ex; + } + + } + + //-------------------------------------------------------------------------- + // Helper methods + //-------------------------------------------------------------------------- + + /** + * Splits the comma-delimited list of browsers into an array of strings. + * @throws Exception If there are no browsers specified. + */ + private void getBrowserList() throws Exception { + + browsers = (properties.getProperty("selenium.browsers", "")).split("\\,"); + if(browsers.length == 0){ + throw new Exception("The configuration property 'selenium.browsers' is missing."); + } + } + + /** + * Creates a Selenium instance for the given browser and with the given base + * URL. The instance is then started and returned. + * @param browser The Selenium browser name to create an instance for. + * @param base The base URL to test. + * @return A Selenium instance based on the given information. + */ + private Selenium startBrowser(String browser, String base){ + Selenium selenium = new DefaultSelenium(properties.getProperty(SELENIUM_HOST), + Integer.parseInt(properties.getProperty(SELENIUM_PORT)), browser, base); + + selenium.start(); + + return selenium; + } + + /** + * Determines if the driver is being run in silent mode. + * @return True if the driver is in silent mode, false if not. + */ + private boolean isSilent(){ + return properties.getProperty(CONSOLE_MODE, "normal").equals("silent"); + } +} diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/selenium/SessionResult.java b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/selenium/SessionResult.java new file mode 100644 index 000000000..b1aecdc52 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/selenium/SessionResult.java @@ -0,0 +1,93 @@ +/* + * YUI Test Selenium Driver + * Author: Nicholas C. Zakas + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ + +package com.yahoo.platform.yuitest.selenium; + +import com.yahoo.platform.yuitest.coverage.results.SummaryCoverageReport; +import com.yahoo.platform.yuitest.results.TestReport; + +/** + * Represents the relevant data related to a test run. + * @author Nicholas C. Zakas + */ +public class SessionResult { + + public static final String XML_FORMAT = "XML"; + public static final String JSON_FORMAT = "JSON"; + public static final String JUNIT_XML_FORMAT = "JUnitXML"; + public static final String TAP_FORMAT = "TAP"; + public static final String GCOV_FORMAT = "GCOV"; + public static final String LCOV_FORMAT = "LCOV"; + + private String url; + private String browser; +// private String resultsReportText = null; +// private String coverageReportText = null; + private String name; + private String[] messages; + private TestReport testReport; + private SummaryCoverageReport coverageReport; + + protected SessionResult(String name, String browser, String url) { + this.url = url; + this.browser = browser; + this.name = name; + } + + public String getBrowser() { + return browser; + } + + public String getName() { + return name; + } + + public String getUrl() { + return url; + } + + public int getFailed() { + return testReport.getFailed(); + } + + public int getIgnored() { + return testReport.getIgnored(); + } + + public String[] getMessages() { + return messages; + } + + protected void setMessages(String[] messages) { + this.messages = messages; + } + + public int getPassed() { + return testReport.getPassed(); + } + + public int getTotal(){ + return testReport.getTotal(); + } + + public void setTestReport(TestReport report){ + testReport = report; + } + + public TestReport getTestReport(){ + return testReport; + } + + public void setCoverageReport(SummaryCoverageReport report){ + coverageReport = report; + } + + public SummaryCoverageReport getCoverageReport(){ + return coverageReport; + } +} diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/selenium/SessionResultFileGenerator.java b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/selenium/SessionResultFileGenerator.java new file mode 100644 index 000000000..a5394a08f --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/selenium/SessionResultFileGenerator.java @@ -0,0 +1,137 @@ +/* + * YUI Test + * Author: Nicholas C. Zakas + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ + +package com.yahoo.platform.yuitest.selenium; + +import com.yahoo.platform.yuitest.coverage.report.CoverageReportGenerator; +import com.yahoo.platform.yuitest.coverage.report.CoverageReportGeneratorFactory; +import com.yahoo.platform.yuitest.coverage.results.SummaryCoverageReport; +import com.yahoo.platform.yuitest.results.TestReport; +import com.yahoo.platform.yuitest.writers.ReportWriter; +import com.yahoo.platform.yuitest.writers.ReportWriterFactory; +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import java.util.Properties; + +/** + * Handles generating results files for a SeleniumDriver with the same set of + * properties. + * @author Nicholas C. Zakas + */ +public class SessionResultFileGenerator { + + private boolean verbose = false; + private Properties properties = null; + + public SessionResultFileGenerator(Properties properties, boolean verbose){ + this.properties = properties; + this.verbose = verbose; + } + + public void generateAll(List results, Date timestamp) throws Exception { + generateAll(results.toArray(new SessionResult[results.size()]), timestamp); + } + + public void generateAll(SessionResult[] results, Date timestamp) throws Exception { + SummaryCoverageReport coverageReport = null; + for (int i=0; i < results.length; i++){ + generateTestResultFile(results[i].getTestReport(), timestamp); + + //if there's a coverage report, merge it in + if (results[i].getCoverageReport() != null){ + if (coverageReport == null){ + coverageReport = results[i].getCoverageReport(); + } else { + coverageReport.merge(results[i].getCoverageReport()); + } + } + } + + //generate the coverage files + if (coverageReport != null){ + generateCoverageFiles(coverageReport, timestamp); + } + } + + private void generateTestResultFile(TestReport report, Date timestamp) throws Exception { + + String type = "results"; + String dirname = properties.getProperty(SeleniumDriver.RESULTS_OUTPUTDIR); + String filenameFormat = properties.getProperty(SeleniumDriver.RESULTS_FILENAME); + String browser = report.getBrowser().replace("*", ""); + + if (dirname == null){ + throw new Exception("Missing '" + type + ".outputdir' configuration parameter."); + } + + if (filenameFormat == null){ + throw new Exception("Missing '" + type + ".outputdir' configuration parameter."); + } + + //create the directory if necessary + File dir = new File(dirname); + if (!dir.exists()){ + + if (verbose){ + System.err.println("[INFO] Creating directory " + dir.getPath()); + } + + dir.mkdirs(); + } + + //format filename + String filename = filenameFormat.replace("{browser}", browser).replace("{name}", report.getName()).trim(); + + int pos = filename.indexOf("{date:"); + + if (pos > -1){ + + int endpos = filename.indexOf("}", pos); + String format = filename.substring(pos + 6, endpos); + + //get the format + SimpleDateFormat formatter = new SimpleDateFormat(format); + + //insert into filename + filename = filename.replace("{date:" + format + "}", formatter.format(timestamp)); + } + + filename = filename.replaceAll("[^a-zA-Z0-9\\.\\-]", "_").replaceAll("_+", "_"); + + if (verbose){ + System.err.println("[INFO] Outputting " + type + " to " + dirname + File.separator + filename); + } + + Writer out = new OutputStreamWriter(new FileOutputStream(dirname + File.separator + filename), "UTF-8"); + ReportWriter writer = (new ReportWriterFactory()).getWriter( out, "TestReport" + properties.getProperty(SeleniumDriver.RESULTS_FORMAT)); + + //String reportText = result.getReport(type); + + writer.write(report, timestamp); + writer.close(); + + } + + private void generateCoverageFiles(SummaryCoverageReport report, Date timestamp) throws Exception { + String dirname = properties.getProperty(SeleniumDriver.COVERAGE_OUTPUTDIR); + + //there should always be a directory + if (dirname == null){ + throw new Exception("Missing 'coverage.outputdir' configuration parameter."); + } + + CoverageReportGenerator generator = + CoverageReportGeneratorFactory.getGenerator(properties.getProperty(SeleniumDriver.COVERAGE_FORMAT), dirname, verbose); + generator.generate(report, timestamp); + } +} diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/selenium/YUITestSeleniumDriver.java b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/selenium/YUITestSeleniumDriver.java new file mode 100644 index 000000000..32b9efe7d --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/selenium/YUITestSeleniumDriver.java @@ -0,0 +1,264 @@ +/* + * YUI Test + * Author: Nicholas C. Zakas + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ +package com.yahoo.platform.yuitest.selenium; + +import com.yahoo.platform.yuitest.config.TestPageGroup; +import com.yahoo.platform.yuitest.config.TestPage; +import com.yahoo.platform.yuitest.config.TestConfig; +import jargs.gnu.CmdLineParser; +import java.io.*; +import java.net.URL; +import java.util.Date; +import java.util.Properties; + +/** + * Main YUI Test Coverage class. + * @author Nicholas C. Zakas + */ +public class YUITestSeleniumDriver { + + public static void main(String args[]) { + + //---------------------------------------------------------------------- + // Initialize command line parser + //---------------------------------------------------------------------- + CmdLineParser parser = new CmdLineParser(); + CmdLineParser.Option verboseOpt = parser.addBooleanOption('v', "verbose"); + CmdLineParser.Option helpOpt = parser.addBooleanOption('h', "help"); + CmdLineParser.Option errorOnFailOpt = parser.addBooleanOption("erroronfail"); + CmdLineParser.Option silentOpt = parser.addBooleanOption("silent"); + CmdLineParser.Option confOpt = parser.addStringOption("conf"); + CmdLineParser.Option hostOpt = parser.addStringOption("host"); + CmdLineParser.Option portOpt = parser.addStringOption("port"); + CmdLineParser.Option browsersOpt = parser.addStringOption("browsers"); + CmdLineParser.Option testsOpt = parser.addStringOption("tests"); + CmdLineParser.Option resultsDirOpt = parser.addStringOption("resultsdir"); + CmdLineParser.Option coverageDirOpt = parser.addStringOption("coveragedir"); + + Reader in = null; + Writer out = null; + boolean verbose = false; + + try { + + parser.parse(args); + + //Help option + Boolean help = (Boolean) parser.getOptionValue(helpOpt); + if (help != null && help.booleanValue()) { + usage(); + System.exit(0); + } + + //Verbose option + verbose = parser.getOptionValue(verboseOpt) != null; + + //load default properties from configuration file + Properties properties = new Properties(); + properties.load(YUITestSeleniumDriver.class.getResourceAsStream("default.properties")); + + //conf option + String confFile = (String) parser.getOptionValue(confOpt); + if (confFile != null){ + if (verbose){ + System.err.println("[INFO] Loading configuration properties from " + confFile); + } + properties.load(new FileInputStream(confFile)); + } + + //load all command-line properties, which override everything else + + //silent option + boolean silent = parser.getOptionValue(silentOpt) != null; + if (silent){ + properties.setProperty(SeleniumDriver.CONSOLE_MODE, "silent"); + } + + //host option + String host = (String) parser.getOptionValue(hostOpt); + if (host != null){ + properties.setProperty(SeleniumDriver.SELENIUM_HOST, host); + if (verbose){ + System.err.println("[INFO] Using command line value for " + SeleniumDriver.SELENIUM_HOST + ": " + host); + } + } + + //port option + String port = (String) parser.getOptionValue(portOpt); + if (port != null){ + properties.setProperty(SeleniumDriver.SELENIUM_PORT, port); + if (verbose){ + System.err.println("[INFO] Using command line value for " + SeleniumDriver.SELENIUM_PORT + ": " + port); + } + } + + //browsers option + String browsers = (String) parser.getOptionValue(browsersOpt); + if (browsers != null){ + properties.setProperty(SeleniumDriver.SELENIUM_BROWSERS, browsers); + if (verbose){ + System.err.println("[INFO] Using command line value for " + SeleniumDriver.SELENIUM_BROWSERS + ": " + browsers); + } + } + + //results directory option + String resultsDir = (String) parser.getOptionValue(resultsDirOpt); + if (resultsDir != null){ + properties.setProperty(SeleniumDriver.RESULTS_OUTPUTDIR, resultsDir); + if (verbose){ + System.err.println("[INFO] Using command line value for " + SeleniumDriver.RESULTS_OUTPUTDIR + ": " + resultsDir); + } + } + + //coverage directory option + String coverageDir = (String) parser.getOptionValue(coverageDirOpt); + if (coverageDir != null){ + properties.setProperty(SeleniumDriver.COVERAGE_OUTPUTDIR, coverageDir); + if (verbose){ + System.err.println("[INFO] Using command line value for " + SeleniumDriver.COVERAGE_OUTPUTDIR + ": " + coverageDir); + } + } + + //erroronfail option + if (parser.getOptionValue(errorOnFailOpt) != null){ + properties.setProperty(SeleniumDriver.ERROR_ON_FAIL, "1"); + if (verbose){ + System.err.println("[INFO] Using command line value for " + SeleniumDriver.ERROR_ON_FAIL + ": 1 (enabled)"); + } + } + + //create a new selenium driver with the properties + SeleniumDriver driver = new SeleniumDriver(properties, verbose); + SessionResult[] results = null; + + //if --tests is specified, run just those tests + String testFile = (String) parser.getOptionValue(testsOpt); + + //if there's nothing on the command line, check the properties file + if (testFile == null){ + testFile = properties.getProperty(SeleniumDriver.YUITEST_TESTS_FILE, null); + } + + //figure out what to do + if (testFile != null){ + TestConfig config = new TestConfig(); + + if (testFile.startsWith("http://")){ //it's a URL + config.load((new URL(testFile)).openStream()); + } else { //it's a local file + config.load(new FileInputStream(testFile)); + } + + if (verbose){ + System.err.println("[INFO] Using tests from " + testFile + "."); + } + + results = driver.runTests(config); + } else { + + //see if there are any test files + String[] testFiles = parser.getRemainingArgs(); + if (testFiles.length > 0){ + + if (verbose){ + System.err.println("[INFO] Using tests from command line."); + } + + TestPageGroup group = new TestPageGroup("", + Integer.parseInt(properties.getProperty(SeleniumDriver.YUITEST_VERSION)), + Integer.parseInt(properties.getProperty(SeleniumDriver.YUITEST_TIMEOUT))); + + for (int i=0; i < testFiles.length; i++){ + TestPage page = new TestPage(testFiles[i], + Integer.parseInt(properties.getProperty(SeleniumDriver.YUITEST_VERSION)), + Integer.parseInt(properties.getProperty(SeleniumDriver.YUITEST_TIMEOUT))); + group.add(page); + } + + results = driver.runTests(group); + } else { + if (verbose){ + System.err.println("[INFO] No tests specified to run, exiting..."); + } + } + + } + + //output result files + if (results != null){ + SessionResultFileGenerator generator = new SessionResultFileGenerator(properties, verbose); + generator.generateAll(results, new Date()); + } + + //verify that there were no errors + if (driver.getErrors().length > 0){ + throw new Exception(driver.getErrors()[0]); + } + + } catch (CmdLineParser.OptionException e) { + + usage(); + System.exit(1); + + } catch (Exception e) { + + System.err.println("[ERROR] " + e.getMessage()); + + if (verbose){ + e.printStackTrace(); + } + + System.exit(1); + +// } catch (Exception e) { +// +// e.printStackTrace(); +// // Return a special error code used specifically by the web front-end. +// System.exit(2); + + } finally { + + if (in != null) { + try { + in.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + if (out != null) { + try { + out.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + private static void usage() { + System.out.println( + "\nUsage: java -jar yuitest-selenium-driver-x.y.z.jar [options] [test files]\n\n" + + + "Global Options\n" + + " -h, --help Displays this information.\n" + + " --browsers Run tests in these browsers (comma-delimited).\n" + + " --conf Load options from .\n" + + " --coveragedir Output coverage files to .\n" + + " --erroronfail Indicates that a test failure should cause\n" + + " an error to be reported to the console.\n" + + " --host Use the Selenium host .\n" + + " --port Use port on the Selenium host.\n" + + " --resultsdir Output test result files to .\n" + + " --silent Don't output test results to the console.\n" + + " --tests Loads test info from .\n" + + " -v, --verbose Display informational messages and warnings.\n\n"); + } + + +} diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/selenium/default.properties b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/selenium/default.properties new file mode 100644 index 000000000..3a5cfc935 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/selenium/default.properties @@ -0,0 +1,36 @@ +#Selenium settings +selenium.host = localhost +selenium.port = 4444 + +#Comma-delimited set of browsers to test +selenium.browsers = *firefox + +#Time to wait for pages to load before giving up +selenium.waitforload = 10000 + +#Version +yuitest.version = 4 + +#Timeout for tests +yuitest.timeout = 10000 + +#Formats +results.format = JUnitXML +coverage.format = LCOV + +#Where to output results +results.outputdir = . +coverage.outputdir = . + +#Filename format +#There are some variables: +# 1.{browser} to insert the browser name +# 2.{name} to insert the name of the test (specified by TestRunner.setName() in JavaScript) +# 3.{date:}, where is a SimpleDateFormat (http://java.sun.com/j2se/1.4.2/docs/api/java/text/SimpleDateFormat.html) +results.filename = test-{browser}-{name}.xml + +#Output results to console +console.mode = normal + +#Set to 1 to cause an error when a test fails +console.erroronfail = 0 \ No newline at end of file diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/writers/CoverageFileReportHTML.stg b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/writers/CoverageFileReportHTML.stg new file mode 100644 index 000000000..1510264c3 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/writers/CoverageFileReportHTML.stg @@ -0,0 +1,173 @@ +group HTMLFileReportTemplates; + +report(report,date) ::= << + + + + + $report.filename$ Report + + + + +
+ +
+ +

$report.filename$ Report

+

Date Generated: $date$

+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
CategoryCalledTotalCoverage
Lines$report.calledLineCount$$report.coveredLineCount$$report.calledLinePercentage$%
Functions$report.calledFunctionCount$$report.coveredFunctionCount$$report.calledFunctionPercentage$%
+ + +
+ + $report.lines:line()$ +
+
+
+ + +
+

Code coverage for $report.filename$ generated on $date$ by YUI Test.

+
+
+ + + +>> + +line() ::= << + + $it.lineNumber$$it:callcount()$$it.text; format="htmlEscapeSpace"$ + +>> + +rowclass() ::= << +$if(!it.covered)$uncovered$elseif(it.called)$called$else$not-called$endif$ +>> + +callcount() ::= << +$if(!it.covered)$ +  +$else$ +$it.callCount$ +$endif$ +>> \ No newline at end of file diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/writers/CoverageSummaryReportHTML.stg b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/writers/CoverageSummaryReportHTML.stg new file mode 100644 index 000000000..ceb7748b9 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/writers/CoverageSummaryReportHTML.stg @@ -0,0 +1,115 @@ +group HTMLSummaryReportTemplates; + +report(report,date) ::= << + + + + + Code Coverage Report + + + + +
+ +
+ +

Code Coverage Report

+

Date Generated: $date$

+
+ +
+ + + + + + + + + + + + $report.fileReports:file()$ + +
FilenameLinesFunctions
+
+ + +
+

Code coverage report generated on $date$ by YUI Test.

+
+
+ + + +>> + +file() ::= << + + $it.filename$ + $it.calledLineCount$/$it.coveredLineCount$ ($it.calledLinePercentage$%) + $it.calledFunctionCount$/$it.coveredFunctionCount$ ($it.calledFunctionPercentage$%) + +>> \ No newline at end of file diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/writers/CoverageSummaryReportLCOV.stg b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/writers/CoverageSummaryReportLCOV.stg new file mode 100644 index 000000000..f65fa3679 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/writers/CoverageSummaryReportLCOV.stg @@ -0,0 +1,32 @@ +group LCOVSummaryReportTemplates; + +report(report,date) ::= << +$report.fileReports:file()$ +>> + +file() ::= << +TN: +SF:$it.absolutePath$ +$it.functions:function()$ +$it.functions:functionCall()$ +FNF:$it.coveredFunctionCount$ +FNH:$it.calledFunctionCount$ +$it.lines:line()$ +LF:$it.coveredLineCount$ +LH:$it.calledLineCount$ +end_of_record$\n$ +>> + +line() ::= << +$if(it.covered)$ +DA:$it.lineNumber$,$it.callCount$$\n$ +$endif$ +>> + +function() ::= << +FN:$it.lineNumber$,$it.name$$\n$ +>> + +functionCall() ::= << +FNDA:$it.callCount$,$it.name$$\n$ +>> \ No newline at end of file diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/writers/GCOVFileReport.stg b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/writers/GCOVFileReport.stg new file mode 100644 index 000000000..f03c2c10e --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/writers/GCOVFileReport.stg @@ -0,0 +1,32 @@ +group GCOVFileReportTemplates; + +report(report,date) ::= << + -: 0:Source:$report.filename$ +$report.lines:line()$ +>> + +line() ::= << +$if(!it.covered)$ +$it:uncoveredline()$ +$elseif(it.called)$ +$it:calledline()$ +$else$ +$it:uncalledline()$ +$endif$ +>> + +uncoveredline() ::= << + -:$it:linedetail()$ +>> + +uncalledline() ::= << +#####:$it:linedetail()$$\n$ +>> + +calledline() ::= << +$it.callCount;format="padLeft5"$:$it:linedetail()$$\n$ +>> + +linedetail() ::= << +$it.lineNumber;format="padLeft5"$:$it.text$ +>> diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/writers/LCOVHTMLDirectoryReport.stg b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/writers/LCOVHTMLDirectoryReport.stg new file mode 100644 index 000000000..e7e9a9631 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/writers/LCOVHTMLDirectoryReport.stg @@ -0,0 +1,630 @@ +group LCOVHTMLDirectoryReportTemplates; + +report(report,date) ::= << + + + + + + LCOV - lcov.info - $report.directory$ + + + + + + + + + + + + + +
LCOV - code coverage report
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:directory - $report.directory$FoundHitCoverage
Test:lcov.infoLines:$report.coveredLineCount$$report.calledLineCount$$report.calledLinePercentage$ %
Date:$date$Functions:$report.coveredFunctionCount$$report.calledFunctionCount$$report.calledFunctionPercentage$ %
Colors: + Line coverage:
+ 0% to 15% + 15% to 50% + 50% to 100% + +
+ Function coverage:
+ 0% to 75% + 75% to 90% + 90% to 100% + +
 
+
 
+ +
+ + + + + + + + + + + + + + + + + + + $report.fileReports:{ + + + + + + + + + + }$ +

FilenameLine CoverageFunctions
$it.file.name$ +
+
$it.calledLinePercentage$ %$it.calledLineCount$ / $it.coveredLineCount$$it.calledFunctionPercentage$ %$it.calledFunctionCount$ / $it.coveredFunctionCount$
+
+
+ + + + + + + + +
 
Generated by: YUI Test
+
+ + + +>> diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/writers/LCOVHTMLFileReport.stg b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/writers/LCOVHTMLFileReport.stg new file mode 100644 index 000000000..1fd4d3c56 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/writers/LCOVHTMLFileReport.stg @@ -0,0 +1,594 @@ +group LCOVHTMLFileReportTemplates; + +report(report,date) ::= << + + + + + + LCOV - lcov.info - $report.filename$ + + + + + + + + + + + + + +
LCOV - code coverage report
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:directory - $report.fileParent$ - $report.file.name$ (source / functions)FoundHitCoverage
Test:lcov.infoLines:$report.coveredLineCount$$report.calledLineCount$$report.calledLinePercentage$ %
Date:$date$Functions:$report.coveredFunctionCount$$report.calledFunctionCount$$report.calledFunctionPercentage$ %
Colors: + not hit + hit + +
 
+
 
+ + + + + + + + +

+
+      $report.lines:line()$
+
+      
+
+
+ + + + + +
 
Generated by: YUI Test
+
+ + + +>> + +line() ::= << +$if(!it.covered)$ +$it.lineNumber;format="padLeft8"$ : $it.text; format="htmlEscapeSpace"$ +$elseif(it.called)$ +$it.lineNumber;format="padLeft8"$ $it.callCount;format="padLeft8"$ : $it.text; format="htmlEscapeSpace"$ + +$else$ +$it.lineNumber;format="padLeft8"$ $it.callCount;format="padLeft8"$ : $it.text; format="htmlEscapeSpace"$ + +$endif$ +>> diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/writers/LCOVHTMLFunctionReport.stg b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/writers/LCOVHTMLFunctionReport.stg new file mode 100644 index 000000000..bd66d8a51 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/writers/LCOVHTMLFunctionReport.stg @@ -0,0 +1,590 @@ +group LCOVHTMLFunctionReportTemplates; + +report(report,date) ::= << + + + + + + LCOV - lcov.info - $report.filename$ + + + + + + + + + + + + + +
LCOV - code coverage report
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:directory - $report.fileParent$ - $report.file.name$ (source / functions)FoundHitCoverage
Test:lcov.infoLines:$report.coveredLineCount$$report.calledLineCount$$report.calledLinePercentage$ %
Date:$date$Functions:$report.coveredFunctionCount$$report.calledFunctionCount$$report.calledFunctionPercentage$ %
Colors: + not hit + hit + +
 
+
 
+ +
+ + + + + + + $report.functions:{ + + + $if(it.called)$ + + $else$ + + $endif$ + + }$ + +

Function Name Hit count
$it.name$$it.callCount$0
+
+
+ + + + + + +
 
Generated by: YUI Test
+
+ + + +>> diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/writers/LCOVHTMLIndexReport.stg b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/writers/LCOVHTMLIndexReport.stg new file mode 100644 index 000000000..d033f8922 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/writers/LCOVHTMLIndexReport.stg @@ -0,0 +1,630 @@ +group LCOVHTMLIndexReportTemplates; + +report(report,date) ::= << + + + + + + LCOV - lcov.info + + + + + + + + + + + + + +
LCOV - code coverage report
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:directoryFoundHitCoverage
Test:lcov.infoLines:$report.coveredLineCount$$report.calledLineCount$$report.calledLinePercentage$ %
Date:$date$Functions:$report.coveredFunctionCount$$report.calledFunctionCount$$report.calledFunctionPercentage$ %
Colors: + Line coverage:
+ 0% to 15% + 15% to 50% + 50% to 100% + +
+ Function coverage:
+ 0% to 75% + 75% to 90% + 90% to 100% + +
 
+
 
+ +
+ + + + + + + + + + + + + + + + + + + $report.directoryReports:{ + + + + + + + + + + }$ +

DirectoryLine CoverageFunctions
$it.directory$ +
+
$it.calledLinePercentage$ %$it.calledLineCount$ / $it.coveredLineCount$$it.calledFunctionPercentage$ %$it.calledFunctionCount$ / $it.coveredFunctionCount$
+
+
+ + + + + + + + +
 
Generated by: YUI Test
+
+ + + +>> diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/writers/ReportWriter.java b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/writers/ReportWriter.java new file mode 100644 index 000000000..ff2dfac4a --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/writers/ReportWriter.java @@ -0,0 +1,22 @@ +/* + * YUI Test + * Author: Nicholas C. Zakas + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ + +package com.yahoo.platform.yuitest.writers; + +import java.io.IOException; +import java.util.Date; + +/** + * + * @author Nicholas C. Zakas + */ +public interface ReportWriter { + public void write(T report) throws IOException; + public void write(T report, Date date) throws IOException; + public void close() throws IOException; +} diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/writers/ReportWriterFactory.java b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/writers/ReportWriterFactory.java new file mode 100644 index 000000000..70b100f47 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/writers/ReportWriterFactory.java @@ -0,0 +1,30 @@ +/* + * YUI Test + * Author: Nicholas C. Zakas + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ + +package com.yahoo.platform.yuitest.writers; + +import java.io.Writer; + +/** + * + * @author Nicholas C. Zakas + */ +public class ReportWriterFactory { + + public ReportWriterFactory(){ + + } + + public ReportWriter getWriter(Writer out, String groupName) { + try { + return new StringTemplateWriter(out, groupName); + } catch(Exception ex){ + throw new IllegalArgumentException(String.format("No writer for '%s' found.", groupName)); + } + } +} diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/writers/StringTemplateWriter.java b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/writers/StringTemplateWriter.java new file mode 100644 index 000000000..22faa8716 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/writers/StringTemplateWriter.java @@ -0,0 +1,115 @@ +/* + * YUI Test + * Author: Nicholas C. Zakas + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ + +package com.yahoo.platform.yuitest.writers; + +import com.yahoo.platform.yuitest.results.TestReport; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Writer; +import java.util.Date; +import org.antlr.stringtemplate.AttributeRenderer; +import org.antlr.stringtemplate.StringTemplate; +import org.antlr.stringtemplate.StringTemplateGroup; +import org.antlr.stringtemplate.language.DefaultTemplateLexer; + +/** + * + * @author Nicholas C. Zakas + */ +public class StringTemplateWriter implements ReportWriter { + + protected Writer out; + protected StringTemplateGroup templateGroup; + + public StringTemplateWriter(Writer out, String groupName) throws IOException { + this.out = out; + this.templateGroup = getStringTemplateGroup(groupName); + } + + private StringTemplateGroup getStringTemplateGroup(String groupName) throws IOException{ + //get string template group + InputStream stgstream = StringTemplateWriter.class.getResourceAsStream(groupName + ".stg"); + InputStreamReader reader = new InputStreamReader(stgstream); + StringTemplateGroup group = new StringTemplateGroup(reader, DefaultTemplateLexer.class); + reader.close(); + return group; + } + + public void write(T report) throws IOException { + write(report, new Date()); + } + + public void write(T report, Date date) throws IOException { + StringTemplate template = templateGroup.getInstanceOf("report"); + template.setAttribute("report", report); + template.setAttribute("date", date); + + //renderer for strings + template.registerRenderer(String.class, new AttributeRenderer(){ + + public String toString(Object o) { + return o.toString(); + } + + public String toString(Object o, String format) { + if (format.equals("classname")){ + return o.toString().replace(TestReport.PATH_SEPARATOR, ".").replaceAll("[^a-zA-Z0-9\\\\.]", ""); + } else if (format.equals("xmlEscape")){ + return o.toString().replace("&", "&").replace(">", ">").replace("<", "<").replace("\"", """).replace("'", "'"); + } else if (format.equals("htmlEscape")){ + return o.toString().replace("&", "&").replace("\"", """).replace("<", "<").replace(">", ">"); + } else if (format.equals("htmlEscapeSpace")){ + return o.toString().replace("&", "&").replace("\"", """).replace("<", "<").replace(">", ">").replace(" ", " "); + } else if (format.equals("relativePath")){ + return o.toString().replaceAll("[^\\\\\\/]+", ".."); + } else if (format.equals("fullPath")){ + return o.toString().replaceFirst("^[\\\\\\/]+", ""); //for files like /home/username/foo, remove first / + } else if (format.equals("fullRelativePath")){ + return toString(toString(o, "fullPath"), "relativePath"); + } else { + return o.toString(); + } + } + }); + + //renderer for numbers + template.registerRenderer(Integer.class, new AttributeRenderer(){ + + private int count=1; + + public String toString(Object o) { + return o.toString(); + } + + public String toString(Object o, String format) { + if (format.equals("count")){ + return String.valueOf(count++); + } else if (format.equals("ms_to_s")){ + return String.valueOf(Double.parseDouble(o.toString()) / 1000); + } else if (format.startsWith("padLeft")){ + String num = o.toString(); + int max = Integer.parseInt(format.substring(7)); + while(num.length() < max){ + num = " " + num; + } + return num; + } else { + return o.toString(); + } + } + }); + + out.write(template.toString()); + } + + public void close() throws IOException { + out.close(); + } +} diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/writers/TestReportJUnitXML.stg b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/writers/TestReportJUnitXML.stg new file mode 100644 index 000000000..1280b0916 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/writers/TestReportJUnitXML.stg @@ -0,0 +1,31 @@ +group JUnitXMLTemplates; + +report(report,date) ::= << + + +$report.testSuites:testsuite()$ +$report:testcases()$ +$report.testSuites:testcases()$ + +>> + +testsuite() ::= "$it.testSuites:testsuite()$" +testcases() ::= "$it.testCases:testcase()$" + +testcase() ::= << +$if(it.total)$ + +$it.tests:test()$ + +$endif$ +>> + +test() ::= << +$if(!it.ignored)$ + +$if(it.failed)$ + +$endif$ + +$endif$ +>> \ No newline at end of file diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/writers/TestReportTAP.stg b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/writers/TestReportTAP.stg new file mode 100644 index 000000000..949cf7fe6 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/writers/TestReportTAP.stg @@ -0,0 +1,37 @@ +group TAPTemplates; + +report(report,date) ::= << +#Browser: $report.browser$ +#Date: $date$ +1..$report.total$ +#Begin report $report:atts()$ +$report.testSuites:testsuite()$ +$report.testCases:testcase()$ +#End report $report.name$ +>> + +testsuite() ::= << +#Begin testsuite $it:atts()$ +$it.testSuites:testsuite()$ +$it.testCases:testcase()$ +#End testsuite $it.name$ + +>> + +testcase() ::= << +#Begin testcase $it:atts()$ +$it.tests:test()$ +#End testcase $it.name$ + +>> + +test() ::= << +$if(it.passed)$ +ok $1; format="count"$ - $it.name$ +$elseif(it.failed)$ +not ok $1; format="count"$ - $it.name$ - $it.message$ + +$endif$ +>> + +atts() ::= "$it.name$ ($it.failed$ failed of $it.total$)" \ No newline at end of file diff --git a/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/writers/TestReportYUITestXML.stg b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/writers/TestReportYUITestXML.stg new file mode 100644 index 000000000..9c988d3fa --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/com/yahoo/platform/yuitest/writers/TestReportYUITestXML.stg @@ -0,0 +1,28 @@ +group YUITestXMLTemplates; + +report(report,date) ::= << + + +$report.testSuites:testsuite()$ +$report.testCases:testcase()$ + +>> + +testsuite() ::= << + +$it.testSuites:testsuite()$ +$it.testCases:testcase()$ + +>> + +testcase() ::= << + +$it.tests:test()$ + +>> + +test() ::= << + +>> + +atts() ::= "name=\"$it.name;format=\"xmlEscape\"$\" passed=\"$it.passed$\" failed=\"$it.failed$\" ignored=\"$it.ignored$\" total=\"$it.totalIncludingIgnored$\" duration=\"$it.duration$\"" \ No newline at end of file diff --git a/tests/harness/lib/yuitest/java/src/org/json/CDL.java b/tests/harness/lib/yuitest/java/src/org/json/CDL.java new file mode 100644 index 000000000..2039f9e27 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/org/json/CDL.java @@ -0,0 +1,279 @@ +package org.json; + +/* +Copyright (c) 2002 JSON.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +The Software shall be used for Good, not Evil. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +/** + * This provides static methods to convert comma delimited text into a + * JSONArray, and to covert a JSONArray into comma delimited text. Comma + * delimited text is a very popular format for data interchange. It is + * understood by most database, spreadsheet, and organizer programs. + *

+ * Each row of text represents a row in a table or a data record. Each row + * ends with a NEWLINE character. Each row contains one or more values. + * Values are separated by commas. A value can contain any character except + * for comma, unless is is wrapped in single quotes or double quotes. + *

+ * The first row usually contains the names of the columns. + *

+ * A comma delimited list can be converted into a JSONArray of JSONObjects. + * The names for the elements in the JSONObjects can be taken from the names + * in the first row. + * @author JSON.org + * @version 2009-09-11 + */ +public class CDL { + + /** + * Get the next value. The value can be wrapped in quotes. The value can + * be empty. + * @param x A JSONTokener of the source text. + * @return The value string, or null if empty. + * @throws JSONException if the quoted string is badly formed. + */ + private static String getValue(JSONTokener x) throws JSONException { + char c; + char q; + StringBuffer sb; + do { + c = x.next(); + } while (c == ' ' || c == '\t'); + switch (c) { + case 0: + return null; + case '"': + case '\'': + q = c; + sb = new StringBuffer(); + for (;;) { + c = x.next(); + if (c == q) { + break; + } + if (c == 0 || c == '\n' || c == '\r') { + throw x.syntaxError("Missing close quote '" + q + "'."); + } + sb.append(c); + } + return sb.toString(); + case ',': + x.back(); + return ""; + default: + x.back(); + return x.nextTo(','); + } + } + + /** + * Produce a JSONArray of strings from a row of comma delimited values. + * @param x A JSONTokener of the source text. + * @return A JSONArray of strings. + * @throws JSONException + */ + public static JSONArray rowToJSONArray(JSONTokener x) throws JSONException { + JSONArray ja = new JSONArray(); + for (;;) { + String value = getValue(x); + char c = x.next(); + if (value == null || + (ja.length() == 0 && value.length() == 0 && c != ',')) { + return null; + } + ja.put(value); + for (;;) { + if (c == ',') { + break; + } + if (c != ' ') { + if (c == '\n' || c == '\r' || c == 0) { + return ja; + } + throw x.syntaxError("Bad character '" + c + "' (" + + (int)c + ")."); + } + c = x.next(); + } + } + } + + /** + * Produce a JSONObject from a row of comma delimited text, using a + * parallel JSONArray of strings to provides the names of the elements. + * @param names A JSONArray of names. This is commonly obtained from the + * first row of a comma delimited text file using the rowToJSONArray + * method. + * @param x A JSONTokener of the source text. + * @return A JSONObject combining the names and values. + * @throws JSONException + */ + public static JSONObject rowToJSONObject(JSONArray names, JSONTokener x) + throws JSONException { + JSONArray ja = rowToJSONArray(x); + return ja != null ? ja.toJSONObject(names) : null; + } + + /** + * Produce a JSONArray of JSONObjects from a comma delimited text string, + * using the first row as a source of names. + * @param string The comma delimited text. + * @return A JSONArray of JSONObjects. + * @throws JSONException + */ + public static JSONArray toJSONArray(String string) throws JSONException { + return toJSONArray(new JSONTokener(string)); + } + + /** + * Produce a JSONArray of JSONObjects from a comma delimited text string, + * using the first row as a source of names. + * @param x The JSONTokener containing the comma delimited text. + * @return A JSONArray of JSONObjects. + * @throws JSONException + */ + public static JSONArray toJSONArray(JSONTokener x) throws JSONException { + return toJSONArray(rowToJSONArray(x), x); + } + + /** + * Produce a JSONArray of JSONObjects from a comma delimited text string + * using a supplied JSONArray as the source of element names. + * @param names A JSONArray of strings. + * @param string The comma delimited text. + * @return A JSONArray of JSONObjects. + * @throws JSONException + */ + public static JSONArray toJSONArray(JSONArray names, String string) + throws JSONException { + return toJSONArray(names, new JSONTokener(string)); + } + + /** + * Produce a JSONArray of JSONObjects from a comma delimited text string + * using a supplied JSONArray as the source of element names. + * @param names A JSONArray of strings. + * @param x A JSONTokener of the source text. + * @return A JSONArray of JSONObjects. + * @throws JSONException + */ + public static JSONArray toJSONArray(JSONArray names, JSONTokener x) + throws JSONException { + if (names == null || names.length() == 0) { + return null; + } + JSONArray ja = new JSONArray(); + for (;;) { + JSONObject jo = rowToJSONObject(names, x); + if (jo == null) { + break; + } + ja.put(jo); + } + if (ja.length() == 0) { + return null; + } + return ja; + } + + + /** + * Produce a comma delimited text row from a JSONArray. Values containing + * the comma character will be quoted. Troublesome characters may be + * removed. + * @param ja A JSONArray of strings. + * @return A string ending in NEWLINE. + */ + public static String rowToString(JSONArray ja) { + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < ja.length(); i += 1) { + if (i > 0) { + sb.append(','); + } + Object o = ja.opt(i); + if (o != null) { + String s = o.toString(); + if (s.length() > 0 && (s.indexOf(',') >= 0 || s.indexOf('\n') >= 0 || + s.indexOf('\r') >= 0 || s.indexOf(0) >= 0 || + s.charAt(0) == '"')) { + sb.append('"'); + int length = s.length(); + for (int j = 0; j < length; j += 1) { + char c = s.charAt(j); + if (c >= ' ' && c != '"') { + sb.append(c); + } + } + sb.append('"'); + } else { + sb.append(s); + } + } + } + sb.append('\n'); + return sb.toString(); + } + + /** + * Produce a comma delimited text from a JSONArray of JSONObjects. The + * first row will be a list of names obtained by inspecting the first + * JSONObject. + * @param ja A JSONArray of JSONObjects. + * @return A comma delimited text. + * @throws JSONException + */ + public static String toString(JSONArray ja) throws JSONException { + JSONObject jo = ja.optJSONObject(0); + if (jo != null) { + JSONArray names = jo.names(); + if (names != null) { + return rowToString(names) + toString(names, ja); + } + } + return null; + } + + /** + * Produce a comma delimited text from a JSONArray of JSONObjects using + * a provided list of names. The list of names is not included in the + * output. + * @param names A JSONArray of strings. + * @param ja A JSONArray of JSONObjects. + * @return A comma delimited text. + * @throws JSONException + */ + public static String toString(JSONArray names, JSONArray ja) + throws JSONException { + if (names == null || names.length() == 0) { + return null; + } + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < ja.length(); i += 1) { + JSONObject jo = ja.optJSONObject(i); + if (jo != null) { + sb.append(rowToString(jo.toJSONArray(names))); + } + } + return sb.toString(); + } +} diff --git a/tests/harness/lib/yuitest/java/src/org/json/Cookie.java b/tests/harness/lib/yuitest/java/src/org/json/Cookie.java new file mode 100644 index 000000000..52a1d1a03 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/org/json/Cookie.java @@ -0,0 +1,169 @@ +package org.json; + +/* +Copyright (c) 2002 JSON.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +The Software shall be used for Good, not Evil. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +/** + * Convert a web browser cookie specification to a JSONObject and back. + * JSON and Cookies are both notations for name/value pairs. + * @author JSON.org + * @version 2008-09-18 + */ +public class Cookie { + + /** + * Produce a copy of a string in which the characters '+', '%', '=', ';' + * and control characters are replaced with "%hh". This is a gentle form + * of URL encoding, attempting to cause as little distortion to the + * string as possible. The characters '=' and ';' are meta characters in + * cookies. By convention, they are escaped using the URL-encoding. This is + * only a convention, not a standard. Often, cookies are expected to have + * encoded values. We encode '=' and ';' because we must. We encode '%' and + * '+' because they are meta characters in URL encoding. + * @param string The source string. + * @return The escaped result. + */ + public static String escape(String string) { + char c; + String s = string.trim(); + StringBuffer sb = new StringBuffer(); + int len = s.length(); + for (int i = 0; i < len; i += 1) { + c = s.charAt(i); + if (c < ' ' || c == '+' || c == '%' || c == '=' || c == ';') { + sb.append('%'); + sb.append(Character.forDigit((char)((c >>> 4) & 0x0f), 16)); + sb.append(Character.forDigit((char)(c & 0x0f), 16)); + } else { + sb.append(c); + } + } + return sb.toString(); + } + + + /** + * Convert a cookie specification string into a JSONObject. The string + * will contain a name value pair separated by '='. The name and the value + * will be unescaped, possibly converting '+' and '%' sequences. The + * cookie properties may follow, separated by ';', also represented as + * name=value (except the secure property, which does not have a value). + * The name will be stored under the key "name", and the value will be + * stored under the key "value". This method does not do checking or + * validation of the parameters. It only converts the cookie string into + * a JSONObject. + * @param string The cookie specification string. + * @return A JSONObject containing "name", "value", and possibly other + * members. + * @throws JSONException + */ + public static JSONObject toJSONObject(String string) throws JSONException { + String n; + JSONObject o = new JSONObject(); + Object v; + JSONTokener x = new JSONTokener(string); + o.put("name", x.nextTo('=')); + x.next('='); + o.put("value", x.nextTo(';')); + x.next(); + while (x.more()) { + n = unescape(x.nextTo("=;")); + if (x.next() != '=') { + if (n.equals("secure")) { + v = Boolean.TRUE; + } else { + throw x.syntaxError("Missing '=' in cookie parameter."); + } + } else { + v = unescape(x.nextTo(';')); + x.next(); + } + o.put(n, v); + } + return o; + } + + + /** + * Convert a JSONObject into a cookie specification string. The JSONObject + * must contain "name" and "value" members. + * If the JSONObject contains "expires", "domain", "path", or "secure" + * members, they will be appended to the cookie specification string. + * All other members are ignored. + * @param o A JSONObject + * @return A cookie specification string + * @throws JSONException + */ + public static String toString(JSONObject o) throws JSONException { + StringBuffer sb = new StringBuffer(); + + sb.append(escape(o.getString("name"))); + sb.append("="); + sb.append(escape(o.getString("value"))); + if (o.has("expires")) { + sb.append(";expires="); + sb.append(o.getString("expires")); + } + if (o.has("domain")) { + sb.append(";domain="); + sb.append(escape(o.getString("domain"))); + } + if (o.has("path")) { + sb.append(";path="); + sb.append(escape(o.getString("path"))); + } + if (o.optBoolean("secure")) { + sb.append(";secure"); + } + return sb.toString(); + } + + /** + * Convert %hh sequences to single characters, and + * convert plus to space. + * @param s A string that may contain + * + (plus) and + * %hh sequences. + * @return The unescaped string. + */ + public static String unescape(String s) { + int len = s.length(); + StringBuffer b = new StringBuffer(); + for (int i = 0; i < len; ++i) { + char c = s.charAt(i); + if (c == '+') { + c = ' '; + } else if (c == '%' && i + 2 < len) { + int d = JSONTokener.dehexchar(s.charAt(i + 1)); + int e = JSONTokener.dehexchar(s.charAt(i + 2)); + if (d >= 0 && e >= 0) { + c = (char)(d * 16 + e); + i += 2; + } + } + b.append(c); + } + return b.toString(); + } +} diff --git a/tests/harness/lib/yuitest/java/src/org/json/CookieList.java b/tests/harness/lib/yuitest/java/src/org/json/CookieList.java new file mode 100644 index 000000000..3219ede6f --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/org/json/CookieList.java @@ -0,0 +1,90 @@ +package org.json; + +/* +Copyright (c) 2002 JSON.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +The Software shall be used for Good, not Evil. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +import java.util.Iterator; + +/** + * Convert a web browser cookie list string to a JSONObject and back. + * @author JSON.org + * @version 2008-09-18 + */ +public class CookieList { + + /** + * Convert a cookie list into a JSONObject. A cookie list is a sequence + * of name/value pairs. The names are separated from the values by '='. + * The pairs are separated by ';'. The names and the values + * will be unescaped, possibly converting '+' and '%' sequences. + * + * To add a cookie to a cooklist, + * cookielistJSONObject.put(cookieJSONObject.getString("name"), + * cookieJSONObject.getString("value")); + * @param string A cookie list string + * @return A JSONObject + * @throws JSONException + */ + public static JSONObject toJSONObject(String string) throws JSONException { + JSONObject o = new JSONObject(); + JSONTokener x = new JSONTokener(string); + while (x.more()) { + String name = Cookie.unescape(x.nextTo('=')); + x.next('='); + o.put(name, Cookie.unescape(x.nextTo(';'))); + x.next(); + } + return o; + } + + + /** + * Convert a JSONObject into a cookie list. A cookie list is a sequence + * of name/value pairs. The names are separated from the values by '='. + * The pairs are separated by ';'. The characters '%', '+', '=', and ';' + * in the names and values are replaced by "%hh". + * @param o A JSONObject + * @return A cookie list string + * @throws JSONException + */ + public static String toString(JSONObject o) throws JSONException { + boolean b = false; + Iterator keys = o.keys(); + String s; + StringBuffer sb = new StringBuffer(); + while (keys.hasNext()) { + s = keys.next().toString(); + if (!o.isNull(s)) { + if (b) { + sb.append(';'); + } + sb.append(Cookie.escape(s)); + sb.append("="); + sb.append(Cookie.escape(o.getString(s))); + b = true; + } + } + return sb.toString(); + } +} diff --git a/tests/harness/lib/yuitest/java/src/org/json/HTTP.java b/tests/harness/lib/yuitest/java/src/org/json/HTTP.java new file mode 100644 index 000000000..e4f301cab --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/org/json/HTTP.java @@ -0,0 +1,163 @@ +package org.json; + +/* +Copyright (c) 2002 JSON.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +The Software shall be used for Good, not Evil. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +import java.util.Iterator; + +/** + * Convert an HTTP header to a JSONObject and back. + * @author JSON.org + * @version 2008-09-18 + */ +public class HTTP { + + /** Carriage return/line feed. */ + public static final String CRLF = "\r\n"; + + /** + * Convert an HTTP header string into a JSONObject. It can be a request + * header or a response header. A request header will contain + *

{
+     *    Method: "POST" (for example),
+     *    "Request-URI": "/" (for example),
+     *    "HTTP-Version": "HTTP/1.1" (for example)
+     * }
+ * A response header will contain + *
{
+     *    "HTTP-Version": "HTTP/1.1" (for example),
+     *    "Status-Code": "200" (for example),
+     *    "Reason-Phrase": "OK" (for example)
+     * }
+ * In addition, the other parameters in the header will be captured, using + * the HTTP field names as JSON names, so that
+     *    Date: Sun, 26 May 2002 18:06:04 GMT
+     *    Cookie: Q=q2=PPEAsg--; B=677gi6ouf29bn&b=2&f=s
+     *    Cache-Control: no-cache
+ * become + *
{...
+     *    Date: "Sun, 26 May 2002 18:06:04 GMT",
+     *    Cookie: "Q=q2=PPEAsg--; B=677gi6ouf29bn&b=2&f=s",
+     *    "Cache-Control": "no-cache",
+     * ...}
+ * It does no further checking or conversion. It does not parse dates. + * It does not do '%' transforms on URLs. + * @param string An HTTP header string. + * @return A JSONObject containing the elements and attributes + * of the XML string. + * @throws JSONException + */ + public static JSONObject toJSONObject(String string) throws JSONException { + JSONObject o = new JSONObject(); + HTTPTokener x = new HTTPTokener(string); + String t; + + t = x.nextToken(); + if (t.toUpperCase().startsWith("HTTP")) { + +// Response + + o.put("HTTP-Version", t); + o.put("Status-Code", x.nextToken()); + o.put("Reason-Phrase", x.nextTo('\0')); + x.next(); + + } else { + +// Request + + o.put("Method", t); + o.put("Request-URI", x.nextToken()); + o.put("HTTP-Version", x.nextToken()); + } + +// Fields + + while (x.more()) { + String name = x.nextTo(':'); + x.next(':'); + o.put(name, x.nextTo('\0')); + x.next(); + } + return o; + } + + + /** + * Convert a JSONObject into an HTTP header. A request header must contain + *
{
+     *    Method: "POST" (for example),
+     *    "Request-URI": "/" (for example),
+     *    "HTTP-Version": "HTTP/1.1" (for example)
+     * }
+ * A response header must contain + *
{
+     *    "HTTP-Version": "HTTP/1.1" (for example),
+     *    "Status-Code": "200" (for example),
+     *    "Reason-Phrase": "OK" (for example)
+     * }
+ * Any other members of the JSONObject will be output as HTTP fields. + * The result will end with two CRLF pairs. + * @param o A JSONObject + * @return An HTTP header string. + * @throws JSONException if the object does not contain enough + * information. + */ + public static String toString(JSONObject o) throws JSONException { + Iterator keys = o.keys(); + String s; + StringBuffer sb = new StringBuffer(); + if (o.has("Status-Code") && o.has("Reason-Phrase")) { + sb.append(o.getString("HTTP-Version")); + sb.append(' '); + sb.append(o.getString("Status-Code")); + sb.append(' '); + sb.append(o.getString("Reason-Phrase")); + } else if (o.has("Method") && o.has("Request-URI")) { + sb.append(o.getString("Method")); + sb.append(' '); + sb.append('"'); + sb.append(o.getString("Request-URI")); + sb.append('"'); + sb.append(' '); + sb.append(o.getString("HTTP-Version")); + } else { + throw new JSONException("Not enough material for an HTTP header."); + } + sb.append(CRLF); + while (keys.hasNext()) { + s = keys.next().toString(); + if (!s.equals("HTTP-Version") && !s.equals("Status-Code") && + !s.equals("Reason-Phrase") && !s.equals("Method") && + !s.equals("Request-URI") && !o.isNull(s)) { + sb.append(s); + sb.append(": "); + sb.append(o.getString(s)); + sb.append(CRLF); + } + } + sb.append(CRLF); + return sb.toString(); + } +} diff --git a/tests/harness/lib/yuitest/java/src/org/json/HTTPTokener.java b/tests/harness/lib/yuitest/java/src/org/json/HTTPTokener.java new file mode 100644 index 000000000..3838de9a2 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/org/json/HTTPTokener.java @@ -0,0 +1,77 @@ +package org.json; + +/* +Copyright (c) 2002 JSON.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +The Software shall be used for Good, not Evil. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +/** + * The HTTPTokener extends the JSONTokener to provide additional methods + * for the parsing of HTTP headers. + * @author JSON.org + * @version 2008-09-18 + */ +public class HTTPTokener extends JSONTokener { + + /** + * Construct an HTTPTokener from a string. + * @param s A source string. + */ + public HTTPTokener(String s) { + super(s); + } + + + /** + * Get the next token or string. This is used in parsing HTTP headers. + * @throws JSONException + * @return A String. + */ + public String nextToken() throws JSONException { + char c; + char q; + StringBuffer sb = new StringBuffer(); + do { + c = next(); + } while (Character.isWhitespace(c)); + if (c == '"' || c == '\'') { + q = c; + for (;;) { + c = next(); + if (c < ' ') { + throw syntaxError("Unterminated string."); + } + if (c == q) { + return sb.toString(); + } + sb.append(c); + } + } + for (;;) { + if (c == 0 || Character.isWhitespace(c)) { + return sb.toString(); + } + sb.append(c); + c = next(); + } + } +} diff --git a/tests/harness/lib/yuitest/java/src/org/json/JSONArray.java b/tests/harness/lib/yuitest/java/src/org/json/JSONArray.java new file mode 100644 index 000000000..444e835f9 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/org/json/JSONArray.java @@ -0,0 +1,960 @@ +package org.json; + +/* +Copyright (c) 2002 JSON.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +The Software shall be used for Good, not Evil. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +import java.io.IOException; +import java.io.Writer; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; + +/** + * A JSONArray is an ordered sequence of values. Its external text form is a + * string wrapped in square brackets with commas separating the values. The + * internal form is an object having get and opt + * methods for accessing the values by index, and put methods for + * adding or replacing values. The values can be any of these types: + * Boolean, JSONArray, JSONObject, + * Number, String, or the + * JSONObject.NULL object. + *

+ * The constructor can convert a JSON text into a Java object. The + * toString method converts to JSON text. + *

+ * A get method returns a value if one can be found, and throws an + * exception if one cannot be found. An opt method returns a + * default value instead of throwing an exception, and so is useful for + * obtaining optional values. + *

+ * The generic get() and opt() methods return an + * object which you can cast or query for type. There are also typed + * get and opt methods that do type checking and type + * coercion for you. + *

+ * The texts produced by the toString methods strictly conform to + * JSON syntax rules. The constructors are more forgiving in the texts they will + * accept: + *

    + *
  • An extra , (comma) may appear just + * before the closing bracket.
  • + *
  • The null value will be inserted when there + * is , (comma) elision.
  • + *
  • Strings may be quoted with ' (single + * quote).
  • + *
  • Strings do not need to be quoted at all if they do not begin with a quote + * or single quote, and if they do not contain leading or trailing spaces, + * and if they do not contain any of these characters: + * { } [ ] / \ : , = ; # and if they do not look like numbers + * and if they are not the reserved words true, + * false, or null.
  • + *
  • Values can be separated by ; (semicolon) as + * well as by , (comma).
  • + *
  • Numbers may have the 0- (octal) or + * 0x- (hex) prefix.
  • + *
+ + * @author JSON.org + * @version 2009-04-13 + */ +public class JSONArray { + + + /** + * The arrayList where the JSONArray's properties are kept. + */ + private ArrayList myArrayList; + + + /** + * Construct an empty JSONArray. + */ + public JSONArray() { + this.myArrayList = new ArrayList(); + } + + /** + * Construct a JSONArray from a JSONTokener. + * @param x A JSONTokener + * @throws JSONException If there is a syntax error. + */ + public JSONArray(JSONTokener x) throws JSONException { + this(); + char c = x.nextClean(); + char q; + if (c == '[') { + q = ']'; + } else if (c == '(') { + q = ')'; + } else { + throw x.syntaxError("A JSONArray text must start with '['"); + } + if (x.nextClean() == ']') { + return; + } + x.back(); + for (;;) { + if (x.nextClean() == ',') { + x.back(); + this.myArrayList.add(null); + } else { + x.back(); + this.myArrayList.add(x.nextValue()); + } + c = x.nextClean(); + switch (c) { + case ';': + case ',': + if (x.nextClean() == ']') { + return; + } + x.back(); + break; + case ']': + case ')': + if (q != c) { + throw x.syntaxError("Expected a '" + new Character(q) + "'"); + } + return; + default: + throw x.syntaxError("Expected a ',' or ']'"); + } + } + } + + + /** + * Construct a JSONArray from a source JSON text. + * @param source A string that begins with + * [ (left bracket) + * and ends with ] (right bracket). + * @throws JSONException If there is a syntax error. + */ + public JSONArray(String source) throws JSONException { + this(new JSONTokener(source)); + } + + + /** + * Construct a JSONArray from a Collection. + * @param collection A Collection. + */ + public JSONArray(Collection collection) { + this.myArrayList = (collection == null) ? + new ArrayList() : + new ArrayList(collection); + } + + /** + * Construct a JSONArray from a collection of beans. + * The collection should have Java Beans. + * + * @throws JSONException If not an array. + */ + + public JSONArray(Collection collection, boolean includeSuperClass) { + this.myArrayList = new ArrayList(); + if (collection != null) { + Iterator iter = collection.iterator();; + while (iter.hasNext()) { + Object o = iter.next(); + if (o instanceof Map) { + this.myArrayList.add(new JSONObject((Map)o, includeSuperClass)); + } else if (!JSONObject.isStandardProperty(o.getClass())) { + this.myArrayList.add(new JSONObject(o, includeSuperClass)); + } else { + this.myArrayList.add(o); + } + } + } + } + + + /** + * Construct a JSONArray from an array + * @throws JSONException If not an array. + */ + public JSONArray(Object array) throws JSONException { + this(); + if (array.getClass().isArray()) { + int length = Array.getLength(array); + for (int i = 0; i < length; i += 1) { + this.put(Array.get(array, i)); + } + } else { + throw new JSONException("JSONArray initial value should be a string or collection or array."); + } + } + + /** + * Construct a JSONArray from an array with a bean. + * The array should have Java Beans. + * + * @throws JSONException If not an array. + */ + public JSONArray(Object array,boolean includeSuperClass) throws JSONException { + this(); + if (array.getClass().isArray()) { + int length = Array.getLength(array); + for (int i = 0; i < length; i += 1) { + Object o = Array.get(array, i); + if (JSONObject.isStandardProperty(o.getClass())) { + this.myArrayList.add(o); + } else { + this.myArrayList.add(new JSONObject(o,includeSuperClass)); + } + } + } else { + throw new JSONException("JSONArray initial value should be a string or collection or array."); + } + } + + + + /** + * Get the object value associated with an index. + * @param index + * The index must be between 0 and length() - 1. + * @return An object value. + * @throws JSONException If there is no value for the index. + */ + public Object get(int index) throws JSONException { + Object o = opt(index); + if (o == null) { + throw new JSONException("JSONArray[" + index + "] not found."); + } + return o; + } + + + /** + * Get the boolean value associated with an index. + * The string values "true" and "false" are converted to boolean. + * + * @param index The index must be between 0 and length() - 1. + * @return The truth. + * @throws JSONException If there is no value for the index or if the + * value is not convertable to boolean. + */ + public boolean getBoolean(int index) throws JSONException { + Object o = get(index); + if (o.equals(Boolean.FALSE) || + (o instanceof String && + ((String)o).equalsIgnoreCase("false"))) { + return false; + } else if (o.equals(Boolean.TRUE) || + (o instanceof String && + ((String)o).equalsIgnoreCase("true"))) { + return true; + } + throw new JSONException("JSONArray[" + index + "] is not a Boolean."); + } + + + /** + * Get the double value associated with an index. + * + * @param index The index must be between 0 and length() - 1. + * @return The value. + * @throws JSONException If the key is not found or if the value cannot + * be converted to a number. + */ + public double getDouble(int index) throws JSONException { + Object o = get(index); + try { + return o instanceof Number ? + ((Number)o).doubleValue() : + Double.valueOf((String)o).doubleValue(); + } catch (Exception e) { + throw new JSONException("JSONArray[" + index + + "] is not a number."); + } + } + + + /** + * Get the int value associated with an index. + * + * @param index The index must be between 0 and length() - 1. + * @return The value. + * @throws JSONException If the key is not found or if the value cannot + * be converted to a number. + * if the value cannot be converted to a number. + */ + public int getInt(int index) throws JSONException { + Object o = get(index); + return o instanceof Number ? + ((Number)o).intValue() : (int)getDouble(index); + } + + + /** + * Get the JSONArray associated with an index. + * @param index The index must be between 0 and length() - 1. + * @return A JSONArray value. + * @throws JSONException If there is no value for the index. or if the + * value is not a JSONArray + */ + public JSONArray getJSONArray(int index) throws JSONException { + Object o = get(index); + if (o instanceof JSONArray) { + return (JSONArray)o; + } + throw new JSONException("JSONArray[" + index + + "] is not a JSONArray."); + } + + + /** + * Get the JSONObject associated with an index. + * @param index subscript + * @return A JSONObject value. + * @throws JSONException If there is no value for the index or if the + * value is not a JSONObject + */ + public JSONObject getJSONObject(int index) throws JSONException { + Object o = get(index); + if (o instanceof JSONObject) { + return (JSONObject)o; + } + throw new JSONException("JSONArray[" + index + + "] is not a JSONObject."); + } + + + /** + * Get the long value associated with an index. + * + * @param index The index must be between 0 and length() - 1. + * @return The value. + * @throws JSONException If the key is not found or if the value cannot + * be converted to a number. + */ + public long getLong(int index) throws JSONException { + Object o = get(index); + return o instanceof Number ? + ((Number)o).longValue() : (long)getDouble(index); + } + + + /** + * Get the string associated with an index. + * @param index The index must be between 0 and length() - 1. + * @return A string value. + * @throws JSONException If there is no value for the index. + */ + public String getString(int index) throws JSONException { + return get(index).toString(); + } + + + /** + * Determine if the value is null. + * @param index The index must be between 0 and length() - 1. + * @return true if the value at the index is null, or if there is no value. + */ + public boolean isNull(int index) { + return JSONObject.NULL.equals(opt(index)); + } + + + /** + * Make a string from the contents of this JSONArray. The + * separator string is inserted between each element. + * Warning: This method assumes that the data structure is acyclical. + * @param separator A string that will be inserted between the elements. + * @return a string. + * @throws JSONException If the array contains an invalid number. + */ + public String join(String separator) throws JSONException { + int len = length(); + StringBuffer sb = new StringBuffer(); + + for (int i = 0; i < len; i += 1) { + if (i > 0) { + sb.append(separator); + } + sb.append(JSONObject.valueToString(this.myArrayList.get(i))); + } + return sb.toString(); + } + + + /** + * Get the number of elements in the JSONArray, included nulls. + * + * @return The length (or size). + */ + public int length() { + return this.myArrayList.size(); + } + + + /** + * Get the optional object value associated with an index. + * @param index The index must be between 0 and length() - 1. + * @return An object value, or null if there is no + * object at that index. + */ + public Object opt(int index) { + return (index < 0 || index >= length()) ? + null : this.myArrayList.get(index); + } + + + /** + * Get the optional boolean value associated with an index. + * It returns false if there is no value at that index, + * or if the value is not Boolean.TRUE or the String "true". + * + * @param index The index must be between 0 and length() - 1. + * @return The truth. + */ + public boolean optBoolean(int index) { + return optBoolean(index, false); + } + + + /** + * Get the optional boolean value associated with an index. + * It returns the defaultValue if there is no value at that index or if + * it is not a Boolean or the String "true" or "false" (case insensitive). + * + * @param index The index must be between 0 and length() - 1. + * @param defaultValue A boolean default. + * @return The truth. + */ + public boolean optBoolean(int index, boolean defaultValue) { + try { + return getBoolean(index); + } catch (Exception e) { + return defaultValue; + } + } + + + /** + * Get the optional double value associated with an index. + * NaN is returned if there is no value for the index, + * or if the value is not a number and cannot be converted to a number. + * + * @param index The index must be between 0 and length() - 1. + * @return The value. + */ + public double optDouble(int index) { + return optDouble(index, Double.NaN); + } + + + /** + * Get the optional double value associated with an index. + * The defaultValue is returned if there is no value for the index, + * or if the value is not a number and cannot be converted to a number. + * + * @param index subscript + * @param defaultValue The default value. + * @return The value. + */ + public double optDouble(int index, double defaultValue) { + try { + return getDouble(index); + } catch (Exception e) { + return defaultValue; + } + } + + + /** + * Get the optional int value associated with an index. + * Zero is returned if there is no value for the index, + * or if the value is not a number and cannot be converted to a number. + * + * @param index The index must be between 0 and length() - 1. + * @return The value. + */ + public int optInt(int index) { + return optInt(index, 0); + } + + + /** + * Get the optional int value associated with an index. + * The defaultValue is returned if there is no value for the index, + * or if the value is not a number and cannot be converted to a number. + * @param index The index must be between 0 and length() - 1. + * @param defaultValue The default value. + * @return The value. + */ + public int optInt(int index, int defaultValue) { + try { + return getInt(index); + } catch (Exception e) { + return defaultValue; + } + } + + + /** + * Get the optional JSONArray associated with an index. + * @param index subscript + * @return A JSONArray value, or null if the index has no value, + * or if the value is not a JSONArray. + */ + public JSONArray optJSONArray(int index) { + Object o = opt(index); + return o instanceof JSONArray ? (JSONArray)o : null; + } + + + /** + * Get the optional JSONObject associated with an index. + * Null is returned if the key is not found, or null if the index has + * no value, or if the value is not a JSONObject. + * + * @param index The index must be between 0 and length() - 1. + * @return A JSONObject value. + */ + public JSONObject optJSONObject(int index) { + Object o = opt(index); + return o instanceof JSONObject ? (JSONObject)o : null; + } + + + /** + * Get the optional long value associated with an index. + * Zero is returned if there is no value for the index, + * or if the value is not a number and cannot be converted to a number. + * + * @param index The index must be between 0 and length() - 1. + * @return The value. + */ + public long optLong(int index) { + return optLong(index, 0); + } + + + /** + * Get the optional long value associated with an index. + * The defaultValue is returned if there is no value for the index, + * or if the value is not a number and cannot be converted to a number. + * @param index The index must be between 0 and length() - 1. + * @param defaultValue The default value. + * @return The value. + */ + public long optLong(int index, long defaultValue) { + try { + return getLong(index); + } catch (Exception e) { + return defaultValue; + } + } + + + /** + * Get the optional string value associated with an index. It returns an + * empty string if there is no value at that index. If the value + * is not a string and is not null, then it is coverted to a string. + * + * @param index The index must be between 0 and length() - 1. + * @return A String value. + */ + public String optString(int index) { + return optString(index, ""); + } + + + /** + * Get the optional string associated with an index. + * The defaultValue is returned if the key is not found. + * + * @param index The index must be between 0 and length() - 1. + * @param defaultValue The default value. + * @return A String value. + */ + public String optString(int index, String defaultValue) { + Object o = opt(index); + return o != null ? o.toString() : defaultValue; + } + + + /** + * Append a boolean value. This increases the array's length by one. + * + * @param value A boolean value. + * @return this. + */ + public JSONArray put(boolean value) { + put(value ? Boolean.TRUE : Boolean.FALSE); + return this; + } + + + /** + * Put a value in the JSONArray, where the value will be a + * JSONArray which is produced from a Collection. + * @param value A Collection value. + * @return this. + */ + public JSONArray put(Collection value) { + put(new JSONArray(value)); + return this; + } + + + /** + * Append a double value. This increases the array's length by one. + * + * @param value A double value. + * @throws JSONException if the value is not finite. + * @return this. + */ + public JSONArray put(double value) throws JSONException { + Double d = new Double(value); + JSONObject.testValidity(d); + put(d); + return this; + } + + + /** + * Append an int value. This increases the array's length by one. + * + * @param value An int value. + * @return this. + */ + public JSONArray put(int value) { + put(new Integer(value)); + return this; + } + + + /** + * Append an long value. This increases the array's length by one. + * + * @param value A long value. + * @return this. + */ + public JSONArray put(long value) { + put(new Long(value)); + return this; + } + + + /** + * Put a value in the JSONArray, where the value will be a + * JSONObject which is produced from a Map. + * @param value A Map value. + * @return this. + */ + public JSONArray put(Map value) { + put(new JSONObject(value)); + return this; + } + + + /** + * Append an object value. This increases the array's length by one. + * @param value An object value. The value should be a + * Boolean, Double, Integer, JSONArray, JSONObject, Long, or String, or the + * JSONObject.NULL object. + * @return this. + */ + public JSONArray put(Object value) { + this.myArrayList.add(value); + return this; + } + + + /** + * Put or replace a boolean value in the JSONArray. If the index is greater + * than the length of the JSONArray, then null elements will be added as + * necessary to pad it out. + * @param index The subscript. + * @param value A boolean value. + * @return this. + * @throws JSONException If the index is negative. + */ + public JSONArray put(int index, boolean value) throws JSONException { + put(index, value ? Boolean.TRUE : Boolean.FALSE); + return this; + } + + + /** + * Put a value in the JSONArray, where the value will be a + * JSONArray which is produced from a Collection. + * @param index The subscript. + * @param value A Collection value. + * @return this. + * @throws JSONException If the index is negative or if the value is + * not finite. + */ + public JSONArray put(int index, Collection value) throws JSONException { + put(index, new JSONArray(value)); + return this; + } + + + /** + * Put or replace a double value. If the index is greater than the length of + * the JSONArray, then null elements will be added as necessary to pad + * it out. + * @param index The subscript. + * @param value A double value. + * @return this. + * @throws JSONException If the index is negative or if the value is + * not finite. + */ + public JSONArray put(int index, double value) throws JSONException { + put(index, new Double(value)); + return this; + } + + + /** + * Put or replace an int value. If the index is greater than the length of + * the JSONArray, then null elements will be added as necessary to pad + * it out. + * @param index The subscript. + * @param value An int value. + * @return this. + * @throws JSONException If the index is negative. + */ + public JSONArray put(int index, int value) throws JSONException { + put(index, new Integer(value)); + return this; + } + + + /** + * Put or replace a long value. If the index is greater than the length of + * the JSONArray, then null elements will be added as necessary to pad + * it out. + * @param index The subscript. + * @param value A long value. + * @return this. + * @throws JSONException If the index is negative. + */ + public JSONArray put(int index, long value) throws JSONException { + put(index, new Long(value)); + return this; + } + + + /** + * Put a value in the JSONArray, where the value will be a + * JSONObject which is produced from a Map. + * @param index The subscript. + * @param value The Map value. + * @return this. + * @throws JSONException If the index is negative or if the the value is + * an invalid number. + */ + public JSONArray put(int index, Map value) throws JSONException { + put(index, new JSONObject(value)); + return this; + } + + + /** + * Put or replace an object value in the JSONArray. If the index is greater + * than the length of the JSONArray, then null elements will be added as + * necessary to pad it out. + * @param index The subscript. + * @param value The value to put into the array. The value should be a + * Boolean, Double, Integer, JSONArray, JSONObject, Long, or String, or the + * JSONObject.NULL object. + * @return this. + * @throws JSONException If the index is negative or if the the value is + * an invalid number. + */ + public JSONArray put(int index, Object value) throws JSONException { + JSONObject.testValidity(value); + if (index < 0) { + throw new JSONException("JSONArray[" + index + "] not found."); + } + if (index < length()) { + this.myArrayList.set(index, value); + } else { + while (index != length()) { + put(JSONObject.NULL); + } + put(value); + } + return this; + } + + + /** + * Remove an index and close the hole. + * @param index The index of the element to be removed. + * @return The value that was associated with the index, + * or null if there was no value. + */ + public Object remove(int index) { + Object o = opt(index); + this.myArrayList.remove(index); + return o; + } + + + /** + * Produce a JSONObject by combining a JSONArray of names with the values + * of this JSONArray. + * @param names A JSONArray containing a list of key strings. These will be + * paired with the values. + * @return A JSONObject, or null if there are no names or if this JSONArray + * has no values. + * @throws JSONException If any of the names are null. + */ + public JSONObject toJSONObject(JSONArray names) throws JSONException { + if (names == null || names.length() == 0 || length() == 0) { + return null; + } + JSONObject jo = new JSONObject(); + for (int i = 0; i < names.length(); i += 1) { + jo.put(names.getString(i), this.opt(i)); + } + return jo; + } + + + /** + * Make a JSON text of this JSONArray. For compactness, no + * unnecessary whitespace is added. If it is not possible to produce a + * syntactically correct JSON text then null will be returned instead. This + * could occur if the array contains an invalid number. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @return a printable, displayable, transmittable + * representation of the array. + */ + public String toString() { + try { + return '[' + join(",") + ']'; + } catch (Exception e) { + return null; + } + } + + + /** + * Make a prettyprinted JSON text of this JSONArray. + * Warning: This method assumes that the data structure is acyclical. + * @param indentFactor The number of spaces to add to each level of + * indentation. + * @return a printable, displayable, transmittable + * representation of the object, beginning + * with [ (left bracket) and ending + * with ] (right bracket). + * @throws JSONException + */ + public String toString(int indentFactor) throws JSONException { + return toString(indentFactor, 0); + } + + + /** + * Make a prettyprinted JSON text of this JSONArray. + * Warning: This method assumes that the data structure is acyclical. + * @param indentFactor The number of spaces to add to each level of + * indentation. + * @param indent The indention of the top level. + * @return a printable, displayable, transmittable + * representation of the array. + * @throws JSONException + */ + String toString(int indentFactor, int indent) throws JSONException { + int len = length(); + if (len == 0) { + return "[]"; + } + int i; + StringBuffer sb = new StringBuffer("["); + if (len == 1) { + sb.append(JSONObject.valueToString(this.myArrayList.get(0), + indentFactor, indent)); + } else { + int newindent = indent + indentFactor; + sb.append('\n'); + for (i = 0; i < len; i += 1) { + if (i > 0) { + sb.append(",\n"); + } + for (int j = 0; j < newindent; j += 1) { + sb.append(' '); + } + sb.append(JSONObject.valueToString(this.myArrayList.get(i), + indentFactor, newindent)); + } + sb.append('\n'); + for (i = 0; i < indent; i += 1) { + sb.append(' '); + } + } + sb.append(']'); + return sb.toString(); + } + + + /** + * Write the contents of the JSONArray as JSON text to a writer. + * For compactness, no whitespace is added. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @return The writer. + * @throws JSONException + */ + public Writer write(Writer writer) throws JSONException { + try { + boolean b = false; + int len = length(); + + writer.write('['); + + for (int i = 0; i < len; i += 1) { + if (b) { + writer.write(','); + } + Object v = this.myArrayList.get(i); + if (v instanceof JSONObject) { + ((JSONObject)v).write(writer); + } else if (v instanceof JSONArray) { + ((JSONArray)v).write(writer); + } else { + writer.write(JSONObject.valueToString(v)); + } + b = true; + } + writer.write(']'); + return writer; + } catch (IOException e) { + throw new JSONException(e); + } + } +} \ No newline at end of file diff --git a/tests/harness/lib/yuitest/java/src/org/json/JSONException.java b/tests/harness/lib/yuitest/java/src/org/json/JSONException.java new file mode 100644 index 000000000..2660aea5e --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/org/json/JSONException.java @@ -0,0 +1,27 @@ +package org.json; + +/** + * The JSONException is thrown by the JSON.org classes when things are amiss. + * @author JSON.org + * @version 2008-09-18 + */ +public class JSONException extends Exception { + private Throwable cause; + + /** + * Constructs a JSONException with an explanatory message. + * @param message Detail about the reason for the exception. + */ + public JSONException(String message) { + super(message); + } + + public JSONException(Throwable t) { + super(t.getMessage()); + this.cause = t; + } + + public Throwable getCause() { + return this.cause; + } +} diff --git a/tests/harness/lib/yuitest/java/src/org/json/JSONML.java b/tests/harness/lib/yuitest/java/src/org/json/JSONML.java new file mode 100644 index 000000000..7feee448c --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/org/json/JSONML.java @@ -0,0 +1,455 @@ +package org.json; + +/* +Copyright (c) 2008 JSON.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +The Software shall be used for Good, not Evil. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +import java.util.Iterator; + + +/** + * This provides static methods to convert an XML text into a JSONArray or + * JSONObject, and to covert a JSONArray or JSONObject into an XML text using + * the JsonML transform. + * @author JSON.org + * @version 2008-11-20 + */ +public class JSONML { + + /** + * Parse XML values and store them in a JSONArray. + * @param x The XMLTokener containing the source string. + * @param arrayForm true if array form, false if object form. + * @param ja The JSONArray that is containing the current tag or null + * if we are at the outermost level. + * @return A JSONArray if the value is the outermost tag, otherwise null. + * @throws JSONException + */ + private static Object parse(XMLTokener x, boolean arrayForm, + JSONArray ja) throws JSONException { + String attribute; + char c; + String closeTag = null; + int i; + JSONArray newja = null; + JSONObject newjo = null; + Object token; + String tagName = null; + +// Test for and skip past these forms: +// +// +// +// + + while (true) { + token = x.nextContent(); + if (token == XML.LT) { + token = x.nextToken(); + if (token instanceof Character) { + if (token == XML.SLASH) { + +// Close tag "); + } + x.back(); + } else if (c == '[') { + token = x.nextToken(); + if (token.equals("CDATA") && x.next() == '[') { + if (ja != null) { + ja.put(x.nextCDATA()); + } + } else { + throw x.syntaxError("Expected 'CDATA['"); + } + } else { + i = 1; + do { + token = x.nextMeta(); + if (token == null) { + throw x.syntaxError("Missing '>' after ' 0); + } + } else if (token == XML.QUEST) { + +// "); + } else { + throw x.syntaxError("Misshaped tag"); + } + +// Open tag < + + } else { + if (!(token instanceof String)) { + throw x.syntaxError("Bad tagName '" + token + "'."); + } + tagName = (String)token; + newja = new JSONArray(); + newjo = new JSONObject(); + if (arrayForm) { + newja.put(tagName); + if (ja != null) { + ja.put(newja); + } + } else { + newjo.put("tagName", tagName); + if (ja != null) { + ja.put(newjo); + } + } + token = null; + for (;;) { + if (token == null) { + token = x.nextToken(); + } + if (token == null) { + throw x.syntaxError("Misshaped tag"); + } + if (!(token instanceof String)) { + break; + } + +// attribute = value + + attribute = (String)token; + if (!arrayForm && (attribute == "tagName" || attribute == "childNode")) { + throw x.syntaxError("Reserved attribute."); + } + token = x.nextToken(); + if (token == XML.EQ) { + token = x.nextToken(); + if (!(token instanceof String)) { + throw x.syntaxError("Missing value"); + } + newjo.accumulate(attribute, JSONObject.stringToValue((String)token)); + token = null; + } else { + newjo.accumulate(attribute, ""); + } + } + if (arrayForm && newjo.length() > 0) { + newja.put(newjo); + } + +// Empty tag <.../> + + if (token == XML.SLASH) { + if (x.nextToken() != XML.GT) { + throw x.syntaxError("Misshaped tag"); + } + if (ja == null) { + if (arrayForm) { + return newja; + } else { + return newjo; + } + } + +// Content, between <...> and + + } else { + if (token != XML.GT) { + throw x.syntaxError("Misshaped tag"); + } + closeTag = (String)parse(x, arrayForm, newja); + if (closeTag != null) { + if (!closeTag.equals(tagName)) { + throw x.syntaxError("Mismatched '" + tagName + + "' and '" + closeTag + "'"); + } + tagName = null; + if (!arrayForm && newja.length() > 0) { + newjo.put("childNodes", newja); + } + if (ja == null) { + if (arrayForm) { + return newja; + } else { + return newjo; + } + } + } + } + } + } else { + if (ja != null) { + ja.put(token instanceof String ? + JSONObject.stringToValue((String)token) : token); + } + } + } + } + + + /** + * Convert a well-formed (but not necessarily valid) XML string into a + * JSONArray using the JsonML transform. Each XML tag is represented as + * a JSONArray in which the first element is the tag name. If the tag has + * attributes, then the second element will be JSONObject containing the + * name/value pairs. If the tag contains children, then strings and + * JSONArrays will represent the child tags. + * Comments, prologs, DTDs, and <[ [ ]]> are ignored. + * @param string The source string. + * @return A JSONArray containing the structured data from the XML string. + * @throws JSONException + */ + public static JSONArray toJSONArray(String string) throws JSONException { + return toJSONArray(new XMLTokener(string)); + } + + + /** + * Convert a well-formed (but not necessarily valid) XML string into a + * JSONArray using the JsonML transform. Each XML tag is represented as + * a JSONArray in which the first element is the tag name. If the tag has + * attributes, then the second element will be JSONObject containing the + * name/value pairs. If the tag contains children, then strings and + * JSONArrays will represent the child content and tags. + * Comments, prologs, DTDs, and <[ [ ]]> are ignored. + * @param x An XMLTokener. + * @return A JSONArray containing the structured data from the XML string. + * @throws JSONException + */ + public static JSONArray toJSONArray(XMLTokener x) throws JSONException { + return (JSONArray)parse(x, true, null); + } + + + + /** + * Convert a well-formed (but not necessarily valid) XML string into a + * JSONObject using the JsonML transform. Each XML tag is represented as + * a JSONObject with a "tagName" property. If the tag has attributes, then + * the attributes will be in the JSONObject as properties. If the tag + * contains children, the object will have a "childNodes" property which + * will be an array of strings and JsonML JSONObjects. + + * Comments, prologs, DTDs, and <[ [ ]]> are ignored. + * @param x An XMLTokener of the XML source text. + * @return A JSONObject containing the structured data from the XML string. + * @throws JSONException + */ + public static JSONObject toJSONObject(XMLTokener x) throws JSONException { + return (JSONObject)parse(x, false, null); + } + /** + * Convert a well-formed (but not necessarily valid) XML string into a + * JSONObject using the JsonML transform. Each XML tag is represented as + * a JSONObject with a "tagName" property. If the tag has attributes, then + * the attributes will be in the JSONObject as properties. If the tag + * contains children, the object will have a "childNodes" property which + * will be an array of strings and JsonML JSONObjects. + + * Comments, prologs, DTDs, and <[ [ ]]> are ignored. + * @param string The XML source text. + * @return A JSONObject containing the structured data from the XML string. + * @throws JSONException + */ + public static JSONObject toJSONObject(String string) throws JSONException { + return toJSONObject(new XMLTokener(string)); + } + + + /** + * Reverse the JSONML transformation, making an XML text from a JSONArray. + * @param ja A JSONArray. + * @return An XML string. + * @throws JSONException + */ + public static String toString(JSONArray ja) throws JSONException { + Object e; + int i; + JSONObject jo; + String k; + Iterator keys; + int length; + StringBuffer sb = new StringBuffer(); + String tagName; + String v; + +// Emit = length) { + sb.append('/'); + sb.append('>'); + } else { + sb.append('>'); + do { + e = ja.get(i); + i += 1; + if (e != null) { + if (e instanceof String) { + sb.append(XML.escape(e.toString())); + } else if (e instanceof JSONObject) { + sb.append(toString((JSONObject)e)); + } else if (e instanceof JSONArray) { + sb.append(toString((JSONArray)e)); + } + } + } while (i < length); + sb.append('<'); + sb.append('/'); + sb.append(tagName); + sb.append('>'); + } + return sb.toString(); + } + + /** + * Reverse the JSONML transformation, making an XML text from a JSONObject. + * The JSONObject must contain a "tagName" property. If it has children, + * then it must have a "childNodes" property containing an array of objects. + * The other properties are attributes with string values. + * @param jo A JSONObject. + * @return An XML string. + * @throws JSONException + */ + public static String toString(JSONObject jo) throws JSONException { + StringBuffer sb = new StringBuffer(); + Object e; + int i; + JSONArray ja; + String k; + Iterator keys; + int len; + String tagName; + String v; + +//Emit '); + } else { + sb.append('>'); + len = ja.length(); + for (i = 0; i < len; i += 1) { + e = ja.get(i); + if (e != null) { + if (e instanceof String) { + sb.append(XML.escape(e.toString())); + } else if (e instanceof JSONObject) { + sb.append(toString((JSONObject)e)); + } else if (e instanceof JSONArray) { + sb.append(toString((JSONArray)e)); + } + } + } + sb.append('<'); + sb.append('/'); + sb.append(tagName); + sb.append('>'); + } + return sb.toString(); + } +} \ No newline at end of file diff --git a/tests/harness/lib/yuitest/java/src/org/json/JSONObject.java b/tests/harness/lib/yuitest/java/src/org/json/JSONObject.java new file mode 100644 index 000000000..6b7c1d410 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/org/json/JSONObject.java @@ -0,0 +1,1569 @@ +package org.json; + +/* +Copyright (c) 2002 JSON.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +The Software shall be used for Good, not Evil. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +import java.io.IOException; +import java.io.Writer; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.TreeSet; + +/** + * A JSONObject is an unordered collection of name/value pairs. Its + * external form is a string wrapped in curly braces with colons between the + * names and values, and commas between the values and names. The internal form + * is an object having get and opt methods for + * accessing the values by name, and put methods for adding or + * replacing values by name. The values can be any of these types: + * Boolean, JSONArray, JSONObject, + * Number, String, or the JSONObject.NULL + * object. A JSONObject constructor can be used to convert an external form + * JSON text into an internal form whose values can be retrieved with the + * get and opt methods, or to convert values into a + * JSON text using the put and toString methods. + * A get method returns a value if one can be found, and throws an + * exception if one cannot be found. An opt method returns a + * default value instead of throwing an exception, and so is useful for + * obtaining optional values. + *

+ * The generic get() and opt() methods return an + * object, which you can cast or query for type. There are also typed + * get and opt methods that do type checking and type + * coercion for you. + *

+ * The put methods adds values to an object. For example,

+ *     myString = new JSONObject().put("JSON", "Hello, World!").toString();
+ * produces the string {"JSON": "Hello, World"}. + *

+ * The texts produced by the toString methods strictly conform to + * the JSON syntax rules. + * The constructors are more forgiving in the texts they will accept: + *

    + *
  • An extra , (comma) may appear just + * before the closing brace.
  • + *
  • Strings may be quoted with ' (single + * quote).
  • + *
  • Strings do not need to be quoted at all if they do not begin with a quote + * or single quote, and if they do not contain leading or trailing spaces, + * and if they do not contain any of these characters: + * { } [ ] / \ : , = ; # and if they do not look like numbers + * and if they are not the reserved words true, + * false, or null.
  • + *
  • Keys can be followed by = or => as well as + * by :.
  • + *
  • Values can be followed by ; (semicolon) as + * well as by , (comma).
  • + *
  • Numbers may have the 0- (octal) or + * 0x- (hex) prefix.
  • + *
+ * @author JSON.org + * @version 2009-03-06 + */ +public class JSONObject { + + /** + * JSONObject.NULL is equivalent to the value that JavaScript calls null, + * whilst Java's null is equivalent to the value that JavaScript calls + * undefined. + */ + private static final class Null { + + /** + * There is only intended to be a single instance of the NULL object, + * so the clone method returns itself. + * @return NULL. + */ + protected final Object clone() { + return this; + } + + + /** + * A Null object is equal to the null value and to itself. + * @param object An object to test for nullness. + * @return true if the object parameter is the JSONObject.NULL object + * or null. + */ + public boolean equals(Object object) { + return object == null || object == this; + } + + + /** + * Get the "null" string value. + * @return The string "null". + */ + public String toString() { + return "null"; + } + } + + + /** + * The map where the JSONObject's properties are kept. + */ + private Map map; + + + /** + * It is sometimes more convenient and less ambiguous to have a + * NULL object than to use Java's null value. + * JSONObject.NULL.equals(null) returns true. + * JSONObject.NULL.toString() returns "null". + */ + public static final Object NULL = new Null(); + + + /** + * Construct an empty JSONObject. + */ + public JSONObject() { + this.map = new HashMap(); + } + + + /** + * Construct a JSONObject from a subset of another JSONObject. + * An array of strings is used to identify the keys that should be copied. + * Missing keys are ignored. + * @param jo A JSONObject. + * @param names An array of strings. + * @exception JSONException If a value is a non-finite number or if a name is duplicated. + */ + public JSONObject(JSONObject jo, String[] names) throws JSONException { + this(); + for (int i = 0; i < names.length; i += 1) { + putOnce(names[i], jo.opt(names[i])); + } + } + + + /** + * Construct a JSONObject from a JSONTokener. + * @param x A JSONTokener object containing the source string. + * @throws JSONException If there is a syntax error in the source string + * or a duplicated key. + */ + public JSONObject(JSONTokener x) throws JSONException { + this(); + char c; + String key; + + if (x.nextClean() != '{') { + throw x.syntaxError("A JSONObject text must begin with '{'"); + } + for (;;) { + c = x.nextClean(); + switch (c) { + case 0: + throw x.syntaxError("A JSONObject text must end with '}'"); + case '}': + return; + default: + x.back(); + key = x.nextValue().toString(); + } + + /* + * The key is followed by ':'. We will also tolerate '=' or '=>'. + */ + + c = x.nextClean(); + if (c == '=') { + if (x.next() != '>') { + x.back(); + } + } else if (c != ':') { + throw x.syntaxError("Expected a ':' after a key"); + } + putOnce(key, x.nextValue()); + + /* + * Pairs are separated by ','. We will also tolerate ';'. + */ + + switch (x.nextClean()) { + case ';': + case ',': + if (x.nextClean() == '}') { + return; + } + x.back(); + break; + case '}': + return; + default: + throw x.syntaxError("Expected a ',' or '}'"); + } + } + } + + + /** + * Construct a JSONObject from a Map. + * + * @param map A map object that can be used to initialize the contents of + * the JSONObject. + */ + public JSONObject(Map map) { + this.map = (map == null) ? new HashMap() : map; + } + + + /** + * Construct a JSONObject from a Map. + * + * Note: Use this constructor when the map contains . + * + * @param map - A map with Key-Bean data. + * @param includeSuperClass - Tell whether to include the super class properties. + */ + public JSONObject(Map map, boolean includeSuperClass) { + this.map = new HashMap(); + if (map != null) { + Iterator i = map.entrySet().iterator(); + while (i.hasNext()) { + Map.Entry e = (Map.Entry)i.next(); + if (isStandardProperty(e.getValue().getClass())) { + this.map.put(e.getKey(), e.getValue()); + } else { + this.map.put(e.getKey(), new JSONObject(e.getValue(), + includeSuperClass)); + } + } + } + } + + + /** + * Construct a JSONObject from an Object using bean getters. + * It reflects on all of the public methods of the object. + * For each of the methods with no parameters and a name starting + * with "get" or "is" followed by an uppercase letter, + * the method is invoked, and a key and the value returned from the getter method + * are put into the new JSONObject. + * + * The key is formed by removing the "get" or "is" prefix. + * If the second remaining character is not upper case, then the first + * character is converted to lower case. + * + * For example, if an object has a method named "getName", and + * if the result of calling object.getName() is "Larry Fine", + * then the JSONObject will contain "name": "Larry Fine". + * + * @param bean An object that has getter methods that should be used + * to make a JSONObject. + */ + public JSONObject(Object bean) { + this(); + populateInternalMap(bean, false); + } + + + /** + * Construct a JSONObject from an Object using bean getters. + * It reflects on all of the public methods of the object. + * For each of the methods with no parameters and a name starting + * with "get" or "is" followed by an uppercase letter, + * the method is invoked, and a key and the value returned from the getter method + * are put into the new JSONObject. + * + * The key is formed by removing the "get" or "is" prefix. + * If the second remaining character is not upper case, then the first + * character is converted to lower case. + * + * @param bean An object that has getter methods that should be used + * to make a JSONObject. + * @param includeSuperClass If true, include the super class properties. + */ + public JSONObject(Object bean, boolean includeSuperClass) { + this(); + populateInternalMap(bean, includeSuperClass); + } + + private void populateInternalMap(Object bean, boolean includeSuperClass){ + Class klass = bean.getClass(); + + /* If klass.getSuperClass is System class then force includeSuperClass to false. */ + + if (klass.getClassLoader() == null) { + includeSuperClass = false; + } + + Method[] methods = (includeSuperClass) ? + klass.getMethods() : klass.getDeclaredMethods(); + for (int i = 0; i < methods.length; i += 1) { + try { + Method method = methods[i]; + if (Modifier.isPublic(method.getModifiers())) { + String name = method.getName(); + String key = ""; + if (name.startsWith("get")) { + key = name.substring(3); + } else if (name.startsWith("is")) { + key = name.substring(2); + } + if (key.length() > 0 && + Character.isUpperCase(key.charAt(0)) && + method.getParameterTypes().length == 0) { + if (key.length() == 1) { + key = key.toLowerCase(); + } else if (!Character.isUpperCase(key.charAt(1))) { + key = key.substring(0, 1).toLowerCase() + + key.substring(1); + } + + Object result = method.invoke(bean, (Object[])null); + if (result == null) { + map.put(key, NULL); + } else if (result.getClass().isArray()) { + map.put(key, new JSONArray(result, includeSuperClass)); + } else if (result instanceof Collection) { // List or Set + map.put(key, new JSONArray((Collection)result, includeSuperClass)); + } else if (result instanceof Map) { + map.put(key, new JSONObject((Map)result, includeSuperClass)); + } else if (isStandardProperty(result.getClass())) { // Primitives, String and Wrapper + map.put(key, result); + } else { + if (result.getClass().getPackage().getName().startsWith("java") || + result.getClass().getClassLoader() == null) { + map.put(key, result.toString()); + } else { // User defined Objects + map.put(key, new JSONObject(result, includeSuperClass)); + } + } + } + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + + + static boolean isStandardProperty(Class clazz) { + return clazz.isPrimitive() || + clazz.isAssignableFrom(Byte.class) || + clazz.isAssignableFrom(Short.class) || + clazz.isAssignableFrom(Integer.class) || + clazz.isAssignableFrom(Long.class) || + clazz.isAssignableFrom(Float.class) || + clazz.isAssignableFrom(Double.class) || + clazz.isAssignableFrom(Character.class) || + clazz.isAssignableFrom(String.class) || + clazz.isAssignableFrom(Boolean.class); + } + + + /** + * Construct a JSONObject from an Object, using reflection to find the + * public members. The resulting JSONObject's keys will be the strings + * from the names array, and the values will be the field values associated + * with those keys in the object. If a key is not found or not visible, + * then it will not be copied into the new JSONObject. + * @param object An object that has fields that should be used to make a + * JSONObject. + * @param names An array of strings, the names of the fields to be obtained + * from the object. + */ + public JSONObject(Object object, String names[]) { + this(); + Class c = object.getClass(); + for (int i = 0; i < names.length; i += 1) { + String name = names[i]; + try { + putOpt(name, c.getField(name).get(object)); + } catch (Exception e) { + /* forget about it */ + } + } + } + + + /** + * Construct a JSONObject from a source JSON text string. + * This is the most commonly used JSONObject constructor. + * @param source A string beginning + * with { (left brace) and ending + * with } (right brace). + * @exception JSONException If there is a syntax error in the source + * string or a duplicated key. + */ + public JSONObject(String source) throws JSONException { + this(new JSONTokener(source)); + } + + + /** + * Accumulate values under a key. It is similar to the put method except + * that if there is already an object stored under the key then a + * JSONArray is stored under the key to hold all of the accumulated values. + * If there is already a JSONArray, then the new value is appended to it. + * In contrast, the put method replaces the previous value. + * @param key A key string. + * @param value An object to be accumulated under the key. + * @return this. + * @throws JSONException If the value is an invalid number + * or if the key is null. + */ + public JSONObject accumulate(String key, Object value) + throws JSONException { + testValidity(value); + Object o = opt(key); + if (o == null) { + put(key, value instanceof JSONArray ? + new JSONArray().put(value) : + value); + } else if (o instanceof JSONArray) { + ((JSONArray)o).put(value); + } else { + put(key, new JSONArray().put(o).put(value)); + } + return this; + } + + + /** + * Append values to the array under a key. If the key does not exist in the + * JSONObject, then the key is put in the JSONObject with its value being a + * JSONArray containing the value parameter. If the key was already + * associated with a JSONArray, then the value parameter is appended to it. + * @param key A key string. + * @param value An object to be accumulated under the key. + * @return this. + * @throws JSONException If the key is null or if the current value + * associated with the key is not a JSONArray. + */ + public JSONObject append(String key, Object value) + throws JSONException { + testValidity(value); + Object o = opt(key); + if (o == null) { + put(key, new JSONArray().put(value)); + } else if (o instanceof JSONArray) { + put(key, ((JSONArray)o).put(value)); + } else { + throw new JSONException("JSONObject[" + key + + "] is not a JSONArray."); + } + return this; + } + + + /** + * Produce a string from a double. The string "null" will be returned if + * the number is not finite. + * @param d A double. + * @return A String. + */ + static public String doubleToString(double d) { + if (Double.isInfinite(d) || Double.isNaN(d)) { + return "null"; + } + +// Shave off trailing zeros and decimal point, if possible. + + String s = Double.toString(d); + if (s.indexOf('.') > 0 && s.indexOf('e') < 0 && s.indexOf('E') < 0) { + while (s.endsWith("0")) { + s = s.substring(0, s.length() - 1); + } + if (s.endsWith(".")) { + s = s.substring(0, s.length() - 1); + } + } + return s; + } + + + /** + * Get the value object associated with a key. + * + * @param key A key string. + * @return The object associated with the key. + * @throws JSONException if the key is not found. + */ + public Object get(String key) throws JSONException { + Object o = opt(key); + if (o == null) { + throw new JSONException("JSONObject[" + quote(key) + + "] not found."); + } + return o; + } + + + /** + * Get the boolean value associated with a key. + * + * @param key A key string. + * @return The truth. + * @throws JSONException + * if the value is not a Boolean or the String "true" or "false". + */ + public boolean getBoolean(String key) throws JSONException { + Object o = get(key); + if (o.equals(Boolean.FALSE) || + (o instanceof String && + ((String)o).equalsIgnoreCase("false"))) { + return false; + } else if (o.equals(Boolean.TRUE) || + (o instanceof String && + ((String)o).equalsIgnoreCase("true"))) { + return true; + } + throw new JSONException("JSONObject[" + quote(key) + + "] is not a Boolean."); + } + + + /** + * Get the double value associated with a key. + * @param key A key string. + * @return The numeric value. + * @throws JSONException if the key is not found or + * if the value is not a Number object and cannot be converted to a number. + */ + public double getDouble(String key) throws JSONException { + Object o = get(key); + try { + return o instanceof Number ? + ((Number)o).doubleValue() : + Double.valueOf((String)o).doubleValue(); + } catch (Exception e) { + throw new JSONException("JSONObject[" + quote(key) + + "] is not a number."); + } + } + + + /** + * Get the int value associated with a key. If the number value is too + * large for an int, it will be clipped. + * + * @param key A key string. + * @return The integer value. + * @throws JSONException if the key is not found or if the value cannot + * be converted to an integer. + */ + public int getInt(String key) throws JSONException { + Object o = get(key); + return o instanceof Number ? + ((Number)o).intValue() : (int)getDouble(key); + } + + + /** + * Get the JSONArray value associated with a key. + * + * @param key A key string. + * @return A JSONArray which is the value. + * @throws JSONException if the key is not found or + * if the value is not a JSONArray. + */ + public JSONArray getJSONArray(String key) throws JSONException { + Object o = get(key); + if (o instanceof JSONArray) { + return (JSONArray)o; + } + throw new JSONException("JSONObject[" + quote(key) + + "] is not a JSONArray."); + } + + + /** + * Get the JSONObject value associated with a key. + * + * @param key A key string. + * @return A JSONObject which is the value. + * @throws JSONException if the key is not found or + * if the value is not a JSONObject. + */ + public JSONObject getJSONObject(String key) throws JSONException { + Object o = get(key); + if (o instanceof JSONObject) { + return (JSONObject)o; + } + throw new JSONException("JSONObject[" + quote(key) + + "] is not a JSONObject."); + } + + + /** + * Get the long value associated with a key. If the number value is too + * long for a long, it will be clipped. + * + * @param key A key string. + * @return The long value. + * @throws JSONException if the key is not found or if the value cannot + * be converted to a long. + */ + public long getLong(String key) throws JSONException { + Object o = get(key); + return o instanceof Number ? + ((Number)o).longValue() : (long)getDouble(key); + } + + + /** + * Get an array of field names from a JSONObject. + * + * @return An array of field names, or null if there are no names. + */ + public static String[] getNames(JSONObject jo) { + int length = jo.length(); + if (length == 0) { + return null; + } + Iterator i = jo.keys(); + String[] names = new String[length]; + int j = 0; + while (i.hasNext()) { + names[j] = (String)i.next(); + j += 1; + } + return names; + } + + + /** + * Get an array of field names from an Object. + * + * @return An array of field names, or null if there are no names. + */ + public static String[] getNames(Object object) { + if (object == null) { + return null; + } + Class klass = object.getClass(); + Field[] fields = klass.getFields(); + int length = fields.length; + if (length == 0) { + return null; + } + String[] names = new String[length]; + for (int i = 0; i < length; i += 1) { + names[i] = fields[i].getName(); + } + return names; + } + + + /** + * Get the string associated with a key. + * + * @param key A key string. + * @return A string which is the value. + * @throws JSONException if the key is not found. + */ + public String getString(String key) throws JSONException { + return get(key).toString(); + } + + + /** + * Determine if the JSONObject contains a specific key. + * @param key A key string. + * @return true if the key exists in the JSONObject. + */ + public boolean has(String key) { + return this.map.containsKey(key); + } + + + /** + * Determine if the value associated with the key is null or if there is + * no value. + * @param key A key string. + * @return true if there is no value associated with the key or if + * the value is the JSONObject.NULL object. + */ + public boolean isNull(String key) { + return JSONObject.NULL.equals(opt(key)); + } + + + /** + * Get an enumeration of the keys of the JSONObject. + * + * @return An iterator of the keys. + */ + public Iterator keys() { + return this.map.keySet().iterator(); + } + + + /** + * Get the number of keys stored in the JSONObject. + * + * @return The number of keys in the JSONObject. + */ + public int length() { + return this.map.size(); + } + + + /** + * Produce a JSONArray containing the names of the elements of this + * JSONObject. + * @return A JSONArray containing the key strings, or null if the JSONObject + * is empty. + */ + public JSONArray names() { + JSONArray ja = new JSONArray(); + Iterator keys = keys(); + while (keys.hasNext()) { + ja.put(keys.next()); + } + return ja.length() == 0 ? null : ja; + } + + /** + * Produce a string from a Number. + * @param n A Number + * @return A String. + * @throws JSONException If n is a non-finite number. + */ + static public String numberToString(Number n) + throws JSONException { + if (n == null) { + throw new JSONException("Null pointer"); + } + testValidity(n); + +// Shave off trailing zeros and decimal point, if possible. + + String s = n.toString(); + if (s.indexOf('.') > 0 && s.indexOf('e') < 0 && s.indexOf('E') < 0) { + while (s.endsWith("0")) { + s = s.substring(0, s.length() - 1); + } + if (s.endsWith(".")) { + s = s.substring(0, s.length() - 1); + } + } + return s; + } + + + /** + * Get an optional value associated with a key. + * @param key A key string. + * @return An object which is the value, or null if there is no value. + */ + public Object opt(String key) { + return key == null ? null : this.map.get(key); + } + + + /** + * Get an optional boolean associated with a key. + * It returns false if there is no such key, or if the value is not + * Boolean.TRUE or the String "true". + * + * @param key A key string. + * @return The truth. + */ + public boolean optBoolean(String key) { + return optBoolean(key, false); + } + + + /** + * Get an optional boolean associated with a key. + * It returns the defaultValue if there is no such key, or if it is not + * a Boolean or the String "true" or "false" (case insensitive). + * + * @param key A key string. + * @param defaultValue The default. + * @return The truth. + */ + public boolean optBoolean(String key, boolean defaultValue) { + try { + return getBoolean(key); + } catch (Exception e) { + return defaultValue; + } + } + + + /** + * Put a key/value pair in the JSONObject, where the value will be a + * JSONArray which is produced from a Collection. + * @param key A key string. + * @param value A Collection value. + * @return this. + * @throws JSONException + */ + public JSONObject put(String key, Collection value) throws JSONException { + put(key, new JSONArray(value)); + return this; + } + + + /** + * Get an optional double associated with a key, + * or NaN if there is no such key or if its value is not a number. + * If the value is a string, an attempt will be made to evaluate it as + * a number. + * + * @param key A string which is the key. + * @return An object which is the value. + */ + public double optDouble(String key) { + return optDouble(key, Double.NaN); + } + + + /** + * Get an optional double associated with a key, or the + * defaultValue if there is no such key or if its value is not a number. + * If the value is a string, an attempt will be made to evaluate it as + * a number. + * + * @param key A key string. + * @param defaultValue The default. + * @return An object which is the value. + */ + public double optDouble(String key, double defaultValue) { + try { + Object o = opt(key); + return o instanceof Number ? ((Number)o).doubleValue() : + new Double((String)o).doubleValue(); + } catch (Exception e) { + return defaultValue; + } + } + + + /** + * Get an optional int value associated with a key, + * or zero if there is no such key or if the value is not a number. + * If the value is a string, an attempt will be made to evaluate it as + * a number. + * + * @param key A key string. + * @return An object which is the value. + */ + public int optInt(String key) { + return optInt(key, 0); + } + + + /** + * Get an optional int value associated with a key, + * or the default if there is no such key or if the value is not a number. + * If the value is a string, an attempt will be made to evaluate it as + * a number. + * + * @param key A key string. + * @param defaultValue The default. + * @return An object which is the value. + */ + public int optInt(String key, int defaultValue) { + try { + return getInt(key); + } catch (Exception e) { + return defaultValue; + } + } + + + /** + * Get an optional JSONArray associated with a key. + * It returns null if there is no such key, or if its value is not a + * JSONArray. + * + * @param key A key string. + * @return A JSONArray which is the value. + */ + public JSONArray optJSONArray(String key) { + Object o = opt(key); + return o instanceof JSONArray ? (JSONArray)o : null; + } + + + /** + * Get an optional JSONObject associated with a key. + * It returns null if there is no such key, or if its value is not a + * JSONObject. + * + * @param key A key string. + * @return A JSONObject which is the value. + */ + public JSONObject optJSONObject(String key) { + Object o = opt(key); + return o instanceof JSONObject ? (JSONObject)o : null; + } + + + /** + * Get an optional long value associated with a key, + * or zero if there is no such key or if the value is not a number. + * If the value is a string, an attempt will be made to evaluate it as + * a number. + * + * @param key A key string. + * @return An object which is the value. + */ + public long optLong(String key) { + return optLong(key, 0); + } + + + /** + * Get an optional long value associated with a key, + * or the default if there is no such key or if the value is not a number. + * If the value is a string, an attempt will be made to evaluate it as + * a number. + * + * @param key A key string. + * @param defaultValue The default. + * @return An object which is the value. + */ + public long optLong(String key, long defaultValue) { + try { + return getLong(key); + } catch (Exception e) { + return defaultValue; + } + } + + + /** + * Get an optional string associated with a key. + * It returns an empty string if there is no such key. If the value is not + * a string and is not null, then it is coverted to a string. + * + * @param key A key string. + * @return A string which is the value. + */ + public String optString(String key) { + return optString(key, ""); + } + + + /** + * Get an optional string associated with a key. + * It returns the defaultValue if there is no such key. + * + * @param key A key string. + * @param defaultValue The default. + * @return A string which is the value. + */ + public String optString(String key, String defaultValue) { + Object o = opt(key); + return o != null ? o.toString() : defaultValue; + } + + + /** + * Put a key/boolean pair in the JSONObject. + * + * @param key A key string. + * @param value A boolean which is the value. + * @return this. + * @throws JSONException If the key is null. + */ + public JSONObject put(String key, boolean value) throws JSONException { + put(key, value ? Boolean.TRUE : Boolean.FALSE); + return this; + } + + + /** + * Put a key/double pair in the JSONObject. + * + * @param key A key string. + * @param value A double which is the value. + * @return this. + * @throws JSONException If the key is null or if the number is invalid. + */ + public JSONObject put(String key, double value) throws JSONException { + put(key, new Double(value)); + return this; + } + + + /** + * Put a key/int pair in the JSONObject. + * + * @param key A key string. + * @param value An int which is the value. + * @return this. + * @throws JSONException If the key is null. + */ + public JSONObject put(String key, int value) throws JSONException { + put(key, new Integer(value)); + return this; + } + + + /** + * Put a key/long pair in the JSONObject. + * + * @param key A key string. + * @param value A long which is the value. + * @return this. + * @throws JSONException If the key is null. + */ + public JSONObject put(String key, long value) throws JSONException { + put(key, new Long(value)); + return this; + } + + + /** + * Put a key/value pair in the JSONObject, where the value will be a + * JSONObject which is produced from a Map. + * @param key A key string. + * @param value A Map value. + * @return this. + * @throws JSONException + */ + public JSONObject put(String key, Map value) throws JSONException { + put(key, new JSONObject(value)); + return this; + } + + + /** + * Put a key/value pair in the JSONObject. If the value is null, + * then the key will be removed from the JSONObject if it is present. + * @param key A key string. + * @param value An object which is the value. It should be of one of these + * types: Boolean, Double, Integer, JSONArray, JSONObject, Long, String, + * or the JSONObject.NULL object. + * @return this. + * @throws JSONException If the value is non-finite number + * or if the key is null. + */ + public JSONObject put(String key, Object value) throws JSONException { + if (key == null) { + throw new JSONException("Null key."); + } + if (value != null) { + testValidity(value); + this.map.put(key, value); + } else { + remove(key); + } + return this; + } + + + /** + * Put a key/value pair in the JSONObject, but only if the key and the + * value are both non-null, and only if there is not already a member + * with that name. + * @param key + * @param value + * @return his. + * @throws JSONException if the key is a duplicate + */ + public JSONObject putOnce(String key, Object value) throws JSONException { + if (key != null && value != null) { + if (opt(key) != null) { + throw new JSONException("Duplicate key \"" + key + "\""); + } + put(key, value); + } + return this; + } + + + /** + * Put a key/value pair in the JSONObject, but only if the + * key and the value are both non-null. + * @param key A key string. + * @param value An object which is the value. It should be of one of these + * types: Boolean, Double, Integer, JSONArray, JSONObject, Long, String, + * or the JSONObject.NULL object. + * @return this. + * @throws JSONException If the value is a non-finite number. + */ + public JSONObject putOpt(String key, Object value) throws JSONException { + if (key != null && value != null) { + put(key, value); + } + return this; + } + + + /** + * Produce a string in double quotes with backslash sequences in all the + * right places. A backslash will be inserted within = '\u0080' && c < '\u00a0') || + (c >= '\u2000' && c < '\u2100')) { + t = "000" + Integer.toHexString(c); + sb.append("\\u" + t.substring(t.length() - 4)); + } else { + sb.append(c); + } + } + } + sb.append('"'); + return sb.toString(); + } + + /** + * Remove a name and its value, if present. + * @param key The name to be removed. + * @return The value that was associated with the name, + * or null if there was no value. + */ + public Object remove(String key) { + return this.map.remove(key); + } + + /** + * Get an enumeration of the keys of the JSONObject. + * The keys will be sorted alphabetically. + * + * @return An iterator of the keys. + */ + public Iterator sortedKeys() { + return new TreeSet(this.map.keySet()).iterator(); + } + + /** + * Try to convert a string into a number, boolean, or null. If the string + * can't be converted, return the string. + * @param s A String. + * @return A simple JSON value. + */ + static public Object stringToValue(String s) { + if (s.equals("")) { + return s; + } + if (s.equalsIgnoreCase("true")) { + return Boolean.TRUE; + } + if (s.equalsIgnoreCase("false")) { + return Boolean.FALSE; + } + if (s.equalsIgnoreCase("null")) { + return JSONObject.NULL; + } + + /* + * If it might be a number, try converting it. We support the 0- and 0x- + * conventions. If a number cannot be produced, then the value will just + * be a string. Note that the 0-, 0x-, plus, and implied string + * conventions are non-standard. A JSON parser is free to accept + * non-JSON forms as long as it accepts all correct JSON forms. + */ + + char b = s.charAt(0); + if ((b >= '0' && b <= '9') || b == '.' || b == '-' || b == '+') { + if (b == '0') { + if (s.length() > 2 && + (s.charAt(1) == 'x' || s.charAt(1) == 'X')) { + try { + return new Integer(Integer.parseInt(s.substring(2), + 16)); + } catch (Exception e) { + /* Ignore the error */ + } + } else { + try { + return new Integer(Integer.parseInt(s, 8)); + } catch (Exception e) { + /* Ignore the error */ + } + } + } + try { + if (s.indexOf('.') > -1 || s.indexOf('e') > -1 || s.indexOf('E') > -1) { + return Double.valueOf(s); + } else { + Long myLong = new Long(s); + if (myLong.longValue() == myLong.intValue()) { + return new Integer(myLong.intValue()); + } else { + return myLong; + } + } + } catch (Exception f) { + /* Ignore the error */ + } + } + return s; + } + + + /** + * Throw an exception if the object is an NaN or infinite number. + * @param o The object to test. + * @throws JSONException If o is a non-finite number. + */ + static void testValidity(Object o) throws JSONException { + if (o != null) { + if (o instanceof Double) { + if (((Double)o).isInfinite() || ((Double)o).isNaN()) { + throw new JSONException( + "JSON does not allow non-finite numbers."); + } + } else if (o instanceof Float) { + if (((Float)o).isInfinite() || ((Float)o).isNaN()) { + throw new JSONException( + "JSON does not allow non-finite numbers."); + } + } + } + } + + + /** + * Produce a JSONArray containing the values of the members of this + * JSONObject. + * @param names A JSONArray containing a list of key strings. This + * determines the sequence of the values in the result. + * @return A JSONArray of values. + * @throws JSONException If any of the values are non-finite numbers. + */ + public JSONArray toJSONArray(JSONArray names) throws JSONException { + if (names == null || names.length() == 0) { + return null; + } + JSONArray ja = new JSONArray(); + for (int i = 0; i < names.length(); i += 1) { + ja.put(this.opt(names.getString(i))); + } + return ja; + } + + /** + * Make a JSON text of this JSONObject. For compactness, no whitespace + * is added. If this would not result in a syntactically correct JSON text, + * then null will be returned instead. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @return a printable, displayable, portable, transmittable + * representation of the object, beginning + * with { (left brace) and ending + * with } (right brace). + */ + public String toString() { + try { + Iterator keys = keys(); + StringBuffer sb = new StringBuffer("{"); + + while (keys.hasNext()) { + if (sb.length() > 1) { + sb.append(','); + } + Object o = keys.next(); + sb.append(quote(o.toString())); + sb.append(':'); + sb.append(valueToString(this.map.get(o))); + } + sb.append('}'); + return sb.toString(); + } catch (Exception e) { + return null; + } + } + + + /** + * Make a prettyprinted JSON text of this JSONObject. + *

+ * Warning: This method assumes that the data structure is acyclical. + * @param indentFactor The number of spaces to add to each level of + * indentation. + * @return a printable, displayable, portable, transmittable + * representation of the object, beginning + * with { (left brace) and ending + * with } (right brace). + * @throws JSONException If the object contains an invalid number. + */ + public String toString(int indentFactor) throws JSONException { + return toString(indentFactor, 0); + } + + + /** + * Make a prettyprinted JSON text of this JSONObject. + *

+ * Warning: This method assumes that the data structure is acyclical. + * @param indentFactor The number of spaces to add to each level of + * indentation. + * @param indent The indentation of the top level. + * @return a printable, displayable, transmittable + * representation of the object, beginning + * with { (left brace) and ending + * with } (right brace). + * @throws JSONException If the object contains an invalid number. + */ + String toString(int indentFactor, int indent) throws JSONException { + int j; + int n = length(); + if (n == 0) { + return "{}"; + } + Iterator keys = sortedKeys(); + StringBuffer sb = new StringBuffer("{"); + int newindent = indent + indentFactor; + Object o; + if (n == 1) { + o = keys.next(); + sb.append(quote(o.toString())); + sb.append(": "); + sb.append(valueToString(this.map.get(o), indentFactor, + indent)); + } else { + while (keys.hasNext()) { + o = keys.next(); + if (sb.length() > 1) { + sb.append(",\n"); + } else { + sb.append('\n'); + } + for (j = 0; j < newindent; j += 1) { + sb.append(' '); + } + sb.append(quote(o.toString())); + sb.append(": "); + sb.append(valueToString(this.map.get(o), indentFactor, + newindent)); + } + if (sb.length() > 1) { + sb.append('\n'); + for (j = 0; j < indent; j += 1) { + sb.append(' '); + } + } + } + sb.append('}'); + return sb.toString(); + } + + + /** + * Make a JSON text of an Object value. If the object has an + * value.toJSONString() method, then that method will be used to produce + * the JSON text. The method is required to produce a strictly + * conforming text. If the object does not contain a toJSONString + * method (which is the most common case), then a text will be + * produced by other means. If the value is an array or Collection, + * then a JSONArray will be made from it and its toJSONString method + * will be called. If the value is a MAP, then a JSONObject will be made + * from it and its toJSONString method will be called. Otherwise, the + * value's toString method will be called, and the result will be quoted. + * + *

+ * Warning: This method assumes that the data structure is acyclical. + * @param value The value to be serialized. + * @return a printable, displayable, transmittable + * representation of the object, beginning + * with { (left brace) and ending + * with } (right brace). + * @throws JSONException If the value is or contains an invalid number. + */ + static String valueToString(Object value) throws JSONException { + if (value == null || value.equals(null)) { + return "null"; + } + if (value instanceof JSONString) { + Object o; + try { + o = ((JSONString)value).toJSONString(); + } catch (Exception e) { + throw new JSONException(e); + } + if (o instanceof String) { + return (String)o; + } + throw new JSONException("Bad value from toJSONString: " + o); + } + if (value instanceof Number) { + return numberToString((Number) value); + } + if (value instanceof Boolean || value instanceof JSONObject || + value instanceof JSONArray) { + return value.toString(); + } + if (value instanceof Map) { + return new JSONObject((Map)value).toString(); + } + if (value instanceof Collection) { + return new JSONArray((Collection)value).toString(); + } + if (value.getClass().isArray()) { + return new JSONArray(value).toString(); + } + return quote(value.toString()); + } + + + /** + * Make a prettyprinted JSON text of an object value. + *

+ * Warning: This method assumes that the data structure is acyclical. + * @param value The value to be serialized. + * @param indentFactor The number of spaces to add to each level of + * indentation. + * @param indent The indentation of the top level. + * @return a printable, displayable, transmittable + * representation of the object, beginning + * with { (left brace) and ending + * with } (right brace). + * @throws JSONException If the object contains an invalid number. + */ + static String valueToString(Object value, int indentFactor, int indent) + throws JSONException { + if (value == null || value.equals(null)) { + return "null"; + } + try { + if (value instanceof JSONString) { + Object o = ((JSONString)value).toJSONString(); + if (o instanceof String) { + return (String)o; + } + } + } catch (Exception e) { + /* forget about it */ + } + if (value instanceof Number) { + return numberToString((Number) value); + } + if (value instanceof Boolean) { + return value.toString(); + } + if (value instanceof JSONObject) { + return ((JSONObject)value).toString(indentFactor, indent); + } + if (value instanceof JSONArray) { + return ((JSONArray)value).toString(indentFactor, indent); + } + if (value instanceof Map) { + return new JSONObject((Map)value).toString(indentFactor, indent); + } + if (value instanceof Collection) { + return new JSONArray((Collection)value).toString(indentFactor, indent); + } + if (value.getClass().isArray()) { + return new JSONArray(value).toString(indentFactor, indent); + } + return quote(value.toString()); + } + + + /** + * Write the contents of the JSONObject as JSON text to a writer. + * For compactness, no whitespace is added. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @return The writer. + * @throws JSONException + */ + public Writer write(Writer writer) throws JSONException { + try { + boolean b = false; + Iterator keys = keys(); + writer.write('{'); + + while (keys.hasNext()) { + if (b) { + writer.write(','); + } + Object k = keys.next(); + writer.write(quote(k.toString())); + writer.write(':'); + Object v = this.map.get(k); + if (v instanceof JSONObject) { + ((JSONObject)v).write(writer); + } else if (v instanceof JSONArray) { + ((JSONArray)v).write(writer); + } else { + writer.write(valueToString(v)); + } + b = true; + } + writer.write('}'); + return writer; + } catch (IOException e) { + throw new JSONException(e); + } + } +} \ No newline at end of file diff --git a/tests/harness/lib/yuitest/java/src/org/json/JSONString.java b/tests/harness/lib/yuitest/java/src/org/json/JSONString.java new file mode 100644 index 000000000..7f4f65b67 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/org/json/JSONString.java @@ -0,0 +1,18 @@ +package org.json; +/** + * The JSONString interface allows a toJSONString() + * method so that a class can change the behavior of + * JSONObject.toString(), JSONArray.toString(), + * and JSONWriter.value(Object). The + * toJSONString method will be used instead of the default behavior + * of using the Object's toString() method and quoting the result. + */ +public interface JSONString { + /** + * The toJSONString method allows a class to produce its own JSON + * serialization. + * + * @return A strictly syntactically correct JSON text. + */ + public String toJSONString(); +} diff --git a/tests/harness/lib/yuitest/java/src/org/json/JSONStringer.java b/tests/harness/lib/yuitest/java/src/org/json/JSONStringer.java new file mode 100644 index 000000000..32c9f7f44 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/org/json/JSONStringer.java @@ -0,0 +1,78 @@ +package org.json; + +/* +Copyright (c) 2006 JSON.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +The Software shall be used for Good, not Evil. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +import java.io.StringWriter; + +/** + * JSONStringer provides a quick and convenient way of producing JSON text. + * The texts produced strictly conform to JSON syntax rules. No whitespace is + * added, so the results are ready for transmission or storage. Each instance of + * JSONStringer can produce one JSON text. + *

+ * A JSONStringer instance provides a value method for appending + * values to the + * text, and a key + * method for adding keys before values in objects. There are array + * and endArray methods that make and bound array values, and + * object and endObject methods which make and bound + * object values. All of these methods return the JSONWriter instance, + * permitting cascade style. For example,

+ * myString = new JSONStringer()
+ *     .object()
+ *         .key("JSON")
+ *         .value("Hello, World!")
+ *     .endObject()
+ *     .toString();
which produces the string
+ * {"JSON":"Hello, World!"}
+ *

+ * The first method called must be array or object. + * There are no methods for adding commas or colons. JSONStringer adds them for + * you. Objects and arrays can be nested up to 20 levels deep. + *

+ * This can sometimes be easier than using a JSONObject to build a string. + * @author JSON.org + * @version 2008-09-18 + */ +public class JSONStringer extends JSONWriter { + /** + * Make a fresh JSONStringer. It can be used to build one JSON text. + */ + public JSONStringer() { + super(new StringWriter()); + } + + /** + * Return the JSON text. This method is used to obtain the product of the + * JSONStringer instance. It will return null if there was a + * problem in the construction of the JSON text (such as the calls to + * array were not properly balanced with calls to + * endArray). + * @return The JSON text. + */ + public String toString() { + return this.mode == 'd' ? this.writer.toString() : null; + } +} diff --git a/tests/harness/lib/yuitest/java/src/org/json/JSONTokener.java b/tests/harness/lib/yuitest/java/src/org/json/JSONTokener.java new file mode 100644 index 000000000..c78059d42 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/org/json/JSONTokener.java @@ -0,0 +1,425 @@ +package org.json; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; + +/* +Copyright (c) 2002 JSON.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +The Software shall be used for Good, not Evil. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +/** + * A JSONTokener takes a source string and extracts characters and tokens from + * it. It is used by the JSONObject and JSONArray constructors to parse + * JSON source strings. + * @author JSON.org + * @version 2008-09-18 + */ +public class JSONTokener { + + private int index; + private Reader reader; + private char lastChar; + private boolean useLastChar; + + + /** + * Construct a JSONTokener from a string. + * + * @param reader A reader. + */ + public JSONTokener(Reader reader) { + this.reader = reader.markSupported() ? + reader : new BufferedReader(reader); + this.useLastChar = false; + this.index = 0; + } + + + /** + * Construct a JSONTokener from a string. + * + * @param s A source string. + */ + public JSONTokener(String s) { + this(new StringReader(s)); + } + + + /** + * Back up one character. This provides a sort of lookahead capability, + * so that you can test for a digit or letter before attempting to parse + * the next number or identifier. + */ + public void back() throws JSONException { + if (useLastChar || index <= 0) { + throw new JSONException("Stepping back two steps is not supported"); + } + index -= 1; + useLastChar = true; + } + + + + /** + * Get the hex value of a character (base16). + * @param c A character between '0' and '9' or between 'A' and 'F' or + * between 'a' and 'f'. + * @return An int between 0 and 15, or -1 if c was not a hex digit. + */ + public static int dehexchar(char c) { + if (c >= '0' && c <= '9') { + return c - '0'; + } + if (c >= 'A' && c <= 'F') { + return c - ('A' - 10); + } + if (c >= 'a' && c <= 'f') { + return c - ('a' - 10); + } + return -1; + } + + + /** + * Determine if the source string still contains characters that next() + * can consume. + * @return true if not yet at the end of the source. + */ + public boolean more() throws JSONException { + char nextChar = next(); + if (nextChar == 0) { + return false; + } + back(); + return true; + } + + + /** + * Get the next character in the source string. + * + * @return The next character, or 0 if past the end of the source string. + */ + public char next() throws JSONException { + if (this.useLastChar) { + this.useLastChar = false; + if (this.lastChar != 0) { + this.index += 1; + } + return this.lastChar; + } + int c; + try { + c = this.reader.read(); + } catch (IOException exc) { + throw new JSONException(exc); + } + + if (c <= 0) { // End of stream + this.lastChar = 0; + return 0; + } + this.index += 1; + this.lastChar = (char) c; + return this.lastChar; + } + + + /** + * Consume the next character, and check that it matches a specified + * character. + * @param c The character to match. + * @return The character. + * @throws JSONException if the character does not match. + */ + public char next(char c) throws JSONException { + char n = next(); + if (n != c) { + throw syntaxError("Expected '" + c + "' and instead saw '" + + n + "'"); + } + return n; + } + + + /** + * Get the next n characters. + * + * @param n The number of characters to take. + * @return A string of n characters. + * @throws JSONException + * Substring bounds error if there are not + * n characters remaining in the source string. + */ + public String next(int n) throws JSONException { + if (n == 0) { + return ""; + } + + char[] buffer = new char[n]; + int pos = 0; + + if (this.useLastChar) { + this.useLastChar = false; + buffer[0] = this.lastChar; + pos = 1; + } + + try { + int len; + while ((pos < n) && ((len = reader.read(buffer, pos, n - pos)) != -1)) { + pos += len; + } + } catch (IOException exc) { + throw new JSONException(exc); + } + this.index += pos; + + if (pos < n) { + throw syntaxError("Substring bounds error"); + } + + this.lastChar = buffer[n - 1]; + return new String(buffer); + } + + + /** + * Get the next char in the string, skipping whitespace. + * @throws JSONException + * @return A character, or 0 if there are no more characters. + */ + public char nextClean() throws JSONException { + for (;;) { + char c = next(); + if (c == 0 || c > ' ') { + return c; + } + } + } + + + /** + * Return the characters up to the next close quote character. + * Backslash processing is done. The formal JSON format does not + * allow strings in single quotes, but an implementation is allowed to + * accept them. + * @param quote The quoting character, either + * " (double quote) or + * ' (single quote). + * @return A String. + * @throws JSONException Unterminated string. + */ + public String nextString(char quote) throws JSONException { + char c; + StringBuffer sb = new StringBuffer(); + for (;;) { + c = next(); + switch (c) { + case 0: + case '\n': + case '\r': + throw syntaxError("Unterminated string"); + case '\\': + c = next(); + switch (c) { + case 'b': + sb.append('\b'); + break; + case 't': + sb.append('\t'); + break; + case 'n': + sb.append('\n'); + break; + case 'f': + sb.append('\f'); + break; + case 'r': + sb.append('\r'); + break; + case 'u': + sb.append((char)Integer.parseInt(next(4), 16)); + break; + case '"': + case '\'': + case '\\': + case '/': + sb.append(c); + break; + default: + throw syntaxError("Illegal escape."); + } + break; + default: + if (c == quote) { + return sb.toString(); + } + sb.append(c); + } + } + } + + + /** + * Get the text up but not including the specified character or the + * end of line, whichever comes first. + * @param d A delimiter character. + * @return A string. + */ + public String nextTo(char d) throws JSONException { + StringBuffer sb = new StringBuffer(); + for (;;) { + char c = next(); + if (c == d || c == 0 || c == '\n' || c == '\r') { + if (c != 0) { + back(); + } + return sb.toString().trim(); + } + sb.append(c); + } + } + + + /** + * Get the text up but not including one of the specified delimiter + * characters or the end of line, whichever comes first. + * @param delimiters A set of delimiter characters. + * @return A string, trimmed. + */ + public String nextTo(String delimiters) throws JSONException { + char c; + StringBuffer sb = new StringBuffer(); + for (;;) { + c = next(); + if (delimiters.indexOf(c) >= 0 || c == 0 || + c == '\n' || c == '\r') { + if (c != 0) { + back(); + } + return sb.toString().trim(); + } + sb.append(c); + } + } + + + /** + * Get the next value. The value can be a Boolean, Double, Integer, + * JSONArray, JSONObject, Long, or String, or the JSONObject.NULL object. + * @throws JSONException If syntax error. + * + * @return An object. + */ + public Object nextValue() throws JSONException { + char c = nextClean(); + String s; + + switch (c) { + case '"': + case '\'': + return nextString(c); + case '{': + back(); + return new JSONObject(this); + case '[': + case '(': + back(); + return new JSONArray(this); + } + + /* + * Handle unquoted text. This could be the values true, false, or + * null, or it can be a number. An implementation (such as this one) + * is allowed to also accept non-standard forms. + * + * Accumulate characters until we reach the end of the text or a + * formatting character. + */ + + StringBuffer sb = new StringBuffer(); + while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) { + sb.append(c); + c = next(); + } + back(); + + s = sb.toString().trim(); + if (s.equals("")) { + throw syntaxError("Missing value"); + } + return JSONObject.stringToValue(s); + } + + + /** + * Skip characters until the next character is the requested character. + * If the requested character is not found, no characters are skipped. + * @param to A character to skip to. + * @return The requested character, or zero if the requested character + * is not found. + */ + public char skipTo(char to) throws JSONException { + char c; + try { + int startIndex = this.index; + reader.mark(Integer.MAX_VALUE); + do { + c = next(); + if (c == 0) { + reader.reset(); + this.index = startIndex; + return c; + } + } while (c != to); + } catch (IOException exc) { + throw new JSONException(exc); + } + + back(); + return c; + } + + /** + * Make a JSONException to signal a syntax error. + * + * @param message The error message. + * @return A JSONException object, suitable for throwing + */ + public JSONException syntaxError(String message) { + return new JSONException(message + toString()); + } + + + /** + * Make a printable string of this JSONTokener. + * + * @return " at character [this.index]" + */ + public String toString() { + return " at character " + index; + } +} \ No newline at end of file diff --git a/tests/harness/lib/yuitest/java/src/org/json/JSONWriter.java b/tests/harness/lib/yuitest/java/src/org/json/JSONWriter.java new file mode 100644 index 000000000..32c2414d0 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/org/json/JSONWriter.java @@ -0,0 +1,323 @@ +package org.json; + +import java.io.IOException; +import java.io.Writer; + +/* +Copyright (c) 2006 JSON.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +The Software shall be used for Good, not Evil. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +/** + * JSONWriter provides a quick and convenient way of producing JSON text. + * The texts produced strictly conform to JSON syntax rules. No whitespace is + * added, so the results are ready for transmission or storage. Each instance of + * JSONWriter can produce one JSON text. + *

+ * A JSONWriter instance provides a value method for appending + * values to the + * text, and a key + * method for adding keys before values in objects. There are array + * and endArray methods that make and bound array values, and + * object and endObject methods which make and bound + * object values. All of these methods return the JSONWriter instance, + * permitting a cascade style. For example,

+ * new JSONWriter(myWriter)
+ *     .object()
+ *         .key("JSON")
+ *         .value("Hello, World!")
+ *     .endObject();
which writes
+ * {"JSON":"Hello, World!"}
+ *

+ * The first method called must be array or object. + * There are no methods for adding commas or colons. JSONWriter adds them for + * you. Objects and arrays can be nested up to 20 levels deep. + *

+ * This can sometimes be easier than using a JSONObject to build a string. + * @author JSON.org + * @version 2008-09-22 + */ +public class JSONWriter { + private static final int maxdepth = 20; + + /** + * The comma flag determines if a comma should be output before the next + * value. + */ + private boolean comma; + + /** + * The current mode. Values: + * 'a' (array), + * 'd' (done), + * 'i' (initial), + * 'k' (key), + * 'o' (object). + */ + protected char mode; + + /** + * The object/array stack. + */ + private JSONObject stack[]; + + /** + * The stack top index. A value of 0 indicates that the stack is empty. + */ + private int top; + + /** + * The writer that will receive the output. + */ + protected Writer writer; + + /** + * Make a fresh JSONWriter. It can be used to build one JSON text. + */ + public JSONWriter(Writer w) { + this.comma = false; + this.mode = 'i'; + this.stack = new JSONObject[maxdepth]; + this.top = 0; + this.writer = w; + } + + /** + * Append a value. + * @param s A string value. + * @return this + * @throws JSONException If the value is out of sequence. + */ + private JSONWriter append(String s) throws JSONException { + if (s == null) { + throw new JSONException("Null pointer"); + } + if (this.mode == 'o' || this.mode == 'a') { + try { + if (this.comma && this.mode == 'a') { + this.writer.write(','); + } + this.writer.write(s); + } catch (IOException e) { + throw new JSONException(e); + } + if (this.mode == 'o') { + this.mode = 'k'; + } + this.comma = true; + return this; + } + throw new JSONException("Value out of sequence."); + } + + /** + * Begin appending a new array. All values until the balancing + * endArray will be appended to this array. The + * endArray method must be called to mark the array's end. + * @return this + * @throws JSONException If the nesting is too deep, or if the object is + * started in the wrong place (for example as a key or after the end of the + * outermost array or object). + */ + public JSONWriter array() throws JSONException { + if (this.mode == 'i' || this.mode == 'o' || this.mode == 'a') { + this.push(null); + this.append("["); + this.comma = false; + return this; + } + throw new JSONException("Misplaced array."); + } + + /** + * End something. + * @param m Mode + * @param c Closing character + * @return this + * @throws JSONException If unbalanced. + */ + private JSONWriter end(char m, char c) throws JSONException { + if (this.mode != m) { + throw new JSONException(m == 'o' ? "Misplaced endObject." : + "Misplaced endArray."); + } + this.pop(m); + try { + this.writer.write(c); + } catch (IOException e) { + throw new JSONException(e); + } + this.comma = true; + return this; + } + + /** + * End an array. This method most be called to balance calls to + * array. + * @return this + * @throws JSONException If incorrectly nested. + */ + public JSONWriter endArray() throws JSONException { + return this.end('a', ']'); + } + + /** + * End an object. This method most be called to balance calls to + * object. + * @return this + * @throws JSONException If incorrectly nested. + */ + public JSONWriter endObject() throws JSONException { + return this.end('k', '}'); + } + + /** + * Append a key. The key will be associated with the next value. In an + * object, every value must be preceded by a key. + * @param s A key string. + * @return this + * @throws JSONException If the key is out of place. For example, keys + * do not belong in arrays or if the key is null. + */ + public JSONWriter key(String s) throws JSONException { + if (s == null) { + throw new JSONException("Null key."); + } + if (this.mode == 'k') { + try { + stack[top - 1].putOnce(s, Boolean.TRUE); + if (this.comma) { + this.writer.write(','); + } + this.writer.write(JSONObject.quote(s)); + this.writer.write(':'); + this.comma = false; + this.mode = 'o'; + return this; + } catch (IOException e) { + throw new JSONException(e); + } + } + throw new JSONException("Misplaced key."); + } + + + /** + * Begin appending a new object. All keys and values until the balancing + * endObject will be appended to this object. The + * endObject method must be called to mark the object's end. + * @return this + * @throws JSONException If the nesting is too deep, or if the object is + * started in the wrong place (for example as a key or after the end of the + * outermost array or object). + */ + public JSONWriter object() throws JSONException { + if (this.mode == 'i') { + this.mode = 'o'; + } + if (this.mode == 'o' || this.mode == 'a') { + this.append("{"); + this.push(new JSONObject()); + this.comma = false; + return this; + } + throw new JSONException("Misplaced object."); + + } + + + /** + * Pop an array or object scope. + * @param c The scope to close. + * @throws JSONException If nesting is wrong. + */ + private void pop(char c) throws JSONException { + if (this.top <= 0) { + throw new JSONException("Nesting error."); + } + char m = this.stack[this.top - 1] == null ? 'a' : 'k'; + if (m != c) { + throw new JSONException("Nesting error."); + } + this.top -= 1; + this.mode = this.top == 0 ? 'd' : this.stack[this.top - 1] == null ? 'a' : 'k'; + } + + /** + * Push an array or object scope. + * @param c The scope to open. + * @throws JSONException If nesting is too deep. + */ + private void push(JSONObject jo) throws JSONException { + if (this.top >= maxdepth) { + throw new JSONException("Nesting too deep."); + } + this.stack[this.top] = jo; + this.mode = jo == null ? 'a' : 'k'; + this.top += 1; + } + + + /** + * Append either the value true or the value + * false. + * @param b A boolean. + * @return this + * @throws JSONException + */ + public JSONWriter value(boolean b) throws JSONException { + return this.append(b ? "true" : "false"); + } + + /** + * Append a double value. + * @param d A double. + * @return this + * @throws JSONException If the number is not finite. + */ + public JSONWriter value(double d) throws JSONException { + return this.value(new Double(d)); + } + + /** + * Append a long value. + * @param l A long. + * @return this + * @throws JSONException + */ + public JSONWriter value(long l) throws JSONException { + return this.append(Long.toString(l)); + } + + + /** + * Append an object value. + * @param o The object to append. It can be null, or a Boolean, Number, + * String, JSONObject, or JSONArray, or an object with a toJSONString() + * method. + * @return this + * @throws JSONException If the value is out of sequence. + */ + public JSONWriter value(Object o) throws JSONException { + return this.append(JSONObject.valueToString(o)); + } +} diff --git a/tests/harness/lib/yuitest/java/src/org/json/XML.java b/tests/harness/lib/yuitest/java/src/org/json/XML.java new file mode 100644 index 000000000..4d95be4b6 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/org/json/XML.java @@ -0,0 +1,437 @@ +package org.json; + +/* +Copyright (c) 2002 JSON.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +The Software shall be used for Good, not Evil. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +import java.util.Iterator; + + +/** + * This provides static methods to convert an XML text into a JSONObject, + * and to covert a JSONObject into an XML text. + * @author JSON.org + * @version 2008-10-14 + */ +public class XML { + + /** The Character '&'. */ + public static final Character AMP = new Character('&'); + + /** The Character '''. */ + public static final Character APOS = new Character('\''); + + /** The Character '!'. */ + public static final Character BANG = new Character('!'); + + /** The Character '='. */ + public static final Character EQ = new Character('='); + + /** The Character '>'. */ + public static final Character GT = new Character('>'); + + /** The Character '<'. */ + public static final Character LT = new Character('<'); + + /** The Character '?'. */ + public static final Character QUEST = new Character('?'); + + /** The Character '"'. */ + public static final Character QUOT = new Character('"'); + + /** The Character '/'. */ + public static final Character SLASH = new Character('/'); + + /** + * Replace special characters with XML escapes: + *

+     * & (ampersand) is replaced by &amp;
+     * < (less than) is replaced by &lt;
+     * > (greater than) is replaced by &gt;
+     * " (double quote) is replaced by &quot;
+     * 
+ * @param string The string to be escaped. + * @return The escaped string. + */ + public static String escape(String string) { + StringBuffer sb = new StringBuffer(); + for (int i = 0, len = string.length(); i < len; i++) { + char c = string.charAt(i); + switch (c) { + case '&': + sb.append("&"); + break; + case '<': + sb.append("<"); + break; + case '>': + sb.append(">"); + break; + case '"': + sb.append("""); + break; + default: + sb.append(c); + } + } + return sb.toString(); + } + + /** + * Throw an exception if the string contains whitespace. + * Whitespace is not allowed in tagNames and attributes. + * @param string + * @throws JSONException + */ + public static void noSpace(String string) throws JSONException { + int i, length = string.length(); + if (length == 0) { + throw new JSONException("Empty string."); + } + for (i = 0; i < length; i += 1) { + if (Character.isWhitespace(string.charAt(i))) { + throw new JSONException("'" + string + + "' contains a space character."); + } + } + } + + /** + * Scan the content following the named tag, attaching it to the context. + * @param x The XMLTokener containing the source string. + * @param context The JSONObject that will include the new material. + * @param name The tag name. + * @return true if the close tag is processed. + * @throws JSONException + */ + private static boolean parse(XMLTokener x, JSONObject context, + String name) throws JSONException { + char c; + int i; + String n; + JSONObject o = null; + String s; + Object t; + +// Test for and skip past these forms: +// +// +// +// +// Report errors for these forms: +// <> +// <= +// << + + t = x.nextToken(); + +// "); + return false; + } + x.back(); + } else if (c == '[') { + t = x.nextToken(); + if (t.equals("CDATA")) { + if (x.next() == '[') { + s = x.nextCDATA(); + if (s.length() > 0) { + context.accumulate("content", s); + } + return false; + } + } + throw x.syntaxError("Expected 'CDATA['"); + } + i = 1; + do { + t = x.nextMeta(); + if (t == null) { + throw x.syntaxError("Missing '>' after ' 0); + return false; + } else if (t == QUEST) { + +// "); + return false; + } else if (t == SLASH) { + +// Close tag + + } else if (t == SLASH) { + if (x.nextToken() != GT) { + throw x.syntaxError("Misshaped tag"); + } + context.accumulate(n, o); + return false; + +// Content, between <...> and + + } else if (t == GT) { + for (;;) { + t = x.nextContent(); + if (t == null) { + if (n != null) { + throw x.syntaxError("Unclosed tag " + n); + } + return false; + } else if (t instanceof String) { + s = (String)t; + if (s.length() > 0) { + o.accumulate("content", JSONObject.stringToValue(s)); + } + +// Nested element + + } else if (t == LT) { + if (parse(x, o, n)) { + if (o.length() == 0) { + context.accumulate(n, ""); + } else if (o.length() == 1 && + o.opt("content") != null) { + context.accumulate(n, o.opt("content")); + } else { + context.accumulate(n, o); + } + return false; + } + } + } + } else { + throw x.syntaxError("Misshaped tag"); + } + } + } + } + + + /** + * Convert a well-formed (but not necessarily valid) XML string into a + * JSONObject. Some information may be lost in this transformation + * because JSON is a data format and XML is a document format. XML uses + * elements, attributes, and content text, while JSON uses unordered + * collections of name/value pairs and arrays of values. JSON does not + * does not like to distinguish between elements and attributes. + * Sequences of similar elements are represented as JSONArrays. Content + * text may be placed in a "content" member. Comments, prologs, DTDs, and + * <[ [ ]]> are ignored. + * @param string The source string. + * @return A JSONObject containing the structured data from the XML string. + * @throws JSONException + */ + public static JSONObject toJSONObject(String string) throws JSONException { + JSONObject o = new JSONObject(); + XMLTokener x = new XMLTokener(string); + while (x.more() && x.skipPast("<")) { + parse(x, o, null); + } + return o; + } + + + /** + * Convert a JSONObject into a well-formed, element-normal XML string. + * @param o A JSONObject. + * @return A string. + * @throws JSONException + */ + public static String toString(Object o) throws JSONException { + return toString(o, null); + } + + + /** + * Convert a JSONObject into a well-formed, element-normal XML string. + * @param o A JSONObject. + * @param tagName The optional name of the enclosing tag. + * @return A string. + * @throws JSONException + */ + public static String toString(Object o, String tagName) + throws JSONException { + StringBuffer b = new StringBuffer(); + int i; + JSONArray ja; + JSONObject jo; + String k; + Iterator keys; + int len; + String s; + Object v; + if (o instanceof JSONObject) { + +// Emit + + if (tagName != null) { + b.append('<'); + b.append(tagName); + b.append('>'); + } + +// Loop thru the keys. + + jo = (JSONObject)o; + keys = jo.keys(); + while (keys.hasNext()) { + k = keys.next().toString(); + v = jo.opt(k); + if (v == null) { + v = ""; + } + if (v instanceof String) { + s = (String)v; + } else { + s = null; + } + +// Emit content in body + + if (k.equals("content")) { + if (v instanceof JSONArray) { + ja = (JSONArray)v; + len = ja.length(); + for (i = 0; i < len; i += 1) { + if (i > 0) { + b.append('\n'); + } + b.append(escape(ja.get(i).toString())); + } + } else { + b.append(escape(v.toString())); + } + +// Emit an array of similar keys + + } else if (v instanceof JSONArray) { + ja = (JSONArray)v; + len = ja.length(); + for (i = 0; i < len; i += 1) { + v = ja.get(i); + if (v instanceof JSONArray) { + b.append('<'); + b.append(k); + b.append('>'); + b.append(toString(v)); + b.append("'); + } else { + b.append(toString(v, k)); + } + } + } else if (v.equals("")) { + b.append('<'); + b.append(k); + b.append("/>"); + +// Emit a new tag + + } else { + b.append(toString(v, k)); + } + } + if (tagName != null) { + +// Emit the close tag + + b.append("'); + } + return b.toString(); + +// XML does not have good support for arrays. If an array appears in a place +// where XML is lacking, synthesize an element. + + } else if (o instanceof JSONArray) { + ja = (JSONArray)o; + len = ja.length(); + for (i = 0; i < len; ++i) { + v = ja.opt(i); + b.append(toString(v, (tagName == null) ? "array" : tagName)); + } + return b.toString(); + } else { + s = (o == null) ? "null" : escape(o.toString()); + return (tagName == null) ? "\"" + s + "\"" : + (s.length() == 0) ? "<" + tagName + "/>" : + "<" + tagName + ">" + s + ""; + } + } +} \ No newline at end of file diff --git a/tests/harness/lib/yuitest/java/src/org/json/XMLTokener.java b/tests/harness/lib/yuitest/java/src/org/json/XMLTokener.java new file mode 100644 index 000000000..0f36084a5 --- /dev/null +++ b/tests/harness/lib/yuitest/java/src/org/json/XMLTokener.java @@ -0,0 +1,365 @@ +package org.json; + +/* +Copyright (c) 2002 JSON.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +The Software shall be used for Good, not Evil. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +/** + * The XMLTokener extends the JSONTokener to provide additional methods + * for the parsing of XML texts. + * @author JSON.org + * @version 2008-09-18 + */ +public class XMLTokener extends JSONTokener { + + + /** The table of entity values. It initially contains Character values for + * amp, apos, gt, lt, quot. + */ + public static final java.util.HashMap entity; + + static { + entity = new java.util.HashMap(8); + entity.put("amp", XML.AMP); + entity.put("apos", XML.APOS); + entity.put("gt", XML.GT); + entity.put("lt", XML.LT); + entity.put("quot", XML.QUOT); + } + + /** + * Construct an XMLTokener from a string. + * @param s A source string. + */ + public XMLTokener(String s) { + super(s); + } + + /** + * Get the text in the CDATA block. + * @return The string up to the ]]>. + * @throws JSONException If the ]]> is not found. + */ + public String nextCDATA() throws JSONException { + char c; + int i; + StringBuffer sb = new StringBuffer(); + for (;;) { + c = next(); + if (c == 0) { + throw syntaxError("Unclosed CDATA"); + } + sb.append(c); + i = sb.length() - 3; + if (i >= 0 && sb.charAt(i) == ']' && + sb.charAt(i + 1) == ']' && sb.charAt(i + 2) == '>') { + sb.setLength(i); + return sb.toString(); + } + } + } + + + /** + * Get the next XML outer token, trimming whitespace. There are two kinds + * of tokens: the '<' character which begins a markup tag, and the content + * text between markup tags. + * + * @return A string, or a '<' Character, or null if there is no more + * source text. + * @throws JSONException + */ + public Object nextContent() throws JSONException { + char c; + StringBuffer sb; + do { + c = next(); + } while (Character.isWhitespace(c)); + if (c == 0) { + return null; + } + if (c == '<') { + return XML.LT; + } + sb = new StringBuffer(); + for (;;) { + if (c == '<' || c == 0) { + back(); + return sb.toString().trim(); + } + if (c == '&') { + sb.append(nextEntity(c)); + } else { + sb.append(c); + } + c = next(); + } + } + + + /** + * Return the next entity. These entities are translated to Characters: + * & ' > < ". + * @param a An ampersand character. + * @return A Character or an entity String if the entity is not recognized. + * @throws JSONException If missing ';' in XML entity. + */ + public Object nextEntity(char a) throws JSONException { + StringBuffer sb = new StringBuffer(); + for (;;) { + char c = next(); + if (Character.isLetterOrDigit(c) || c == '#') { + sb.append(Character.toLowerCase(c)); + } else if (c == ';') { + break; + } else { + throw syntaxError("Missing ';' in XML entity: &" + sb); + } + } + String s = sb.toString(); + Object e = entity.get(s); + return e != null ? e : a + s + ";"; + } + + + /** + * Returns the next XML meta token. This is used for skipping over + * and structures. + * @return Syntax characters (< > / = ! ?) are returned as + * Character, and strings and names are returned as Boolean. We don't care + * what the values actually are. + * @throws JSONException If a string is not properly closed or if the XML + * is badly structured. + */ + public Object nextMeta() throws JSONException { + char c; + char q; + do { + c = next(); + } while (Character.isWhitespace(c)); + switch (c) { + case 0: + throw syntaxError("Misshaped meta tag"); + case '<': + return XML.LT; + case '>': + return XML.GT; + case '/': + return XML.SLASH; + case '=': + return XML.EQ; + case '!': + return XML.BANG; + case '?': + return XML.QUEST; + case '"': + case '\'': + q = c; + for (;;) { + c = next(); + if (c == 0) { + throw syntaxError("Unterminated string"); + } + if (c == q) { + return Boolean.TRUE; + } + } + default: + for (;;) { + c = next(); + if (Character.isWhitespace(c)) { + return Boolean.TRUE; + } + switch (c) { + case 0: + case '<': + case '>': + case '/': + case '=': + case '!': + case '?': + case '"': + case '\'': + back(); + return Boolean.TRUE; + } + } + } + } + + + /** + * Get the next XML Token. These tokens are found inside of angle + * brackets. It may be one of these characters: / > = ! ? or it + * may be a string wrapped in single quotes or double quotes, or it may be a + * name. + * @return a String or a Character. + * @throws JSONException If the XML is not well formed. + */ + public Object nextToken() throws JSONException { + char c; + char q; + StringBuffer sb; + do { + c = next(); + } while (Character.isWhitespace(c)); + switch (c) { + case 0: + throw syntaxError("Misshaped element"); + case '<': + throw syntaxError("Misplaced '<'"); + case '>': + return XML.GT; + case '/': + return XML.SLASH; + case '=': + return XML.EQ; + case '!': + return XML.BANG; + case '?': + return XML.QUEST; + +// Quoted string + + case '"': + case '\'': + q = c; + sb = new StringBuffer(); + for (;;) { + c = next(); + if (c == 0) { + throw syntaxError("Unterminated string"); + } + if (c == q) { + return sb.toString(); + } + if (c == '&') { + sb.append(nextEntity(c)); + } else { + sb.append(c); + } + } + default: + +// Name + + sb = new StringBuffer(); + for (;;) { + sb.append(c); + c = next(); + if (Character.isWhitespace(c)) { + return sb.toString(); + } + switch (c) { + case 0: + return sb.toString(); + case '>': + case '/': + case '=': + case '!': + case '?': + case '[': + case ']': + back(); + return sb.toString(); + case '<': + case '"': + case '\'': + throw syntaxError("Bad character in a name"); + } + } + } + } + + + /** + * Skip characters until past the requested string. + * If it is not found, we are left at the end of the source with a result of false. + * @param to A string to skip past. + * @throws JSONException + */ + public boolean skipPast(String to) throws JSONException { + boolean b; + char c; + int i; + int j; + int offset = 0; + int n = to.length(); + char[] circle = new char[n]; + + /* + * First fill the circle buffer with as many characters as are in the + * to string. If we reach an early end, bail. + */ + + for (i = 0; i < n; i += 1) { + c = next(); + if (c == 0) { + return false; + } + circle[i] = c; + } + /* + * We will loop, possibly for all of the remaining characters. + */ + for (;;) { + j = offset; + b = true; + /* + * Compare the circle buffer with the to string. + */ + for (i = 0; i < n; i += 1) { + if (circle[j] != to.charAt(i)) { + b = false; + break; + } + j += 1; + if (j >= n) { + j -= n; + } + } + /* + * If we exit the loop with b intact, then victory is ours. + */ + if (b) { + return true; + } + /* + * Get the next character. If there isn't one, then defeat is ours. + */ + c = next(); + if (c == 0) { + return false; + } + /* + * Shove the character in the circle buffer and advance the + * circle offset. The offset is mod n. + */ + circle[offset] = c; + offset += 1; + if (offset >= n) { + offset -= n; + } + } + } +} diff --git a/tests/harness/lib/yuitest/java/tests/com/yahoo/platform/yuitest/config/TestConfigTest.java b/tests/harness/lib/yuitest/java/tests/com/yahoo/platform/yuitest/config/TestConfigTest.java new file mode 100644 index 000000000..35f9b3f39 --- /dev/null +++ b/tests/harness/lib/yuitest/java/tests/com/yahoo/platform/yuitest/config/TestConfigTest.java @@ -0,0 +1,101 @@ +/* + * YUI Test + * Author: Nicholas C. Zakas + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ + +package com.yahoo.platform.yuitest.config; + +import java.io.IOException; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import static org.junit.Assert.*; +import org.xml.sax.SAXException; + +/** + * + * @author Nicholas C. Zakas + */ +public class TestConfigTest { + + private TestConfig config; + + public TestConfigTest() { + } + + @Before + public void setUp() throws SAXException, IOException { + config = new TestConfig(); + config.load(TestConfigTest.class.getResourceAsStream("tests.xml")); + } + + @After + public void tearDown() { + config = null; + } + + + @Test + public void testGroupCount(){ + assertEquals(2, config.getGroups().length); + } + + @Test + public void testGroupSettings(){ + + TestPageGroup group = config.getGroups()[0]; + + //make sure the group's properties are correct + assertEquals(3, group.getVersion()); + assertEquals("http://www.example.com/tests/", group.getBase()); + assertEquals(10000, group.getTimeout()); + } + + @Test + public void testGroupPage(){ + TestPage page = config.getGroups()[0].getTestPages()[0]; + + //make sure each test page's properties are correct + assertEquals("test1", page.getPath()); + assertEquals("http://www.example.com/tests/test1", page.getAbsolutePath()); + assertEquals(3, page.getVersion()); + assertEquals(10000, page.getTimeout()); + } + + @Test + public void testGroupPageWithTimeoutOverride(){ + TestPage page = config.getGroups()[0].getTestPages()[1]; + + //make sure each test page's properties are correct + assertEquals("test2/more", page.getPath()); + assertEquals("http://www.example.com/tests/test2/more", page.getAbsolutePath()); + assertEquals(3, page.getVersion()); + assertEquals(50000, page.getTimeout()); + } + + @Test + public void testGroupPageWithVersionOverride(){ + TestPage page = config.getGroups()[0].getTestPages()[2]; + //make sure each test page's properties are correct + assertEquals("test3/more?a=b", page.getPath()); + assertEquals("http://www.example.com/tests/test3/more?a=b", page.getAbsolutePath()); + assertEquals(2, page.getVersion()); + assertEquals(10000, page.getTimeout()); + } + + @Test + public void testGroupPageWitQueryStringArguments(){ + TestPage page = config.getGroups()[0].getTestPages()[3]; + + //make sure each test page's properties are correct + assertEquals("test4/more?a=b&c=d", page.getPath()); + assertEquals("http://www.example.com/tests/test4/more?a=b&c=d", page.getAbsolutePath()); + assertEquals(3, page.getVersion()); + assertEquals(10000, page.getTimeout()); + } + + +} \ No newline at end of file diff --git a/tests/harness/lib/yuitest/java/tests/com/yahoo/platform/yuitest/config/tests.xml b/tests/harness/lib/yuitest/java/tests/com/yahoo/platform/yuitest/config/tests.xml new file mode 100644 index 000000000..940a86869 --- /dev/null +++ b/tests/harness/lib/yuitest/java/tests/com/yahoo/platform/yuitest/config/tests.xml @@ -0,0 +1,14 @@ + + + + test1 + test2/more + test3/more?a=b + test4/more?a=b&c=d + + + http://www.example.com:8080/tests/test1 + http://www.example.net/tests/test2 + http://www.example.cc/tests/test3 + + \ No newline at end of file diff --git a/tests/harness/lib/yuitest/java/tests/com/yahoo/platform/yuitest/coverage/results/DirectoryReportTest.java b/tests/harness/lib/yuitest/java/tests/com/yahoo/platform/yuitest/coverage/results/DirectoryReportTest.java new file mode 100644 index 000000000..4123dea74 --- /dev/null +++ b/tests/harness/lib/yuitest/java/tests/com/yahoo/platform/yuitest/coverage/results/DirectoryReportTest.java @@ -0,0 +1,138 @@ +/* + * YUI Test + * Author: Nicholas C. Zakas + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ + +package com.yahoo.platform.yuitest.coverage.results; + +import java.io.InputStreamReader; +import java.io.Reader; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + * @author nzakas + */ +public class DirectoryReportTest { + + private DirectoryCoverageReport report; + + public DirectoryReportTest() { + } + + @BeforeClass + public static void setUpClass() throws Exception { + } + + @AfterClass + public static void tearDownClass() throws Exception { + } + + @Before + public void setUp() throws Exception { + Reader in = new InputStreamReader(SummaryReportTest.class.getResourceAsStream("coverage.json")); + SummaryCoverageReport summaryReport = new SummaryCoverageReport(in); + summaryReport.merge(new SummaryCoverageReport(new InputStreamReader(SummaryReportTest.class.getResourceAsStream("coverage2.json")))); + report = summaryReport.getDirectoryReports()[0]; + } + + @After + public void tearDown() { + } + + /** + * Test of getDirectory method, of class DirectoryCoverageReport. + */ + @Test + public void testGetDirectory() { + assertEquals("build", report.getDirectory()); + } + /** + * Test of getFileReports method, of class DirectoryCoverageReport. + */ + @Test + public void testGetFileReports() { + FileCoverageReport[] result = report.getFileReports(); + assertEquals(2, result.length); + assertEquals("build/cookie.js", result[0].getFilename()); + } + + /** + * Test of getCoveredLineCount method, of class DirectoryCoverageReport. + */ + @Test + public void testGetCoveredLineCount() throws Exception { + FileCoverageReport[] fileReports = report.getFileReports(); + assertEquals(fileReports[0].getCoveredLineCount() + fileReports[1].getCoveredLineCount(), report.getCoveredLineCount()); + } + + /** + * Test of getCalledLineCount method, of class DirectoryCoverageReport. + */ + @Test + public void testGetCalledLineCount() throws Exception { + FileCoverageReport[] fileReports = report.getFileReports(); + assertEquals(fileReports[0].getCalledLineCount() + fileReports[1].getCalledLineCount(), report.getCalledLineCount()); + } + + /** + * Test of getCalledLinePercentage method, of class DirectoryCoverageReport. + */ + @Test + public void testGetCalledLinePercentage() throws Exception { + FileCoverageReport[] fileReports = report.getFileReports(); + assertEquals((double)(fileReports[0].getCalledLinePercentage() + fileReports[1].getCalledLinePercentage())/2, report.getCalledLinePercentage(), 0.2); + } + + /** + * Test of getCalledLinePercentageName method, of class DirectoryCoverageReport. + */ + @Test + public void testGetCalledLinePercentageName() throws Exception { + assertEquals("high", report.getCalledLinePercentageName()); + } + + /** + * Test of getCoveredFunctionCount method, of class DirectoryCoverageReport. + */ + @Test + public void testGetCoveredFunctionCount() throws Exception { + FileCoverageReport[] fileReports = report.getFileReports(); + assertEquals(fileReports[0].getCoveredFunctionCount() + fileReports[1].getCoveredFunctionCount(), report.getCoveredFunctionCount()); + } + + /** + * Test of getCalledFunctionCount method, of class DirectoryCoverageReport. + */ + @Test + public void testGetCalledFunctionCount() throws Exception { + FileCoverageReport[] fileReports = report.getFileReports(); + assertEquals(fileReports[0].getCalledFunctionCount() + fileReports[1].getCalledFunctionCount(), report.getCalledFunctionCount()); + } + + /** + * Test of getCalledFunctionPercentage method, of class DirectoryCoverageReport. + */ + @Test + public void testGetCalledFunctionPercentage() throws Exception { + FileCoverageReport[] fileReports = report.getFileReports(); + assertEquals((double)(fileReports[0].getCalledFunctionPercentage() + fileReports[1].getCalledFunctionPercentage())/2, report.getCalledLinePercentage(), 1.0); + } + + /** + * Test of getCalledFunctionPercentageName method, of class DirectoryCoverageReport. + */ + @Test + public void testGetCalledFunctionPercentageName() throws Exception { + assertEquals("med", report.getCalledFunctionPercentageName()); + } + +} \ No newline at end of file diff --git a/tests/harness/lib/yuitest/java/tests/com/yahoo/platform/yuitest/coverage/results/FileReportTest.java b/tests/harness/lib/yuitest/java/tests/com/yahoo/platform/yuitest/coverage/results/FileReportTest.java new file mode 100644 index 000000000..f64ba129b --- /dev/null +++ b/tests/harness/lib/yuitest/java/tests/com/yahoo/platform/yuitest/coverage/results/FileReportTest.java @@ -0,0 +1,277 @@ +/* + * YUI Test + * Author: Nicholas C. Zakas + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ + +package com.yahoo.platform.yuitest.coverage.results; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + * @author nzakas + */ +public class FileReportTest { + + private FileCoverageReport report; + + + public FileReportTest() { + } + + @BeforeClass + public static void setUpClass() throws Exception { + } + + @AfterClass + public static void tearDownClass() throws Exception { + } + + @Before + public void setUp() throws IOException, JSONException { + Reader in = new InputStreamReader(SummaryReportTest.class.getResourceAsStream("coverage.json")); + SummaryCoverageReport summaryReport = new SummaryCoverageReport(in); + report = summaryReport.getFileReport(0); + } + + @After + public void tearDown() { + report = null; + } + + /** + * Test of getFilename method, of class FileCoverageReport. + */ + @Test + public void testGetFilename() { + String expResult = "build/cookie.js"; + String result = report.getFilename(); + assertEquals(expResult, result); + } + + /** + * Test of getFilename method, of class FileCoverageReport. + */ + @Test + public void testGetFileParent() { + String expResult = "build"; + String result = report.getFileParent(); + assertEquals(expResult, result); + } + + /** + * Test of getAbsolutePath method, of class FileCoverageReport. + */ + @Test + public void testGetAbsolutePath() { + String expResult = "C:\\Documents and Settings\\nzakas\\My Documents\\Projects\\yui\\yuitest\\java\\tests\\cookie.js"; + String result = report.getAbsolutePath(); + assertEquals(expResult, result); + } + + /** + * Test of getCoveredLineCount method, of class FileCoverageReport. + */ + @Test + public void testGetTotalLineCount() throws Exception { + assertEquals(127, report.getCoveredLineCount()); + } + + /** + * Test of getCalledLineCount method, of class FileCoverageReport. + */ + @Test + public void testGetCalledLineCount() throws Exception { + assertEquals(109, report.getCalledLineCount()); + } + + /** + * Test of getCalledLinePercentage method, of class FileCoverageReport. + */ + @Test + public void testGetCalledLinePercentage() throws Exception { + assertEquals(85.83, report.getCalledLinePercentage(), 0.05); + } + + /** + * Test of getCoveredFunctionCount method, of class FileCoverageReport. + */ + @Test + public void testGetTotalFunctionCount() throws Exception { + assertEquals(14, report.getCoveredFunctionCount()); + } + + /** + * Test of getCalledFunctionCount method, of class FileCoverageReport. + */ + @Test + public void testGetCalledFunctionCount() throws Exception { + assertEquals(13, report.getCalledFunctionCount()); + } + + /** + * Test of getCalledFunctionPercentage method, of class FileCoverageReport. + */ + @Test + public void testGetCalledFunctionPercentage() throws Exception { + assertEquals(92.86, report.getCalledFunctionPercentage(), 0.05); + } + + /** + * Test of getLine method, of class FileCoverageReport. + */ + @Test + public void testGetUncoveredLine() throws Exception { + FileLine result = report.getLine(1); + assertEquals(-1, result.getCallCount()); + assertFalse(result.isCovered()); + assertFalse(result.isCalled()); + assertEquals("/**", result.getText()); + assertEquals(1, result.getLineNumber()); + } + + /** + * Test of getLine method, of class FileCoverageReport. + */ + @Test + public void testGetCoveredandCalledLine() throws Exception { + FileLine result = report.getLine(6); + assertEquals(1, result.getCallCount()); + assertTrue(result.isCovered()); + assertTrue(result.isCalled()); + assertEquals("YAHOO.namespace(\"util\");", result.getText()); + assertEquals(6, result.getLineNumber()); + + } + + /** + * Test of getLine method, of class FileCoverageReport. + */ + @Test + public void testGetCoveredandNotCalledLine() throws Exception { + FileLine result = report.getLine(76); + assertEquals(0, result.getCallCount()); + assertTrue(result.isCovered()); + assertFalse(result.isCalled()); + assertEquals(" throw new TypeError(\"Cookie._createCookieHashString(): Argument must be an object.\");", result.getText()); + assertEquals(76, result.getLineNumber()); + + } + + /** + * Test of getLines method, of class FileCoverageReport. + */ + @Test + public void testGetLines() throws Exception { + FileLine[] result = report.getLines(); + assertEquals(476, result.length); + + FileLine line = report.getLine(1); + assertEquals(line.getCallCount(), result[0].getCallCount()); + assertEquals(line.isCovered(), result[0].isCovered()); + assertEquals(line.isCalled(), result[0].isCalled()); + assertEquals(line.getText(), result[0].getText()); + assertEquals(line.getLineNumber(), result[0].getLineNumber()); + } + + /** + * Test of getFunctions method, of class FileCoverageReport. + */ + @Test + public void testGetFunctions() throws Exception { + FileFunction[] result = report.getFunctions(); + assertEquals(14, result.length); + + FileFunction function = result[1]; + assertEquals("_createCookieHashString", function.getName()); + assertEquals(70, function.getLineNumber()); + assertEquals(33, function.getCallCount()); + } + + + /** + * Test of getLineCallCount method, of class FileCoverageReport. + */ + @Test + public void testGetLineCallCount() throws Exception { + assertEquals(-1, report.getLineCallCount(1)); + assertEquals(1, report.getLineCallCount(6)); + assertEquals(2, report.getLineCallCount(371)); + } + + /** + * Test of getFunctionCallCount method, of class FileCoverageReport. + */ + @Test + public void testGetFunctionCallCount() throws Exception { + int expResult = 72; + int result = report.getFunctionCallCount("_createCookieString:30"); + assertEquals(expResult, result); + } + + /** + * Test of getFunctionNames method, of class FileCoverageReport. + */ + @Test + public void testGetFunctionNames() throws Exception { + + String[] result = report.getFunctionNames(); + assertEquals(14, result.length); + assertEquals("_parseCookieString:123", result[0]); + } + + /** + * Test of getReportName method, of class FileCoverageReport. + */ + @Test + public void testGetReportName() { + assertEquals("build_cookie.js", report.getReportName()); + } + + /** + * Test of merge() method. + */ + @Test + public void testMerge() throws JSONException { + + //get clone of JSONObject + JSONObject clone = new JSONObject(report.toJSONObject().toString()); + clone.put("calledLines", 110); + clone.getJSONObject("lines").put("207", 1); + + FileCoverageReport newReport = new FileCoverageReport("cookie.js", clone); + report.merge(newReport); + + assertEquals(110, report.getCalledLineCount()); + assertEquals(1, report.getLineCallCount(207)); + assertEquals(18, report.getLineCallCount(222)); + } + + /** + * Test of merge() method. + */ + @Test(expected=IllegalArgumentException.class) + public void testInvalidMerge() throws JSONException { + + //get clone of JSONObject + JSONObject clone = new JSONObject(report.toJSONObject().toString()); + clone.put("path", "cookie2.js"); + FileCoverageReport newReport = new FileCoverageReport("cookie2.js", clone); + report.merge(newReport); + + } + +} \ No newline at end of file diff --git a/tests/harness/lib/yuitest/java/tests/com/yahoo/platform/yuitest/coverage/results/SummaryReportTest.java b/tests/harness/lib/yuitest/java/tests/com/yahoo/platform/yuitest/coverage/results/SummaryReportTest.java new file mode 100644 index 000000000..92ccec455 --- /dev/null +++ b/tests/harness/lib/yuitest/java/tests/com/yahoo/platform/yuitest/coverage/results/SummaryReportTest.java @@ -0,0 +1,100 @@ +/* + * YUI Test + * Author: Nicholas C. Zakas + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ + +package com.yahoo.platform.yuitest.coverage.results; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.json.*; +import static org.junit.Assert.*; + +/** + * + * @author Nicholas C. Zakas + */ +public class SummaryReportTest { + + public SummaryReportTest() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + @Test + public void testSummaryReportLoad() throws IOException, JSONException { + Reader in = new InputStreamReader(SummaryReportTest.class.getResourceAsStream("coverage.json")); + SummaryCoverageReport report = new SummaryCoverageReport(in); + + //make sure the number of file reports is correct + FileCoverageReport[] fileReports = report.getFileReports(); + String[] filenames = report.getFilenames(); + assertEquals(1, fileReports.length); + assertEquals("build/cookie.js", filenames[0]); + + //check directory reports + assertEquals(1, report.getDirectoryReports().length); + assertEquals("build", report.getDirectoryReports()[0].getDirectory()); + + //check file reports on directory reports + assertEquals(1, report.getDirectoryReports()[0].getFileReports().length); + assertEquals("build/cookie.js", report.getDirectoryReports()[0].getFileReports()[0].getFilename()); + + } + + @Test + public void testSummaryReportMergeNewData() throws IOException, JSONException { + Reader in = new InputStreamReader(SummaryReportTest.class.getResourceAsStream("coverage.json")); + SummaryCoverageReport report1 = new SummaryCoverageReport(in); + + //another coverage report with a different file + in = new InputStreamReader(SummaryReportTest.class.getResourceAsStream("coverage2.json")); + SummaryCoverageReport report2 = new SummaryCoverageReport(in); + + //merge into the first + report1.merge(report2); + + //make sure the number of file reports is correct + FileCoverageReport[] fileReports = report1.getFileReports(); + String[] filenames = report1.getFilenames(); + assertEquals(2, fileReports.length); + assertEquals("build/cookie.js", filenames[0]); + assertEquals("build/profiler.js", filenames[1]); + } + + @Test + public void testSummaryReportMergeExistingData() throws IOException, JSONException { + Reader in = new InputStreamReader(SummaryReportTest.class.getResourceAsStream("coverage.json")); + SummaryCoverageReport report1 = new SummaryCoverageReport(in); + + //another coverage report with the same file, some different results + in = new InputStreamReader(SummaryReportTest.class.getResourceAsStream("coverage3.json")); + SummaryCoverageReport report2 = new SummaryCoverageReport(in); + + //merge into the first + report1.merge(report2); + + //make sure the number of file reports is correct + FileCoverageReport[] fileReports = report1.getFileReports(); + String[] filenames = report1.getFilenames(); + assertEquals(1, fileReports.length); + assertEquals("build/cookie.js", filenames[0]); + assertEquals(32, fileReports[0].getFunctionCallCount("setSub:418")); + assertEquals(31, fileReports[0].getFunctionCallCount("setSubs:457")); + assertEquals(91, fileReports[0].getLineCallCount(111)); + } + +} \ No newline at end of file diff --git a/tests/harness/lib/yuitest/java/tests/com/yahoo/platform/yuitest/coverage/results/coverage.json b/tests/harness/lib/yuitest/java/tests/com/yahoo/platform/yuitest/coverage/results/coverage.json new file mode 100644 index 000000000..318d65fd4 --- /dev/null +++ b/tests/harness/lib/yuitest/java/tests/com/yahoo/platform/yuitest/coverage/results/coverage.json @@ -0,0 +1,155 @@ +{ + "build/cookie.js": { + "lines": { + "6": 1, + "13": 1, + "33": 72, + "37": 72, + "39": 64, + "40": 13, + "44": 64, + "45": 12, + "49": 64, + "50": 12, + "54": 64, + "55": 12, + "59": 72, + "73": 33, + "75": 33, + "76": 0, + "79": 33, + "81": 33, + "82": 111, + "83": 111, + "87": 33, + "100": 41, + "104": 41, + "105": 40, + "106": 223, + "107": 223, + "111": 41, + "125": 72, + "127": 72, + "129": 68, + "132": 68, + "137": 68, + "140": 801, + "141": 801, + "142": 799, + "143": 799, + "144": 799, + "150": 2, + "151": 2, + "153": 801, + "158": 72, + "175": 0, + "176": 0, + "179": 0, + "181": 0, + "200": 17, + "203": 17, + "204": 4, + "205": 4, + "206": 13, + "207": 0, + "209": 13, + "212": 17, + "214": 17, + "215": 5, + "218": 12, + "219": 3, + "222": 9, + "223": 6, + "225": 3, + "244": 24, + "247": 19, + "249": 18, + "250": 5, + "253": 13, + "254": 2, + "257": 11, + "258": 8, + "260": 3, + "263": 1, + "278": 46, + "281": 46, + "282": 5, + "285": 41, + "286": 41, + "287": 38, + "289": 3, + "307": 6, + "308": 5, + "312": 1, + "317": 1, + "335": 13, + "337": 13, + "340": 13, + "341": 5, + "345": 8, + "346": 5, + "350": 3, + "353": 3, + "354": 1, + "356": 1, + "359": 1, + "362": 0, + "363": 0, + "364": 0, + "368": 0, + "371": 2, + "389": 22, + "391": 22, + "393": 22, + "394": 0, + "397": 22, + "398": 0, + "401": 22, + "402": 22, + "403": 22, + "420": 16, + "422": 16, + "423": 0, + "426": 16, + "427": 0, + "430": 16, + "431": 0, + "434": 16, + "436": 16, + "437": 0, + "440": 16, + "442": 16, + "459": 31, + "461": 31, + "462": 0, + "465": 31, + "466": 0, + "469": 31, + "470": 31, + "471": 31, + "476": 1 + }, + "functions": { + "_createCookieString:30": 72, + "_createCookieHashString:70": 33, + "_parseCookieHash:98": 41, + "(anonymous 5):129": 556, + "_parseCookieString:123": 72, + "exists:173": 0, + "get:198": 17, + "getSub:242": 24, + "getSubs:276": 46, + "remove:304": 6, + "removeSub:333": 13, + "set:387": 22, + "setSub:418": 16, + "setSubs:457": 31 + }, + "coveredLines": 127, + "calledLines": 109, + "coveredFunctions": 14, + "calledFunctions": 13, + "code": ["/**", " * Utilities for cookie management", " * @namespace YAHOO.util", " * @module cookie", " */", "YAHOO.namespace(\"util\");", "", "/**", " * Cookie utility.", " * @class Cookie", " * @static", " */", "YAHOO.util.Cookie = {", " ", " //-------------------------------------------------------------------------", " // Private Methods", " //-------------------------------------------------------------------------", " ", " /**", " * Creates a cookie string that can be assigned into document.cookie.", " * @param {String} name The name of the cookie.", " * @param {String} value The value of the cookie.", " * @param {Boolean} encodeValue True to encode the value, false to leave as-is.", " * @param {Object} options (Optional) Options for the cookie.", " * @return {String} The formatted cookie string.", " * @method _createCookieString", " * @private", " * @static", " */", " _createCookieString : function (name /*:String*/, value /*:Variant*/, encodeValue /*:Boolean*/, options /*:Object*/) /*:String*/ {", " ", " //shortcut", " var lang = YAHOO.lang,", " text = encodeURIComponent(name) + \"=\" + (encodeValue ? encodeURIComponent(value) : value);", " ", " ", " if (lang.isObject(options)){", " //expiration date", " if (options.expires instanceof Date){", " text += \"; expires=\" + options.expires.toUTCString();", " }", " ", " //path", " if (lang.isString(options.path) && options.path !== \"\"){", " text += \"; path=\" + options.path;", " }", " ", " //domain", " if (lang.isString(options.domain) && options.domain !== \"\"){", " text += \"; domain=\" + options.domain;", " }", " ", " //secure", " if (options.secure === true){", " text += \"; secure\";", " }", " }", " ", " return text;", " },", " ", " /**", " * Formats a cookie value for an object containing multiple values.", " * @param {Object} hash An object of key-value pairs to create a string for.", " * @return {String} A string suitable for use as a cookie value.", " * @method _createCookieHashString", " * @private", " * @static", " */", " _createCookieHashString : function (hash /*:Object*/) /*:String*/ {", " ", " //shortcuts", " var lang = YAHOO.lang;", " ", " if (!lang.isObject(hash)){", " throw new TypeError(\"Cookie._createCookieHashString(): Argument must be an object.\");", " }", " ", " var text /*:Array*/ = [];", " ", " for (var key in hash){", " if (lang.hasOwnProperty(hash, key) && !lang.isFunction(hash[key]) && !lang.isUndefined(hash[key])){", " text.push(encodeURIComponent(key) + \"=\" + encodeURIComponent(String(hash[key])));", " }", " }", " ", " return text.join(\"&\");", " },", " ", " /**", " * Parses a cookie hash string into an object.", " * @param {String} text The cookie hash string to parse. The string should already be URL-decoded.", " * @return {Object} An object containing entries for each cookie value.", " * @method _parseCookieHash", " * @private", " * @static", " */", " _parseCookieHash : function (text /*:String*/) /*:Object*/ {", " ", " var hashParts /*:Array*/ = text.split(\"&\"),", " hashPart /*:Array*/ = null,", " hash /*:Object*/ = {};", " ", " if (text.length > 0){", " for (var i=0, len=hashParts.length; i < len; i++){", " hashPart = hashParts[i].split(\"=\");", " hash[decodeURIComponent(hashPart[0])] = decodeURIComponent(hashPart[1]);", " }", " }", " ", " return hash;", " },", " ", " /**", " * Parses a cookie string into an object representing all accessible cookies.", " * @param {String} text The cookie string to parse.", " * @param {Boolean} decode (Optional) Indicates if the cookie values should be decoded or not. Default is true.", " * @return {Object} An object containing entries for each accessible cookie.", " * @method _parseCookieString", " * @private", " * @static", " */", " _parseCookieString : function (text /*:String*/, decode /*:Boolean*/) /*:Object*/ {", " ", " var cookies /*:Object*/ = {};", " ", " if (YAHOO.lang.isString(text) && text.length > 0) {", " ", " var decodeValue = (decode === false ? function(s){return s;} : decodeURIComponent);", " ", " //if (/[^=]+=[^=;]?(?:; [^=]+=[^=]?)?/.test(text)){", " var cookieParts /*:Array*/ = text.split(/;\\s/g),", " cookieName /*:String*/ = null,", " cookieValue /*:String*/ = null,", " cookieNameValue /*:Array*/ = null;", " ", " for (var i=0, len=cookieParts.length; i < len; i++){", " ", " //check for normally-formatted cookie (name-value)", " cookieNameValue = cookieParts[i].match(/([^=]+)=/i);", " if (cookieNameValue instanceof Array){", " try {", " cookieName = decodeURIComponent(cookieNameValue[1]);", " cookieValue = decodeValue(cookieParts[i].substring(cookieNameValue[1].length+1));", " } catch (ex){", " //ignore the entire cookie - encoding is likely invalid", " }", " } else {", " //means the cookie does not have an \"=\", so treat it as a boolean flag", " cookieName = decodeURIComponent(cookieParts[i]);", " cookieValue = \"\";", " }", " cookies[cookieName] = cookieValue;", " }", " //}", " }", " ", " return cookies;", " },", " ", " //-------------------------------------------------------------------------", " // Public Methods", " //-------------------------------------------------------------------------", " ", " /**", " * Determines if the cookie with the given name exists. This is useful for", " * Boolean cookies (those that do not follow the name=value convention).", " * @param {String} name The name of the cookie to check.", " * @return {Boolean} True if the cookie exists, false if not.", " * @method exists", " * @static", " */", " exists: function(name) {", "", " if (!YAHOO.lang.isString(name) || name === \"\"){", " throw new TypeError(\"Cookie.exists(): Cookie name must be a non-empty string.\");", " }", "", " var cookies /*:Object*/ = this._parseCookieString(document.cookie, true);", " ", " return cookies.hasOwnProperty(name);", " },", " ", " /**", " * Returns the cookie value for the given name.", " * @param {String} name The name of the cookie to retrieve.", " * @param {Object|Function} options (Optional) An object containing one or more", " * cookie options: raw (true/false) and converter (a function).", " * The converter function is run on the value before returning it. The", " * function is not used if the cookie doesn't exist. The function can be", " * passed instead of the options object for backwards compatibility.", " * @return {Variant} If no converter is specified, returns a string or null if", " * the cookie doesn't exist. If the converter is specified, returns the value", " * returned from the converter or null if the cookie doesn't exist.", " * @method get", " * @static", " */", " get : function (name /*:String*/, options /*:Variant*/) /*:Variant*/{", " ", " var lang = YAHOO.lang,", " converter;", " ", " if (lang.isFunction(options)) {", " converter = options;", " options = {};", " } else if (lang.isObject(options)) {", " converter = options.converter;", " } else {", " options = {};", " }", " ", " var cookies /*:Object*/ = this._parseCookieString(document.cookie, !options.raw);", " ", " if (!lang.isString(name) || name === \"\"){", " throw new TypeError(\"Cookie.get(): Cookie name must be a non-empty string.\");", " }", " ", " if (lang.isUndefined(cookies[name])) {", " return null;", " }", " ", " if (!lang.isFunction(converter)){", " return cookies[name];", " } else {", " return converter(cookies[name]);", " }", " },", " ", " /**", " * Returns the value of a subcookie.", " * @param {String} name The name of the cookie to retrieve.", " * @param {String} subName The name of the subcookie to retrieve.", " * @param {Function} converter (Optional) A function to run on the value before returning", " * it. The function is not used if the cookie doesn't exist.", " * @return {Variant} If the cookie doesn't exist, null is returned. If the subcookie", " * doesn't exist, null if also returned. If no converter is specified and the", " * subcookie exists, a string is returned. If a converter is specified and the", " * subcookie exists, the value returned from the converter is returned.", " * @method getSub", " * @static", " */", " getSub : function (name, subName, converter) {", " ", " var lang = YAHOO.lang,", " hash = this.getSubs(name);", " ", " if (hash !== null) {", " ", " if (!lang.isString(subName) || subName === \"\"){", " throw new TypeError(\"Cookie.getSub(): Subcookie name must be a non-empty string.\");", " }", " ", " if (lang.isUndefined(hash[subName])){", " return null;", " }", " ", " if (!lang.isFunction(converter)){", " return hash[subName];", " } else {", " return converter(hash[subName]);", " }", " } else {", " return null;", " }", " ", " },", " ", " /**", " * Returns an object containing name-value pairs stored in the cookie with the given name.", " * @param {String} name The name of the cookie to retrieve.", " * @return {Object} An object of name-value pairs if the cookie with the given name", " * exists, null if it does not.", " * @method getSubs", " * @static", " */", " getSubs : function (name /*:String*/) /*:Object*/ {", " ", " var isString = YAHOO.lang.isString;", " ", " //check cookie name", " if (!isString(name) || name === \"\"){", " throw new TypeError(\"Cookie.getSubs(): Cookie name must be a non-empty string.\");", " }", " ", " var cookies = this._parseCookieString(document.cookie, false);", " if (isString(cookies[name])){", " return this._parseCookieHash(cookies[name]);", " }", " return null;", " },", " ", " /**", " * Removes a cookie from the machine by setting its expiration date to", " * sometime in the past.", " * @param {String} name The name of the cookie to remove.", " * @param {Object} options (Optional) An object containing one or more", " * cookie options: path (a string), domain (a string),", " * and secure (true/false). The expires option will be overwritten", " * by the method.", " * @return {String} The created cookie string.", " * @method remove", " * @static", " */", " remove : function (name /*:String*/, options /*:Object*/) /*:String*/ {", " ", " //check cookie name", " if (!YAHOO.lang.isString(name) || name === \"\"){", " throw new TypeError(\"Cookie.remove(): Cookie name must be a non-empty string.\");", " }", " ", " //set options - clone options so the original isn't affected", " options = YAHOO.lang.merge(options || {}, {", " expires: new Date(0)", " });", " ", " //set cookie", " return this.set(name, \"\", options);", " },", " ", " /**", " * Removes a subcookie with a given name. Removing the last subcookie", " * won't remove the entire cookie unless options.removeIfEmpty is true.", " * @param {String} name The name of the cookie in which the subcookie exists.", " * @param {String} subName The name of the subcookie to remove.", " * @param {Object} options (Optional) An object containing one or more", " * cookie options: path (a string), domain (a string), expires (a Date object),", " * removeIfEmpty (true/false), and secure (true/false). This must be the same", " * settings as the original subcookie.", " * @return {String} The created cookie string.", " * @method removeSub", " * @static", " */", " removeSub : function(name /*:String*/, subName /*:String*/, options /*:Object*/) /*:String*/ {", " ", " var lang = YAHOO.lang;", " ", " options = options || {};", " ", " //check cookie name", " if (!lang.isString(name) || name === \"\"){", " throw new TypeError(\"Cookie.removeSub(): Cookie name must be a non-empty string.\");", " }", " ", " //check subcookie name", " if (!lang.isString(subName) || subName === \"\"){", " throw new TypeError(\"Cookie.removeSub(): Subcookie name must be a non-empty string.\");", " }", " ", " //get all subcookies for this cookie", " var subs = this.getSubs(name);", " ", " //delete the indicated subcookie", " if (lang.isObject(subs) && lang.hasOwnProperty(subs, subName)){", " delete subs[subName];", "", " if (!options.removeIfEmpty) {", " //reset the cookie", "", " return this.setSubs(name, subs, options);", " } else {", " //reset the cookie if there are subcookies left, else remove", " for (var key in subs){", " if (lang.hasOwnProperty(subs, key) && !lang.isFunction(subs[key]) && !lang.isUndefined(subs[key])){", " return this.setSubs(name, subs, options);", " }", " }", " ", " return this.remove(name, options);", " }", " } else {", " return \"\";", " }", " ", " },", " ", " /**", " * Sets a cookie with a given name and value.", " * @param {String} name The name of the cookie to set.", " * @param {Variant} value The value to set for the cookie.", " * @param {Object} options (Optional) An object containing one or more", " * cookie options: path (a string), domain (a string), expires (a Date object),", " * raw (true/false), and secure (true/false).", " * @return {String} The created cookie string.", " * @method set", " * @static", " */", " set : function (name /*:String*/, value /*:Variant*/, options /*:Object*/) /*:String*/ {", " ", " var lang = YAHOO.lang;", " ", " options = options || {};", " ", " if (!lang.isString(name)){", " throw new TypeError(\"Cookie.set(): Cookie name must be a string.\");", " }", " ", " if (lang.isUndefined(value)){", " throw new TypeError(\"Cookie.set(): Value cannot be undefined.\");", " }", " ", " var text /*:String*/ = this._createCookieString(name, value, !options.raw, options);", " document.cookie = text;", " return text;", " },", " ", " /**", " * Sets a sub cookie with a given name to a particular value.", " * @param {String} name The name of the cookie to set.", " * @param {String} subName The name of the subcookie to set.", " * @param {Variant} value The value to set.", " * @param {Object} options (Optional) An object containing one or more", " * cookie options: path (a string), domain (a string), expires (a Date object),", " * and secure (true/false).", " * @return {String} The created cookie string.", " * @method setSub", " * @static", " */", " setSub : function (name /*:String*/, subName /*:String*/, value /*:Variant*/, options /*:Object*/) /*:String*/ {", " ", " var lang = YAHOO.lang;", " ", " if (!lang.isString(name) || name === \"\"){", " throw new TypeError(\"Cookie.setSub(): Cookie name must be a non-empty string.\");", " }", " ", " if (!lang.isString(subName) || subName === \"\"){", " throw new TypeError(\"Cookie.setSub(): Subcookie name must be a non-empty string.\");", " }", " ", " if (lang.isUndefined(value)){", " throw new TypeError(\"Cookie.setSub(): Subcookie value cannot be undefined.\");", " }", " ", " var hash /*:Object*/ = this.getSubs(name);", " ", " if (!lang.isObject(hash)){", " hash = {};", " }", " ", " hash[subName] = value;", " ", " return this.setSubs(name, hash, options);", " ", " },", " ", " /**", " * Sets a cookie with a given name to contain a hash of name-value pairs.", " * @param {String} name The name of the cookie to set.", " * @param {Object} value An object containing name-value pairs.", " * @param {Object} options (Optional) An object containing one or more", " * cookie options: path (a string), domain (a string), expires (a Date object),", " * and secure (true/false).", " * @return {String} The created cookie string.", " * @method setSubs", " * @static", " */", " setSubs : function (name /*:String*/, value /*:Object*/, options /*:Object*/) /*:String*/ {", " ", " var lang = YAHOO.lang;", " ", " if (!lang.isString(name)){", " throw new TypeError(\"Cookie.setSubs(): Cookie name must be a string.\");", " }", " ", " if (!lang.isObject(value)){", " throw new TypeError(\"Cookie.setSubs(): Cookie value must be an object.\");", " }", " ", " var text /*:String*/ = this._createCookieString(name, this._createCookieHashString(value), false, options);", " document.cookie = text;", " return text;", " }", "", "};", "", "YAHOO.register(\"cookie\", YAHOO.util.Cookie, {version: \"@VERSION@\", build: \"@BUILD@\"});"], + "path": "C:\\Documents and Settings\\nzakas\\My Documents\\Projects\\yui\\yuitest\\java\\tests\\cookie.js" + } +} \ No newline at end of file diff --git a/tests/harness/lib/yuitest/java/tests/com/yahoo/platform/yuitest/coverage/results/coverage2.json b/tests/harness/lib/yuitest/java/tests/com/yahoo/platform/yuitest/coverage/results/coverage2.json new file mode 100644 index 000000000..348f66032 --- /dev/null +++ b/tests/harness/lib/yuitest/java/tests/com/yahoo/platform/yuitest/coverage/results/coverage2.json @@ -0,0 +1,148 @@ +{ + "build/profiler.js": { + "lines": { + "1": 1, + "13": 1, + "31": 1, + "32": 22, + "39": 22, + "54": 1, + "57": 39, + "60": 39, + "61": 0, + "65": 39, + "66": 39, + "69": 39, + "70": 28, + "71": 28, + "72": 28, + "74": 11, + "75": 11, + "76": 11, + "90": 1, + "105": 12, + "106": 0, + "107": 0, + "109": 12, + "110": 12, + "122": 13, + "136": 19, + "138": 35, + "142": 35, + "144": 35, + "149": 19, + "152": 19, + "153": 19, + "156": 19, + "157": 19, + "160": 19, + "163": 19, + "178": 1, + "181": 1, + "182": 1, + "183": 1, + "184": 1, + "199": 5, + "200": 0, + "204": 5, + "205": 3, + "209": 5, + "210": 3, + "217": 5, + "218": 4, + "219": 4, + "233": 4, + "236": 4, + "237": 4, + "238": 3, + "239": 1, + "240": 1, + "244": 4, + "245": 4, + "246": 4, + "265": 0, + "276": 1, + "290": 0, + "304": 0, + "316": 0, + "327": 10, + "339": 1, + "341": 1, + "342": 1, + "344": 1, + "345": 3, + "346": 3, + "350": 1, + "367": 4, + "390": 18, + "396": 18, + "397": 1, + "401": 18, + "402": 18, + "405": 18, + "408": 18, + "415": 18, + "416": 18, + "419": 18, + "420": 4, + "443": 5, + "446": 5, + "448": 5, + "449": 12, + "450": 12, + "451": 9, + "453": 0, + "454": 0, + "471": 4, + "472": 4, + "487": 21, + "490": 18, + "491": 4, + "495": 18, + "499": 18, + "500": 18, + "503": 18, + "506": 18, + "527": 5, + "528": 5, + "530": 5, + "531": 12, + "532": 12, + "533": 0, + "534": 0, + "538": 5 + }, + "functions": { + "createReport:31": 22, + "saveDataPoint:54": 39, + "clear:104": 12, + "getOriginal:121": 13, + "newMethod:136": 35, + "instrument:133": 19, + "pause:177": 1, + "start:198": 5, + "stop:232": 4, + "getAverage:264": 0, + "getCallCount:275": 1, + "getMax:289": 0, + "getMin:303": 0, + "getFunctionReport:315": 0, + "getReport:326": 10, + "(anonymous 16):339": 3, + "getFullReport:338": 1, + "registerConstructor:366": 4, + "registerFunction:387": 18, + "registerObject:440": 5, + "unregisterConstructor:468": 4, + "unregisterFunction:484": 21, + "unregisterObject:524": 5, + "(anonymous 1):1": 1 + }, + "coveredLines": 110, + "calledLines": 98, + "coveredFunctions": 24, + "calledFunctions": 20, + "path": "C:\\Documents and Settings\\nzakas\\My Documents\\Projects\\yui\\yuitest\\java\\build\\profiler.js", + "code": ["YUI.add('profiler', function(Y) {", "", " /**", " * The YUI JavaScript profiler.", " * @module profiler", " * @requires yui", " */", " ", " //-------------------------------------------------------------------------", " // Private Variables and Functions", " //-------------------------------------------------------------------------", " ", " var container = {}, //Container object on which to put the original unprofiled methods.", " report = {}, //Profiling information for functions", " stopwatches = {}, //Additional stopwatch information", " ", " WATCH_STARTED = 0,", " WATCH_STOPPED = 1,", " WATCH_PAUSED = 2, ", " ", " //shortcuts", " L = Y.Lang;", "", " /* (intentionally not documented)", " * Creates a report object with the given name.", " * @param {String} name The name to store for the report object.", " * @return {Void}", " * @method createReport", " * @private", " */", " function createReport(name){", " report[name] = {", " calls: 0,", " max: 0,", " min: 0,", " avg: 0,", " points: []", " };", " return report[name];", " }", " ", " /* (intentionally not documented)", " * Called when a method ends execution. Marks the start and end time of the ", " * method so it can calculate how long the function took to execute. Also ", " * updates min/max/avg calculations for the function.", " * @param {String} name The name of the function to mark as stopped.", " * @param {int} duration The number of milliseconds it took the function to", " * execute.", " * @return {Void}", " * @method saveDataPoint", " * @private", " * @static", " */", " function saveDataPoint(name, duration){", "", " //get the function data", " var functionData /*:Object*/ = report[name];", " ", " //just in case clear() was called", " if (!functionData){", " functionData = createReport(name);", " }", " ", " //increment the calls", " functionData.calls++;", " functionData.points.push(duration);", "", " //if it's already been called at least once, do more complex calculations", " if (functionData.calls > 1) {", " functionData.avg = ((functionData.avg*(functionData.calls-1))+duration)/functionData.calls;", " functionData.min = Math.min(functionData.min, duration);", " functionData.max = Math.max(functionData.max, duration);", " } else {", " functionData.avg = duration;", " functionData.min = duration;", " functionData.max = duration;", " } ", " ", " }", " ", " //-------------------------------------------------------------------------", " // Public Interface", " //-------------------------------------------------------------------------", " ", " /**", " * Profiles functions in JavaScript.", " * @class Profiler", " * @static", " */", " Y.Profiler = {", " ", " //-------------------------------------------------------------------------", " // Utility Methods", " //------------------------------------------------------------------------- ", " ", " /**", " * Removes all report data from the profiler.", " * @param {String} name (Optional) The name of the report to clear. If", " * omitted, then all report data is cleared.", " * @return {Void}", " * @method clear", " * @static", " */", " clear: function(name){", " if (L.isString(name)){", " delete report[name];", " delete stopwatches[name];", " } else {", " report = {};", " stopwatches = {};", " }", " },", "", " /**", " * Returns the uninstrumented version of a function/object.", " * @param {String} name The name of the function/object to retrieve.", " * @return {Function|Object} The uninstrumented version of a function/object.", " * @method getOriginal", " * @static", " */ ", " getOriginal: function(name){", " return container[name];", " },", " ", " /**", " * Instruments a method to have profiling calls.", " * @param {String} name The name of the report for the function.", " * @param {Function} method The function to instrument.", " * @return {Function} An instrumented version of the function.", " * @method instrument", " * @static", " */", " instrument: function(name, method){", " ", " //create instrumented version of function", " var newMethod = function () {", " ", " var start = new Date(),", " retval = method.apply(this, arguments),", " stop = new Date();", " ", " saveDataPoint(name, stop-start);", " ", " return retval; ", " ", " }; ", "", " //copy the function properties over", " Y.mix(newMethod, method);", " ", " //assign prototype and flag as being profiled", " newMethod.__yuiProfiled = true;", " newMethod.prototype = method.prototype;", " ", " //store original method", " container[name] = method;", " container[name].__yuiFuncName = name;", " ", " //create the report", " createReport(name);", "", " //return the new method", " return newMethod;", " }, ", " ", " //-------------------------------------------------------------------------", " // Stopwatch Methods", " //------------------------------------------------------------------------- ", " ", " /**", " * Pauses profiling information for a given name.", " * @param {String} name The name of the data point.", " * @return {Void}", " * @method pause", " * @static", " */ ", " pause: function(name){", " var now = new Date(),", " stopwatch = stopwatches[name];", " ", " if (stopwatch && stopwatch.state == WATCH_STARTED){", " stopwatch.total += (now - stopwatch.start);", " stopwatch.start = 0;", " stopwatch.state = WATCH_PAUSED;", " }", " ", " },", " ", " /**", " * Start profiling information for a given name. The name cannot be the name", " * of a registered function or object. This is used to start timing for a", " * particular block of code rather than instrumenting the entire function.", " * @param {String} name The name of the data point.", " * @return {Void}", " * @method start", " * @static", " */", " start: function(name){", " if(container[name]){", " throw new Error(\"Cannot use '\" + name + \"' for profiling through start(), name is already in use.\");", " } else {", " ", " //create report if necessary", " if (!report[name]){", " createReport(name);", " }", " ", " //create stopwatch object if necessary", " if (!stopwatches[name]){ ", " stopwatches[name] = {", " state: WATCH_STOPPED,", " start: 0,", " total: 0", " };", " }", " ", " if (stopwatches[name].state == WATCH_STOPPED){", " stopwatches[name].state = WATCH_STARTED;", " stopwatches[name].start = new Date(); ", " }", "", " }", " },", " ", " /**", " * Stops profiling information for a given name.", " * @param {String} name The name of the data point.", " * @return {Void}", " * @method stop", " * @static", " */", " stop: function(name){", " var now = new Date(),", " stopwatch = stopwatches[name];", " ", " if (stopwatch){", " if (stopwatch.state == WATCH_STARTED){", " saveDataPoint(name, stopwatch.total + (now - stopwatch.start)); ", " } else if (stopwatch.state == WATCH_PAUSED){", " saveDataPoint(name, stopwatch.total);", " }", " ", " //reset stopwatch information", " stopwatch.start = 0;", " stopwatch.total = 0;", " stopwatch.state = WATCH_STOPPED; ", " }", " },", " ", " //-------------------------------------------------------------------------", " // Reporting Methods", " //------------------------------------------------------------------------- ", " ", " /**", " * Returns the average amount of time (in milliseconds) that the function", " * with the given name takes to execute.", " * @param {String} name The name of the function whose data should be returned.", " * If an object type method, it should be 'constructor.prototype.methodName';", " * a normal object method would just be 'object.methodName'.", " * @return {float} The average time it takes the function to execute.", " * @method getAverage", " * @static", " */", " getAverage : function (name /*:String*/) /*:float*/ {", " return report[name].avg;", " },", " ", " /**", " * Returns the number of times that the given function has been called.", " * @param {String} name The name of the function whose data should be returned.", " * @return {int} The number of times the function was called.", " * @method getCallCount", " * @static", " */", " getCallCount : function (name /*:String*/) /*:int*/ {", " return report[name].calls; ", " },", " ", " /**", " * Returns the maximum amount of time (in milliseconds) that the function", " * with the given name takes to execute.", " * @param {String} name The name of the function whose data should be returned.", " * If an object type method, it should be 'constructor.prototype.methodName';", " * a normal object method would just be 'object.methodName'.", " * @return {float} The maximum time it takes the function to execute.", " * @method getMax", " * @static", " */", " getMax : function (name /*:String*/) /*:int*/ {", " return report[name].max;", " },", " ", " /**", " * Returns the minimum amount of time (in milliseconds) that the function", " * with the given name takes to execute.", " * @param {String} name The name of the function whose data should be returned.", " * If an object type method, it should be 'constructor.prototype.methodName';", " * a normal object method would just be 'object.methodName'.", " * @return {float} The minimum time it takes the function to execute.", " * @method getMin", " * @static", " */", " getMin : function (name /*:String*/) /*:int*/ {", " return report[name].min;", " },", " ", " /**", " * Returns an object containing profiling data for a single function.", " * The object has an entry for min, max, avg, calls, and points).", " * @return {Object} An object containing profile data for a given function.", " * @method getFunctionReport", " * @static", " * @deprecated Use getReport() instead.", " */", " getFunctionReport : function (name /*:String*/) /*:Object*/ {", " return report[name];", " },", " ", " /**", " * Returns an object containing profiling data for a single function.", " * The object has an entry for min, max, avg, calls, and points).", " * @return {Object} An object containing profile data for a given function.", " * @method getReport", " * @static", " */", " getReport : function (name /*:String*/) /*:Object*/ {", " return report[name];", " },", " ", " /**", " * Returns an object containing profiling data for all of the functions ", " * that were profiled. The object has an entry for each function and ", " * returns all information (min, max, average, calls, etc.) for each", " * function.", " * @return {Object} An object containing all profile data.", " * @static", " */", " getFullReport : function (filter /*:Function*/) /*:Object*/ {", " filter = filter || function(){return true;};", " ", " if (L.isFunction(filter)) {", " var fullReport = {};", " ", " for (var name in report){", " if (filter(report[name])){", " fullReport[name] = report[name]; ", " }", " }", " ", " return fullReport;", " }", " },", " ", " //-------------------------------------------------------------------------", " // Profiling Methods", " //------------------------------------------------------------------------- ", " ", " /**", " * Sets up a constructor for profiling, including all properties and methods on the prototype.", " * @param {string} name The fully-qualified name of the function including namespace information.", " * @param {Object} owner (Optional) The object that owns the function (namespace or containing object).", " * @return {Void}", " * @method registerConstructor", " * @static", " */", " registerConstructor : function (name /*:String*/, owner /*:Object*/) /*:Void*/ { ", " this.registerFunction(name, owner, true);", " },", " ", " /**", " * Sets up a function for profiling. It essentially overwrites the function with one", " * that has instrumentation data. This method also creates an entry for the function", " * in the profile report. The original function is stored on the container object.", " * @param {String} name The full name of the function including namespacing. This", " * is the name of the function that is stored in the report.", " * @param {Object} owner (Optional) The object that owns the function. If the function", " * isn't global then this argument is required. This could be the namespace that", " * the function belongs to or the object on which it's", " * a method.", " * @param {Boolean} registerPrototype (Optional) Indicates that the prototype should", " * also be instrumented. Setting to true has the same effect as calling", " * registerConstructor().", " * @return {Void}", " * @method registerFunction", " * @static", " */ ", " registerFunction : function(name /*:String*/, owner /*:Object*/, registerPrototype /*:Boolean*/) /*:Void*/{", " ", " //figure out the function name without namespacing", " var funcName = (name.indexOf(\".\") > -1 ? ", " name.substring(name.lastIndexOf(\".\")+1) : name),", " method,", " prototype;", " ", " //if owner isn't an object, try to find it from the name", " if (!L.isObject(owner)){", " owner = eval(name.substring(0, name.lastIndexOf(\".\")));", " }", " ", " //get the method and prototype", " method = owner[funcName];", " prototype = method.prototype;", " ", " //see if the method has already been registered", " if (L.isFunction(method) && !method.__yuiProfiled){", " ", " //replace the function with the profiling one", " owner[funcName] = this.instrument(name, method);", " ", " /*", " * Store original function information. We store the actual", " * function as well as the owner and the name used to identify", " * the function so it can be restored later.", " */", " container[name].__yuiOwner = owner;", " container[name].__yuiFuncName = funcName; //overwrite with less-specific name", " ", " //register prototype if necessary", " if (registerPrototype) { ", " this.registerObject(name + \".prototype\", prototype); ", " }", " ", " }", " ", " },", " ", " ", " /**", " * Sets up an object for profiling. It takes the object and looks for functions.", " * When a function is found, registerMethod() is called on it. If set to recrusive", " * mode, it will also setup objects found inside of this object for profiling, ", " * using the same methodology.", " * @param {String} name The name of the object to profile (shows up in report).", " * @param {Object} owner (Optional) The object represented by the name.", " * @param {Boolean} recurse (Optional) Determines if subobject methods are also profiled.", " * @return {Void}", " * @method registerObject", " * @static", " */", " registerObject : function (name /*:String*/, object /*:Object*/, recurse /*:Boolean*/) /*:Void*/{", " ", " //get the object", " object = (L.isObject(object) ? object : eval(name));", " ", " //save the object", " container[name] = object;", " ", " for (var prop in object) {", " if (typeof object[prop] == \"function\"){", " if (prop != \"constructor\" && prop != \"superclass\"){ //don't do constructor or superclass, it's recursive", " this.registerFunction(name + \".\" + prop, object);", " }", " } else if (typeof object[prop] == \"object\" && recurse){", " this.registerObject(name + \".\" + prop, object[prop], recurse);", " }", " }", " ", " }, ", " ", " /**", " * Removes a constructor function from profiling. Reverses the registerConstructor() method.", " * @param {String} name The full name of the function including namespacing. This", " * is the name of the function that is stored in the report.", " * @return {Void}", " * @method unregisterFunction", " * @static", " */ ", " unregisterConstructor : function(name /*:String*/) /*:Void*/{", " ", " //see if the method has been registered", " if (L.isFunction(container[name])){", " this.unregisterFunction(name, true);", " } ", " },", " ", " /**", " * Removes function from profiling. Reverses the registerFunction() method.", " * @param {String} name The full name of the function including namespacing. This", " * is the name of the function that is stored in the report.", " * @return {Void}", " * @method unregisterFunction", " * @static", " */ ", " unregisterFunction : function(name /*:String*/, unregisterPrototype /*:Boolean*/) /*:Void*/{", " ", " //see if the method has been registered", " if (L.isFunction(container[name])){", " ", " //check to see if you should unregister the prototype", " if (unregisterPrototype){", " this.unregisterObject(name + \".prototype\", container[name].prototype);", " }", " ", " //get original data", " var owner /*:Object*/ = container[name].__yuiOwner,", " funcName /*:String*/ = container[name].__yuiFuncName;", " ", " //delete extra information", " delete container[name].__yuiOwner;", " delete container[name].__yuiFuncName;", " ", " //replace instrumented function", " owner[funcName] = container[name];", " ", " //delete supporting information", " delete container[name]; ", " }", " ", " ", " },", " ", " /**", " * Unregisters an object for profiling. It takes the object and looks for functions.", " * When a function is found, unregisterMethod() is called on it. If set to recrusive", " * mode, it will also unregister objects found inside of this object, ", " * using the same methodology.", " * @param {String} name The name of the object to unregister.", " * @param {Boolean} recurse (Optional) Determines if subobject methods should also be", " * unregistered.", " * @return {Void}", " * @method unregisterObject", " * @static", " */", " unregisterObject : function (name /*:String*/, recurse /*:Boolean*/) /*:Void*/{", " ", " //get the object", " if (L.isObject(container[name])){ ", " var object = container[name]; ", " ", " for (var prop in object) {", " if (typeof object[prop] == \"function\"){", " this.unregisterFunction(name + \".\" + prop);", " } else if (typeof object[prop] == \"object\" && recurse){", " this.unregisterObject(name + \".\" + prop, recurse);", " }", " }", " ", " delete container[name];", " }", " ", " }", " ", " ", " };", "", "", "", "}, '@VERSION@' ,{requires:['oop']});"] + } +} \ No newline at end of file diff --git a/tests/harness/lib/yuitest/java/tests/com/yahoo/platform/yuitest/coverage/results/coverage3.json b/tests/harness/lib/yuitest/java/tests/com/yahoo/platform/yuitest/coverage/results/coverage3.json new file mode 100644 index 000000000..515d2abdb --- /dev/null +++ b/tests/harness/lib/yuitest/java/tests/com/yahoo/platform/yuitest/coverage/results/coverage3.json @@ -0,0 +1,155 @@ +{ + "build/cookie.js": { + "lines": { + "6": 1, + "13": 1, + "33": 72, + "37": 72, + "39": 64, + "40": 13, + "44": 64, + "45": 12, + "49": 64, + "50": 12, + "54": 64, + "55": 12, + "59": 72, + "73": 33, + "75": 33, + "76": 0, + "79": 33, + "81": 33, + "82": 111, + "83": 111, + "87": 33, + "100": 41, + "104": 41, + "105": 40, + "106": 223, + "107": 223, + "111": 50, + "125": 72, + "127": 72, + "129": 68, + "132": 68, + "137": 68, + "140": 801, + "141": 801, + "142": 799, + "143": 799, + "144": 799, + "150": 2, + "151": 2, + "153": 801, + "158": 72, + "175": 0, + "176": 0, + "179": 0, + "181": 0, + "200": 17, + "203": 17, + "204": 4, + "205": 4, + "206": 13, + "207": 0, + "209": 13, + "212": 17, + "214": 17, + "215": 5, + "218": 12, + "219": 3, + "222": 9, + "223": 6, + "225": 3, + "244": 24, + "247": 19, + "249": 18, + "250": 5, + "253": 13, + "254": 2, + "257": 11, + "258": 8, + "260": 3, + "263": 1, + "278": 46, + "281": 46, + "282": 5, + "285": 41, + "286": 41, + "287": 38, + "289": 3, + "307": 6, + "308": 5, + "312": 1, + "317": 1, + "335": 13, + "337": 13, + "340": 13, + "341": 5, + "345": 8, + "346": 5, + "350": 3, + "353": 3, + "354": 1, + "356": 1, + "359": 1, + "362": 0, + "363": 0, + "364": 0, + "368": 0, + "371": 2, + "389": 22, + "391": 22, + "393": 22, + "394": 0, + "397": 22, + "398": 0, + "401": 22, + "402": 22, + "403": 22, + "420": 16, + "422": 16, + "423": 0, + "426": 16, + "427": 0, + "430": 16, + "431": 0, + "434": 16, + "436": 16, + "437": 0, + "440": 16, + "442": 16, + "459": 31, + "461": 31, + "462": 0, + "465": 31, + "466": 0, + "469": 31, + "470": 31, + "471": 31, + "476": 1 + }, + "functions": { + "_createCookieString:30": 72, + "_createCookieHashString:70": 33, + "_parseCookieHash:98": 41, + "(anonymous 5):129": 556, + "_parseCookieString:123": 72, + "exists:173": 0, + "get:198": 17, + "getSub:242": 24, + "getSubs:276": 46, + "remove:304": 6, + "removeSub:333": 13, + "set:387": 22, + "setSub:418": 16, + "setSubs:457": 0 + }, + "coveredLines": 127, + "calledLines": 109, + "coveredFunctions": 14, + "calledFunctions": 13, + "code": ["/**", " * Utilities for cookie management", " * @namespace YAHOO.util", " * @module cookie", " */", "YAHOO.namespace(\"util\");", "", "/**", " * Cookie utility.", " * @class Cookie", " * @static", " */", "YAHOO.util.Cookie = {", " ", " //-------------------------------------------------------------------------", " // Private Methods", " //-------------------------------------------------------------------------", " ", " /**", " * Creates a cookie string that can be assigned into document.cookie.", " * @param {String} name The name of the cookie.", " * @param {String} value The value of the cookie.", " * @param {Boolean} encodeValue True to encode the value, false to leave as-is.", " * @param {Object} options (Optional) Options for the cookie.", " * @return {String} The formatted cookie string.", " * @method _createCookieString", " * @private", " * @static", " */", " _createCookieString : function (name /*:String*/, value /*:Variant*/, encodeValue /*:Boolean*/, options /*:Object*/) /*:String*/ {", " ", " //shortcut", " var lang = YAHOO.lang,", " text = encodeURIComponent(name) + \"=\" + (encodeValue ? encodeURIComponent(value) : value);", " ", " ", " if (lang.isObject(options)){", " //expiration date", " if (options.expires instanceof Date){", " text += \"; expires=\" + options.expires.toUTCString();", " }", " ", " //path", " if (lang.isString(options.path) && options.path !== \"\"){", " text += \"; path=\" + options.path;", " }", " ", " //domain", " if (lang.isString(options.domain) && options.domain !== \"\"){", " text += \"; domain=\" + options.domain;", " }", " ", " //secure", " if (options.secure === true){", " text += \"; secure\";", " }", " }", " ", " return text;", " },", " ", " /**", " * Formats a cookie value for an object containing multiple values.", " * @param {Object} hash An object of key-value pairs to create a string for.", " * @return {String} A string suitable for use as a cookie value.", " * @method _createCookieHashString", " * @private", " * @static", " */", " _createCookieHashString : function (hash /*:Object*/) /*:String*/ {", " ", " //shortcuts", " var lang = YAHOO.lang;", " ", " if (!lang.isObject(hash)){", " throw new TypeError(\"Cookie._createCookieHashString(): Argument must be an object.\");", " }", " ", " var text /*:Array*/ = [];", " ", " for (var key in hash){", " if (lang.hasOwnProperty(hash, key) && !lang.isFunction(hash[key]) && !lang.isUndefined(hash[key])){", " text.push(encodeURIComponent(key) + \"=\" + encodeURIComponent(String(hash[key])));", " }", " }", " ", " return text.join(\"&\");", " },", " ", " /**", " * Parses a cookie hash string into an object.", " * @param {String} text The cookie hash string to parse. The string should already be URL-decoded.", " * @return {Object} An object containing entries for each cookie value.", " * @method _parseCookieHash", " * @private", " * @static", " */", " _parseCookieHash : function (text /*:String*/) /*:Object*/ {", " ", " var hashParts /*:Array*/ = text.split(\"&\"),", " hashPart /*:Array*/ = null,", " hash /*:Object*/ = {};", " ", " if (text.length > 0){", " for (var i=0, len=hashParts.length; i < len; i++){", " hashPart = hashParts[i].split(\"=\");", " hash[decodeURIComponent(hashPart[0])] = decodeURIComponent(hashPart[1]);", " }", " }", " ", " return hash;", " },", " ", " /**", " * Parses a cookie string into an object representing all accessible cookies.", " * @param {String} text The cookie string to parse.", " * @param {Boolean} decode (Optional) Indicates if the cookie values should be decoded or not. Default is true.", " * @return {Object} An object containing entries for each accessible cookie.", " * @method _parseCookieString", " * @private", " * @static", " */", " _parseCookieString : function (text /*:String*/, decode /*:Boolean*/) /*:Object*/ {", " ", " var cookies /*:Object*/ = {};", " ", " if (YAHOO.lang.isString(text) && text.length > 0) {", " ", " var decodeValue = (decode === false ? function(s){return s;} : decodeURIComponent);", " ", " //if (/[^=]+=[^=;]?(?:; [^=]+=[^=]?)?/.test(text)){", " var cookieParts /*:Array*/ = text.split(/;\\s/g),", " cookieName /*:String*/ = null,", " cookieValue /*:String*/ = null,", " cookieNameValue /*:Array*/ = null;", " ", " for (var i=0, len=cookieParts.length; i < len; i++){", " ", " //check for normally-formatted cookie (name-value)", " cookieNameValue = cookieParts[i].match(/([^=]+)=/i);", " if (cookieNameValue instanceof Array){", " try {", " cookieName = decodeURIComponent(cookieNameValue[1]);", " cookieValue = decodeValue(cookieParts[i].substring(cookieNameValue[1].length+1));", " } catch (ex){", " //ignore the entire cookie - encoding is likely invalid", " }", " } else {", " //means the cookie does not have an \"=\", so treat it as a boolean flag", " cookieName = decodeURIComponent(cookieParts[i]);", " cookieValue = \"\";", " }", " cookies[cookieName] = cookieValue;", " }", " //}", " }", " ", " return cookies;", " },", " ", " //-------------------------------------------------------------------------", " // Public Methods", " //-------------------------------------------------------------------------", " ", " /**", " * Determines if the cookie with the given name exists. This is useful for", " * Boolean cookies (those that do not follow the name=value convention).", " * @param {String} name The name of the cookie to check.", " * @return {Boolean} True if the cookie exists, false if not.", " * @method exists", " * @static", " */", " exists: function(name) {", "", " if (!YAHOO.lang.isString(name) || name === \"\"){", " throw new TypeError(\"Cookie.exists(): Cookie name must be a non-empty string.\");", " }", "", " var cookies /*:Object*/ = this._parseCookieString(document.cookie, true);", " ", " return cookies.hasOwnProperty(name);", " },", " ", " /**", " * Returns the cookie value for the given name.", " * @param {String} name The name of the cookie to retrieve.", " * @param {Object|Function} options (Optional) An object containing one or more", " * cookie options: raw (true/false) and converter (a function).", " * The converter function is run on the value before returning it. The", " * function is not used if the cookie doesn't exist. The function can be", " * passed instead of the options object for backwards compatibility.", " * @return {Variant} If no converter is specified, returns a string or null if", " * the cookie doesn't exist. If the converter is specified, returns the value", " * returned from the converter or null if the cookie doesn't exist.", " * @method get", " * @static", " */", " get : function (name /*:String*/, options /*:Variant*/) /*:Variant*/{", " ", " var lang = YAHOO.lang,", " converter;", " ", " if (lang.isFunction(options)) {", " converter = options;", " options = {};", " } else if (lang.isObject(options)) {", " converter = options.converter;", " } else {", " options = {};", " }", " ", " var cookies /*:Object*/ = this._parseCookieString(document.cookie, !options.raw);", " ", " if (!lang.isString(name) || name === \"\"){", " throw new TypeError(\"Cookie.get(): Cookie name must be a non-empty string.\");", " }", " ", " if (lang.isUndefined(cookies[name])) {", " return null;", " }", " ", " if (!lang.isFunction(converter)){", " return cookies[name];", " } else {", " return converter(cookies[name]);", " }", " },", " ", " /**", " * Returns the value of a subcookie.", " * @param {String} name The name of the cookie to retrieve.", " * @param {String} subName The name of the subcookie to retrieve.", " * @param {Function} converter (Optional) A function to run on the value before returning", " * it. The function is not used if the cookie doesn't exist.", " * @return {Variant} If the cookie doesn't exist, null is returned. If the subcookie", " * doesn't exist, null if also returned. If no converter is specified and the", " * subcookie exists, a string is returned. If a converter is specified and the", " * subcookie exists, the value returned from the converter is returned.", " * @method getSub", " * @static", " */", " getSub : function (name, subName, converter) {", " ", " var lang = YAHOO.lang,", " hash = this.getSubs(name);", " ", " if (hash !== null) {", " ", " if (!lang.isString(subName) || subName === \"\"){", " throw new TypeError(\"Cookie.getSub(): Subcookie name must be a non-empty string.\");", " }", " ", " if (lang.isUndefined(hash[subName])){", " return null;", " }", " ", " if (!lang.isFunction(converter)){", " return hash[subName];", " } else {", " return converter(hash[subName]);", " }", " } else {", " return null;", " }", " ", " },", " ", " /**", " * Returns an object containing name-value pairs stored in the cookie with the given name.", " * @param {String} name The name of the cookie to retrieve.", " * @return {Object} An object of name-value pairs if the cookie with the given name", " * exists, null if it does not.", " * @method getSubs", " * @static", " */", " getSubs : function (name /*:String*/) /*:Object*/ {", " ", " var isString = YAHOO.lang.isString;", " ", " //check cookie name", " if (!isString(name) || name === \"\"){", " throw new TypeError(\"Cookie.getSubs(): Cookie name must be a non-empty string.\");", " }", " ", " var cookies = this._parseCookieString(document.cookie, false);", " if (isString(cookies[name])){", " return this._parseCookieHash(cookies[name]);", " }", " return null;", " },", " ", " /**", " * Removes a cookie from the machine by setting its expiration date to", " * sometime in the past.", " * @param {String} name The name of the cookie to remove.", " * @param {Object} options (Optional) An object containing one or more", " * cookie options: path (a string), domain (a string),", " * and secure (true/false). The expires option will be overwritten", " * by the method.", " * @return {String} The created cookie string.", " * @method remove", " * @static", " */", " remove : function (name /*:String*/, options /*:Object*/) /*:String*/ {", " ", " //check cookie name", " if (!YAHOO.lang.isString(name) || name === \"\"){", " throw new TypeError(\"Cookie.remove(): Cookie name must be a non-empty string.\");", " }", " ", " //set options - clone options so the original isn't affected", " options = YAHOO.lang.merge(options || {}, {", " expires: new Date(0)", " });", " ", " //set cookie", " return this.set(name, \"\", options);", " },", " ", " /**", " * Removes a subcookie with a given name. Removing the last subcookie", " * won't remove the entire cookie unless options.removeIfEmpty is true.", " * @param {String} name The name of the cookie in which the subcookie exists.", " * @param {String} subName The name of the subcookie to remove.", " * @param {Object} options (Optional) An object containing one or more", " * cookie options: path (a string), domain (a string), expires (a Date object),", " * removeIfEmpty (true/false), and secure (true/false). This must be the same", " * settings as the original subcookie.", " * @return {String} The created cookie string.", " * @method removeSub", " * @static", " */", " removeSub : function(name /*:String*/, subName /*:String*/, options /*:Object*/) /*:String*/ {", " ", " var lang = YAHOO.lang;", " ", " options = options || {};", " ", " //check cookie name", " if (!lang.isString(name) || name === \"\"){", " throw new TypeError(\"Cookie.removeSub(): Cookie name must be a non-empty string.\");", " }", " ", " //check subcookie name", " if (!lang.isString(subName) || subName === \"\"){", " throw new TypeError(\"Cookie.removeSub(): Subcookie name must be a non-empty string.\");", " }", " ", " //get all subcookies for this cookie", " var subs = this.getSubs(name);", " ", " //delete the indicated subcookie", " if (lang.isObject(subs) && lang.hasOwnProperty(subs, subName)){", " delete subs[subName];", "", " if (!options.removeIfEmpty) {", " //reset the cookie", "", " return this.setSubs(name, subs, options);", " } else {", " //reset the cookie if there are subcookies left, else remove", " for (var key in subs){", " if (lang.hasOwnProperty(subs, key) && !lang.isFunction(subs[key]) && !lang.isUndefined(subs[key])){", " return this.setSubs(name, subs, options);", " }", " }", " ", " return this.remove(name, options);", " }", " } else {", " return \"\";", " }", " ", " },", " ", " /**", " * Sets a cookie with a given name and value.", " * @param {String} name The name of the cookie to set.", " * @param {Variant} value The value to set for the cookie.", " * @param {Object} options (Optional) An object containing one or more", " * cookie options: path (a string), domain (a string), expires (a Date object),", " * raw (true/false), and secure (true/false).", " * @return {String} The created cookie string.", " * @method set", " * @static", " */", " set : function (name /*:String*/, value /*:Variant*/, options /*:Object*/) /*:String*/ {", " ", " var lang = YAHOO.lang;", " ", " options = options || {};", " ", " if (!lang.isString(name)){", " throw new TypeError(\"Cookie.set(): Cookie name must be a string.\");", " }", " ", " if (lang.isUndefined(value)){", " throw new TypeError(\"Cookie.set(): Value cannot be undefined.\");", " }", " ", " var text /*:String*/ = this._createCookieString(name, value, !options.raw, options);", " document.cookie = text;", " return text;", " },", " ", " /**", " * Sets a sub cookie with a given name to a particular value.", " * @param {String} name The name of the cookie to set.", " * @param {String} subName The name of the subcookie to set.", " * @param {Variant} value The value to set.", " * @param {Object} options (Optional) An object containing one or more", " * cookie options: path (a string), domain (a string), expires (a Date object),", " * and secure (true/false).", " * @return {String} The created cookie string.", " * @method setSub", " * @static", " */", " setSub : function (name /*:String*/, subName /*:String*/, value /*:Variant*/, options /*:Object*/) /*:String*/ {", " ", " var lang = YAHOO.lang;", " ", " if (!lang.isString(name) || name === \"\"){", " throw new TypeError(\"Cookie.setSub(): Cookie name must be a non-empty string.\");", " }", " ", " if (!lang.isString(subName) || subName === \"\"){", " throw new TypeError(\"Cookie.setSub(): Subcookie name must be a non-empty string.\");", " }", " ", " if (lang.isUndefined(value)){", " throw new TypeError(\"Cookie.setSub(): Subcookie value cannot be undefined.\");", " }", " ", " var hash /*:Object*/ = this.getSubs(name);", " ", " if (!lang.isObject(hash)){", " hash = {};", " }", " ", " hash[subName] = value;", " ", " return this.setSubs(name, hash, options);", " ", " },", " ", " /**", " * Sets a cookie with a given name to contain a hash of name-value pairs.", " * @param {String} name The name of the cookie to set.", " * @param {Object} value An object containing name-value pairs.", " * @param {Object} options (Optional) An object containing one or more", " * cookie options: path (a string), domain (a string), expires (a Date object),", " * and secure (true/false).", " * @return {String} The created cookie string.", " * @method setSubs", " * @static", " */", " setSubs : function (name /*:String*/, value /*:Object*/, options /*:Object*/) /*:String*/ {", " ", " var lang = YAHOO.lang;", " ", " if (!lang.isString(name)){", " throw new TypeError(\"Cookie.setSubs(): Cookie name must be a string.\");", " }", " ", " if (!lang.isObject(value)){", " throw new TypeError(\"Cookie.setSubs(): Cookie value must be an object.\");", " }", " ", " var text /*:String*/ = this._createCookieString(name, this._createCookieHashString(value), false, options);", " document.cookie = text;", " return text;", " }", "", "};", "", "YAHOO.register(\"cookie\", YAHOO.util.Cookie, {version: \"@VERSION@\", build: \"@BUILD@\"});"], + "path": "C:\\Documents and Settings\\nzakas\\My Documents\\Projects\\yui\\yuitest\\java\\tests\\cookie.js" + } +} \ No newline at end of file diff --git a/tests/harness/lib/yuitest/java/tests/com/yahoo/platform/yuitest/results/TestCaseTest.java b/tests/harness/lib/yuitest/java/tests/com/yahoo/platform/yuitest/results/TestCaseTest.java new file mode 100644 index 000000000..fe6c665a1 --- /dev/null +++ b/tests/harness/lib/yuitest/java/tests/com/yahoo/platform/yuitest/results/TestCaseTest.java @@ -0,0 +1,101 @@ +/* + * YUI Test + * Author: Nicholas C. Zakas + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ + +package com.yahoo.platform.yuitest.results; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + * @author nzakas + */ +public class TestCaseTest { + + private TestCase instance; + + public TestCaseTest() { + } + + @Before + public void setUp() { + instance = new TestCase("testcase1", 50, 5, 3, 1); + } + + @After + public void tearDown() { + instance = null; + } + + /** + * Test of addTest method, of class TestCase. + */ + @Test + public void testAddTest() { + com.yahoo.platform.yuitest.results.Test test = new com.yahoo.platform.yuitest.results.Test("test1", 50, 0, null); + instance.addTest(test); + + assertEquals(1, instance.getTests().length); + assertEquals(test, instance.getTests()[0]); + assertEquals(instance, test.getParent()); + } + + /** + * Test of getDuration method, of class TestCase. + */ + @Test + public void testGetDuration() { + assertEquals(50, instance.getDuration()); + } + + /** + * Test of getFailed method, of class TestCase. + */ + @Test + public void testGetFailed() { + assertEquals(3, instance.getFailed()); + } + + /** + * Test of getIgnored method, of class TestCase. + */ + @Test + public void testGetIgnored() { + assertEquals(1, instance.getIgnored()); + + } + + /** + * Test of getName method, of class TestCase. + */ + @Test + public void testGetName() { + assertEquals("testcase1", instance.getName()); + + } + + /** + * Test of getPassed method, of class TestCase. + */ + @Test + public void testGetPassed() { + assertEquals(5, instance.getPassed()); + } + + /** + * Test of getPath method, of class TestCase. + */ + @Test + public void testGetPath() { + assertEquals("", instance.getPath()); + assertEquals("testcase1", instance.getFullPath()); + } + +} \ No newline at end of file diff --git a/tests/harness/lib/yuitest/java/tests/com/yahoo/platform/yuitest/results/TestReportTest.java b/tests/harness/lib/yuitest/java/tests/com/yahoo/platform/yuitest/results/TestReportTest.java new file mode 100644 index 000000000..8f817345f --- /dev/null +++ b/tests/harness/lib/yuitest/java/tests/com/yahoo/platform/yuitest/results/TestReportTest.java @@ -0,0 +1,105 @@ +/* + * YUI Test + * Author: Nicholas C. Zakas + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ + +package com.yahoo.platform.yuitest.results; + +import java.io.File; +import java.io.InputStreamReader; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import static org.junit.Assert.*; +import org.xml.sax.InputSource; + +/** + * + * @author nzakas + */ +public class TestReportTest { + + public TestReportTest() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + private void checkReport(TestReport report){ + + TestSuite firstSuite = report.getTestSuites()[0]; + + TestCase firstTestCase = firstSuite.getTestCases()[0]; + TestCase secondTestCase = firstSuite.getTestCases()[1]; + + //duration="729" passed="48" failed="0" ignored="0" total="48 + assertEquals("All Mock Tests", report.getName()); + assertEquals(729, report.getDuration()); + assertEquals(48, report.getPassed()); + assertEquals(0, report.getFailed()); + assertEquals(0, report.getIgnored()); + + assertEquals(1, report.getTestSuites().length); + assertEquals(3, firstSuite.getTestCases().length); + + assertEquals(40, secondTestCase.getTests().length); + + assertEquals("Arguments Tests", secondTestCase.getName()); + assertEquals("Passing correct number of arguments should make the test pass", secondTestCase.getTests()[0].getName()); + } + + /** + * Test of load method, of class TestReport. + */ + @Test + public void testLoad_InputStream() throws Exception { + TestReport report = TestReport.load(TestReportTest.class.getResourceAsStream("results.xml")); + checkReport(report); + } + + /** + * Test of load method, of class TestReport. + */ + @Test + public void testLoad_Reader() throws Exception { + TestReport report = TestReport.load(new InputStreamReader(TestReportTest.class.getResourceAsStream("results.xml"))); + checkReport(report); + + } + + /** + * Test of load method, of class TestReport. + */ + @Test + public void testLoad_InputSource() throws Exception { + TestReport report = TestReport.load(new InputSource(TestReportTest.class.getResource("results.xml").getFile())); + checkReport(report); + + } + + /** + * Test of load method, of class TestReport. + */ + @Test + public void testLoad_InputSourceWithBrowser() throws Exception { + TestReport report = TestReport.load(new InputSource(TestReportTest.class.getResource("results.xml").getFile()), "Firefox"); + assertEquals("Firefox", report.getBrowser()); + } + + @Test + public void testGetPath() throws Exception { + TestReport report = new TestReport("testreport1", 100, 2, 3, 4); + report.setBrowser("firefox"); + assertEquals("YUITest\\firefox", report.getPath()); + assertEquals("YUITest\\firefox\\testreport1", report.getFullPath()); + } + +} \ No newline at end of file diff --git a/tests/harness/lib/yuitest/java/tests/com/yahoo/platform/yuitest/results/TestSuiteTest.java b/tests/harness/lib/yuitest/java/tests/com/yahoo/platform/yuitest/results/TestSuiteTest.java new file mode 100644 index 000000000..cd3772f3e --- /dev/null +++ b/tests/harness/lib/yuitest/java/tests/com/yahoo/platform/yuitest/results/TestSuiteTest.java @@ -0,0 +1,116 @@ +/* + * YUI Test + * Author: Nicholas C. Zakas + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ +package com.yahoo.platform.yuitest.results; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + * @author Nicholas C. Zakas + */ +public class TestSuiteTest { + + private TestSuite instance; + + public TestSuiteTest() { + } + + @Before + public void setUp() { + instance = new TestSuite("testsuite1", 50, 5, 3, 2); + } + + @After + public void tearDown() { + instance = null; + } + + /** + * Test of addTestSuite method, of class TestSuite. + */ + @Test + public void testAddTestSuite() { + TestSuite suite = new TestSuite("testsuite2", 30, 3, 2, 1); + instance.addTestSuite(suite); + assertEquals(1, instance.getTestSuites().length); + assertEquals(suite, instance.getTestSuites()[0]); + assertEquals("testsuite1", suite.getPath()); + assertEquals("testsuite1\\testsuite2", suite.getFullPath()); + assertEquals(instance, suite.getParent()); + + } + + /** + * Test of addTestCase method, of class TestSuite. + */ + @Test + public void testAddTestCase() { + TestCase testCase = new TestCase("testcase1", 30, 3, 2, 1); + instance.addTestCase(testCase); + + assertEquals(1, instance.getTestCases().length); + assertEquals(testCase, instance.getTestCases()[0]); + assertEquals("testsuite1", testCase.getPath()); + assertEquals("testsuite1\\testcase1", testCase.getFullPath()); + assertEquals(instance, testCase.getParent()); + } + + /** + * Test of getDuration method, of class TestSuite. + */ + @Test + public void testGetDuration() { + assertEquals(50, instance.getDuration()); + } + + /** + * Test of getFailed method, of class TestSuite. + */ + @Test + public void testGetFailed() { + assertEquals(3, instance.getFailed()); + } + + /** + * Test of getName method, of class TestSuite. + */ + @Test + public void testGetName() { + assertEquals("testsuite1", instance.getName()); + } + + /** + * Test of getPassed method, of class TestSuite. + */ + @Test + public void testGetPassed() { + assertEquals(5, instance.getPassed()); + } + + /** + * Test of getIgnored method, of class TestSuite. + */ + @Test + public void testGetIgnored() { + assertEquals(2, instance.getIgnored()); + } + + + /** + * Test of getPath method, of class TestSuite. + */ + @Test + public void testGetPath() { + assertEquals("", instance.getPath()); + assertEquals("testsuite1", instance.getFullPath()); + } + +} \ No newline at end of file diff --git a/tests/harness/lib/yuitest/java/tests/com/yahoo/platform/yuitest/results/TestTest.java b/tests/harness/lib/yuitest/java/tests/com/yahoo/platform/yuitest/results/TestTest.java new file mode 100644 index 000000000..f2b60bfdb --- /dev/null +++ b/tests/harness/lib/yuitest/java/tests/com/yahoo/platform/yuitest/results/TestTest.java @@ -0,0 +1,108 @@ +/* + * YUI Test + * Author: Nicholas C. Zakas + * Copyright (c) 2009, Yahoo! Inc. All rights reserved. + * Code licensed under the BSD License: + * http://developer.yahoo.net/yui/license.txt + */ +package com.yahoo.platform.yuitest.results; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + * @author nzakas + */ +public class TestTest { + + private com.yahoo.platform.yuitest.results.Test instance; + + public TestTest() { + } + + @Before + public void setUp() { + instance = new com.yahoo.platform.yuitest.results.Test("test1", 50, com.yahoo.platform.yuitest.results.Test.PASS, "Test passed."); + } + + @After + public void tearDown() { + instance = null; + } + + /** + * Test of getMessage method, of class Test. + */ + @Test + public void testGetMessage() { + assertEquals("Test passed.", instance.getMessage()); + } + + /** + * Test of getName method, of class Test. + */ + @Test + public void testGetName() { + assertEquals("test1", instance.getName()); + + } + + /** + * Test of getResult method, of class Test. + */ + @Test + public void testGetResult() { + assertEquals(com.yahoo.platform.yuitest.results.Test.PASS, instance.getResult()); + + } + + /** + * Test of getStackTrace method, of class Test. + */ + @Test + public void testGetStackTrace() { + //TODO + } + + /** + * Test of getDuration method, of class Test. + */ + @Test + public void testGetDuration() { + assertEquals(50, instance.getDuration()); + + } + + /** + * Test of setStackTrace method, of class Test. + */ + @Test + public void testSetStackTrace() { + instance.setStackTrace("Hello"); + assertEquals("Hello", instance.getStackTrace()); + } + + /** + * Test of getPath method, of class Test. + */ + @Test + public void testGetPath() { + assertEquals("", instance.getPath()); + assertEquals("test1", instance.getFullPath()); + } + + /** + * Test of getPath method, of class Test. + */ + @Test + public void testGetPathWithParent() { + TestCase testCase = new TestCase("testcase1", 20, 1, 2, 0); + testCase.addTest(instance); + assertEquals("testcase1", instance.getPath(".")); + assertEquals("testcase1.test1", instance.getFullPath(".")); + } + +} \ No newline at end of file diff --git a/tests/harness/lib/yuitest/java/tests/com/yahoo/platform/yuitest/results/results.xml b/tests/harness/lib/yuitest/java/tests/com/yahoo/platform/yuitest/results/results.xml new file mode 100644 index 000000000..175445401 --- /dev/null +++ b/tests/harness/lib/yuitest/java/tests/com/yahoo/platform/yuitest/results/results.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/harness/lib/yuitest/java/tests/coverage.json b/tests/harness/lib/yuitest/java/tests/coverage.json new file mode 100644 index 000000000..a82b30768 --- /dev/null +++ b/tests/harness/lib/yuitest/java/tests/coverage.json @@ -0,0 +1 @@ +{"cookie.js":{"lines":{"6":1,"13":1,"33":72,"37":72,"39":64,"40":13,"44":64,"45":12,"49":64,"50":12,"54":64,"55":12,"59":72,"73":33,"75":33,"76":0,"79":33,"81":33,"82":111,"83":111,"87":33,"100":41,"104":41,"105":40,"106":223,"107":223,"111":41,"125":72,"127":72,"129":68,"132":68,"137":68,"140":801,"141":801,"142":799,"143":799,"144":799,"150":2,"151":2,"153":801,"158":72,"175":0,"176":0,"179":0,"181":0,"200":17,"203":17,"204":4,"205":4,"206":13,"207":0,"209":13,"212":17,"214":17,"215":5,"218":12,"219":3,"222":9,"223":6,"225":3,"244":24,"247":19,"249":18,"250":5,"253":13,"254":2,"257":11,"258":8,"260":3,"263":1,"278":46,"281":46,"282":5,"285":41,"286":41,"287":38,"289":3,"307":6,"308":5,"312":1,"317":1,"335":13,"337":13,"340":13,"341":5,"345":8,"346":5,"350":3,"353":3,"354":1,"356":1,"359":1,"362":0,"363":0,"364":0,"368":0,"371":2,"389":22,"391":22,"393":22,"394":0,"397":22,"398":0,"401":22,"402":22,"403":22,"420":16,"422":16,"423":0,"426":16,"427":0,"430":16,"431":0,"434":16,"436":16,"437":0,"440":16,"442":16,"459":31,"461":31,"462":0,"465":31,"466":0,"469":31,"470":31,"471":31,"476":1},"functions":{"_createCookieString:30":72,"_createCookieHashString:70":33,"_parseCookieHash:98":41,"(anonymous 5):129":556,"_parseCookieString:123":72,"exists:173":0,"get:198":17,"getSub:242":24,"getSubs:276":46,"remove:304":6,"removeSub:333":13,"set:387":22,"setSub:418":16,"setSubs:457":31},"totalLines":127,"calledLines":109,"totalFunctions":14,"calledFunctions":13,"code":["/**"," * Utilities for cookie management"," * @namespace YAHOO.util"," * @module cookie"," */","YAHOO.namespace(\"util\");","","/**"," * Cookie utility."," * @class Cookie"," * @static"," */","YAHOO.util.Cookie = {"," "," //-------------------------------------------------------------------------"," // Private Methods"," //-------------------------------------------------------------------------"," "," /**"," * Creates a cookie string that can be assigned into document.cookie."," * @param {String} name The name of the cookie."," * @param {String} value The value of the cookie."," * @param {Boolean} encodeValue True to encode the value, false to leave as-is."," * @param {Object} options (Optional) Options for the cookie."," * @return {String} The formatted cookie string."," * @method _createCookieString"," * @private"," * @static"," */"," _createCookieString : function (name /*:String*/, value /*:Variant*/, encodeValue /*:Boolean*/, options /*:Object*/) /*:String*/ {"," "," //shortcut"," var lang = YAHOO.lang,"," text = encodeURIComponent(name) + \"=\" + (encodeValue ? encodeURIComponent(value) : value);"," "," "," if (lang.isObject(options)){"," //expiration date"," if (options.expires instanceof Date){"," text += \"; expires=\" + options.expires.toUTCString();"," }"," "," //path"," if (lang.isString(options.path) && options.path !== \"\"){"," text += \"; path=\" + options.path;"," }"," "," //domain"," if (lang.isString(options.domain) && options.domain !== \"\"){"," text += \"; domain=\" + options.domain;"," }"," "," //secure"," if (options.secure === true){"," text += \"; secure\";"," }"," }"," "," return text;"," },"," "," /**"," * Formats a cookie value for an object containing multiple values."," * @param {Object} hash An object of key-value pairs to create a string for."," * @return {String} A string suitable for use as a cookie value."," * @method _createCookieHashString"," * @private"," * @static"," */"," _createCookieHashString : function (hash /*:Object*/) /*:String*/ {"," "," //shortcuts"," var lang = YAHOO.lang;"," "," if (!lang.isObject(hash)){"," throw new TypeError(\"Cookie._createCookieHashString(): Argument must be an object.\");"," }"," "," var text /*:Array*/ = [];"," "," for (var key in hash){"," if (lang.hasOwnProperty(hash, key) && !lang.isFunction(hash[key]) && !lang.isUndefined(hash[key])){"," text.push(encodeURIComponent(key) + \"=\" + encodeURIComponent(String(hash[key])));"," }"," }"," "," return text.join(\"&\");"," },"," "," /**"," * Parses a cookie hash string into an object."," * @param {String} text The cookie hash string to parse. The string should already be URL-decoded."," * @return {Object} An object containing entries for each cookie value."," * @method _parseCookieHash"," * @private"," * @static"," */"," _parseCookieHash : function (text /*:String*/) /*:Object*/ {"," "," var hashParts /*:Array*/ = text.split(\"&\"),"," hashPart /*:Array*/ = null,"," hash /*:Object*/ = {};"," "," if (text.length > 0){"," for (var i=0, len=hashParts.length; i < len; i++){"," hashPart = hashParts[i].split(\"=\");"," hash[decodeURIComponent(hashPart[0])] = decodeURIComponent(hashPart[1]);"," }"," }"," "," return hash;"," },"," "," /**"," * Parses a cookie string into an object representing all accessible cookies."," * @param {String} text The cookie string to parse."," * @param {Boolean} decode (Optional) Indicates if the cookie values should be decoded or not. Default is true."," * @return {Object} An object containing entries for each accessible cookie."," * @method _parseCookieString"," * @private"," * @static"," */"," _parseCookieString : function (text /*:String*/, decode /*:Boolean*/) /*:Object*/ {"," "," var cookies /*:Object*/ = {};"," "," if (YAHOO.lang.isString(text) && text.length > 0) {"," "," var decodeValue = (decode === false ? function(s){return s;} : decodeURIComponent);"," "," //if (/[^=]+=[^=;]?(?:; [^=]+=[^=]?)?/.test(text)){"," var cookieParts /*:Array*/ = text.split(/;\\s/g),"," cookieName /*:String*/ = null,"," cookieValue /*:String*/ = null,"," cookieNameValue /*:Array*/ = null;"," "," for (var i=0, len=cookieParts.length; i < len; i++){"," "," //check for normally-formatted cookie (name-value)"," cookieNameValue = cookieParts[i].match(/([^=]+)=/i);"," if (cookieNameValue instanceof Array){"," try {"," cookieName = decodeURIComponent(cookieNameValue[1]);"," cookieValue = decodeValue(cookieParts[i].substring(cookieNameValue[1].length+1));"," } catch (ex){"," //ignore the entire cookie - encoding is likely invalid"," }"," } else {"," //means the cookie does not have an \"=\", so treat it as a boolean flag"," cookieName = decodeURIComponent(cookieParts[i]);"," cookieValue = \"\";"," }"," cookies[cookieName] = cookieValue;"," }"," //}"," }"," "," return cookies;"," },"," "," //-------------------------------------------------------------------------"," // Public Methods"," //-------------------------------------------------------------------------"," "," /**"," * Determines if the cookie with the given name exists. This is useful for"," * Boolean cookies (those that do not follow the name=value convention)."," * @param {String} name The name of the cookie to check."," * @return {Boolean} True if the cookie exists, false if not."," * @method exists"," * @static"," */"," exists: function(name) {",""," if (!YAHOO.lang.isString(name) || name === \"\"){"," throw new TypeError(\"Cookie.exists(): Cookie name must be a non-empty string.\");"," }",""," var cookies /*:Object*/ = this._parseCookieString(document.cookie, true);"," "," return cookies.hasOwnProperty(name);"," },"," "," /**"," * Returns the cookie value for the given name."," * @param {String} name The name of the cookie to retrieve."," * @param {Object|Function} options (Optional) An object containing one or more"," * cookie options: raw (true/false) and converter (a function)."," * The converter function is run on the value before returning it. The"," * function is not used if the cookie doesn't exist. The function can be"," * passed instead of the options object for backwards compatibility."," * @return {Variant} If no converter is specified, returns a string or null if"," * the cookie doesn't exist. If the converter is specified, returns the value"," * returned from the converter or null if the cookie doesn't exist."," * @method get"," * @static"," */"," get : function (name /*:String*/, options /*:Variant*/) /*:Variant*/{"," "," var lang = YAHOO.lang,"," converter;"," "," if (lang.isFunction(options)) {"," converter = options;"," options = {};"," } else if (lang.isObject(options)) {"," converter = options.converter;"," } else {"," options = {};"," }"," "," var cookies /*:Object*/ = this._parseCookieString(document.cookie, !options.raw);"," "," if (!lang.isString(name) || name === \"\"){"," throw new TypeError(\"Cookie.get(): Cookie name must be a non-empty string.\");"," }"," "," if (lang.isUndefined(cookies[name])) {"," return null;"," }"," "," if (!lang.isFunction(converter)){"," return cookies[name];"," } else {"," return converter(cookies[name]);"," }"," },"," "," /**"," * Returns the value of a subcookie."," * @param {String} name The name of the cookie to retrieve."," * @param {String} subName The name of the subcookie to retrieve."," * @param {Function} converter (Optional) A function to run on the value before returning"," * it. The function is not used if the cookie doesn't exist."," * @return {Variant} If the cookie doesn't exist, null is returned. If the subcookie"," * doesn't exist, null if also returned. If no converter is specified and the"," * subcookie exists, a string is returned. If a converter is specified and the"," * subcookie exists, the value returned from the converter is returned."," * @method getSub"," * @static"," */"," getSub : function (name, subName, converter) {"," "," var lang = YAHOO.lang,"," hash = this.getSubs(name);"," "," if (hash !== null) {"," "," if (!lang.isString(subName) || subName === \"\"){"," throw new TypeError(\"Cookie.getSub(): Subcookie name must be a non-empty string.\");"," }"," "," if (lang.isUndefined(hash[subName])){"," return null;"," }"," "," if (!lang.isFunction(converter)){"," return hash[subName];"," } else {"," return converter(hash[subName]);"," }"," } else {"," return null;"," }"," "," },"," "," /**"," * Returns an object containing name-value pairs stored in the cookie with the given name."," * @param {String} name The name of the cookie to retrieve."," * @return {Object} An object of name-value pairs if the cookie with the given name"," * exists, null if it does not."," * @method getSubs"," * @static"," */"," getSubs : function (name /*:String*/) /*:Object*/ {"," "," var isString = YAHOO.lang.isString;"," "," //check cookie name"," if (!isString(name) || name === \"\"){"," throw new TypeError(\"Cookie.getSubs(): Cookie name must be a non-empty string.\");"," }"," "," var cookies = this._parseCookieString(document.cookie, false);"," if (isString(cookies[name])){"," return this._parseCookieHash(cookies[name]);"," }"," return null;"," },"," "," /**"," * Removes a cookie from the machine by setting its expiration date to"," * sometime in the past."," * @param {String} name The name of the cookie to remove."," * @param {Object} options (Optional) An object containing one or more"," * cookie options: path (a string), domain (a string),"," * and secure (true/false). The expires option will be overwritten"," * by the method."," * @return {String} The created cookie string."," * @method remove"," * @static"," */"," remove : function (name /*:String*/, options /*:Object*/) /*:String*/ {"," "," //check cookie name"," if (!YAHOO.lang.isString(name) || name === \"\"){"," throw new TypeError(\"Cookie.remove(): Cookie name must be a non-empty string.\");"," }"," "," //set options - clone options so the original isn't affected"," options = YAHOO.lang.merge(options || {}, {"," expires: new Date(0)"," });"," "," //set cookie"," return this.set(name, \"\", options);"," },"," "," /**"," * Removes a subcookie with a given name. Removing the last subcookie"," * won't remove the entire cookie unless options.removeIfEmpty is true."," * @param {String} name The name of the cookie in which the subcookie exists."," * @param {String} subName The name of the subcookie to remove."," * @param {Object} options (Optional) An object containing one or more"," * cookie options: path (a string), domain (a string), expires (a Date object),"," * removeIfEmpty (true/false), and secure (true/false). This must be the same"," * settings as the original subcookie."," * @return {String} The created cookie string."," * @method removeSub"," * @static"," */"," removeSub : function(name /*:String*/, subName /*:String*/, options /*:Object*/) /*:String*/ {"," "," var lang = YAHOO.lang;"," "," options = options || {};"," "," //check cookie name"," if (!lang.isString(name) || name === \"\"){"," throw new TypeError(\"Cookie.removeSub(): Cookie name must be a non-empty string.\");"," }"," "," //check subcookie name"," if (!lang.isString(subName) || subName === \"\"){"," throw new TypeError(\"Cookie.removeSub(): Subcookie name must be a non-empty string.\");"," }"," "," //get all subcookies for this cookie"," var subs = this.getSubs(name);"," "," //delete the indicated subcookie"," if (lang.isObject(subs) && lang.hasOwnProperty(subs, subName)){"," delete subs[subName];",""," if (!options.removeIfEmpty) {"," //reset the cookie",""," return this.setSubs(name, subs, options);"," } else {"," //reset the cookie if there are subcookies left, else remove"," for (var key in subs){"," if (lang.hasOwnProperty(subs, key) && !lang.isFunction(subs[key]) && !lang.isUndefined(subs[key])){"," return this.setSubs(name, subs, options);"," }"," }"," "," return this.remove(name, options);"," }"," } else {"," return \"\";"," }"," "," },"," "," /**"," * Sets a cookie with a given name and value."," * @param {String} name The name of the cookie to set."," * @param {Variant} value The value to set for the cookie."," * @param {Object} options (Optional) An object containing one or more"," * cookie options: path (a string), domain (a string), expires (a Date object),"," * raw (true/false), and secure (true/false)."," * @return {String} The created cookie string."," * @method set"," * @static"," */"," set : function (name /*:String*/, value /*:Variant*/, options /*:Object*/) /*:String*/ {"," "," var lang = YAHOO.lang;"," "," options = options || {};"," "," if (!lang.isString(name)){"," throw new TypeError(\"Cookie.set(): Cookie name must be a string.\");"," }"," "," if (lang.isUndefined(value)){"," throw new TypeError(\"Cookie.set(): Value cannot be undefined.\");"," }"," "," var text /*:String*/ = this._createCookieString(name, value, !options.raw, options);"," document.cookie = text;"," return text;"," },"," "," /**"," * Sets a sub cookie with a given name to a particular value."," * @param {String} name The name of the cookie to set."," * @param {String} subName The name of the subcookie to set."," * @param {Variant} value The value to set."," * @param {Object} options (Optional) An object containing one or more"," * cookie options: path (a string), domain (a string), expires (a Date object),"," * and secure (true/false)."," * @return {String} The created cookie string."," * @method setSub"," * @static"," */"," setSub : function (name /*:String*/, subName /*:String*/, value /*:Variant*/, options /*:Object*/) /*:String*/ {"," "," var lang = YAHOO.lang;"," "," if (!lang.isString(name) || name === \"\"){"," throw new TypeError(\"Cookie.setSub(): Cookie name must be a non-empty string.\");"," }"," "," if (!lang.isString(subName) || subName === \"\"){"," throw new TypeError(\"Cookie.setSub(): Subcookie name must be a non-empty string.\");"," }"," "," if (lang.isUndefined(value)){"," throw new TypeError(\"Cookie.setSub(): Subcookie value cannot be undefined.\");"," }"," "," var hash /*:Object*/ = this.getSubs(name);"," "," if (!lang.isObject(hash)){"," hash = {};"," }"," "," hash[subName] = value;"," "," return this.setSubs(name, hash, options);"," "," },"," "," /**"," * Sets a cookie with a given name to contain a hash of name-value pairs."," * @param {String} name The name of the cookie to set."," * @param {Object} value An object containing name-value pairs."," * @param {Object} options (Optional) An object containing one or more"," * cookie options: path (a string), domain (a string), expires (a Date object),"," * and secure (true/false)."," * @return {String} The created cookie string."," * @method setSubs"," * @static"," */"," setSubs : function (name /*:String*/, value /*:Object*/, options /*:Object*/) /*:String*/ {"," "," var lang = YAHOO.lang;"," "," if (!lang.isString(name)){"," throw new TypeError(\"Cookie.setSubs(): Cookie name must be a string.\");"," }"," "," if (!lang.isObject(value)){"," throw new TypeError(\"Cookie.setSubs(): Cookie value must be an object.\");"," }"," "," var text /*:String*/ = this._createCookieString(name, this._createCookieHashString(value), false, options);"," document.cookie = text;"," return text;"," }","","};","","YAHOO.register(\"cookie\", YAHOO.util.Cookie, {version: \"@VERSION@\", build: \"@BUILD@\"});"],"path":"C:\\Documents and Settings\\nzakas\\My Documents\\Projects\\yui\\yuitest\\java\\tests\\cookie.js"}} \ No newline at end of file diff --git a/tests/run.js b/tests/run.js index 6008b72e4..5e4212a3c 100755 --- a/tests/run.js +++ b/tests/run.js @@ -20,12 +20,14 @@ program.command('test') .option('-d, --no-deploy', 'Don\'t deploy the apps') .option('-s, --no-selenium', 'Don\'t run arrow_selenium') .option('-a, --no-arrow', 'Don\'t run arrow_server') + .option('--debugApps', 'show STDOUT and STDERR from apps') .option('--logLevel ', 'Arrow logLevel') .option('--testName ', 'Arrow testName') + .option('--descriptor ', 'which descriptor to run. filename (or glob) relative to --path') .option('--group ', 'Arrow group') .option('--driver ', 'Arrow driver') .option('--browser ', 'Arrow browser') - .option('--path ', 'Path to find the tests') + .option('--path ', 'Path to find the tests. defaults to ./func or ./unit') .action(test); program.command('build') @@ -130,6 +132,7 @@ function runUnitTests (cmd, callback) { var commandArgs = [ cwd + "/../node_modules/yahoo-arrow/index.js", cmd.unitPath + "/**/*_descriptor.json", + "--coverage=true", "--report=true", "--reportFolder=" + arrowReportDir ]; @@ -172,9 +175,8 @@ function deploy (cmd, callback) { apps = appsConfig.applications; for (var i=0; i', - ' YUI.applyConfig({"fetchCSS":true,"combine":true,"base":"http://yui.yahooapis.com/3.6.0/build/","comboBase":"http://yui.yahooapis.com/combo?","root":"3.6.0/build/","seed":"/static/combo?yui-base.js\\\\u0026loader-base.js\\\\u0026loader-yui3.js\\\\u0026loader-app-base_klingon.js\\\\u0026loader.js","foo":"bar","lang":"klingon"});', + ' YUI.applyConfig({"fetchCSS":true,"combine":true,"base":"http://yui.yahooapis.com/3.6.0/build/","comboBase":"http://yui.yahooapis.com/combo?","root":"3.6.0/build/","groups":{"app":{}},"foo":"bar","lang":"klingon"});', ' YUI().use(\'mojito-client\', function(Y) {', - ' window.YMojito = { client: new Y.mojito.Client({"context":{"lang":"klingon","runtime":"client"},"binderMap":{"viewId1":{"needs":"a drink"},"viewId2":{"needs":"another drink"}},"routes":["routes"]}) };', + ' window.YMojito = { client: new Y.mojito.Client({"context":{"lang":"klingon","runtime":"client"},"binderMap":{"viewId1":{"needs":"a drink"},"viewId2":{"needs":"another drink"}},"appConfig":{"yui":{}},"routes":["routes"]}) };', ' });', '', '' @@ -220,7 +226,10 @@ YUI().use('mojito-deploy-addon', 'test', 'json-parse', function(Y) { return { ondemandBaseYuiModules:[] }; }, yui: { - getConfigShared: function() { return {}; } + getAppSeedFiles: function () { return ['/static/seed.js']; }, + getAppGroupConfig: function() { return {}; }, + getConfigShared: function() { return {}; }, + langs: { klingon: true } } }); @@ -297,7 +306,10 @@ YUI().use('mojito-deploy-addon', 'test', 'json-parse', function(Y) { return { ondemandBaseYuiModules:[] }; }, yui: { - getConfigShared: function() { return {}; } + getAppSeedFiles: function () { return ['/static/seed.js']; }, + getAppGroupConfig: function() { return {}; }, + getConfigShared: function() { return {}; }, + langs: { klingon: true } } }); @@ -358,7 +370,10 @@ YUI().use('mojito-deploy-addon', 'test', 'json-parse', function(Y) { return { ondemandBaseYuiModules:[] }; }, yui: { - getConfigShared: function() { return {}; } + getAppSeedFiles: function () { return ['/static/seed.js']; }, + getAppGroupConfig: function() { return {}; }, + getConfigShared: function() { return {}; }, + langs: { klingon: true } } }); @@ -419,7 +434,10 @@ YUI().use('mojito-deploy-addon', 'test', 'json-parse', function(Y) { return { ondemandBaseYuiModules:[] }; }, yui: { - getConfigShared: function() { return {}; } + getAppSeedFiles: function () { return ['/static/seed.js']; }, + getAppGroupConfig: function() { return {}; }, + getConfigShared: function() { return {}; }, + langs: { klingon: true } } }); diff --git a/tests/unit/lib/app/addons/ac/test-intl.common.js b/tests/unit/lib/app/addons/ac/test-intl.common.js index dc1cd8742..eb5cd968f 100644 --- a/tests/unit/lib/app/addons/ac/test-intl.common.js +++ b/tests/unit/lib/app/addons/ac/test-intl.common.js @@ -17,19 +17,22 @@ YUI().use('mojito-intl-addon', 'test', 'datatype-date', function(Y) { var command = {}, adapter = null, ac = { - type: 'acType', - context: { lang: 'foo' } + context: { lang: 'foo' }, + instance: { + controller: 'controller-yui-module-name', + langs: { foo: true } + } }; var mockYIntl = Mock(); Mock.expect(mockYIntl, { method: 'setLang', - args: [ac.type, 'foo'], + args: [ac.instance.controller, 'foo'], returns: 'true' }); Mock.expect(mockYIntl, { method: 'get', - args: [ac.type, 'key'], + args: [ac.instance.controller, 'key'], returns: 'translation' }); @@ -49,19 +52,22 @@ YUI().use('mojito-intl-addon', 'test', 'datatype-date', function(Y) { var command = {}, adapter = null, ac = { - type: 'acType', - context: { lang: 'foo' } + context: { lang: 'foo' }, + instance: { + controller: 'controller-yui-module-name', + langs: { foo: true } + } }; var mockYIntl = Mock(); Mock.expect(mockYIntl, { method: 'setLang', - args: [ac.type, 'foo'], + args: [ac.instance.controller, 'foo'], returns: 'true' }); Mock.expect(mockYIntl, { method: 'get', - args: [ac.type, 'key'], + args: [ac.instance.controller, 'key'], returns: 'translation {0} {1}' }); @@ -80,9 +86,26 @@ YUI().use('mojito-intl-addon', 'test', 'datatype-date', function(Y) { 'test formatDate() delegates to Y.DataType.Date.format': function() { var command = {}, adapter = null, - ac = { type: 'acType' }, + ac, argDate = new Date(); + ac = { + context: { lang: 'foo' }, + instance: { + controller: 'controller-yui-module-name', + langs: { foo: true } + } + }; + + var mockYIntl = Mock(); + Mock.expect(mockYIntl, { + method: 'setLang', + args: ['datatype-date-format', 'foo'], + returns: 'true' + }); + var yIntl = Y.Intl; + Y.Intl = mockYIntl; + var mockYDataTypeDate = Mock(); Mock.expect(mockYDataTypeDate, { method: 'format', @@ -96,11 +119,16 @@ YUI().use('mojito-intl-addon', 'test', 'datatype-date', function(Y) { Y.DataType.Date = mockYDataTypeDate; var addon = new Y.mojito.addons.ac.intl(command, adapter, ac); - var value = addon.formatDate(argDate); - - Y.DataType.Date = yDataTypeDate; + var value; + try { + value = addon.formatDate(argDate); + } finally { + Y.Intl = yIntl; + Y.DataType.Date = yDataTypeDate; + } Assert.areEqual('formattedDate', value, 'The return value of Y.DataType.Date.format() was not used'); + Mock.verify(mockYIntl); Mock.verify(mockYDataTypeDate); } diff --git a/tests/unit/lib/app/addons/rs/test-url.js b/tests/unit/lib/app/addons/rs/test-url.js index cc06d3efc..87d2b62d0 100644 --- a/tests/unit/lib/app/addons/rs/test-url.js +++ b/tests/unit/lib/app/addons/rs/test-url.js @@ -171,7 +171,7 @@ YUI().use('addon-rs-url', 'base', 'oop', 'test', function(Y) { A.areSame('/static/X/controller--controller.common.ext', store._mojitRVs.X[0].url); A.areSame(2, store._mojitRVs.Y.length); A.areSame('/static/Y/controller--controller.client.ext', store._mojitRVs.Y[0].url); - A.areSame('/static/Y/controller--controller.server.ext', store._mojitRVs.Y[1].url); + A.isUndefined(store._mojitRVs.Y[1].url); }, @@ -226,39 +226,8 @@ YUI().use('addon-rs-url', 'base', 'oop', 'test', function(Y) { store._makeResource('mojito', 'shared', 'x', 'y', 'common'); store._makeResource('orange', 'shared', 'x', 'y', 'common'); store.resolveResourceVersions(); - A.areSame('/FFF/x--y.common.ext', store._mojitRVs.shared[0].url); - A.areSame('/AAA/x--y.common.ext', store._mojitRVs.shared[1].url); - }, - - - 'assume rollups': function() { - var fixtures = libpath.join(__dirname, '../../../../fixtures/store'); - var store = new MockRS({ - root: fixtures, - appConfig: { - staticHandling: { assumeRollups: true } - } - }); - store.plug(Y.mojito.addons.rs.url, { appRoot: fixtures, mojitoRoot: mojitoRoot } ); - - store._makeResource('mojito', 'shared', 'x', 'y', 'common', 'red'); - store._makeResource('orange', 'shared', 'x', 'y', 'common', 'red'); - store._makeResource('orange', null, 'mojit', 'X', 'common'); - store._makeResource('orange', 'X', 'x', 'y', 'common', 'red'); - store._makeResource('orange', null, 'mojit', 'Y', 'common'); - store._makeResource('orange', 'Y', 'x', 'y', 'common', 'red'); - store._makeResource('orange', 'Y', 'not', 'yui', 'common'); - store.resolveResourceVersions(); - A.areSame('/static/store/rollup.client.js', store._mojitRVs.shared[0].url); - A.areSame(libpath.join(fixtures, 'rollup.client.js'), store._mojitRVs.shared[0].source.fs.rollupPath); - A.areSame('/static/store/rollup.client.js', store._mojitRVs.shared[1].url); - A.areSame(libpath.join(fixtures, 'rollup.client.js'), store._mojitRVs.shared[1].source.fs.rollupPath); - A.areSame('/static/X/rollup.client.js', store._mojitRVs.X[0].url); - A.areSame('path/for/mojit--X.common.ext/rollup.client.js', store._mojitRVs.X[0].source.fs.rollupPath); - A.areSame('/static/Y/rollup.client.js', store._mojitRVs.Y[0].url); - A.areSame('path/for/mojit--Y.common.ext/rollup.client.js', store._mojitRVs.Y[0].source.fs.rollupPath); - A.areSame('/static/Y/not--yui.common.ext', store._mojitRVs.Y[1].url); - A.isUndefined(store._mojitRVs.Y[1].source.fs.rollupPath); + A.areSame('/static/FFF/x--y.common.ext', store._mojitRVs.shared[0].url); + A.areSame('/static/AAA/x--y.common.ext', store._mojitRVs.shared[1].url); }, @@ -297,51 +266,51 @@ YUI().use('addon-rs-url', 'base', 'oop', 'test', function(Y) { store.plug(Y.mojito.addons.rs.url, {appRoot: fixtures, mojitoRoot: mojitoRoot}); store._makeResource('mojito', 'shared', 'x', 'y', 'common', 'red'); - store._makeResource('orange', 'shared', 'x', 'y', 'common', 'red'); + store._makeResource('orange', 'shared', 'x', 'y', 'common', 'blue'); store.resolveResourceVersions(); cb(store); } runTest({appName: '/'}, function(store) { - A.areSame('/static/mojito/x--y.common.ext', store._mojitRVs.shared[0].url, 'mojito with appName /'); - A.areSame('/static/x--y.common.ext', store._mojitRVs.shared[1].url, 'shared with /'); + A.areSame('/static/red.js', store._mojitRVs.shared[0].url, 'mojito with appName /'); + A.areSame('/static/blue.js', store._mojitRVs.shared[1].url, 'shared with /'); }); runTest({appName: '/AAA'}, function(store) { - A.areSame('/static/mojito/x--y.common.ext', store._mojitRVs.shared[0].url, 'mojito with appName /AAA'); - A.areSame('/static/AAA/x--y.common.ext', store._mojitRVs.shared[1].url, 'shared with appName /AAA'); + A.areSame('/static/red.js', store._mojitRVs.shared[0].url, 'mojito with appName /AAA'); + A.areSame('/static/blue.js', store._mojitRVs.shared[1].url, 'shared with appName /AAA'); }); runTest({appName: 'AAA/'}, function(store) { - A.areSame('/static/mojito/x--y.common.ext', store._mojitRVs.shared[0].url, 'mojito with appName AAA/'); - A.areSame('/static/AAA/x--y.common.ext', store._mojitRVs.shared[1].url, 'shared with appName AAA/'); + A.areSame('/static/red.js', store._mojitRVs.shared[0].url, 'mojito with appName AAA/'); + A.areSame('/static/blue.js', store._mojitRVs.shared[1].url, 'shared with appName AAA/'); }); runTest({appName: '/AAA/'}, function(store) { - A.areSame('/static/mojito/x--y.common.ext', store._mojitRVs.shared[0].url, 'mojito with appName /AAA/'); - A.areSame('/static/AAA/x--y.common.ext', store._mojitRVs.shared[1].url, 'shared with appName /AAA/'); + A.areSame('/static/red.js', store._mojitRVs.shared[0].url, 'mojito with appName /AAA/'); + A.areSame('/static/blue.js', store._mojitRVs.shared[1].url, 'shared with appName /AAA/'); }); runTest({appName: '/AAA/BBB/'}, function(store) { - A.areSame('/static/mojito/x--y.common.ext', store._mojitRVs.shared[0].url, 'mojito with appName /AAA/BBB/'); - A.areSame('/static/AAA/BBB/x--y.common.ext', store._mojitRVs.shared[1].url, 'shared with appName /AAA/BBB/'); + A.areSame('/static/red.js', store._mojitRVs.shared[0].url, 'mojito with appName /AAA/BBB/'); + A.areSame('/static/blue.js', store._mojitRVs.shared[1].url, 'shared with appName /AAA/BBB/'); }); runTest({frameworkName: '/'}, function(store) { - A.areSame('/static/x--y.common.ext', store._mojitRVs.shared[0].url, 'mojito with frameworkName /'); - A.areSame('/static/store/x--y.common.ext', store._mojitRVs.shared[1].url, 'shared with frameworkName /'); + A.areSame('/static/red.js', store._mojitRVs.shared[0].url, 'mojito with frameworkName /'); + A.areSame('/static/blue.js', store._mojitRVs.shared[1].url, 'shared with frameworkName /'); }); runTest({frameworkName: '/FFF'}, function(store) { - A.areSame('/static/FFF/x--y.common.ext', store._mojitRVs.shared[0].url, 'mojito with frameworkName /FFF'); - A.areSame('/static/store/x--y.common.ext', store._mojitRVs.shared[1].url, 'shared with frameworkName /FFF'); + A.areSame('/static/red.js', store._mojitRVs.shared[0].url, 'mojito with frameworkName /FFF'); + A.areSame('/static/blue.js', store._mojitRVs.shared[1].url, 'shared with frameworkName /FFF'); }); runTest({frameworkName: 'FFF/'}, function(store) { - A.areSame('/static/FFF/x--y.common.ext', store._mojitRVs.shared[0].url, 'mojito with frameworkName FFF/'); - A.areSame('/static/store/x--y.common.ext', store._mojitRVs.shared[1].url, 'shared with frameworkName FFF/'); + A.areSame('/static/red.js', store._mojitRVs.shared[0].url, 'mojito with frameworkName FFF/'); + A.areSame('/static/blue.js', store._mojitRVs.shared[1].url, 'shared with frameworkName FFF/'); }); runTest({frameworkName: '/FFF/'}, function(store) { - A.areSame('/static/FFF/x--y.common.ext', store._mojitRVs.shared[0].url, 'mojito with frameworkName /FFF/'); - A.areSame('/static/store/x--y.common.ext', store._mojitRVs.shared[1].url, 'shared with frameworkName /FFF/'); + A.areSame('/static/red.js', store._mojitRVs.shared[0].url, 'mojito with frameworkName /FFF/'); + A.areSame('/static/blue.js', store._mojitRVs.shared[1].url, 'shared with frameworkName /FFF/'); }); runTest({frameworkName: '/FFF/BBB/'}, function(store) { - A.areSame('/static/FFF/BBB/x--y.common.ext', store._mojitRVs.shared[0].url, 'mojito with frameworkName /FFF/BBB/'); - A.areSame('/static/store/x--y.common.ext', store._mojitRVs.shared[1].url, 'shared with frameworkName /FFF/BBB/'); + A.areSame('/static/red.js', store._mojitRVs.shared[0].url, 'mojito with frameworkName /FFF/BBB/'); + A.areSame('/static/blue.js', store._mojitRVs.shared[1].url, 'shared with frameworkName /FFF/BBB/'); }); } diff --git a/tests/unit/lib/app/addons/rs/test-yui.js b/tests/unit/lib/app/addons/rs/test-yui.js index b516b2184..19c5f3139 100644 --- a/tests/unit/lib/app/addons/rs/test-yui.js +++ b/tests/unit/lib/app/addons/rs/test-yui.js @@ -13,14 +13,30 @@ YUI().use( 'json', 'test', function(Y) { - + var suite = new YUITest.TestSuite('mojito-addon-rs-yui-tests'), + libasync = require('async'), libpath = require('path'), + libvm = require('vm'), mojitoRoot = libpath.join(__dirname, '../../../../../../lib'), A = Y.Assert, AA = Y.ArrayAssert; + function parseConfig(config) { + var ctx = { + Y: { + config: {}, + merge: Y.merge + }, + x: undefined + }; + config = 'x = ' + config + ';'; + libvm.runInNewContext(config, ctx, 'config'); + return ctx.x; + } + + function MockRS(config) { MockRS.superclass.constructor.apply(this, arguments); } @@ -539,13 +555,166 @@ YUI().use( }, - 'ignore: makeResourceVersions()': function() { - // TODO - }, - - - 'ignore: getResourceContent()': function() { - // TODO + 'yui meta': function() { + var fixtures = libpath.join(__dirname, '../../../../../fixtures/gsg5'), + store = new Y.mojito.ResourceStore({ root: fixtures }), + series = []; + store.preload(); + + series.push(function(next) { + var res, ress; + ress = store.getResourceVersions({mojit: 'shared', type: 'yui-module', subtype:'synthetic', name:'loader-app-base-en-US' }); + A.isArray(ress); + A.areSame(1, ress.length, "didn't find yui-module-synthetic-loader-app-base-en-US"); + res = ress[0]; + A.isObject(res); + store.getResourceContent(res, function(err, buffer, stat) { + A.isNull(err, 'error'); + A.isNotNull(stat, 'stat'); + meta = buffer.toString(); + var matches = meta.match(/Y\.applyConfig\(([\s\S]+?)\);/); + var config = parseConfig(matches[1]); + A.isObject(config); + A.isObject(config.groups); + A.areSame(1, Object.keys(config.groups).length); + A.isObject(config.groups.app); + // we'll just spot-check a few things + A.isObject(config.groups.app.modules); + A.isObject(config.groups.app.modules['lang/PagedFlickr_en-US']); + A.isArray(config.groups.app.modules['lang/PagedFlickr_en-US'].requires); + AA.itemsAreEqual(['intl'], config.groups.app.modules['lang/PagedFlickr_en-US'].requires); + A.isObject(config.groups.app.modules['mojito-client']); + A.isArray(config.groups.app.modules['mojito-client'].requires); + A.isUndefined(config.groups.app.modules['mojito-client'].expanded_map); + A.isTrue(Object.keys(config.groups.app.modules['mojito-client'].requires).length > 0); + A.isObject(config.groups.app.modules['PagedFlickrBinderIndex']); + A.isArray(config.groups.app.modules['PagedFlickrBinderIndex'].requires); + A.isUndefined(config.groups.app.modules['lang/PagedFlickr_de']); + next(); + }); + }); + series.push(function(next) { + var res, ress; + ress = store.getResourceVersions({mojit: 'shared', type: 'yui-module', subtype:'synthetic', name:'loader-app-resolved-en-US' }); + A.isArray(ress); + A.areSame(1, ress.length); + res = ress[0]; + A.isObject(res); + store.getResourceContent(res, function(err, buffer, stat) { + A.isNull(err, 'err'); + A.isNotNull(stat, 'stat'); + meta = buffer.toString(); + var matches = meta.match(/Y\.applyConfig\(([\s\S]+?)\);/); + var config = parseConfig(matches[1]); + A.isObject(config); + A.isObject(config.groups); + A.areSame(1, Object.keys(config.groups).length); + A.isObject(config.groups.app); + // we'll just spot-check a few things + A.isObject(config.groups.app.modules); + A.isObject(config.groups.app.modules['lang/PagedFlickr_en-US']); + A.isArray(config.groups.app.modules['lang/PagedFlickr_en-US'].requires); + AA.itemsAreEqual(['intl'], config.groups.app.modules['lang/PagedFlickr_en-US'].requires); + A.isObject(config.groups.app.modules['mojito-client']); + A.isArray(config.groups.app.modules['mojito-client'].requires); + A.isTrue(Object.keys(config.groups.app.modules['mojito-client'].requires).length > 0); + A.isObject(config.groups.app.modules['PagedFlickrBinderIndex']); + A.isArray(config.groups.app.modules['PagedFlickrBinderIndex'].requires); + A.isUndefined(config.groups.app.modules['lang/PagedFlickr_de']); + var i, j, obj; + for (i in config.groups.app.modules) { + if (config.groups.app.modules.hasOwnProperty(i)) { + obj = config.groups.app.modules[i]; + A.isNotUndefined(obj.name); + A.isNotUndefined(obj.type); + A.isNotUndefined(obj.path); + A.isNotUndefined(obj.requires); + A.isNotUndefined(obj.defaults); + // language bundles don't have expanded_map + if (!obj.langPack) { + for (j = 0; j < obj.requires.length; j += 1) { + A.isNotUndefined(obj.expanded_map[obj.requires[j]]); + } + } + } + } + next(); + }); + }); + series.push(function(next) { + var res, ress; + ress = store.getResourceVersions({mojit: 'shared', type: 'yui-module', subtype:'synthetic', name:'loader-yui3-base-en-US' }); + A.isArray(ress); + A.areSame(1, ress.length); + res = ress[0]; + A.isObject(res); + store.getResourceContent(res, function(err, buffer, stat) { + A.isNull(err); + A.isNotNull(stat); + meta = buffer.toString(); + var matches = meta.match(/\.modules=[^|]+\|\|([\s\S]+?);},"",{requires:/); + var config = parseConfig(matches[1]); + A.isObject(config); + A.isObject(config.intl); + A.isUndefined(config.intl.expanded_map); + A.isObject(config['dom-style-ie']); + A.isObject(config['dom-style-ie'].condition); + A.areSame('function', typeof config['dom-style-ie'].condition.test); + A.isUndefined(config['dom-style-ie'].expanded_map); + next(); + }); + }); + series.push(function(next) { + var res, ress; + ress = store.getResourceVersions({mojit: 'shared', type: 'yui-module', subtype:'synthetic', name:'loader-yui3-resolved-en-US' }); + A.isArray(ress); + A.areSame(1, ress.length); + res = ress[0]; + A.isObject(res); + store.getResourceContent(res, function(err, buffer, stat) { + A.isNull(err); + A.isNotNull(stat); + meta = buffer.toString(); + var matches = meta.match(/\.modules=[^|]+\|\|([\s\S]+?);},"",{requires:/); + var config = parseConfig(matches[1]); + for (i in config) { + if (config.hasOwnProperty(i)) { + obj = config[i]; + A.isNotUndefined(obj.name, 'name'); + A.isNotUndefined(obj.type, 'type'); + A.isNotUndefined(obj.requires, 'requires'); + A.isNotUndefined(obj.defaults, 'defaults'); + // language bundles don't have expanded_map + if (!obj.langPack) { + A.isNotUndefined(obj.expanded_map); + } + } + } + A.isObject(config.intl); + A.isObject(config['dom-style-ie']); + A.isObject(config['dom-style-ie'].condition); + A.areSame('function', typeof config['dom-style-ie'].condition.test); + next(); + }); + }); + series.push(function(next) { + var res, ress; + ress = store.getResourceVersions({mojit: 'shared', type: 'yui-module', subtype:'synthetic', name:'loader-app' }); + A.isArray(ress); + A.areSame(1, ress.length); + res = ress[0]; + A.isObject(res); + store.getResourceContent(res, function(err, buffer, stat) { + A.isNull(err); + A.isNotNull(stat); + meta = buffer.toString(); + A.areSame('YUI.add("loader",function(Y){},"",{requires:["loader-base","loader-yui3","loader-app"]});', meta); + next(); + }); + }); + libasync.series(series, function(err) { + A.isNull(err, 'no errors for all tests'); + }); }, diff --git a/tests/unit/lib/app/autoload/autoload_test_descriptor.json b/tests/unit/lib/app/autoload/autoload_test_descriptor.json index 9e34605dd..1ff10af58 100644 --- a/tests/unit/lib/app/autoload/autoload_test_descriptor.json +++ b/tests/unit/lib/app/autoload/autoload_test_descriptor.json @@ -106,8 +106,8 @@ "tunnel.client": { "params": { "page": "$$config.base$$/mojito-test.html", - "lib": "$$config.lib$$/app/autoload/tunnel.client-optional.js", - "test": "./test-tunnel.client-optional.js" + "lib": "$$config.lib$$/app/autoload/tunnel.common.js", + "test": "./test-tunnel.common.js" }, "group": "fw,unit,client" }, diff --git a/tests/unit/lib/app/autoload/test-dispatch.common.js b/tests/unit/lib/app/autoload/test-dispatch.common.js index 0e0fa42a8..ea4d30298 100644 --- a/tests/unit/lib/app/autoload/test-dispatch.common.js +++ b/tests/unit/lib/app/autoload/test-dispatch.common.js @@ -33,9 +33,6 @@ YUI.add('mojito-dispatcher-tests', function(Y, NAME) { id: 'xyz123', instanceId: 'xyz123', 'controller-module': 'dispatch', - createController: function() { - return { index: function() {} }; - }, yui: { config: {}, langs: [], @@ -52,7 +49,7 @@ YUI.add('mojito-dispatcher-tests', function(Y, NAME) { instance: { type: 'M' }, - context: { + context: { lang: 'klingon', langs: 'klingon' } @@ -67,79 +64,153 @@ YUI.add('mojito-dispatcher-tests', function(Y, NAME) { adapter = null; }, - 'test dispatch uses supplied getter': function() { - var getterInvoked = false, - res; + 'test rpc with tunnel': function () { + var tunnel, + tunnelCommand; - var originalActionContext = Y.namespace('mojito').ActionContext; - - Y.namespace('mojito').ActionContext = function(opts) { - return this; + tunnel = { + rpc: function (c, a) { + tunnelCommand = c; + } }; + errorTriggered = false; + dispatcher.init(store, tunnel); + dispatcher.rpc(command, { + error: function () { + A.fail('tunnel should be called instead'); + } + }); + A.areSame(command, tunnelCommand, 'delegate command to tunnel'); + }, - store.expandInstance = function(instance, context, cb) { - cb(null, { - type: instance.type, - id: 'xyz123', - instanceId: 'xyz123', - 'controller-module': 'dispatch', - createController: function() { - getterInvoked = true; - return { index: function() {} }; - }, - yui: { - config: { - modules: ['mojito', 'mojito-action-context'] - }, - langs: [], - requires: [], - sorted: ['mojito', 'mojito-action-context'], - sortedPaths: {} - } - }); - }; - - Y.namespace('mojito').ActionContext = originalActionContext; + 'test rpc without tunnel available': function () { + var tunnel, + errorTriggered, + tunnelCommand; + + tunnel = null; + errorTriggered = false; + dispatcher.init(store, tunnel); + dispatcher.rpc(command, { + error: function () { + errorTriggered = true; + } + }); + A.isTrue(errorTriggered, 'if tunnel is not set, it should call adapter.error'); + }, - res = dispatcher.init(store); - A.areSame(res, dispatcher); + 'test dispatch with command.rpc=1': function () { + var tunnel, + tunnelCommand; - res.dispatch(command, adapter); - A.isTrue(getterInvoked); + tunnel = { + rpc: function (c, a) { + tunnelCommand = c; + } + }; + command.rpc = 1; + errorTriggered = false; + dispatcher.init(store, tunnel); + dispatcher.rpc(command, { + error: function () { + A.fail('tunnel should be called instead'); + } + }); + A.areSame(command, tunnelCommand, 'delegate command to tunnel'); }, - 'test dispatch uses supplied action': function() { - var actionInvoked = false, - res; + 'test dispatch with invalid mojit': function () { + var tunnel, + tunnelCommand; - store.expandInstance = function(instance, context, cb) { - cb(null, { - type: instance.type, - id: 'xyz123', - instanceId: 'xyz123', - 'controller-module': 'dispatch', - createController: function() { - return { index: function() { - actionInvoked = true; - } }; - }, - yui: { - config: { - modules: ['mojito', 'mojito-action-context'] - }, - langs: [], - requires: [], - sorted: ['mojito', 'mojito-action-context'], - sortedPaths: {} - } - }); + tunnel = { + rpc: function (c, a) { + tunnelCommand = c; + } + }; + errorTriggered = false; + dispatcher.init(store, tunnel); + // if the expandInstance calls with an error, the tunnel + // should be tried. + store.expandInstance = function (instance, context, callback) { + callback({error: 1}); + }; + dispatcher.dispatch(command, { + error: function () { + A.fail('tunnel should be called instead'); + } + }); + A.areSame(command, tunnelCommand, 'delegate command to tunnel'); + }, + + 'test dispatch with valid controller': function () { + var tunnel, + useCommand, + acCommand, + _createActionContext = dispatcher._createActionContext, + _useController = dispatcher._useController; + + errorTriggered = false; + dispatcher.init(store, tunnel); + // if the expandInstance calls with an error, the tunnel + // should be tried. + store.expandInstance = function (instance, context, callback) { + instance.controller = 'foo'; + Y.mojito.controllers[instance.controller] = { + fakeController: true }; + callback(null, instance); + }; + dispatcher._useController = function (c) { + A.fail('_createActionContext should be called instead'); + }; + dispatcher._createActionContext = function (c) { + acCommand = c; + }; + dispatcher.dispatch(command, { + error: function () { + A.fail('_createActionContext should be called instead'); + } + }); + A.areSame(command, acCommand, 'AC should be created based on the original command'); - res = dispatcher.init(store); - A.areSame(res, dispatcher); + // restoring references + dispatcher._createActionContext = _createActionContext; + dispatcher._useController = _useController; + }, + + 'test dispatch with invalid controller': function () { + var tunnel, + useCommand, + acCommand, + _createActionContext = dispatcher._createActionContext, + _useController = dispatcher._useController; + + errorTriggered = false; + dispatcher.init(store, tunnel); + // if the expandInstance calls with an error, the tunnel + // should be tried. + store.expandInstance = function (instance, context, callback) { + instance.controller = 'foo'; + Y.mojito.controllers[instance.controller] = null; + callback(null, instance); + }; + dispatcher._useController = function (c) { + useCommand = c; + }; + dispatcher._createActionContext = function (c) { + A.fail('_createActionContext should be called instead'); + }; + dispatcher.dispatch(command, { + error: function () { + A.fail('_useController should be called instead'); + } + }); + A.areSame(command, useCommand, '_useController should be called based on the original command'); - res.dispatch(command, adapter); - A.isTrue(actionInvoked); + // restoring references + dispatcher._createActionContext = _createActionContext; + dispatcher._useController = _useController; } })); diff --git a/tests/unit/lib/app/autoload/test-store.server.js b/tests/unit/lib/app/autoload/test-store.server.js index 61daf1156..f85c14d0e 100644 --- a/tests/unit/lib/app/autoload/test-store.server.js +++ b/tests/unit/lib/app/autoload/test-store.server.js @@ -265,19 +265,16 @@ YUI().use( 'expandInstance caching': function() { var inInstance = { base: 'a', - action: 'b', - type: 'c', - id: 'd' + type: 'c' }; var context = {}; var key = Y.JSON.stringify([inInstance, ['*'], context.lang]); store._expandInstanceCache.server[key] = { x: 'y' }; store.expandInstance(inInstance, context, function(err, outInstance) { - A.areEqual(5, Object.keys(outInstance).length); + A.isNull(err); + A.areEqual(3, Object.keys(outInstance).length); A.areEqual('a', outInstance.base); - A.areEqual('b', outInstance.action); A.areEqual('c', outInstance.type); - A.areEqual('d', outInstance.id); A.areEqual('y', outInstance.x); }); }, @@ -448,7 +445,7 @@ YUI().use( 'appConfig staticHandling.prefix': function() { var spec = { type: 'PagedFlickr' }; store.expandInstance(spec, {}, function(err, instance) { - A.areSame('/PagedFlickr/assets', instance.assetsRoot); + A.areSame('/static/PagedFlickr/assets', instance.assetsRoot); }); } diff --git a/tests/unit/lib/app/autoload/test-tunnel.client-optional.js b/tests/unit/lib/app/autoload/test-tunnel.common.js similarity index 98% rename from tests/unit/lib/app/autoload/test-tunnel.client-optional.js rename to tests/unit/lib/app/autoload/test-tunnel.common.js index d9702226b..42729b695 100644 --- a/tests/unit/lib/app/autoload/test-tunnel.client-optional.js +++ b/tests/unit/lib/app/autoload/test-tunnel.common.js @@ -10,7 +10,7 @@ /* - * Test suite for the tunnel.client-optional.js file functionality. + * Test suite for the tunnel.common.js file functionality. */ YUI({useBrowserConsole: true}).use( "mojito-tunnel-client", diff --git a/tests/unit/lib/app/autoload/test-util.common.js b/tests/unit/lib/app/autoload/test-util.common.js index 314c355f5..dda0247b1 100644 --- a/tests/unit/lib/app/autoload/test-util.common.js +++ b/tests/unit/lib/app/autoload/test-util.common.js @@ -527,6 +527,22 @@ }; var result = Y.mojito.util.metaMerge(to, from); OA.areEqual(expected['content-type'], result['content-type']); + }, + + 'test findClosestLang': function() { + var have = { + 'en-US': true, + 'en': true, + 'de': true, + }; + A.areSame('en-US', Y.mojito.util.findClosestLang('en-US-midwest', have), 'en-US-midwest'); + A.areSame('en-US', Y.mojito.util.findClosestLang('en-US', have), 'en-US'); + A.areSame('en', Y.mojito.util.findClosestLang('en', have), 'en'); + A.areSame('de', Y.mojito.util.findClosestLang('de-DE', have), 'de-DE'); + A.areSame('de', Y.mojito.util.findClosestLang('de', have), 'de'); + A.areSame('', Y.mojito.util.findClosestLang('nl-NL', have), 'nl-NL'); + A.areSame('', Y.mojito.util.findClosestLang('nl', have), 'nl'); + A.areSame('', Y.mojito.util.findClosestLang('', have), 'no lang'); } }; diff --git a/tests/unit/lib/app/middleware/test-contextualizer.js b/tests/unit/lib/app/middleware/test-contextualizer.js index d46b72cb5..d9de85869 100644 --- a/tests/unit/lib/app/middleware/test-contextualizer.js +++ b/tests/unit/lib/app/middleware/test-contextualizer.js @@ -22,7 +22,7 @@ YUI().use('mojito-test-extra', 'test', function(Y) { }; cases = { - name: 'basic', + name: 'contextualizer middleware tests', setUp: function() { handler = factory({ diff --git a/tests/unit/lib/app/middleware/test-handler-static.js b/tests/unit/lib/app/middleware/test-handler-static.js index ebc8e4599..c5d577d94 100644 --- a/tests/unit/lib/app/middleware/test-handler-static.js +++ b/tests/unit/lib/app/middleware/test-handler-static.js @@ -9,21 +9,32 @@ YUI().use('mojito-test-extra', 'test', function(Y) { cases = {}, store, urlRess, + yuiRess, factory = require(Y.MOJITO_DIR + 'lib/app/middleware/mojito-handler-static'); + yuiRess = { + '/static/yui/yui-base/yui-base-min.js': { + mime: { + type: 'text/javascript', + charset: 'UTF-8' + } + } + }; urlRess = { - "/compiled.css": { + "/static/compiled.css": { mime: { type: 'text/css', charset: 'UTF-8' } }, "/favicon.ico": { + id: 'favicon.ico', mime: { type: 'image/vnc.microsoft.com' } }, "/robots.txt": { + id: 'robots.txt', mime: { type: 'text/plain', charset: 'UTF-8' @@ -34,6 +45,12 @@ YUI().use('mojito-test-extra', 'test', function(Y) { type: 'text/xml', charset: 'UTF-8' } + }, + "/static/cacheable.css": { + mime: { + type: 'text/css', + charset: 'UTF-8' + } } }; cases = { @@ -47,6 +64,9 @@ YUI().use('mojito-test-extra', 'test', function(Y) { getAllURLResources: function () { return urlRess; }, + getResourceVersions: function () { + return {}; + }, getResourceContent: function (args, callback) { var content, stat; content = new Buffer('1234567890'); @@ -62,13 +82,18 @@ YUI().use('mojito-test-extra', 'test', function(Y) { getStaticAppConfig: function() { return { staticHandling: { - cache: false, + cache: true, maxAge: null } }; }, getResources: function(env, ctx, filter) { return [{ filter: filter }]; + }, + yui: { + getYUIURLResources: function () { + return yuiRess; + } } }; @@ -90,13 +115,13 @@ YUI().use('mojito-test-extra', 'test', function(Y) { 'handler calls next() when HTTP method is not HEAD or GET': function() { var callCount = 0; this._handler({ - url: '/foo', + url: '/static/foo', method: 'PUT' }, null, function() { callCount++; }); this._handler({ - url: '/bar', + url: '/combo~/static/bar', method: 'POST' }, null, function() { callCount++; @@ -105,13 +130,32 @@ YUI().use('mojito-test-extra', 'test', function(Y) { }, + 'handler calls next() when no combo or static prefix is used': function() { + var callCount = 0; + this._handler({ + url: '/foo/baz', + method: 'GET' + }, null, function() { + callCount++; + }); + this._handler({ + url: '/bar~baz', + method: 'GET' + }, null, function() { + callCount++; + }); + A.areEqual(2, callCount, 'next() handler should have been called'); + }, + + 'handler detects forbidden calls': function() { var callCount = 0, errorCode, end, req = { - url: '/foo/../bar.css', - method: 'GET' + url: '/static/foo/../bar.css', + method: 'GET', + headers: {} }, res = { writeHead: function (c) { @@ -134,7 +178,7 @@ YUI().use('mojito-test-extra', 'test', function(Y) { 'handler calls next() when URL is not in RS hash': function() { var callCount = 0; this._handler({ - url: '/foo', + url: '/static/foo', method: 'GET' }, null, function() { callCount++; @@ -143,7 +187,48 @@ YUI().use('mojito-test-extra', 'test', function(Y) { }, - 'ignore: handler uses cache when possible': function () { + 'handler uses cache when possible': function () { + var resCode, + resHeader, + end = 0, + next = 0, + hits = 0, + req = { + url: '/static/cacheable.css', + method: 'GET', + headers: {} + }, + res = { + writeHead: function(code, header) { + resCode = code; + resHeader = header; + }, + end: function() { + end++; + } + }, + // backing up the original getResourceContent to count + // the hits + getResourceContentFn = store.getResourceContent; + + store.getResourceContent = function() { + hits++; + // counting and executing the original function + getResourceContentFn.apply(this, arguments); + }; + + this._handler(req, res, function() { + next++; + }); + this._handler(req, res, function() { + next++; + }); + + A.areEqual(0, next, 'next() should not be called for valid entries'); + A.areEqual(1, hits, 'one hit to the store should be issued, the next should use the cached version.'); + A.areEqual(2, end, 'two valid requests should be counted'); + + store.getResourceContent = getResourceContentFn; }, @@ -157,7 +242,7 @@ YUI().use('mojito-test-extra', 'test', function(Y) { 'handler supports compiled resources': function () { var req = { - url: '/compiled.css', + url: '/static/compiled.css', method: 'GET', headers: {} }, @@ -213,7 +298,7 @@ YUI().use('mojito-test-extra', 'test', function(Y) { }, end: function() { } - } + }; for (i = 0; i < urls.length; i += 1) { @@ -224,17 +309,17 @@ YUI().use('mojito-test-extra', 'test', function(Y) { context: {}, store: store, logger: { - log: function() {} + log: function () {} } }); store.getResourceContent = function(resource, cb) { OA.areEqual(urlRess[req.url], resource, 'wrong resource'); resourceContentCalled = true; }; - handler(req, res, function() { + handler(req, res, function () { callCount++; }); - A.areEqual(0, callCount, 'next() handler should not have been called') + A.areEqual(0, callCount, 'next() handler should not have been called'); A.isTrue(resourceContentCalled, 'getResourceContent was not called for url: ' + req.url); } @@ -251,7 +336,7 @@ YUI().use('mojito-test-extra', 'test', function(Y) { mockResources; mockResources = { - "/robots.txt": { + "/robots.txt": { mime: { type: 'text/html' } } }; @@ -265,11 +350,11 @@ YUI().use('mojito-test-extra', 'test', function(Y) { headers: {} }; resp = { - writeHeader: function() { }, + writeHead: function() { }, end: function() { } - } + }; - // + // // handle res of type obj store.getAllURLResources = function() { return mockResources; @@ -291,10 +376,10 @@ YUI().use('mojito-test-extra', 'test', function(Y) { A.fail('next() handler 1 should not have been called'); }); - // + // // handle res of type array store.getAllURLResources = function() { - return {}; + return mockResources; }; store.getResources = function() { return [mockResources["/robots.txt"]]; @@ -316,7 +401,249 @@ YUI().use('mojito-test-extra', 'test', function(Y) { store.getResources = getResourcesFn; store.getResourceContent = getResourceContentFn; store.getAllURLResources = getAllURLResourcesFn; + }, + + + 'bad or missing files': function() { + var handler = factory({ + context: {}, + store: store, + logger: { log: function() {} } + }); + + var req = { + method: 'GET', + // combining an existing file with an invalid one should trigger 400 + url: '/combo~/static/compiled.css~/static/PagedFlickrModel.js', + headers: {} + }; + var writeHeadCalled = 0, + gotCode, + gotHeaders, + res = { + writeHead: function(code, headers) { + writeHeadCalled += 1; + gotCode = code; + gotHeaders = headers; + }, + end: function(body) { + var i; + A.areSame(1, writeHeadCalled); + A.areSame(400, gotCode); + A.isUndefined(gotHeaders); + A.isUndefined(body); + } + }; + handler(req, res); + }, + + + 'valid combo url': function() { + var handler = factory({ + context: {}, + store: store, + logger: { log: function() {} } + }); + + var req = { + method: 'GET', + url: '/combo~/static/compiled.css~/static/cacheable.css', + headers: {} + }; + var writeHeadCalled = 0, + gotCode, + gotHeaders, + res = { + writeHead: function(code, headers) { + writeHeadCalled += 1; + gotCode = code; + gotHeaders = headers; + }, + end: function(body) { + var i; + A.areSame(1, writeHeadCalled); + A.areSame(200, gotCode); + A.areSame(20, body.length, 'two segments of 10 digits according to getResourceContent method'); + } + }; + handler(req, res); + }, + + 'valid combo url with one file': function() { + var handler = factory({ + context: {}, + store: store, + logger: { log: function() {} } + }); + + var req = { + method: 'GET', + url: '/combo~/static/compiled.css', + headers: {} + }; + var writeHeadCalled = 0, + gotCode, + gotHeaders, + res = { + writeHead: function(code, headers) { + writeHeadCalled += 1; + gotCode = code; + gotHeaders = headers; + }, + end: function(body) { + var i; + A.areSame(1, writeHeadCalled); + A.areSame(200, gotCode); + A.areSame(10, body.length, 'one segments of 10 digits according to getResourceContent method'); + } + }; + handler(req, res); + }, + + 'broken valid combo url with one and a half files': function() { + var handler = factory({ + context: {}, + store: store, + logger: { log: function() {} } + }); + + var req = { + method: 'GET', + url: '/combo~/static/compiled.css~/st', + headers: {} + }; + var writeHeadCalled = 0, + gotCode, + gotHeaders, + res = { + writeHead: function(code, headers) { + writeHeadCalled += 1; + gotCode = code; + gotHeaders = headers; + }, + end: function(body) { + var i; + A.areSame(1, writeHeadCalled); + A.areSame(200, gotCode); + A.areSame(10, body.length, + 'one segments of 10 digits according to getResourceContent method, ' + + 'the second part of the combo is invalid but we should be tolerant on this one.'); + } + }; + handler(req, res); + }, + + + 'serve single binary file': function() { + var handler, + realGetResourceContent, + req, res, + writeHeadCalled = 0, + gotCode, gotHeaders, gotBody; + + realGetResourceContent = store.getResourceContent; + store.getResourceContent = function(res, callback) { + var stat = { + mtime: new Date(), + ctime: new Date(), + // this size -shouldn't- be used for content-length header + size: 5 + }; + callback(null, new Buffer('we ✔ are ∞ good', 'utf8'), stat); + }; + + req = { + url: '/favicon.ico', + method: 'GET', + headers: {} + }; + res = { + writeHead: function(code, headers) { + writeHeadCalled += 1; + gotCode = code; + gotHeaders = headers; + }, + end: function(body) { + A.areSame(1, writeHeadCalled); + A.areSame(200, gotCode); + A.isObject(gotHeaders); + A.areSame(19, gotHeaders['Content-Length']); + A.areSame('we ✔ are ∞ good', body.toString('utf8')); + } + }; + + handler = factory({ + context: {}, + store: store, + logger: { log: function() {} } + }); + handler(req, res, function() { + A.fail('next() handler should not have been called'); + }); + + store.getResourceContent = realGetResourceContent; + }, + + + 'combo binary file': function() { + var handler, + realGetResourceContent, + req, res, + writeHeadCalled = 0, + gotCode, gotHeaders, gotBody; + + realGetResourceContent = store.getResourceContent; + store.getResourceContent = function(res, callback) { + var stat = { + mtime: new Date(), + ctime: new Date(), + // this size -shouldn't- be used for content-length header + size: 5 + }; + var buffer; + if ('favicon.ico' === res.id) { + callback(null, new Buffer('we ✔ are ∞ good', 'utf8'), stat); + return; + } + if ('robots.txt' === res.id) { + callback(null, new Buffer("aren't ∀ you ⸘ happy", 'utf8'), stat); + return; + } + callback(new Error('unknown resource')); + }; + + req = { + url: '/combo~/favicon.ico~/robots.txt', + method: 'GET', + headers: {} + }; + res = { + writeHead: function(code, headers) { + writeHeadCalled += 1; + gotCode = code; + gotHeaders = headers; + }, + end: function(body) { + A.areSame(1, writeHeadCalled); + A.areSame(200, gotCode); + A.isObject(gotHeaders); + A.areSame(43, gotHeaders['Content-Length']); + A.areSame("we ✔ are ∞ goodaren't ∀ you ⸘ happy", body.toString('utf8')); + } + }; + + handler = factory({ + context: {}, + store: store, + logger: { log: function() {} } + }); + handler(req, res, function() { + A.fail('next() handler should not have been called'); + }); + + store.getResourceContent = realGetResourceContent; } + }; Y.Test.Runner.add(new Y.Test.Case(cases)); diff --git a/tests/unit/lib/app/middleware/test-handler-tunnel.js b/tests/unit/lib/app/middleware/test-handler-tunnel.js index 4cceb8eaa..6365e4ba1 100644 --- a/tests/unit/lib/app/middleware/test-handler-tunnel.js +++ b/tests/unit/lib/app/middleware/test-handler-tunnel.js @@ -13,7 +13,7 @@ YUI().use('mojito-test-extra', 'test', function(Y) { expandedContext; cases = { - name: 'handler tests', + name: 'tunnel handler tests', _handler: null, diff --git a/tests/unit/lib/app/middleware/test-router.js b/tests/unit/lib/app/middleware/test-router.js index c0babdfc8..201b095f9 100644 --- a/tests/unit/lib/app/middleware/test-router.js +++ b/tests/unit/lib/app/middleware/test-router.js @@ -12,7 +12,7 @@ YUI().use('mojito-route-maker', 'mojito-test-extra', 'test', function(Y) { factory = require(Y.MOJITO_DIR + 'lib/app/middleware/mojito-router'); cases = { - name: 'Handler route matching', + name: 'router middleware tests', 'dynamic id and action': function() { autoRouteMatchTester( diff --git a/tests/unit/lib/lib_test_descriptor.json b/tests/unit/lib/lib_test_descriptor.json index 4b68c2d53..9e9aad7a2 100644 --- a/tests/unit/lib/lib_test_descriptor.json +++ b/tests/unit/lib/lib_test_descriptor.json @@ -12,7 +12,8 @@ "params": { "lib": "$$config.lib$$/mojito.js", "test": "./test-mojito.js", - "page": "$$config.base$$/mojito-test.html" + "page": "$$config.base$$/mojito-test.html", + "driver": "nodejs" }, "group": "fw,server" }, diff --git a/tests/unit/lib/test-mojito.js b/tests/unit/lib/test-mojito.js index 89e89cacf..ea0539261 100644 --- a/tests/unit/lib/test-mojito.js +++ b/tests/unit/lib/test-mojito.js @@ -12,6 +12,7 @@ YUI().use('mojito', 'test', function (Y) { var suite = new Y.Test.Suite('mojito tests'), path = require('path'), A = Y.Assert, + AA = Y.ArrayAssert, OA = Y.ObjectAssert, Mojito = require(path.join(__dirname, '../../../lib/mojito')), noop = function() {}, @@ -21,6 +22,30 @@ YUI().use('mojito', 'test', function (Y) { server, app; + function cmp(x, y, msg) { + if (Y.Lang.isArray(x)) { + A.isArray(x, msg || 'first arg should be an array'); + A.isArray(y, msg || 'second arg should be an array'); + A.areSame(x.length, y.length, msg || 'arrays are different lengths'); + for (var i = 0; i < x.length; i += 1) { + cmp(x[i], y[i], msg); + } + return; + } + if (Y.Lang.isObject(x)) { + A.isObject(x, msg || 'first arg should be an object'); + A.isObject(y, msg || 'second arg should be an object'); + A.areSame(Object.keys(x).length, Object.keys(y).length, msg || 'object keys are different lengths'); + for (var i in x) { + if (x.hasOwnProperty(i)) { + cmp(x[i], y[i], msg); + } + } + return; + } + A.areSame(x, y, msg || 'args should be the same'); + } + suite.add(new Y.Test.Case({ name: 'Mojito object interface tests', @@ -182,6 +207,64 @@ YUI().use('mojito', 'test', function (Y) { A.isTrue(configured); }, + 'configure YUI': function() { + var mockY, mockStore, load = []; + var haveConfig, wantConfig; + mockY = { + merge: Y.merge, + applyConfig: function(cfg) { + haveConfig = cfg; + } + }; + mockStore = { + yui: { + langs: { + 'xx-00': true, + 'yy-11': true + }, + getConfigAllMojits: function() { + return { + modules: { + 'mojits-A': 'mojits-A-val', + 'mojits-B': 'mojits-B-val' + } + }; + }, + getConfigShared: function() { + return { + modules: { + 'shared-A': 'shared-A-val', + 'shared-B': 'shared-B-val' + } + }; + } + } + } + + A.isFunction(server._configureYUI); + + var res = server._configureYUI(mockY, mockStore, load); + A.isUndefined(res); + + A.areSame(6, load.length); + AA.contains('mojits-A', load); + AA.contains('mojits-B', load); + AA.contains('shared-A', load); + AA.contains('shared-B', load); + AA.contains('lang/datatype-date-format_xx-00', load); + AA.contains('lang/datatype-date-format_yy-11', load); + + wantConfig = { + modules: { + 'mojits-A': 'mojits-A-val', + 'mojits-B': 'mojits-B-val', + 'shared-A': 'shared-A-val', + 'shared-B': 'shared-B-val' + } + }; + cmp(wantConfig, haveConfig); + } + })); Y.Test.Runner.add(suite); diff --git a/tests/unit/lib/test-server-log.server.js b/tests/unit/lib/test-server-log.server.js index 859939e61..dfdf66461 100644 --- a/tests/unit/lib/test-server-log.server.js +++ b/tests/unit/lib/test-server-log.server.js @@ -7,6 +7,18 @@ YUI().use('test', function(Y) { var path = require('path'), suite = new Y.Test.Suite('mojito-server-log-tests'), - serverLog = require(path.join(__dirname, '../../../lib/server-log')); + serverLog = require(path.join(__dirname, '../../../lib/server-log')), + suite = new Y.Test.Suite('server-log-tests'); + + suite.add(new Y.Test.Case({ + + name: 'server-log.server tests', + + 'test serverLog not undefined': function() { + Y.Test.Assert.isNotUndefined(serverLog, 'serverLog was not required correctly!'); + } + })); + + Y.Test.Runner.add(suite); });