Mercurial > libervia-backend
annotate sat/bridge/bridge_constructor/base_constructor.py @ 3028:ab2696e34d29
Python 3 port:
/!\ this is a huge commit
/!\ starting from this commit, SàT is needs Python 3.6+
/!\ SàT maybe be instable or some feature may not work anymore, this will improve with time
This patch port backend, bridge and frontends to Python 3.
Roughly this has been done this way:
- 2to3 tools has been applied (with python 3.7)
- all references to python2 have been replaced with python3 (notably shebangs)
- fixed files not handled by 2to3 (notably the shell script)
- several manual fixes
- fixed issues reported by Python 3 that where not handled in Python 2
- replaced "async" with "async_" when needed (it's a reserved word from Python 3.7)
- replaced zope's "implements" with @implementer decorator
- temporary hack to handle data pickled in database, as str or bytes may be returned,
to be checked later
- fixed hash comparison for password
- removed some code which is not needed anymore with Python 3
- deactivated some code which needs to be checked (notably certificate validation)
- tested with jp, fixed reported issues until some basic commands worked
- ported Primitivus (after porting dependencies like urwid satext)
- more manual fixes
author | Goffi <goffi@goffi.org> |
---|---|
date | Tue, 13 Aug 2019 19:08:41 +0200 |
parents | 003b8b4b56a7 |
children | a1bc34f90fa5 |
rev | line source |
---|---|
3028 | 1 #!/usr/bin/env python3 |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
2 # -*- coding: utf-8 -*- |
2085 | 3 |
4 # SàT: a XMPP client | |
2771 | 5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org) |
2085 | 6 |
7 # This program is free software: you can redistribute it and/or modify | |
8 # it under the terms of the GNU Affero General Public License as published by | |
9 # the Free Software Foundation, either version 3 of the License, or | |
10 # (at your option) any later version. | |
11 | |
12 # This program is distributed in the hope that it will be useful, | |
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 # GNU Affero General Public License for more details. | |
16 | |
17 # You should have received a copy of the GNU Affero General Public License | |
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
19 | |
20 """base constructor class""" | |
21 | |
22 from sat.bridge.bridge_constructor.constants import Const as C | |
3028 | 23 from configparser import NoOptionError |
2085 | 24 import sys |
25 import os | |
26 import os.path | |
27 import re | |
28 from importlib import import_module | |
29 | |
30 | |
31 class ParseError(Exception): | |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
32 # Used when the signature parsing is going wrong (invalid signature ?) |
2085 | 33 pass |
34 | |
35 | |
36 class Constructor(object): | |
37 NAME = None # used in arguments parsing, filename will be used if not set | |
38 # following attribute are used by default generation method | |
39 # they can be set to dict of strings using python formatting syntax | |
40 # dict keys will be used to select part to replace (e.g. "signals" key will | |
41 # replace ##SIGNALS_PART## in template), while the value is the format | |
42 # keys starting with "signal" will be used for signals, while ones starting with | |
43 # "method" will be used for methods | |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
44 # check D-Bus constructor for an example |
2085 | 45 CORE_FORMATS = None |
46 CORE_TEMPLATE = None | |
47 CORE_DEST = None | |
48 FRONTEND_FORMATS = None | |
49 FRONTEND_TEMPLATE = None | |
50 FRONTEND_DEST = None | |
51 | |
2087
159250d66407
bridge (constructor): embedded bridge generator:
Goffi <goffi@goffi.org>
parents:
2085
diff
changeset
|
52 # set to False if your bridge need only core |
159250d66407
bridge (constructor): embedded bridge generator:
Goffi <goffi@goffi.org>
parents:
2085
diff
changeset
|
53 FRONTEND_ACTIVATE = True |
159250d66407
bridge (constructor): embedded bridge generator:
Goffi <goffi@goffi.org>
parents:
2085
diff
changeset
|
54 |
2085 | 55 def __init__(self, bridge_template, options): |
56 self.bridge_template = bridge_template | |
57 self.args = options | |
58 | |
59 @property | |
60 def constructor_dir(self): | |
61 constructor_mod = import_module(self.__module__) | |
62 return os.path.dirname(constructor_mod.__file__) | |
63 | |
64 def getValues(self, name): | |
65 """Return values of a function in a dict | |
66 @param name: Name of the function to get | |
67 @return: dict, each key has the config value or None if the value is not set""" | |
68 function = {} | |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
69 for option in ["type", "category", "sig_in", "sig_out", "doc"]: |
2085 | 70 try: |
71 value = self.bridge_template.get(name, option) | |
72 except NoOptionError: | |
73 value = None | |
74 function[option] = value | |
75 return function | |
76 | |
77 def getDefault(self, name): | |
78 """Return default values of a function in a dict | |
79 @param name: Name of the function to get | |
80 @return: dict, each key is the integer param number (no key if no default value)""" | |
81 default_dict = {} | |
82 def_re = re.compile(r"param_(\d+)_default") | |
83 | |
84 for option in self.bridge_template.options(name): | |
85 match = def_re.match(option) | |
86 if match: | |
87 try: | |
88 idx = int(match.group(1)) | |
89 except ValueError: | |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
90 raise ParseError( |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
91 "Invalid value [%s] for parameter number" % match.group(1) |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
92 ) |
2085 | 93 default_dict[idx] = self.bridge_template.get(name, option) |
94 | |
95 return default_dict | |
96 | |
97 def getFlags(self, name): | |
98 """Return list of flags set for this function | |
99 | |
100 @param name: Name of the function to get | |
101 @return: List of flags (string) | |
102 """ | |
103 flags = [] | |
104 for option in self.bridge_template.options(name): | |
105 if option in C.DECLARATION_FLAGS: | |
106 flags.append(option) | |
107 return flags | |
108 | |
109 def getArgumentsDoc(self, name): | |
110 """Return documentation of arguments | |
111 @param name: Name of the function to get | |
112 @return: dict, each key is the integer param number (no key if no argument doc), value is a tuple (name, doc)""" | |
113 doc_dict = {} | |
114 option_re = re.compile(r"doc_param_(\d+)") | |
115 value_re = re.compile(r"^(\w+): (.*)$", re.MULTILINE | re.DOTALL) | |
116 for option in self.bridge_template.options(name): | |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
117 if option == "doc_return": |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
118 doc_dict["return"] = self.bridge_template.get(name, option) |
2085 | 119 continue |
120 match = option_re.match(option) | |
121 if match: | |
122 try: | |
123 idx = int(match.group(1)) | |
124 except ValueError: | |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
125 raise ParseError( |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
126 "Invalid value [%s] for parameter number" % match.group(1) |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
127 ) |
2085 | 128 value_match = value_re.match(self.bridge_template.get(name, option)) |
129 if not value_match: | |
130 raise ParseError("Invalid value for parameter doc [%i]" % idx) | |
131 doc_dict[idx] = (value_match.group(1), value_match.group(2)) | |
132 return doc_dict | |
133 | |
134 def getDoc(self, name): | |
135 """Return documentation of the method | |
136 @param name: Name of the function to get | |
137 @return: string documentation, or None""" | |
138 if self.bridge_template.has_option(name, "doc"): | |
139 return self.bridge_template.get(name, "doc") | |
140 return None | |
141 | |
142 def argumentsParser(self, signature): | |
143 """Generator which return individual arguments signatures from a global signature""" | |
144 start = 0 | |
145 i = 0 | |
146 | |
147 while i < len(signature): | |
2628
779351da2c13
core, frontends: replaced org\.goffi namespaces by org.salutatoi + fixed generation:
Goffi <goffi@goffi.org>
parents:
2624
diff
changeset
|
148 if signature[i] not in ["b", "y", "n", "i", "x", "q", "u", "t", "d", "s", |
779351da2c13
core, frontends: replaced org\.goffi namespaces by org.salutatoi + fixed generation:
Goffi <goffi@goffi.org>
parents:
2624
diff
changeset
|
149 "a"]: |
2085 | 150 raise ParseError("Unmanaged attribute type [%c]" % signature[i]) |
151 | |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
152 if signature[i] == "a": |
2085 | 153 i += 1 |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
154 if ( |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
155 signature[i] != "{" and signature[i] != "(" |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
156 ): # FIXME: must manage tuples out of arrays |
2085 | 157 i += 1 |
158 yield signature[start:i] | |
159 start = i | |
160 continue # we have a simple type for the array | |
161 opening_car = signature[i] | |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
162 assert opening_car in ["{", "("] |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
163 closing_car = "}" if opening_car == "{" else ")" |
2085 | 164 opening_count = 1 |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
165 while True: # we have a dict or a list of tuples |
2085 | 166 i += 1 |
167 if i >= len(signature): | |
168 raise ParseError("missing }") | |
169 if signature[i] == opening_car: | |
170 opening_count += 1 | |
171 if signature[i] == closing_car: | |
172 opening_count -= 1 | |
173 if opening_count == 0: | |
174 break | |
175 i += 1 | |
176 yield signature[start:i] | |
177 start = i | |
178 | |
179 def getArguments(self, signature, name=None, default=None, unicode_protect=False): | |
180 """Return arguments to user given a signature | |
181 | |
182 @param signature: signature in the short form (using s,a,i,b etc) | |
2087
159250d66407
bridge (constructor): embedded bridge generator:
Goffi <goffi@goffi.org>
parents:
2085
diff
changeset
|
183 @param name: dictionary of arguments name like given by getArgumentsDoc |
2085 | 184 @param default: dictionary of default values, like given by getDefault |
185 @param unicode_protect: activate unicode protection on strings (return strings as unicode(str)) | |
2091
f413bfc24458
bridge, quick_frontend: preparation for async bridge
Goffi <goffi@goffi.org>
parents:
2087
diff
changeset
|
186 @return (str): arguments that correspond to a signature (e.g.: "sss" return "arg1, arg2, arg3") |
2085 | 187 """ |
188 idx = 0 | |
189 attr_string = [] | |
190 | |
191 for arg in self.argumentsParser(signature): | |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
192 attr_string.append( |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
193 ( |
3028 | 194 "str(%(name)s)%(default)s" |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
195 if (unicode_protect and arg == "s") |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
196 else "%(name)s%(default)s" |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
197 ) |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
198 % { |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
199 "name": name[idx][0] if (name and idx in name) else "arg_%i" % idx, |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
200 "default": "=" + default[idx] if (default and idx in default) else "", |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
201 } |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
202 ) |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
203 # give arg_1, arg2, etc or name1, name2=default, etc. |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
204 # give unicode(arg_1), unicode(arg_2), etc. if unicode_protect is set and arg is a string |
2085 | 205 idx += 1 |
206 | |
207 return ", ".join(attr_string) | |
208 | |
209 def getTemplatePath(self, template_file): | |
210 """return template path corresponding to file name | |
211 | |
212 @param template_file(str): name of template file | |
213 """ | |
214 return os.path.join(self.constructor_dir, template_file) | |
215 | |
216 def core_completion_method(self, completion, function, default, arg_doc, async_): | |
217 """override this method to extend completion""" | |
218 pass | |
219 | |
220 def core_completion_signal(self, completion, function, default, arg_doc, async_): | |
221 """override this method to extend completion""" | |
222 pass | |
223 | |
224 def frontend_completion_method(self, completion, function, default, arg_doc, async_): | |
225 """override this method to extend completion""" | |
226 pass | |
227 | |
228 def frontend_completion_signal(self, completion, function, default, arg_doc, async_): | |
229 """override this method to extend completion""" | |
230 pass | |
231 | |
232 def generate(self, side): | |
233 """generate bridge | |
234 | |
235 call generateCoreSide or generateFrontendSide if they exists | |
236 else call generic self._generate method | |
237 """ | |
238 try: | |
239 if side == "core": | |
240 method = self.generateCoreSide | |
241 elif side == "frontend": | |
2087
159250d66407
bridge (constructor): embedded bridge generator:
Goffi <goffi@goffi.org>
parents:
2085
diff
changeset
|
242 if not self.FRONTEND_ACTIVATE: |
3028 | 243 print("This constructor only handle core, please use core side") |
2087
159250d66407
bridge (constructor): embedded bridge generator:
Goffi <goffi@goffi.org>
parents:
2085
diff
changeset
|
244 sys.exit(1) |
2085 | 245 method = self.generateFrontendSide |
246 except AttributeError: | |
247 self._generate(side) | |
248 else: | |
249 method() | |
250 | |
251 def _generate(self, side): | |
252 """generate the backend | |
253 | |
254 this is a generic method which will use formats found in self.CORE_SIGNAL_FORMAT | |
255 and self.CORE_METHOD_FORMAT (standard format method will be used) | |
256 @param side(str): core or frontend | |
257 """ | |
258 side_vars = [] | |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
259 for var in ("FORMATS", "TEMPLATE", "DEST"): |
2085 | 260 attr = "{}_{}".format(side.upper(), var) |
261 value = getattr(self, attr) | |
262 if value is None: | |
263 raise NotImplementedError | |
264 side_vars.append(value) | |
265 | |
266 FORMATS, TEMPLATE, DEST = side_vars | |
267 del side_vars | |
268 | |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
269 parts = {part.upper(): [] for part in FORMATS} |
2085 | 270 sections = self.bridge_template.sections() |
271 sections.sort() | |
272 for section in sections: | |
273 function = self.getValues(section) | |
3028 | 274 print(("Adding %s %s" % (section, function["type"]))) |
2085 | 275 default = self.getDefault(section) |
276 arg_doc = self.getArgumentsDoc(section) | |
277 async_ = "async" in self.getFlags(section) | |
278 completion = { | |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
279 "sig_in": function["sig_in"] or "", |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
280 "sig_out": function["sig_out"] or "", |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
281 "category": "plugin" if function["category"] == "plugin" else "core", |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
282 "name": section, |
2087
159250d66407
bridge (constructor): embedded bridge generator:
Goffi <goffi@goffi.org>
parents:
2085
diff
changeset
|
283 # arguments with default values |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
284 "args": self.getArguments( |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
285 function["sig_in"], name=arg_doc, default=default |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
286 ), |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
287 } |
2085 | 288 |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
289 extend_method = getattr( |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
290 self, "{}_completion_{}".format(side, function["type"]) |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
291 ) |
2085 | 292 extend_method(completion, function, default, arg_doc, async_) |
293 | |
3028 | 294 for part, fmt in FORMATS.items(): |
2085 | 295 if part.startswith(function["type"]): |
296 parts[part.upper()].append(fmt.format(**completion)) | |
297 | |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
298 # at this point, signals_part, methods_part and direct_calls should be filled, |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
299 # we just have to place them in the right part of the template |
2085 | 300 bridge = [] |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
301 const_override = { |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
302 env[len(C.ENV_OVERRIDE) :]: v |
3028 | 303 for env, v in os.environ.items() |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
304 if env.startswith(C.ENV_OVERRIDE) |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
305 } |
2085 | 306 template_path = self.getTemplatePath(TEMPLATE) |
307 try: | |
308 with open(template_path) as template: | |
309 for line in template: | |
310 | |
3028 | 311 for part, extend_list in parts.items(): |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
312 if line.startswith("##{}_PART##".format(part)): |
2085 | 313 bridge.extend(extend_list) |
314 break | |
315 else: | |
316 # the line is not a magic part replacement | |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
317 if line.startswith("const_"): |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
318 const_name = line[len("const_") : line.find(" = ")].strip() |
2085 | 319 if const_name in const_override: |
3028 | 320 print(("const {} overriden".format(const_name))) |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
321 bridge.append( |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
322 "const_{} = {}".format( |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
323 const_name, const_override[const_name] |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
324 ) |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
325 ) |
2085 | 326 continue |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
327 bridge.append(line.replace("\n", "")) |
2085 | 328 except IOError: |
3028 | 329 print(("can't open template file [{}]".format(template_path))) |
2085 | 330 sys.exit(1) |
331 | |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
332 # now we write to final file |
2085 | 333 self.finalWrite(DEST, bridge) |
334 | |
335 def finalWrite(self, filename, file_buf): | |
336 """Write the final generated file in [dest dir]/filename | |
337 | |
338 @param filename: name of the file to generate | |
339 @param file_buf: list of lines (stings) of the file | |
340 """ | |
341 if os.path.exists(self.args.dest_dir) and not os.path.isdir(self.args.dest_dir): | |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
342 print( |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
343 "The destination dir [%s] can't be created: a file with this name already exists !" |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
344 ) |
2085 | 345 sys.exit(1) |
346 try: | |
347 if not os.path.exists(self.args.dest_dir): | |
348 os.mkdir(self.args.dest_dir) | |
349 full_path = os.path.join(self.args.dest_dir, filename) | |
350 if os.path.exists(full_path) and not self.args.force: | |
3028 | 351 print(( |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
352 "The destination file [%s] already exists ! Use --force to overwrite it" |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
353 % full_path |
3028 | 354 )) |
2085 | 355 try: |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
356 with open(full_path, "w") as dest_file: |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
357 dest_file.write("\n".join(file_buf)) |
2085 | 358 except IOError: |
3028 | 359 print(("Can't open destination file [%s]" % full_path)) |
2085 | 360 except OSError: |
361 print("It's not possible to generate the file, check your permissions") | |
362 exit(1) |