Added some small boring scripts and programs writen in few last years
[mirrors/Programs.git] / bash / bbs / utils / menu.py
1 #!/usr/bin/python
2 """Yet another curses-based directory tree browser, in Python.
3
4 I thought I could use something like this for filename entry, kind of
5 like the old 4DOS 'select' command --- cd $(cursoutline.py). So you
6 navigate and hit Enter, and it exits and spits out the file you're on.
7
8 """
9 # There are several general approaches to the drawing-an-outline
10 # problem. This program supports the following operations:
11 # - move cursor to previous item (in preorder traversal)
12 # - move cursor to next item (likewise)
13 # - hide descendants
14 # - reveal children
15 # And because it runs over the filesystem, it must be at least somewhat lazy
16 # about expanding children.
17 # And it doesn't really bother to worry about someone else changing the outline
18 # behind its back.
19 # So the strategy is to store our current linear position in the
20 # inorder traversal, and defer operations on the current node until the next
21 # time we're traversing.
22
23
24 import curses.wrapper, time, random, cgitb, os, sys
25 cgitb.enable(format="text")
26 ESC = 27
27 result = ''
28 start = '.'
29
30 def pad(data, width):
31 # XXX this won't work with UTF-8
32 return data + ' ' * (width - len(data))
33
34 class File:
35 def __init__(self, name):
36 self.name = name
37 def render(self, depth, width):
38 return pad('%s%s %s' % (' ' * 4 * depth, self.icon(),
39 os.path.basename(self.name)), width)
40 def icon(self): return ' '
41 def traverse(self): yield self, 0
42 def expand(self): pass
43 def collapse(self): pass
44
45 class Dir(File):
46 def __init__(self, name):
47 File.__init__(self, name)
48 try: self.kidnames = os.listdir(name)
49 except: self.kidnames = None # probably permission denied
50 self.kids = None
51 self.expanded = False
52 def children(self):
53 if self.kidnames is None: return []
54 if self.kids is None:
55 self.kids = [factory(os.path.join(self.name, kid))
56 for kid in self.kidnames]
57 return self.kids
58 def icon(self):
59 if self.expanded: return '[-]'
60 elif self.kidnames is None: return '[?]'
61 elif self.children(): return '[+]'
62 else: return '[ ]'
63 def expand(self): self.expanded = True
64 def collapse(self): self.expanded = False
65 def traverse(self):
66 yield self, 0
67 if not self.expanded: return
68 for child in self.children():
69 for kid, depth in child.traverse():
70 yield kid, depth + 1
71
72 def factory(name):
73 if os.path.isdir(name): return Dir(name)
74 else: return File(name)
75
76 def main(stdscr):
77 cargo_cult_routine(stdscr)
78 stdscr.nodelay(0)
79 mydir = factory(start)
80 mydir.expand()
81 curidx = 3
82 pending_action = None
83 pending_save = False
84
85 while 1:
86 stdscr.clear()
87 curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLUE)
88 line = 0
89 offset = max(0, curidx - curses.LINES + 3)
90 for data, depth in mydir.traverse():
91 if line == curidx:
92 stdscr.attrset(curses.color_pair(1) | curses.A_BOLD)
93 if pending_action:
94 getattr(data, pending_action)()
95 pending_action = None
96 elif pending_save:
97 global result
98 result = data.name
99 return
100 else:
101 stdscr.attrset(curses.color_pair(0))
102 if 0 <= line - offset < curses.LINES - 1:
103 stdscr.addstr(line - offset, 0,
104 data.render(depth, curses.COLS))
105 line += 1
106 stdscr.refresh()
107 ch = stdscr.getch()
108 if ch == curses.KEY_UP: curidx -= 1
109 elif ch == curses.KEY_DOWN: curidx += 1
110 elif ch == curses.KEY_PPAGE:
111 curidx -= curses.LINES
112 if curidx < 0: curidx = 0
113 elif ch == curses.KEY_NPAGE:
114 curidx += curses.LINES
115 if curidx >= line: curidx = line - 1
116 elif ch == curses.KEY_RIGHT: pending_action = 'expand'
117 elif ch == curses.KEY_LEFT: pending_action = 'collapse'
118 elif ch == ESC: return
119 elif ch == ord('\n'): pending_save = True
120 curidx %= line
121
122 def cargo_cult_routine(win):
123 win.clear()
124 win.refresh()
125 curses.nl()
126 curses.noecho()
127 win.timeout(0)
128
129 def open_tty():
130 saved_stdin = os.dup(0)
131 saved_stdout = os.dup(1)
132 os.close(0)
133 os.close(1)
134 stdin = os.open('/dev/tty', os.O_RDONLY)
135 stdout = os.open('/dev/tty', os.O_RDWR)
136 return saved_stdin, saved_stdout
137
138 def restore_stdio((saved_stdin, saved_stdout)):
139 os.close(0)
140 os.close(1)
141 os.dup(saved_stdin)
142 os.dup(saved_stdout)
143
144 if __name__ == '__main__':
145 global start
146 if len(sys.argv) > 1:
147 start = sys.argv[1]
148 saved_fds = open_tty()
149 try: curses.wrapper(main)
150 finally: restore_stdio(saved_fds)
151 print result
152
This page took 0.688061 seconds and 4 git commands to generate.