1. Matthew Schinckel
  2. garmin-plugin

Commits

Matthew Schinckel  committed f052e47

Start documenting.
Add higher-level events.

  • Participants
  • Parent commits 6bedff6
  • Branches default

Comments (0)

Files changed (9)

File README.md

View file
  • Ignore whitespace
+# Garmin Communicator for jQuery
 
+The Garmin Communicator plugin is pretty nice. It allows you to upload/download data to/from your Garmin Heart Rate monitor from a website. As well as Garmin Connect, lots of other sites use it, including Strava, my new favourite fitness tracking site.
+
+One thing that Strava doesn't do is Workouts, which are training plans, and can be scheduled on a compatible HRM, so that you can start a specific workout on a given day without having to navigate through the menus to find it. Workouts can be complicated combinations of rules, like:
+
+  * run for 10 mins, keeping pace below 5:00/km
+  * run for 4 km, keeping HR within zone 2
+  * rest until I press the button
+  
+and so on. I am in the process of writing a web app that allows for creation of these. I know Garmin Connect can do it, but it's a fun exercise. Being able to push the data from my web app to my HRM is kind-of useful.
+
+So, whilst the Communicator plugin is good, the JavaScript API for it is pretty ordinary. For starters, it requires Prototype, and I'm strictly a jQuery guy. I know you can use both, but that's only the start of it.
+
+To use the plugin, you need to include about a dozen JS files. Then, you have to do all of this crazy crap that just doesn't seem necessary. They have their own Broadcaster, which seems to do PubSub, and a couple of libraries for detecting the browser and if the plugin is installed. Then there is the heirarchy of DevicePlugin, DeviceControl and DeviceDisplay.
+
+Anyway, the more I looked through the code, the more I thought it was needlessly complicated. Perhaps it's been written by Java guys.
+
+So, welcome to the replacement.
+
+## Installation
+
+    <script src="jquery.js"></script>
+    <script src="underscore.js"></script>
+    <script src="garmin.js"></script>
+
+I'm currently using underscore.js, as it gives a nicer `.each()` function, and provides `_.union()` for arrays. That may change, as less dependencies is better.
+
+## Usage
+
+    <script>
+      $(function(){
+        // Put the listeners you want in this.
+        var delegate = {};
+        var g = Garmin.Communicator(delegate, true);
+      })
+    </script>
+
+This will inject the plugin into the page if it isn't already there, create a new instance of the `Communicator` controller, and (if the second argument is true), start looking for devices right away.
+
+Your delegate's attributes that match the various events are then called when an event occurs. You can also manually set listeners for the events like `$(document).on('onFinishReadFromDevice', handler)`. The handler will recieve a jQuery Event object, and depending upon the event, there may also be a `data` object. Event.data contains a reference to the controller object.
+
+## Events
+
+### onStartFindDevices
+
+Called before the search for devices occurs.
+

File doc/docs.css

View file
  • Ignore whitespace
+body {
+    font-family: palatino, palatino linotype, georgia, serif;
+    font-size: 12pt;
+    text-align: center;
+}
+
+.popup-enabled {
+    cursor: pointer;
+    border-bottom: dotted 1px black;
+}
+
+.popup {
+    position: absolute;
+    z-index: 100;
+}
+
+.popup .item {
+    cursor: pointer;
+    font-family: helvetica neue, arial, sans-serif;
+    background-color: #f0f0f0;
+    color: black;
+    padding: 3px;
+    border: black dotted;
+    border-width: 1px 1px 0 1px;
+}
+
+.popup .selected {
+    background-color: black;
+    color: white;
+}
+
+.popup .bottom {
+    border-bottom: 1px black dotted;
+}
+
+#content {
+    text-align: left;
+    margin: 0 auto;
+}
+
+.documentation {
+    float: left;
+    line-height: 1.4em;
+}
+
+.documentation h1, .documentation h2 {
+    font-weight: normal;
+    line-height: 1.4em;
+}
+
+
+.documentation pre {
+    font-family: Inconsolata-dz, monaco, consolas, andale mono, monospace;
+    font-size: 10pt;
+    line-height: 1.4em;
+    overflow-x: scroll;
+}
+
+.documentation h1, .documentation h2, .documentation h3, .documentation h4 {
+  border-bottom: 2px solid #f0f0f0;
+}
+.code {
+    font-family: Inconsolata-dz, monaco, consolas, andale mono, monospace;
+    float: left;
+    white-space: pre;
+    font-size: 10pt;
+    background-color: #f0f0f0;
+    margin: 0;
+}
+
+.divider {
+    clear: both;
+    height: 0;
+    line-height: 0;
+}
+
+.intra-wiki {
+    color: black;
+    text-decoration: none;
+    border-bottom: solid 1px gray;
+}
+
+.highlight { 
+    background-color: yellow;
+}
+
+a {
+    color: black;
+    text-decoration: underline;
+}

File doc/docs.js

