diff --git a/docs/galaxy.tools.linters.rst b/docs/galaxy.tools.linters.rst
index 05d4326d6db4e8c8bb3b932e5458139658d94046..2dd3a00996570262f82e7626e77406e9d6eca747 100644
--- a/docs/galaxy.tools.linters.rst
+++ b/docs/galaxy.tools.linters.rst
@@ -12,6 +12,14 @@ galaxy.tools.linters.citations module
     :undoc-members:
     :show-inheritance:
 
+galaxy.tools.linters.command module
+-----------------------------------
+
+.. automodule:: galaxy.tools.linters.command
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
 galaxy.tools.linters.help module
 --------------------------------
 
diff --git a/galaxy/tools/lint.py b/galaxy/tools/lint.py
index 4682629cb159252205e719088c3f0aaf3220c3f0..895d0a6cbb63eb0278040b843d5462b405b131c9 100644
--- a/galaxy/tools/lint.py
+++ b/galaxy/tools/lint.py
@@ -31,6 +31,7 @@ class LintContext(object):
         self.found_warns = False
 
     def lint(self, module, name, lint_func, tool_xml):
+        name = name.replace("tsts", "tests")
         self.printed_linter_info = False
         self.valid_messages = []
         self.info_messages = []
@@ -51,7 +52,7 @@ class LintContext(object):
                 return
             self.printed_linter_info = True
             print("Applying linter %s... %s" % (name, status))
-        
+
         for message in self.error_messages:
             self.found_errors = True
             print_linter_info()
diff --git a/galaxy/tools/linters/citations.py b/galaxy/tools/linters/citations.py
index 1811563b3d2cd41ea1a0c7971934e7f3db43a1e7..2ff7e8c4cb14edf17b0614c257484a1a72485e5d 100644
--- a/galaxy/tools/linters/citations.py
+++ b/galaxy/tools/linters/citations.py
@@ -12,7 +12,7 @@ def lint_citations(tool_xml, lint_ctx):
         return
 
     valid_citations = 0
-    for citation in citations.children():
+    for citation in citations[0]:
         if citation.tag != "citation":
             lint_ctx.warn("Unknown tag discovered in citations block [%s], will be ignored." % citation.tag)
         if "type" in citation.attrib:
diff --git a/galaxy/tools/linters/command.py b/galaxy/tools/linters/command.py
new file mode 100644
index 0000000000000000000000000000000000000000..c2a355333dafbf4b156cc1392a11c3c81f84c20d
--- /dev/null
+++ b/galaxy/tools/linters/command.py
@@ -0,0 +1,18 @@
+
+
+def lint_command(tool_xml, lint_ctx):
+    root = tool_xml.getroot()
+    commands = root.findall("command")
+    if len(commands) > 1:
+        lint_ctx.error("More than one command tag found, behavior undefined.")
+        return
+
+    if len(commands) == 0:
+        lint_ctx.error("No command tag found, must specify a command template to execute.")
+        return
+
+    command = commands[0]
+    if "TODO" in command:
+        lint_ctx.warn("Command template contains TODO text.")
+
+    lint_ctx.info("Tool contains a command.")
diff --git a/galaxy/tools/linters/help.py b/galaxy/tools/linters/help.py
index 28ae675d114e45377510d707d738778dfb3ed2c9..ca13197e58b0718491b15080c987ae3945f1235d 100644
--- a/galaxy/tools/linters/help.py
+++ b/galaxy/tools/linters/help.py
@@ -1,3 +1,4 @@
+from galaxy.util import rst_to_html
 
 
 def lint_help(tool_xml, lint_ctx):
@@ -11,5 +12,22 @@ def lint_help(tool_xml, lint_ctx):
         lint_ctx.warn("No help section found, consider adding a help section to your tool.")
         return
 
-    # TODO: validate help section RST.
+    help = helps[0].text
+    if not help.strip():
+        lint_ctx.warn("Help section appears to be empty.")
+        return
+
     lint_ctx.valid("Tool contains help section.")
+    invalid_rst = False
+    try:
+        rst_to_html(help)
+    except Exception as e:
+        invalid_rst = str(e)
+
+    if "TODO" in help:
+        lint_ctx.warn("Help contains TODO text.")
+
+    if invalid_rst:
+        lint_ctx.warn("Invalid reStructuredText found in help - [%s]." % invalid_rst)
+    else:
+        lint_ctx.valid("Help contains valid reStructuredText.")
diff --git a/galaxy/tools/linters/outputs.py b/galaxy/tools/linters/outputs.py
index 2891985043f2f4cdb49a8f7d891eb3922afd43f8..40e086cbaadcfaa76617f73aa778293e4dbb12fc 100644
--- a/galaxy/tools/linters/outputs.py
+++ b/galaxy/tools/linters/outputs.py
@@ -3,7 +3,7 @@
 def lint_output(tool_xml, lint_ctx):
     outputs = tool_xml.findall("./outputs/data")
     if not outputs:
-        lint_ctx.warn("Tool contains no outputs, most tools should produce outputs..")
+        lint_ctx.warn("Tool contains no outputs, most tools should produce outputs.")
         return
 
     num_outputs = 0
@@ -15,7 +15,7 @@ def lint_output(tool_xml, lint_ctx):
             format_set = True
             format = output_attrib["format"]
             if format == "input":
-                lint_ctx.warn("Using format='input' on output data, format_source attribute is less ambigious and should be used instead.")
+                lint_ctx.warn("Using format='input' on output data, format_source attribute is less ambiguous and should be used instead.")
         elif "format_source" in output_attrib:
             format_set = True
 
