From 326bb24a89e0e1d14971b235724b55fe94e231aa Mon Sep 17 00:00:00 2001
From: Dag Wieers <dag@wieers.com>
Date: Tue, 15 Mar 2016 15:39:44 +0100
Subject: [PATCH] Various improvements, now supports different verbosity levels

---
 lib/ansible/plugins/callback/dense.py | 210 +++++++++++++++++++-------
 1 file changed, 153 insertions(+), 57 deletions(-)

diff --git a/lib/ansible/plugins/callback/dense.py b/lib/ansible/plugins/callback/dense.py
index a2a7b6e96d7..10b3db30be3 100644
--- a/lib/ansible/plugins/callback/dense.py
+++ b/lib/ansible/plugins/callback/dense.py
@@ -21,9 +21,13 @@ __metaclass__ = type
 
 from ansible.plugins.callback.default import CallbackModule as CallbackModule_default
 
-import sys
-import time
+try:
+    from __main__ import display
+except ImportError:
+    from ansible.utils.display import Display
+    display = Display()
 
+import sys
 
 # Design goals:
 #
@@ -43,7 +47,7 @@ import time
 #    - We use the cursor to indicate where in the task we are.
 #      Output after the prompt is the output of the previous task
 #
-#    - Use the same color-conventions of Ansible
+#  + Use the same color-conventions of Ansible
 
 
 # TODO:
@@ -51,8 +55,8 @@ import time
 #  + Ensure all other output is properly displayed
 #  + Properly test for terminal capabilities, and fall back to default
 #  + Modify Ansible mechanism so we don't need to use sys.stdout directly
-#  + Check verbosity 1, display task details on change/failure !
-#  + Check verbosity 2, use default callback for everything
+#  + Remove items from result to compact the json output when using -v
+#  + Make colored output nicer
 
 
 # Taken from Dstat
@@ -117,27 +121,64 @@ class CallbackModule(CallbackModule_default):
     CALLBACK_TYPE = 'stdout'
     CALLBACK_NAME = 'dense'
 
-    hosts = []
-    keep = False
-#    task = ''
-    tasknr = 0
-
     def __init__(self):
+
+        # From CallbackModule
+        if display:
+            self._display = display
+        else:
+            self._display = global_display
+
+        if self._display.verbosity >= 4:
+            name = getattr(self, 'CALLBACK_NAME', 'unnamed')
+            ctype = getattr(self, 'CALLBACK_TYPE', 'old')
+            version = getattr(self, 'CALLBACK_VERSION', '1.0')
+            self._display.vvvv('Loaded callback %s of type %s, v%s' % (name, ctype, version))
+
+        self.super_ref = super(CallbackModule, self)
+        self.super_ref.__init__()
+
+        if self._display.verbosity > 1:
+            return
+        self.hosts = []
+        self.keep = False
+        self.shown_title = False
+        self.tasknr = 0
+        self.playnr = 0
         sys.stdout.write(ansi.save + ansi.clearline)
         sys.stdout.flush()
  
-    def _add_host(self, name, status):
+    def _add_host(self, result, status):
+        self.hosts.append((result._host.get_name(), status))
+        self._display_progress()
         # Ensure that tasks with changes/failures stay on-screen
-        if not self.keep and status in ['changed', 'failed', 'unreachable']:
+        if status in ['changed', 'failed', 'unreachable']:
             self.keep = True
-        self.hosts.append((name, status))
-        self._print_status()
+            if self._display.verbosity == 1:
+                self._display_task_banner()
+                # TODO: clean up result output, eg. remove changed, delta, end, start, ...
+                if status == 'changed':
+                    self.super_ref.v2_runner_on_ok(result)
+                elif status == 'failed':
+                    self.super_ref.v2_runner_on_failed(result)
+                elif status == 'unreachable':
+                    self.super_ref.v2_runner_on_unreachable(result)
 
-    def _print_status(self):
+    def _display_task_banner(self):
+        if not self.shown_title:
+            self.shown_title = True
+            sys.stdout.write(ansi.restore + ansi.clearline)
+            sys.stdout.write(ansi.underline + 'task %d: %s' % (self.tasknr, self.task.get_name().strip()))
+            sys.stdout.write(ansi.restore + '\n' + ansi.reset + ansi.clearline)
+            sys.stdout.flush()
+        else:
+            sys.stdout.write(ansi.restore + ansi.clearline)
+
+    def _display_progress(self):
         # Always rewrite the complete line
-        sys.stdout.write(ansi.restore)
-        sys.stdout.write(ansi.clearline)
-        sys.stdout.write('- task %d:  ' % self.tasknr)
+        sys.stdout.write(ansi.restore + ansi.clearline + ansi.underline)
+        sys.stdout.write('task %d:' % self.tasknr)
+        sys.stdout.write(ansi.reset + '  ')
         sys.stdout.flush()
 
         # Print out each host with its own status-color
@@ -145,7 +186,7 @@ class CallbackModule(CallbackModule_default):
             if status == 'ok':
                 color = ansi.darkgreen
             elif status == 'changed':
-                color = ansi.yellow
+                color = ansi.darkyellow
             elif status == 'skipped':
                 color = ansi.darkcyan
             elif status == 'failed':
@@ -160,62 +201,117 @@ class CallbackModule(CallbackModule_default):
         # Place cursor at start of the line
         sys.stdout.write(ansi.default)
 
