Repository: pallets/click

Issue:
resultcallback is not being called when invoke_without_command=True
I have a group-command (called `groupcmd`) which I would like the user to be able to invoke without having to specify a subcommand. I also have a resultcallback (via `@groupcmd.resultcallback`) which I would like to call after the group-command finishes for processing errors and logging them to file. 

If I set `invoke_without_command=True` inside of the `@click.group` definition, then the resultcallback does not get called. 

I am using click 6.6 on python 2.7.12


Discussion:
Click 6 will only receive security fixes. Please verify that this still happens on Click 7.
I've confirmed this happens with click 7.0 on python 3.5.2. Here is some minimal code to reproduce the issue (test.py):

```
import click

@click.group(invoke_without_command=True)
def main():
    print("Invoking main")
    return "main result"


@main.command()
def sub():
    print("Invoking sub")
    return "sub result"


@main.resultcallback()
def process_result(result):
    print("result was:", result)


if __name__ == '__main__':
    main()
```

If the subcommand is invoked (i.e, `python test.py sub`) then it will print `result was: sub result` but if main is invoked without a subcommand (i.e, `python test.py`) then it will not print the result. 
I'm not familiar with `resultcallback` off the top of my head, but if you don't find anything in the docs contradicting the behavior you want, I'd be happy to review a PR.
I'm looking into this issue at the moment
[The documentation](https://click.palletsprojects.com/en/7.x/api/?highlight=resultcallback#click.MultiCommand.resultcallback) states that `resultcallback` *"Adds a result callback to the **chain** command."*

[Another example](https://click.palletsprojects.com/en/7.x/commands/#multi-command-pipelines) in the docs shows the result callback being invoked when using subcommands which is what it is meant for. From this I think that `resultcallback` is just used more or less for getting data from subcommands and not singular calls without any subcommands invoked which your script attempts to do.

@IamCathal further on in the doc for `resultcallback`, it calls out a difference in behavior for chaining, which seems to imply that a result callback should be called for either type of command. And that is the case, it's called for a group with a subcommand and for a chain.

Looking through the code, the picture gets more complicated. If `invoke_without_command` is triggered, the code specifically calls out only invoking the result callback for chained commands (with an empty list of results), while ignoring groups.

https://github.com/pallets/click/blob/35f73b8c2c314e56968de8bc483da9a84ac5c53e/src/click/core.py#L1261-L1272

So it seems like this is intentional behavior, but there's no explanation for why it was done. The best explanation I can guess is that in a chain you would get an empty list for no commands, but for a regular group you'd only get `None` and wouldn't be able to distinguish between no subcommand and one that returned `None`. But if you were using `resultcallback` with a regular group, you'd presumably be returning a value from subcommands, or not care if the result was `None`.

I'm ok with changing this behavior.

Resolving patch:
diff --git a/src/click/core.py b/src/click/core.py
--- a/src/click/core.py
+++ b/src/click/core.py
@@ -1170,7 +1170,7 @@ def format_options(self, ctx, formatter):
         self.format_commands(ctx, formatter)
 
     def resultcallback(self, replace=False):
-        """Adds a result callback to the chain command.  By default if a
+        """Adds a result callback to the command.  By default if a
         result callback is already registered this will chain them but
         this can be disabled with the `replace` parameter.  The result
         callback is invoked with the return value of the subcommand
@@ -1189,10 +1189,10 @@ def cli(input):
             def process_result(result, input):
                 return result + input
 
-        .. versionadded:: 3.0
-
         :param replace: if set to `True` an already existing result
                         callback will be removed.
+
+        .. versionadded:: 3.0
         """
 
         def decorator(f):
@@ -1258,18 +1258,13 @@ def _process_result(value):
             return value
 
         if not ctx.protected_args:
-            # If we are invoked without command the chain flag controls
-            # how this happens.  If we are not in chain mode, the return
-            # value here is the return value of the command.
-            # If however we are in chain mode, the return value is the
-            # return value of the result processor invoked with an empty
-            # list (which means that no subcommand actually was executed).
             if self.invoke_without_command:
-                if not self.chain:
-                    return Command.invoke(self, ctx)
+                # No subcommand was invoked, so the result callback is
+                # invoked with None for regular groups, or an empty list
+                # for chained groups.
                 with ctx:
                     Command.invoke(self, ctx)
-                    return _process_result([])
+                    return _process_result([] if self.chain else None)
             ctx.fail("Missing command.")
 
         # Fetch args back out


Test patch:
diff --git a/tests/test_chain.py b/tests/test_chain.py
--- a/tests/test_chain.py
+++ b/tests/test_chain.py
@@ -88,6 +88,25 @@ def bdist(format):
     assert result.output.splitlines() == ["bdist called 1", "sdist called 2"]
 
 
+@pytest.mark.parametrize(("chain", "expect"), [(False, "None"), (True, "[]")])
+def test_no_command_result_callback(runner, chain, expect):
+    """When a group has ``invoke_without_command=True``, the result
+    callback is always invoked. A regular group invokes it with
+    ``None``, a chained group with ``[]``.
+    """
+
+    @click.group(invoke_without_command=True, chain=chain)
+    def cli():
+        pass
+
+    @cli.resultcallback()
+    def process_result(result):
+        click.echo(str(result), nl=False)
+
+    result = runner.invoke(cli, [])
+    assert result.output == expect
+
+
 def test_chaining_with_arguments(runner):
     @click.group(chain=True)
     def cli():