See /Planned for a list of potentially planned bananas.
Banana is the module system for MonkeyScript. (PHP's PEAR acronym is a fruit, Ruby's Gems are related to the language's name; MonkeyScript (Fruit + Language name relevance) = Banana)
Banana is inspired by a number of other package system, taking various ideas from other package systems, namely RubyGems and Common Lisp's ASDF (Another System Definition Facility).
Bananas (banana modules) are defined each with a `banana.json` metadata file. Similarly to common lisp /etc/monkeyscript/rc.js, ~/.monkeyscriptrc (or ~/.monkeyscriptrc.js), and a local variant are used. (For the local variant monkeyscript uses $PWD/.monkeyscriptrc (or $PWD/.monkeyscriptrc.js) and a mod_monkeyscript will probably do something similar to .htaccess and let you put an rc file in the docroot at least).
The banana loading system (formerly known as JSDF (JavaScript System Definition Facility
I later realized the amusing note that JS DF matched JavaScript+DanielFriesen) is loaded by the core monkeyscript.js, inside of the rc files you define the location of any banana registries which are used to lookup bananas when you include them.
Contents
Quick glossary
- registry
A .json file in a specified location used to aggregate information about metadata from bananas. The metadata from a number of banana meta files is compiled into a single "registry" .json file which contains portions of that metadata and absolute paths to the packages. The registry is then referred to when looking up any "installed" banana packages.
- repository
- A remote location running a banana server which serves out bananas using the banana protocol. The local banana system keeps a list of these which can be changed and talks to the remote repositories when searching, listing upgrades, upgrading, and installing bananas.
name, shortname, and namespace
There are three important pieces of naming for a banana.
The shortname is a lower-case id like name for a banana, it must match the regex /^[a-z][-\w]+$/. For example jQuery would use "jquery", CURL would be "curl", and JSON Template might use "json-template".
The name is a full display name of the banana. This is normally the fully formatted name a project likes to go by like jQuery, CURL, JSON Template.
The namespace is a fully qualified namespace and id for identification. It's multi leveled separated by dots (.) and individual parts must match the standard /^[$_a-z][$_\w]*$/ regex. Normally a category based hierarchy is used, such as 'client.framework.jquery', 'database.couchdb', or 'protocol.http', though you may use the classic reverse-domain style used in Java like 'org.apache.couchdb', 'com.jquery', and 'com.googlecode.json-template`. Of course, since namespace definition may be an array you can use both and the first one defined will be the primary.
Different parts are used in different areas of the system. The shortname is used when installing modules most of the time, shortnames are allowed to conflict with other names but ideally should avoid doing so. namespaces however are supposed to be absolutely unique and are thus used by actual banana() calls to avoid ambiguously loading things. The name itself is just used when displaying the name of the module to readers. Namespaces are not exclusive of namespaces below them. In other words the existence of markup.data.yaml does not mean a markup.data.yaml.libyaml banana cannot exist. In fact in cases like this markup.data.yaml would be reserved for the canonical standardized YAML parser, while markup.data.yaml.* would be used by different implementations. (Side note here, YAML spec asks for YAML.dump and YAML.load, we'll probably make those aliases for YAML.parse and YAML.stringify to be consistent with native JSON).
Including into code
Bananas are included into code by importing (with) them into the scope using banana(namespace) (see banana())
- This is normally done using a full source with wrapper, it's also possible to put the module into a variable to isolate it.
1 with(banana('database.chimpdb'))
2 with(banana('markup.data.yaml')) {
3 var io = banana('io');
4
5 var config = YAML.parse((new io.File('config.yml')).content);
6
7 var db = new ChimpDB(config.db);
8
9 // program code
10
11 }
Note that bananas are gc-able. A banana may completely disappear after it is no longer being referenced by anything. However if a banana is already loaded then a banana() call to the same banana will return the same loaded banana. This is done by storing loaded bananas inside of a GCHash (Garbage Collectible Hash) which references values using the unique id of the banana's object instead of the object itself and looks the object up when asked for it. For reference an example:
1 // We're assuming that Object.__getID__ returns the absolute unique ID of an object.
2 with(banana('native')) { // assuming native is a module which gives access to things like the GC
3
4 var ioA = banana('io');
5 var ioB = banana('io');
6 var idA = Object.__getID__(ioA);
7 var idB = Object.__getID__(ioB);
8
9 print( 'ioA == ioB : ', idA === idB ); // true; Banana stays in memory because it is in use
10
11 ioA = ioB = null; // Remove all references to the banana so the GC will get rid of it
12 GC.run(true); // Force the GC to be run making the io module disappear
13
14 var ioC = banana('io');
15 var idC = Object.__getID__(ioC);
16
17 print( 'ioA == ioC : ', idC === idC ); // false; Banana disappeared from memory, a new one has been created
18
19 }
banana()
The banana() function is responsible for loading bananas. banana() takes a single argument to it, the full namespace of a banana module with a little bit of extra syntax (defined below). When called it will return the object exported from the banana module or throw a LoadError if it was unable to load the banana, normally due to it not existing.
You may also use banana.info() which given the same argument as banana() will return the metadata for a banana (or undefined if not found) instead of the banana object (ie: it won't be loaded) so you can query for information like the display name of the banana or the location of it's files.
Format
The argument given to banana() is the full dot separated namespace for the banana, additional requirements may be specified using a special syntax, namely support for the loading with a version limitation is possible.
1 banana('client.framework.jquery[v=1.2.6]') // specifically load version 1.2.6 of jQuery
2 banana('client.framework.jquery[v>=1.2.6]') // jQuery should be 1.2.6 or newer
3 banana('client.framework.jquery[v>=1.2.6][v<1.3.0]') // jQuery should be 1.2.6 or newer, but older than 1.3.0
4 banana('client.framework.jquery[1.2.6<=v<1.3.0]') // same as above but using combined notation
5 banana('client.framework.jquery[v=1.2]') // load the newest version of jQuery 1.2.*
6 banana('client.framework.jquery[v=1]') // load the newest version of jQuery 1.*.*
7 banana('client.framework.jquery') // just load the newest version of jQuery we have installed
8
Registries
Registries are json files defining a compiled form of metadata in modules. Registry files are used by the banana system to find the location of bananas to be loaded.
Behind the scenes the various install, update, etc... functions of the banana system which deal in installing remote packages into a central repository run commands to compile those installed bananas into a central registry file. That registry file is the only registry in the list by default, however you can add your own into the list using your own rc scripts.
You are not limited to the central registry, though it is the primary thing most users will make use of. It's possible to use banana to compile your own registries from other lists of bananas such as stuff you installed on your own, stuff another packaging system installed, projects you are developing on your own, or copies with local modifications in case you need to patch a library. From there you can use rc scripts to add those registries to the lists and have bananas defined inside them become possible to load.
There are two lists where registry locations may be defined. banana.registries.local and banana.registries.global. Normally only the global registry is used, this is where the central banana installed registry, package system installed registries, and registries of things you've installed on your own reside. The local registry is a special type of registry list. local registries are meant for when you've copied global bananas to a local area so you can patch them, or have a banana you are developing. bananas defined in local registries are always used if there is one of a version that matches what is asked for. This means that if you have the banana client.framework.jquery installed with versions 1.2.6 and 1.3.2, and have a potentially modified 1.2.6 defined in a local registry and just ask for client.framework.jquery then that 1.2.6 in the local registry will always be used even though there is a newer 1.3.2 defined in a global registry.
`banana`
The banana binary is used for all forms of installation, searching, and building of repositories of bananas.
banana info <shortname|namespace>
Show metadata about the banana.
#!console
1 $ banana info jquery
2 Name: jQuery
3 Short: jquery
4 Namespace(s):
5 - client.framework.jquery
6 - com.jquery
7 Versions installed:
8 - 1.3.2 (@bananas.monkeyscript.org)
9 - 1.2.6 [v=1.2] (@bananas.monkeyscript.org)
banana search <pattern>
Search for a module that can be installed. This looks for a regex match based on the shortname and name of the banana (should we do namespace to?).
#!console
1 $ banana search db|database
2 database.sqlite: SQLite
3 database.chimpdb: ChimpDB
4 database.couchdb: CouchDB
banana install <shortname|namespace>
Installs a banana from a remote location. If a shortname is used and multiple bananas of the same shortname are found then banana will instead print out a list of matching bananas with their primary namespace so that banana install can be run again using a specific namespace.
The argument specifying a banana to banana install can use the same version filtering format to the one use in banana().
((Other than jQuery these packages probably won't exist, they're just here to show of handling of ambiguity))
#!console
1 $ banana install jquery
2 Your cached package lists were last updated 3 days ago, fetching updated lists
3 Installing package client.framework.jquery v1.3.2 found at bananas.monkeyscript.org
4 $ banana install spidermonkey
5 Your cached package lists were last updated 1 minute ago.
6 2 packages matched the shortname spidermonkey
7 please use `banana install <namespace>` using one of the namespaces below to install "spidermonkey"
8
9 language.javascript.spidermonkey: SpiderMonkey JavaScript Engine
10 species.animal.primate.spidermonkey: Spider monkey
Version filtered install commands are stored individually and not considered the same as each other or a non filtered install command. banana also installs separate versions in separate locations per version. So if you banana install jquery and get jQuery 1.2.6 then later banana upgrade jquery and are upgraded to 1.3.2 banana will install these separately in separate 1.2.6 and 1.3.2 folders, initially the upgrade will leave the 1.2.6 on the filesystem but if there are no install requirements that need it to stay it may be cleaned up later.
For example if you banana install jquery and get 1.2.3 later banana install jquery[v=1.2] and get 1.2.6 and later banana install jquery[v=1.3] and get 1.3.0, then later banana upgrade jquery installing v=1.3.2 you will have the install definitions ["client.framework.jquery", "client.framework.jquery[v=1.2]", "client.framework.jquery[v=1.3]"] and have 1.2.3, 1.2.6, 1.3.0, and 1.3.2 installed in your system. Running a periodic cleanup will remove 1.2.3 and 1.3.0 because client.framework.jquery[v=1.2] requires 1.2.6 while both client.framework.jquery[v=1.3] and client.framework.jquery match 1.3.2 and keep them installed.
banana list {repos|upgrades}
List various types of information.
banana list repos
List remote repositories banana is configured to look for new packages in.
#!console
1 $ banana list repos
2 #1 bananas.monkeyscript.org
3 #2 bananas.chimpdb.org
4 #3 bananas.apache.org
banana list upgrades
List banana packages which have a newer version available for upgrade.
#!console
1 $ banana list upgrades
2 Your cached package lists were last updated 5 minutes ago.
3
4 Normally upgradeable packages:
5 client.framework.jquery v1.3.0 -> v1.3.2; (@bananas.monkeyscript.org)
6 client.framework.jquery[v=1.2] v1.2.3 -> v1.2.6; (@bananas.monkeyscript.org)
7 database.chimpdb v0.0.3 -> v0.0.9; (@bananas.chimpdb.org)
8
9 Packages with newer versions in a more preferred repository
10 database.chimpdb v0.0.3 -> v0.1.2; (@bananas.chimpdb.org) -> (@bananas.monkeyscript.org)
11
12 Packages with newer versions is a less preferred repository
13 database.couchdb v0.8.1 -> v0.9.0; (@bananas.monkeyscript.org) -> (@bananas.apache.org)
banana upgrade [shortname|namespace]
Upgrade banana packages which have gone out of date.
banana bootstrap [namespace|path]
Bootstrap a banana found in the local registry for use inside of banana itself. namespace must be a valid namespace for a banana which banana has a use for bootstrapping. Currently this is only markup.data.yaml, protocol.banana (protocol.monkeyscript.banana?), and misc.version.banana (this one might need to be bootstrapped by path first). Or you may omit namespace to bootstrap all bananas useful for bootstrapping that are installed.
banana fork <path> <banana> [banana] ...
Clone the source of a number of installed bananas on the system to another path. This is useful for cases like enterprise systems or development where it's possible you may need to modify the source code of a library because of a bug or missing feature whether or not that feature may be released in a future version of the library. In this case you can simply use banana fork to create copies of the libraries your library depends on, compile a registry file for them, then use a local rc script to shift that registry on the local registry list thus overriding libraries with local ones which can be modified and patched.
banana.json
A banana.json file is used to define the metadata for a banana package. The banana.json file itself is located in the root of an installed library and paths inside are relative to the banana.json file.
While the metadata file is named banana.json and only JSON support is enabled initially by default a banana metadata file can technically be in any format that can be decoded into a hierarchical JS object. Support for YAML is planned and Bootstrapping will be used so that banana can bootstrap a markup.data.yaml library into itself and allow any later library to define a banana.yml instead of a banana.json meta file.
Name and Version
The keys name, shortname, and namespace are required to be defined in the banana metadata file. The version is also highly recommended but can be omitted, it should only be omitted in meta packages and packages which should not be getting any new releases after the first one. These pairs are accessible to the metadata object inside JS.
1 {
2 "name": "Embedded JavaScript",
3 "shortname": "ejs",
4 "namespace": "templating.ejs",
5 ...
Project and Author info
The keys description, license, authors (author or authors may be used) describe data about the project itself. Description is a simple text description of the banana. License is a short license string, banana keeps a registry of them. Author(s) is a single author or an array of authors. Authors can be described using a plaintext string, or as an object containing the optional keys name (Author's name), email (e-mail address for the author), and url|site (The author's website, either key works).
1 ...
2 "description": "Templating by embedding JS into templates similarly to ERB/Erubis and PHP.",
3 "license": "LGPLv3",
4 "author": { "name": "Daniel Friesen", "email": "daniel@monkeyscript.org", "url": "http://daniel.friesen.name" },
5 ...
Components
Dependency
Two types of dependency are defined, requires and recommends. Requires is a list of bananas which are required, while recommends is a list of modules the banana can optionally depend on to add more features.
The format of the value for both keys is a recursive and/or list. The first level is and, ors may be nested, with ands inside. Thus ["bananaA", "bananaB"] means "require bananaA and bananaB." ["bananaA", ["bananaB", "bananaC"]] means "require bananaA and require either bananaB or bananaC with preference to bananaB" ["bananaA", [["bananaB, "bananaC"], "bananaD"]] means "require bananaA and either require bananaB and bananaC or require bananaD instead of those two. a simple { "require": "bananaA" } is a simple form for { "require": ["bananaA"] }.
This leaves room for special meta bananas. For example, markup.data.yaml could rather than being a yaml implementation itself be an empty banana which requires an OR list of possible YAML libraries to use which all share the same API. For example a libyaml library and a jsyaml library, both that support YAML.parse and YAML.stringify and can be used in place of each other. This metadata file would allow people to use banana install yaml and be prompted whether they want to install the libyaml variant or jsyaml variant.
1 {
2 "name": "YAML - YAML is not A Markup Language",
3 "shortname": "yaml",
4 "namespace": "markup.data.yaml",
5 "require": [
6 ["markup.data.yaml.libyaml", "markup.data.yaml.jsyaml"]
7 ]
8 }
banana does not automatically put required things into the scope of the script files. After much thought and ugly ideas for how to describe "I want io to be put in an io variable so I can do io.File" I've opted for using the metadata a formal declaration of what to require, and do the actual inclusion inside of the script itself using the same standard banana methods. The difference is that the banana() inside of a module's scope is a subset that will only allow you to include bananas defined as required or recommended inside the metadata. This is done to discourage the flat use of banana() to include something using while inside module scope resulting in the banana system running it not knowing anything about dependencies and what should be installed.
Stale
This section stale. The c key will likely be reworked and the submodules and file definitions will likely be reworked a little now that I have actually looked at the ASDF documentation and some advantages to a different format presented by defsystem, namely components.
The parsed banana meta object contains a number of hierarchical pairs, metadata if set on a banana variable would be something like:
banana.name
The display name of the banana.
banana.shortname
The identifying shortname of the banana.
banana.namespace
The full namespace identification of the banana (formerly shortname was "id" and namespace was the namespace to prefix the id with).
banana.version
The version of the banana.
banana.js
JavaScript files to load. This may be a string for a single file to load, or an array of files. If an array is used their scope is roughly the same as if you had used on file and used exec() to load the other files from there making use of the banana's relative path. When loaded the banana system itself will convert the string form into a single item array for consistency.
banana.c
A list of dynamic libraries to load. These are used for `_native` or may potentially be standalone. On UNIX systems the name is expanded to libmonkeyscript_???.so so if curl defined { "c": "curl" } then on UNIX a libmonkeyscript_curl.so would be loaded from an absolute path.
banana.depends
A string, array, or object of other bananas that are required. A string is treated like a single item array. Arrays are a list of bananas to with(). An object indicates a number of bananas as keys to load, if the value is "" it is for with() otherwise the value is the name of a key (variable) to put it into. (ie: { "markup.data.yaml": "", "io": "io" } gives access to YAML and io.File).
banana.submodules
An object containing submodules where each key is the extra identifier to add to the banana.namespace, and the the value is an object which may contain c and js keys.
If io were to define a File submodule (which was still part of the module (ie depends on the same c code), but could be loaded on it's own using with(banana('io.File')); Note that in this context whether you use banana('io'); or banana('io.File'); the class is still exports.File, thus in both cases the File object is imported. The difference between loading a module and a submodule is that some parts of the module are omitted and thus the exports may omit classes like Socket.
Module environment
Within the script (js) environment of files loaded by the banana module loader some extra bits of the environment are defined.
_native
The _native variable is a object which will be filled with anything exported by the C/C++ defined dynamic library for the module.
self
The metadata object for the module. This contains extra information like self.path which can be used to include other files in the library.
exports
An a free variable containing an object of pairs to export. Unlike ServerJS this is a free variable, not a reference to an object. So you are free to use exports = { foo: function() 'bar' }; rather than being limited to only using exports.foo = function() 'bar';. Just remember that if you do so you can't do that in every file in your library cause you'll just overwrite your own exports object.