ciocore 7.0.2b5__py2.py3-none-any.whl → 8.0.0__py2.py3-none-any.whl

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.

Potentially problematic release.


This version of ciocore might be problematic. Click here for more details.

Files changed (111) hide show
  1. ciocore/VERSION +1 -1
  2. ciocore/__init__.py +23 -1
  3. ciocore/api_client.py +422 -156
  4. ciocore/cli.py +503 -0
  5. ciocore/common.py +10 -1
  6. ciocore/config.py +86 -53
  7. ciocore/data.py +20 -73
  8. ciocore/docsite/404.html +723 -0
  9. ciocore/docsite/apidoc/api_client/index.html +3203 -0
  10. ciocore/docsite/apidoc/apidoc/index.html +868 -0
  11. ciocore/docsite/apidoc/config/index.html +1591 -0
  12. ciocore/docsite/apidoc/data/index.html +1480 -0
  13. ciocore/docsite/apidoc/hardware_set/index.html +2367 -0
  14. ciocore/docsite/apidoc/package_environment/index.html +1450 -0
  15. ciocore/docsite/apidoc/package_tree/index.html +2310 -0
  16. ciocore/docsite/assets/_mkdocstrings.css +16 -0
  17. ciocore/docsite/assets/images/favicon.png +0 -0
  18. ciocore/docsite/assets/javascripts/bundle.4e31edb1.min.js +29 -0
  19. ciocore/docsite/assets/javascripts/bundle.4e31edb1.min.js.map +8 -0
  20. ciocore/docsite/assets/javascripts/lunr/min/lunr.ar.min.js +1 -0
  21. ciocore/docsite/assets/javascripts/lunr/min/lunr.da.min.js +18 -0
  22. ciocore/docsite/assets/javascripts/lunr/min/lunr.de.min.js +18 -0
  23. ciocore/docsite/assets/javascripts/lunr/min/lunr.du.min.js +18 -0
  24. ciocore/docsite/assets/javascripts/lunr/min/lunr.es.min.js +18 -0
  25. ciocore/docsite/assets/javascripts/lunr/min/lunr.fi.min.js +18 -0
  26. ciocore/docsite/assets/javascripts/lunr/min/lunr.fr.min.js +18 -0
  27. ciocore/docsite/assets/javascripts/lunr/min/lunr.hi.min.js +1 -0
  28. ciocore/docsite/assets/javascripts/lunr/min/lunr.hu.min.js +18 -0
  29. ciocore/docsite/assets/javascripts/lunr/min/lunr.hy.min.js +1 -0
  30. ciocore/docsite/assets/javascripts/lunr/min/lunr.it.min.js +18 -0
  31. ciocore/docsite/assets/javascripts/lunr/min/lunr.ja.min.js +1 -0
  32. ciocore/docsite/assets/javascripts/lunr/min/lunr.jp.min.js +1 -0
  33. ciocore/docsite/assets/javascripts/lunr/min/lunr.kn.min.js +1 -0
  34. ciocore/docsite/assets/javascripts/lunr/min/lunr.ko.min.js +1 -0
  35. ciocore/docsite/assets/javascripts/lunr/min/lunr.multi.min.js +1 -0
  36. ciocore/docsite/assets/javascripts/lunr/min/lunr.nl.min.js +18 -0
  37. ciocore/docsite/assets/javascripts/lunr/min/lunr.no.min.js +18 -0
  38. ciocore/docsite/assets/javascripts/lunr/min/lunr.pt.min.js +18 -0
  39. ciocore/docsite/assets/javascripts/lunr/min/lunr.ro.min.js +18 -0
  40. ciocore/docsite/assets/javascripts/lunr/min/lunr.ru.min.js +18 -0
  41. ciocore/docsite/assets/javascripts/lunr/min/lunr.sa.min.js +1 -0
  42. ciocore/docsite/assets/javascripts/lunr/min/lunr.stemmer.support.min.js +1 -0
  43. ciocore/docsite/assets/javascripts/lunr/min/lunr.sv.min.js +18 -0
  44. ciocore/docsite/assets/javascripts/lunr/min/lunr.ta.min.js +1 -0
  45. ciocore/docsite/assets/javascripts/lunr/min/lunr.te.min.js +1 -0
  46. ciocore/docsite/assets/javascripts/lunr/min/lunr.th.min.js +1 -0
  47. ciocore/docsite/assets/javascripts/lunr/min/lunr.tr.min.js +18 -0
  48. ciocore/docsite/assets/javascripts/lunr/min/lunr.vi.min.js +1 -0
  49. ciocore/docsite/assets/javascripts/lunr/min/lunr.zh.min.js +1 -0
  50. ciocore/docsite/assets/javascripts/lunr/tinyseg.js +206 -0
  51. ciocore/docsite/assets/javascripts/lunr/wordcut.js +6708 -0
  52. ciocore/docsite/assets/javascripts/workers/search.dfff1995.min.js +42 -0
  53. ciocore/docsite/assets/javascripts/workers/search.dfff1995.min.js.map +8 -0
  54. ciocore/docsite/assets/stylesheets/main.83068744.min.css +1 -0
  55. ciocore/docsite/assets/stylesheets/main.83068744.min.css.map +1 -0
  56. ciocore/docsite/assets/stylesheets/palette.ecc896b0.min.css +1 -0
  57. ciocore/docsite/assets/stylesheets/palette.ecc896b0.min.css.map +1 -0
  58. ciocore/docsite/cmdline/docs/index.html +834 -0
  59. ciocore/docsite/cmdline/downloader/index.html +897 -0
  60. ciocore/docsite/cmdline/packages/index.html +841 -0
  61. ciocore/docsite/cmdline/uploader/index.html +950 -0
  62. ciocore/docsite/how-to-guides/index.html +831 -0
  63. ciocore/docsite/index.html +853 -0
  64. ciocore/docsite/logo.png +0 -0
  65. ciocore/docsite/objects.inv +0 -0
  66. ciocore/docsite/search/search_index.json +1 -0
  67. ciocore/docsite/sitemap.xml +3 -0
  68. ciocore/docsite/sitemap.xml.gz +0 -0
  69. ciocore/docsite/stylesheets/extra.css +26 -0
  70. ciocore/docsite/stylesheets/tables.css +167 -0
  71. ciocore/downloader/__init__.py +0 -0
  72. ciocore/downloader/base_downloader.py +644 -0
  73. ciocore/downloader/download_runner_base.py +47 -0
  74. ciocore/downloader/job_downloader.py +119 -0
  75. ciocore/{downloader.py → downloader/legacy_downloader.py} +0 -1
  76. ciocore/downloader/log.py +73 -0
  77. ciocore/downloader/logging_download_runner.py +87 -0
  78. ciocore/downloader/perpetual_downloader.py +63 -0
  79. ciocore/downloader/registry.py +97 -0
  80. ciocore/downloader/reporter.py +135 -0
  81. ciocore/file_utils.py +3 -3
  82. ciocore/hardware_set.py +0 -4
  83. ciocore/package_environment.py +67 -75
  84. ciocore/package_query.py +171 -0
  85. ciocore/package_tree.py +300 -377
  86. ciocore/retry.py +0 -0
  87. ciocore/uploader/_uploader.py +205 -152
  88. {ciocore-7.0.2b5.dist-info → ciocore-8.0.0.dist-info}/METADATA +34 -16
  89. ciocore-8.0.0.dist-info/RECORD +127 -0
  90. {ciocore-7.0.2b5.dist-info → ciocore-8.0.0.dist-info}/WHEEL +1 -1
  91. ciocore-8.0.0.dist-info/entry_points.txt +2 -0
  92. tests/extra_env_fixtures.py +57 -0
  93. tests/instance_type_fixtures.py +42 -8
  94. tests/project_fixtures.py +8 -0
  95. tests/test_api_client.py +121 -2
  96. tests/test_base_downloader.py +104 -0
  97. tests/test_cli.py +163 -0
  98. tests/test_common.py +8 -8
  99. tests/test_config.py +23 -9
  100. tests/test_data.py +144 -160
  101. tests/test_downloader.py +118 -0
  102. tests/test_hardware_set.py +69 -20
  103. tests/test_job_downloader.py +213 -0
  104. ciocore/__about__.py +0 -10
  105. ciocore/cli/__init__.py +0 -3
  106. ciocore/cli/conductor.py +0 -210
  107. ciocore-7.0.2b5.data/scripts/conductor +0 -19
  108. ciocore-7.0.2b5.data/scripts/conductor.bat +0 -13
  109. ciocore-7.0.2b5.dist-info/RECORD +0 -51
  110. tests/mocks/api_client_mock.py +0 -31
  111. {ciocore-7.0.2b5.dist-info → ciocore-8.0.0.dist-info}/top_level.txt +0 -0
