Repository: sedders123/phial

Issue:
Have ability to hide a command from !help
Allow users to hide a command from the in built help command if passed a parameter.

Discussion:

Patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index e2a4b2d..ae2a87d 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -5,8 +5,9 @@ jobs:
       - image: circleci/python:3.7
     steps:
       - checkout
+      - run: echo "$CIRCLE_JOB" > _circle_job
       - restore_cache:
-          key: v1-deps-{{ checksum "dev-requirements.txt" }}-{{ checksum "setup.py" }}
+          key: v1-deps-{{ checksum "_circle_job" }}-{{ checksum "dev-requirements.txt" }}-{{ checksum "setup.py" }}
       - run:
           name: Create Virtual Env
           command: |
@@ -22,7 +23,7 @@ jobs:
             . venv/bin/activate
             pip install -e .
       - save_cache:
-          key: v1-deps-{{ checksum "dev-requirements.txt" }}-{{ checksum "setup.py" }}
+          key: v1-deps-{{ checksum "_circle_job" }}-{{ checksum "dev-requirements.txt" }}-{{ checksum "setup.py" }}
           paths:
             - "venv"
       - run:
@@ -44,7 +45,7 @@ jobs:
           name: Upload coverage
           command: |
             . venv/bin/activate
-            if [ -z $COVERALLS_REPO_TOKEN ]; then echo "No Coveralls Token"; else coveralls; fi
+            if [ -z $COVERALLS_REPO_TOKEN ] || [ "$CIRCLE_JOB" != "build" ]; then echo "No Coveralls Token"; else coveralls; fi
 
   test-3.6:
     <<: *template
@@ -61,9 +62,9 @@ jobs:
       - image: circleci/python:3.7
     steps:
       - checkout
-
+      - run: echo "build" > build_job
       - restore_cache:
-          key: v1-deps-{{ checksum "dev-requirements.txt" }}-{{ checksum "setup.py" }}
+          key: v1-deps-{{checksum "build_job"}}--{{ checksum "dev-requirements.txt" }}-{{ checksum "setup.py" }}
 
       - run:
           name: Create Virtual Env
@@ -83,7 +84,7 @@ jobs:
             pip install -e .
 
       - save_cache:
-          key: v1-deps-{{ checksum "dev-requirements.txt" }}-{{ checksum "setup.py" }}
+          key: v1-deps-{{checksum "build_job"}}-{{ checksum "dev-requirements.txt" }}-{{ checksum "setup.py" }}
           paths:
             - "venv"
 
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0149024..7658e83 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
 
 ### Added
  - Added 'loopDelay' config option. Specifes how to long to sleep for in the main loop, this helps to prevent super high CPU usage while doing nothing. Defaults to 0.001
+ - Added `hide_from_help_command` argument to command creation. This hides the command from the list generated by inbuilt help command.
 
 ### Changed
  - Python 3.6.1 is now marked as incompatible due to an issue with the `NoReturn` type
diff --git a/examples/starter.py b/examples/starter.py
index b91b49e..ec5defe 100644
--- a/examples/starter.py
+++ b/examples/starter.py
@@ -67,7 +67,7 @@ def case_sensitive():
 @slackbot.command('messageWithAttachment')
 def get_message_with_attachment():
     '''
-        A command that posts a message with a Slack attachment
+        A command that posts a message with a Slack attachment.
         Read more: https://api.slack.com/docs/message-attachments
     '''
     attachments = [{
@@ -78,6 +78,14 @@ def get_message_with_attachment():
     return Response(channel=command.channel, attachments=attachments)
 
 
+@slackbot.command('hidden', hide_from_help_command=True)
+def hidden():
+    '''
+        A command that is hidden from the default help command
+    '''
+    return "Suprise"
+
+
 @slackbot.fallback_command()
 def fallback_command(command):
     return "Thats not a command"
diff --git a/phial/bot.py b/phial/bot.py
index 33d0eb4..ced4ecb 100644
--- a/phial/bot.py
+++ b/phial/bot.py
@@ -43,7 +43,7 @@ class Phial:
         self.scheduler = Scheduler()
         self.fallback_func = None  # type: Optional[Callable[[Message], PhialResponse]] # noqa: E501
         self.logger = logging.getLogger(__name__)
-        if not self.logger.hasHandlers():
+        if not self.logger.hasHandlers():  # pragma: nocover
             handler = logging.StreamHandler()
             formatter = logging.Formatter(
                 fmt="%(asctime)s [%(name)s] - %(message)s")
@@ -57,7 +57,8 @@ class Phial:
                     pattern: str,
                     func: Callable[..., PhialResponse],
                     case_sensitive: bool = False,
-                    help_text_override: Optional[str] = None) -> None:
+                    help_text_override: Optional[str] = None,
+                    hide_from_help_command: Optional[bool] = False) -> None:
         """
         Registers a command with the bot.
 
@@ -80,6 +81,12 @@ class Phial:
                                    help text.
 
                                    Defaults to None
+        :param hide_from_help_command: A flag to specify whether or not
+                                       the inbuilt help command should
+                                       hide this command from the list
+                                       it generates.
+
+                                       Defaults to False
 
         :raises ValueError: If command with the same pattern is already
                             registered
@@ -110,7 +117,11 @@ class Phial:
                                  .format(pattern.split("<")[0]))
 
         # Create and add command
-        command = Command(pattern, func, case_sensitive, help_text_override)
+        command = Command(pattern,
+                          func,
+                          case_sensitive,
+                          help_text_override=help_text_override,
+                          hide_from_help_command=hide_from_help_command)
         self.commands.append(command)
         self.logger.debug("Command {0} added"
                           .format(pattern))
@@ -118,7 +129,8 @@ class Phial:
     def command(self,
                 pattern: str,
                 case_sensitive: bool = False,
-                help_text_override: Optional[str] = None) -> Callable:
+                help_text_override: Optional[str] = None,
+                hide_from_help_command: Optional[bool] = False) -> Callable:
         """
         Registers a command with the bot.
 
