├── README.md └── pwk /README.md: -------------------------------------------------------------------------------- 1 | # ``pwk``: **P**ython **W**ith **K**urly braces 2 | **Making Python more one-liner-esque** 3 | 4 | 5 |
6 | 7 | ## Motivation 8 | 9 | We love Python. We love them bash one-liners. We want to do one-liners in Python. 10 | 11 | 12 | Now, this is already possible even in many non-trivial cases: 13 | 14 | * We can use, for example, the ternary operators (which however feel somewhat stilted especially if nested), or list comprehensions. 15 | 16 | * Some constructs (``try``/``except``) require more hacky workarounds -- like controlling your own block indentation via some ``bash -c '..'`` wrapper. 17 | 18 | All of this requires us to rewrite code snippets, and at least a modicum of mental effort we wanted to avoid with one-liners in the first place. 19 | 20 | ``pwk`` allows to denote blocks in Python one-liners with kurly, née curly, braces. It generates corresponding multi-line Python with proper indentation on the fly and ``exec()``-utes the resulting code. You can thus write one-liners in pwk/Python like this: 21 | 22 | if "braces"=="bad": { print("Be gone!"); exit(99) } else: { print("Howdy!") } 23 | 24 | 25 |
26 |
27 | 28 | ## Usage 29 | 30 | ### HELLO WORLD 31 | 32 | We feel compelled to start with: 33 | 34 | $ pwk 'print("Brace yourself, World!")' 35 | Brace yourself, World! 36 | 37 | 38 | 39 |
40 | 41 | ### CONDITIONS 42 | 43 | Let's look at the above example again: 44 | 45 | $ pwk 'if "braces"=="bad": { print("Be gone!"); exit(99) } else: { print("Howdy!") }' 46 | Howdy! 47 | 48 | We can use the debug option ``-d`` to show the actual Python code that *would* be executed: 49 | 50 | $ pwk 'if "braces"=="bad": { print("Be gone!"); exit(99) } else: { print("Howdy!") }' -d 51 | if "braces" == "bad" : 52 | print ( "Be gone!" ) 53 | exit ( 99 ) 54 | else : 55 | print ( "Howdy!" ) 56 | 57 | 58 |
59 | 60 | ### FUNCTIONS 61 | 62 | We can define and use our own functions: 63 | 64 | $ pwk 'def s2i(s): { return int(s) } print(s2i("41")+1)' 65 | 42 66 | 67 | as per 68 | 69 | $ pwk 'def s2i(s): { return int(s) } print(s2i("41")+1)' -d 70 | def s2i ( s ) : 71 | return int ( s ) 72 | print ( s2i ( "41" ) + 1 ) 73 | 74 | Feel free to use imports (note: ``sys``, ``io``, ``os``, and ``tokenize`` are already imported and available): 75 | 76 | $ pwk 'import numpy as np; print(np.sin(3.14))' 77 | 0.0015926529164868282 78 | 79 | 80 |
81 | 82 | ### STANDARD INPUT & EXCEPTIONS 83 | 84 | We can use ``pwk`` in an ``awk``-like way. Here, we list the content of all subfolders in the root directory, and ignore the exceptions ``listdir()`` raises if fed a file instead of a directory: 85 | 86 | $ ls / | pwk 'for s in sys.stdin: { try: { print(os.listdir("/"+s.strip())) } except: pass }' 87 | 88 | Because there is no code after the for-block, we can omit its braces: 89 | 90 | $ ls / | pwk 'for s in sys.stdin: try: { print(os.listdir("/"+s.strip())) } except: pass' 91 | 92 | (To the best of my knowledge, there is as of 2020 no purely pythonic way to write one-lined ``try``-blocks.) 93 | 94 | 95 |
96 | 97 | ### VARIABLES & THE ENVIRONMENT 98 | 99 | We don't want to use bash variable replacement in our pwk-code string -- there are just to many special characters floating around. Instead, we can use named variables and lists of variables (TODO: typing via ): 100 | 101 | $ pwk -v my_variable $USER 'print(my_variable)' 102 | 103 | (Of course, use double quotes around environment variables containing spaces.) 104 | 105 | You can use multiple variables. And the position of variable declarations doesn't matter -- you can also write: 106 | 107 | $ pwk 'print(s1+", "+s2)' -v s1 $USER -v s2 $SHELL 108 | martin, /bin/bash 109 | 110 | The resulting code to be executed is, unsurprisingly: 111 | 112 | $ pwk 'print(s1+", "+s2)' -v s1 $USER -v s2 $SHELL -d 113 | u = "martin" 114 | s = "/bin/bash" 115 | print ( u + ", " + s ) 116 | 117 | 118 |
119 | 120 | ### VARIABLE LISTS 121 | 122 | ``pkw`` supports variable lists with ``-V``: 123 | 124 | $ pwk 'print(ls1[0]); print(ls2[-1])' -V ls1 a b c d -V ls2 x y z 125 | a 126 | z 127 | 128 | If lists precede the pwk-code, use ``-c``: 129 | 130 | $ pwk -V ls1 a b c d -V ls2 x y z -c 'print(ls1[0]); print(ls2[-1])' 131 | 132 | Without the ``-c``, you'd get: 133 | 134 | $ pwk -V ls1 a b c d -V ls2 x y z 'print(ls1[0]); print(ls2[-1])' -d 135 | ls1 = ["a", "b", "c", "d"] 136 | ls2 = ["x", "y", "z", "print(ls1[0]); print(ls2[-1])"] 137 | 138 | (This is not necessary for individual variables, as their argument count is known.) 139 | 140 | Variable lists work nicely with bash wildcards: 141 | 142 | $ pwk 'print(my_list)' -V my_list /bin/who* 143 | ['/bin/who', '/bin/whoami'] 144 | 145 |
146 | 147 | ### DICTIONARIES 148 | 149 | Dictionaries should work (except, for now, dicts with dicts as values, but so be it): 150 | 151 | $ pwk 'if True: { d={0:"foo", 1:"bar"} ; for i in range(2): { print(d[i]) } } else: exit(99)' 152 | foo 153 | bar 154 | $ pwk 'if False: { d={0:"foo", 1:"bar"} ; for i in range(2): { print(d[i]) } } else: exit(99)' 155 | $ echo $? 156 | 99 157 | 158 | 159 |
160 |
161 | 162 | ## Background 163 | 164 | ### INSTALL 165 | 166 | ``pwk`` is a single-file app. Just download and ``chmod`` it, and you should be good to go. 167 | 168 | 169 |
170 | 171 | ### HISTORY 172 | 173 | * v0.1 -- 2020-11-22 -- Alpha release 174 | 175 | 176 |
177 | 178 | ### METHOD 179 | 180 | ``pwk`` is itself a Python3 tool. It reformats the given pwk-line into into multi-line Python using ``tokeinze``. It discards open braces that follow a ``:``, which indents and starts a block. The corresponding closing brace de-indents. Other braces (like in dictionaries) are (hopefully) left alone. 181 | 182 | 183 |
184 | 185 | ### THOUGHTS 186 | 187 | Actually, I would really like to have the option to surround blocks with braces right in basic Python. I am, though, not on the edge of my seat waiting for that to happen (see also: ``from __future__ import braces``; [*thx, user OJFord*]). 188 | 189 | My main reason is fairly prosaic: in business code, for example, heavy with assert-like checks and early exits/exceptions, I'd like to compress this "subordinate" code portion into as little horizontal space as possible in order to get it out of way and mind. 190 | 191 | Methinks that the Python community abhors braces more than the vacuum. In fact, we'd only need a "close block" identifier -- possibly the ``°``-character? I find braces more elegant. While I love Python, as stated at the beginning, I also love C, in my view as elegant as can be. 192 | 193 | 194 |
195 | 196 | ### CAVEAT & OUTLOOK 197 | 198 | **This is alpha code!** 199 | 200 | I cooked this up yesterday (2020-11-21), on WSL2/Ubuntu, and would fully expect bugs (it *does* seem to run on macOS). There are quite a few kinks I'd like to work on -- especially to give users earlier syntax errors (instead of passing invalid code to ``exec()``). Also, typed variables/variable lists are planned. (Later, once stable, ``pwk`` hopefully finds its way to some package repos..) 201 | 202 | 203 |
204 | 205 | ### FEEDBACK & BUG REPORTS 206 | 207 | Reach us at info@umlet.com. 208 | 209 | Enjoy! 210 | 211 |
212 |
213 | 214 | --- 215 | *GPL3, Martin Auer, 2020-* 216 | -------------------------------------------------------------------------------- /pwk: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import io 5 | import os 6 | import tokenize 7 | 8 | 9 | 10 | 11 | def pwk2p(s): 12 | ret = "" 13 | 14 | lt = tokenize.tokenize(io.BytesIO(s.encode('utf-8')).readline) 15 | 16 | indent = 0 17 | open_brackets = 0 18 | 19 | newline = False 20 | indent_up = False 21 | indent_down = False 22 | skip = False 23 | 24 | while True: 25 | try: 26 | t = next(lt) #print(t) 27 | 28 | if t.type == tokenize.ENCODING: 29 | last_t = t 30 | continue 31 | 32 | if t.type == tokenize.OP and t.string == ";": 33 | newline = True 34 | elif t.type == tokenize.OP and t.string == ":": 35 | if open_brackets == 0: 36 | newline = True 37 | indent_up = True 38 | 39 | elif t.type == tokenize.OP and t.string == "{": 40 | if last_t.type == tokenize.OP and last_t.string == ":": 41 | skip = True 42 | else: 43 | open_brackets += 1 44 | elif t.type == tokenize.OP and t.string == "}": 45 | if open_brackets > 0: 46 | open_brackets -= 1 47 | elif open_brackets == 0: 48 | if indent > 0: 49 | newline = True 50 | indent_down = True 51 | skip = True 52 | else: 53 | raise Epwk("ERROR: too many closing brackets") 54 | 55 | 56 | if indent_up: 57 | indent += 4 58 | elif indent_down: 59 | indent -= 4 60 | 61 | if newline and indent_up: 62 | ret += ":\n" + " "*indent 63 | elif newline: 64 | ret += "\n" + " "*indent 65 | else: 66 | if not skip: 67 | ret += t.string + " " 68 | 69 | newline = False 70 | indent_up = False 71 | indent_down = False 72 | skip = False 73 | last_t = t 74 | 75 | except StopIteration: 76 | break 77 | except tokenize.TokenError: 78 | continue 79 | 80 | ret += "\n" 81 | 82 | return ret 83 | 84 | 85 | 86 | 87 | def is_opt(s): return s in ["-c", "-v", "-V", "-d"] 88 | def main(argv): 89 | debug = False 90 | keep_order = False 91 | l_subarg = [] 92 | while len(argv) > 0: 93 | arg = argv.pop(0) 94 | if arg == "-c": 95 | if len(argv) < 1: raise Epwk("'-c' requires one argument: ''") 96 | v = argv.pop(0) 97 | l_subarg.append( [arg, v] ) 98 | elif arg == "-v": 99 | if len(argv) < 2: raise Epwk("'-v' requires two arguments: ") 100 | k = argv.pop(0); v = argv.pop(0) 101 | l_subarg.append( [arg, k, v] ) 102 | elif arg == "-V": 103 | if len(argv) < 1: raise Epwk("'-V' requires at least one argument: [ ..]") 104 | k = argv.pop(0) 105 | l_subarg.append( [arg, k] ) 106 | while len(argv) > 0: 107 | if is_opt(argv[0]): break 108 | l_subarg[-1].append(argv.pop(0)) 109 | elif arg == "-d": 110 | l_subarg.append( [arg] ) 111 | debug = True 112 | else: 113 | l_subarg.append( ["-c", arg] ) 114 | 115 | python_cmd_o = "" # ordered 116 | python_cmd_uc = "" # unordered, commands 117 | python_cmd_uv = "" # unordered, variables 118 | for subarg in l_subarg: 119 | if subarg[0] == "-c": 120 | python_cmd_o += pwk2p(subarg[1]) 121 | python_cmd_uc += pwk2p(subarg[1]) 122 | elif subarg[0] == "-v": 123 | python_cmd_o += '%s = "%s"\n' % (subarg[1], subarg[2]) 124 | python_cmd_uv += '%s = "%s"\n' % (subarg[1], subarg[2]) 125 | elif subarg[0] == "-V": 126 | python_cmd_o += "%s = [%s]\n" % (subarg[1], ", ".join('"%s"' % x for x in subarg[2:])) 127 | python_cmd_uv += "%s = [%s]\n" % (subarg[1], ", ".join('"%s"' % x for x in subarg[2:])) 128 | 129 | python_cmd = python_cmd_o 130 | if not keep_order: 131 | python_cmd = python_cmd_uv + python_cmd_uc 132 | 133 | if debug: 134 | print(python_cmd) 135 | return 136 | 137 | exec(python_cmd) 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | try: 147 | # avoid broken pipe 148 | from signal import signal, SIGPIPE, SIG_DFL 149 | signal(SIGPIPE, SIG_DFL) 150 | except ImportError: 151 | pass 152 | 153 | class Epwk(ValueError): pass 154 | 155 | if __name__ == "__main__": 156 | if len(sys.argv) == 1: 157 | msg = """ 158 | pwk (Python With Kurly braces) provides one-liner support for Python via curly braces. 159 | 160 | It transforms (one-lined) Python code containing curly braces to standard Python and exec()-utes it; 161 | has variables and lists to integrate with bash; and can process stdin, a bit awk-ish. 162 | 163 | 164 | Basic usage: 165 | 166 | >pwk [-c] 167 | 168 | [-v ] # sets a Python variable or.. 169 | [-V [, ,..]] # ..variable list 170 | # (both can be used multiple times) 171 | 172 | [-d] # does not execute, but outputs the transformed code for debugging 173 | 174 | 175 | Examples: 176 | 177 | >pwk 'print("Brace yourself, World!")' 178 | Brace yourself, World! 179 | 180 | --- 181 | >pwk 'if "braces"=="bad": { print("Be gone!"); exit(99) } else: { print("Howdy!") }' 182 | Howdy! 183 | 184 | >pwk 'if "braces"=="bad": { print("Be gone!"); exit(99) } else: { print("Howdy!") }' -d 185 | if "braces" == "bad" : 186 | print ( "Be gone!" ) 187 | exit ( 99 ) 188 | else : 189 | print ( "Howdy!" ) 190 | 191 | --- 192 | >pwk 'def s2i(s): { return int(s) } print(s2i("41")+1)' 193 | 42 194 | 195 | >pwk 'import numpy as np; print(np.sin(3.14))' 196 | 0.0015926529164868282 197 | # Note: modules sys, io, os, and tokenize are already available. 198 | 199 | --- 200 | >ls / | pwk 'for s in sys.stdin: print(s.strip())' 201 | 202 | >ls / | pwk 'for s in sys.stdin: { try: { print(os.listdir("/"+s.strip())) } except: pass }' 203 | 204 | --- 205 | >pwk 'print("%s, %s!" % (s1, s2))' -v s1 Hello -v s2 World 206 | Hello, World! 207 | 208 | >pwk 'print(ls1[0]+ls2[-1])' -V ls1 En a b c -V ls2 x y joy! 209 | Enjoy! 210 | 211 | """ 212 | print(msg) 213 | exit(2) 214 | 215 | main(sys.argv[1:]) --------------------------------------------------------------------------------