@@ -7,21 +7,15 @@ class PackageEnvironment(object):
7
7
 
8
8
  def __init__(self, env_list=None, platform=None):
9
9
  """
10
- Create a list of environment variables.
10
+ Encapsulate a list of environment variables.
11
11
 
12
- Typically, one would initialize a PackageEnvironment with a package, and then modify by
13
- adding more packages or lists of variables. Extra variables can be added by the customer, or
14
- programmatically such as during asset scraping.
15
-
16
- Keyword Arguments:
17
-
18
- * **`env_list`** -- Either a Package that contains an "environment" property, or a list of
19
- environment objects. -- Defaults to `None`.
20
- * **`platform`** -- If env_list is not a package, then the platform must be provided. --
21
- Defaults to `None`.
22
-
23
- On instantiation, delegete args to the [extend()](#extend) method.
12
+ Typically, one would initialize a PackageEnvironment with a package, and then modify by adding more packages or lists of variables. Extra variables can be added by the customer, or programmatically such as during asset scraping.
24
13
 
14
+ Args:
15
+ env_list (object|list): An object that provides a list of dictionaries with properties: `name`, `value`, and `merge_policy`.
16
+ platform (str): If the env_list is a regular list, then this is required.
17
+
18
+ Args are delegated to [extend()](/package_environment/#ciocore.package_environment.PackageEnvironment.extend).
25
19
  """