@@ -139,6 +151,12 @@ class Phial:
                                    help text.
 
                                    Defaults to None
+        :param hide_from_help_command: A flag to specify whether or not
+                                       the inbuilt help command should
+                                       hide this command from the list
+                                       it generates.
+
+                                       Defaults to False
 
         .. rubric:: Example
 
@@ -154,7 +172,10 @@ class Phial:
 
         """
         def decorator(f: Callable) -> Callable:
-            self.add_command(pattern, f, case_sensitive, help_text_override)
+            self.add_command(pattern, f,
+                             case_sensitive,
+                             help_text_override=help_text_override,
+                             hide_from_help_command=hide_from_help_command)
             return f
         return decorator
 
diff --git a/phial/commands.py b/phial/commands.py
index f696d2e..cb6be4d 100644
--- a/phial/commands.py
+++ b/phial/commands.py
@@ -13,6 +13,8 @@ def help_command(bot: 'Phial') -> str:
     if help_text:
         help_text += "\n"
     for command in bot.commands:
+        if command.hide_from_help_command:
+            continue
         command_doc = command.help_text
         if not command_doc:
             # If no help text default to blank string
diff --git a/phial/types.py b/phial/types.py
index f215336..074230d 100644
--- a/phial/types.py
+++ b/phial/types.py
@@ -1,7 +1,7 @@
 """Shared types for phial."""
 from typing import TYPE_CHECKING, Union
 
-if TYPE_CHECKING:
+if TYPE_CHECKING:  # pragma: nocover
     from phial.wrappers import Response, Attachment  # noqa
 
 #: A union of all response types phial can use
diff --git a/phial/wrappers.py b/phial/wrappers.py
index 57fddea..d30b5fd 100644
--- a/phial/wrappers.py
+++ b/phial/wrappers.py
@@ -151,19 +151,23 @@ class Command:
                            sensitivity
     :param help_text_override: Overrides the function's docstring in the
                                standard help command
+    :param hide_from_help_command: Prevents function from being displayed by
+                                   the standard help command
     """
 
     def __init__(self,
                  pattern: str,
                  func: Callable[..., PhialResponse],
                  case_sensitive: bool = False,
-                 help_text_override: Optional[str] = None):
+                 help_text_override: Optional[str] = None,
+                 hide_from_help_command: Optional[bool] = False):
         self.pattern_string = pattern
         self.pattern = self._build_pattern_regex(pattern, case_sensitive)
         self.alias_patterns = self._get_alias_patterns(func)
         self.func = func
         self.case_sensitive = case_sensitive
         self.help_text_override = help_text_override
+        self.hide_from_help_command = hide_from_help_command
 
     def __repr__(self) -> str:
         return "<Command: {0}>".format(self.pattern_string)


Test patch:
diff --git a/tests/commands/test_help_command.py b/tests/commands/test_help_command.py
index 00db3f1..1972141 100644
--- a/tests/commands/test_help_command.py
+++ b/tests/commands/test_help_command.py
@@ -33,6 +33,37 @@ def test_returns_help_string_correctly_when_no_help_text() -> None:
     assert help_text == expected_help_text
 
 
+def test_hide_from_help_command_hides_correctly() -> None:
+    """Test returns help text correctly when none set."""
+    bot = Phial('token')
+
+    @bot.command("test", hide_from_help_command=True)
+    def test() -> None:
+        pass
+
+    help_text = help_command(bot)
+    expected_help_text = ("All available commands:\n*!help* - List all"
+                          " available commmands\n")
+
+    assert help_text == expected_help_text
+
+
+def test_help_text_override_overrides_correctly() -> None:
+    """Test returns help text correctly when none set."""
+    bot = Phial('token')
+
+    @bot.command("test", help_text_override="Override")
+    def test() -> None:
+        """Not this."""
+        pass
+
+    help_text = help_command(bot)
+    expected_help_text = ("All available commands:\n*!help* - List all"
+                          " available commmands\n*!test* - Override\n")
+
+    assert help_text == expected_help_text
+
+
 def test_returns_help_string_correctly_when_no_base() -> None:
     """Test returns help text correctly when no base."""
     bot = Phial('token', {'registerHelpCommand': False,