1# -*- coding: utf-8 -*-
2# cython: language_level=3, always_allow_keywords=True
3
4## Copyright 2004-2018 by LivingLogic AG, Bayreuth/Germany
5## Copyright 2004-2018 by Walter Dörwald
6##
7## All Rights Reserved
8##
9## See ll/xist/__init__.py for the license
10
11
12"""
13:mod:`ll.misc` contains various utility functions and classes used by the other
14LivingLogic modules and packages.
15"""
16
17
18import sys, os, types, datetime, collections, io, gzip as gzip_, argparse, functools, signal, contextlib, subprocess
19
20from ll import ul4c, color
21
22
23__docformat__ = "reStructuredText"
24
25
26# get the current directory as early as possible to minimize the chance that someone has called ``os.chdir()``
27_curdir = os.getcwd()
28
29
30notifycmd = os.environ.get("LL_MISC_NOTIFY", "/usr/local/bin/terminal-notifier")
31
32
33# Try to fetch ``xmlescape`` from C implementation
34try:
35	from ll._misc import *
36except ImportError:
37	def xmlescape(string):
38		"""
39		Return a copy of the argument string, where every occurrence of ``<``,
40		``>``, ``&``, ``\"``, ``'`` and every restricted character has been
41		replaced with their XML character entity or character reference.
42		"""
43		if isinstance(string, str):
44			return string.translate({0x00: '&#0;', 0x01: '&#1;', 0x02: '&#2;', 0x03: '&#3;', 0x04: '&#4;', 0x05: '&#5;', 0x06: '&#6;', 0x07: '&#7;', 0x08: '&#8;', 0x0b: '&#11;', 0x0c: '&#12;', 0x0e: '&#14;', 0x0f: '&#15;', 0x10: '&#16;', 0x11: '&#17;', 0x12: '&#18;', 0x13: '&#19;', 0x14: '&#20;', 0x15: '&#21;', 0x16: '&#22;', 0x17: '&#23;', 0x18: '&#24;', 0x19: '&#25;', 0x1a: '&#26;', 0x1b: '&#27;', 0x1c: '&#28;', 0x1d: '&#29;', 0x1e: '&#30;', 0x1f: '&#31;', 0x22: '&quot;', 0x26: '&amp;', 0x27: '&#39;', 0x3c: '&lt;', 0x3e: '&gt;', 0x7f: '&#127;', 0x80: '&#128;', 0x81: '&#129;', 0x82: '&#130;', 0x83: '&#131;', 0x84: '&#132;', 0x86: '&#134;', 0x87: '&#135;', 0x88: '&#136;', 0x89: '&#137;', 0x8a: '&#138;', 0x8b: '&#139;', 0x8c: '&#140;', 0x8d: '&#141;', 0x8e: '&#142;', 0x8f: '&#143;', 0x90: '&#144;', 0x91: '&#145;', 0x92: '&#146;', 0x93: '&#147;', 0x94: '&#148;', 0x95: '&#149;', 0x96: '&#150;', 0x97: '&#151;', 0x98: '&#152;', 0x99: '&#153;', 0x9a: '&#154;', 0x9b: '&#155;', 0x9c: '&#156;', 0x9d: '&#157;', 0x9e: '&#158;', 0x9f: '&#159;'})
45		else:
46			string = string.replace("&", "&amp;")
47			string = string.replace("<", "&lt;")
48			string = string.replace(">", "&gt;")
49			string = string.replace("'", "&#39;")
50			string = string.replace('"', "&quot;")
51			for c in "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0c\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1f\x7f\x80\x81\x82\x83\x84\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f":
52				string = string.replace(c, f"&#{ord(c)};")
53			return string
54
55	def xmlescape_text(string):
56		"""
57		Return a copy of the argument string, where every occurrence of ``<``,
58		``>``, ``&``, and every restricted character has been replaced with their
59		XML character entity or character reference.
60		"""
61		if isinstance(string, str):
62			return string.translate({0x00: '&#0;', 0x01: '&#1;', 0x02: '&#2;', 0x03: '&#3;', 0x04: '&#4;', 0x05: '&#5;', 0x06: '&#6;', 0x07: '&#7;', 0x08: '&#8;', 0x0b: '&#11;', 0x0c: '&#12;', 0x0e: '&#14;', 0x0f: '&#15;', 0x10: '&#16;', 0x11: '&#17;', 0x12: '&#18;', 0x13: '&#19;', 0x14: '&#20;', 0x15: '&#21;', 0x16: '&#22;', 0x17: '&#23;', 0x18: '&#24;', 0x19: '&#25;', 0x1a: '&#26;', 0x1b: '&#27;', 0x1c: '&#28;', 0x1d: '&#29;', 0x1e: '&#30;', 0x1f: '&#31;', 0x26: '&amp;', 0x3c: '&lt;', 0x3e: '&gt;', 0x7f: '&#127;', 0x80: '&#128;', 0x81: '&#129;', 0x82: '&#130;', 0x83: '&#131;', 0x84: '&#132;', 0x86: '&#134;', 0x87: '&#135;', 0x88: '&#136;', 0x89: '&#137;', 0x8a: '&#138;', 0x8b: '&#139;', 0x8c: '&#140;', 0x8d: '&#141;', 0x8e: '&#142;', 0x8f: '&#143;', 0x90: '&#144;', 0x91: '&#145;', 0x92: '&#146;', 0x93: '&#147;', 0x94: '&#148;', 0x95: '&#149;', 0x96: '&#150;', 0x97: '&#151;', 0x98: '&#152;', 0x99: '&#153;', 0x9a: '&#154;', 0x9b: '&#155;', 0x9c: '&#156;', 0x9d: '&#157;', 0x9e: '&#158;', 0x9f: '&#159;'})
63		else:
64			string = string.replace("&", "&amp;")
65			string = string.replace("<", "&lt;")
66			string = string.replace(">", "&gt;")
67			for c in "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0c\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1f\x7f\x80\x81\x82\x83\x84\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f":
68				string = string.replace(c, f"&#{ord(c)};")
69			return string
70
71	def xmlescape_attr(string):
72		"""
73		Return a copy of the argument string, where every occurrence of ``<``,
74		``>``, ``&``, ``"`` and every restricted character has been replaced with
75		their XML character entity or character reference.
76		"""
77		if isinstance(string, str):
78			return string.translate({0x00: '&#0;', 0x01: '&#1;', 0x02: '&#2;', 0x03: '&#3;', 0x04: '&#4;', 0x05: '&#5;', 0x06: '&#6;', 0x07: '&#7;', 0x08: '&#8;', 0x0b: '&#11;', 0x0c: '&#12;', 0x0e: '&#14;', 0x0f: '&#15;', 0x10: '&#16;', 0x11: '&#17;', 0x12: '&#18;', 0x13: '&#19;', 0x14: '&#20;', 0x15: '&#21;', 0x16: '&#22;', 0x17: '&#23;', 0x18: '&#24;', 0x19: '&#25;', 0x1a: '&#26;', 0x1b: '&#27;', 0x1c: '&#28;', 0x1d: '&#29;', 0x1e: '&#30;', 0x1f: '&#31;', 0x22: '&quot;', 0x26: '&amp;', 0x3c: '&lt;', 0x3e: '&gt;', 0x7f: '&#127;', 0x80: '&#128;', 0x81: '&#129;', 0x82: '&#130;', 0x83: '&#131;', 0x84: '&#132;', 0x86: '&#134;', 0x87: '&#135;', 0x88: '&#136;', 0x89: '&#137;', 0x8a: '&#138;', 0x8b: '&#139;', 0x8c: '&#140;', 0x8d: '&#141;', 0x8e: '&#142;', 0x8f: '&#143;', 0x90: '&#144;', 0x91: '&#145;', 0x92: '&#146;', 0x93: '&#147;', 0x94: '&#148;', 0x95: '&#149;', 0x96: '&#150;', 0x97: '&#151;', 0x98: '&#152;', 0x99: '&#153;', 0x9a: '&#154;', 0x9b: '&#155;', 0x9c: '&#156;', 0x9d: '&#157;', 0x9e: '&#158;', 0x9f: '&#159;'})
79		else:
80			string = string.replace("&", "&amp;")
81			string = string.replace("<", "&lt;")
82			string = string.replace(">", "&gt;")
83			string = string.replace('"', "&quot;")
84			for c in "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0c\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1f\x7f\x80\x81\x82\x83\x84\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f":
85				string = string.replace(c, f"&#{ord(c)};")
86			return string
87
88
89def item(iterable, index, default=None):
90	"""
91	Returns the :obj:`index`'th item from the iterable. :obj:`index` may be
92	negative to count from the end. E.g. 0 returns the first item produced by
93	the iterator, 1 the second, -1 the last one etc. If :obj:`index` is negative
94	the iterator will be completely exhausted, if it's positive it will be
95	exhausted up to the :obj:`index`'th item. If the iterator doesn't produce
96	that many items :obj:`default` will be returned.
97
98	:obj:`index` may also be an iterable of indexes, in which case :meth:`item`
99	will be applied recursively, i.e. ``item(["foo", "bar"], (1, -1))`` returns
100	``'r'``.
101	"""
102	if isinstance(index, int):
103		index = (index,)
104	for i in index:
105		if i >= 0:
106			for item in iterable:
107				if not i:
108					iterable = item
109					break
110				i -= 1
111			else:
112				return default
113		else:
114			i = -i
115			cache = collections.deque()
116			for item in iterable:
117				cache.append(item)
118				if len(cache) > i:
119					cache.popleft()
120			if len(cache) == i:
121				iterable = cache.popleft()
122			else:
123				return default
124	return iterable
125
126
127def first(iterable, default=None):
128	"""
129	Return the first item from the iterable. If the iterator doesn't
130	produce any items :obj:`default` will be returned.
131	"""
132	for item in iterable:
133		return item
134	return default
135
136
137def last(iterable, default=None):
138	"""
139	Return the last item from the iterable. If the iterator doesn't produce any
140	items :obj:`default` will be returned.
141	"""
142	item = default
143	for item in iterable:
144		pass
145	return item
146
147
148def count(iterable):
149	"""
150	Count the number of items produced by the iterable. Calling this function
151	will exhaust the iterator.
152	"""
153	count = 0
154	for node in iterable:
155		count += 1
156	return count
157
158
159def notimplemented(function):
160	"""
161	A decorator that raises :exc:`NotImplementedError` when the method is called.
162	This saves you the trouble of formatting the error message yourself for each
163	implementation.
164	"""
165	@functools.wraps(function)
166	def wrapper(self, *args, **kwargs):
167		raise NotImplementedError(f"method {function.__name__}() not implemented in {format_class(self)}")
168	return wrapper
169
170
171def withdoc(doc):
172	"""
173	A decorator that adds a docstring to the function it decorates. This can be
174	useful if the docstring is not static, and adding it afterwards is not
175	possible.
176	"""
177	def wrapper(function):
178		function.__doc__ = doc
179		return function
180	return wrapper
181
182
183class _propclass_Meta(type):
184	def __new__(cls, name, bases, dict):
185		if bases == (property,):
186			# create propclass itself normally
187			return super(_propclass_Meta, cls).__new__(cls, name, bases, dict)
188		newdict = dict.copy()
189		newdict.pop("__get__", None)
190		newdict.pop("__set__", None)
191		newdict.pop("__delete__", None)
192		newdict.pop("__metaclass__", None)
193		self = type.__new__(cls, name, bases, newdict)
194		inst = self(
195			dict.get("__get__", None),
196			dict.get("__set__", None),
197			dict.get("__delete__", None),
198			dict.get("__doc__", None)
199		)
200		inst.__name__ = name
201		return inst
202
203
204class propclass(property, metaclass=_propclass_Meta):
205	'''
206	:class:`propclass` provides an alternate way to define properties.
207
208	Subclassing :class:`propclass` and defining methods :meth:`__get__`,
209	:meth:`__set__` and :meth:`__delete__` will automatically generate the
210	appropriate property::
211
212		class name(misc.propclass):
213			"""
214			The name property
215			"""
216			def __get__(self):
217				return self._name
218			def __set__(self, name):
219				self._name = name.lower()
220			def __delete__(self):
221				self._name = None
222	'''
223
224
225def format_class(obj):
226	"""
227	Format the name of the class of :obj:`obj`::
228
229		>>> misc.format_class(42)
230		'int'
231		>>> misc.format_class(open('README.rst', 'rb'))
232		'_io.BufferedReader'
233	"""
234	if obj.__class__.__module__ not in ("builtins", "exceptions"):
235		return f"{obj.__class__.__module__}.{obj.__class__.__qualname__}"
236	else:
237		return obj.__class__.__qualname__
238
239
240def format_exception(exc):
241	"""
242	Format an exception object::
243
244		>>> misc.format_exception(ValueError("bad value"))
245		'ValueError: bad value'
246	"""
247	try:
248		strexc = str(exc).strip()
249		if strexc:
250			return f"{format_class(exc)}: {strexc}"
251		else:
252			return format_class(exc)
253	except UnicodeError:
254		return f"{format_class(exc)}: ?"
255
256
257def exception_chain(exc):
258	"""
259	Traverses the chain of exceptions. This is a generator.
260	"""
261	while True:
262		yield exc
263		if exc.__cause__ is not None:
264			exc = exc.__cause__
265		elif exc.__context__ is not None and not exc.__suppress_context__:
266			exc = exc.__context__
267		else:
268			break
269
270
271class Pool:
272	"""
273	A :class:`Pool` object can be used as an inheritable alternative to modules.
274	The attributes of a module can be put into a pool and each pool can have
275	base pools where lookup continues if an attribute can't be found.
276	"""
277	def __init__(self, *objects):
278		self._attrs = {}
279		self.bases = []
280		for object in objects:
281			self.register(object)
282
283	def register(self, object):
284		"""
285		Register :obj:`object` in the pool. :obj:`object` can be a module, a
286		dictionary or a :class:`Pool` objects (with registers the pool as a base
287		pool). If :obj:`object` is a module and has an attribute :attr:`__bases__`
288		(being a sequence of other modules) this attribute will be used to
289		initialize :obj:`self`\s base pool.
290		"""
291		if isinstance(object, types.ModuleType):
292			self.register(object.__dict__)
293		elif isinstance(object, dict):
294			for (key, value) in object.items():
295				if key == "__bases__":
296					for base in value:
297						if not isinstance(base, Pool):
298							base = self.__class__(base)
299						self.bases.append(base)
300				elif not isinstance(value, (types.ModuleType, dict)):
301					try:
302						self._attrs[key] = value
303					except TypeError:
304						pass
305		elif isinstance(object, Pool):
306			self.bases.append(object)
307		elif isinstance(object, type):
308			self._attrs[object.__name__] = object
309
310	def __getitem__(self, key):
311		try:
312			return self._attrs[key]
313		except KeyError:
314			for base in self.bases:
315				return base[key]
316			raise
317
318	def __getattr__(self, key):
319		try:
320			return self.__getitem__(key)
321		except KeyError:
322			raise AttributeError(key)
323
324	def clear(self):
325		"""
326		Make :obj:`self` empty.
327		"""
328		self._attrs.clear()
329		del self.bases[:]
330
331	def clone(self):
332		"""
333		Return a copy of :obj:`self`.
334		"""
335		copy = self.__class__()
336		copy._attrs = self._attrs.copy()
337		copy.bases = self.bases[:]
338		return copy
339
340	def __repr__(self):
341		return f"<{self.__class__.__module__}.{self.__class__.__qualname__} object with {len(self._attrs):,} items at {id(self):#x}>"
342
343
344def iterone(item):
345	"""
346	Return an iterator that will produce one item: :obj:`item`.
347	"""
348	yield item
349
350
351class Iterator:
352	"""
353	:class:`Iterator` adds :meth:`__getitem__` support to an iterator. This is
354	done by calling :func:`item` internally.
355	"""
356	__slots__ = ("iterator", )
357
358	def __init__(self, iterator):
359		self.iterator = iterator
360
361	def __getitem__(self, index):
362		if isinstance(index, slice):
363			return list(self.iterator)[index]
364		default = object()
365		result = item(self, index, default)
366		if result is default:
367			raise IndexError(index)
368		return result
369
370	def __iter__(self):
371		return self
372
373	def __next__(self):
374		return next(self.iterator)
375
376	# We can't implement :meth:`__len__`, because if such an object is passed to
377	# :class:`list`, :meth:`__len__` would be called, exhausting the iterator
378
379	def __bool__(self):
380		for node in self:
381			return True
382		return False
383
384	def get(self, index, default=None):
385		"""
386		Return the :obj:`index`'th item from the iterator (or :obj:`default` if
387		there's no such item).
388		"""
389		return item(self, index, default)
390
391
392class Queue:
393	"""
394	:class:`Queue` provides FIFO queues: The method :meth:`write` writes to the
395	queue and the method :meth:`read` read from the other end of the queue and
396	remove the characters read.
397	"""
398	def __init__(self):
399		self._buffer = ""
400
401	def write(self, chars):
402		"""
403		Write the string :obj:`chars` to the buffer.
404		"""
405		self._buffer += chars
406
407	def read(self, size=-1):
408		"""
409		Read up to :obj:`size` character from the buffer (or all if :obj:`size`
410		is negative). Those characters will be removed from the buffer.
411		"""
412		if size < 0:
413			s = self._buffer
414			self._buffer = ""
415			return s
416		else:
417			s = self._buffer[:size]
418			self._buffer = self._buffer[size:]
419			return s
420
421
422class Const:
423	"""
424	This class can be used for singleton constants.
425	"""
426	__slots__ = ("_name", "_module")
427
428	def __init__(self, name, module=None):
429		self._name = name
430		self._module = module
431
432	def __repr__(self):
433		return f"{self._module or self.__module__}.{self._name}"
434
435
436class FlagAction(argparse.Action):
437	"""
438	:class:`FlagAction` can be use with :mod:`argparse` for options that
439	represent flags. An options can have a value like ``yes`` or ``no`` for the
440	correspending boolean value, or if the value is omitted it is the inverted
441	default value (i.e. specifying the option toggles it).
442	"""
443	true_choices = ("1", "true", "yes", "on")
444	false_choices = ("0", "false", "no", "off")
445
446	def __init__(self, option_strings, dest, default=False, help=None):
447		super().__init__(option_strings=option_strings, dest=dest, default="yes" if default else "no", help=help, metavar="yes|no", const="no" if default else "yes", type=self.str2bool, nargs="?")
448
449	# implementing this prevents :meth:`__repr__` from generating an infinite recursion
450	def _get_kwargs(self):
451		return [(key, getattr(self, key)) for key in ("option_strings", "dest", "default", "help")]
452
453	def str2bool(self, value):
454		value = value.lower()
455		if value in self.true_choices:
456			return True
457		elif value in self.false_choices:
458			return False
459		else:
460			choices = ", ".join(self.true_choices + self.false_choices)
461			raise argparse.ArgumentTypeError(f"invalid flag value: {value!r} (use any of {choices})")
462
463	def __call__(self, parser, namespace, values, option_string=None):
464		setattr(namespace, self.dest, values)
465
466
467def tokenizepi(string):
468	"""
469	Tokenize the string object :obj:`string` according to the processing
470	instructions in the string. :func:`tokenize` will generate tuples with the
471	first item being the processing instruction target and the second being the
472	PI data. "Text" content (i.e. anything other than PIs) will be returned as
473	``(None, data)``.
474	"""
475
476	pos = 0
477	while True:
478		pos1 = string.find("<?", pos)
479		if pos1 < 0:
480			part = string[pos:]
481			if part:
482				yield (None, part)
483			return
484		pos2 = string.find("?>", pos1)
485		if pos2 < 0:
486			part = string[pos:]
487			if part:
488				yield (None, part)
489			return
490		part = string[pos:pos1]
491		if part:
492			yield (None, part)
493		part = string[pos1+2: pos2].strip()
494		parts = part.split(None, 1)
495		target = parts[0]
496		if len(parts) > 1:
497			data = parts[1]
498		else:
499			data = ""
500		yield (target, data)
501		pos = pos2+2
502
503
504def itersplitat(string, positions):
505	"""
506	Split :obj:`string` at the positions specified in :obj:`positions`.
507
508	For example::
509
510		>>> from ll import misc
511		>>> import datetime
512		>>> datetime.datetime(*map(int, misc.itersplitat("20090609172345", (4, 6, 8, 10, 12))))
513		datetime.datetime(2009, 6, 9, 17, 23, 45)
514
515	This is a generator.
516	"""
517	curpos = 0
518	for pos in positions:
519		part = string[curpos:pos]
520		if part:
521			yield part
522		curpos = pos
523	part = string[curpos:]
524	if part:
525		yield part
526
527
528def module(source, filename="unnamed.py", name=None):
529	"""
530	Create a module from the Python source code :obj:`source`. :obj:`filename`
531	will be used as the filename for the module and :obj:`name` as the module
532	name (defaulting to the filename part of :obj:`filename`).
533	"""
534	if name is None:
535		name = os.path.splitext(os.path.basename(filename))[0]
536	mod = types.ModuleType(name)
537	mod.__file__ = filename
538	code = compile(source, filename, "exec")
539	exec(code, mod.__dict__)
540	return mod
541
542
543def javaexpr(obj):
544	"""
545	Return a Java expression for the object :obj:`obj`.
546
547	Example::
548
549		>>> print(misc.javaexpr([1, 2, 3]))
550		java.util.Arrays.asList(1, 2, 3)
551	"""
552
553	if obj is None:
554		return "null"
555	elif obj is True:
556		return "true"
557	elif obj is False:
558		return "false"
559	elif isinstance(obj, str):
560		if len(obj) > 10000: # Otherwise javac complains about ``constant string too long`` (the upper limit is 65535 UTF-8 bytes)
561			parts = "".join(f".append({javaexpr(obj[i:i+10000])})" for i in range(0, len(obj), 10000))
562			return f"new StringBuilder({len(obj)}){parts}.toString()"
563		else:
564			v = []
565			specialchars = {"\r": "\\r", "\n": "\\n", "\t": "\\t", "\f": "\\f", "\b": "\\b", '"': '\\"', "\\": "\\\\"}
566			for c in obj:
567				try:
568					v.append(specialchars[c])
569				except KeyError:
570					oc = ord(c)
571					v.append(c if 32 <= oc < 128 else f"\\u{oc:04x}")
572			string = "".join(v)
573			return f'"{string}"'
574	elif isinstance(obj, datetime.datetime): # check ``datetime`` before ``date``, as ``datetime`` is a subclass of ``date``
575		return f"com.livinglogic.ul4.FunctionDate.call({obj.year}, {obj.month}, {obj.day}, {obj.hour}, {obj.minute}, {obj.second}, {obj.microsecond})"
576	elif isinstance(obj, datetime.date):
577		return f"com.livinglogic.ul4.FunctionDate.call({obj.year}, {obj.month}, {obj.day})"
578	elif isinstance(obj, datetime.timedelta):
579		return f"com.livinglogic.ul4.FunctionTimeDelta.call({obj.days}, {obj.seconds}, {obj.microseconds})"
580	elif isinstance(obj, monthdelta):
581		return f"com.livinglogic.ul4.FunctionMonthDelta.call({obj.months()})"
582	elif isinstance(obj, color.Color):
583		return "new com.livinglogic.ul4.Color({obj[0]}, {obj[1]}, {obj[2]}, {obj[3]})"
584	elif isinstance(obj, float):
585		return repr(obj)
586	elif isinstance(obj, int):
587		if -0x80000000 <= obj <= 0x7fffffff:
588			return repr(obj)
589		elif -0x8000000000000000 <= obj <= 0x7fffffffffffffff:
590			return repr(obj) + "L"
591		else:
592			return f'new java.math.BigInteger("{obj}")'
593		return repr(obj)
594	elif isinstance(obj, collections.Sequence):
595		items = ", ".join(javaexpr(item) for item in obj)
596		return f"java.util.Arrays.asList({items})"
597	elif isinstance(obj, collections.Mapping):
598		items = ", ".join(f"{javaexpr(key)}, {javaexpr(value)}" for (key, value) in obj.items())
599		return f"com.livinglogic.utils.MapUtils.makeMap({items})"
600	elif isinstance(obj, collections.Set):
601		items = ", ".join(javaexpr(item) for item in obj)
602		return f"com.livinglogic.utils.SetUtils.makeSet({items})"
603	elif isinstance(obj, ul4c.UndefinedKey):
604		return f"new com.livinglogic.ul4.UndefinedKey({javaexpr(obj._key)})"
605	elif isinstance(obj, ul4c.UndefinedVariable):
606		return f"new com.livinglogic.ul4.UndefinedVariable({javaexpr(obj._name)})"
607	elif isinstance(obj, ul4c.Template):
608		return obj.javasource()
609	else:
610		raise TypeError(f"can't handle object of type {type(obj)}")
611
612
613class SysInfo:
614	"""
615	A :class:`SysInfo` object contains information about the host, user, python
616	version and script. Available attributes are ``host_name``, ``host_fqdn``,
617	``host_ip``, ``host_sysname``, ``host_nodename``, ``host_release``,
618	``host_version``, ``host_machine``, ``user_name``, ``user_uid``, ``user_gid``,
619	``user_gecos``, ``user_dir``, ``user_shell``, ``python_executable``,
620	``python_version``, ``pid``, ``script_name``, ``short_script_name`` and
621	``script_url``.
622
623	:class:`SysInfo` object also support a mimimal dictionary interface (i.e.
624	:meth:`__getitem__` and :meth:`__iter__`).
625
626	One module global instance named :obj:`sysinfo` is created at module import
627	time.
628	"""
629
630	_keys = {"host_name", "host_fqdn", "host_ip", "host_sysname", "host_nodename", "host_release", "host_version", "host_machine", "user_name", "user_uid", "user_gid", "user_gecos", "user_dir", "user_shell", "python_executable", "python_version", "pid", "script_name", "short_script_name", "script_url"}
631
632	def __init__(self):
633		# Use ``object`` as a marker for "not initialized"
634		self._host_name = object
635		self._host_fqdn = object
636		self._host_ip = object
637		self._host_sysname = object
638		self._host_nodename = object
639		self._host_release = object
640		self._host_version = object
641		self._host_machine = object
642		self._user_name = object
643		self._user_uid = object
644		self._user_gid = object
645		self._user_gecos = object
646		self._user_dir = object
647		self._user_shell = object
648		self._pid = object
649		self._script_name = object
650		self._short_script_name = object
651		self._script_url = object
652
653	@property
654	def host_name(self):
655		if self._host_name is object:
656			import socket
657			self._host_name = socket.gethostname()
658		return self._host_name
659
660	@property
661	def host_fqdn(self):
662		return self.host_name
663
664	@property
665	def host_ip(self):
666		if self._host_ip is object:
667			import socket
668			self._host_ip = socket.gethostbyname(self.host_name)
669		return self._host_ip
670
671	def _make_host_info(self):
672		(self._host_sysname, self._host_nodename, self._host_release, self._host_version, self._host_machine) = os.uname()
673
674	@property
675	def host_sysname(self):
676		if self._host_sysname is object:
677			self._make_host_info()
678		return self._host_sysname
679
680	@property
681	def host_nodename(self):
682		if self._host_nodename is object:
683			self._make_host_info()
684		return self._host_nodename
685
686	@property
687	def host_release(self):
688		if self._host_release is object:
689			self._make_host_info()
690		return self._host_release
691
692	@property
693	def host_version(self):
694		if self._host_version is object:
695			self._make_host_info()
696		return self._host_version
697
698	@property
699	def host_machine(self):
700		if self._host_machine is object:
701			self._make_host_info()
702		return self._host_machine
703
704	def _make_user_info(self):
705		import pwd
706		(self._user_name, _, self._user_uid, self._user_gid, self._user_gecos, self._user_dir, self._user_shell) = pwd.getpwuid(os.getuid())
707
708	@property
709	def user_name(self):
710		if self._user_name is object:
711			self._make_user_info()
712		return self._user_name
713
714	@property
715	def user_uid(self):
716		if self._user_uid is object:
717			self._make_user_info()
718		return self._user_uid
719
720	@property
721	def user_gid(self):
722		if self._user_gid is object:
723			self._make_user_info()
724		return self._user_gid
725
726	@property
727	def user_gecos(self):
728		if self._user_gecos is object:
729			self._make_user_info()
730		return self._user_gecos
731
732	@property
733	def user_dir(self):
734		if self._user_dir is object:
735			self._make_user_info()
736		return self._user_dir
737
738	@property
739	def user_shell(self):
740		if self._user_shell is object:
741			self._make_user_info()
742		return self._user_shell
743
744	@property
745	def python_executable(self):
746		return sys.executable
747
748	@property
749	def python_version(self):
750		v = sys.version_info
751		if v.releaselevel != 'final':
752			return f"{v.major}.{v.minor}.{v.micro} {v.releaselevel}"
753		elif v.micro:
754			return f"{v.major}.{v.minor}.{v.micro}"
755		else:
756			return f"{v.major}.{v.minor}"
757
758	@property
759	def pid(self):
760		return os.getpid()
761
762	@property
763	def script_name(self):
764		if self._script_name is object:
765			main = sys.modules["__main__"]
766			if hasattr(main, "__file__"):
767				self._script_name = os.path.join(_curdir, main.__file__)
768			else:
769				self._script_name = "<shell>"
770		return self._script_name
771
772	@property
773	def short_script_name(self):
774		if self._short_script_name is object:
775			script_name = self.script_name
776			if script_name != "<shell>":
777				userhome = os.path.expanduser("~")
778				if script_name.startswith(userhome+"/"):
779					script_name = "~" + script_name[len(userhome):]
780			self._short_script_name = script_name
781		return self._short_script_name
782
783	@property
784	def script_url(self):
785		if self._script_url is object:
786			from ll import url
787			u = self.short_script_name
788			if u != "<shell>":
789				u = str(url.Ssh(self.user_name, self.host_fqdn or self.host_name, u))
790			self._script_url = u
791		return self._script_url
792
793	def __getitem__(self, key):
794		if key in self._keys:
795			return getattr(self, key)
796		raise KeyError(key)
797
798	def __iter__(self):
799		return iter(self._keys)
800
801
802# Single instance
803sysinfo = SysInfo()
804
805
806class monthdelta:
807	"""
808	:class:`monthdelta` objects can be used to add months/years to a
809	:class:`datetime.datetime` or :class:`datetime.date` object. If the resulting
810	day falls out of the range of valid days for the target month, the last day
811	for the target month will be used instead::
812
813		>>> import datetime
814		>>> from ll import misc
815		>>> datetime.date(2000, 1, 31) + misc.monthdelta(1)
816		datetime.date(2000, 2, 29)
817	"""
818
819	__slots__ = ("_months",)
820	ul4attrs = {"months"}
821
822	def __init__(self, months=0):
823		self._months = months
824
825	def __bool__(self):
826		return self._months != 0
827
828	def __hash__(self):
829		return self._months
830
831	def __eq__(self, other):
832		return isinstance(other, monthdelta) and self._months == other._months
833
834	def __ne__(self, other):
835		return not isinstance(other, monthdelta) or self._months != other._months
836
837	def __lt__(self, other):
838		if not isinstance(other, monthdelta):
839			raise TypeError(f"unorderable types: {format_class(self)}() < {format_class(other)}()")
840		return self._months < other._months
841
842	def __le__(self, other):
843		if not isinstance(other, monthdelta):
844			raise TypeError(f"unorderable types: {format_class(self)}() <= {format_class(other)}()")
845		return self._months <= other._months
846
847	def __gt__(self, other):
848		if not isinstance(other, monthdelta):
849			raise TypeError(f"unorderable types: {format_class(self)}() > {format_class(other)}()")
850		return self._months > other._months
851
852	def __ge__(self, other):
853		if not isinstance(other, monthdelta):
854			raise TypeError(f"unorderable types: {format_class(self)}() >= {format_class(other)}()")
855		return self._months >= other._months
856
857	def __add__(self, other):
858		if isinstance(other, monthdelta):
859			return monthdelta(self._months+other._months)
860		elif isinstance(other, (datetime.datetime, datetime.date)):
861			year = other.year
862			month = other.month + self._months
863			(years_add, month) = divmod(month-1, 12)
864			month += 1
865			year += years_add
866			day = other.day
867			while True:
868				try:
869					return other.replace(year=year, month=month, day=day)
870				except ValueError:
871					day -= 1
872					if day == 1:
873						raise
874		else:
875			return NotImplemented
876
877	def __radd__(self, other):
878		return self.__add__(other)
879
880	def __sub__(self, other):
881		if isinstance(other, monthdelta):
882			return monthdelta(self._months-other._months)
883		else:
884			return NotImplemented
885
886	def __rsub__(self, other):
887		return other + (-self)
888
889	def __neg__(self):
890		return monthdelta(-self._months)
891
892	def __abs__(self):
893		return monthdelta(abs(self._months))
894
895	def __mul__(self, other):
896		if isinstance(other, int) and not isinstance(other, monthdelta):
897			return monthdelta(self._months*other)
898		else:
899			return NotImplemented
900
901	def __rmul__(self, other):
902		return self.__mul__(other)
903
904	def __floordiv__(self, other):
905		if isinstance(other, int):
906			return monthdelta(self._months//other)
907		elif isinstance(other, monthdelta):
908			return self._months//other._months
909		else:
910			return NotImplemented
911
912	def __truediv__(self, other):
913		if isinstance(other, monthdelta):
914			return self._months/other._months
915		else:
916			return NotImplemented
917
918	def __str__(self):
919		m = self._months
920		return f"{m} month{'s' if m != 1 and m != -1 else ''}"
921
922	def __repr__(self):
923		m = self._months
924		if m:
925			return f"monthdelta({m!r})"
926		else:
927			return f"monthdelta()"
928
929	def months(self):
930		return self._months
931
932
933class Timeout(Exception):
934	"""
935	Exception that is raised when a timeout in :func:`timeout` occurs.
936	"""
937	def __init__(self, seconds):
938		self.seconds = seconds
939
940	def __str__(self):
941		return f"timed out after {self.seconds} seconds"
942
943
944@contextlib.contextmanager
945def timeout(seconds):
946	"""
947	A context manager that limits the runtime of the wrapped code.
948
949	This doesn't work with threads and only on UNIX.
950	"""
951
952	def _timeouthandler(signum, frame):
953		raise Timeout(seconds)
954
955	oldsignal = signal.signal(signal.SIGALRM, _timeouthandler)
956	signal.alarm(seconds)
957	try:
958		yield
959	finally:
960		signal.alarm(0)
961		signal.signal(signal.SIGALRM, oldsignal)
962
963
964def notifystart():
965	"""
966	Notify OS X of the start of a process by removing the previous notification.
967	"""
968	cmd = [notifycmd, "-remove", sysinfo.script_name]
969
970	with open("/dev/null", "wb") as f:
971		status = subprocess.call(cmd, stdout=f)
972
973
974def notifyfinish(title, subtitle, message):
975	"""
976	Notify OS X of the end of a process.
977	"""
978	cmd = [notifycmd, "-title", title, "-subtitle", subtitle, "-message", message, "-group", sysinfo.script_name]
979
980	with open("/dev/null", "wb") as f:
981		status = subprocess.call(cmd, stdout=f)
982
983
984def prettycsv(rows, padding="   "):
985	"""
986	Format table :obj:`rows`.
987
988	:obj:`rows` must be a list of lists of strings (e.g. as produced by the
989	:mod:`csv` module). :obj:`padding` is the padding between columns.
990
991	:func:`prettycsv` is a generator.
992	"""
993
994	def width(row, i):
995		try:
996			return len(row[i])
997		except IndexError:
998			return 0
999
1000	maxlen = max(len(row) for row in rows)
1001	lengths = [max(width(row, i) for row in rows) for i in range(maxlen)]
1002	for row in rows:
1003		lasti = len(row)-1
1004		for (i, (w, f)) in enumerate(zip(lengths, row)):
1005			if i:
1006				yield padding
1007			if i == lasti:
1008				f = f.rstrip() # don't add padding to the last column
1009			else:
1010				f = f"{f:<{w}}"
1011			yield f
1012		yield "\n"
1013
1014
1015class JSMinUnterminatedComment(Exception):
1016	pass
1017
1018
1019class JSMinUnterminatedStringLiteral(Exception):
1020	pass
1021
1022
1023class JSMinUnterminatedRegularExpression(Exception):
1024	pass
1025
1026
1027def jsmin(input):
1028	"""
1029	Minimizes the Javascript source :obj:`input`.
1030	"""
1031	indata = iter(input.replace("\r", "\n"))
1032
1033	# Copy the input to the output, deleting the characters which are
1034	# insignificant to JavaScript. Comments will be removed. Tabs will be
1035	# replaced with spaces. Carriage returns will be replaced with linefeeds.
1036	# Most spaces and linefeeds will be removed.
1037
1038	class var:
1039		a = "\n"
1040		b = None
1041		lookahead = None
1042	outdata = []
1043
1044	def _get():
1045		# Return the next character from the input. Watch out for lookahead. If
1046		# the character is a control character, translate it to a space or linefeed.
1047		c = var.lookahead
1048		var.lookahead = None
1049		if c is None:
1050			try:
1051				c = next(indata)
1052			except StopIteration:
1053				return "" # EOF
1054		if c >= " " or c == "\n":
1055			return c
1056		return " "
1057
1058	def _peek():
1059		var.lookahead = _get()
1060		return var.lookahead
1061
1062	def isalphanum(c):
1063		# Return true if the character is a letter, digit, underscore, dollar sign, or non-ASCII character.
1064		return ('a' <= c <= 'z') or ('0' <= c <= '9') or ('A' <= c <= 'Z') or c in "_$\\" or (c is not None and ord(c) > 126)
1065
1066	def _next():
1067		# Get the next character, excluding comments. peek() is used to see if an unescaped '/' is followed by a '/' or '*'.
1068		c = _get()
1069		if c == "/" and var.a != "\\":
1070			p = _peek()
1071			if p == "/":
1072				c = _get()
1073				while c > "\n":
1074					c = _get()
1075				return c
1076			if p == "*":
1077				c = _get()
1078				while 1:
1079					c = _get()
1080					if c == "*":
1081						if _peek() == "/":
1082							_get()
1083							return " "
1084					if not c:
1085						raise JSMinUnterminatedComment()
1086		return c
1087
1088	def _action(action):
1089		"""
1090		Do something! What you do is determined by the argument:
1091		   1   Output A. Copy B to A. Get the next B.
1092		   2   Copy B to A. Get the next B. (Delete A).
1093		   3   Get the next B. (Delete B).
1094		action treats a string as a single character. Wow!
1095		action recognizes a regular expression if it is preceded by ( or , or =.
1096		"""
1097		if action <= 1:
1098			outdata.append(var.a)
1099
1100		if action <= 2:
1101			var.a = var.b
1102			if var.a in "'\"":
1103				while True:
1104					outdata.append(var.a)
1105					var.a = _get()
1106					if var.a == var.b:
1107						break
1108					if var.a <= "\n":
1109						raise JSMinUnterminatedStringLiteral()
1110					if var.a == "\\":
1111						outdata.append(var.a)
1112						var.a = _get()
1113
1114		if action <= 3:
1115			var.b = _next()
1116			if var.b == "/" and var.a in "(,=:[?!&|;{}\n":
1117				outdata.append(var.a)
1118				outdata.append(var.b)
1119				while True:
1120					var.a = _get()
1121					if var.a == "/":
1122						break
1123					elif var.a == "\\":
1124						outdata.append(var.a)
1125						var.a = _get()
1126					elif var.a <= "\n":
1127						raise JSMinUnterminatedRegularExpression()
1128					outdata.append(var.a)
1129				var.b = _next()
1130
1131	_action(3)
1132
1133	while var.a:
1134		if var.a == " ":
1135			_action(1 if isalphanum(var.b) else 2)
1136		elif var.a == "\n":
1137			if var.b in "{[(+-":
1138				_action(1)
1139			elif var.b == " ":
1140				_action(3)
1141			else:
1142				_action(1 if isalphanum(var.b) else 2)
1143		else:
1144			if var.b == " ":
1145				_action(1 if isalphanum(var.a) else 3)
1146			elif var.b == "\n":
1147				_action(1 if isalphanum(var.a) or var.a in "}])+-\"'" else 3)
1148			else:
1149				_action(1)
1150	return "".join(outdata).lstrip()
1151