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()

Reply via email to