azdev 0.1.48__tar.gz → 0.1.53__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. {azdev-0.1.48 → azdev-0.1.53}/HISTORY.rst +21 -0
  2. {azdev-0.1.48/azdev.egg-info → azdev-0.1.53}/PKG-INFO +22 -1
  3. {azdev-0.1.48 → azdev-0.1.53}/azdev/__init__.py +1 -1
  4. {azdev-0.1.48 → azdev-0.1.53}/azdev/commands.py +3 -0
  5. {azdev-0.1.48 → azdev-0.1.53}/azdev/help.py +11 -0
  6. azdev-0.1.53/azdev/operations/cmdcov/__init__.py +186 -0
  7. azdev-0.1.53/azdev/operations/cmdcov/_macros.j2 +35 -0
  8. azdev-0.1.53/azdev/operations/cmdcov/cmdcov.py +489 -0
  9. azdev-0.1.53/azdev/operations/cmdcov/component.css +235 -0
  10. azdev-0.1.53/azdev/operations/cmdcov/component.js +159 -0
  11. azdev-0.1.53/azdev/operations/cmdcov/favicon.ico +0 -0
  12. azdev-0.1.53/azdev/operations/cmdcov/index.j2 +66 -0
  13. azdev-0.1.53/azdev/operations/cmdcov/index2.j2 +60 -0
  14. azdev-0.1.53/azdev/operations/cmdcov/module.j2 +52 -0
  15. azdev-0.1.53/azdev/operations/cmdcov/tested_command.txt +15665 -0
  16. {azdev-0.1.48 → azdev-0.1.53}/azdev/operations/command_change/custom.py +2 -1
  17. {azdev-0.1.48 → azdev-0.1.53}/azdev/operations/command_change/meta_changes.py +3 -1
  18. azdev-0.1.53/azdev/operations/constant.py +222 -0
  19. {azdev-0.1.48 → azdev-0.1.53}/azdev/operations/linter/__init__.py +8 -2
  20. {azdev-0.1.48 → azdev-0.1.53}/azdev/operations/linter/linter.py +202 -14
  21. {azdev-0.1.48 → azdev-0.1.53}/azdev/operations/linter/rule_decorators.py +23 -1
  22. azdev-0.1.53/azdev/operations/linter/rules/command_coverage_rules.py +24 -0
  23. {azdev-0.1.48 → azdev-0.1.53}/azdev/operations/linter/rules/help_rules.py +1 -0
  24. azdev-0.1.53/azdev/operations/regex.py +161 -0
  25. {azdev-0.1.48 → azdev-0.1.53}/azdev/params.py +7 -2
  26. {azdev-0.1.48 → azdev-0.1.53}/azdev/utilities/__init__.py +8 -2
  27. {azdev-0.1.48 → azdev-0.1.53}/azdev/utilities/const.py +3 -0
  28. {azdev-0.1.48 → azdev-0.1.53}/azdev/utilities/git_util.py +36 -1
  29. {azdev-0.1.48 → azdev-0.1.53/azdev.egg-info}/PKG-INFO +22 -1
  30. {azdev-0.1.48 → azdev-0.1.53}/azdev.egg-info/SOURCES.txt +13 -0
  31. {azdev-0.1.48 → azdev-0.1.53}/azdev.egg-info/requires.txt +1 -0
  32. {azdev-0.1.48 → azdev-0.1.53}/setup.py +4 -1
  33. {azdev-0.1.48 → azdev-0.1.53}/LICENSE +0 -0
  34. {azdev-0.1.48 → azdev-0.1.53}/MANIFEST.in +0 -0
  35. {azdev-0.1.48 → azdev-0.1.53}/README.md +0 -0
  36. {azdev-0.1.48 → azdev-0.1.53}/README.rst +0 -0
  37. {azdev-0.1.48 → azdev-0.1.53}/azdev/__main__.py +0 -0
  38. {azdev-0.1.48 → azdev-0.1.53}/azdev/completer.py +0 -0
  39. {azdev-0.1.48 → azdev-0.1.53}/azdev/config/__init__.py +0 -0
  40. {azdev-0.1.48 → azdev-0.1.53}/azdev/config/cli.flake8 +0 -0
  41. {azdev-0.1.48 → azdev-0.1.53}/azdev/config/cli_pylintrc +0 -0
  42. {azdev-0.1.48 → azdev-0.1.53}/azdev/config/ext.flake8 +0 -0
  43. {azdev-0.1.48 → azdev-0.1.53}/azdev/config/ext_pylintrc +0 -0
  44. {azdev-0.1.48 → azdev-0.1.53}/azdev/mod_templates/HISTORY.rst +0 -0
  45. {azdev-0.1.48 → azdev-0.1.53}/azdev/mod_templates/README.rst +0 -0
  46. {azdev-0.1.48 → azdev-0.1.53}/azdev/mod_templates/_client_factory.py +0 -0
  47. {azdev-0.1.48 → azdev-0.1.53}/azdev/mod_templates/_help.py +0 -0
  48. {azdev-0.1.48 → azdev-0.1.53}/azdev/mod_templates/_params.py +0 -0
  49. {azdev-0.1.48 → azdev-0.1.53}/azdev/mod_templates/_validators.py +0 -0
  50. {azdev-0.1.48 → azdev-0.1.53}/azdev/mod_templates/azext_metadata.json +0 -0
  51. {azdev-0.1.48 → azdev-0.1.53}/azdev/mod_templates/blank__init__.py +0 -0
  52. {azdev-0.1.48 → azdev-0.1.53}/azdev/mod_templates/commands.py +0 -0
  53. {azdev-0.1.48 → azdev-0.1.53}/azdev/mod_templates/custom.py +0 -0
  54. {azdev-0.1.48 → azdev-0.1.53}/azdev/mod_templates/module__init__.py +0 -0
  55. {azdev-0.1.48 → azdev-0.1.53}/azdev/mod_templates/pkg_declare__init__.py +0 -0
  56. {azdev-0.1.48 → azdev-0.1.53}/azdev/mod_templates/setup.cfg +0 -0
  57. {azdev-0.1.48 → azdev-0.1.53}/azdev/mod_templates/setup.py +0 -0
  58. {azdev-0.1.48 → azdev-0.1.53}/azdev/mod_templates/test_service_scenario.py +0 -0
  59. {azdev-0.1.48 → azdev-0.1.53}/azdev/operations/__init__.py +0 -0
  60. {azdev-0.1.48 → azdev-0.1.53}/azdev/operations/code_gen.py +0 -0
  61. {azdev-0.1.48 → azdev-0.1.53}/azdev/operations/command_change/__init__.py +0 -0
  62. {azdev-0.1.48 → azdev-0.1.53}/azdev/operations/command_change/util.py +0 -0
  63. {azdev-0.1.48 → azdev-0.1.53}/azdev/operations/extensions/__init__.py +0 -0
  64. {azdev-0.1.48 → azdev-0.1.53}/azdev/operations/extensions/util.py +0 -0
  65. {azdev-0.1.48 → azdev-0.1.53}/azdev/operations/help/__init__.py +0 -0
  66. {azdev-0.1.48 → azdev-0.1.53}/azdev/operations/help/refdoc/__init__.py +0 -0
  67. {azdev-0.1.48 → azdev-0.1.53}/azdev/operations/help/refdoc/conf.py +0 -0
  68. {azdev-0.1.48 → azdev-0.1.53}/azdev/operations/legal.py +0 -0
  69. {azdev-0.1.48 → azdev-0.1.53}/azdev/operations/linter/pylint_checkers/__init__.py +0 -0
  70. {azdev-0.1.48 → azdev-0.1.53}/azdev/operations/linter/pylint_checkers/show_command.py +0 -0
  71. {azdev-0.1.48 → azdev-0.1.53}/azdev/operations/linter/rules/__init__.py +0 -0
  72. {azdev-0.1.48 → azdev-0.1.53}/azdev/operations/linter/rules/ci_exclusions.yml +0 -0
  73. {azdev-0.1.48 → azdev-0.1.53}/azdev/operations/linter/rules/command_group_rules.py +0 -0
  74. {azdev-0.1.48 → azdev-0.1.53}/azdev/operations/linter/rules/command_rules.py +0 -0
  75. {azdev-0.1.48 → azdev-0.1.53}/azdev/operations/linter/rules/linter_exclusions.yml +0 -0
  76. {azdev-0.1.48 → azdev-0.1.53}/azdev/operations/linter/rules/parameter_rules.py +0 -0
  77. {azdev-0.1.48 → azdev-0.1.53}/azdev/operations/linter/util.py +0 -0
  78. {azdev-0.1.48 → azdev-0.1.53}/azdev/operations/performance.py +0 -0
  79. {azdev-0.1.48 → azdev-0.1.53}/azdev/operations/pypi.py +0 -0
  80. {azdev-0.1.48 → azdev-0.1.53}/azdev/operations/python_sdk.py +0 -0
  81. {azdev-0.1.48 → azdev-0.1.53}/azdev/operations/resource.py +0 -0
  82. {azdev-0.1.48 → azdev-0.1.53}/azdev/operations/setup.py +0 -0
  83. {azdev-0.1.48 → azdev-0.1.53}/azdev/operations/statistics/__init__.py +0 -0
  84. {azdev-0.1.48 → azdev-0.1.53}/azdev/operations/statistics/util.py +0 -0
  85. {azdev-0.1.48 → azdev-0.1.53}/azdev/operations/style.py +0 -0
  86. {azdev-0.1.48 → azdev-0.1.53}/azdev/operations/testtool/__init__.py +0 -0
  87. {azdev-0.1.48 → azdev-0.1.53}/azdev/operations/testtool/incremental_strategy.py +0 -0
  88. {azdev-0.1.48 → azdev-0.1.53}/azdev/operations/testtool/profile_context.py +0 -0
  89. {azdev-0.1.48 → azdev-0.1.53}/azdev/operations/testtool/pytest_runner.py +0 -0
  90. {azdev-0.1.48 → azdev-0.1.53}/azdev/transformers.py +0 -0
  91. {azdev-0.1.48 → azdev-0.1.53}/azdev/utilities/command.py +0 -0
  92. {azdev-0.1.48 → azdev-0.1.53}/azdev/utilities/config.py +0 -0
  93. {azdev-0.1.48 → azdev-0.1.53}/azdev/utilities/display.py +0 -0
  94. {azdev-0.1.48 → azdev-0.1.53}/azdev/utilities/path.py +0 -0
  95. {azdev-0.1.48 → azdev-0.1.53}/azdev/utilities/pypi.py +0 -0
  96. {azdev-0.1.48 → azdev-0.1.53}/azdev/utilities/testing.py +0 -0
  97. {azdev-0.1.48 → azdev-0.1.53}/azdev/utilities/tools.py +0 -0
  98. {azdev-0.1.48 → azdev-0.1.53}/azdev.egg-info/dependency_links.txt +0 -0
  99. {azdev-0.1.48 → azdev-0.1.53}/azdev.egg-info/entry_points.txt +0 -0
  100. {azdev-0.1.48 → azdev-0.1.53}/azdev.egg-info/top_level.txt +0 -0
  101. {azdev-0.1.48 → azdev-0.1.53}/pyproject.toml +0 -0
  102. {azdev-0.1.48 → azdev-0.1.53}/setup.cfg +0 -0
