We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
turbinia-client status summary formatting warning
Running turbini-client status summary might yield the following warning for some requests:

```WARNING turbinia - Error formatting the Markdown report: unsupported format string passed to NoneType.__format__```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `turbinia/api/cli/turbinia_client/helpers/formatter.py`
Content:
```
1 #!/usr/bin/env python
2 #
3 # Copyright 2017 Google Inc.
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 #      http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16 """Methods for formatting text."""
17 
18 from __future__ import annotations
19 
20 from abc import ABC, abstractmethod
21 from click import echo as click_echo
22 
23 import logging
24 import json
25 
26 log = logging.getLogger('turbinia')
27 
28 
29 def echo_json(json_data: dict) -> None:
30   """Pretty print JSON data."""
31   if isinstance(json_data, dict):
32     click_echo(json.dumps(json_data, indent=2))
33 
34 
35 class MarkdownReportComponent(ABC):
36   """Components for generating Turbinia request/task
37       markdown reports.
38   """
39 
40   def __init__(self):
41     """Instantiates a MarkdownReportComponent object."""
42     self._components: list(MarkdownReportComponent) = []
43     self._parent: MarkdownReportComponent = None
44     self._report: str = None
45 
46   @property
47   def components(self):
48     """Returns the components list."""
49     return self._components
50 
51   @property
52   def report(self):
53     """Returns the markdown report text."""
54     return self._report
55 
56   @report.setter
57   def report(self, report):
58     self._report = report
59 
60   @property
61   def parent(self) -> MarkdownReportComponent:
62     """Returns the parent object."""
63     return self._parent
64 
65   @parent.setter
66   def parent(self, parent: MarkdownReportComponent):
67     self._parent = parent
68 
69   def bold(self, text):
70     """Formats text as bold in Markdown format.
71 
72     Args:
73         text(string): Text to format
74 
75     Return:
76         string: Formatted text.
77     """
78     return f'**{text.strip():s}**'
79 
80   def heading1(self, text):
81     """Formats text as heading 1 in Markdown format.
82 
83     Args:
84         text(string): Text to format
85 
86     Return:
87         string: Formatted text.
88     """
89     return f'# {text.strip():s}'
90 
91   def heading2(self, text):
92     """Formats text as heading 2 in Markdown format.
93 
94     Args:
95         text(string): Text to format
96 
97     Return:
98         string: Formatted text.
99     """
100     return f'## {text.strip():s}'
101 
102   def heading3(self, text):
103     """Formats text as heading 3 in Markdown format.
104 
105     Args:
106         text(string): Text to format
107 
108     Return:
109         string: Formatted text.
110     """
111     return f'### {text.strip():s}'
112 
113   def heading4(self, text):
114     """Formats text as heading 4 in Markdown format.
115 
116     Args:
117         text(string): Text to format
118 
119     Return:
120         string: Formatted text.
121     """
122     return f'#### {text.strip():s}'
123 
124   def heading5(self, text):
125     """Formats text as heading 5 in Markdown format.
126      Args:
127         text(string): Text to format
128      Return:
129         string: Formatted text.
130     """
131     return f'##### {text.strip():s}'
132 
133   def bullet(self, text, level=1):
134     """Formats text as a bullet in Markdown format.
135 
136       Args:
137         text(string): Text to format
138         level(int): Indentation level.
139       Return:
140         string: Formatted text.
141     """
142     return f"{'    ' * (level - 1):s}* {text.strip():s}"
143 
144   def code(self, text):
145     """Formats text as code in Markdown format.
146 
147       Args:
148           text(string): Text to format
149 
150      Return:
151           string: Formatted text.
152     """
153     return f'`{text.strip():s}`'
154 
155   def add(self, component: MarkdownReportComponent) -> None:
156     """Adds a MarkdownReportComponent object to the components list.
157 
158     This method should additionally set the parent object.
159     """
160     pass
161 
162   def add_components(self, components: list[MarkdownReportComponent]) -> None:
163     """Adds multiple MarkdownReportComponent objects to the components list."""
164     pass
165 
166   def remove(self, component: MarkdownReportComponent) -> None:
167     """Removes a MarkdownReportComponent object from the components list.
168 
169     This method should set the component's object to None.
170     """
171     pass
172 
173   @abstractmethod
174   def generate_markdown(self) -> str:
175     pass
176 
177 
178 class TaskMarkdownReport(MarkdownReportComponent):
179   """Turbinia Task markdown report."""
180 
181   def __init__(self, request_data: dict = None):
182     """Initialize TaskMarkdownReport"""
183     super().__init__()
184     self._request_data: dict = request_data
185 
186   def generate_markdown(self) -> str:
187     """Generate a markdown report."""
188     report: list[str] = []
189     task: dict = self._request_data
190     if not task:
191       return ''
192 
193     try:
194       report.append(self.heading2(task.get('name')))
195       line = f"{self.bold('Evidence:'):s} {task.get('evidence_name')!s}"
196       report.append(self.bullet(line))
197       line = f"{self.bold('Status:'):s} {task.get('status'):s}"
198       report.append(self.bullet(line))
199       report.append(self.bullet(f"Task Id: {task.get('id')!s}"))
200       report.append(
201           self.bullet(f"Executed on worker {task.get('worker_name')!s}"))
202       if task.get('report_data'):
203         report.append('')
204         report.append(self.heading3('Task Reported Data'))
205         report.extend(task.get('report_data').splitlines())
206       report.append('')
207       report.append(self.heading3('Saved Task Files:'))
208 
209       saved_paths = task.get('saved_paths')
210       if saved_paths:
211         for path in saved_paths:
212           report.append(self.bullet(self.code(path)))
213           report.append('')
214     except TypeError as exception:
215       log.warning(f'Error formatting the Markdown report: {exception!s}')
216 
217     self.report = '\n'.join(report)
218     return self.report
219 
220 
221 class RequestMarkdownReport(MarkdownReportComponent):
222   """Turbinia Request Markdown report."""
223 
224   def __init__(self, request_data: dict):
225     """Initializes a RequestMarkdownReport object."""
226     super().__init__()
227     self._request_data: dict = request_data
228 
229     tasks = [TaskMarkdownReport(task) for task in request_data.get('tasks')]
230     self.add_components(tasks)
231 
232   def add(self, component: MarkdownReportComponent) -> None:
233     if component:
234       self.components.append(component)
235       component.parent = self
236 
237   def remove(self, component: MarkdownReportComponent) -> None:
238     self.components.remove(component)
239     component.parent = None
240 
241   def add_components(self, components: list[MarkdownReportComponent]) -> None:
242     if components:
243       for component in components:
244         self.components.append(component)
245         component.parent = self
246 
247   def generate_markdown(self) -> str:
248     """Generates a Markdown version of Requests results."""
249     report: list[str] = []
250     request_dict: dict = self._request_data
251     if not request_dict:
252       return ''
253 
254     try:
255       report.append(
256           self.heading2(f"Request ID: {request_dict.get('request_id')!s}"))
257       report.append(
258           self.bullet(
259               f"Last Update: {request_dict.get('last_task_update_time')!s}"))
260       report.append(
261           self.bullet(f"Requester: {request_dict.get('requester')!s}"))
262       report.append(self.bullet(f"Reason: {request_dict.get('reason')!s}"))
263       report.append(self.bullet(f"Status: {request_dict.get('status')!s}"))
264       report.append(
265           self.bullet(f"Failed tasks: {request_dict.get('failed_tasks'):d}"))
266       report.append(
267           self.bullet(f"Running tasks: {request_dict.get('running_tasks'):d}"))
268       report.append(
269           self.bullet(
270               f"Successful tasks: {request_dict.get('successful_tasks'):d}"))
271       report.append(
272           self.bullet(f"Task Count: {request_dict.get('task_count'):d}"))
273       report.append(
274           self.bullet(f"Queued tasks: {request_dict.get('queued_tasks'):d}"))
275       report.append(
276           self.bullet(f"Evidence Name: {request_dict.get('evidence_name'):d}"))
277       report.append('')
278     except TypeError as exception:
279       log.warning(f'Error formatting the Markdown report: {exception!s}')
280 
281     for task in self.components:
282       report.append(task.generate_markdown())
283 
284     self.report = '\n'.join(report)
285     return self.report
286 
287 
288 class SummaryMarkdownReport(MarkdownReportComponent):
289   """A markdown report summary of all Turbinia Requests."""
290 
291   def __init__(self, requests_summary: list[dict]):
292     """Initialize SummaryMarkdownReport."""
293     super().__init__()
294     self._requests_summary = requests_summary
295 
296   def generate_markdown(self) -> str:
297     """Generate a Markdown version of Requests summary results."""
298     report: list[str] = []
299     requests_status_list = None
300     if self._requests_summary:
301       requests_status_list = self._requests_summary.get('requests_status')
302 
303     if not requests_status_list:
304       return '## No requests found.'
305 
306     for request_dict in requests_status_list:
307       request_report = RequestMarkdownReport(request_dict).generate_markdown()
308       report.append(request_report)
309 
310     self.report = '\n'.join(report)
311     return self.report
312 
313 
314 class WorkersMarkdownReport(MarkdownReportComponent):
315   """A markdown report of all tasks for a specific worker."""
316 
317   def __init__(self, request_data: dict):
318     super().__init__()
319     self._request_data: dict = request_data
320 
321   def generate_markdown(self) -> str:
322     """Generates a Markdown version of tasks per worker."""
323     raise NotImplementedError
324
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/turbinia/api/cli/turbinia_client/helpers/formatter.py b/turbinia/api/cli/turbinia_client/helpers/formatter.py
--- a/turbinia/api/cli/turbinia_client/helpers/formatter.py
+++ b/turbinia/api/cli/turbinia_client/helpers/formatter.py
@@ -194,7 +194,7 @@
       report.append(self.heading2(task.get('name')))
       line = f"{self.bold('Evidence:'):s} {task.get('evidence_name')!s}"
       report.append(self.bullet(line))
-      line = f"{self.bold('Status:'):s} {task.get('status'):s}"
+      line = f"{self.bold('Status:'):s} {task.get('status')!s}"
       report.append(self.bullet(line))
       report.append(self.bullet(f"Task Id: {task.get('id')!s}"))
       report.append(
@@ -253,27 +253,26 @@
 
     try:
       report.append(
-          self.heading2(f"Request ID: {request_dict.get('request_id')!s}"))
+          self.heading2(f"Request ID: {request_dict.get('request_id')}"))
       report.append(
           self.bullet(
-              f"Last Update: {request_dict.get('last_task_update_time')!s}"))
+              f"Last Update: {request_dict.get('last_task_update_time')}"))
+      report.append(self.bullet(f"Requester: {request_dict.get('requester')}"))
+      report.append(self.bullet(f"Reason: {request_dict.get('reason')}"))
+      report.append(self.bullet(f"Status: {request_dict.get('status')}"))
       report.append(
-          self.bullet(f"Requester: {request_dict.get('requester')!s}"))
-      report.append(self.bullet(f"Reason: {request_dict.get('reason')!s}"))
-      report.append(self.bullet(f"Status: {request_dict.get('status')!s}"))
+          self.bullet(f"Failed tasks: {request_dict.get('failed_tasks')}"))
       report.append(
-          self.bullet(f"Failed tasks: {request_dict.get('failed_tasks'):d}"))
-      report.append(
-          self.bullet(f"Running tasks: {request_dict.get('running_tasks'):d}"))
+          self.bullet(f"Running tasks: {request_dict.get('running_tasks')}"))
       report.append(
           self.bullet(
-              f"Successful tasks: {request_dict.get('successful_tasks'):d}"))
+              f"Successful tasks: {request_dict.get('successful_tasks')}"))
       report.append(
-          self.bullet(f"Task Count: {request_dict.get('task_count'):d}"))
+          self.bullet(f"Task Count: {request_dict.get('task_count')}"))
       report.append(
-          self.bullet(f"Queued tasks: {request_dict.get('queued_tasks'):d}"))
+          self.bullet(f"Queued tasks: {request_dict.get('queued_tasks')}"))
       report.append(
-          self.bullet(f"Evidence Name: {request_dict.get('evidence_name'):d}"))
+          self.bullet(f"Evidence Name: {request_dict.get('evidence_name')}"))
       report.append('')
     except TypeError as exception:
       log.warning(f'Error formatting the Markdown report: {exception!s}')

{"golden_diff": "diff --git a/turbinia/api/cli/turbinia_client/helpers/formatter.py b/turbinia/api/cli/turbinia_client/helpers/formatter.py\n--- a/turbinia/api/cli/turbinia_client/helpers/formatter.py\n+++ b/turbinia/api/cli/turbinia_client/helpers/formatter.py\n@@ -194,7 +194,7 @@\n       report.append(self.heading2(task.get('name')))\n       line = f\"{self.bold('Evidence:'):s} {task.get('evidence_name')!s}\"\n       report.append(self.bullet(line))\n-      line = f\"{self.bold('Status:'):s} {task.get('status'):s}\"\n+      line = f\"{self.bold('Status:'):s} {task.get('status')!s}\"\n       report.append(self.bullet(line))\n       report.append(self.bullet(f\"Task Id: {task.get('id')!s}\"))\n       report.append(\n@@ -253,27 +253,26 @@\n \n     try:\n       report.append(\n-          self.heading2(f\"Request ID: {request_dict.get('request_id')!s}\"))\n+          self.heading2(f\"Request ID: {request_dict.get('request_id')}\"))\n       report.append(\n           self.bullet(\n-              f\"Last Update: {request_dict.get('last_task_update_time')!s}\"))\n+              f\"Last Update: {request_dict.get('last_task_update_time')}\"))\n+      report.append(self.bullet(f\"Requester: {request_dict.get('requester')}\"))\n+      report.append(self.bullet(f\"Reason: {request_dict.get('reason')}\"))\n+      report.append(self.bullet(f\"Status: {request_dict.get('status')}\"))\n       report.append(\n-          self.bullet(f\"Requester: {request_dict.get('requester')!s}\"))\n-      report.append(self.bullet(f\"Reason: {request_dict.get('reason')!s}\"))\n-      report.append(self.bullet(f\"Status: {request_dict.get('status')!s}\"))\n+          self.bullet(f\"Failed tasks: {request_dict.get('failed_tasks')}\"))\n       report.append(\n-          self.bullet(f\"Failed tasks: {request_dict.get('failed_tasks'):d}\"))\n-      report.append(\n-          self.bullet(f\"Running tasks: {request_dict.get('running_tasks'):d}\"))\n+          self.bullet(f\"Running tasks: {request_dict.get('running_tasks')}\"))\n       report.append(\n           self.bullet(\n-              f\"Successful tasks: {request_dict.get('successful_tasks'):d}\"))\n+              f\"Successful tasks: {request_dict.get('successful_tasks')}\"))\n       report.append(\n-          self.bullet(f\"Task Count: {request_dict.get('task_count'):d}\"))\n+          self.bullet(f\"Task Count: {request_dict.get('task_count')}\"))\n       report.append(\n-          self.bullet(f\"Queued tasks: {request_dict.get('queued_tasks'):d}\"))\n+          self.bullet(f\"Queued tasks: {request_dict.get('queued_tasks')}\"))\n       report.append(\n-          self.bullet(f\"Evidence Name: {request_dict.get('evidence_name'):d}\"))\n+          self.bullet(f\"Evidence Name: {request_dict.get('evidence_name')}\"))\n       report.append('')\n     except TypeError as exception:\n       log.warning(f'Error formatting the Markdown report: {exception!s}')\n", "issue": "turbinia-client status summary formatting warning\nRunning turbini-client status summary might yield the following warning for some requests:\r\n\r\n```WARNING turbinia - Error formatting the Markdown report: unsupported format string passed to NoneType.__format__```\n", "before_files": [{"content": "#!/usr/bin/env python\n#\n# Copyright 2017 Google Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"Methods for formatting text.\"\"\"\n\nfrom __future__ import annotations\n\nfrom abc import ABC, abstractmethod\nfrom click import echo as click_echo\n\nimport logging\nimport json\n\nlog = logging.getLogger('turbinia')\n\n\ndef echo_json(json_data: dict) -> None:\n  \"\"\"Pretty print JSON data.\"\"\"\n  if isinstance(json_data, dict):\n    click_echo(json.dumps(json_data, indent=2))\n\n\nclass MarkdownReportComponent(ABC):\n  \"\"\"Components for generating Turbinia request/task\n      markdown reports.\n  \"\"\"\n\n  def __init__(self):\n    \"\"\"Instantiates a MarkdownReportComponent object.\"\"\"\n    self._components: list(MarkdownReportComponent) = []\n    self._parent: MarkdownReportComponent = None\n    self._report: str = None\n\n  @property\n  def components(self):\n    \"\"\"Returns the components list.\"\"\"\n    return self._components\n\n  @property\n  def report(self):\n    \"\"\"Returns the markdown report text.\"\"\"\n    return self._report\n\n  @report.setter\n  def report(self, report):\n    self._report = report\n\n  @property\n  def parent(self) -> MarkdownReportComponent:\n    \"\"\"Returns the parent object.\"\"\"\n    return self._parent\n\n  @parent.setter\n  def parent(self, parent: MarkdownReportComponent):\n    self._parent = parent\n\n  def bold(self, text):\n    \"\"\"Formats text as bold in Markdown format.\n\n    Args:\n        text(string): Text to format\n\n    Return:\n        string: Formatted text.\n    \"\"\"\n    return f'**{text.strip():s}**'\n\n  def heading1(self, text):\n    \"\"\"Formats text as heading 1 in Markdown format.\n\n    Args:\n        text(string): Text to format\n\n    Return:\n        string: Formatted text.\n    \"\"\"\n    return f'# {text.strip():s}'\n\n  def heading2(self, text):\n    \"\"\"Formats text as heading 2 in Markdown format.\n\n    Args:\n        text(string): Text to format\n\n    Return:\n        string: Formatted text.\n    \"\"\"\n    return f'## {text.strip():s}'\n\n  def heading3(self, text):\n    \"\"\"Formats text as heading 3 in Markdown format.\n\n    Args:\n        text(string): Text to format\n\n    Return:\n        string: Formatted text.\n    \"\"\"\n    return f'### {text.strip():s}'\n\n  def heading4(self, text):\n    \"\"\"Formats text as heading 4 in Markdown format.\n\n    Args:\n        text(string): Text to format\n\n    Return:\n        string: Formatted text.\n    \"\"\"\n    return f'#### {text.strip():s}'\n\n  def heading5(self, text):\n    \"\"\"Formats text as heading 5 in Markdown format.\n     Args:\n        text(string): Text to format\n     Return:\n        string: Formatted text.\n    \"\"\"\n    return f'##### {text.strip():s}'\n\n  def bullet(self, text, level=1):\n    \"\"\"Formats text as a bullet in Markdown format.\n\n      Args:\n        text(string): Text to format\n        level(int): Indentation level.\n      Return:\n        string: Formatted text.\n    \"\"\"\n    return f\"{'    ' * (level - 1):s}* {text.strip():s}\"\n\n  def code(self, text):\n    \"\"\"Formats text as code in Markdown format.\n\n      Args:\n          text(string): Text to format\n\n     Return:\n          string: Formatted text.\n    \"\"\"\n    return f'`{text.strip():s}`'\n\n  def add(self, component: MarkdownReportComponent) -> None:\n    \"\"\"Adds a MarkdownReportComponent object to the components list.\n\n    This method should additionally set the parent object.\n    \"\"\"\n    pass\n\n  def add_components(self, components: list[MarkdownReportComponent]) -> None:\n    \"\"\"Adds multiple MarkdownReportComponent objects to the components list.\"\"\"\n    pass\n\n  def remove(self, component: MarkdownReportComponent) -> None:\n    \"\"\"Removes a MarkdownReportComponent object from the components list.\n\n    This method should set the component's object to None.\n    \"\"\"\n    pass\n\n  @abstractmethod\n  def generate_markdown(self) -> str:\n    pass\n\n\nclass TaskMarkdownReport(MarkdownReportComponent):\n  \"\"\"Turbinia Task markdown report.\"\"\"\n\n  def __init__(self, request_data: dict = None):\n    \"\"\"Initialize TaskMarkdownReport\"\"\"\n    super().__init__()\n    self._request_data: dict = request_data\n\n  def generate_markdown(self) -> str:\n    \"\"\"Generate a markdown report.\"\"\"\n    report: list[str] = []\n    task: dict = self._request_data\n    if not task:\n      return ''\n\n    try:\n      report.append(self.heading2(task.get('name')))\n      line = f\"{self.bold('Evidence:'):s} {task.get('evidence_name')!s}\"\n      report.append(self.bullet(line))\n      line = f\"{self.bold('Status:'):s} {task.get('status'):s}\"\n      report.append(self.bullet(line))\n      report.append(self.bullet(f\"Task Id: {task.get('id')!s}\"))\n      report.append(\n          self.bullet(f\"Executed on worker {task.get('worker_name')!s}\"))\n      if task.get('report_data'):\n        report.append('')\n        report.append(self.heading3('Task Reported Data'))\n        report.extend(task.get('report_data').splitlines())\n      report.append('')\n      report.append(self.heading3('Saved Task Files:'))\n\n      saved_paths = task.get('saved_paths')\n      if saved_paths:\n        for path in saved_paths:\n          report.append(self.bullet(self.code(path)))\n          report.append('')\n    except TypeError as exception:\n      log.warning(f'Error formatting the Markdown report: {exception!s}')\n\n    self.report = '\\n'.join(report)\n    return self.report\n\n\nclass RequestMarkdownReport(MarkdownReportComponent):\n  \"\"\"Turbinia Request Markdown report.\"\"\"\n\n  def __init__(self, request_data: dict):\n    \"\"\"Initializes a RequestMarkdownReport object.\"\"\"\n    super().__init__()\n    self._request_data: dict = request_data\n\n    tasks = [TaskMarkdownReport(task) for task in request_data.get('tasks')]\n    self.add_components(tasks)\n\n  def add(self, component: MarkdownReportComponent) -> None:\n    if component:\n      self.components.append(component)\n      component.parent = self\n\n  def remove(self, component: MarkdownReportComponent) -> None:\n    self.components.remove(component)\n    component.parent = None\n\n  def add_components(self, components: list[MarkdownReportComponent]) -> None:\n    if components:\n      for component in components:\n        self.components.append(component)\n        component.parent = self\n\n  def generate_markdown(self) -> str:\n    \"\"\"Generates a Markdown version of Requests results.\"\"\"\n    report: list[str] = []\n    request_dict: dict = self._request_data\n    if not request_dict:\n      return ''\n\n    try:\n      report.append(\n          self.heading2(f\"Request ID: {request_dict.get('request_id')!s}\"))\n      report.append(\n          self.bullet(\n              f\"Last Update: {request_dict.get('last_task_update_time')!s}\"))\n      report.append(\n          self.bullet(f\"Requester: {request_dict.get('requester')!s}\"))\n      report.append(self.bullet(f\"Reason: {request_dict.get('reason')!s}\"))\n      report.append(self.bullet(f\"Status: {request_dict.get('status')!s}\"))\n      report.append(\n          self.bullet(f\"Failed tasks: {request_dict.get('failed_tasks'):d}\"))\n      report.append(\n          self.bullet(f\"Running tasks: {request_dict.get('running_tasks'):d}\"))\n      report.append(\n          self.bullet(\n              f\"Successful tasks: {request_dict.get('successful_tasks'):d}\"))\n      report.append(\n          self.bullet(f\"Task Count: {request_dict.get('task_count'):d}\"))\n      report.append(\n          self.bullet(f\"Queued tasks: {request_dict.get('queued_tasks'):d}\"))\n      report.append(\n          self.bullet(f\"Evidence Name: {request_dict.get('evidence_name'):d}\"))\n      report.append('')\n    except TypeError as exception:\n      log.warning(f'Error formatting the Markdown report: {exception!s}')\n\n    for task in self.components:\n      report.append(task.generate_markdown())\n\n    self.report = '\\n'.join(report)\n    return self.report\n\n\nclass SummaryMarkdownReport(MarkdownReportComponent):\n  \"\"\"A markdown report summary of all Turbinia Requests.\"\"\"\n\n  def __init__(self, requests_summary: list[dict]):\n    \"\"\"Initialize SummaryMarkdownReport.\"\"\"\n    super().__init__()\n    self._requests_summary = requests_summary\n\n  def generate_markdown(self) -> str:\n    \"\"\"Generate a Markdown version of Requests summary results.\"\"\"\n    report: list[str] = []\n    requests_status_list = None\n    if self._requests_summary:\n      requests_status_list = self._requests_summary.get('requests_status')\n\n    if not requests_status_list:\n      return '## No requests found.'\n\n    for request_dict in requests_status_list:\n      request_report = RequestMarkdownReport(request_dict).generate_markdown()\n      report.append(request_report)\n\n    self.report = '\\n'.join(report)\n    return self.report\n\n\nclass WorkersMarkdownReport(MarkdownReportComponent):\n  \"\"\"A markdown report of all tasks for a specific worker.\"\"\"\n\n  def __init__(self, request_data: dict):\n    super().__init__()\n    self._request_data: dict = request_data\n\n  def generate_markdown(self) -> str:\n    \"\"\"Generates a Markdown version of tasks per worker.\"\"\"\n    raise NotImplementedError\n", "path": "turbinia/api/cli/turbinia_client/helpers/formatter.py"}], "after_files": [{"content": "#!/usr/bin/env python\n#\n# Copyright 2017 Google Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"Methods for formatting text.\"\"\"\n\nfrom __future__ import annotations\n\nfrom abc import ABC, abstractmethod\nfrom click import echo as click_echo\n\nimport logging\nimport json\n\nlog = logging.getLogger('turbinia')\n\n\ndef echo_json(json_data: dict) -> None:\n  \"\"\"Pretty print JSON data.\"\"\"\n  if isinstance(json_data, dict):\n    click_echo(json.dumps(json_data, indent=2))\n\n\nclass MarkdownReportComponent(ABC):\n  \"\"\"Components for generating Turbinia request/task\n      markdown reports.\n  \"\"\"\n\n  def __init__(self):\n    \"\"\"Instantiates a MarkdownReportComponent object.\"\"\"\n    self._components: list(MarkdownReportComponent) = []\n    self._parent: MarkdownReportComponent = None\n    self._report: str = None\n\n  @property\n  def components(self):\n    \"\"\"Returns the components list.\"\"\"\n    return self._components\n\n  @property\n  def report(self):\n    \"\"\"Returns the markdown report text.\"\"\"\n    return self._report\n\n  @report.setter\n  def report(self, report):\n    self._report = report\n\n  @property\n  def parent(self) -> MarkdownReportComponent:\n    \"\"\"Returns the parent object.\"\"\"\n    return self._parent\n\n  @parent.setter\n  def parent(self, parent: MarkdownReportComponent):\n    self._parent = parent\n\n  def bold(self, text):\n    \"\"\"Formats text as bold in Markdown format.\n\n    Args:\n        text(string): Text to format\n\n    Return:\n        string: Formatted text.\n    \"\"\"\n    return f'**{text.strip():s}**'\n\n  def heading1(self, text):\n    \"\"\"Formats text as heading 1 in Markdown format.\n\n    Args:\n        text(string): Text to format\n\n    Return:\n        string: Formatted text.\n    \"\"\"\n    return f'# {text.strip():s}'\n\n  def heading2(self, text):\n    \"\"\"Formats text as heading 2 in Markdown format.\n\n    Args:\n        text(string): Text to format\n\n    Return:\n        string: Formatted text.\n    \"\"\"\n    return f'## {text.strip():s}'\n\n  def heading3(self, text):\n    \"\"\"Formats text as heading 3 in Markdown format.\n\n    Args:\n        text(string): Text to format\n\n    Return:\n        string: Formatted text.\n    \"\"\"\n    return f'### {text.strip():s}'\n\n  def heading4(self, text):\n    \"\"\"Formats text as heading 4 in Markdown format.\n\n    Args:\n        text(string): Text to format\n\n    Return:\n        string: Formatted text.\n    \"\"\"\n    return f'#### {text.strip():s}'\n\n  def heading5(self, text):\n    \"\"\"Formats text as heading 5 in Markdown format.\n     Args:\n        text(string): Text to format\n     Return:\n        string: Formatted text.\n    \"\"\"\n    return f'##### {text.strip():s}'\n\n  def bullet(self, text, level=1):\n    \"\"\"Formats text as a bullet in Markdown format.\n\n      Args:\n        text(string): Text to format\n        level(int): Indentation level.\n      Return:\n        string: Formatted text.\n    \"\"\"\n    return f\"{'    ' * (level - 1):s}* {text.strip():s}\"\n\n  def code(self, text):\n    \"\"\"Formats text as code in Markdown format.\n\n      Args:\n          text(string): Text to format\n\n     Return:\n          string: Formatted text.\n    \"\"\"\n    return f'`{text.strip():s}`'\n\n  def add(self, component: MarkdownReportComponent) -> None:\n    \"\"\"Adds a MarkdownReportComponent object to the components list.\n\n    This method should additionally set the parent object.\n    \"\"\"\n    pass\n\n  def add_components(self, components: list[MarkdownReportComponent]) -> None:\n    \"\"\"Adds multiple MarkdownReportComponent objects to the components list.\"\"\"\n    pass\n\n  def remove(self, component: MarkdownReportComponent) -> None:\n    \"\"\"Removes a MarkdownReportComponent object from the components list.\n\n    This method should set the component's object to None.\n    \"\"\"\n    pass\n\n  @abstractmethod\n  def generate_markdown(self) -> str:\n    pass\n\n\nclass TaskMarkdownReport(MarkdownReportComponent):\n  \"\"\"Turbinia Task markdown report.\"\"\"\n\n  def __init__(self, request_data: dict = None):\n    \"\"\"Initialize TaskMarkdownReport\"\"\"\n    super().__init__()\n    self._request_data: dict = request_data\n\n  def generate_markdown(self) -> str:\n    \"\"\"Generate a markdown report.\"\"\"\n    report: list[str] = []\n    task: dict = self._request_data\n    if not task:\n      return ''\n\n    try:\n      report.append(self.heading2(task.get('name')))\n      line = f\"{self.bold('Evidence:'):s} {task.get('evidence_name')!s}\"\n      report.append(self.bullet(line))\n      line = f\"{self.bold('Status:'):s} {task.get('status')!s}\"\n      report.append(self.bullet(line))\n      report.append(self.bullet(f\"Task Id: {task.get('id')!s}\"))\n      report.append(\n          self.bullet(f\"Executed on worker {task.get('worker_name')!s}\"))\n      if task.get('report_data'):\n        report.append('')\n        report.append(self.heading3('Task Reported Data'))\n        report.extend(task.get('report_data').splitlines())\n      report.append('')\n      report.append(self.heading3('Saved Task Files:'))\n\n      saved_paths = task.get('saved_paths')\n      if saved_paths:\n        for path in saved_paths:\n          report.append(self.bullet(self.code(path)))\n          report.append('')\n    except TypeError as exception:\n      log.warning(f'Error formatting the Markdown report: {exception!s}')\n\n    self.report = '\\n'.join(report)\n    return self.report\n\n\nclass RequestMarkdownReport(MarkdownReportComponent):\n  \"\"\"Turbinia Request Markdown report.\"\"\"\n\n  def __init__(self, request_data: dict):\n    \"\"\"Initializes a RequestMarkdownReport object.\"\"\"\n    super().__init__()\n    self._request_data: dict = request_data\n\n    tasks = [TaskMarkdownReport(task) for task in request_data.get('tasks')]\n    self.add_components(tasks)\n\n  def add(self, component: MarkdownReportComponent) -> None:\n    if component:\n      self.components.append(component)\n      component.parent = self\n\n  def remove(self, component: MarkdownReportComponent) -> None:\n    self.components.remove(component)\n    component.parent = None\n\n  def add_components(self, components: list[MarkdownReportComponent]) -> None:\n    if components:\n      for component in components:\n        self.components.append(component)\n        component.parent = self\n\n  def generate_markdown(self) -> str:\n    \"\"\"Generates a Markdown version of Requests results.\"\"\"\n    report: list[str] = []\n    request_dict: dict = self._request_data\n    if not request_dict:\n      return ''\n\n    try:\n      report.append(\n          self.heading2(f\"Request ID: {request_dict.get('request_id')}\"))\n      report.append(\n          self.bullet(\n              f\"Last Update: {request_dict.get('last_task_update_time')}\"))\n      report.append(self.bullet(f\"Requester: {request_dict.get('requester')}\"))\n      report.append(self.bullet(f\"Reason: {request_dict.get('reason')}\"))\n      report.append(self.bullet(f\"Status: {request_dict.get('status')}\"))\n      report.append(\n          self.bullet(f\"Failed tasks: {request_dict.get('failed_tasks')}\"))\n      report.append(\n          self.bullet(f\"Running tasks: {request_dict.get('running_tasks')}\"))\n      report.append(\n          self.bullet(\n              f\"Successful tasks: {request_dict.get('successful_tasks')}\"))\n      report.append(\n          self.bullet(f\"Task Count: {request_dict.get('task_count')}\"))\n      report.append(\n          self.bullet(f\"Queued tasks: {request_dict.get('queued_tasks')}\"))\n      report.append(\n          self.bullet(f\"Evidence Name: {request_dict.get('evidence_name')}\"))\n      report.append('')\n    except TypeError as exception:\n      log.warning(f'Error formatting the Markdown report: {exception!s}')\n\n    for task in self.components:\n      report.append(task.generate_markdown())\n\n    self.report = '\\n'.join(report)\n    return self.report\n\n\nclass SummaryMarkdownReport(MarkdownReportComponent):\n  \"\"\"A markdown report summary of all Turbinia Requests.\"\"\"\n\n  def __init__(self, requests_summary: list[dict]):\n    \"\"\"Initialize SummaryMarkdownReport.\"\"\"\n    super().__init__()\n    self._requests_summary = requests_summary\n\n  def generate_markdown(self) -> str:\n    \"\"\"Generate a Markdown version of Requests summary results.\"\"\"\n    report: list[str] = []\n    requests_status_list = None\n    if self._requests_summary:\n      requests_status_list = self._requests_summary.get('requests_status')\n\n    if not requests_status_list:\n      return '## No requests found.'\n\n    for request_dict in requests_status_list:\n      request_report = RequestMarkdownReport(request_dict).generate_markdown()\n      report.append(request_report)\n\n    self.report = '\\n'.join(report)\n    return self.report\n\n\nclass WorkersMarkdownReport(MarkdownReportComponent):\n  \"\"\"A markdown report of all tasks for a specific worker.\"\"\"\n\n  def __init__(self, request_data: dict):\n    super().__init__()\n    self._request_data: dict = request_data\n\n  def generate_markdown(self) -> str:\n    \"\"\"Generates a Markdown version of tasks per worker.\"\"\"\n    raise NotImplementedError\n", "path": "turbinia/api/cli/turbinia_client/helpers/formatter.py"}]}