diff --git a/galaxy/tools/loader.py b/galaxy/tools/loader.py
index a17a3a5fc6e93b54d955c4eef8f535c38808371a..42fc53105ba50e07c1dd8c4b43adfcf8141b2db7 100644
--- a/galaxy/tools/loader.py
+++ b/galaxy/tools/loader.py
@@ -120,6 +120,7 @@ def _expand_macro(element, expand_el, macros):
     # HACK for elementtree, newer implementations (etree/lxml) won't
     # require this parent_map data structure but elementtree does not
     # track parents or recongnize .find('..').
+    # TODO fix this now that we're not using elementtree
     parent_map = dict((c, p) for p in element.getiterator() for c in p)
     _xml_replace(expand_el, macro_def, parent_map)
 
diff --git a/galaxy/tools/loader_directory.py b/galaxy/tools/loader_directory.py
index 95508318a24f9ab1f3b1285f71dd1ee3ca6dd90c..8b473a93ee7e79d51a2c39db76620298f33abd4f 100644
--- a/galaxy/tools/loader_directory.py
+++ b/galaxy/tools/loader_directory.py
@@ -2,21 +2,44 @@ import glob
 import os
 from ..tools import loader
 
+import sys
+
+import logging
+log = logging.getLogger(__name__)
+
 PATH_DOES_NOT_EXIST_ERROR = "Could not load tools from path [%s] - this path does not exist."
+LOAD_FAILURE_ERROR = "Failed to load tool with path %s."
+
 
+def load_exception_handler(path, exc_info):
+    log.warn(LOAD_FAILURE_ERROR % path, exc_info=exc_info)
 
-def load_tool_elements_from_path(path):
+
+def load_tool_elements_from_path(path, load_exception_handler=load_exception_handler):
     tool_elements = []
     for file in __find_tool_files(path):
-        if __looks_like_a_tool(file):
-            tool_elements.append((file, loader.load_tool(file)))
+        try:
+            looks_like_a_tool = __looks_like_a_tool(file)
+        except IOError:
+            # Some problem reading the tool file, skip.
+            continue
+
+        if looks_like_a_tool:
+            try:
+                tool_elements.append((file, loader.load_tool(file)))
+            except Exception:
+                exc_info = sys.exc_info()
+                load_exception_handler(file, exc_info)
     return tool_elements
 
 
 def __looks_like_a_tool(path):
     with open(path) as f:
         for i in range(10):
-            line = f.next()
+            try:
+                line = f.next()
+            except StopIteration:
+                break
             if "<tool" in line:
                 return True
     return False
diff --git a/galaxy/util/__init__.py b/galaxy/util/__init__.py
index 27e5e9c926b0dbebae5d80e8986ea9df4bb5892c..3469dca65e97330140f7792839c130d24606f4dc 100644
--- a/galaxy/util/__init__.py
+++ b/galaxy/util/__init__.py
@@ -10,13 +10,19 @@ try:
     import grp
 except ImportError:
     grp = None
+try:
+    import docutils.core
+    import docutils.writers.html4css1
+except ImportError:
+    pass
 import errno
 import urlparse
 from tempfile import NamedTemporaryFile
-from logging import getLogger
-log = getLogger(__name__)
+import logging
+log = logging.getLogger(__name__)
 
 BUFFER_SIZE = 4096
+DEFAULT_ENCODING = os.environ.get('GALAXY_DEFAULT_ENCODING', 'utf-8')
 
 
 def enum(**enums):
@@ -130,6 +136,8 @@ def xml_text(root, name=None):
 # asbool implementation pulled from PasteDeploy
 truthy = frozenset(['true', 'yes', 'on', 'y', 't', '1'])
 falsy = frozenset(['false', 'no', 'off', 'n', 'f', '0'])
+
+
 def asbool(obj):
     if isinstance(obj, basestring):
         obj = obj.strip().lower()
@@ -196,3 +204,29 @@ def mask_password_from_url( url ):
             split = split._replace(netloc=split.netloc.replace("%s:%s" % (split.username, split.password), '%s:********' % split.username))
             url = urlparse.urlunsplit(split)
     return url
+
+
+def unicodify( value, encoding=DEFAULT_ENCODING, error='replace', default=None ):
+    """
+    Returns a unicode string or None
+    """
+
+    if isinstance( value, unicode ):
+        return value
+    try:
+        return unicode( str( value ), encoding, error )
+    except:
+        return default
+
+
+def rst_to_html( s ):
+    """Convert a blob of reStructuredText to HTML"""
+    log = logging.getLogger( "docutils" )
+
+    class FakeStream( object ):
+        def write( self, str ):
+            if len( str ) > 0 and not str.isspace():
+                log.warn( str )
+    return unicodify( docutils.core.publish_string( s,
+                      writer=docutils.writers.html4css1.Writer(),
+                      settings_overrides={ "embed_stylesheet": False, "template": os.path.join(os.path.dirname(__file__), "docutils_template.txt"), "warning_stream": FakeStream() } ) )
diff --git a/galaxy/util/docutils_template.txt b/galaxy/util/docutils_template.txt
new file mode 100644
index 0000000000000000000000000000000000000000..92aee1b5257ebd47e3b7392549284f520bd651f2
--- /dev/null
+++ b/galaxy/util/docutils_template.txt
@@ -0,0 +1 @@
+%(body)s