Mercurial > libervia-backend
annotate sat/bridge/bridge_constructor/base_constructor.py @ 3942:a92eef737703
plugin XEP-0373: download public keys if they are not found in local storage:
public keys were only obtained from PEP notifications, however this wasn't working if the
entity was not in our roster.
Now if no public key is retrieved from local storage, the public key node is requested,
and an error is raised if nothing is found. This allows the use of OX with entities which
are not in roster.
rel 380
author | Goffi <goffi@goffi.org> |
---|---|
date | Sat, 15 Oct 2022 20:38:33 +0200 |
parents | c605a0d6506f |
children | 524856bd7b19 |
rev | line source |
---|---|
3028 | 1 #!/usr/bin/env python3 |
3137 | 2 |
2085 | 3 |
3480
7550ae9cfbac
Renamed the project from "Salut à Toi" to "Libervia":
Goffi <goffi@goffi.org>
parents:
3479
diff
changeset
|
4 # Libervia: an XMPP client |
3479 | 5 # Copyright (C) 2009-2021 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 | |
3538
c605a0d6506f
bridge (constructor/base_constructor): add `args_no_default` to `completion`
Goffi <goffi@goffi.org>
parents:
3480
diff
changeset
|
52 # set to False if your bridge needs only core |
2087
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 ), |
3538
c605a0d6506f
bridge (constructor/base_constructor): add `args_no_default` to `completion`
Goffi <goffi@goffi.org>
parents:
3480
diff
changeset
|
287 "args_no_default": self.getArguments(function["sig_in"], name=arg_doc), |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
288 } |
2085 | 289 |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
290 extend_method = getattr( |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
291 self, "{}_completion_{}".format(side, function["type"]) |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
292 ) |
2085 | 293 extend_method(completion, function, default, arg_doc, async_) |
294 | |
3028 | 295 for part, fmt in FORMATS.items(): |
3039
a1bc34f90fa5
bridge (pb): implemented an asyncio compatible bridge:
Goffi <goffi@goffi.org>
parents:
3028
diff
changeset
|
296 if (part.startswith(function["type"]) |
a1bc34f90fa5
bridge (pb): implemented an asyncio compatible bridge:
Goffi <goffi@goffi.org>
parents:
3028
diff
changeset
|
297 or part.startswith(f"async_{function['type']}")): |
2085 | 298 parts[part.upper()].append(fmt.format(**completion)) |
299 | |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
300 # 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
|
301 # we just have to place them in the right part of the template |
2085 | 302 bridge = [] |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
303 const_override = { |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
304 env[len(C.ENV_OVERRIDE) :]: v |
3028 | 305 for env, v in os.environ.items() |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
306 if env.startswith(C.ENV_OVERRIDE) |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
307 } |
2085 | 308 template_path = self.getTemplatePath(TEMPLATE) |
309 try: | |
310 with open(template_path) as template: | |
311 for line in template: | |
312 | |
3028 | 313 for part, extend_list in parts.items(): |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
314 if line.startswith("##{}_PART##".format(part)): |
2085 | 315 bridge.extend(extend_list) |
316 break | |
317 else: | |
318 # the line is not a magic part replacement | |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
319 if line.startswith("const_"): |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
320 const_name = line[len("const_") : line.find(" = ")].strip() |
2085 | 321 if const_name in const_override: |
3028 | 322 print(("const {} overriden".format(const_name))) |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
323 bridge.append( |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
324 "const_{} = {}".format( |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
325 const_name, const_override[const_name] |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
326 ) |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
327 ) |
2085 | 328 continue |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
329 bridge.append(line.replace("\n", "")) |
2085 | 330 except IOError: |
3028 | 331 print(("can't open template file [{}]".format(template_path))) |
2085 | 332 sys.exit(1) |
333 | |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
334 # now we write to final file |
2085 | 335 self.finalWrite(DEST, bridge) |
336 | |
337 def finalWrite(self, filename, file_buf): | |
338 """Write the final generated file in [dest dir]/filename | |
339 | |
340 @param filename: name of the file to generate | |
341 @param file_buf: list of lines (stings) of the file | |
342 """ | |
343 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
|
344 print( |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
345 "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
|
346 ) |
2085 | 347 sys.exit(1) |
348 try: | |
349 if not os.path.exists(self.args.dest_dir): | |
350 os.mkdir(self.args.dest_dir) | |
351 full_path = os.path.join(self.args.dest_dir, filename) | |
352 if os.path.exists(full_path) and not self.args.force: | |
3028 | 353 print(( |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
354 "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
|
355 % full_path |
3028 | 356 )) |
2085 | 357 try: |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
358 with open(full_path, "w") as dest_file: |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
359 dest_file.write("\n".join(file_buf)) |
2085 | 360 except IOError: |
3028 | 361 print(("Can't open destination file [%s]" % full_path)) |
2085 | 362 except OSError: |
363 print("It's not possible to generate the file, check your permissions") | |
364 exit(1) |