+    def v2_playbook_on_play_start(self, play):
+        if self._display.verbosity > 1:
+            self.super_ref.v2_playbook_on_play_start(play)
+            return
+
+        self.tasknr = 0
+        self.playnr += 1
+        self.play = play
+        # Leave the previous task on screen (as it has changes/errors)
+        if self.keep:
+            sys.stdout.write(ansi.restore + '\n' + ansi.save + ansi.clearline + ansi.bold)
+        else:
+            sys.stdout.write(ansi.restore + ansi.clearline + ansi.bold)
+        name = play.get_name().strip()
+        if name:
+            sys.stdout.write('PLAY %d: %s' % (self.playnr, name.upper()))
+        else:
+            sys.stdout.write('PLAY %d' % self.playnr)
+        # Always leave the PLAY output on screen
+        sys.stdout.write(ansi.restore + '\n' + ansi.save + ansi.reset + ansi.clearline)
+        sys.stdout.flush()
+
+    def v2_playbook_on_task_start(self, task, is_conditional):
+        if self._display.verbosity > 1:
+            self.super_ref.v2_playbook_on_task_start(task, is_conditional)
+            return
+
+        # Leave the previous task on screen (as it has changes/errors)
+        if self._display.verbosity == 0 and self.keep:
+            sys.stdout.write(ansi.restore + '\n' + ansi.save + ansi.clearline)
+
+        # Reset counters at the start of each new task
+        self.keep = False
+        self.shown_title = False
+        self.hosts = []
+        self.task = task
+
+        # Enumerate task if not setup (task names are too long for dense output)
+        if task.get_name() != 'setup':
+            self.tasknr += 1
+#        self.task = task.get_name().strip()
+        # Write the next task on screen (behind the prompt is the previous output)
+        sys.stdout.write(ansi.restore + ansi.underline)
+        sys.stdout.write('task %d' % self.tasknr)
+        sys.stdout.write(ansi.reset)
+        sys.stdout.flush()
+
     def v2_runner_on_failed(self, result, ignore_errors=False):
-        self._add_host(result._host.get_name(), 'failed')
+        if self._display.verbosity > 1:
+            self.super_ref.v2_runner_on_failed(result, ignore_errors)
+            return
+
+        self._add_host(result, 'failed')
 
     def v2_runner_on_ok(self, result):
+        if self._display.verbosity > 1:
+            self.super_ref.v2_runner_on_ok(result)
+            return
+
         if result._result.get('changed', False):
-            self._add_host(result._host.get_name(), 'changed')
+            self._add_host(result, 'changed')
         else:
-            self._add_host(result._host.get_name(), 'ok')
+            self._add_host(result, 'ok')
 
     def v2_runner_on_skipped(self, result):
-        self._add_host(result._host.get_name(), 'skipped')
+        if self._display.verbosity > 1:
+            self.super_ref.v2_runner_on_skipped(result)
+            return
+
+        self._add_host(result, 'skipped')
 
     def v2_runner_on_unreachable(self, result):
-        self._add_host(result._host.get_name(), 'unreachable')
+        if self._display.verbosity > 1:
+            self.super_ref.v2_runner_on_unreachable(result)
+            return
+
+        self._add_host(result, 'unreachable')
+
+    def v2_runner_on_include(self, included_file):
+        if self._display.verbosity > 1:
+            self.super_ref.v2_runner_on_include(included_file)
+
+    def v2_playbook_item_on_ok(self, result):
+        if self._display.verbosity > 1:
+            self.super_ref.v2_playbook_item_on_ok(result)
+
+        # TBD
+
+    def v2_playbook_item_on_failed(self, result):
+        if self._display.verbosity > 1:
+            self.super_ref.v2_playbook_item_on_failed(result)
+
+        # TBD
 
     def v2_playbook_item_on_skipped(self, result):
-        pass
+        if self._display.verbosity > 1:
+            self.super_ref.v2_playbook_item_on_skipped(result)
+
+        # TBD
 
     def v2_playbook_on_no_hosts_remaining(self):
+        if self._display.verbosity > 1:
+            self.super_ref.v2_runner_on_no_hosts_remaining()
+            return
+
         # TBD
         if self.keep:
-            sys.stdout.write(ansi.restore + '\n' + ansi.save)
+            sys.stdout.write(ansi.restore + '\n' + ansi.save + ansi.clearline)
         else:
             sys.stdout.write(ansi.restore + ansi.clearline)
 
         sys.stdout.write(ansi.white + ansi.redbg + 'NO MORE HOSTS LEFT' + ansi.default)
         sys.stdout.write(ansi.reset)
-        sys.stdout.flush()
-
-    def v2_playbook_on_task_start(self, task, is_conditional):
-        # Leave the previous task on screen (as it has changes/errors)
-        if self.keep:
-            sys.stdout.write(ansi.restore + '\n' + ansi.save + ansi.clearline)
-        # Reset counters at the start of each new task
-        self.keep = False
-        self.hosts = []
-        # Enumerate task (task names are too long for dense output)
-        self.tasknr += 1
-#        self.task = task.get_name().strip()
-        # Write the next task on screen (behind the prompt is the previous output)
-        sys.stdout.write(ansi.restore)
-        sys.stdout.write('- task %d|' % self.tasknr)
-        sys.stdout.flush()
-
-    def v2_playbook_on_play_start(self, play):
-        self.tasknr = 0
-        # Leave the previous task on screen (as it has changes/errors)
-        if self.keep:
-            sys.stdout.write(ansi.restore + '\n' + ansi.save + ansi.clearline)
-        else:
-            sys.stdout.write(ansi.restore + ansi.clearline)
-        name = play.get_name().strip()
-        if name:
-            sys.stdout.write('PLAY [%s]' % name)
-        else:
-            sys.stdout.write('PLAY')
-        # Always leave the PLAY output on screen
-        sys.stdout.write(ansi.restore + '\n' + ansi.save + ansi.clearline)
         sys.stdout.flush()
\ No newline at end of file