diff options
36 files changed, 2791 insertions, 88 deletions
diff --git a/.externalToolBuilders/docs.launch b/.externalToolBuilders/docs.launch new file mode 100644 index 00000000..80cd1429 --- /dev/null +++ b/.externalToolBuilders/docs.launch @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<launchConfiguration type="org.eclipse.ui.externaltools.ProgramBuilderLaunchConfigurationType"> +<stringAttribute key="org.eclipse.debug.core.ATTR_REFRESH_SCOPE" value="${working_set:<?xml version="1.0" encoding="UTF-8"?> <resources> <item path="/angular.js/docs" type="2"/> </resources>}"/> +<booleanAttribute key="org.eclipse.debug.ui.ATTR_LAUNCH_IN_BACKGROUND" value="false"/> +<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_BUILDER_ENABLED" value="false"/> +<stringAttribute key="org.eclipse.ui.externaltools.ATTR_BUILD_SCOPE" value="${working_set:<?xml version="1.0" encoding="UTF-8"?> <resources> <item path="/angular.js/docs/callback.js" type="1"/> <item path="/angular.js/docs/collect.js" type="1"/> <item path="/angular.js/docs/filter.template" type="1"/> </resources>}"/> +<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/angular.js/gen_docs.sh}"/> +<stringAttribute key="org.eclipse.ui.externaltools.ATTR_RUN_BUILD_KINDS" value="full,incremental,auto,"/> +<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_TRIGGERS_CONFIGURED" value="true"/> +<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/angular.js}"/> +</launchConfiguration> diff --git a/.externalToolBuilders/gen_docs.launch b/.externalToolBuilders/gen_docs.launch new file mode 100644 index 00000000..5898ce80 --- /dev/null +++ b/.externalToolBuilders/gen_docs.launch @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<launchConfiguration type="org.eclipse.ui.externaltools.ProgramLaunchConfigurationType"> +<stringAttribute key="org.eclipse.debug.core.ATTR_REFRESH_SCOPE" value="${working_set:<?xml version="1.0" encoding="UTF-8"?> <resources> <item path="/angular.js/docs" type="2"/> </resources>}"/> +<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LAUNCH_CONFIGURATION_BUILD_SCOPE" value="${none}"/> +<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/angular.js/gen_docs.sh}"/> +<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/angular.js}"/> +</launchConfiguration> diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index a7c382ed..00000000 --- a/.idea/.gitignore +++ /dev/null @@ -1 +0,0 @@ -workspace.xml diff --git a/.idea/.rakeTasks b/.idea/.rakeTasks deleted file mode 100644 index 50fb6fec..00000000 --- a/.idea/.rakeTasks +++ /dev/null @@ -1,7 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?>
-<Settings><!--This file was automatically generated by Ruby plugin. -You are allowed to: -1. Remove rake task -2. Add existing rake tasks -To add existing rake tasks automatically delete this file and reload the project. ---><RakeGroup description="" fullCmd="" taksId="rake"><RakeTask description="Compile JavaScript" fullCmd="compile" taksId="compile" /><RakeTask description="Generate Externs" fullCmd="compileexterns" taksId="compileexterns" /><RakeTask description="Lint" fullCmd="lint" taksId="lint" /><RakeGroup description="" fullCmd="" taksId="server"><RakeTask description="Run JsTestDriver Server" fullCmd="server:start" taksId="start" /><RakeTask description="Run JavaScript tests against the server" fullCmd="server:test" taksId="test" /></RakeGroup><RakeTask description="Run JavaScript tests" fullCmd="test" taksId="test" /><RakeTask description="" fullCmd="default" taksId="default" /></RakeGroup></Settings>
diff --git a/.idea/master.iml b/.idea/angular.js.iml index 8f7472a8..6b8184f8 100644 --- a/.idea/master.iml +++ b/.idea/angular.js.iml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8"?> -<module type="RUBY_MODULE" version="4"> +<module type="WEB_MODULE" version="4"> <component name="NewModuleRootManager"> <content url="file://$MODULE_DIR$" /> <orderEntry type="inheritedJdk" /> diff --git a/.idea/misc.xml b/.idea/misc.xml index bf08d02d..f7e602b4 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -3,10 +3,7 @@ <component name="DependencyValidationManager"> <option name="SKIP_IMPORT_STATEMENTS" value="false" /> </component> - <component name="ProjectDetails"> - <option name="projectName" value="master" /> - </component> - <component name="ProjectRootManager" version="2" project-jdk-name="Ruby SDK 1.8.7 (/usr/bin/ruby)" project-jdk-type="RUBY_SDK" /> + <component name="ProjectRootManager" version="2" /> <component name="SvnBranchConfigurationManager"> <option name="mySupportsUserInfoFilter" value="true" /> </component> diff --git a/.idea/modules.xml b/.idea/modules.xml index 12b24804..3bb010cf 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,7 +2,7 @@ <project version="4"> <component name="ProjectModuleManager"> <modules> - <module fileurl="file://$PROJECT_DIR$/.idea/master.iml" filepath="$PROJECT_DIR$/.idea/master.iml" /> + <module fileurl="file://$PROJECT_DIR$/.idea/angular.js.iml" filepath="$PROJECT_DIR$/.idea/angular.js.iml" /> </modules> </component> </project> diff --git a/.idea/projectCodeStyle.xml b/.idea/projectCodeStyle.xml new file mode 100644 index 00000000..a5c450f7 --- /dev/null +++ b/.idea/projectCodeStyle.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="CodeStyleSettingsManager"> + <option name="PER_PROJECT_SETTINGS"> + <value> + <option name="USE_SAME_INDENTS" value="true" /> + <option name="OTHER_INDENT_OPTIONS"> + <value> + <option name="INDENT_SIZE" value="2" /> + <option name="CONTINUATION_INDENT_SIZE" value="4" /> + <option name="TAB_SIZE" value="2" /> + <option name="USE_TAB_CHARACTER" value="false" /> + <option name="SMART_TABS" value="false" /> + <option name="LABEL_INDENT_SIZE" value="0" /> + <option name="LABEL_INDENT_ABSOLUTE" value="false" /> + </value> + </option> + <ADDITIONAL_INDENT_OPTIONS fileType="js"> + <option name="INDENT_SIZE" value="4" /> + <option name="CONTINUATION_INDENT_SIZE" value="8" /> + <option name="TAB_SIZE" value="4" /> + <option name="USE_TAB_CHARACTER" value="false" /> + <option name="SMART_TABS" value="false" /> + <option name="LABEL_INDENT_SIZE" value="0" /> + <option name="LABEL_INDENT_ABSOLUTE" value="false" /> + </ADDITIONAL_INDENT_OPTIONS> + <ADDITIONAL_INDENT_OPTIONS fileType="sass"> + <option name="INDENT_SIZE" value="2" /> + <option name="CONTINUATION_INDENT_SIZE" value="8" /> + <option name="TAB_SIZE" value="4" /> + <option name="USE_TAB_CHARACTER" value="false" /> + <option name="SMART_TABS" value="false" /> + <option name="LABEL_INDENT_SIZE" value="0" /> + <option name="LABEL_INDENT_ABSOLUTE" value="false" /> + </ADDITIONAL_INDENT_OPTIONS> + <ADDITIONAL_INDENT_OPTIONS fileType="xml"> + <option name="INDENT_SIZE" value="4" /> + <option name="CONTINUATION_INDENT_SIZE" value="8" /> + <option name="TAB_SIZE" value="4" /> + <option name="USE_TAB_CHARACTER" value="false" /> + <option name="SMART_TABS" value="false" /> + <option name="LABEL_INDENT_SIZE" value="0" /> + <option name="LABEL_INDENT_ABSOLUTE" value="false" /> + </ADDITIONAL_INDENT_OPTIONS> + <ADDITIONAL_INDENT_OPTIONS fileType="yml"> + <option name="INDENT_SIZE" value="2" /> + <option name="CONTINUATION_INDENT_SIZE" value="8" /> + <option name="TAB_SIZE" value="4" /> + <option name="USE_TAB_CHARACTER" value="false" /> + <option name="SMART_TABS" value="false" /> + <option name="LABEL_INDENT_SIZE" value="0" /> + <option name="LABEL_INDENT_ABSOLUTE" value="false" /> + </ADDITIONAL_INDENT_OPTIONS> + </value> + </option> + <option name="USE_PER_PROJECT_SETTINGS" value="true" /> + </component> +</project> + diff --git a/.idea/runConfigurations/gen_docs.xml b/.idea/runConfigurations/gen_docs.xml new file mode 100644 index 00000000..23afac13 --- /dev/null +++ b/.idea/runConfigurations/gen_docs.xml @@ -0,0 +1,15 @@ +<component name="ProjectRunConfigurationManager"> + <configuration default="false" name="gen_docs" type="BashConfigurationType" factoryName="Bash"> + <option name="INTERPRETER_OPTIONS" value="" /> + <option name="INTERPRETER_PATH" value="/bin/bash" /> + <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" /> + <option name="PARENT_ENVS" value="true" /> + <envs /> + <module name="" /> + <option name="SCRIPT_NAME" value="$PROJECT_DIR$/gen_docs.sh" /> + <option name="PARAMETERS" value="" /> + <RunnerSettings RunnerId="BashRunner" /> + <ConfigurationWrapper RunnerId="BashRunner" /> + <method /> + </configuration> +</component>
\ No newline at end of file diff --git a/.idea/runConfigurations/rake_compile.xml b/.idea/runConfigurations/rake_compile.xml new file mode 100644 index 00000000..d69f3466 --- /dev/null +++ b/.idea/runConfigurations/rake_compile.xml @@ -0,0 +1,15 @@ +<component name="ProjectRunConfigurationManager"> + <configuration default="false" name="rake compile" type="BashConfigurationType" factoryName="Bash"> + <option name="INTERPRETER_OPTIONS" value="" /> + <option name="INTERPRETER_PATH" value="/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby" /> + <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" /> + <option name="PARENT_ENVS" value="true" /> + <envs /> + <module name="" /> + <option name="SCRIPT_NAME" value="/usr/bin/rake" /> + <option name="PARAMETERS" value="compile" /> + <RunnerSettings RunnerId="BashRunner" /> + <ConfigurationWrapper RunnerId="BashRunner" /> + <method /> + </configuration> +</component>
\ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 9d32e507..275077f8 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -1,8 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> <project version="4"> <component name="VcsDirectoryMappings"> - <mapping directory="" vcs="" /> - <mapping directory="$PROJECT_DIR$" vcs="Git" /> + <mapping directory="" vcs="Git" /> </component> </project> diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 00000000..62adcd33 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,321 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="ChangeListManager"> + <list default="true" readonly="true" id="2e561485-a685-4e95-bea4-cabeda87d953" name="Default" comment="" /> + <ignored path=".idea/workspace.xml" /> + <ignored path="angular.js.iws" /> + <option name="TRACKING_ENABLED" value="true" /> + <option name="SHOW_DIALOG" value="false" /> + <option name="HIGHLIGHT_CONFLICTS" value="true" /> + <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" /> + <option name="LAST_RESOLUTION" value="IGNORE" /> + </component> + <component name="ChangesViewManager" flattened_view="true" show_ignored="false" /> + <component name="CreatePatchCommitExecutor"> + <option name="PATCH_PATH" value="" /> + <option name="REVERSE_PATCH" value="false" /> + </component> + <component name="DaemonCodeAnalyzer"> + <disable_hints /> + </component> + <component name="FavoritesManager"> + <favorites_list name="angular.js" /> + </component> + <component name="FileColors" enabled="true" enabledForTabs="true" /> + <component name="FileEditorManager"> + <leaf /> + </component> + <component name="FindManager"> + <FindUsagesManager> + <setting name="OPEN_NEW_TAB" value="false" /> + </FindUsagesManager> + </component> + <component name="Git.Settings"> + <option name="GIT_EXECUTABLE" value="/usr/local/git/bin/git" /> + <option name="CHECKOUT_INCLUDE_TAGS" value="false" /> + </component> + <component name="IdeDocumentHistory"> + <option name="changedFiles"> + <list> + <option value="$PROJECT_DIR$/lib/nodeserver/server.js" /> + <option value="$PROJECT_DIR$/Rakefile" /> + <option value="$PROJECT_DIR$/docs/collect.js" /> + </list> + </option> + </component> + <component name="ProjectLevelVcsManager"> + <OptionsSetting value="true" id="Add" /> + <OptionsSetting value="true" id="Remove" /> + <OptionsSetting value="true" id="Checkout" /> + <OptionsSetting value="true" id="Update" /> + <OptionsSetting value="true" id="Status" /> + <OptionsSetting value="true" id="Edit" /> + <ConfirmationsSetting value="0" id="Add" /> + <ConfirmationsSetting value="0" id="Remove" /> + </component> + <component name="ProjectReloadState"> + <option name="STATE" value="0" /> + </component> + <component name="ProjectView"> + <navigator currentView="ProjectPane" proportions="" version="1" splitterProportion="0.5"> + <flattenPackages /> + <showMembers /> + <showModules /> + <showLibraryContents /> + <hideEmptyPackages /> + <abbreviatePackageNames /> + <autoscrollToSource /> + <autoscrollFromSource /> + <sortByType /> + </navigator> + <panes> + <pane id="Favorites" /> + <pane id="Scope" /> + <pane id="ProjectPane"> + <subPane> + <PATH> + <PATH_ELEMENT> + <option name="myItemId" value="angular.js" /> + <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" /> + </PATH_ELEMENT> + </PATH> + <PATH> + <PATH_ELEMENT> + <option name="myItemId" value="angular.js" /> + <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" /> + </PATH_ELEMENT> + <PATH_ELEMENT> + <option name="myItemId" value="angular.js" /> + <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" /> + </PATH_ELEMENT> + </PATH> + <PATH> + <PATH_ELEMENT> + <option name="myItemId" value="angular.js" /> + <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" /> + </PATH_ELEMENT> + <PATH_ELEMENT> + <option name="myItemId" value="angular.js" /> + <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" /> + </PATH_ELEMENT> + <PATH_ELEMENT> + <option name="myItemId" value="build" /> + <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" /> + </PATH_ELEMENT> + </PATH> + <PATH> + <PATH_ELEMENT> + <option name="myItemId" value="angular.js" /> + <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" /> + </PATH_ELEMENT> + <PATH_ELEMENT> + <option name="myItemId" value="angular.js" /> + <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" /> + </PATH_ELEMENT> + <PATH_ELEMENT> + <option name="myItemId" value="build" /> + <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" /> + </PATH_ELEMENT> + <PATH_ELEMENT> + <option name="myItemId" value="pkg" /> + <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" /> + </PATH_ELEMENT> + <PATH_ELEMENT> + <option name="myItemId" value="angular-0.9.2-a838b3ef" /> + <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" /> + </PATH_ELEMENT> + </PATH> + <PATH> + <PATH_ELEMENT> + <option name="myItemId" value="angular.js" /> + <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" /> + </PATH_ELEMENT> + <PATH_ELEMENT> + <option name="myItemId" value="angular.js" /> + <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" /> + </PATH_ELEMENT> + <PATH_ELEMENT> + <option name="myItemId" value="build" /> + <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" /> + </PATH_ELEMENT> + <PATH_ELEMENT> + <option name="myItemId" value="docs" /> + <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" /> + </PATH_ELEMENT> + </PATH> + </subPane> + </pane> + </panes> + </component> + <component name="PropertiesComponent"> + <property name="options.splitter.main.proportions" value="0.3" /> + <property name="WebServerToolWindowFactoryState" value="false" /> + <property name="recentsLimit" value="5" /> + <property name="options.lastSelected" value="project.propVCSSupport.VCSs.Git" /> + <property name="GoToClass.includeJavaFiles" value="false" /> + <property name="options.splitter.details.proportions" value="0.2" /> + <property name="options.searchVisible" value="true" /> + </component> + <component name="RunManager" selected="Bash.gen_docs.sh"> + <configuration default="false" name="gen_docs.sh" type="BashConfigurationType" factoryName="Bash" temporary="true"> + <option name="INTERPRETER_OPTIONS" value="" /> + <option name="INTERPRETER_PATH" value="/bin/bash" /> + <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" /> + <option name="PARENT_ENVS" value="true" /> + <envs /> + <module name="angular.js" /> + <option name="SCRIPT_NAME" value="$PROJECT_DIR$/gen_docs.sh" /> + <option name="PARAMETERS" value="" /> + <RunnerSettings RunnerId="BashRunner" /> + <ConfigurationWrapper RunnerId="BashRunner" /> + <method /> + </configuration> + <configuration default="true" type="BashConfigurationType" factoryName="Bash"> + <option name="INTERPRETER_OPTIONS" value="" /> + <option name="INTERPRETER_PATH" value="/bin/bash" /> + <option name="WORKING_DIRECTORY" value="" /> + <option name="PARENT_ENVS" value="true" /> + <envs /> + <module name="angular.js" /> + <option name="SCRIPT_NAME" value="" /> + <option name="PARAMETERS" value="" /> + <method /> + </configuration> + <list size="1"> + <item index="0" class="java.lang.String" itemvalue="Bash.gen_docs.sh" /> + </list> + </component> + <component name="ShelveChangesManager" show_recycled="false" /> + <component name="SvnConfiguration" maxAnnotateRevisions="500"> + <option name="USER" value="" /> + <option name="PASSWORD" value="" /> + <option name="LAST_MERGED_REVISION" /> + <option name="UPDATE_RUN_STATUS" value="false" /> + <option name="MERGE_DRY_RUN" value="false" /> + <option name="MERGE_DIFF_USE_ANCESTRY" value="true" /> + <option name="UPDATE_LOCK_ON_DEMAND" value="false" /> + <option name="IGNORE_SPACES_IN_MERGE" value="false" /> + <option name="DETECT_NESTED_COPIES" value="true" /> + <option name="CHECK_NESTED_FOR_QUICK_MERGE" value="false" /> + <option name="IGNORE_SPACES_IN_ANNOTATE" value="true" /> + <option name="SHOW_MERGE_SOURCES_IN_ANNOTATE" value="true" /> + <configuration useDefault="true">$PROJECT_DIR$/../../.subversion_IDEA</configuration> + <myIsUseDefaultProxy>false</myIsUseDefaultProxy> + <supportedVersion>125</supportedVersion> + </component> + <component name="TaskManager"> + <task active="true" id="Default" summary="Default task"> + <changelist id="2e561485-a685-4e95-bea4-cabeda87d953" name="Default" comment="" /> + <created>1288738700234</created> + <updated>1288738700234</updated> + </task> + <servers /> + </component> + <component name="ToolWindowManager"> + <frame x="0" y="22" width="2560" height="1574" extended-state="6" /> + <editor active="false" /> + <layout> + <window_info id="Messages" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" /> + <window_info id="Changes" active="true" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" weight="0.32943603" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" /> + <window_info id="TODO" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="6" side_tool="false" content_ui="tabs" /> + <window_info id="Structure" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.25" sideWeight="0.5" order="1" side_tool="true" content_ui="tabs" /> + <window_info id="Dependency Viewer" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" /> + <window_info id="Project" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" weight="0.18020505" sideWeight="0.66574967" order="0" side_tool="false" content_ui="tabs" /> + <window_info id="Debug" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.4" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" /> + <window_info id="Run" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.32943603" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" /> + <window_info id="Version Control" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" /> + <window_info id="Cvs" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.25" sideWeight="0.5" order="4" side_tool="false" content_ui="tabs" /> + <window_info id="Message" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" /> + <window_info id="Ant Build" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" /> + <window_info id="Find" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" /> + <window_info id="Commander" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.4" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" /> + <window_info id="Hierarchy" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.25" sideWeight="0.5" order="2" side_tool="false" content_ui="combo" /> + <window_info id="Inspection" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.4" sideWeight="0.5" order="5" side_tool="false" content_ui="tabs" /> + </layout> + </component> + <component name="VcsManagerConfiguration"> + <option name="OFFER_MOVE_TO_ANOTHER_CHANGELIST_ON_PARTIAL_COMMIT" value="true" /> + <option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="true" /> + <option name="PERFORM_UPDATE_IN_BACKGROUND" value="true" /> + <option name="PERFORM_COMMIT_IN_BACKGROUND" value="true" /> + <option name="PERFORM_EDIT_IN_BACKGROUND" value="true" /> + <option name="PERFORM_CHECKOUT_IN_BACKGROUND" value="true" /> + <option name="PERFORM_ADD_REMOVE_IN_BACKGROUND" value="true" /> + <option name="PERFORM_ROLLBACK_IN_BACKGROUND" value="false" /> + <option name="CHECK_LOCALLY_CHANGED_CONFLICTS_IN_BACKGROUND" value="false" /> + <option name="ENABLE_BACKGROUND_PROCESSES" value="false" /> + <option name="CHANGED_ON_SERVER_INTERVAL" value="60" /> + <option name="FORCE_NON_EMPTY_COMMENT" value="false" /> + <option name="LAST_COMMIT_MESSAGE" value="updated Rakefile to support packaging of the docs" /> + <option name="MAKE_NEW_CHANGELIST_ACTIVE" value="true" /> + <option name="OPTIMIZE_IMPORTS_BEFORE_PROJECT_COMMIT" value="false" /> + <option name="CHECK_FILES_UP_TO_DATE_BEFORE_COMMIT" value="false" /> + <option name="REFORMAT_BEFORE_PROJECT_COMMIT" value="false" /> + <option name="REFORMAT_BEFORE_FILE_COMMIT" value="false" /> + <option name="FILE_HISTORY_DIALOG_COMMENTS_SPLITTER_PROPORTION" value="0.8" /> + <option name="FILE_HISTORY_DIALOG_SPLITTER_PROPORTION" value="0.5" /> + <option name="ACTIVE_VCS_NAME" /> + <option name="UPDATE_GROUP_BY_PACKAGES" value="false" /> + <option name="UPDATE_GROUP_BY_CHANGELIST" value="false" /> + <option name="SHOW_FILE_HISTORY_AS_TREE" value="false" /> + <option name="FILE_HISTORY_SPLITTER_PROPORTION" value="0.6" /> + <MESSAGE value="move the output of generation to build" /> + <MESSAGE value="updated Rakefile to support packaging of the docs" /> + </component> + <component name="XDebuggerManager"> + <breakpoint-manager /> + </component> + <component name="XSLT-Support.FileAssociations.UIState"> + <PATH> + <PATH_ELEMENT> + <option name="myItemId" value="angular.js" /> + <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" /> + </PATH_ELEMENT> + </PATH> + </component> + <component name="editorHistoryManager"> + <entry file="file://$PROJECT_DIR$/version.yaml"> + <provider selected="true" editor-type-id="text-editor"> + <state line="2" column="23" selection-start="50" selection-end="58" vertical-scroll-proportion="0.025559105"> + <folding /> + </state> + </provider> + </entry> + <entry file="file://$PROJECT_DIR$/docs/collect.js"> + <provider selected="true" editor-type-id="text-editor"> + <state line="78" column="0" selection-start="2539" selection-end="2539" vertical-scroll-proportion="0.99680513"> + <folding /> + </state> + </provider> + </entry> + <entry file="file://$PROJECT_DIR$/Rakefile"> + <provider selected="true" editor-type-id="text-editor"> + <state line="184" column="5" selection-start="5171" selection-end="5171" vertical-scroll-proportion="0.47603834"> + <folding /> + </state> + </provider> + </entry> + <entry file="file://$PROJECT_DIR$/src/Resource.js"> + <provider selected="true" editor-type-id="text-editor"> + <state line="37" column="1" selection-start="1038" selection-end="1038" vertical-scroll-proportion="0.47284344"> + <folding /> + </state> + </provider> + </entry> + <entry file="file://$PROJECT_DIR$/test/ResourceSpec.js"> + <provider selected="true" editor-type-id="text-editor"> + <state line="13" column="35" selection-start="399" selection-end="399" vertical-scroll-proportion="0.16613418"> + <folding /> + </state> + </provider> + </entry> + <entry file="file://$PROJECT_DIR$/lib/nodeserver/server.js"> + <provider selected="true" editor-type-id="text-editor"> + <state line="37" column="0" selection-start="864" selection-end="864" vertical-scroll-proportion="0.4345048"> + <folding /> + </state> + </provider> + </entry> + </component> +</project> + @@ -20,6 +20,16 @@ </dictionary> </arguments> </buildCommand> + <buildCommand> + <name>org.eclipse.ui.externaltools.ExternalToolBuilder</name> + <triggers>auto,full,incremental,</triggers> + <arguments> + <dictionary> + <key>LaunchConfigHandle</key> + <value><project>/.externalToolBuilders/docs.launch</value> + </dictionary> + </arguments> + </buildCommand> </buildSpec> <natures> <nature>org.eclipse.wst.jsdt.core.jsNature</nature> diff --git a/.settings/.jsdtscope b/.settings/.jsdtscope index 7beec24e..a8f1ce8f 100644 --- a/.settings/.jsdtscope +++ b/.settings/.jsdtscope @@ -1,9 +1,11 @@ <?xml version="1.0" encoding="UTF-8"?> <classpath> <classpathentry excluding="test/" kind="src" path="src"/> + <classpathentry kind="src" path="docs"/> <classpathentry kind="src" path="src/test"/> <classpathentry excluding="test/" kind="src" path="test"/> <classpathentry kind="src" path="test/test"/> <classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.JRE_CONTAINER"/> <classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.baseBrowserLibrary"/> + <classpathentry kind="output" path=""/> </classpath> @@ -156,8 +156,14 @@ task :compile => [:init, :compile_scenario, :generate_ie_compat] do end +desc 'Generate docs' +task :docs do + `node docs/collect.js` +end + + desc 'Create angular distribution' -task :package => [:clean, :compile] do +task :package => [:clean, :compile, :docs] do v = YAML::load( File.open( 'version.yaml' ) )['version'] match = v.match(/^([^-]*)(-snapshot)?$/) version = match[1] + (match[2] ? ('-' + %x(git rev-parse HEAD)[0..7]) : '') @@ -178,6 +184,20 @@ task :package => [:clean, :compile] do FileUtils.cp(src, pkg_dir + '/' + dest) end + FileUtils.cp_r path_to('docs'), "#{pkg_dir}/docs-#{version}" + + File.open("#{pkg_dir}/docs-#{version}/index.html", File::RDWR) do |f| + text = f.read + f.rewind + f.write text.sub('angular.min.js', "angular-#{version}.min.js") + end + + File.open("#{pkg_dir}/docs-#{version}/docs-scenario.html", File::RDWR) do |f| + text = f.read + f.rewind + f.write text.sub('angular-scenario.js', "angular-scenario-#{version}.js") + end + %x(tar -czf #{path_to(tarball)} -C #{path_to('pkg')} .) puts "Package created: #{path_to(tarball)}" @@ -256,5 +276,5 @@ end # returns path to the file in the build directory # def path_to(filename) - return File.join(BUILD_DIR, filename) + return File.join(BUILD_DIR, *filename) end diff --git a/docs/callback.js b/docs/callback.js new file mode 100644 index 00000000..0d0669d1 --- /dev/null +++ b/docs/callback.js @@ -0,0 +1,66 @@ +function noop(){} + +function chain(delegateFn, explicitDone){ + var onDoneFn = noop; + var onErrorFn = noop; + var waitForCount = 1; + delegateFn = delegateFn || noop; + var stackError = new Error('capture stack'); + + function decrementWaitFor() { + waitForCount--; + if (waitForCount == 0) + onDoneFn(); + } + + function self(){ + try { + return delegateFn.apply(self, arguments); + } catch (error) { + self.error(error); + } finally { + if (!explicitDone) + decrementWaitFor(); + } + }; + self.onDone = function(callback){ + onDoneFn = callback; + return self; + }; + self.onError = function(callback){ + onErrorFn = callback; + return self; + }; + self.waitFor = function(callback){ + if (waitForCount == 0) + throw new Error("Can not wait on already called callback."); + waitForCount++; + return chain(callback).onDone(decrementWaitFor).onError(self.error); + }; + + self.waitMany = function(callback){ + if (waitForCount == 0) + throw new Error("Can not wait on already called callback."); + waitForCount++; + return chain(callback, true).onDone(decrementWaitFor).onError(self.error); + }; + + self.done = function(callback){ + decrementWaitFor(); + }; + + self.error = function(error) { + var stack = stackError.stack.split(/\n\r?/).splice(2); + var nakedStack = []; + stack.forEach(function(frame){ + if (!frame.match(/callback\.js:\d+:\d+\)$/)) + nakedStack.push(frame); + }); + error.stack = error.stack + '\nCalled from:\n' + nakedStack.join('\n'); + onErrorFn(error); + }; + + return self; +} + +exports.chain = chain; diff --git a/docs/collect.js b/docs/collect.js new file mode 100644 index 00000000..a3c2a25c --- /dev/null +++ b/docs/collect.js @@ -0,0 +1,206 @@ +var fs = require('fs'), + spawn = require('child_process').spawn, + mustache = require('../lib/mustache'), + callback = require('./callback'), + markdown = require('../lib/markdown'); + +var documentation = { + section:{}, + all:[] +}; + +var SRC_DIR = "docs/"; +var OUTPUT_DIR = "build/docs/"; + +var work = callback.chain(function () { + console.log('Parsing Angular Reference Documentation'); + mkdirPath(OUTPUT_DIR, work.waitFor(function(){ + findJsFiles('src', work.waitMany(function(file) { + //console.log('reading', file, '...'); + findNgDoc(file, work.waitMany(function(doc) { + parseNgDoc(doc); + if (doc.ngdoc) { + delete doc.raw.text; + var section = documentation.section; + (section[doc.ngdoc] = section[doc.ngdoc] || []).push(doc); + documentation.all.push(doc); + console.log('Found:', doc.ngdoc + ':' + doc.shortName); + mergeTemplate( + doc.ngdoc + '.template', + doc.name + '.html', doc, work.waitFor()); + } + })); + })); + })); +}).onError(function(err){ + console.log('ERROR:', err.stack || err); +}).onDone(function(){ + mergeTemplate('docs-data.js', 'docs-data.js', {JSON:JSON.stringify(documentation)}, callback.chain()); + mergeTemplate('docs-scenario.js', 'docs-scenario.js', documentation, callback.chain()); + copy('docs-scenario.html', callback.chain()); + copy('index.html', callback.chain()); + mergeTemplate('docs.js', 'docs.js', documentation, callback.chain()); + mergeTemplate('wiki_widgets.css', 'wiki_widgets.css', documentation, callback.chain()); + mergeTemplate('wiki_widgets.js', 'wiki_widgets.js', documentation, callback.chain()); + console.log('DONE'); +}); +work(); +//////////////////// + +function noop(){} +function mkdirPath(path, callback) { + var parts = path.split(/\//); + path = '.'; + (function next(){ + if (parts.length) { + path += '/' + parts.shift(); + fs.mkdir(path, 0777, next); + } else { + callback(); + } + })(); +} + +function copy(name, callback){ + fs.readFile(SRC_DIR + name, callback.waitFor(function(err, content){ + if (err) return this.error(err); + fs.writeFile(OUTPUT_DIR + name, content, callback); + })); +} + +function mergeTemplate(template, output, doc, callback){ + fs.readFile(SRC_DIR + template, + callback.waitFor(function(err, template){ + if (err) return this.error(err); + var content = mustache.to_html(template.toString(), doc); + fs.writeFile(OUTPUT_DIR + output, content, callback); + })); +} + + +function unknownTag(doc, name) { + var error = "[" + doc.raw.file + ":" + doc.raw.line + "]: unknown tag: " + name; + console.log(error); + throw new Error(error); +} + +function valueTag(doc, name, value) { + doc[name] = value; +} + +function markdownTag(doc, name, value) { + doc[name] = markdown.toHTML(value); +} + +var TAG = { + ngdoc: valueTag, + example: valueTag, + scenario: valueTag, + namespace: valueTag, + css: valueTag, + see: valueTag, + 'function': valueTag, + description: markdownTag, + returns: markdownTag, + name: function(doc, name, value) { + doc.name = value; + doc.shortName = value.split(/\./).pop(); + }, + param: function(doc, name, value){ + doc.param = doc.param || []; + doc.paramRest = doc.paramRest || []; + var match = value.match(/^({([^\s=]+)(=([^\s]+))?}\s*)?([^\s]+)\s*(.*)/); + if (match) { + var param = { + type: match[2], + 'default':match[4], + name: match[5], + description:match[6]}; + doc.param.push(param); + if (!doc.paramFirst) { + doc.paramFirst = param; + } else { + doc.paramRest.push(param); + } + } else { + throw "[" + doc.raw.file + ":" + doc.raw.line + + "]: @param must be in format '{type} name=value description' got: " + value; + } + } +}; + +function parseNgDoc(doc){ + var atName; + var atText; + var match; + doc.raw.text.split(/\n/).forEach(function(line, lineNumber){ + if (match = line.match(/^@(\w+)(\s+(.*))?/)) { + // we found @name ... + // if we have existing name + if (atName) { + (TAG[atName] || unknownTag)(doc, atName, atText.join('\n')); + } + atName = match[1]; + atText = []; + if(match[3]) atText.push(match[3]); + } else { + if (atName) { + atText.push(line); + } else { + // ignore + } + } + }); + if (atName) { + (TAG[atName] || unknownTag)(doc, atName, atText.join('\n')); + } +} + +function findNgDoc(file, callback) { + fs.readFile(file, callback.waitFor(function(err, content){ + var lines = content.toString().split(/\n\r?/); + var doc; + var match; + var inDoc = false; + lines.forEach(function(line, lineNumber){ + lineNumber++; + // is the comment starting? + if (!inDoc && (match = line.match(/^\s*\/\*\*\s*(.*)$/))) { + line = match[1]; + inDoc = true; + doc = {raw:{file:file, line:lineNumber, text:[]}}; + } + // are we done? + if (inDoc && line.match(/\*\//)) { + doc.raw.text = doc.raw.text.join('\n'); + doc.raw.text = doc.raw.text.replace(/^\n/, ''); + if (doc.raw.text.match(/@ngdoc/)) + callback(doc); + doc = null; + inDoc = false; + } + // is the comment add text + if (inDoc){ + doc.raw.text.push(line.replace(/^\s*\*\s?/, '')); + } + }); + callback.done(); + })); +} + +function findJsFiles(dir, callback){ + fs.readdir(dir, callback.waitFor(function(err, files){ + if (err) return this.error(err); + files.forEach(function(file){ + var path = dir + '/' + file; + fs.lstat(path, callback.waitFor(function(err, stat){ + if (err) return this.error(err); + if (stat.isDirectory()) + findJsFiles(path, callback.waitMany(callback)); + else if (/\.js$/.test(path)) + callback(path); + })); + }); + callback.done(); + })); +} diff --git a/docs/docs-data.js b/docs/docs-data.js new file mode 100644 index 00000000..27b3fab5 --- /dev/null +++ b/docs/docs-data.js @@ -0,0 +1 @@ +NG_DOC={{{JSON}}};
\ No newline at end of file diff --git a/docs/docs-scenario.html b/docs/docs-scenario.html new file mode 100644 index 00000000..83ca6fdf --- /dev/null +++ b/docs/docs-scenario.html @@ -0,0 +1,9 @@ +<!DOCTYPE HTML> +<html xmlns:ng="http://angularjs.org" wiki:ng="http://angularjs.org"> +<head> + <script type="text/javascript" src="../angular-scenario.js" ng:autobind></script> + <script type="text/javascript" src="docs-scenario.js"></script> +</head> +<body> +</body> +</html>
\ No newline at end of file diff --git a/docs/docs-scenario.js b/docs/docs-scenario.js new file mode 100644 index 00000000..f0c6edb9 --- /dev/null +++ b/docs/docs-scenario.js @@ -0,0 +1,9 @@ +{{#all}} +describe('{{name}}', function(){ + beforeEach(function(){ + navigateTo('index.html#{{name}}'); + }); + // {{raw.file}}:{{raw.line}} +{{{scenario}}} +}); +{{/all}}
\ No newline at end of file diff --git a/docs/docs.js b/docs/docs.js new file mode 100644 index 00000000..6f5c5034 --- /dev/null +++ b/docs/docs.js @@ -0,0 +1,7 @@ +function DocController($resource, $location){ + this.docs = $resource('documentation.json').get(); + this.getPartialDoc = function(){ + return encodeURIComponent($location.hashPath) + '.html'; + }; +} +DocController.$inject=['$resource', '$location'];
\ No newline at end of file diff --git a/docs/filter.template b/docs/filter.template new file mode 100644 index 00000000..677a8785 --- /dev/null +++ b/docs/filter.template @@ -0,0 +1,33 @@ +<h1><tt>{{name}}</tt></h1> +<h2>Usage</h2> +<h3>In HTML Template Binding</h3> +<tt> + <span>{{</span> + {{paramFirst.name}}_expression + | {{shortName}}{{#paramRest}}{{^default}}:{{name}}{{/default}}{{#default}}<i>[:{{name}}={{default}}]</i>{{/default}}{{/paramRest}} + <span> }}</span> +</tt> +<h3>In JavaScript</h3> +<tt ng:non-bindable> +angular.filter.{{shortName}}({{paramFirst.name}}{{#paramRest}}, {{name}}{{/paramRest}} ); +</tt> + +<h3>Parameters</h3> +<ul> + {{#param}} + <li><tt>{{name}}{{#type}}({{type}}){{/type}}</tt>: {{description}}</li> + {{/param}} +</ul> + +<h3>Returns</h3> +{{{returns}}} + +<h3>CSS</h3> +{{{css}}} + +<h2>Description</h2> +{{{description}}} + +<WIKI:SOURCE style="display:block;"> +{{example}} +</WIKI:SOURCE>
\ No newline at end of file diff --git a/docs/filter:currency.html b/docs/filter:currency.html deleted file mode 100644 index bd277bb8..00000000 --- a/docs/filter:currency.html +++ /dev/null @@ -1,21 +0,0 @@ -<h1>filter:currency</h1> - -<p>formats a number as a currency (ie $1,234.56)</p> - -<p>@format <em>expression</em> | currency</p> - -<p><example> -<input type="text" name="amount" value="1234.56"/> <br/> -{{amount | currency}} -</example></p> - -<p><test> - it('should init with 1234.56', function(){ - expect(bind('amount')).toEqual('$1,234.56'); - }); - it('should update', function(){ - element(':input[name=amount]').value('-1234'); - expect(bind('amount')).toEqual('-$1,234.00'); - expect(bind('amount')).toHaveColor('red'); - }); -</test></p> diff --git a/docs/filter:currency.md b/docs/filter:currency.md deleted file mode 100644 index 44687a91..00000000 --- a/docs/filter:currency.md +++ /dev/null @@ -1,20 +0,0 @@ -# filter:currency -formats a number as a currency (ie $1,234.56) - -@format _expression_ | currency - -<example> -<input type="text" name="amount" value="1234.56"/> <br/> -{{amount | currency}} -</example> - -<test> - it('should init with 1234.56', function(){ - expect(bind('amount')).toEqual('$1,234.56'); - }); - it('should update', function(){ - element(':input[name=amount]').value('-1234'); - expect(bind('amount')).toEqual('-$1,234.00'); - expect(bind('amount')).toHaveColor('red'); - }); -</test>
\ No newline at end of file diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 00000000..f61893a4 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,25 @@ +<!DOCTYPE HTML> +<html xmlns:ng="http://angularjs.org" wiki:ng="http://angularjs.org"> +<head> + <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js"></script> + <script type="text/javascript" src="docs-data.js"></script> + <script type="text/javascript" src="../angular.min.js" ng:autobind></script> + <script type="text/javascript" src="http://angularjs.org/extensions/wiki_widgets.js"></script> + <link rel="stylesheet" href="http://angularjs.org/extensions/wiki_widgets.css" type="text/css" media="screen" /> +</head> +<body ng:init="docs=$window.NG_DOC; $window.$root = $root"> + <table> + <tr> + <td valign="top"> + <div ng:repeat="(name, type) in docs.section"> + <b>{{name}}</b> + <div ng:repeat="page in type"> + <a href="#{{page.name}}"><tt>{{page.shortName}}</tt></a> + </div> + </div> + </td> + <td valign="top"><ng:include src="$location.hashPath + '.html' "></ng:include></td> + </tr> + </table> +</body> +</html>
\ No newline at end of file diff --git a/docs/overview.template b/docs/overview.template new file mode 100644 index 00000000..dbb91e2a --- /dev/null +++ b/docs/overview.template @@ -0,0 +1 @@ +{{{description}}}
\ No newline at end of file diff --git a/docs/wiki_widgets.css b/docs/wiki_widgets.css new file mode 100644 index 00000000..a1f6e939 --- /dev/null +++ b/docs/wiki_widgets.css @@ -0,0 +1,58 @@ +WIKI\:SOURCE, +WIKI\:SOURCE>ul.tabs, +WIKI\:SOURCE>ul.tabs>li { + display: block; + margin: 0; + padding: 0; +} +WIKI\:SOURCE>ul.tabs { + margin: 0 !important; + padding: 0 !important; +} + +WIKI\:SOURCE > ul.tabs > li.tab { + margin: 0 0 0 .8em !important; + display: inline-block; + border: 1px solid gray; + padding: .1em .8em .4em .8em !important; + -moz-border-radius-topleft: 5px; + -moz-border-radius-topright: 5px; + -webkit-border-top-left-radius: 5px; + -webkit-border-top-right-radius: 5px; + border-bottom: none; + cursor: pointer; + background-color: lightgray; + margin-bottom: -2px; + position: relative; + z-index: 0; +} + +WIKI\:SOURCE > ul.tabs > li.tab.selected { + z-index: 2; + background-color: white; + font-weight: bold; + border-width: 2px; +} + +WIKI\:SOURCE > ul.tabs > li.pane { + border: 2px solid gray; + border-radius: 5px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + background-color: white; + padding: 5px !important; +} + +WIKI\:SOURCE > ul.tabs > li.pane { + display:none; +} + +WIKI\:SOURCE > ul.tabs > li.pane.selected { + position: relative; + z-index: 1; + display:block; +} +WIKI\:SOURCE > ul.tabs > li.pane.source { + font-size: .8em !important; +} +//////////////////
\ No newline at end of file diff --git a/docs/wiki_widgets.js b/docs/wiki_widgets.js new file mode 100644 index 00000000..0147536b --- /dev/null +++ b/docs/wiki_widgets.js @@ -0,0 +1,51 @@ +(function(){ + var HTML_TEMPLATE = + '<!DOCTYPE HTML>\n' + + '<html xmlns:ng="http://angularjs.org">\n' + + ' <head>\n' + + ' <script type="text/javascript"\n' + + ' src="http://angularjs.org/ng/js/angular-debug.js" ng:autobind></script>\n' + + ' </head>\n' + + ' <body>\n' + + '_HTML_SOURCE_\n' + + ' </body>\n' + + '</html>'; + + angular.widget('WIKI:SOURCE', function(element){ + this.descend(true); + var html = element.text(); + element.show(); + var tabs = angular.element( + '<ul class="tabs">' + + '<li class="tab selected" to="angular"><angular/></li>' + + '<li class="tab" to="plain">plain</li>' + + '<li class="tab" to="source">source</li>' + + '<li class="pane selected angular">' + html + '</li>' + + '<li class="pane plain" ng:non-bindable>' + html + '</li>' + + '<li class="pane source" ng:non-bindable><pre class="brush: js; html-script: true"></pre></li>' + + '</ul>'); + var pre = tabs. + find('>li.source>pre'). + text(HTML_TEMPLATE.replace('_HTML_SOURCE_', html)); + var color = element.attr('color') || 'white'; + element.html(''); + element.append(tabs); + element.find('>ul.tabs>li.pane').css('background-color', color); + var script = (html.match(/<script[^\>]*>([\s\S]*)<\/script>/) || [])[1] || ''; + try { + eval(script); + } catch (e) { + alert(e); + } + return function(element){ + element.find('>ul.tabs>li.tab').click(function(){ + if ($(this).is(".selected")) return; + element. + find('>ul.tabs>li.selected'). + add(this). + add(element.find('>ul>li.pane.' + angular.element(this).attr('to'))). + toggleClass('selected'); + }); + }; + }); +})();
\ No newline at end of file diff --git a/gen_docs.sh b/gen_docs.sh new file mode 100755 index 00000000..d883fd34 --- /dev/null +++ b/gen_docs.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +/usr/local/bin/node docs/collect.js diff --git a/lib/markdown/Index.js b/lib/markdown/Index.js new file mode 100644 index 00000000..c834c2d6 --- /dev/null +++ b/lib/markdown/Index.js @@ -0,0 +1,1446 @@ +// Released under MIT license +// Copyright (c) 2009-2010 Dominic Baggott +// Copyright (c) 2009-2010 Ash Berlin + +(function( expose ) { + +/** + * class Markdown + * + * Markdown processing in Javascript done right. We have very particular views + * on what constitutes 'right' which include: + * + * - produces well-formed HTML (this means that em and strong nesting is + * important) + * + * - has an intermediate representation to allow processing of parsed data (We + * in fact have two, both as [JsonML]: a markdown tree and an HTML tree). + * + * - is easily extensible to add new dialects without having to rewrite the + * entire parsing mechanics + * + * - has a good test suite + * + * This implementation fulfills all of these (except that the test suite could + * do with expanding to automatically run all the fixtures from other Markdown + * implementations.) + * + * ##### Intermediate Representation + * + * *TODO* Talk about this :) Its JsonML, but document the node names we use. + * + * [JsonML]: http://jsonml.org/ "JSON Markup Language" + **/ +var Markdown = expose.Markdown = function Markdown(dialect) { + switch (typeof dialect) { + case "undefined": + this.dialect = Markdown.dialects.Gruber; + break; + case "object": + this.dialect = dialect; + break; + default: + if (dialect in Markdown.dialects) { + this.dialect = Markdown.dialects[dialect]; + } + else { + throw new Error("Unknown Markdown dialect '" + String(dialect) + "'"); + } + break; + } + this.em_state = []; + this.strong_state = []; + this.debug_indent = ""; +} + +/** + * parse( markdown, [dialect] ) -> JsonML + * - markdown (String): markdown string to parse + * - dialect (String | Dialect): the dialect to use, defaults to gruber + * + * Parse `markdown` and return a markdown document as a Markdown.JsonML tree. + **/ +expose.parse = function( source, dialect ) { + // dialect will default if undefined + var md = new Markdown( dialect ); + return md.toTree( source ); +} + +/** + * toHTML( markdown ) -> String + * toHTML( md_tree ) -> String + * - markdown (String): markdown string to parse + * - md_tree (Markdown.JsonML): parsed markdown tree + * + * Take markdown (either as a string or as a JsonML tree) and run it through + * [[toHTMLTree]] then turn it into a well-formated HTML fragment. + **/ +expose.toHTML = function toHTML( source ) { + var input = expose.toHTMLTree( source ); + + return expose.renderJsonML( input ); +} + +/** + * toHTMLTree( markdown, [dialect] ) -> JsonML + * toHTMLTree( md_tree ) -> JsonML + * - markdown (String): markdown string to parse + * - dialect (String | Dialect): the dialect to use, defaults to gruber + * - md_tree (Markdown.JsonML): parsed markdown tree + * + * Turn markdown into HTML, represented as a JsonML tree. If a string is given + * to this function, it is first parsed into a markdown tree by calling + * [[parse]]. + **/ +expose.toHTMLTree = function toHTMLTree( input, dialect ) { + // convert string input to an MD tree + if ( typeof input ==="string" ) input = this.parse( input, dialect ); + + // Now convert the MD tree to an HTML tree + + // remove references from the tree + var attrs = extract_attr( input ), + refs = {}; + + if ( attrs && attrs.references ) { + refs = attrs.references; + } + + var html = convert_tree_to_html( input, refs ); + merge_text_nodes( html ); + return html; +} + +var mk_block = Markdown.mk_block = function(block, trail, line) { + // Be helpful for default case in tests. + if ( arguments.length == 1 ) trail = "\n\n"; + + var s = new String(block); + s.trailing = trail; + // To make it clear its not just a string + s.toSource = function() { + return "Markdown.mk_block( " + + uneval(block) + + ", " + + uneval(trail) + + ", " + + uneval(line) + + " )" + } + + if (line != undefined) + s.lineNumber = line; + + return s; +} + +function count_lines( str ) { + var n = 0, i = -1;; + while ( ( i = str.indexOf('\n', i+1) ) != -1) n++; + return n; +} + +// Internal - split source into rough blocks +Markdown.prototype.split_blocks = function splitBlocks( input, startLine ) { + // [\s\S] matches _anything_ (newline or space) + var re = /([\s\S]+?)($|\n(?:\s*\n|$)+)/g, + blocks = [], + m; + + var line_no = 1; + + if ( ( m = (/^(\s*\n)/)(input) ) != null ) { + // skip (but count) leading blank lines + line_no += count_lines( m[0] ); + re.lastIndex = m[0].length; + } + + while ( ( m = re(input) ) != null ) { + blocks.push( mk_block( m[1], m[2], line_no ) ); + line_no += count_lines( m[0] ); + } + + return blocks; +} + +/** + * Markdown#processBlock( block, next ) -> undefined | [ JsonML, ... ] + * - block (String): the block to process + * - next (Array): the following blocks + * + * Process `block` and return an array of JsonML nodes representing `block`. + * + * It does this by asking each block level function in the dialect to process + * the block until one can. Succesful handling is indicated by returning an + * array (with zero or more JsonML nodes), failure by a false value. + * + * Blocks handlers are responsible for calling [[Markdown#processInline]] + * themselves as appropriate. + * + * If the blocks were split incorrectly or adjacent blocks need collapsing you + * can adjust `next` in place using shift/splice etc. + * + * If any of this default behaviour is not right for the dialect, you can + * define a `__call__` method on the dialect that will get invoked to handle + * the block processing. + */ +Markdown.prototype.processBlock = function processBlock( block, next ) { + var cbs = this.dialect.block, + ord = cbs.__order__; + + if ( "__call__" in cbs ) { + return cvs.__call__.call(this, block, next); + } + + for ( var i = 0; i < ord.length; i++ ) { + //D:this.debug( "Testing", ord[i] ); + var res = cbs[ ord[i] ].call( this, block, next ); + if ( res ) { + //D:this.debug(" matched"); + if ( !res instanceof Array || ( res.length > 0 && !( res[0] instanceof Array ) ) ) + this.debug(ord[i], "didn't return a proper array"); + //D:this.debug( "" ); + return res; + } + } + + // Uhoh! no match! Should we throw an error? + return []; +} + +Markdown.prototype.processInline = function processInline( block ) { + return this.dialect.inline.__call__.call( this, String( block ) ); +} + +/** + * Markdown#toTree( source ) -> JsonML + * - source (String): markdown source to parse + * + * Parse `source` into a JsonML tree representing the markdown document. + **/ +// custom_tree means set this.tree to `custom_tree` and restore old value on return +Markdown.prototype.toTree = function toTree( source, custom_root ) { + var blocks = source instanceof Array + ? source + : this.split_blocks( source ); + + // Make tree a member variable so its easier to mess with in extensions + var old_tree = this.tree; + try { + this.tree = custom_root || this.tree || [ "markdown" ]; + + blocks: + while ( blocks.length ) { + var b = this.processBlock( blocks.shift(), blocks ); + + // Reference blocks and the like won't return any content + if ( !b.length ) continue blocks; + + this.tree.push.apply( this.tree, b ); + } + return this.tree; + } + finally { + if ( custom_root ) + this.tree = old_tree; + } + +} + +// Noop by default +Markdown.prototype.debug = function () { + var args = Array.prototype.slice.call( arguments); + args.unshift(this.debug_indent); + print.apply( print, args ); +} + +Markdown.prototype.loop_re_over_block = function( re, block, cb ) { + // Dont use /g regexps with this + var m, + b = block.valueOf(); + + while ( b.length && (m = re(b) ) != null) { + b = b.substr( m[0].length ); + cb.call(this, m); + } + return b; +} + +/** + * Markdown.dialects + * + * Namespace of built-in dialects. + **/ +Markdown.dialects = {}; + +/** + * Markdown.dialects.Gruber + * + * The default dialect that follows the rules set out by John Gruber's + * markdown.pl as closely as possible. Well actually we follow the behaviour of + * that script which in some places is not exactly what the syntax web page + * says. + **/ +Markdown.dialects.Gruber = { + block: { + atxHeader: function atxHeader( block, next ) { + var m = block.match( /^(#{1,6})\s*(.*?)\s*#*\s*(?:\n|$)/ ); + + if ( !m ) return undefined; + + var header = [ "header", { level: m[ 1 ].length }, m[ 2 ] ]; + + if ( m[0].length < block.length ) + next.unshift( mk_block( block.substr( m[0].length ), block.trailing, block.lineNumber + 2 ) ); + + return [ header ]; + }, + + setextHeader: function setextHeader( block, next ) { + var m = block.match( /^(.*)\n([-=])\2\2+(?:\n|$)/ ); + + if ( !m ) return undefined; + + var level = ( m[ 2 ] === "=" ) ? 1 : 2; + var header = [ "header", { level : level }, m[ 1 ] ]; + + if ( m[0].length < block.length ) + next.unshift( mk_block( block.substr( m[0].length ), block.trailing, block.lineNumber + 2 ) ); + + return [ header ]; + }, + + code: function code( block, next ) { + // | Foo + // |bar + // should be a code block followed by a paragraph. Fun + // + // There might also be adjacent code block to merge. + + var ret = [], + re = /^(?: {0,3}\t| {4})(.*)\n?/, + lines; + + // 4 spaces + content + var m = block.match( re ); + + if ( !m ) return undefined; + + block_search: + do { + // Now pull out the rest of the lines + var b = this.loop_re_over_block( + re, block.valueOf(), function( m ) { ret.push( m[1] ) } ); + + if (b.length) { + // Case alluded to in first comment. push it back on as a new block + next.unshift( mk_block(b, block.trailing) ); + break block_search; + } + else if (next.length) { + // Check the next block - it might be code too + var m = next[0].match( re ); + + if ( !m ) break block_search; + + // Pull how how many blanks lines follow - minus two to account for .join + ret.push ( block.trailing.replace(/[^\n]/g, '').substring(2) ); + + block = next.shift(); + } + else + break block_search; + } while (true); + + return [ [ "code_block", ret.join("\n") ] ]; + }, + + horizRule: function horizRule( block, next ) { + // this needs to find any hr in the block to handle abutting blocks + var m = block.match( /^(?:([\s\S]*?)\n)?[ \t]*([-_*])(?:[ \t]*\2){2,}[ \t]*(?:\n([\s\S]*))?$/ ); + + if ( !m ) { + return undefined; + } + + var jsonml = [ [ "hr" ] ]; + + // if there's a leading abutting block, process it + if ( m[ 1 ] ) { + jsonml.unshift.apply( jsonml, this.processBlock( m[ 1 ], [] ) ); + } + + // if there's a trailing abutting block, stick it into next + if ( m[ 3 ] ) { + next.unshift( mk_block( m[ 3 ] ) ); + } + + return jsonml; + }, + + // There are two types of lists. Tight and loose. Tight lists have no whitespace + // between the items (and result in text just in the <li>) and loose lists, + // which have an empty line between list items, resulting in (one or more) + // paragraphs inside the <li>. + // + // There are all sorts weird edge cases about the original markdown.pl's + // handling of lists: + // + // * Nested lists are supposed to be indented by four chars per level. But + // if they aren't, you can get a nested list by indenting by less than + // four so long as the indent doesn't match an indent of an existing list + // item in the 'nest stack'. + // + // * The type of the list (bullet or number) is controlled just by the + // first item at the indent. Subsequent changes are ignored unless they + // are for nested lists + // + lists: (function( ) { + // Use a closure to hide a few variables. + var any_list = "[*+-]|\\d\\.", + bullet_list = /[*+-]/, + number_list = /\d+\./, + // Capture leading indent as it matters for determining nested lists. + is_list_re = new RegExp( "^( {0,3})(" + any_list + ")[ \t]+" ), + indent_re = "(?: {0,3}\\t| {4})"; + + // TODO: Cache this regexp for certain depths. + // Create a regexp suitable for matching an li for a given stack depth + function regex_for_depth( depth ) { + + return new RegExp( + // m[1] = indent, m[2] = list_type + "(?:^(" + indent_re + "{0," + depth + "} {0,3})(" + any_list + ")\\s+)|" + + // m[3] = cont + "(^" + indent_re + "{0," + (depth-1) + "}[ ]{0,4})" + ); + } + function expand_tab( input ) { + return input.replace( / {0,3}\t/g, " " ); + } + + // Add inline content `inline` to `li`. inline comes from processInline + // so is an array of content + function add(li, loose, inline, nl) { + if (loose) { + li.push( [ "para" ].concat(inline) ); + return; + } + // Hmmm, should this be any block level element or just paras? + var add_to = li[li.length -1] instanceof Array && li[li.length - 1][0] == "para" + ? li[li.length -1] + : li; + + // If there is already some content in this list, add the new line in + if (nl && li.length > 1) inline.unshift(nl); + + for (var i=0; i < inline.length; i++) { + var what = inline[i], + is_str = typeof what == "string"; + if (is_str && add_to.length > 1 && typeof add_to[add_to.length-1] == "string" ) + { + add_to[ add_to.length-1 ] += what; + } + else { + add_to.push( what ); + } + } + } + + // contained means have an indent greater than the current one. On + // *every* line in the block + function get_contained_blocks( depth, blocks ) { + + var re = new RegExp( "^(" + indent_re + "{" + depth + "}.*?\\n?)*$" ), + replace = new RegExp("^" + indent_re + "{" + depth + "}", "gm"), + ret = []; + + while ( blocks.length > 0 ) { + if ( re( blocks[0] ) ) { + var b = blocks.shift(), + // Now remove that indent + x = b.replace( replace, ""); + + ret.push( mk_block( x, b.trailing, b.lineNumber ) ); + } + break; + } + return ret; + } + + // passed to stack.forEach to turn list items up the stack into paras + function paragraphify(s, i, stack) { + var list = s.list; + var last_li = list[list.length-1]; + + if (last_li[1] instanceof Array && last_li[1][0] == "para") { + return; + } + if (i+1 == stack.length) { + // Last stack frame + // Keep the same array, but replace the contents + last_li.push( ["para"].concat( last_li.splice(1) ) ); + } + else { + var sublist = last_li.pop(); + last_li.push( ["para"].concat( last_li.splice(1) ), sublist ); + } + } + + // The matcher function + return function( block, next ) { + var m = block.match( is_list_re ); + if ( !m ) return undefined; + + function make_list( m ) { + var list = bullet_list( m[2] ) + ? ["bulletlist"] + : ["numberlist"]; + + stack.push( { list: list, indent: m[1] } ); + return list; + } + + + var stack = [], // Stack of lists for nesting. + list = make_list( m ), + last_li, + loose = false, + ret = [ stack[0].list ]; + + // Loop to search over block looking for inner block elements and loose lists + loose_search: + while( true ) { + // Split into lines preserving new lines at end of line + var lines = block.split( /(?=\n)/ ); + + // We have to grab all lines for a li and call processInline on them + // once as there are some inline things that can span lines. + var li_accumulate = ""; + + // Loop over the lines in this block looking for tight lists. + tight_search: + for (var line_no=0; line_no < lines.length; line_no++) { + var nl = "", + l = lines[line_no].replace(/^\n/, function(n) { nl = n; return "" }); + + // TODO: really should cache this + var line_re = regex_for_depth( stack.length ); + + m = l.match( line_re ); + //print( "line:", uneval(l), "\nline match:", uneval(m) ); + + // We have a list item + if ( m[1] !== undefined ) { + // Process the previous list item, if any + if ( li_accumulate.length ) { + add( last_li, loose, this.processInline( li_accumulate ), nl ); + // Loose mode will have been dealt with. Reset it + loose = false; + li_accumulate = ""; + } + + m[1] = expand_tab( m[1] ); + var wanted_depth = Math.floor(m[1].length/4)+1; + //print( "want:", wanted_depth, "stack:", stack.length); + if ( wanted_depth > stack.length ) { + // Deep enough for a nested list outright + //print ( "new nested list" ); + list = make_list( m ); + last_li.push( list ); + last_li = list[1] = [ "listitem" ]; + } + else { + // We aren't deep enough to be strictly a new level. This is + // where Md.pl goes nuts. If the indent matches a level in the + // stack, put it there, else put it one deeper then the + // wanted_depth deserves. + var found = stack.some(function(s, i) { + if ( s.indent != m[1] ) return false; + list = s.list; // Found the level we want + stack.splice(i+1); // Remove the others + //print("found"); + return true; // And stop looping + }); + + if (!found) { + //print("not found. l:", uneval(l)); + wanted_depth++; + if (wanted_depth <= stack.length) { + stack.splice(wanted_depth); + //print("Desired depth now", wanted_depth, "stack:", stack.length); + list = stack[wanted_depth-1].list; + //print("list:", uneval(list) ); + } + else { + //print ("made new stack for messy indent"); + list = make_list(m); + last_li.push(list); + } + } + + //print( uneval(list), "last", list === stack[stack.length-1].list ); + last_li = [ "listitem" ]; + list.push(last_li); + } // end depth of shenegains + nl = ""; + } + + // Add content + if (l.length > m[0].length) { + li_accumulate += nl + l.substr( m[0].length ); + } + } // tight_search + + if ( li_accumulate.length ) { + add( last_li, loose, this.processInline( li_accumulate ), nl ); + // Loose mode will have been dealt with. Reset it + loose = false; + li_accumulate = ""; + } + + // Look at the next block - we might have a loose list. Or an extra + // paragraph for the current li + var contained = get_contained_blocks( stack.length, next ); + + // Deal with code blocks or properly nested lists + if (contained.length > 0) { + // Make sure all listitems up the stack are paragraphs + stack.forEach( paragraphify, this ); + + last_li.push.apply( last_li, this.toTree( contained, [] ) ); + } + + var next_block = next[0] && next[0].valueOf() || ""; + + if ( next_block.match(is_list_re) || next_block.match( /^ / ) ) { + block = next.shift(); + + // Check for an HR following a list: features/lists/hr_abutting + var hr = this.dialect.block.horizRule( block, next ); + + if (hr) { + ret.push.apply(ret, hr); + break; + } + + // Make sure all listitems up the stack are paragraphs + stack.forEach( paragraphify , this ); + + loose = true; + continue loose_search; + } + break; + } // loose_search + + return ret; + } + })(), + + blockquote: function blockquote( block, next ) { + if ( !block.match( /^>/m ) ) + return undefined; + + var jsonml = []; + + // separate out the leading abutting block, if any + if ( block[ 0 ] != ">" ) { + var lines = block.split( /\n/ ), + prev = []; + + // keep shifting lines until you find a crotchet + while ( lines.length && lines[ 0 ][ 0 ] != ">" ) { + prev.push( lines.shift() ); + } + + // reassemble! + block = lines.join( "\n" ); + jsonml.push.apply( jsonml, this.processBlock( prev.join( "\n" ), [] ) ); + } + + // if the next block is also a blockquote merge it in + while ( next.length && next[ 0 ][ 0 ] == ">" ) { + var b = next.shift(); + block += block.trailing + b; + block.trailing = b.trailing; + } + + // Strip off the leading "> " and re-process as a block. + var input = block.replace( /^> ?/gm, '' ), + old_tree = this.tree; + jsonml.push( this.toTree( input, [ "blockquote" ] ) ); + + return jsonml; + }, + + referenceDefn: function referenceDefn( block, next) { + var re = /^\s*\[(.*?)\]:\s*(\S+)(?:\s+(?:(['"])(.*?)\3|\((.*?)\)))?\n?/; + // interesting matches are [ , ref_id, url, , title, title ] + + if ( !block.match(re) ) + return undefined; + + // make an attribute node if it doesn't exist + if ( !extract_attr( this.tree ) ) { + this.tree.splice( 1, 0, {} ); + } + + var attrs = extract_attr( this.tree ); + + // make a references hash if it doesn't exist + if ( attrs.references === undefined ) { + attrs.references = {}; + } + + var b = this.loop_re_over_block(re, block, function( m ) { + + if ( m[2] && m[2][0] == '<' && m[2][m[2].length-1] == '>' ) + m[2] = m[2].substring( 1, m[2].length - 1 ); + + var ref = attrs.references[ m[1].toLowerCase() ] = { + href: m[2] + }; + + if (m[4] !== undefined) + ref.title = m[4]; + else if (m[5] !== undefined) + ref.title = m[5]; + + } ); + + if (b.length) + next.unshift( mk_block( b, block.trailing ) ); + + return []; + }, + + para: function para( block, next ) { + // everything's a para! + return [ ["para"].concat( this.processInline( block ) ) ]; + } + } +} + +Markdown.dialects.Gruber.inline = { + __call__: function inline( text, patterns ) { + // Hmmm - should this function be directly in Md#processInline, or + // conversely, should Md#processBlock be moved into block.__call__ too + var out = [ ], + m, + // Look for the next occurange of a special character/pattern + re = new RegExp( "([\\s\\S]*?)(" + (patterns.source || patterns) + ")", "g" ), + lastIndex = 0; + + //D:var self = this; + //D:self.debug("processInline:", uneval(text) ); + function add(x) { + //D:self.debug(" adding output", uneval(x)); + if (typeof x == "string" && typeof out[out.length-1] == "string") + out[ out.length-1 ] += x; + else + out.push(x); + } + + while ( ( m = re.exec(text) ) != null) { + if ( m[1] ) add( m[1] ); // Some un-interesting text matched + else m[1] = { length: 0 }; // Or there was none, but make m[1].length == 0 + + var res; + if ( m[2] in this.dialect.inline ) { + res = this.dialect.inline[ m[2] ].call( + this, + text.substr( m.index + m[1].length ), m, out ); + } + // Default for now to make dev easier. just slurp special and output it. + res = res || [ m[2].length, m[2] ]; + + var len = res.shift(); + // Update how much input was consumed + re.lastIndex += ( len - m[2].length ); + + // Add children + res.forEach(add); + + lastIndex = re.lastIndex; + } + + // Add last 'boring' chunk + if ( text.length > lastIndex ) + add( text.substr( lastIndex ) ); + + return out; + }, + + "\\": function escaped( text ) { + // [ length of input processed, node/children to add... ] + // Only esacape: \ ` * _ { } [ ] ( ) # * + - . ! + if ( text.match( /^\\[\\`\*_{}\[\]()#\+.!\-]/ ) ) + return [ 2, text[1] ]; + else + // Not an esacpe + return [ 1, "\\" ]; + }, + + " + // 1 2 3 4 <--- captures + var m = text.match( /^!\[(.*?)\][ \t]*\([ \t]*(\S*)(?:[ \t]+(["'])(.*?)\3)?[ \t]*\)/ ); + + if ( m ) { + if ( m[2] && m[2][0] == '<' && m[2][m[2].length-1] == '>' ) + m[2] = m[2].substring( 1, m[2].length - 1 ); + + m[2] == this.dialect.inline.__call__.call( this, m[2], /\\/ )[0]; + + var attrs = { alt: m[1], href: m[2] || "" }; + if ( m[4] !== undefined) + attrs.title = m[4]; + + return [ m[0].length, [ "img", attrs ] ]; + } + + // ![Alt text][id] + m = text.match( /^!\[(.*?)\][ \t]*\[(.*?)\]/ ); + + if ( m ) { + // We can't check if the reference is known here as it likely wont be + // found till after. Check it in md tree->hmtl tree conversion + return [ m[0].length, [ "img_ref", { alt: m[1], ref: m[2].toLowerCase(), text: m[0] } ] ]; + } + + // Just consume the '![' + return [ 2, "![" ]; + }, + + "[": function link( text ) { + // [link text](/path/to/img.jpg "Optional title") + // 1 2 3 4 <--- captures + var m = text.match( /^\[([\s\S]*?)\][ \t]*\([ \t]*(\S+)(?:[ \t]+(["'])(.*?)\3)?[ \t]*\)/ ); + + if ( m ) { + if ( m[2] && m[2][0] == '<' && m[2][m[2].length-1] == '>' ) + m[2] = m[2].substring( 1, m[2].length - 1 ); + + // Process escapes only + m[2] = this.dialect.inline.__call__.call( this, m[2], /\\/ )[0]; + + var attrs = { href: m[2] || "" }; + if ( m[4] !== undefined) + attrs.title = m[4]; + + return [ m[0].length, [ "link", attrs, m[1] ] ]; + } + + // [Alt text][id] + // [Alt text] [id] + // [id] + m = text.match( /^\[([\s\S]*?)\](?: ?\[(.*?)\])?/ ); + + if ( m ) { + // [id] case, text == id + if ( m[2] === undefined || m[2] === "" ) m[2] = m[1]; + + // We can't check if the reference is known here as it likely wont be + // found till after. Check it in md tree->hmtl tree conversion. + // Store the original so that conversion can revert if the ref isn't found. + return [ + m[ 0 ].length, + [ + "link_ref", + { + ref: m[ 2 ].toLowerCase(), + original: m[ 0 ] + }, + m[ 1 ] + ] + ]; + } + + // Just consume the '[' + return [ 1, "[" ]; + }, + + + "<": function autoLink( text ) { + var m; + + if ( ( m = text.match( /^<(?:((https?|ftp|mailto):[^>]+)|(.*?@.*?\.[a-zA-Z]+))>/ ) ) != null ) { + if ( m[3] ) { + return [ m[0].length, [ "link", { href: "mailto:" + m[3] }, m[3] ] ]; + + } + else if ( m[2] == "mailto" ) { + return [ m[0].length, [ "link", { href: m[1] }, m[1].substr("mailto:".length ) ] ]; + } + else + return [ m[0].length, [ "link", { href: m[1] }, m[1] ] ]; + } + + return [ 1, "<" ]; + }, + + "`": function inlineCode( text ) { + // Inline code block. as many backticks as you like to start it + // Always skip over the opening ticks. + var m = text.match( /(`+)(([\s\S]*?)\1)/ ); + + if ( m && m[2] ) + return [ m[1].length + m[2].length, [ "inlinecode", m[3] ] ]; + else { + // TODO: No matching end code found - warn! + return [ 1, "`" ]; + } + }, + + " \n": function lineBreak( text ) { + return [ 3, [ "linebreak" ] ]; + } + +} + +// Meta Helper/generator method for em and strong handling +function strong_em( tag, md ) { + + var state_slot = tag + "_state", + other_slot = tag == "strong" ? "em_state" : "strong_state"; + + function CloseTag(len) { + this.len_after = len; + this.name = "close_" + md; + } + + return function ( text, orig_match ) { + + if (this[state_slot][0] == md) { + // Most recent em is of this type + //D:this.debug("closing", md); + this[state_slot].shift(); + + // "Consume" everything to go back to the recrusion in the else-block below + return[ text.length, new CloseTag(text.length-md.length) ]; + } + else { + // Store a clone of the em/strong states + var other = this[other_slot].slice(), + state = this[state_slot].slice(); + + this[state_slot].unshift(md); + + //D:this.debug_indent += " "; + + // Recurse + var res = this.processInline( text.substr( md.length ) ); + //D:this.debug_indent = this.debug_indent.substr(2); + + var last = res[res.length - 1]; + + //D:this.debug("processInline from", tag + ": ", uneval( res ) ); + + var check = this[state_slot].shift(); + if (last instanceof CloseTag) { + res.pop(); + // We matched! Huzzah. + var consumed = text.length - last.len_after; + return [ consumed, [ tag ].concat(res) ]; + } + else { + // Restore the state of the other kind. We might have mistakenly closed it. + this[other_slot] = other; + this[state_slot] = state; + + // We can't reuse the processed result as it could have wrong parsing contexts in it. + return [ md.length, md ]; + } + } + } // End returned function +} + +Markdown.dialects.Gruber.inline["**"] = strong_em("strong", "**"); +Markdown.dialects.Gruber.inline["__"] = strong_em("strong", "__"); +Markdown.dialects.Gruber.inline["*"] = strong_em("em", "*"); +Markdown.dialects.Gruber.inline["_"] = strong_em("em", "_"); + + +// Build default order from insertion order. +Markdown.buildBlockOrder = function(d) { + var ord = []; + for ( var i in d ) { + if ( i == "__order__" || i == "__call__" ) continue; + ord.push( i ); + } + d.__order__ = ord; +} + +// Build patterns for inline matcher +Markdown.buildInlinePatterns = function(d) { + var patterns = []; + + for ( var i in d ) { + if (i == "__call__") continue; + var l = i.replace( /([\\.*+?|()\[\]{}])/g, "\\$1" ) + .replace( /\n/, "\\n" ); + patterns.push( i.length == 1 ? l : "(?:" + l + ")" ); + } + + patterns = patterns.join("|"); + //print("patterns:", uneval( patterns ) ); + + var fn = d.__call__; + d.__call__ = function(text, pattern) { + if (pattern != undefined) + return fn.call(this, text, pattern); + else + return fn.call(this, text, patterns); + } +} + +// Helper function to make sub-classing a dialect easier +Markdown.subclassDialect = function( d ) { + function Block() {}; + Block.prototype = d.block; + function Inline() {}; + Inline.prototype = d.inline; + + return { block: new Block(), inline: new Inline() }; +} + +Markdown.buildBlockOrder ( Markdown.dialects.Gruber.block ); +Markdown.buildInlinePatterns( Markdown.dialects.Gruber.inline ); + +Markdown.dialects.Maruku = Markdown.subclassDialect( Markdown.dialects.Gruber ); + +Markdown.dialects.Maruku.block.document_meta = function document_meta( block, next ) { + // we're only interested in the first block + if ( block.lineNumber > 1 ) return undefined; + + // document_meta blocks consist of one or more lines of `Key: Value\n` + if ( ! block.match( /^(?:\w+:.*\n)*\w+:.*$/ ) ) return undefined; + + // make an attribute node if it doesn't exist + if ( !extract_attr( this.tree ) ) { + this.tree.splice( 1, 0, {} ); + } + + var pairs = block.split( /\n/ ); + for ( p in pairs ) { + var m = pairs[ p ].match( /(\w+):\s*(.*)$/ ), + key = m[ 1 ].toLowerCase(), + value = m[ 2 ]; + + this.tree[ 1 ][ key ] = value; + } + + // document_meta produces no content! + return []; +} + +Markdown.dialects.Maruku.block.block_meta = function block_meta( block, next ) { + // check if the last line of the block is an meta hash + var m = block.match( /(^|\n) {0,3}\{:\s*((?:\\\}|[^\}])*)\s*\}$/ ); + if ( !m ) return undefined; + + // process the meta hash + var attr = process_meta_hash( m[ 2 ] ); + + // if we matched ^ then we need to apply meta to the previous block + if ( m[ 1 ] === "" ) { + var node = this.tree[ this.tree.length - 1 ], + hash = extract_attr( node ); + + // if the node is a string (rather than JsonML), bail + if ( typeof node === "string" ) return undefined; + + // create the attribute hash if it doesn't exist + if ( !hash ) { + hash = {}; + node.splice( 1, 0, hash ); + } + + // add the attributes in + for ( a in attr ) { + hash[ a ] = attr[ a ]; + } + + // return nothing so the meta hash is removed + return []; + } + + // pull the meta hash off the block and process what's left + var b = block.replace( /\n.*$/, "" ), + result = this.processBlock( b, [] ); + + // get or make the attributes hash + var hash = extract_attr( result[ 0 ] ); + if ( !hash ) { + hash = {}; + result[ 0 ].splice( 1, 0, hash ); + } + + // attach the attributes to the block + for ( a in attr ) { + hash[ a ] = attr[ a ]; + } + + return result; +} + +Markdown.dialects.Maruku.block.definition_list = function definition_list( block, next ) { + // one or more terms followed by one or more definitions, in a single block + var tight = /^((?:[^\s:].*\n)+):\s+([^]+)$/, + list = [ "dl" ]; + + // see if we're dealing with a tight or loose block + if ( ( m = block.match( tight ) ) ) { + // pull subsequent tight DL blocks out of `next` + var blocks = [ block ]; + while ( next.length && tight.exec( next[ 0 ] ) ) { + blocks.push( next.shift() ); + } + + for ( var b = 0; b < blocks.length; ++b ) { + var m = blocks[ b ].match( tight ), + terms = m[ 1 ].replace( /\n$/, "" ).split( /\n/ ), + defns = m[ 2 ].split( /\n:\s+/ ); + + // print( uneval( m ) ); + + for ( var i = 0; i < terms.length; ++i ) { + list.push( [ "dt", terms[ i ] ] ); + } + + for ( var i = 0; i < defns.length; ++i ) { + // run inline processing over the definition + list.push( [ "dd" ].concat( this.processInline( defns[ i ].replace( /(\n)\s+/, "$1" ) ) ) ); + } + } + } + else { + return undefined; + } + + return [ list ]; +} + +Markdown.dialects.Maruku.inline[ "{:" ] = function inline_meta( text, matches, out ) { + if ( !out.length ) { + return [ 2, "{:" ]; + } + + // get the preceeding element + var before = out[ out.length - 1 ]; + + if ( typeof before === "string" ) { + return [ 2, "{:" ]; + } + + // match a meta hash + var m = text.match( /^\{:\s*((?:\\\}|[^\}])*)\s*\}/ ); + + // no match, false alarm + if ( !m ) { + return [ 2, "{:" ]; + } + + // attach the attributes to the preceeding element + var meta = process_meta_hash( m[ 1 ] ), + attr = extract_attr( before ); + + if ( !attr ) { + attr = {}; + before.splice( 1, 0, attr ); + } + + for ( var k in meta ) { + attr[ k ] = meta[ k ]; + } + + // cut out the string and replace it with nothing + return [ m[ 0 ].length, "" ]; +} + +Markdown.buildBlockOrder ( Markdown.dialects.Maruku.block ); +Markdown.buildInlinePatterns( Markdown.dialects.Maruku.inline ); + +function extract_attr( jsonml ) { + return jsonml instanceof Array + && jsonml.length > 1 + && typeof jsonml[ 1 ] === "object" + && !( jsonml[ 1 ] instanceof Array ) + ? jsonml[ 1 ] + : undefined; +} + +function process_meta_hash( meta_string ) { + var meta = split_meta_hash( meta_string ), + attr = {}; + + for ( var i = 0; i < meta.length; ++i ) { + // id: #foo + if ( /^#/.test( meta[ i ] ) ) { + attr.id = meta[ i ].substring( 1 ); + } + // class: .foo + else if ( /^\./.test( meta[ i ] ) ) { + // if class already exists, append the new one + if ( attr['class'] ) { + attr['class'] = attr['class'] + meta[ i ].replace( /./, " " ); + } + else { + attr['class'] = meta[ i ].substring( 1 ); + } + } + // attribute: foo=bar + else if ( /=/.test( meta[ i ] ) ) { + var s = meta[ i ].split( /=/ ); + attr[ s[ 0 ] ] = s[ 1 ]; + } + } + + return attr; +} + +function split_meta_hash( meta_string ) { + var meta = meta_string.split( "" ), + parts = [ "" ], + in_quotes = false; + + while ( meta.length ) { + var letter = meta.shift(); + switch ( letter ) { + case " " : + // if we're in a quoted section, keep it + if ( in_quotes ) { + parts[ parts.length - 1 ] += letter; + } + // otherwise make a new part + else { + parts.push( "" ); + } + break; + case "'" : + case '"' : + // reverse the quotes and move straight on + in_quotes = !in_quotes; + break; + case "\\" : + // shift off the next letter to be used straight away. + // it was escaped so we'll keep it whatever it is + letter = meta.shift(); + default : + parts[ parts.length - 1 ] += letter; + break; + } + } + + return parts; +} + +/** + * renderJsonML( jsonml[, options] ) -> String + * - jsonml (Array): JsonML array to render to XML + * - options (Object): options + * + * Converts the given JsonML into well-formed XML. + * + * The options currently understood are: + * + * - root (Boolean): wether or not the root node should be included in the + * output, or just its children. The default `false` is to not include the + * root itself. + */ +expose.renderJsonML = function( jsonml, options ) { + options = options || {}; + // include the root element in the rendered output? + options.root = options.root || false; + + var content = []; + + if ( options.root ) { + content.push( render_tree( jsonml ) ); + } + else { + jsonml.shift(); // get rid of the tag + if ( jsonml.length && typeof jsonml[ 0 ] === "object" && !( jsonml[ 0 ] instanceof Array ) ) { + jsonml.shift(); // get rid of the attributes + } + + while ( jsonml.length ) { + content.push( render_tree( jsonml.shift() ) ); + } + } + + return content.join( "\n\n" ); +} + +function render_tree( jsonml ) { + // basic case + if ( typeof jsonml === "string" ) { + return jsonml.replace( /&/g, "&" ) + .replace( /</g, "<" ) + .replace( />/g, ">" ); + } + + var tag = jsonml.shift(), + attributes = {}, + content = []; + + if ( jsonml.length && typeof jsonml[ 0 ] === "object" && !( jsonml[ 0 ] instanceof Array ) ) { + attributes = jsonml.shift(); + } + + while ( jsonml.length ) { + content.push( arguments.callee( jsonml.shift() ) ); + } + + var tag_attrs = ""; + for ( var a in attributes ) { + tag_attrs += " " + a + '="' + attributes[ a ] + '"'; + } + + // be careful about adding whitespace here for inline elements + return "<"+ tag + tag_attrs + ">" + content.join( "" ) + "</" + tag + ">"; +} + +function convert_tree_to_html( tree, references ) { + // shallow clone + var jsonml = tree.slice( 0 ); + + // Clone attributes if the exist + var attrs = extract_attr( jsonml ); + if ( attrs ) { + jsonml[ 1 ] = {}; + for ( var i in attrs ) { + jsonml[ 1 ][ i ] = attrs[ i ]; + } + attrs = jsonml[ 1 ]; + } + + // basic case + if ( typeof jsonml === "string" ) { + return jsonml; + } + + // convert this node + switch ( jsonml[ 0 ] ) { + case "header": + jsonml[ 0 ] = "h" + jsonml[ 1 ].level; + delete jsonml[ 1 ].level; + break; + case "bulletlist": + jsonml[ 0 ] = "ul"; + break; + case "numberlist": + jsonml[ 0 ] = "ol"; + break; + case "listitem": + jsonml[ 0 ] = "li"; + break; + case "para": + jsonml[ 0 ] = "p"; + break; + case "markdown": + jsonml[ 0 ] = "html"; + if ( attrs ) delete attrs.references; + break; + case "code_block": + jsonml[ 0 ] = "pre"; + var i = attrs ? 2 : 1; + var code = [ "code" ]; + code.push.apply( code, jsonml.splice( i ) ); + jsonml[ i ] = code; + break; + case "inlinecode": + jsonml[ 0 ] = "code"; + break; + case "img": + jsonml[ 1 ].src = jsonml[ 1 ].href; + delete jsonml[ 1 ].href; + break; + case "linebreak": + jsonml[0] = "br"; + break; + case "link": + jsonml[ 0 ] = "a"; + break; + case "link_ref": + jsonml[ 0 ] = "a"; + + // grab this ref and clean up the attribute node + var ref = references[ attrs.ref ]; + + // if the reference exists, make the link + if ( ref ) { + delete attrs.ref; + + // add in the href and title, if present + attrs.href = ref.href; + if ( ref.title ) { + attrs.title = ref.title; + } + + // get rid of the unneeded original text + delete attrs.original; + } + // the reference doesn't exist, so revert to plain text + else { + return attrs.original; + } + break; + } + + // convert all the children + var i = 1; + + // deal with the attribute node, if it exists + if ( attrs ) { + // if there are keys, skip over it + for ( var key in jsonml[ 1 ] ) { + i = 2; + } + // if there aren't, remove it + if ( i === 1 ) { + jsonml.splice( i, 1 ); + } + } + + for ( ; i < jsonml.length; ++i ) { + jsonml[ i ] = arguments.callee( jsonml[ i ], references ); + } + + return jsonml; +} + + +// merges adjacent text nodes into a single node +function merge_text_nodes( jsonml ) { + // skip the tag name and attribute hash + var i = extract_attr( jsonml ) ? 2 : 1; + + while ( i < jsonml.length ) { + // if it's a string check the next item too + if ( typeof jsonml[ i ] === "string" ) { + if ( i + 1 < jsonml.length && typeof jsonml[ i + 1 ] === "string" ) { + // merge the second string into the first and remove it + jsonml[ i ] += jsonml.splice( i + 1, 1 )[ 0 ]; + } + else { + ++i; + } + } + // if it's not a string recurse + else { + arguments.callee( jsonml[ i ] ); + ++i; + } + } +} + +} )( (function() { + if ( typeof exports === "undefined" ) { + window.markdown = {}; + return window.markdown; + } + else { + return exports; + } +} )() );
\ No newline at end of file diff --git a/lib/mustache/LICENSE b/lib/mustache/LICENSE new file mode 100644 index 00000000..e06f8eea --- /dev/null +++ b/lib/mustache/LICENSE @@ -0,0 +1,21 @@ +Copyright (c) 2009 Chris Wanstrath (Ruby) +Copyright (c) 2010 Jan Lehnardt (JavaScript) + +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.
\ No newline at end of file diff --git a/lib/mustache/index.js b/lib/mustache/index.js new file mode 100644 index 00000000..292db14d --- /dev/null +++ b/lib/mustache/index.js @@ -0,0 +1,344 @@ +/* + * CommonJS-compatible mustache.js module + * + * See http://github.com/janl/mustache.js for more info. + */ +/* + mustache.js Logic-less templates in JavaScript + + See http://mustache.github.com/ for more info. +*/ + +var Mustache = function() { + var Renderer = function() {}; + + Renderer.prototype = { + otag: "{{", + ctag: "}}", + pragmas: {}, + buffer: [], + pragmas_implemented: { + "IMPLICIT-ITERATOR": true + }, + context: {}, + + render: function(template, context, partials, in_recursion) { + // reset buffer & set context + if(!in_recursion) { + this.context = context; + this.buffer = []; // TODO: make this non-lazy + } + + // fail fast + if(!this.includes("", template)) { + if(in_recursion) { + return template; + } else { + this.send(template); + return; + } + } + + template = this.render_pragmas(template); + var html = this.render_section(template, context, partials); + if(in_recursion) { + return this.render_tags(html, context, partials, in_recursion); + } + + this.render_tags(html, context, partials, in_recursion); + }, + + /* + Sends parsed lines + */ + send: function(line) { + if(line != "") { + this.buffer.push(line); + } + }, + + /* + Looks for %PRAGMAS + */ + render_pragmas: function(template) { + // no pragmas + if(!this.includes("%", template)) { + return template; + } + + var that = this; + var regex = new RegExp(this.otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" + + this.ctag); + return template.replace(regex, function(match, pragma, options) { + if(!that.pragmas_implemented[pragma]) { + throw({message: + "This implementation of mustache doesn't understand the '" + + pragma + "' pragma"}); + } + that.pragmas[pragma] = {}; + if(options) { + var opts = options.split("="); + that.pragmas[pragma][opts[0]] = opts[1]; + } + return ""; + // ignore unknown pragmas silently + }); + }, + + /* + Tries to find a partial in the curent scope and render it + */ + render_partial: function(name, context, partials) { + name = this.trim(name); + if(!partials || partials[name] === undefined) { + throw({message: "unknown_partial '" + name + "'"}); + } + if(typeof(context[name]) != "object") { + return this.render(partials[name], context, partials, true); + } + return this.render(partials[name], context[name], partials, true); + }, + + /* + Renders inverted (^) and normal (#) sections + */ + render_section: function(template, context, partials) { + if(!this.includes("#", template) && !this.includes("^", template)) { + return template; + } + + var that = this; + // CSW - Added "+?" so it finds the tighest bound, not the widest + var regex = new RegExp(this.otag + "(\\^|\\#)\\s*(.+)\\s*" + this.ctag + + "\n*([\\s\\S]+?)" + this.otag + "\\/\\s*\\2\\s*" + this.ctag + + "\\s*", "mg"); + + // for each {{#foo}}{{/foo}} section do... + return template.replace(regex, function(match, type, name, content) { + var value = that.find(name, context); + if(type == "^") { // inverted section + if(!value || that.is_array(value) && value.length === 0) { + // false or empty list, render it + return that.render(content, context, partials, true); + } else { + return ""; + } + } else if(type == "#") { // normal section + if(that.is_array(value)) { // Enumerable, Let's loop! + return that.map(value, function(row) { + return that.render(content, that.create_context(row), + partials, true); + }).join(""); + } else if(that.is_object(value)) { // Object, Use it as subcontext! + return that.render(content, that.create_context(value), + partials, true); + } else if(typeof value === "function") { + // higher order section + return value.call(context, content, function(text) { + return that.render(text, context, partials, true); + }); + } else if(value) { // boolean section + return that.render(content, context, partials, true); + } else { + return ""; + } + } + }); + }, + + /* + Replace {{foo}} and friends with values from our view + */ + render_tags: function(template, context, partials, in_recursion) { + // tit for tat + var that = this; + + var new_regex = function() { + return new RegExp(that.otag + "(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?" + + that.ctag + "+", "g"); + }; + + var regex = new_regex(); + var tag_replace_callback = function(match, operator, name) { + switch(operator) { + case "!": // ignore comments + return ""; + case "=": // set new delimiters, rebuild the replace regexp + that.set_delimiters(name); + regex = new_regex(); + return ""; + case ">": // render partial + return that.render_partial(name, context, partials); + case "{": // the triple mustache is unescaped + return that.find(name, context); + default: // escape the value + return that.escape(that.find(name, context)); + } + }; + var lines = template.split("\n"); + for(var i = 0; i < lines.length; i++) { + lines[i] = lines[i].replace(regex, tag_replace_callback, this); + if(!in_recursion) { + this.send(lines[i]); + } + } + + if(in_recursion) { + return lines.join("\n"); + } + }, + + set_delimiters: function(delimiters) { + var dels = delimiters.split(" "); + this.otag = this.escape_regex(dels[0]); + this.ctag = this.escape_regex(dels[1]); + }, + + escape_regex: function(text) { + // thank you Simon Willison + if(!arguments.callee.sRE) { + var specials = [ + '/', '.', '*', '+', '?', '|', + '(', ')', '[', ']', '{', '}', '\\' + ]; + arguments.callee.sRE = new RegExp( + '(\\' + specials.join('|\\') + ')', 'g' + ); + } + return text.replace(arguments.callee.sRE, '\\$1'); + }, + + /* + find `name` in current `context`. That is find me a value + from the view object + */ + find: function(name, context) { + name = this.trim(name); + + // Checks whether a value is thruthy or false or 0 + function is_kinda_truthy(bool) { + return bool === false || bool === 0 || bool; + } + + var value = context; + var path = name.split(/\./); + for(var i = 0; i < path.length; i++) { + name = path[i]; + if(value && is_kinda_truthy(value[name])) { + value = value[name]; + } else if(i == 0 && is_kinda_truthy(this.context[name])) { + value = this.context[name]; + } else { + value = undefined; + } + } + + if(typeof value === "function") { + return value.apply(context); + } + if(value !== undefined) { + return value; + } + // silently ignore unkown variables + return ""; + }, + + // Utility methods + + /* includes tag */ + includes: function(needle, haystack) { + return haystack.indexOf(this.otag + needle) != -1; + }, + + /* + Does away with nasty characters + */ + escape: function(s) { + s = String(s === null ? "" : s); + return s.replace(/&(?!\w+;)|["'<>\\]/g, function(s) { + switch(s) { + case "&": return "&"; + case "\\": return "\\\\"; + case '"': return '"'; + case "'": return '''; + case "<": return "<"; + case ">": return ">"; + default: return s; + } + }); + }, + + // by @langalex, support for arrays of strings + create_context: function(_context) { + if(this.is_object(_context)) { + return _context; + } else { + var iterator = "."; + if(this.pragmas["IMPLICIT-ITERATOR"]) { + iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator; + } + var ctx = {}; + ctx[iterator] = _context; + return ctx; + } + }, + + is_object: function(a) { + return a && typeof a == "object"; + }, + + is_array: function(a) { + return Object.prototype.toString.call(a) === '[object Array]'; + }, + + /* + Gets rid of leading and trailing whitespace + */ + trim: function(s) { + return s.replace(/^\s*|\s*$/g, ""); + }, + + /* + Why, why, why? Because IE. Cry, cry cry. + */ + map: function(array, fn) { + if (typeof array.map == "function") { + return array.map(fn); + } else { + var r = []; + var l = array.length; + for(var i = 0; i < l; i++) { + r.push(fn(array[i])); + } + return r; + } + } + }; + + return({ + name: "mustache.js", + version: "0.3.1-dev", + + /* + Turns a template and view into HTML + */ + to_html: function(template, view, partials, send_fun) { + var renderer = new Renderer(); + if(send_fun) { + renderer.send = send_fun; + } + renderer.render(template, view, partials); + if(!send_fun) { + return renderer.buffer.join("\n"); + } + } + }); +}(); + + +exports.name = Mustache.name; +exports.version = Mustache.version; + +exports.to_html = function() { + return Mustache.to_html.apply(this, arguments); +};
\ No newline at end of file diff --git a/lib/nodeserver/server.js b/lib/nodeserver/server.js index f91f6afa..bcaed299 100644 --- a/lib/nodeserver/server.js +++ b/lib/nodeserver/server.js @@ -84,12 +84,14 @@ StaticServlet.MimeMap = { StaticServlet.prototype.handleRequest = function(req, res) { var self = this; - var path = ('./' + req.url.pathname).replace('//','/'); + var path = ('./' + req.url.pathname).replace('//','/').replace(/%(..)/, function(match, hex){ + return String.fromCharCode(parseInt(hex, 16)); + }); var parts = path.split('/'); if (parts[parts.length-1].charAt(0) === '.') return self.sendForbidden_(req, res, path); fs.stat(path, function(err, stat) { - if (err) + if (err) return self.sendMissing_(req, res, path); if (stat.isDirectory()) return self.sendDirectory_(req, res, path); @@ -118,8 +120,8 @@ StaticServlet.prototype.sendMissing_ = function(req, res, path) { res.write('<title>404 Not Found</title>\n'); res.write('<h1>Not Found</h1>'); res.write( - '<p>The requested URL ' + - escapeHtml(path) + + '<p>The requested URL ' + + escapeHtml(path) + ' was not found on this server.</p>' ); res.end(); @@ -135,7 +137,7 @@ StaticServlet.prototype.sendForbidden_ = function(req, res, path) { res.write('<title>403 Forbidden</title>\n'); res.write('<h1>Forbidden</h1>'); res.write( - '<p>You do not have permission to access ' + + '<p>You do not have permission to access ' + escapeHtml(path) + ' on this server.</p>' ); res.end(); @@ -151,8 +153,8 @@ StaticServlet.prototype.sendRedirect_ = function(req, res, redirectUrl) { res.write('<title>301 Moved Permanently</title>\n'); res.write('<h1>Moved Permanently</h1>'); res.write( - '<p>The document has moved <a href="' + - redirectUrl + + '<p>The document has moved <a href="' + + redirectUrl + '">here</a>.</p>' ); res.end(); @@ -187,7 +189,7 @@ StaticServlet.prototype.sendDirectory_ = function(req, res, path) { return self.sendRedirect_(req, res, redirectUrl); } fs.readdir(path, function(err, files) { - if (err) + if (err) return self.sendError_(req, res, error); if (!files.length) @@ -235,5 +237,5 @@ StaticServlet.prototype.writeDirectoryIndex_ = function(req, res, path, files) { res.end(); }; -// Must be last, +// Must be last, main(process.argv); diff --git a/src/Angular.js b/src/Angular.js index 62cfdef6..004ab48f 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -10,7 +10,7 @@ if (typeof document.getAttribute == $undefined) * * @description Converts string to lowercase * @param {string} value - * @return {string} Lowercased string. + * @returns {string} Lowercased string. */ var lowercase = function (value){ return isString(value) ? value.toLowerCase() : value; }; @@ -22,7 +22,7 @@ var lowercase = function (value){ return isString(value) ? value.toLowerCase() : * * @description Converts string to uppercase. * @param {string} value - * @return {string} Uppercased string. + * @returns {string} Uppercased string. */ var uppercase = function (value){ return isString(value) ? value.toUpperCase() : value; }; @@ -100,7 +100,7 @@ var _undefined = undefined, * @ngdoc overview * @name angular.filter * @namespace Namespace for all filters. - * + * @description * # Overview * Filters are a standard way to format your data for display to the user. For example, you * might have the number 1234.5678 and would like to display it as US currency: $1,234.57. @@ -364,7 +364,7 @@ function isLeafNode (node) { * @param {*} source The source to be used during copy. * Can be any type including primitives, null and undefined. * @param {(Object|Array)=} destination Optional destination into which the source is copied - * @return {*} + * @returns {*} */ function copy(source, destination){ if (!destination) { @@ -507,7 +507,7 @@ function compile(element, existingScope) { /** * Parses an escaped url query string into key-value pairs. - * @return Object.<(string|boolean)> + * @returns Object.<(string|boolean)> */ function parseKeyValue(/**string*/keyValue) { var obj = {}, key_value, key; diff --git a/src/filters.js b/src/filters.js index ed824f93..767c1234 100644 --- a/src/filters.js +++ b/src/filters.js @@ -6,24 +6,25 @@ * @description * Formats a number as a currency (ie $1,234.56). * - * @param {number} amout Input to filter. + * @param {number} amount Input to filter. * @returns {string} Formated number. * * @css ng-format-negative * When the value is negative, this css class is applied to the binding making it by default red. - * + * * @example * <input type="text" name="amount" value="1234.56"/> <br/> * {{amount | currency}} * * @scenario * it('should init with 1234.56', function(){ - * expect(bind('amount')).toEqual('$1,234.56'); + * expect(binding('amount')).toEqual('$1,234.56'); * }); * it('should update', function(){ - * element(':input[name=amount]').value('-1234'); - * expect(bind('amount')).toEqual('-$1,234.00'); - * expect(bind('amount')).toHaveColor('red'); + * input('amount').enter('-1234'); + * expect(binding('amount')).toEqual('$-1,234.00'); + * // TODO: implement + * // expect(binding('amount')).toHaveColor('red'); * }); */ angularFilter.currency = function(amount){ @@ -31,7 +32,6 @@ angularFilter.currency = function(amount){ return '$' + angularFilter['number'].apply(this, [amount, 2]); }; - /** * @ngdoc filter * @name angular.filter.number @@ -43,7 +43,7 @@ angularFilter.currency = function(amount){ * If the input is not a number empty string is returned. * * @param {(number|string)} number Number to format. - * @param {(number|string)=} fractionSize Number of decimal places to round the number to. Default 2. + * @param {(number|string)=2} fractionSize Number of decimal places to round the number to. Default 2. * @returns {string} Number rounded to decimalPlaces and places a “,” after each third digit. * * @example @@ -54,10 +54,10 @@ angularFilter.currency = function(amount){ * * @scenario * it('should format numbers', function(){ - * expect(element('span[ng\\:bind="1234.56789|number"]').val()).toBe('1,234.57'); - * expect(element('span[ng\\:bind="1234.56789|number:0"]').val()).toBe('1,234'); - * expect(element('span[ng\\:bind="1234.56789|number:2"]').val()).toBe('1,234.56'); - * expect(element('span[ng\\:bind="-1234.56789|number:4"]').val()).toBe('-1,234.56789'); + * expect(binding('1234.56789|number')).toEqual('1,234.57'); + * expect(binding('1234.56789|number:0')).toEqual('1,235'); + * expect(binding('1234.56789|number:2')).toEqual('1,234.57'); + * expect(binding('-1234.56789|number:4')).toEqual('-1,234.5679'); * }); */ angularFilter.number = function(number, fractionSize){ @@ -204,10 +204,10 @@ angularFilter.date = function(date, format) { * * @scenario * it('should jsonify filtered objects', function() { - * expect(element('pre[ng\\:bind-template="{{ {a:1, b:[]} | json }}"]').val()).toBe( + * expect(binding('{{ {a:1, b:[]} | json')).toEqual( * '{\n "a":1,\n "b":[]}' * ); - * } + * }); * */ angularFilter.json = function(object) { @@ -257,7 +257,7 @@ angularFilter.uppercase = uppercase; * filtered and you can't get the content through the sanitizer. * * @param {string} html Html input. - * @param {string=} option If 'unsafe' then do not sanitize the HTML input. + * @param {string='safe'} option If 'unsafe' then do not sanitize the HTML input. * @returns {string} Sanitized or raw html. */ angularFilter.html = function(html, option){ diff --git a/src/widgets.js b/src/widgets.js index 4d8c71d5..5eda5345 100644 --- a/src/widgets.js +++ b/src/widgets.js @@ -1,3 +1,7 @@ +/** + * + */ + function modelAccessor(scope, element) { var expr = element.attr('name'); if (!expr) throw "Required field 'name' not found."; @@ -240,6 +244,16 @@ angularWidget('option', function(){ }); +/*ng:doc + * @type widget + * @name ng:include + * + * @description + * + * @example + * + * @scenario + */ angularWidget('ng:include', function(element){ var compiler = this, srcExp = element.attr("src"), |