26
20
  self.platform = None
27
21
  self._env = {}
@@ -33,73 +27,53 @@ class PackageEnvironment(object):
33
27
  """
34
28
  Extend the Package environment with the given variable specifications.
35
29
 
36
- Arguments:
37
-
38
- * **`env_list`** -- Either:
39
- 1. A list of dictionaries with properties: `name`, `value`, and `merge_policy`.
40
- 2. An object with an `environment` key that contains a list of the dictionaries described
41
- above. The latter is the structure of a package. Therefore we can initialize or extend a
42
- PackageEnvironment with a package.
43
-
44
-
45
- Keyword Arguments:
30
+ Args:
31
+ env_list (object|list): Either:
32
+ * A list of dictionaries with properties: `name`, `value`, and `merge_policy`.
33
+ * An object with an `environment` key that contains a list of the dictionaries described above. The latter is the structure of a package. Therefore we can initialize or extend a PackageEnvironment with a package.
34
+ platform (str): Defaults to `None`. If env_list is a package, then the platform is taken from the package and the `platform` keyword is ignored. If env_list is a list, then if this is the first add, a platform should be specified, otherwise it will default to linux.
35
+
36
+ The first time data is added to a PackageEnvironment, the platform is set in stone. Subsequent `adds` that try to change the platform are considered an error.
46
37
 
47
- * **`platform`** -- Defaults to `None`.
48
- If env_list is a package, then the platform is taken from the package and the `platform`
49
- keyword is ignored. If env_list is a list, then if this is the first add, a platform
50
- should be specified, otherwise it willl default to linux.
51
-
52
- The first time data is added to a PackageEnvironment, the platform is set in stone.
53
- Subsequent `adds` that try to change the platform are considered an error.
54
-
55
- Each variable to be added specifies a merge_policy: `append` or `exclusive`. Once an
56
- individual variable has been initialized with a merge policy, it can't be changed. This
57
- means: 1. It's not possible to overwrite variables that have been set as append. 2.
58
- Exclusive variables are always overwritten by subsequent adds.
38
+ Each variable to be added specifies a merge_policy: `append`, `prepend`, or `exclusive` `append` and `prepend` can be thought of as lists= types. Once an individual variable has been initialized as a list, it can't be changed to `exclusive`. This means:
39
+
40
+ 1. It's not possible to overwrite variables that have been added as `append` or `prepend`.
41
+ 2. Exclusive variables are always overwritten by subsequent adds.
59
42
 
60
43
  Raises:
61
-
62
- * **`ValueError`** -- Attempt to change the platform once initialized.
63
- * **`ValueError`** -- Unknown merge policy
64
- .
65
-
66
- ???+ example
67
- ``` python
68
-
69
- from ciocore import api_client, package_tree, package_environment
70
- packages = api_client.request_software_packages()
71
- pt = package_tree.PackageTree(packages, product="cinema4d")
72
- one_dcc_name = pt.supported_host_names()[0]
73
- # 'cinema4d 21.209.RB305619 linux'
74
- pkg = pt.find_by_name(one_dcc_name)
75
- pe = package_environment.PackageEnvironment(pkg)
76
- print(dict(pe))
77
-
78
- # Result:
79
- # {
80
- # "PATH": "/opt/maxon/cinema4d/21/cinema4d21.209vRB305619/bin",
81
- # "g_licenseServerRLM": "conductor-rlm:6112",
82
- # "LD_LIBRARY_PATH": "/opt/maxon/cinema4d/21/cinema4d21.209vRB305619/lib64:/opt/maxon/cinema4d/21/cinema4d21.209vRB305619/bin/resource/modules/python/libs/linux64/python.linux64.framework/lib64:/opt/maxon/cinema4d/21/cinema4d21.209vRB305619/bin/resource/modules/embree.module/libs/linux64",
83
- # "PYTHONPATH": "/opt/maxon/cinema4d/21/cinema4d21.209vRB305619/bin/resource/modules/python/libs/linux64/python.linux64.framework/lib64/python2.7/lib-dynload",
84
- # }
85
-
86
- extra_env = [
44
+ ValueError: Either an attempt to change the platform once initialized, or an invalid merge policy.
45
+
46
+
47
+ Example:
48
+ >>> from ciocore import api_client, package_tree, package_environment
49
+ >>> packages = api_client.request_software_packages()
50
+ >>> pt = package_tree.PackageTree(packages, product="cinema4d")
51
+ >>> one_dcc_name = pt.supported_host_names()[0]
52
+ cinema4d 21.209.RB305619 linux
53
+
54
+ >>> pkg = pt.find_by_name(one_dcc_name)
55
+ >>> pe = package_environment.PackageEnvironment(pkg)
56
+ >>> print(dict(pe))
57
+ {
58
+ "PATH": "/opt/maxon/cinema4d/21/cinema4d21.209vRB305619/bin",
59
+ "g_licenseServerRLM": "conductor-rlm:6112",
60
+ "LD_LIBRARY_PATH": "/opt/maxon/cinema4d/21/cinema4d21.209vRB305619/lib64:/opt/maxon/cinema4d/21/cinema4d21.209vRB305619/bin/resource/modules/python/libs/linux64/python.linux64.framework/lib64:/opt/maxon/cinema4d/21/cinema4d21.209vRB305619/bin/resource/modules/embree.module/libs/linux64",
61
+ "PYTHONPATH": "/opt/maxon/cinema4d/21/cinema4d21.209vRB305619/bin/resource/modules/python/libs/linux64/python.linux64.framework/lib64/python2.7/lib-dynload",
62
+ }
63
+
64
+ >>> extra_env = [
87
65
  {"name":"PATH", "value": "/my/custom/scripts", "merge_policy":"append"},
88
66
  {"name":"DEBUG_MODE", "value": "1", "merge_policy":"exclusive"}
89
67
  ]
90
-
91
- pe.extend(extra_env)
92
- print(dict(pe))
93
-
94
- # Result:
95
- # {
96
- # "PATH": "/opt/maxon/cinema4d/21/cinema4d21.209vRB305619/bin:/my/custom/scripts",
97
- # "g_licenseServerRLM": "conductor-rlm:6112",
98
- # "LD_LIBRARY_PATH": "/opt/maxon/cinema4d/21/cinema4d21.209vRB305619/lib64:/opt/maxon/cinema4d/21/cinema4d21.209vRB305619/bin/resource/modules/python/libs/linux64/python.linux64.framework/lib64:/opt/maxon/cinema4d/21/cinema4d21.209vRB305619/bin/resource/modules/embree.module/libs/linux64",
99
- # "PYTHONPATH": "/opt/maxon/cinema4d/21/cinema4d21.209vRB305619/bin/resource/modules/python/libs/linux64/python.linux64.framework/lib64/python2.7/lib-dynload",
100
- # "DEBUG_MODE": "1",
101
- # }
102
- ```
68
+ >>> pe.extend(extra_env)
69
+ >>> print(dict(pe))
70
+ {
71
+ "PATH": "/opt/maxon/cinema4d/21/cinema4d21.209vRB305619/bin:/my/custom/scripts",
72
+ "g_licenseServerRLM": "conductor-rlm:6112",
73
+ "LD_LIBRARY_PATH": "/opt/maxon/cinema4d/21/cinema4d21.209vRB305619/lib64:/opt/maxon/cinema4d/21/cinema4d21.209vRB305619/bin/resource/modules/python/libs/linux64/python.linux64.framework/lib64:/opt/maxon/cinema4d/21/cinema4d21.209vRB305619/bin/resource/modules/embree.module/libs/linux64",
74
+ "PYTHONPATH": "/opt/maxon/cinema4d/21/cinema4d21.209vRB305619/bin/resource/modules/python/libs/linux64/python.linux64.framework/lib64/python2.7/lib-dynload",
75
+ "DEBUG_MODE": "1",
76
+ }
103
77
  """