View file
  • Ignore whitespace
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Ubiquity.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Atul Varma <atul@mozilla.com>
+ *   Sander Dijkhuis <sander.dijkhuis@gmail.com>
+ *   Alberto Santini <albertosantini@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// = App =
+//
+// This is the application that processes the code and lets the user
+// navigate through and read the documentation.
+
+var App = {
+};
+
+// ** {{{ App.trim() }}} **
+//
+// Returns {{{str}}} without whitespace at the beginning and the end.
+
+App.trim = function trim(str) {
+  return str.replace(/^\s+|\s+$/g,"");
+};
+
+// ** {{{ App.processors }}} **
+//
+// An array of user-defined processor functions.  They should take one
+// argument, the DOM node containing the documentation.  User-defined
+// processor functions are called after standard processing is done.
+
+App.processors = [];
+
+App.menuItems = {};   // Has a {label, urlOrCallback} dict for each keyword.
+
+// ** {{{ App.processCode() }}} **
+//
+// Splits {{{code}}} in documented blocks and puts them in {{{div}}}.
+// The used structure for each block is:
+// {{{
+// <div class="documentation"> (...) </div>
+// <pre class="code prettyprint"> (...) </pre>
+// <div class="divider"/>
+// }}}
+// Documentation is parsed using [[http://wikicreole.org/|Creole]].
+
+App.processCode = function processCode(code, div) {
+  var lines = code.replace(/\r\n/g,'\n').replace(/\r/g,'\n').split('\n');
+  var blocks = [];
+  var blockText = "";
+  var codeText = "";
+  var firstCommentLine;
+  var lastCommentLine;
+
+  function maybeAppendBlock() {
+    if (blockText)
+      blocks.push({text: blockText,
+                   lineno: firstCommentLine,
+                   numLines: lastCommentLine - firstCommentLine + 1,
+                   code: codeText});
+  }
+
+  jQuery.each(
+    lines,
+    function(lineNum) {
+      var line = this;
+      var isCode = true;
+      var isComment = (App.trim(line).indexOf("//") == 0);
+      if (isComment) {
+        var startIndex = line.indexOf("//");
+        var text = App.trim(line.slice(startIndex + 3));
+        if (lineNum == lastCommentLine + 1) {
+          blockText += text + "\n";
+          lastCommentLine += 1;
+          isCode = false;
+        } else if (text.charAt(0) == "=" || text.charAt(0) == "*" || text.match('^TODO:')) {
+          maybeAppendBlock();
+          firstCommentLine = lineNum;
+          lastCommentLine = lineNum;
+          blockText = text + "\n";
+          codeText = "";
+          isCode = false;
+        }
+      }
+      if (isCode)
+        codeText += line + "\r\n";
+    });
+  maybeAppendBlock();
+
+  var creole = new Parse.Simple.Creole(
+    {
+      forIE: document.all,
+      interwiki: {
+        WikiCreole: 'http://www.wikicreole.org/wiki/',
+        Wikipedia: 'http://en.wikipedia.org/wiki/'
+      },
+      linkFormat: ''
+    });
+
+  jQuery.each(
+    blocks,
+    function(i) {
+      var docs = $('<div class="documentation">');
+      $(docs).css(App.columnCss);
+      creole.parse(docs.get(0), this.text);
+      $(div).append(docs);
+      var code = $('<pre class="code prettyprint">');
+      $(code).css(App.columnCssCode);
+      code.text(this.code);
+      $(div).append(code);
+
+      // Make sure the block ends with a blank line to make it high enough.
+      // For IE8 an extra space is needed, because otherwise the \n is ignored.
+      // FIXME: This doesn't fix issue 13 in IE7 yet.
+      code.append('\n ');
+
+      var headingHeight = docs.find(':first-child', 'h1 h2, h3, h4, h3').height() * 2;
+      var docsSurplus = docs.height() - code.height() + 1;
+      if (docsSurplus > 0)
+        code.css({paddingBottom: docsSurplus + "px", paddingTop: headingHeight + "px"});
+
+      $(div).append('<div class="divider">');
+    });
+
+  // Run the user-defined processors.
+  jQuery.each(
+    App.processors,
+    function(i) {
+      App.processors[i]($(div).find(".documentation"));
+    });
+};
+
+// ** {{{ App.addMenuItem() }}} **
+//
+// Adds a menu item to the {{{element}}} DOM node showing the {{{label}}}
+// text.  If {{{urlOrCallback}}} is an URL, choosing the item causes a new
+// window to be opened with that URL.  If it's a function, it will be called
+// when choosing the item.
+//
+// If the node does not have a menu yet, one will be created.
+
+App.addMenuItem = function addMenuItem(element, label, urlOrCallback) {
+  var text = $(element).text();
+
+  if (!$(element).parent().hasClass("popup-enabled")) {
+    App.menuItems[text] = [];
+
+    $(element).wrap('<span class="popup-enabled"></span>');
+
+    $(element).mousedown(
+      function(evt) {
+        evt.preventDefault();
+        var popup = $('<div class="popup"></div>');
+
+        function addItemToPopup(label, urlOrCallback) {
+          var callback;
+          var menuItem = $('<div class="item"></div>');
+          menuItem.text(label);
+          function onOverOrOut() { $(this).toggleClass("selected"); }
+          menuItem.mouseover(onOverOrOut);
+          menuItem.mouseout(onOverOrOut);
+          if (typeof(urlOrCallback) == "string")
+            callback = function() {
+              window.open(urlOrCallback);
+            };
+          else
+            callback = urlOrCallback;
+          menuItem.mouseup(callback);
+          popup.append(menuItem);
+        }
+
+        jQuery.each(
+          App.menuItems[text],
+          function(i) {
+            var item = App.menuItems[text][i];
+            addItemToPopup(item.label, item.urlOrCallback);
+          });
+
+        popup.find(".item:last").addClass("bottom");
+
+        popup.css({left: evt.pageX + "px"});
+        $(window).mouseup(
+          function mouseup() {
+            popup.remove();
+            $(window).unbind("mouseup", mouseup);
+          });
+        $(this).append(popup);
+      });
+  }
+
+  App.menuItems[text].push({ label: label, urlOrCallback: urlOrCallback });
+};
+
+App.currentPage = null;
+
+App.pages = {};
+
+// ** {{{ App.navigate() }}} **
+//
+// Navigates to a different view if needed.  The appropriate view is
+// fetched from the URL hash.  If that is empty, the original page content
+// is shown.
+
+App.navigate = function navigate() {
+  var newPage;
+  if (window.location.hash)
+    newPage = window.location.hash.slice(1);
+  else
+    newPage = "overview";
+
+  if (App.currentPage != newPage) {
+    if (App.currentPage)
+      $(App.pages[App.currentPage]).hide();
+    if (!App.pages[newPage]) {
+      var newDiv = $("<div>");
+      newDiv.attr("name", newPage);
+      $("#content").append(newDiv);
+      App.pages[newPage] = newDiv;
+      jQuery.get(newPage,
+                 {},
+                 function(code) { 
+                   App.processCode(code, newDiv);                       
+                   prettyPrint();
+                 },
+                 "text");
+    }
+    $(App.pages[newPage]).show();
+    App.currentPage = newPage;
+  }
+};
+
+App.CHARS_PER_ROW = 80;
+
+App.initColumnSizes = function initSizes() {
+  // Get the width of a single monospaced character of code.
+  var oneCodeCharacter = $('<div class="code">M</div>');
+  $("#content").append(oneCodeCharacter);
+  App.charWidth = oneCodeCharacter.width();
+  App.columnWidth = App.charWidth * App.CHARS_PER_ROW;
+  $(oneCodeCharacter).remove();
+
+  // Dynamically determine the column widths and padding based on
+  // the font size.
+  var padding = App.charWidth * 2;
+  App.columnCss = {width: App.columnWidth,
+                   paddingLeft: padding*2,
+                  // paddingRight: padding
+                 };
+  App.columnCssCode = {width: App.columnWidth, paddingRight: padding, paddingLeft: padding}; 
+  $("#content").css({width: (App.columnWidth + padding*2) * 2});
+  $(".documentation").css(App.columnCss);
+  $(".code").css(App.columnCssCode);
+};
+
+$(window).ready(   
+  function() {      
+    App.pages["overview"] = $("#overview").get(0);
+    App.initColumnSizes();
+    window.setInterval(
+      function() { App.navigate(); },
+      100
+    );
+    App.navigate();       
+      
+    // Get the selected text in a cross-browser fashion      
+    function getSelectedText(){
+      if(window.getSelection){
+        return window.getSelection().toString();
+      } else if (document.getSelection) {
+        return document.getSelection();
+      } else if(document.selection){
+        return document.selection.createRange().text;
+      }
+    }
+    
+    // Double clicking on a word, it will be yellow highlighted 
+    // in the documentation and code section 
+    $("#content").bind("dblclick", function () {
+      var text = App.trim(getSelectedText());
+      if (text) {
+        jQuery("#content").removeHighlight().highlight(text);
+      }
+    });
+  });

File doc/index.html

View file
  • Ignore whitespace
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Garmin Communicator+</title>
+    <link rel="stylesheet" href="docs.css" type="text/css" media="screen" title="no title" charset="utf-8">
+    <link rel="stylesheet" href="prettify.css" type="text/css" media="screen" title="no title" charset="utf-8">
+  </head>
+  <body>
+    <div id="content">
+      <div id="overview" class="documentation" style="display: none;">
+        <h1>Garmin Communicator+</h1>
+        <p></p>
+        <ul>
+          <li><a href="#../src/garmin.js">Garmin.Communicator +</a></li>
+        </ul>
+      </div>
+    </div>
+    <script src="../vendor/jquery-1.7.2.js"></script>
+    <script src="wikicreole.js"></script>
+    <script src="prettify.js"></script>
+    <script src="docs.js"></script>
+    <script>
+    location.hash = '../src/garmin.js'
+    </script>
+  </body>
+</html>

File doc/prettify.css

View file
  • Ignore whitespace
