├── 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:])
--------------------------------------------------------------------------------