Thanks for sharing this (and I'm honored to make your virtual acquaintance, Eric).
I'd love to make use of pytogo my own project. I've got a little over 20K lines that still need translation. So I cloned it and gave it a quick try. It seems to be choking on doc strings of the form. """ Some docstring text. """ All of my functions and modules have doc strings in that format. In case it's useful as a sample for your development, I've attached the file I tried to convert. It's a short collection of miscellaneous utility functions that didn't quite fit elsewhere in the project. Here's what comes out when I try to convert it. $ cat utils.py | ./pytogo """ ` func iterf(func, sentinel, *args, **kwargs) { """ } If I chop out the doc strings from the top of the module and the first function, iterf, it makes it through the first function (which I realize is probably impossible to represent in Go) and then gives up at the triple-quotes in the next function, $ cat utils.py | ./pytogo func iterf(func, sentinel, *args, **kwargs) { return iter(lambda { func(*args, **kwargs), sentinel) } func base36encode(number) { """ } On Sunday, September 30, 2018 at 6:48:14 AM UTC-4, Eric Raymond wrote: > > I have mentioned before on this list that I am engaged in source > translation of a largeish Python prgram - 14KLOC - to Go. I found the > automated AST-based translation tools already available unsatisfactory; > they don't produce a quality of Go code I would consider maintainable. So > I wrote my own using crude, old-school regexp-bashing. > > This turned out to be surprisingly effective. The following is from the > manual page: > > * Opens Python block scopes with { and closes them with }. > > * Changes docstrings to comments. > > * Changes comments from #-led to //-led > > * Translates Python boolean constants True, False to Go true, false > > * Translates Python "in range" to Go ":= range", You will need to fix > up the comma and range clauses by hand. > > * Maps Python single-quote literals to Go double-quote literals. > > * Translates Python logical connectives and or not to Go && || !. > > * Changes Python None to Go nil. > > * Changes "def " at the left margin to "func ". > > * Common cases of various Python string library methods - capitalize(), > count(), endswith(), find(), join(), lower(), lstrip(), rfind(), replace(), > rstrip(), split(), startswith(), split(), upper() - are translated to Go > string library calls. > > * Some common Python standard library calls, notably from os and > os.filepath, are translated into Go equivalents. The result is not > guaranteed to be perfectly correct; a Python call that throws a signal on > failure may map into a Go function with an error return. But it will always > be a step in the right direction, and the Go compiler will respond with > indicative error messages. > > * The append() method of Python is translated to a call to Go's append > intrinsic. > > * Python's "del foo[bar]" is translated to Go "delete(foo, bar)". Note > that this can false-match on a list del with a numeric key; this > will throw a compilation error in Go and you can hand-fix it. > > * Trailing line-continuation backslashes are removed. > > * Python r-prefix string literals become Go backtick literals > > * Python try/finally/catch become pseudo-statements with delimited block > scopes. You will need to fix these up by hand. > > * Single-line % expressions with a string-literal left hand and either > tuple or single-variable right hands are translated into fmt.Sprintf > calls. You will need to translate %-cookies by hand; %r -> %v or %q is the > usual trouble spot. > > * Changes multiline strings to backtick literals. You'll need to fix up > backslash escapes and %-cookies yourself. > > * Many, but not necessarily all, cases where Go ":=" assignment can > replace a plain "=" assignment in Python are translated. > > This doesn't do a complete job, of course. But it takes a lot of the pain > out of hand-translation, and it does preserve comments and structure. > Probably the automated block scope closing is the biggest single win; > that's a tedious, fiddly job by hand and mistakes are all too easy. > > Future releases will translate a larger subset of Python standard library > calls. > > The repository is at https://gitlab.com/esr/pytogo and the project home > page at http://www.catb.org/esr/pytogo/ > -- You received this message because you are subscribed to the Google Groups "golang-nuts" group. To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.
""" Miscellaneous utility functions. """ def iterf(func, sentinel, *args, **kwargs): """ The Python built-in 'iter' doesn't explicityly handle functions with arguments. This variation accomplishes that by currying with lambda. Typical usage: for x in iterf(foo, None, arg1, ...): doSomethingWith(x) The loop with terminate when foo returns None. """ return iter(lambda : func(*args, **kwargs), sentinel) def base36encode(number): """ Encode an integer as Base 36. >>> base36decode('1BIGNUMBER') 134038991273283 """ if not isinstance(number, int): raise TypeError('number must be an integer') if number < 0: raise ValueError('number must be positive') alphabet = '0123456789abcdefghijklmnopqrstuvwxyz' base36 = '' while number: number, i = divmod(number, 36) base36 = alphabet[i] + base36 return base36.upper() or alphabet[0] def base36decode(b36str): """ Decode a string as a base36 integer. >>> base36encode(134038991273283) '1BIGNUMBER' """ return int(b36str, 36) def leftpadbytelist(bytelist, lmin=4): """ >>> leftpadbytelist([1],4) [0, 0, 0, 1] """ l0 = len(bytelist) if l0 >= lmin: return bytelist else: padded = ([0]*lmin) padded.extend(bytelist) return padded[-lmin:] def i2bytes(i): """ Convert arbitrary int, i, to list of bytes in big-endian order. >>> i2bytes(0x4050) [64, 80] """ result = [] if i == 0: result.append(0) else: while i: result.append(i & 0xFF) i >>= 8 result.reverse() return result def bytes2i(bytelist): """ inverse of i2bytes() >>> '0x{:x}'.format(bytes2i([64, 80])) '0x4050' """ ival = 0 shift = 0 for b in reversed(bytelist): ival += int(b) << shift shift += 8 return ival def ilist2bytes(ilist): """ Convert list of ints to list of bytes >>> ilist2bytes([4096, 64, 65536]) [16, 0, 64, 1, 0, 0] """ result = [] for i in ilist: result.extend(i2bytes(i)) return result def checksum(intlist): """ Compute LRC checksum on list of bytes. >>> checksum([0]) 0 >>> checksum([1]) 255 >>> checksum([16, 0, 64, 1, 0, 0]) 175 """ lrc = 0 for byte in intlist: lrc += byte & 0xFF return ((lrc ^ 0xFF) + 1) & 0xFF def secondsHMS(input_str): """ Flexible bi-directional converter for seconds from H:M:S format. Args: input_str : "H:M:S" or "M:S" or "S" or int where H, M, and S match [0-9]+ Returns: tuple (integer_seconds, string_hms) if successful else (None,None) Raises: Nothing Doctests >>> secondsHMS("01:02:03") (3723, '01:02:03') >>> secondsHMS("62:03") (3723, '01:02:03') >>> secondsHMS("3723") (3723, '01:02:03') Allow integer argument interpreted as seconds >>> secondsHMS(3723) (3723, '01:02:03') Allow whitespace around colons >>> secondsHMS("01 : 02\\t :03") (3723, '01:02:03') """ result = (None, None) ## assume failure try: s = int(input_str) h = m = "00" except ValueError: stripped = input_str.strip() ## remove leading and trailing whitespace try: h, m, s = stripped.split(":") except ValueError: ## see if we can handle it as m:s try: h = "00" m, s = stripped.split(":") except ValueError: ## Try to handle as seconds only h = m = "00" s = stripped try: ## Seconds as integer seconds = 3600 * int(h) + 60 * int(m) + int(s) ## HMS in canonical form (hh:mm:ss) h, s = divmod(seconds, 3600) m, s = divmod(s, 60) hms = "{h:02d}:{m:02d}:{s:02d}".format(**locals()) result = (seconds, hms) except ValueError: pass return result def ago(value, divisor, singular, plural, lastword="ago"): """ Used by the pretty date function (see below) to emit singular or plural units, e.g. a minute ago vs 2 minutes ago. Args: value : integer, typically seconds divisor: number of seconds in, say, an hour singular: string, e.g. 'hour' plural: string, e.g. 'hours' Returns: string Raises: Assertion Error """ assert value >= divisor howmany = int(value)//int(divisor) # use py3 // for int division if howmany == 1: result = "{singular} {lastword}".format(**locals()) else: result = "{howmany} {plural} {lastword}".format(**locals()) return result def pretty_date(timestamp): """ Get a datetime object or a int() Epoch timestamp and return a pretty string like 'an hour ago', 'Yesterday', '3 months ago', 'just now', etc """ from datetime import datetime now = datetime.now() diff = now - datetime.fromtimestamp(int(timestamp)) assert diff.total_seconds() > 0 second_diff = diff.seconds day_diff = diff.days if day_diff == 0: if second_diff < 10: result = "just now" elif second_diff < 60: result = ago(second_diff, 1, "a second", "seconds") elif second_diff < 3600: result = ago(second_diff, 60, "a minute", "minutes") elif second_diff < 86400: result = ago(second_diff, 3600, "an hour", "hours") elif day_diff < 7: result = ago(day_diff, 1, "a day", "days") elif day_diff < 31: result = ago(day_diff, 7, "a week", "weeks") elif day_diff < 365: result = ago(day_diff, 30, "a month", "months") else: result = ago(day_diff, 365, "a year", "years") return result def contiguousIntegers(seq): """ Return True if seq is empty or contains only contiquous integers. >>> good = (2,3,4) >>> single = (2,) >>> empty = () >>> gap = (2,3,5) >>> dup = (2,2,4) >>> empty = () >>> nonint = (1,2.0) >>> [contiguousIntegers(seq) for seq in (good, single, empty, gap, dup, nonint)] [True, True, True, False, False, False] """ if len(seq) == 0: return True elif not all([isinstance(x, int) for x in seq]): return False elif (len(set(seq)) != len(seq)): return False elif max(seq) - min(seq) + 1 != len(seq): return False else: return True if __name__ == '__main__': from doctest import testmod testmod()