104
78
 
105
79
  if not env_list:
@@ -121,11 +95,13 @@ class PackageEnvironment(object):
121
95
  name = var["name"]
122
96
  value = var["value"]
123
97
  policy = var["merge_policy"]
124
- if policy not in ["append", "exclusive"]:
98
+ if policy not in ["append", "prepend", "exclusive"]:
125
99
  raise ValueError("Unexpected merge policy: %s" % policy)
126
100
 
127
101
  if policy == "append":
128
102
  self._append(name, value)
103
+ elif policy == "prepend":
104
+ self._prepend(name, value)
129
105
  else:
130
106
  self._set(name, value)
131
107
 
@@ -159,6 +135,22 @@ class PackageEnvironment(object):
159
135
  self._env[name] = []
160
136
  self._env[name].append(value)
161
137
 
138
+ def _prepend(self, name, value):
139
+ """Set the value of an append/prepend variable.
140
+
141
+ Can be appended to with subsequent adds.
142
+
143
+ It is an error if the variable has already been declared with policy=exclusive.
144
+ """
145
+ if self._env.get(name):
146
+ if not isinstance(self._env[name], list):
147
+ raise ValueError(
148
+ "Can't change merge policy for '{}' from 'exclusive' to 'prepend'.".format(name)
149
+ )
150
+ else:
151
+ self._env[name] = []
152
+ self._env[name].insert(0, value)
153
+
162
154
  def __iter__(self):