+/* Pretty printing styles. Used with prettify.js. */
+
+.str { color: #080; }
+.kwd { color: #008; }
+.com { color: #800; }
+.typ { color: #606; }
+.lit { color: #066; }
+.pun { color: #660; }
+.pln { color: #000; }
+.tag { color: #008; }
+.atn { color: #606; }
+.atv { color: #080; }
+.dec { color: #606; }
+/* pre.prettyprint { padding: 2px; border: 1px solid #888; } */
+
+@media print {
+  .str { color: #060; }
+  .kwd { color: #006; font-weight: bold; }
+  .com { color: #600; font-style: italic; }
+  .typ { color: #404; font-weight: bold; }
+  .lit { color: #044; }
+  .pun { color: #440; }
+  .pln { color: #000; }
+  .tag { color: #006; font-weight: bold; }
+  .atn { color: #404; }
+  .atv { color: #060; }
+}

File doc/prettify.js

View file
  • Ignore whitespace
+(function(){
+var o=true,r=null,z=false;window.PR_SHOULD_USE_CONTINUATION=o;window.PR_TAB_WIDTH=8;window.PR_normalizedHtml=window.PR=window.prettyPrintOne=window.prettyPrint=void 0;window._pr_isIE6=function(){var N=navigator&&navigator.userAgent&&/\bMSIE 6\./.test(navigator.userAgent);window._pr_isIE6=function(){return N};return N};
+var aa="!",ba="!=",ca="!==",F="#",da="%",ea="%=",G="&",fa="&&",ja="&&=",ka="&=",H="(",la="*",ma="*=",na="+=",oa=",",pa="-=",qa="->",ra="/",sa="/=",ta=":",ua="::",va=";",I="<",wa="<<",xa="<<=",ya="<=",za="=",Aa="==",Ba="===",J=">",Ca=">=",Da=">>",Ea=">>=",Fa=">>>",Ga=">>>=",Ha="?",Ia="@",L="[",M="^",Ta="^=",Ua="^^",Va="^^=",Wa="{",O="|",Xa="|=",Ya="||",Za="||=",$a="~",ab="break",bb="case",cb="continue",db="delete",eb="do",fb="else",gb="finally",hb="instanceof",ib="return",jb="throw",kb="try",lb="typeof",
+mb="(?:^^|[+-]",nb="\\$1",ob=")\\s*",pb="&amp;",qb="&lt;",rb="&gt;",sb="&quot;",tb="&#",ub="x",vb="'",wb='"',xb=" ",yb="XMP",zb="</",Ab='="',P="",Q="\\",Bb="b",Cb="t",Db="n",Eb="v",Fb="f",Gb="r",Hb="u",Ib="0",Jb="1",Kb="2",Lb="3",Mb="4",Nb="5",Ob="6",Pb="7",Qb="\\x0",Rb="\\x",Sb="-",Tb="]",Ub="\\\\u[0-9A-Fa-f]{4}|\\\\x[0-9A-Fa-f]{2}|\\\\[0-3][0-7]{0,2}|\\\\[0-7]{1,2}|\\\\[\\s\\S]|-|[^-\\\\]",R="g",Vb="\\B",Wb="\\b",Xb="\\D",Yb="\\d",Zb="\\S",$b="\\s",ac="\\W",bc="\\w",cc="(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)",
+dc="(?:",ec=")",fc="gi",gc="PRE",hc='<!DOCTYPE foo PUBLIC "foo bar">\n<foo />',ic="\t",jc="\n",kc="[^<]+|<!--[\\s\\S]*?--\>|<!\\[CDATA\\[[\\s\\S]*?\\]\\]>|</?[a-zA-Z][^>]*>|<",lc="nocode",mc=' $1="$2$3$4"',S="pln",nc="string",T="lang-",oc="src",U="str",pc="'\"",qc="'\"`",rc="\"'",V="com",sc="lang-regex",tc="(/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/)",uc="kwd",vc="^(?:",wc=")\\b",xc=" \r\n\t\u00a0",yc="lit",zc="typ",Ac="0123456789",Y="pun",Bc="break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try alignof align_union asm axiom bool concept concept_map const_cast constexpr decltype dynamic_cast explicit export friend inline late_check mutable namespace nullptr reinterpret_cast static_assert static_cast template typeid typename typeof using virtual wchar_t where break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try boolean byte extends final finally implements import instanceof null native package strictfp super synchronized throws transient as base by checked decimal delegate descending event fixed foreach from group implicit in interface internal into is lock object out override orderby params partial readonly ref sbyte sealed stackalloc string select uint ulong unchecked unsafe ushort var break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try debugger eval export function get null set undefined var with Infinity NaN caller delete die do dump elsif eval exit foreach for goto if import last local my next no our print package redo require sub undef unless until use wantarray while BEGIN END break continue do else for if return while and as assert class def del elif except exec finally from global import in is lambda nonlocal not or pass print raise try with yield False True None break continue do else for if return while alias and begin case class def defined elsif end ensure false in module next nil not or redo rescue retry self super then true undef unless until when yield BEGIN END break continue do else for if return while case done elif esac eval fi function in local set then until ",
+Cc="</span>",Dc='<span class="',Ec='">',Fc="$1&nbsp;",Gc="&nbsp;<br />",Hc="<br />",Ic="console",Jc="cannot override language handler %s",Kc="default-markup",Lc="default-code",Mc="dec",Z="lang-js",$="lang-css",Nc="lang-in.tag",Oc="htm",Pc="html",Qc="mxml",Rc="xhtml",Sc="xml",Tc="xsl",Uc=" \t\r\n",Vc="atv",Wc="tag",Xc="atn",Yc="lang-uq.val",Zc="in.tag",$c="uq.val",ad="break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try alignof align_union asm axiom bool concept concept_map const_cast constexpr decltype dynamic_cast explicit export friend inline late_check mutable namespace nullptr reinterpret_cast static_assert static_cast template typeid typename typeof using virtual wchar_t where ",
+bd="c",cd="cc",dd="cpp",ed="cxx",fd="cyc",gd="m",hd="null true false",id="json",jd="break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try boolean byte extends final finally implements import instanceof null native package strictfp super synchronized throws transient as base by checked decimal delegate descending event fixed foreach from group implicit in interface internal into is lock object out override orderby params partial readonly ref sbyte sealed stackalloc string select uint ulong unchecked unsafe ushort var ",
+kd="cs",ld="break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try boolean byte extends final finally implements import instanceof null native package strictfp super synchronized throws transient ",md="java",nd="break continue do else for if return while case done elif esac eval fi function in local set then until ",
+od="bsh",pd="csh",qd="sh",rd="break continue do else for if return while and as assert class def del elif except exec finally from global import in is lambda nonlocal not or pass print raise try with yield False True None ",sd="cv",td="py",ud="caller delete die do dump elsif eval exit foreach for goto if import last local my next no our print package redo require sub undef unless until use wantarray while BEGIN END ",vd="perl",wd="pl",xd="pm",yd="break continue do else for if return while alias and begin case class def defined elsif end ensure false in module next nil not or redo rescue retry self super then true undef unless until when yield BEGIN END ",
+zd="rb",Ad="break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try debugger eval export function get null set undefined var with Infinity NaN ",Bd="js",Cd="regex",Dd="pre",Ed="code",Fd="xmp",Gd="prettyprint",Hd="class",Id="br",Jd="\r";
+(function(){var N=function(){for(var a=[aa,ba,ca,F,da,ea,G,fa,ja,ka,H,la,ma,na,oa,pa,qa,ra,sa,ta,ua,va,I,wa,xa,ya,za,Aa,Ba,J,Ca,Da,Ea,Fa,Ga,Ha,Ia,L,M,Ta,Ua,Va,Wa,O,Xa,Ya,Za,$a,ab,bb,cb,db,eb,fb,gb,hb,ib,jb,kb,lb],b=mb,c=0;c<a.length;++c)b+=O+a[c].replace(/([^=<>:&a-z])/g,nb);b+=ob;return b}(),Ja=/&/g,Ka=/</g,La=/>/g,Kd=/\"/g;function Ld(a){return a.replace(Ja,pb).replace(Ka,qb).replace(La,rb).replace(Kd,sb)}function ga(a){return a.replace(Ja,pb).replace(Ka,qb).replace(La,rb)}var Md=/&lt;/g,Nd=/&gt;/g,
+Od=/&apos;/g,Pd=/&quot;/g,Qd=/&amp;/g,Rd=/&nbsp;/g;function Sd(a){var b=a.indexOf(G);if(b<0)return a;for(--b;(b=a.indexOf(tb,b+1))>=0;){var c=a.indexOf(va,b);if(c>=0){var d=a.substring(b+3,c),g=10;if(d&&d.charAt(0)===ub){d=d.substring(1);g=16}var i=parseInt(d,g);isNaN(i)||(a=a.substring(0,b)+String.fromCharCode(i)+a.substring(c+1))}}return a.replace(Md,I).replace(Nd,J).replace(Od,vb).replace(Pd,wb).replace(Qd,G).replace(Rd,xb)}function Ma(a){return yb===a.tagName}function W(a,b){switch(a.nodeType){case 1:var c=
+a.tagName.toLowerCase();b.push(I,c);for(var d=0;d<a.attributes.length;++d){var g=a.attributes[d];if(g.specified){b.push(xb);W(g,b)}}b.push(J);for(var i=a.firstChild;i;i=i.nextSibling)W(i,b);if(a.firstChild||!/^(?:br|link|img)$/.test(c))b.push(zb,c,J);break;case 2:b.push(a.name.toLowerCase(),Ab,Ld(a.value),wb);break;case 3:case 4:b.push(ga(a.nodeValue));break}}function Na(a){for(var b=0,c=z,d=z,g=0,i=a.length;g<i;++g){var m=a[g];if(m.ignoreCase)d=o;else if(/[a-z]/i.test(m.source.replace(/\\u[0-9a-f]{4}|\\x[0-9a-f]{2}|\\[^ux]/gi,
+P))){c=o;d=z;break}}function l(j){if(j.charAt(0)!==Q)return j.charCodeAt(0);switch(j.charAt(1)){case Bb:return 8;case Cb:return 9;case Db:return 10;case Eb:return 11;case Fb:return 12;case Gb:return 13;case Hb:case ub:return parseInt(j.substring(2),16)||j.charCodeAt(1);case Ib:case Jb:case Kb:case Lb:case Mb:case Nb:case Ob:case Pb:return parseInt(j.substring(1),8);default:return j.charCodeAt(1)}}function n(j){if(j<32)return(j<16?Qb:Rb)+j.toString(16);var f=String.fromCharCode(j);if(f===Q||f===Sb||
+f===L||f===Tb)f=Q+f;return f}function q(j){for(var f=j.substring(1,j.length-1).match(new RegExp(Ub,R)),s=[],k=[],h=f[0]===M,e=h?1:0,p=f.length;e<p;++e){var t=f[e];switch(t){case Vb:case Wb:case Xb:case Yb:case Zb:case $b:case ac:case bc:s.push(t);continue}var u=l(t),x;if(e+2<p&&Sb===f[e+1]){x=l(f[e+2]);e+=2}else x=u;k.push([u,x]);if(!(x<65||u>122)){x<65||u>90||k.push([Math.max(65,u)|32,Math.min(x,90)|32]);x<97||u>122||k.push([Math.max(97,u)&-33,Math.min(x,122)&-33])}}k.sort(function(Oa,Pa){return Oa[0]-
+Pa[0]||Pa[1]-Oa[1]});var B=[],E=[NaN,NaN];for(e=0;e<k.length;++e){var A=k[e];if(A[0]<=E[1]+1)E[1]=Math.max(E[1],A[1]);else B.push(E=A)}var D=[L];h&&D.push(M);D.push.apply(D,s);for(e=0;e<B.length;++e){A=B[e];D.push(n(A[0]));if(A[1]>A[0]){A[1]+1>A[0]&&D.push(Sb);D.push(n(A[1]))}}D.push(Tb);return D.join(P)}function v(j){var f=j.source.match(new RegExp(cc,R)),s=f.length,k=[],h,e=0;for(h=0;e<s;++e){var p=f[e];if(p===H)++h;else if(Q===p.charAt(0)){var t=+p.substring(1);if(t&&t<=h)k[t]=-1}}for(e=1;e<k.length;++e)if(-1===
+k[e])k[e]=++b;for(h=e=0;e<s;++e){p=f[e];if(p===H){++h;if(k[h]===undefined)f[e]=dc}else if(Q===p.charAt(0))if((t=+p.substring(1))&&t<=h)f[e]=Q+k[h]}for(h=e=0;e<s;++e)if(M===f[e]&&M!==f[e+1])f[e]=P;if(j.ignoreCase&&c)for(e=0;e<s;++e){p=f[e];var u=p.charAt(0);if(p.length>=2&&u===L)f[e]=q(p);else if(u!==Q)f[e]=p.replace(/[a-zA-Z]/g,function(x){var B=x.charCodeAt(0);return L+String.fromCharCode(B&-33,B|32)+Tb})}return f.join(P)}var w=[];g=0;for(i=a.length;g<i;++g){m=a[g];if(m.global||m.multiline)throw new Error(P+
+m);w.push(dc+v(m)+ec)}return new RegExp(w.join(O),d?fc:R)}var ha=r;function Td(a){if(r===ha){var b=document.createElement(gc);b.appendChild(document.createTextNode(hc));ha=!/</.test(b.innerHTML)}if(ha){var c=a.innerHTML;if(Ma(a))c=ga(c);return c}for(var d=[],g=a.firstChild;g;g=g.nextSibling)W(g,d);return d.join(P)}function Ud(a){var b=0;return function(c){for(var d=r,g=0,i=0,m=c.length;i<m;++i){var l=c.charAt(i);switch(l){case ic:d||(d=[]);d.push(c.substring(g,i));var n=a-b%a;for(b+=n;n>=0;n-="                ".length)d.push("                ".substring(0,
+n));g=i+1;break;case jc:b=0;break;default:++b}}if(!d)return c;d.push(c.substring(g));return d.join(P)}}var Vd=new RegExp(kc,R),Wd=/^<\!--/,Xd=/^<\[CDATA\[/,Yd=/^<br\b/i,Qa=/^<(\/?)([a-zA-Z]+)/;function Zd(a){var b=a.match(Vd),c=[],d=0,g=[];if(b)for(var i=0,m=b.length;i<m;++i){var l=b[i];if(l.length>1&&l.charAt(0)===I){if(!Wd.test(l))if(Xd.test(l)){c.push(l.substring(9,l.length-3));d+=l.length-12}else if(Yd.test(l)){c.push(jc);++d}else if(l.indexOf(lc)>=0&&$d(l)){var n=l.match(Qa)[2],q=1,v;v=i+1;a:for(;v<
+m;++v){var w=b[v].match(Qa);if(w&&w[2]===n)if(w[1]===ra){if(--q===0)break a}else++q}if(v<m){g.push(d,b.slice(i,v+1).join(P));i=v}else g.push(d,l)}else g.push(d,l)}else{var j=Sd(l);c.push(j);d+=j.length}}return{source:c.join(P),tags:g}}function $d(a){return!!a.replace(/\s(\w+)\s*=\s*(?:\"([^\"]*)\"|'([^\']*)'|(\S+))/g,mc).match(/[cC][lL][aA][sS][sS]=\"[^\"]*\bnocode\b/)}function ia(a,b,c,d){if(b){var g={source:b,b:a};c(g);d.push.apply(d,g.c)}}function K(a,b){var c={},d;(function(){for(var m=a.concat(b),
+l=[],n={},q=0,v=m.length;q<v;++q){var w=m[q],j=w[3];if(j)for(var f=j.length;--f>=0;)c[j.charAt(f)]=w;var s=w[1],k=P+s;if(!n.hasOwnProperty(k)){l.push(s);n[k]=r}}l.push(/[\0-\uffff]/);d=Na(l)})();var g=b.length,i=function(m){for(var l=m.source,n=m.b,q=[n,S],v=0,w=l.match(d)||[],j={},f=0,s=w.length;f<s;++f){var k=w[f],h=j[k],e,p;if(typeof h===nc)p=z;else{var t=c[k.charAt(0)];if(t){e=k.match(t[1]);h=t[0]}else{for(var u=0;u<g;++u){t=b[u];if(e=k.match(t[1])){h=t[0];break}}e||(h=S)}if((p=h.length>=5&&T===
+h.substring(0,5))&&!(e&&e[1])){p=z;h=oc}p||(j[k]=h)}var x=v;v+=k.length;if(p){var B=e[1],E=k.indexOf(B),A=E+B.length,D=h.substring(5);ia(n+x,k.substring(0,E),i,q);ia(n+x+E,B,Ra(D,B),q);ia(n+x+A,k.substring(A),i,q)}else q.push(n+x,h)}m.c=q};return i}function C(a){var b=[],c=[];if(a.tripleQuotedStrings)b.push([U,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,r,pc]);
+else a.multiLineStrings?b.push([U,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,r,qc]):b.push([U,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,r,rc]);if(a.hashComments)a.cStyleComments?b.push([V,/^#(?:[^\r\n\/]|\/(?!\*)|\/\*[^\r\n]*?\*\/)*/,r,F]):b.push([V,/^#[^\r\n]*/,r,F]);if(a.cStyleComments){c.push([V,/^\/\/[^\r\n]*/,r]);c.push([V,/^\/\*[\s\S]*?(?:\*\/|$)/,r])}a.regexLiterals&&c.push([sc,new RegExp(M+N+tc)]);var d=
+a.keywords.replace(/^\s+|\s+$/g,P);d.length&&c.push([uc,new RegExp(vc+d.replace(/\s+/g,O)+wc),r]);b.push([S,/^\s+/,r,xc]);c.push([yc,/^@[a-z_$][a-z_$@0-9]*/i,r,Ia],[zc,/^@?[A-Z]+[a-z][A-Za-z_$@0-9]*/,r],[S,/^[a-z_$][a-z_$@0-9]*/i,r],[yc,/^(?:0x[a-f0-9]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+\-]?\d+)?)[a-z]*/i,r,Ac],[Y,/^.[^\s\w\.$@\'\"\`\/\#]*/,r]);return K(b,c)}var ae=C({keywords:Bc,hashComments:o,cStyleComments:o,multiLineStrings:o,regexLiterals:o});function be(a){var b=a.source,c=a.f,d=a.c,
+g=[],i=0,m=r,l=r,n=0,q=0,v=Ud(window.PR_TAB_WIDTH),w=/([\r\n ]) /g,j=/(^| ) /gm,f=/\r\n?|\n/g,s=/[ \r\n]$/,k=o;function h(p){if(p>i){if(m&&m!==l){g.push(Cc);m=r}if(!m&&l){m=l;g.push(Dc,m,Ec)}var t=ga(v(b.substring(i,p))).replace(k?j:w,Fc);k=s.test(t);var u=window._pr_isIE6()?Gc:Hc;g.push(t.replace(f,u));i=p}}for(;1;){var e;if(e=n<c.length?q<d.length?c[n]<=d[q]:o:z){h(c[n]);if(m){g.push(Cc);m=r}g.push(c[n+1]);n+=2}else if(q<d.length){h(d[q]);l=d[q+1];q+=2}else break}h(b.length);m&&g.push(Cc);a.a=g.join(P)}
+var X={};function y(a,b){for(var c=b.length;--c>=0;){var d=b[c];if(X.hasOwnProperty(d))Ic in window&&console.i(Jc,d);else X[d]=a}}function Ra(a,b){a&&X.hasOwnProperty(a)||(a=/^\s*</.test(b)?Kc:Lc);return X[a]}y(ae,[Lc]);y(K([],[[S,/^[^<?]+/],[Mc,/^<!\w[^>]*(?:>|$)/],[V,/^<\!--[\s\S]*?(?:-\->|$)/],[T,/^<\?([\s\S]+?)(?:\?>|$)/],[T,/^<%([\s\S]+?)(?:%>|$)/],[Y,/^(?:<[%?]|[%?]>)/],[T,/^<xmp\b[^>]*>([\s\S]+?)<\/xmp\b[^>]*>/i],[Z,/^<script\b[^>]*>([\s\S]+?)<\/script\b[^>]*>/i],[$,/^<style\b[^>]*>([\s\S]+?)<\/style\b[^>]*>/i],
+[Nc,/^(<\/?[a-z][^<>]*>)/i]]),[Kc,Oc,Pc,Qc,Rc,Sc,Tc]);y(K([[S,/^[\s]+/,r,Uc],[Vc,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,r,rc]],[[Wc,/^^<\/?[a-z](?:[\w:-]*\w)?|\/?>$/],[Xc,/^(?!style\b|on)[a-z](?:[\w:-]*\w)?/],[Yc,/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[Y,/^[=<>\/]+/],[Z,/^on\w+\s*=\s*\"([^\"]+)\"/i],[Z,/^on\w+\s*=\s*\'([^\']+)\'/i],[Z,/^on\w+\s*=\s*([^\"\'>\s]+)/i],[$,/^sty\w+\s*=\s*\"([^\"]+)\"/i],[$,/^sty\w+\s*=\s*\'([^\']+)\'/i],[$,/^sty\w+\s*=\s*([^\"\'>\s]+)/i]]),[Zc]);y(K([],[[Vc,/^[\s\S]+/]]),
+[$c]);y(C({keywords:ad,hashComments:o,cStyleComments:o}),[bd,cd,dd,ed,fd,gd]);y(C({keywords:hd}),[id]);y(C({keywords:jd,hashComments:o,cStyleComments:o}),[kd]);y(C({keywords:ld,cStyleComments:o}),[md]);y(C({keywords:nd,hashComments:o,multiLineStrings:o}),[od,pd,qd]);y(C({keywords:rd,hashComments:o,multiLineStrings:o,tripleQuotedStrings:o}),[sd,td]);y(C({keywords:ud,hashComments:o,multiLineStrings:o,regexLiterals:o}),[vd,wd,xd]);y(C({keywords:yd,hashComments:o,multiLineStrings:o,regexLiterals:o}),
+[zd]);y(C({keywords:Ad,cStyleComments:o,regexLiterals:o}),[Bd]);y(K([],[[U,/^[\s\S]+/]]),[Cd]);function Sa(a){var b=a.e,c=a.d;a.a=b;try{var d=Zd(b),g=d.source;a.source=g;a.b=0;a.f=d.tags;Ra(c,g)(a);be(a)}catch(i){if(Ic in window){console.log(i);console.h()}}}function ce(a,b){var c={e:a,d:b};Sa(c);return c.a}function de(a){for(var b=window._pr_isIE6(),c=[document.getElementsByTagName(Dd),document.getElementsByTagName(Ed),document.getElementsByTagName(Fd)],d=[],g=0;g<c.length;++g)for(var i=0,m=c[g].length;i<
+m;++i)d.push(c[g][i]);c=r;var l=Date;l.now||(l={now:function(){return(new Date).getTime()}});var n=0,q;function v(){for(var j=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;n<d.length&&l.now()<j;n++){var f=d[n];if(f.className&&f.className.indexOf(Gd)>=0){var s=f.className.match(/\blang-(\w+)\b/);if(s)s=s[1];for(var k=z,h=f.parentNode;h;h=h.parentNode)if((h.tagName===Dd||h.tagName===Ed||h.tagName===Fd)&&h.className&&h.className.indexOf(Gd)>=0){k=o;break}if(!k){var e=Td(f);e=e.replace(/(?:\r\n?|\n)$/,
+P);q={e:e,d:s,g:f};Sa(q);w()}}}if(n<d.length)setTimeout(v,250);else a&&a()}function w(){var j=q.a;if(j){var f=q.g;if(Ma(f)){for(var s=document.createElement(gc),k=0;k<f.attributes.length;++k){var h=f.attributes[k];if(h.specified){var e=h.name.toLowerCase();if(e===Hd)s.className=h.value;else s.setAttribute(h.name,h.value)}}s.innerHTML=j;f.parentNode.replaceChild(s,f);f=s}else f.innerHTML=j;if(b&&f.tagName===gc)for(var p=f.getElementsByTagName(Id),t=p.length;--t>=0;){var u=p[t];u.parentNode.replaceChild(document.createTextNode(Jd),
+u)}}}v()}window.PR_normalizedHtml=W;window.prettyPrintOne=ce;window.prettyPrint=de;window.PR={combinePrefixPatterns:Na,createSimpleLexer:K,registerLangHandler:y,sourceDecorator:C,PR_ATTRIB_NAME:Xc,PR_ATTRIB_VALUE:Vc,PR_COMMENT:V,PR_DECLARATION:Mc,PR_KEYWORD:uc,PR_LITERAL:yc,PR_NOCODE:lc,PR_PLAIN:S,PR_PUNCTUATION:Y,PR_SOURCE:oc,PR_STRING:U,PR_TAG:Wc,PR_TYPE:zc}})();
+})()

File doc/wikicreole.js

View file
  • Ignore whitespace
+/*
+ * JavaScript Creole 1.0 Wiki Markup Parser
+ * $Id: creole.js 14 2009-03-21 16:15:08Z ifomichev $
+ *
+ * Copyright (c) 2009 Ivan Fomichev
+ *
+ * Portions Copyright (c) 2007 Chris Purcell
+ *
+ * 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 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.
+ */
+
+if (!Parse) { var Parse = {}; }
+if (!Parse.Simple) { Parse.Simple = {}; }
+
+Parse.Simple.Base = function(grammar, options) {
+    if (!arguments.length) { return; }
+
+    this.grammar = grammar;
+    this.grammar.root = new this.ruleConstructor(this.grammar.root);
+    this.options = options;
+};
+
+Parse.Simple.Base.prototype = {
+    ruleConstructor: null,
+    grammar: null,
+    options: null,
+
+    parse: function(node, data, options) {
+        if (options) {
+            for (i in this.options) {
+                if (typeof options[i] == 'undefined') { options[i] = this.options[i]; }
+            }
+        }
+        else {
+            options = this.options;
+        }
+        data = data.replace(/\r\n?/g, '\n');
+        this.grammar.root.apply(node, data, options);
+        if (options && options.forIE) { node.innerHTML = node.innerHTML.replace(/\r?\n/g, '\r\n'); }
+    }
+};
+
+Parse.Simple.Base.prototype.constructor = Parse.Simple.Base;
+
+Parse.Simple.Base.Rule = function(params) {
+    if (!arguments.length) { return; }
+
+    for (var p in params) { this[p] = params[p]; }
+    if (!this.children) { this.children = []; }
+};
+
+Parse.Simple.Base.prototype.ruleConstructor = Parse.Simple.Base.Rule;
+
+Parse.Simple.Base.Rule.prototype = {
+    regex: null,
+    capture: null,
+    replaceRegex: null,
+    replaceString: null,
+    tag: null,
+    attrs: null,
+    children: null,
+
+    match: function(data, options) {
+        return data.match(this.regex);
+    },
+
+    build: function(node, r, options) {
+        var data;
+        if (this.capture !== null) {
+            data = r[this.capture];
+        }
+
+        var target;
+        if (this.tag) {
+            target = document.createElement(this.tag);
+            node.appendChild(target);
+        }
+        else { target = node; }
+
+        if (data) {
+            if (this.replaceRegex) {
+                data = data.replace(this.replaceRegex, this.replaceString);
+            }
+            this.apply(target, data, options);
+        }
+
+        if (this.attrs) {
+            for (var i in this.attrs) {
+                target.setAttribute(i, this.attrs[i]);
+                if (options && options.forIE && i == 'class') { target.className = this.attrs[i]; }
+            }
+        }
+        return this;
+    },
+
+    apply: function(node, data, options) {
+        var tail = '' + data;
+        var matches = [];
+
+        if (!this.fallback.apply) {
+            this.fallback = new this.constructor(this.fallback);
+        }
+
+        while (true) {
+            var best = false;
+            var rule  = false;
+            for (var i = 0; i < this.children.length; i++) {
+                if (typeof matches[i] == 'undefined') {
+                    if (!this.children[i].match) {
+                        this.children[i] = new this.constructor(this.children[i]);
+                    }
+                    matches[i] = this.children[i].match(tail, options);
+                }
+                if (matches[i] && (!best || best.index > matches[i].index)) {
+                    best = matches[i];
+                    rule = this.children[i];
+                    if (best.index == 0) { break; }
+                }
+            }
+                
+            var pos = best ? best.index : tail.length;
+            if (pos > 0) {
+                this.fallback.apply(node, tail.substring(0, pos), options);
+            }
+            
+            if (!best) { break; }
+
+            if (!rule.build) { rule = new this.constructor(rule); }
+            rule.build(node, best, options);
+
+            var chopped = best.index + best[0].length;
+            tail = tail.substring(chopped);
+            for (var i = 0; i < this.children.length; i++) {
+                if (matches[i]) {
+                    if (matches[i].index >= chopped) {
+                        matches[i].index -= chopped;
+                    }
+                    else {
+                        matches[i] = void 0;
+                    }
+                }
+            }
+        }
+
+        return this;
+    },
+
+    fallback: {
+        apply: function(node, data, options) {
+            if (options && options.forIE) {
+                // workaround for bad IE
+                data = data.replace(/\n/g, ' \r');
+            }
+            node.appendChild(document.createTextNode(data));
+        }
+    }    
+};
+
+Parse.Simple.Base.Rule.prototype.constructor = Parse.Simple.Base.Rule;
+
+Parse.Simple.Creole = function(options) {
+    var rx = {};
+    rx.link = '[^\\]|~\\n]*(?:(?:\\](?!\\])|~.)[^\\]|~\\n]*)*';
+    rx.linkText = '[^\\]~\\n]*(?:(?:\\](?!\\])|~.)[^\\]~\\n]*)*';
+    rx.uriPrefix = '\\b(?:(?:https?|ftp)://|mailto:)';
+    rx.uri = rx.uriPrefix + rx.link;
+    rx.rawUri = rx.uriPrefix + '\\S*[^\\s!"\',.:;?]';
+    rx.interwikiPrefix = '[\\w.]+:';
+    rx.interwikiLink = rx.interwikiPrefix + rx.link;
+    rx.img = '\\{\\{((?!\\{)[^|}\\n]*(?:}(?!})[^|}\\n]*)*)' +
+             (options && options.strict ? '' : '(?:') + 
+             '\\|([^}~\\n]*((}(?!})|~.)[^}~\\n]*)*)' +
+             (options && options.strict ? '' : ')?') +
+             '}}';
+
+    var formatLink = function(link, format) {
+        if (format instanceof Function) {
+            return format(link);
+        }
+
+        format = format instanceof Array ? format : [ format ];
+        if (typeof format[1] == 'undefined') { format[1] = ''; }
+        return format[0] + link + format[1];
+    };
+
+    var g = {
+        hr: { tag: 'hr', regex: /(^|\n)\s*----\s*(\n|$)/ },
+
+        br: { tag: 'br', regex: /\\\\/ },
+        
+        preBlock: { tag: 'pre', capture: 2,
+            regex: /(^|\n)\{\{\{\n((.*\n)*?)\}\}\}(\n|$)/,
+            replaceRegex: /^ ([ \t]*\}\}\})/gm,
+            replaceString: '$1' },
+        tt: { tag: 'tt',
+            regex: /\{\{\{(.*?\}\}\}+)/, capture: 1,
+            replaceRegex: /\}\}\}$/, replaceString: '' },
+
+        ulist: { tag: 'ul', capture: 0,
+            regex: /(^|\n)([ \t]*\*[^*#].*(\n|$)([ \t]*[^\s*#].*(\n|$))*([ \t]*[*#]{2}.*(\n|$))*)+/ },
+        olist: { tag: 'ol', capture: 0,
+            regex: /(^|\n)([ \t]*#[^*#].*(\n|$)([ \t]*[^\s*#].*(\n|$))*([ \t]*[*#]{2}.*(\n|$))*)+/ },
+        li: { tag: 'li', capture: 0,
+            regex: /[ \t]*([*#]).+(\n[ \t]*[^*#\s].*)*(\n[ \t]*\1[*#].+)*/,
+            replaceRegex: /(^|\n)[ \t]*[*#]/g, replaceString: '$1' },
+
+        table: { tag: 'table', capture: 0,
+            regex: /(^|\n)(\|.*?[ \t]*(\n|$))+/ },
+        tr: { tag: 'tr', capture: 2, regex: /(^|\n)(\|.*?)\|?[ \t]*(\n|$)/ },
+        th: { tag: 'th', regex: /\|+=([^|]*)/, capture: 1 },
+        td: { tag: 'td', capture: 1,
+            regex: '\\|+([^|~\\[{]*((~(.|(?=\\n)|$)|' +
+                   '\\[\\[' + rx.link + '(\\|' + rx.linkText + ')?\\]\\]' +
+                   (options && options.strict ? '' : '|' + rx.img) +
+                   '|[\\[{])[^|~]*)*)' },
+
+        singleLine: { regex: /.+/, capture: 0 },
+        paragraph: { tag: 'p', capture: 0,
+            regex: /(^|\n)([ \t]*\S.*(\n|$))+/ },
+        text: { capture: 0, regex: /(^|\n)([ \t]*[^\s].*(\n|$))+/ },
+
+        strong: { tag: 'strong', capture: 1,
+            regex: /\*\*([^*~]*((\*(?!\*)|~(.|(?=\n)|$))[^*~]*)*)(\*\*|\n|$)/ },
+        em: { tag: 'em', capture: 1,
+            regex: '\\/\\/(((?!' + rx.uriPrefix + ')[^\\/~])*' +
+                   '((' + rx.rawUri + '|\\/(?!\\/)|~(.|(?=\\n)|$))' +
+                   '((?!' + rx.uriPrefix + ')[^\\/~])*)*)(\\/\\/|\\n|$)' },
+
+        img: { regex: rx.img,
+            build: function(node, r, options) {
+                var img = document.createElement('img');
+                img.src = r[1];
+                img.alt = r[2] === undefined
+                    ? (options && options.defaultImageText ? options.defaultImageText : '')
+                    : r[2].replace(/~(.)/g, '$1');
+                node.appendChild(img);
+            } },
+
+        namedUri: { regex: '\\[\\[(' + rx.uri + ')\\|(' + rx.linkText + ')\\]\\]',
+            build: function(node, r, options) {
+                var link = document.createElement('a');
+                link.href = r[1];
+                if (options && options.isPlainUri) {
+                    link.appendChild(document.createTextNode(r[2]));
+                }
+                else {
+                    this.apply(link, r[2], options);
+                }
+                node.appendChild(link);
+            } },
+
+        namedLink: { regex: '\\[\\[(' + rx.link + ')\\|(' + rx.linkText + ')\\]\\]',
+            build: function(node, r, options) {
+                var link = document.createElement('a');
+                
+                link.href = options && options.linkFormat
+                    ? formatLink(r[1].replace(/~(.)/g, '$1'), options.linkFormat)
+                    : r[1].replace(/~(.)/g, '$1');
+                this.apply(link, r[2], options);
+                
+                node.appendChild(link);
+            } },
+
+        unnamedUri: { regex: '\\[\\[(' + rx.uri + ')\\]\\]',
+            build: 'dummy' },
+        unnamedLink: { regex: '\\[\\[(' + rx.link + ')\\]\\]',
+            build: 'dummy' },
+        unnamedInterwikiLink: { regex: '\\[\\[(' + rx.interwikiLink + ')\\]\\]',
+            build: 'dummy' },
+
+        rawUri: { regex: '(' + rx.rawUri + ')',
+            build: 'dummy' },
+
+        escapedSequence: { regex: '~(' + rx.rawUri + '|.)', capture: 1,
+            tag: 'span', attrs: { 'class': 'escaped' } },
+        escapedSymbol: { regex: /~(.)/, capture: 1,
+            tag: 'span', attrs: { 'class': 'escaped' } }
+    };
+    g.unnamedUri.build = g.rawUri.build = function(node, r, options) {
+        if (!options) { options = {}; }
+        options.isPlainUri = true;
+        g.namedUri.build.call(this, node, Array(r[0], r[1], r[1]), options);
+    };
+    g.unnamedLink.build = function(node, r, options) {
+        g.namedLink.build.call(this, node, Array(r[0], r[1], r[1]), options);
+    };
+    g.namedInterwikiLink = { regex: '\\[\\[(' + rx.interwikiLink + ')\\|(' + rx.linkText + ')\\]\\]',
+        build: function(node, r, options) {
+                var link = document.createElement('a');
+                
+                var m, f;
+                if (options && options.interwiki) {
+                m = r[1].match(/(.*?):(.*)/);
+                f = options.interwiki[m[1]];
+            }
+            
+            if (typeof f == 'undefined') {
+                if (!g.namedLink.apply) {
+                    g.namedLink = new this.constructor(g.namedLink);
+                }
+                return g.namedLink.build.call(g.namedLink, node, r, options);
+            }
+
+            link.href = formatLink(m[2].replace(/~(.)/g, '$1'), f);
+            
+            this.apply(link, r[2], options);
+            
+            node.appendChild(link);
+        }
+    };
+    g.unnamedInterwikiLink.build = function(node, r, options) {
+        g.namedInterwikiLink.build.call(this, node, Array(r[0], r[1], r[1]), options);
+    };
+    g.namedUri.children = g.unnamedUri.children = g.rawUri.children =
+            g.namedLink.children = g.unnamedLink.children =
+            g.namedInterwikiLink.children = g.unnamedInterwikiLink.children =
+        [ g.escapedSymbol, g.img ];
+
+    for (var i = 1; i <= 6; i++) {
+        g['h' + i] = { tag: 'h' + i, capture: 2,
+            regex: '(^|\\n)[ \\t]*={' + i + '}[ \\t]' +
+                   '([^~]*?(~(.|(?=\\n)|$))*)[ \\t]*=*\\s*(\\n|$)'
+        };
+    }
+
+    g.ulist.children = g.olist.children = [ g.li ];
+    g.li.children = [ g.ulist, g.olist ];
+    g.li.fallback = g.text;
+
+    g.table.children = [ g.tr ];
+    g.tr.children = [ g.th, g.td ];
+    g.td.children = [ g.singleLine ];
+    g.th.children = [ g.singleLine ];
+
+    g.h1.children = g.h2.children = g.h3.children =
+            g.h4.children = g.h5.children = g.h6.children =
+            g.singleLine.children = g.paragraph.children =
+            g.text.children = g.strong.children = g.em.children =
+        [ g.escapedSequence, g.strong, g.em, g.br, g.rawUri,
+            g.namedUri, g.namedInterwikiLink, g.namedLink,
+            g.unnamedUri, g.unnamedInterwikiLink, g.unnamedLink,
+            g.tt, g.img ];
+
+    g.root = {
+        children: [ g.h1, g.h2, g.h3, g.h4, g.h5, g.h6,
+            g.hr, g.ulist, g.olist, g.preBlock, g.table ],
+        fallback: { children: [ g.paragraph ] }
+    };
+
+    Parse.Simple.Base.call(this, g, options);
+};
+
+Parse.Simple.Creole.prototype = new Parse.Simple.Base();
+
+Parse.Simple.Creole.prototype.constructor = Parse.Simple.Creole;

File src/garmin.js

View file
  • Ignore whitespace
+// = Garmin Communicator =
+//
+// A revisit of the Garmin Communicator plugin API.
+//
+// Written for jQuery, instead of Prototype.
+//
+
+// ==== Details ====
+// Matthew Schinckel [[mailto:matt@schinckel.net|matt@schinckel.net]]
+
+// == Setup ==
+// Run inside our own scope.
+//
+// Put {{{Garmin}}} into the global scope if it isn't there already.
+//
 (function($, undefined){
-  if (!window.Garmin) { window.Garmin={};}
-    
+    if (!window.Garmin) { window.Garmin={};}  
   var Garmin = window.Garmin;
-  var parser = new window.DOMParser();
-  var plugin;
   
+  // == Garmin.Plugin ==
+  // 
+  // Return a reference to the plugin object.
+  //
+  // This will inject the plugin object into the page if it could not find it.
+  //
+  // Put the non-IE version in a {{{<div>}}}, apparently there is a Safari bug that requires this.
+
+  Garmin.Plugin = function() {
+    var plugin;
+  
+    // Inject the plugin into the page if it isn't already there.
     if (window.ActiveXObject) {
       // IE
       plugin = $('#GarminActiveXControl');
       }
     }
   
-  plugin = plugin[0];
-  
-  var parseXML = function(xml) {
-    return $($.parseXML(xml));
+    return plugin[0];
   };
   
-  var unlock = function unlock(codes) {
-    var unlocked = false;
-    _.each(_.range(0, codes.length, 2), function(i) {
-      unlocked = unlocked || plugin.Unlock(codes[i], codes[i+1]);
-    });
-    return unlocked;
-  };
+  // == Progress ==
+  // 
+  // Status codes representing the progress of the current transfer.
   
   Garmin.PROGRESS = {
     idle: 0,
     finished: 3
   };
   
+  // == Fitness Types ==
+  //
+  // The supported transfer types.
+  //
+  // TODO: Goals do not seem to work just yet. Data format error?
+  
   Garmin.FITNESS_TYPES = {
     Activities: ['FitnessHistory', 'FitnessDirectory'],
     Workouts: ['FitnessWorkouts', 'FitnessData'],
     Courses: ['FitnessCourses', 'FitnessData'],
-    Goals: ['FitnessActivityGoals', 'FitnessData'],     // TODO: This one does not seem to work just yet. Data format error?
+    Goals: ['FitnessActivityGoals', 'FitnessData'],
     Profile: ['FitnessUserProfile', 'FitnessData']
   };
   
+  // == Events ==
+  // 
+  // List of events that are triggered by this controller.
+  //
+  // All events are jQuery events, and the sending controller can be found in {{{Event.data.controller}}}.
+  //
+  // {{{onStartXXX}}} events occur before the process starts.
+  //
+  // {{{onFinishXXX}}} events occur after the process is complete, and typically provide the expected data in an object passed as their second argument.
+  //
+  // Note that there are also {{{onStartReadActivities}}}, {{{onFinishReadActivities}}}, as well as writing version of events for all of the Fitness Types listed above. These can be used instead of the low-level ones provided here (which mirror the original Garmin events).
   Garmin.EVENTS = [
-    'onStartFindDevices', 'onCancelFindDevices', 'onFinishFindDevices',
+    'onStartFindDevices', 
+    'onCancelFindDevices', 
+    'onFinishFindDevices',
     'onSelectDevice',
-    'onException', 'onInteractionWithNoDevice', 'onConcurrentInteraction',
-    'onStartReadFromDevice', 'onWaitingReadFromDevice', 'onProgressReadFromDevice', 'onCancelReadFromDevice', 'onFinishReadFromDevice',
-    'onStartWriteToDevice', 'onWaitingWriteToDevice', 'onProgressWriteToDevice', 'onCancelWriteToDevice', 'onFinishWriteToDevice'
+    'onException', 
+    'onInteractionWithNoDevice', 
+    'onConcurrentInteraction',
+    'onStartReadFromDevice', 
+    'onWaitingReadFromDevice', 
+    'onProgressReadFromDevice', 
+    'onCancelReadFromDevice', 
+    'onFinishReadFromDevice',
+    'onStartWriteToDevice', 
+    'onWaitingWriteToDevice', 
+    'onProgressWriteToDevice', 
+    'onCancelWriteToDevice', 
+    'onFinishWriteToDevice'
   ];
   
-  Garmin.DeviceControl = function DeviceControl(delegate) {
+  _.each(Garmin.FITNESS_TYPES, function(data, name){
+    Garmin.EVENTS.push('onStartRead' + name);
+    Garmin.EVENTS.push('onFinishRead' + name);
+    Garmin.EVENTS.push('onStartWrite' + name);
+    Garmin.EVENTS.push('onFinishWrite' + name);
+  })
+  
+  /**
+  
+  @class Garmin.Communicator
+  **/
+  Garmin.Communicator = function Communicator(delegate, autostart) {
     
     delegate = delegate || {};
     
     // We need to have a flag so we can't try to have two things happening at once.
     var inUse = false;
     var currentDevice = null;
-    var $plugin = $(document);
+    var plugin = new Garmin.Plugin();
+    var $plugin = $(plugin);
+
     
     var unlockCodes = [
       "file:///","cb1492ae040612408d87cc53e3f7ff3c",
       "http://localhost","45517b532362fc3149e4211ade14c9b2",
       "http://127.0.0.1","40cd4860f7988c53b15b8491693de133"
     ];
-    unlock(_.union(unlockCodes, delegate.unlockCodes || []));
     
     // Data we store.
     var devices = [];
     
     //************** Helpers **************//
     
+    var parseXML = function(xml) {
+      return $($.parseXML(xml));
+    };
+    
+    var unlock = function(codes) {
+      var unlocked = false;
+      _.each(_.range(0, codes.length, 2), function(i) {
+        unlocked = unlocked || plugin.Unlock(codes[i], codes[i+1]);
+      });
+      return unlocked;
+    };
+    
     var checkUnlocked = function() {
       if (plugin.Locked) {
         throw new Error("Plugin is not unlocked");
       });
       return message;
     };
-    
-    //************** Bind delegate event listeners **************//
-    
-    // Bind any listeners that we find on the delegate object
-    _.each(Garmin.EVENTS, function(evt) {
-      if (delegate[evt]) {
-        $plugin.on(evt, {controller: this}, delegate[evt]);        
-      }
-    }, this);
-    
+        
     //************** Handlers **************//
     
-    // Find all devices, and store their data.
     this.findDevices = function() {
       checkUnlocked();
       plugin.StartFindDevices();
     
     this.selectDevice = function(d) {
       // Ensure device number is 0..devices.length;
-      if (d < 0 || d >= devices.length) {
-        throw new Error("Select a device between 0 and " + (devices.length - 1));
+      if (isNaN(d) || d < 0 || d >= devices.length) {
+        $plugin.trigger('onException', {message: "Invalid device number selected"});
+        currentDevice = null;
+        return;
       }
       currentDevice = d;
-      $plugin.trigger('onSelectDevice', devices[d]);
+      $plugin.trigger('onSelectDevice', {device: devices[d], deviceNumber: d});
       return devices[d];
     };
     
       return devices[currentDevice];
     };
             
-    var baseHandler = function(readWrite, dataType, pluginMethod) {
-      if (currentDevice == null) {
-        console.log("No device selected");
+    var baseHandler = function(readWrite, dataType, pluginMethod, name) {
+      if (devices.length === 0) {
+        $plugin.trigger('')
+      }
+      if (currentDevice === null) {
         $plugin.trigger('onInteractionWithNoDevice');
         return;
       }
       }
       inUse = true;
       $plugin.trigger('onStart' + readWrite + toFrom + 'Device', {device:device, dataType:dataType});
+      $plugin.trigger('onStart' + readWrite + name, {device:device, dataType:dataType});
       plugin['Start' + readWrite + pluginMethod](currentDevice, dataType);
       
       var finishHandler = function finishHandler() {
             break;
           case Garmin.PROGRESS.finished:
             $plugin.trigger('onFinish' + readWrite + toFrom  + 'Device', {device:device, dataType: dataType, data: plugin.TcdXml});
+            $plugin.trigger('onFinish' + readWrite + name, {device:device, dataType: dataType, data: plugin.TcdXml});
+            console.log('onFinish' + readWrite + name);
             inUse = false;
             break;
           default:
       finishHandler();
     };
     
-    var readHandler = function(dataType, pluginMethod) {
+    var readHandler = function(dataType, pluginMethod, type) {
       return function(){
         plugin.TcdXml = "";
-        baseHandler('Read', dataType, pluginMethod);
+        baseHandler('Read', dataType, pluginMethod, type);
       };
     };
     
-    var writeHandler = function(dataType, pluginMethod) {
+    var writeHandler = function(dataType, pluginMethod, type) {
       return function(data, filename) {
         // Strip out any newlines, as they break things.
         plugin.TcdXml = data.replace(/[\n\r]+/gm, '');
         plugin.FileName = filename;
-        baseHandler('Write', dataType, pluginMethod);
+        baseHandler('Write', dataType, pluginMethod, type);
       };
     };
     
     _.each(Garmin.FITNESS_TYPES, function(data, type) {
-      this['read' + type] = readHandler(data[0], data[1]);
-      this['write' + type] = writeHandler(data[0], data[1]);
+      this['read' + type] = readHandler(data[0], data[1], type);
+      this['write' + type] = writeHandler(data[0], data[1], type);
     }, this);
     
-    // Only while developing!
-    this.plugin = plugin;
+    //************** Bind delegate event listeners **************//
+    
+    // Bind any listeners that we find on the delegate object
+    _.each(Garmin.EVENTS, function(evt) {
+      if (delegate[evt]) {
+        $(document).on(evt, '#' + plugin.id, {controller: this}, delegate[evt]);        
+      }
+    }, this);
+    
+    // See if the plugin is actually installed.
+    if (!plugin.Unlock) {
+      $plugin.trigger('onMissingCommunicatorPlugin');
+      return;
+    }
+    
+    // Unlock the plugin (or die trying)
+    unlock(_.union(unlockCodes, delegate.unlockCodes || []));
+    
+    if (autostart) {
+      this.findDevices();
+    }
   };
-})(jQuery);
-
-var delegate = {};
-
-_.each(Garmin.EVENTS, function(evt) {
-  this[evt] = function(e){ 
-    console.log(e);
-  };
-}, delegate);
-
-var g = new Garmin.DeviceControl(delegate);
-g.findDevices();
-
-
+})(jQuery);

File tests/basic_test.html

View file
  • Ignore whitespace
     <script src="../vendor/jquery-1.7.2.js"></script>
     <script src="../vendor/underscore-1.3.3.js"></script>
     <script src="../src/garmin.js"></script>
+    <script>
+    var g;
+    var handler = {
+      onFinishFindDevices: function(evt, data) {
+        console.log(data.devices);
+        g.selectDevice(1);
+        g.readActivities();
+      },
+      onFinishReadActivities: function(evt, data) {
+        console.log(data.data.length);
+      },
+    };
+    $(function(){
+      g = new Garmin.Communicator(handler, true);
+    });
+    </script>
     <textarea>
       
     </textarea>