@@ -2,6 +2,27 @@
2
2
 
3
3
  Release History
4
4
  ===============
5
+ 0.1.53
6
+ ++++++
7
+ * `azdev command-change meta-export`: Add rule link (#402)
8
+
9
+ 0.1.52
10
+ ++++++
11
+ * Fix cmdcov issue(#396): Fix the regex which to get the command group
12
+
13
+ 0.1.51
14
+ ++++++
15
+ * Fix cmdcov issue(#391): Add violation message to cmdcov linter rule and fix related issues
16
+
17
+ 0.1.50
18
+ ++++++
19
+ * Fix cmdcov issue(#385): Add cmdcov to package data
20
+
21
+ 0.1.49
22
+ ++++++
23
+ * Add Command Coverage Report for CLI modules (#323)
24
+ * Add Linter rule missing_command_coverage and missing_parameter_coverage (#323)
25
+ * Add Command Coverage Report for CLI extensions (#383)
5
26
 
6
27
  0.1.48
7
28
  ++++++
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: azdev
3
- Version: 0.1.48
3
+ Version: 0.1.53
4
4
  Summary: Microsoft Azure CLI Developer Tools
5
5
  Home-page: https://github.com/Azure/azure-cli-dev-tools
6
6
  Author: Microsoft Corporation
@@ -125,6 +125,27 @@ License
125
125
 
126
126
  Release History
127
127
  ===============
128
+ 0.1.53
129
+ ++++++
130
+ * `azdev command-change meta-export`: Add rule link (#402)
131
+
132
+ 0.1.52
133
+ ++++++
134
+ * Fix cmdcov issue(#396): Fix the regex which to get the command group
135
+
136
+ 0.1.51
137
+ ++++++
138
+ * Fix cmdcov issue(#391): Add violation message to cmdcov linter rule and fix related issues
139
+
140
+ 0.1.50
141
+ ++++++
142
+ * Fix cmdcov issue(#385): Add cmdcov to package data
143
+
144
+ 0.1.49
145
+ ++++++
146
+ * Add Command Coverage Report for CLI modules (#323)
147
+ * Add Linter rule missing_command_coverage and missing_parameter_coverage (#323)
148
+ * Add Command Coverage Report for CLI extensions (#383)
128
149
 
129
150
  0.1.48
130
151
  ++++++
@@ -4,4 +4,4 @@
4
4
  # license information.
5
5
  # -----------------------------------------------------------------------------
6
6
 
7
- __VERSION__ = '0.1.48'
7
+ __VERSION__ = '0.1.53'
@@ -31,6 +31,9 @@ def load_command_table(self, _):
31
31
  g.command('list-command-table', 'list_command_table')
32
32
  g.command('diff-command-tables', 'diff_command_tables')
33
33
 
34
+ with CommandGroup(self, '', operation_group('cmdcov')) as g:
35
+ g.command('cmdcov', 'run_cmdcov')
36
+
34
37
  with CommandGroup(self, 'verify', operation_group('pypi')) as g:
35
38
  g.command('history', 'check_history')
36
39
 
@@ -280,3 +280,14 @@ helps['extension generate-docs'] = """
280
280
  long-summary: >
281
281
  This command installs the extensions in a temporary directory and sets it as the extensions dir when generating reference docs.
282
282
  """
283
+
284
+ helps['cmdcov'] = """
285
+ short-summary: Run command test coverage and generate CLI command test coverage report.
286
+ examples:
287
+ - name: Check all CLI modules command test coverage.
288
+ text: azdev cmdcov CLI
289
+ - name: Check one or serveral modules command test coverage.
290
+ text: azdev cmdcov vm storage
291
+ - name: Check CLI modules command test coverage in argument level.
292
+ text: azdev cmdcov CLI --level argument
293
+ """
@@ -0,0 +1,186 @@
1
+ # -----------------------------------------------------------------------------
2
+ # Copyright (c) Microsoft Corporation. All rights reserved.
3
+ # Licensed under the MIT License. See License.txt in the project root for
4
+ # license information.
5
+ # -----------------------------------------------------------------------------
6
+
7
+ import os
8
+ import time
9
+ import yaml
10
+
11
+ from knack.log import get_logger
12
+ from knack.util import CLIError
13
+ from azdev.utilities import (
14
+ heading, display, get_path_table, require_azure_cli, filter_by_git_diff)
15
+ from azdev.utilities.path import get_cli_repo_path, get_ext_repo_paths
16
+ from .cmdcov import CmdcovManager
17
+
18
+ logger = get_logger(__name__)
19
+
20
+ try:
21
+ with open(os.path.join(get_cli_repo_path(), 'scripts', 'ci', 'cmdcov.yml'), 'r') as file:
22
+ config = yaml.safe_load(file)
23
+ EXCLUDE_MODULES = config['EXCLUDE_MODULES']
24
+ except CLIError as ex:
25
+ logger.warning('Failed to load cmdcov.yml: %s, please make sure your repo contains the following file '
26
+ 'https://github.com/Azure/azure-cli/blob/dev/scripts/ci/cmdcov.yml', str(ex))
27
+
28
+
29
+ # pylint:disable=too-many-locals, too-many-statements, too-many-branches, duplicate-code
30
+ def run_cmdcov(modules=None, git_source=None, git_target=None, git_repo=None, level='command'):
31
+ """
32
+ :param modules:
33
+ :param git_source:
34
+ :param git_target:
35
+ :param git_target:
36
+ :return: None
37
+ """
38
+ require_azure_cli()
39
+ from azure.cli.core import get_default_cli # pylint: disable=import-error
40
+ from azure.cli.core.file_util import ( # pylint: disable=import-error
41
+ get_all_help, create_invoker_and_load_cmds_and_args)
42
+
43
+ heading('CLI Command Test Coverage')
44
+
45
+ # allow user to run only on CLI or extensions
46
+ cli_only = modules == ['CLI']
47
+ ext_only = modules == ['EXT']
48
+ both_cli_ext = False
49
+ if modules == ['ALL']:
50
+ both_cli_ext = True
51
+ if cli_only or ext_only or both_cli_ext:
52
+ modules = None
53
+
54
+ enable_cli_own = bool(modules is None)
55
+
56
+ selected_modules = get_path_table(include_only=modules)
57
+
58
+ # filter down to only modules that have changed based on git diff
59
+ selected_modules = filter_by_git_diff(selected_modules, git_source, git_target, git_repo)
60
+
61
+ if not any(selected_modules.values()):
62
+ logger.warning('No commands selected to check.')
63
+
64
+ # mapping special extension name
65
+ def _map_extension_module(selected_modules):
66
+ # azext_applicationinsights -> azext_application_insights
67
+ # azext_firewall -> azext_azure_firewall
68
+ # azext_dms -> azext_dms_preview
69
+ # azext_dnsresolver -> azext_dns_resolver
70
+ # azext_expressroutecrossconnection -> azext_express_route_cross_connection
71
+ # azext_imagecopy -> azext_image_copy
72
+ # azext_loganalytics -> azext_log_analytics
73
+ # azext_amcs -> azext_monitor_control_service
74
+ # azext_resourcegraph -> azext_resource_graph
75
+ # azext_sentinel -> azext_securityinsight
76
+ # azext_serialconsole -> azext_serial_console
77
+ # azext_vnettap -> azext_virtual_network_tap
78
+ # azext_vwan -> azext_virtual_wan
79
+ # azext_connection_monitor_preview -> azure cli 2.0.81
80
+ # azext_spring_cloud -> deprecate
81
+ # azext_interactive -> no command
82
+ special_extensions_name = {
83
+ 'azext_applicationinsights': 'azext_application_insights',
84
+ 'azext_firewall': 'azext_azure_firewall',
85
+ 'azext_dms': 'azext_dms_preview',
86
+ 'azext_dnsresolver': 'azext_dns_resolver',
87
+ 'azext_expressroutecrossconnection': 'azext_express_route_cross_connection',
88
+ 'azext_imagecopy': 'azext_image_copy',
89
+ 'azext_loganalytics': 'azext_log_analytics',
90
+ 'azext_amcs': 'azext_monitor_control_service',
91
+ 'azext_resourcegraph': 'azext_resource_graph',
92
+ 'azext_sentinel': 'azext_securityinsight',
93
+ 'azext_serialconsole': 'azext_serial_console',
94
+ 'azext_vnettap': 'azext_virtual_network_tap',
95
+ 'azext_vwan': 'azext_virtual_wan',
96
+ }
97
+ import copy
98
+ copied_selected_modules = copy.deepcopy(selected_modules)
99
+ unsupported_extensions = ['azext_connection_monitor_preview', 'azext_spring_cloud', 'azext_interactive']
100
+ for k, v in copied_selected_modules['ext'].items():
101
+ if k in special_extensions_name:
102
+ selected_modules['ext'][special_extensions_name[k]] = v
103
+ del selected_modules['ext'][k]
104
+ if k in unsupported_extensions:
105
+ del selected_modules['ext'][k]
106
+
107
+ _map_extension_module(selected_modules)
108
+
109
+ if EXCLUDE_MODULES:
110
+ selected_modules['mod'] = {k: v for k, v in selected_modules['mod'].items() if k not in EXCLUDE_MODULES}
111
+ selected_modules['ext'] = {k: v for k, v in selected_modules['ext'].items() if k not in EXCLUDE_MODULES}
112
+
113
+ if cli_only and not both_cli_ext:
114
+ selected_mod_names = list(selected_modules['mod'].keys())
115
+ selected_mod_paths = list(selected_modules['mod'].values())
116
+ elif ext_only and not both_cli_ext:
117
+ selected_mod_names = list(selected_modules['ext'].keys())
118
+ selected_mod_paths = list(selected_modules['ext'].values())
119
+ else:
120
+ selected_mod_names = list(selected_modules['mod'].keys()) + list(selected_modules['ext'].keys())
121
+ selected_mod_paths = list(selected_modules['mod'].values()) + list(selected_modules['ext'].values())
122
+
123
+ if selected_mod_names:
124
+ display('Modules: {}\n'.format(', '.join(selected_mod_names)))
125
+
126
+ start = time.time()
127
+ display('Initializing cmdcov with command table and help files...')
128
+ az_cli = get_default_cli()
129
+
130
+ # load commands, args, and help
131
+ create_invoker_and_load_cmds_and_args(az_cli)
132
+ loaded_help = get_all_help(az_cli)
133
+
134
+ stop = time.time()
135
+ logger.info('Commands and help loaded in %i sec', stop - start)
136
+
137
+ # format loaded help
138
+ loaded_help = {data.command: data for data in loaded_help if data.command}
139
+
140
+ linter_exclusions = {}
141
+
142
+ # collect rule exclusions from selected mod paths
143
+ for path in selected_mod_paths:
144
+ mod_exclusion_path = os.path.join(path, 'linter_exclusions.yml')
145
+ if os.path.isfile(mod_exclusion_path):
146
+ with open(mod_exclusion_path) as f:
147
+ mod_exclusions = yaml.safe_load(f)
148
+ merge_exclusions(linter_exclusions, mod_exclusions or {})
149
+
150
+ # collect rule exclusions from global exclusion paths
151
+ global_exclusion_paths = [os.path.join(get_cli_repo_path(), 'linter_exclusions.yml')]
152
+ try:
153
+ global_exclusion_paths.extend([os.path.join(path, 'linter_exclusions.yml')
154
+ for path in (get_ext_repo_paths() or [])])
155
+ except CLIError:
156
+ pass
157
+ for path in global_exclusion_paths:
158
+ if os.path.isfile(path):
159
+ with open(path) as f:
160
+ mod_exclusions = yaml.safe_load(f)
161
+ merge_exclusions(linter_exclusions, mod_exclusions or {})
162
+
163
+ cmdcov_manager = CmdcovManager(selected_mod_names=selected_mod_names,
164
+ selected_mod_paths=selected_mod_paths,
165
+ loaded_help=loaded_help,
166
+ level=level,
167
+ enable_cli_own=enable_cli_own,
168
+ exclusions=linter_exclusions)
169
+ cmdcov_manager.run()
170
+
171
+
172
+ # pylint: disable=line-too-long
173
+ def merge_exclusions(left_exclusion, right_exclusion):
174
+ for command_name, value in right_exclusion.items():
175
+ for rule_name in value.get('rule_exclusions', []):
176
+ left_exclusion.setdefault(command_name, {}).setdefault('rule_exclusions', []).append(rule_name)
177
+ for param_name in value.get('parameters', {}):
178
+ for rule_name in value.get('parameters', {}).get(param_name, {}).get('rule_exclusions', []):
179
+ left_exclusion.setdefault(command_name, {}).setdefault('parameters', {}).setdefault(param_name, {}).setdefault('rule_exclusions', []).append(rule_name)
180
+
181
+
182
+ if __name__ == '__main__':
183
+ pass
184
+ # _get_all_tested_commands(['a'], ['b'])
185
+ # regex2()
186
+ # regex3()
@@ -0,0 +1,35 @@
1
+ {% macro render_percentage(detail, module) %}
2
+ {% if module['color'] == 'N/A' -%}
3
+ <td name="td-percentage" class="column-percentage">
4
+ <div style="text-align: center">N/A</div>
5
+ <div class="detail">Not applicable</div>
6
+ </td>
7
+ {% elif module['color'] != 'gold' -%}
8
+ <td name="td-percentage" class="column-percentage">
9
+ <div class="single-chart">
10
+ <svg viewBox="0 0 36 36" class="circular-chart {{ module['color'] }}">
11
+ <path class="circle-bg"
12
+ d="M18 2.0845
13
+ a 15.9155 15.9155 0 0 1 0 31.831
14
+ a 15.9155 15.9155 0 0 1 0 -31.831"
15
+ />
16
+ <path class="circle"
17
+ stroke-dasharray="{{ module['percentage'] }}, 100"
18
+ d="M18 2.0845
19
+ a 15.9155 15.9155 0 0 1 0 31.831
20
+ a 15.9155 15.9155 0 0 1 0 -31.831"
21
+ />
22
+ <text x="18" y="20.35" class="percentage">{{ module['percentage'] }}%</text>
23
+ </svg>
24
+ <div class="detail">{{ detail }}</div>
25
+ </div>
26
+ </td>
27
+ {% else -%}
28
+ <td name="td-percentage" class="medal column-percentage">
29
+ <div class="ribbon"></div>
30
+ <div class="coin"></div>
31
+ <div class="detail">100.000%</div>
32
+ </td>
33
+ {% endif -%}
34
+ {% endmacro %}
35
+