163
155
  """Cast the object as a dict."""
164
156
  sep = ";" if self.platform == "windows" else ":"
@@ -0,0 +1,171 @@
1
+ """
2
+ Generate markdown from the software packages list.
3
+ """
4
+ import os
5
+ import sys
6
+ import json
7
+ from ciocore.package_tree import PackageTree
8
+ from ciocore import api_client
9
+ import markdown
10
+ import io
11
+ import tempfile
12
+ import webbrowser
13
+
14
+ PURE = """
15
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/purecss@3.0.0/build/pure-min.css" integrity="sha384-X38yfunGUhNzHpBaEBsWLO+A0HDYOQi8ufWDkZ0k9e0eXz/tH3II7uKZ9msv++Ls" crossorigin="anonymous">
16
+ <meta name="viewport" content="width=device-width, initial-scale=1">
17
+ """
18
+
19
+ def green(rhs):
20
+ return "\033[92m{}\033[0m".format(rhs)
21
+
22
+
23
+ def red(rhs):
24
+ return "\033[91m{}\033[0m".format(rhs)
25
+
26
+
27
+ def blue(rhs):
28
+ return "\033[94m{}\033[0m".format(rhs)
29
+
30
+
31
+ def magenta(rhs):
32
+ return "\033[95m{}\033[0m".format(rhs)
33
+
34
+
35
+ def raw(rhs, stream):
36
+ stream.write("{}\n\n".format(rhs))
37
+
38
+
39
+ def d(n, rhs, stream):
40
+ """Indent with dashes"""
41
+ stream.write("{} {}\n".format("-" * n, rhs))
42
+
43
+
44
+ def hr(stream):
45
+ stream.write("---\n\n")
46
+
47
+
48
+ def h(n, rhs, stream):
49
+ stream.write("{} {}\n\n".format("#" * n, rhs))
50
+
51
+
52
+ def plugin_table_header(stream):
53
+ stream.write(
54
+ '|<div style="width:150px">Plugin</div> |<div style="min-width:400px">Versions</div>|\n|:------------|:-------------|\n'
55
+ )
56
+
57
+
58
+ def plugin_table_row(plugin, versions, stream):
59
+ stream.write("|{}|{}|\n".format(plugin, versions))
60
+
61
+
62
+ def write_markdown(hostnames, tree_data, platform, stream):
63
+ """
64
+ Write the tree of packages in Markdown.
65
+
66
+ Use this to generate docs for the Conductor mkdocs site.
67
+ """
68
+ if not hostnames:
69
+ return
70
+ h(2, "{} Software".format(platform.capitalize()), stream)
71
+ last_hostgroup = None
72
+ for hostname in hostnames:
73
+ display_hostname = " ".join(hostname.split()[:2])
74
+ hostgroup = hostname.split(" ")[0]
75
+ stream.write("\n")
76
+ if not hostgroup == last_hostgroup:
77
+ hr(stream)
78
+ h(3, hostgroup, stream)
79
+ h(4, display_hostname, stream)
80
+ last_hostgroup = hostgroup
81
+ plugins = tree_data.supported_plugins(hostname)
82
+ if plugins:
83
+ plugin_table_header(stream)
84
+ for plugin in plugins:
85
+ plugin_table_row(
86
+ plugin["plugin"], ", ".join(plugin["versions"]), stream
87
+ )
88
+
89
+
90
+ def write_text(hostnames, tree_data, platform, color_func, stream):
91
+ """
92
+ Write the tree of packages as text.
93
+
94
+ Products are indented with one dash.
95
+ Host packages are indented with two dashes.
96
+ Plugin packages are indented with three dashes.
97
+ """
98
+ if not hostnames:
99
+ d(0, red("There are no '{}' host packages".format(platform)), stream)
100
+ return
101
+ d(0, "{} Software".format(platform).upper(), stream)
102
+ last_hostgroup = None
103
+ for hostname in hostnames:
104
+ display_hostname = " ".join(hostname.split()[:2])
105
+ hostgroup = hostname.split(" ")[0]
106
+ if not hostgroup == last_hostgroup:
107
+ d(0, green("-" * 30), stream)
108
+ d(1, color_func(hostgroup), stream)
109
+ d(2, color_func(display_hostname), stream)
110
+ last_hostgroup = hostgroup
111
+ plugins = tree_data.supported_plugins(hostname)
112
+ if plugins:
113
+ for plugin in plugins:
114
+ d(
115
+ 3,
116
+ color_func(
117
+ "{} [{}]".format(
118
+ plugin["plugin"], ", ".join(plugin["versions"])
119
+ )
120
+ ),
121
+ stream,
122
+ )
123
+
124
+
125
+ def pq(format="text"):
126
+ packages = api_client.request_software_packages()
127
+
128
+ tree_data = PackageTree(packages)
129
+
130
+ hostnames = tree_data.supported_host_names()
131
+ linux_hostnames = [h for h in hostnames if h.endswith("linux")]
132
+ windows_hostnames = [h for h in hostnames if h.endswith("windows")]
133
+
134
+ if format == "markdown":
135
+ stream = sys.stdout
136
+ raw(
137
+ "This page contains the complete list of software available at Conductor. If you require applications or plugins that are not in the list, please [create a support ticket](https://support.conductortech.com/hc/en-us/requests/new) and let us know.",
138
+ stream,
139
+ )
140
+ write_markdown(linux_hostnames, tree_data, "linux", stream)
141
+ write_markdown(windows_hostnames, tree_data, "windows", stream)
142
+ elif format == "text":
143
+ stream = sys.stdout
144
+ write_text(linux_hostnames, tree_data, "linux", magenta, stream)
145
+ d(0, "", stream)
146
+ write_text(windows_hostnames, tree_data, "windows", blue, stream)
147
+ elif format == "html":
148
+ stream = io.StringIO()
149
+ raw(
150
+ "This page contains the complete list of software available at Conductor. If you require applications or plugins that are not in the list, please [create a support ticket](https://support.conductortech.com/hc/en-us/requests/new) and let us know.",
151
+ stream,
152
+ )
153
+
154
+ write_markdown(linux_hostnames, tree_data, "linux", stream)
155
+ write_markdown(windows_hostnames, tree_data, "windows", stream)
156
+
157
+ html = markdown.markdown(
158
+ stream.getvalue(), extensions=["markdown.extensions.tables"]
159
+ )
160
+
161
+ html = decorate(html)
162
+
163
+ stream.close()
164
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False) as f:
165
+ f.write(html)
166
+ webbrowser.open("file://" + f.name, new=2)
167
+
168
+ def decorate(html):
169
+ html = html.replace("<table>", '<table class="pure-table pure-table-bordered">')
170
+ html = '<html><head>{}</head><body style="margin: 2em;">{}</body></html>'.format(PURE, html)
171
+ return html