pnb 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pnb/__init__.py +469 -0
- pnb/__main__.py +5 -0
- pnb-0.4.0.dist-info/METADATA +123 -0
- pnb-0.4.0.dist-info/RECORD +7 -0
- pnb-0.4.0.dist-info/WHEEL +4 -0
- pnb-0.4.0.dist-info/entry_points.txt +3 -0
- pnb-0.4.0.dist-info/licenses/LICENSE +38 -0
pnb/__init__.py
ADDED
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
import wx
|
|
8
|
+
except ImportError: # wxPython is an optional dependency, see README
|
|
9
|
+
sys.exit('pnb needs wxPython 4.2 or newer. Install it with\n'
|
|
10
|
+
' pip install "pnb[wx]"\n'
|
|
11
|
+
'or use the package of your Linux distribution '
|
|
12
|
+
'(python3-wxgtk4.0, python3-wxpython4).')
|
|
13
|
+
|
|
14
|
+
import pynotebook
|
|
15
|
+
from pynotebook import graphics
|
|
16
|
+
from pynotebook.nbtexels import NotFound
|
|
17
|
+
from pynotebook.nbview import TextModel, NBView
|
|
18
|
+
from pynotebook.inspector import TextCellInspector
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
wildcard = "Pynotebook files (*.pnb)|*.pnb|" \
|
|
22
|
+
"Python source (*.py)|*.py|" \
|
|
23
|
+
"All files (*.*)|*.*"
|
|
24
|
+
|
|
25
|
+
def action2cmd(label, action):
|
|
26
|
+
# Create a menu command from an editor action
|
|
27
|
+
def method(self, event, action=action):
|
|
28
|
+
self.shell.handle_action(action)
|
|
29
|
+
method.__doc__ = label
|
|
30
|
+
return method
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def method2cmd(label, name):
|
|
34
|
+
# Create a menu command from a method of NBView
|
|
35
|
+
def method(self, event, name=name):
|
|
36
|
+
fun = getattr(self.shell, name)
|
|
37
|
+
fun()
|
|
38
|
+
method.__doc__ = label
|
|
39
|
+
return method
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class MainWindow(wx.Frame):
|
|
46
|
+
set_width = ['Set width', 'increase_maxw', 'decrease_maxw', 'set_w1', 'set_w2', 'set_w3']
|
|
47
|
+
ctxt_entries = ['inspector', 'open', 'save', 'save_as', 'close']
|
|
48
|
+
file_entries = ['new', 'open', 'save', 'save_as', 'close']
|
|
49
|
+
edit_entries = ['copy', 'paste', 'cut', 'undo', 'redo', 'indent', 'dedent']
|
|
50
|
+
cell_entries = ['inspector', 'insert_textcell', 'insert_pycell',
|
|
51
|
+
'remove_output', 'split_cell', set_width]
|
|
52
|
+
interpreter_entries = ['execute', 'execute_all', 'complete', 'help', 'reset']
|
|
53
|
+
debug_entries = ['rebuild_view', 'clean_model', 'dump_texels',
|
|
54
|
+
'dump_styles', 'dump_boxes', 'profile_insert']
|
|
55
|
+
updaters = ()
|
|
56
|
+
def __init__(self, filename=None):
|
|
57
|
+
displayw, displayh = wx.GetDisplaySize()
|
|
58
|
+
wx.Frame.__init__(self, None,
|
|
59
|
+
size=(min(680, displayw), min(800, displayh)))
|
|
60
|
+
icon = logo.GetIcon()
|
|
61
|
+
self.SetIcon(icon)
|
|
62
|
+
panel = wx.Panel(self, -1)
|
|
63
|
+
shell = NBView(panel, -1, filename=filename, maxw=50*12)
|
|
64
|
+
box = wx.BoxSizer(wx.VERTICAL)
|
|
65
|
+
box.Add(shell, 1, wx.ALL|wx.GROW, 1)
|
|
66
|
+
panel.SetSizer(box)
|
|
67
|
+
panel.SetAutoLayout(True)
|
|
68
|
+
shell.Bind(wx.EVT_RIGHT_DOWN, self.right_click)
|
|
69
|
+
shell.SetFocus()
|
|
70
|
+
self.shell = shell
|
|
71
|
+
self.filename = filename
|
|
72
|
+
self.SetMenuBar(self.make_menubar())
|
|
73
|
+
sb = wx.StatusBar(self)
|
|
74
|
+
self.SetStatusBar(sb)
|
|
75
|
+
self.Bind(wx.EVT_IDLE, self.update)
|
|
76
|
+
self.SetName("pnb_python_notebook")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def make_menubar(self):
|
|
80
|
+
menubar = wx.MenuBar()
|
|
81
|
+
updaters = []
|
|
82
|
+
accel = []
|
|
83
|
+
|
|
84
|
+
def extract_accel(s):
|
|
85
|
+
# Extract accelerator information from menu
|
|
86
|
+
# labels. Currently only extracts simple accelerators of
|
|
87
|
+
# the form 'Ctrl+A'. Many accelerators are not parsed
|
|
88
|
+
# correctly, such as 'Shift-Return'. This is ok for now,
|
|
89
|
+
# as these are already handled in nbview. Extracting
|
|
90
|
+
# accelerators became necessary with wxpython 3.0 on. I
|
|
91
|
+
# think it is a bug.
|
|
92
|
+
if not '\t' in s: return
|
|
93
|
+
a, b = s.split("\t", 1)
|
|
94
|
+
if not '-' in b: return
|
|
95
|
+
modifier, key = b.split('-', 1)
|
|
96
|
+
m = dict(Ctrl=wx.ACCEL_CTRL, Alt=wx.ACCEL_ALT)
|
|
97
|
+
if not modifier in m: return
|
|
98
|
+
return m[modifier], ord(key)
|
|
99
|
+
|
|
100
|
+
def mk_menu(entries, self=self, updaters=updaters, accel=accel):
|
|
101
|
+
menu = wx.Menu()
|
|
102
|
+
for entry in entries:
|
|
103
|
+
if entry is None:
|
|
104
|
+
menu.AppendSeperator()
|
|
105
|
+
elif type(entry) is list:
|
|
106
|
+
submenu = mk_menu(entry[1:])
|
|
107
|
+
menu.AppendSubMenu(submenu, entry[0])
|
|
108
|
+
else:
|
|
109
|
+
fun = getattr(self, entry)
|
|
110
|
+
title = fun.__doc__
|
|
111
|
+
item = menu.Append(-1, title)
|
|
112
|
+
self.Bind(wx.EVT_MENU, fun, item)
|
|
113
|
+
shortcut = extract_accel(title)
|
|
114
|
+
if shortcut is not None:
|
|
115
|
+
accel.append(shortcut+(item.Id,))
|
|
116
|
+
if hasattr(self, 'can_'+entry):
|
|
117
|
+
fun = getattr(self, 'can_'+entry)
|
|
118
|
+
def update(fun=fun, item=item, menu=menu):
|
|
119
|
+
menu.Enable(item.Id, fun())
|
|
120
|
+
updaters.append(update)
|
|
121
|
+
return menu
|
|
122
|
+
menubar.Append(mk_menu(self.file_entries), '&File')
|
|
123
|
+
menubar.Append(mk_menu(self.edit_entries), '&Edit')
|
|
124
|
+
menubar.Append(mk_menu(self.cell_entries), '&Cell')
|
|
125
|
+
menubar.Append(mk_menu(self.interpreter_entries), '&Interpreter')
|
|
126
|
+
if debug:
|
|
127
|
+
menubar.Append(mk_menu(self.debug_entries), 'Debug')
|
|
128
|
+
|
|
129
|
+
self.SetAcceleratorTable(wx.AcceleratorTable(accel))
|
|
130
|
+
self.updaters = updaters
|
|
131
|
+
return menubar
|
|
132
|
+
|
|
133
|
+
def make_ctxtmenu(self):
|
|
134
|
+
menu = wx.Menu()
|
|
135
|
+
for entry in self.ctxt_entries:
|
|
136
|
+
fun = getattr(self, entry)
|
|
137
|
+
active = True
|
|
138
|
+
try:
|
|
139
|
+
statefun = getattr(self, 'can_'+entry)
|
|
140
|
+
active = statefun()
|
|
141
|
+
except AttributeError:
|
|
142
|
+
pass
|
|
143
|
+
title = fun.__doc__
|
|
144
|
+
item = menu.Append(-1, title)
|
|
145
|
+
menu.Enable(item.Id, active)
|
|
146
|
+
menu.Bind(wx.EVT_MENU, fun, item)
|
|
147
|
+
return menu
|
|
148
|
+
|
|
149
|
+
def right_click( self, event):
|
|
150
|
+
menu = self.make_ctxtmenu()
|
|
151
|
+
self.PopupMenu(menu, event.Position)
|
|
152
|
+
menu.Destroy() # destroy to avoid mem leak
|
|
153
|
+
|
|
154
|
+
def changed(self):
|
|
155
|
+
return self.shell.undocount()>0
|
|
156
|
+
|
|
157
|
+
def update(self, event):
|
|
158
|
+
# update filename in window title
|
|
159
|
+
if self.filename:
|
|
160
|
+
path, name = os.path.split(self.filename)
|
|
161
|
+
title = name
|
|
162
|
+
else:
|
|
163
|
+
title = '<unnamed>'
|
|
164
|
+
if self.changed():
|
|
165
|
+
title = title+' *'
|
|
166
|
+
self.SetTitle(title)
|
|
167
|
+
|
|
168
|
+
# update menus
|
|
169
|
+
for updater in self.updaters:
|
|
170
|
+
updater()
|
|
171
|
+
|
|
172
|
+
# update statusbar
|
|
173
|
+
i = self.shell.index
|
|
174
|
+
row, col, _ = self.shell.model.index2position(i)
|
|
175
|
+
try:
|
|
176
|
+
i, cell = self.shell.find_cell()
|
|
177
|
+
except NotFound:
|
|
178
|
+
self.StatusBar.SetStatusText('')
|
|
179
|
+
return
|
|
180
|
+
self.StatusBar.SetStatusText('Line: %i, Position: %i' % (row, col))
|
|
181
|
+
|
|
182
|
+
def new(self, event):
|
|
183
|
+
"&New Notebook\tCtrl-N"
|
|
184
|
+
win = MainWindow()
|
|
185
|
+
win.Show()
|
|
186
|
+
|
|
187
|
+
def open(self, event):
|
|
188
|
+
"&Open File ...\tCtrl-O"
|
|
189
|
+
dlg = wx.FileDialog(
|
|
190
|
+
self, message="Choose a file",
|
|
191
|
+
wildcard=wildcard,
|
|
192
|
+
style=wx.FD_OPEN | wx.FD_MULTIPLE | wx.FD_CHANGE_DIR
|
|
193
|
+
)
|
|
194
|
+
if dlg.ShowModal() == wx.ID_OK:
|
|
195
|
+
paths = dlg.GetPaths()
|
|
196
|
+
for path in paths:
|
|
197
|
+
win = MainWindow(path)
|
|
198
|
+
win.Show()
|
|
199
|
+
dlg.Destroy()
|
|
200
|
+
|
|
201
|
+
def save(self, event):
|
|
202
|
+
"&Save\tCtrl-S"
|
|
203
|
+
if self.filename is None:
|
|
204
|
+
self.save_as(event)
|
|
205
|
+
else:
|
|
206
|
+
self.shell.save(self.filename)
|
|
207
|
+
self.shell.clear_undo()
|
|
208
|
+
|
|
209
|
+
def save_as(self, event):
|
|
210
|
+
"Save &As ..."
|
|
211
|
+
dlg = wx.FileDialog(
|
|
212
|
+
self, message="Save File as ...",
|
|
213
|
+
defaultFile="", wildcard=wildcard,
|
|
214
|
+
style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT
|
|
215
|
+
)
|
|
216
|
+
if dlg.ShowModal() == wx.ID_OK:
|
|
217
|
+
path = dlg.GetPath()
|
|
218
|
+
self.shell.save(path)
|
|
219
|
+
self.filename = path
|
|
220
|
+
self.shell.clear_undo()
|
|
221
|
+
|
|
222
|
+
dlg.Destroy()
|
|
223
|
+
|
|
224
|
+
def close(self, event):
|
|
225
|
+
"&Close\tCtrl-W"
|
|
226
|
+
if self.changed():
|
|
227
|
+
dlg = wx.MessageDialog(
|
|
228
|
+
self, 'There are unsaved changes. Do you really want to close?',
|
|
229
|
+
'Close window',
|
|
230
|
+
wx.YES_NO | wx.NO_DEFAULT | wx.CANCEL | wx.ICON_INFORMATION
|
|
231
|
+
)
|
|
232
|
+
result = dlg.ShowModal()
|
|
233
|
+
dlg.Destroy()
|
|
234
|
+
if result != wx.ID_YES:
|
|
235
|
+
return
|
|
236
|
+
self.Close(True)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def increase_maxw(self, event):
|
|
240
|
+
"Increase width\tCtrl-+"
|
|
241
|
+
self.shell.set_maxw(self.shell.maxw+20)
|
|
242
|
+
|
|
243
|
+
def decrease_maxw(self, event):
|
|
244
|
+
"Decrease width\tCtrl--"
|
|
245
|
+
self.shell.set_maxw(self.shell.maxw-20)
|
|
246
|
+
|
|
247
|
+
def can_decrease_maxw(self):
|
|
248
|
+
return self.shell.maxw>50
|
|
249
|
+
|
|
250
|
+
def set_w1(self, event):
|
|
251
|
+
"Set to 600 pixels"
|
|
252
|
+
self.shell.set_maxw(50*12)
|
|
253
|
+
|
|
254
|
+
def set_w2(self, event):
|
|
255
|
+
"Set to 720 pixels"
|
|
256
|
+
self.shell.set_maxw(60*12)
|
|
257
|
+
|
|
258
|
+
def set_w3(self, event):
|
|
259
|
+
"Set to 960 pixels"
|
|
260
|
+
self.shell.set_maxw(80*12)
|
|
261
|
+
|
|
262
|
+
cut = action2cmd("Cut\tCtrl-X", "cut")
|
|
263
|
+
copy = action2cmd("Copy\tCtrl-C", "copy")
|
|
264
|
+
paste = action2cmd("Paste\tCtrl-V", "paste")
|
|
265
|
+
indent = action2cmd("Indent\tCtrl-I", "indent")
|
|
266
|
+
dedent = action2cmd("Dedent\tCtrl-U", "dedent")
|
|
267
|
+
complete = action2cmd("Complete\tTab", "complete")
|
|
268
|
+
help = action2cmd("Help\tCtrl-H", "help")
|
|
269
|
+
execute = method2cmd("Execute Cell\tShift-Return", "execute")
|
|
270
|
+
execute_all = method2cmd("Execute all", "execute_all")
|
|
271
|
+
reset = method2cmd("Reset Interpreter", "reset_interpreter")
|
|
272
|
+
remove_output = method2cmd("Remove Output", "remove_output")
|
|
273
|
+
split_cell = method2cmd("Split Cell\tCtrl-B", "split_cell")
|
|
274
|
+
insert_textcell = method2cmd("Insert Text Cell\tCtrl-T", "insert_textcell")
|
|
275
|
+
insert_pycell = method2cmd("Insert Python Cell", "insert_pycell")
|
|
276
|
+
undo = method2cmd("Undo\tCtrl-Z", "undo")
|
|
277
|
+
redo = method2cmd("Redo\tCtrl-R", "redo")
|
|
278
|
+
dump_texels = action2cmd("Dump Texels\tESC", "dump_info")
|
|
279
|
+
dump_boxes = action2cmd("Dump all Boxes", "dump_boxes")
|
|
280
|
+
|
|
281
|
+
def dump_styles(self, event):
|
|
282
|
+
"Dump styles"
|
|
283
|
+
from pynotebook.textmodel.texeltree import length
|
|
284
|
+
|
|
285
|
+
def _dump_styles(texel, i=0):
|
|
286
|
+
if texel.is_single or texel.is_text:
|
|
287
|
+
print(i, ":", i+length(texel), id(texel.style), texel.__class__.__name__)
|
|
288
|
+
else:
|
|
289
|
+
for child in texel.childs:
|
|
290
|
+
_dump_styles(child, i)
|
|
291
|
+
i += length(child)
|
|
292
|
+
_dump_styles(self.shell.model.texel)
|
|
293
|
+
|
|
294
|
+
def between_cells(self):
|
|
295
|
+
return self.shell.between_cells()
|
|
296
|
+
can_insert_textcell = can_insert_pycell = between_cells
|
|
297
|
+
|
|
298
|
+
def can_undo(self):
|
|
299
|
+
return self.shell.undocount() > 0
|
|
300
|
+
|
|
301
|
+
def can_redo(self):
|
|
302
|
+
return self.shell.redocount() > 0
|
|
303
|
+
|
|
304
|
+
def inspector(self, event):
|
|
305
|
+
"Format ...\tCtrl-F"
|
|
306
|
+
inspector = TextCellInspector(self.shell, self.shell.Parent)
|
|
307
|
+
inspector.Show()
|
|
308
|
+
inspector.update()
|
|
309
|
+
|
|
310
|
+
def rebuild_view(self, event):
|
|
311
|
+
"Rebuild View"
|
|
312
|
+
self.shell.rebuild()
|
|
313
|
+
|
|
314
|
+
def clean_model(self, event):
|
|
315
|
+
"Clean Model"
|
|
316
|
+
# This is a bit hacky ...
|
|
317
|
+
from pynotebook import cerealizerformat
|
|
318
|
+
# 1. replace all styles to the canonical ones
|
|
319
|
+
model = self.shell.model
|
|
320
|
+
cerealizerformat._replace_styles(model.texel, {})
|
|
321
|
+
# 2. call set_properties (with no properties given) to merge
|
|
322
|
+
# character texels of equal style
|
|
323
|
+
self.shell.set_properties(0, len(model))
|
|
324
|
+
|
|
325
|
+
def profile_insert(self, event):
|
|
326
|
+
"Profile Insert"
|
|
327
|
+
from cProfile import runctx
|
|
328
|
+
runctx("self.shell.model.insert_text(self.shell.index, 'X')", globals(), locals())
|
|
329
|
+
self._set_logfont()
|
|
330
|
+
|
|
331
|
+
def _set_logfont(self):
|
|
332
|
+
# Change the font of the output window to make it more readable
|
|
333
|
+
app = wx.GetApp()
|
|
334
|
+
win = app.stdioWin
|
|
335
|
+
if hasattr(win, 'text'):
|
|
336
|
+
text = win.text
|
|
337
|
+
old_font = text.Font
|
|
338
|
+
new_font = wx.Font(old_font.GetPointSize(), wx.FONTFAMILY_MODERN,
|
|
339
|
+
old_font.GetStyle(), old_font.GetWeight())
|
|
340
|
+
text.SetFont(new_font)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
debug = False
|
|
344
|
+
def main(argv=None):
|
|
345
|
+
if argv is None:
|
|
346
|
+
argv = sys.argv[1:]
|
|
347
|
+
|
|
348
|
+
global debug
|
|
349
|
+
redirect = console = False
|
|
350
|
+
filenames = []
|
|
351
|
+
for arg in argv:
|
|
352
|
+
if arg == '--redirect':
|
|
353
|
+
redirect = True
|
|
354
|
+
elif arg == '--debug':
|
|
355
|
+
debug = True
|
|
356
|
+
elif arg == '--console':
|
|
357
|
+
console = True
|
|
358
|
+
else:
|
|
359
|
+
filenames.append(arg)
|
|
360
|
+
|
|
361
|
+
if debug:
|
|
362
|
+
redirect = True
|
|
363
|
+
|
|
364
|
+
app = wx.App(redirect=redirect)
|
|
365
|
+
|
|
366
|
+
win = None
|
|
367
|
+
for name in filenames:
|
|
368
|
+
# Each file is opened in a seperate window.
|
|
369
|
+
win = MainWindow(name)
|
|
370
|
+
win.Show()
|
|
371
|
+
if win is None:
|
|
372
|
+
win = MainWindow()
|
|
373
|
+
win.Show()
|
|
374
|
+
|
|
375
|
+
if console:
|
|
376
|
+
from pynotebook.wxtextview import testing
|
|
377
|
+
testing.pyshell(namespace=dict(win=win))
|
|
378
|
+
|
|
379
|
+
app.MainLoop()
|
|
380
|
+
|
|
381
|
+
# Register classes to the fileformat
|
|
382
|
+
graphics.register_classes()
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
from wx.lib.embeddedimage import PyEmbeddedImage
|
|
386
|
+
|
|
387
|
+
logo = PyEmbeddedImage(
|
|
388
|
+
"iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAYAAADimHc4AAAABmJLR0QA/wD/AP+gvaeTAAAA"
|
|
389
|
+
"CXBIWXMAADddAAA3XQEZgEZdAAAAB3RJTUUH4QcbFAUil8zQBAAAABl0RVh0Q29tbWVudABD"
|
|
390
|
+
"cmVhdGVkIHdpdGggR0lNUFeBDhcAAA8USURBVHja7Z15UFVXnsc/59z73gNZRONCi0gSo9F2"
|
|
391
|
+
"yWBc2pgmaY2TpKykW5G4N0mPUWYmSbkhRBFRQRBcxhmrYtLpNHYEUQxIMmaW2DFttxF7bDOm"
|
|
392
|
+
"p9VEgyvuhiAqyHv3nvkDITEKvA0xer9VVmG9y49b3+/5bWd7Ag8wed57rF8aC8CCglPBCqI0"
|
|
393
|
+
"zTYeIZ4B0UdKGchdDqXMa0qpgyj136bhKlSm+UX6xIhLAPPzT5IxsatH9oS7DyblHmLZiz0A"
|
|
394
|
+
"SN10Nk5K7RUhZYwHJu5GOVCm+WfTNN/cVbI696O8DDNhxR9YO/sJ/wqQtO4wy+Ifuk7+uSJN"
|
|
395
|
+
"tz0PaFioh2Gaxn9VXDg1evT0frXvrd7Nmhk/8a8HJL5zMKhNaIftUmqDLL4b8QfD+Lyq6sJj"
|
|
396
|
+
"K37V68qi9ypYGNvOdwFSNpzCNA3N5gjcIKUWZ9HcNEzT+LSmquKZZS/1qHLnednUhzPXfkb6"
|
|
397
|
+
"hC5omi3eIt89SKk9FhgclvzPq/4gfBLguefSWJUQzbTsbQ7NZv+NRa0nKmiv3NelV1efBBic"
|
|
398
|
+
"MBeA8Pv7ZwkhLFI9KS2FCEXwTwCvrPyT9zngV2kf2qP6/eQwQkRatHqYkE3TmTb2PrtPOSCi"
|
|
399
|
+
"V/QAhGhr0emFF0hpe339sSE+CSA1vTsQYNHpHWx2x2M+CYAQ7QGbRaXXyaCbbwLUkW9lYG/5"
|
|
400
|
+
"p/m5seYEUBaNPiRiN/iTFk2t3DJYFFgC3NPQ78jYqVo/+Qhxe6oP/U4i3a5DsAM6hSgcumy1"
|
|
401
|
+
"8ssEKq+afH1VcOVayw4G/U4QSCmIaAdP9rLzUGc7SHmLCGk2ETVVIwWb+E4Vrdws6r61f7m6"
|
|
402
|
+
"ln3HavnTl05qDNEiA6JJgg1X7R4ltTXN17toCBkhpHxKCNnG05do10Yx4SfBBDokhgIMhZSS"
|
|
403
|
+
"I0fKqKysxOFw0Lt3b4QQHDx4kC1btlBWVkZAQABDhw4lLi4OXdcxDOMGuxcvXuD06dMAREZG"
|
|
404
|
+
"EhYWhlKNi2AYBocOHcLpdBISEkJUVBRDewYSEiD4YJ8Tw2yJXqERzN9QTsaECI8NLth45jXd"
|
|
405
|
+
"Zl8M7s0huQzF1BgH3ToGYJjfkqNpGiNHjmTbtm106tSJkydPMm3aNHJzc2+y0b59ewoLC3ny"
|
|
406
|
+
"yScbCBZCsHv3bmJiYnC5XEybNo01a9Yg5a3rDiklJSUljB49GoCUlBQWLVqEUgpNg3f/WEXZ"
|
|
407
|
+
"+brc4C4Mw7V2cVzHf/SqCvKU/Lm5XwKwZFz4v9ZcrRqilHmg2VirIDxU0a3TjeTXw+FwABAU"
|
|
408
|
+
"FMSUKVPIzc1l+PDhJCcnk5aWRnx8PMHBwXz99deMGDGCd955B03Troc1xdChQ4mJiQGgpKSE"
|
|
409
|
+
"ioqKRt+ltraWLVu2ANC2bVsmTZr0nQgn+GkPG64W8IAGAZZlZPhkKPvFnnWes+4AmZOjvjCd"
|
|
410
|
+
"ta8AV5oTIKqD1mxoPn78OJs3byYjI4Pi4mIyMjJYuHAhb775Jrt27SIqKgqA5ORk9u7dy3fX"
|
|
411
|
+
"L7KysgA4e/YsJSUlDQJ9HxUVFRQVFQEQExNDr169GrzJMBXdOtgwTP8roC9ftow5SUk4HI7A"
|
|
412
|
+
"ldnZj0spB3sz/2MqdVSZ5r8bJ4u+AVg8/kcfpxVe+B+haU821avbNeGGKxsMGzaMefPmYRgG"
|
|
413
|
+
"SikMw0DXdfr27UtWVhbx8fFcvHiR/Px8HnnkkYbfGzRoEAMHDmTPnj2kpaXx8ssv32Rf0zTy"
|
|
414
|
+
"8vKoqqpbxl20aNHNI1XXEJh+b530OUlJzJw9WxNCvKvreqwvrqSk/LMQYmTeb9+9NOmlKRiG"
|
|
415
|
+
"8029KQE8QEYjHmoYBrGxsSQlJXH8+HF27NjBpUuXCA0NbQhFmZmZjBw5klOnTrF582bGjh3b"
|
|
416
|
+
"kLCFECilSE9PB2DYsGFER0fflNBbtBPuFh7+mBDiGd+bFzEYGDLppSkAOK9V/8VfL/rEE080"
|
|
417
|
+
"SorNZmPQoLrdMl9++SXV1dXfeqZpMmDAAKKjowFYvHjxTcn3rbfe4ptvvgEgMzOzyUqppXKA"
|
|
418
|
+
"30pc8d0ArAy/2A0PD2/2me7duwNQWVmJy+W64bOwsDBiY+uc+8iRI3zyySc3VEM5OTkAREdH"
|
|
419
|
+
"07dvX8wWiPVNClBVVfWpUur3vjdU6jOlVOm76zfWxbeAoIF+iZO67tEztxrBcXFxtG3blsuX"
|
|
420
|
+
"L1NcXIzL5UJKydatWykvL0cIwZgxYxpC122bC1qZnc2suXNdK7Ozxwsp/14KMdALFxTAUdM0"
|
|
421
|
+
"i6WmXZoyeVydcc2e4I+XLC8vb/aZ+oYrICDgpkpHKUXPnj0ZMWIERUVFFBUVkZKSQmhoKMXF"
|
|
422
|
+
"xdTU1BAWFsbo0aMbcsJtE2DW3Ln1BUn1rDlzSoASXwymrvsbMJsFG0+PFJr2qH+mKhRlZWU8"
|
|
423
|
+
"+OCDjeaB0tJSAKKioggIuPUydnp6OkVFRZw8eZJt27YxfPhw3n//fQAGDx5Mnz59blvyvakP"
|
|
424
|
+
"mH1dCG+R/LuyuiQX34f5eSf6aLrj34A2/nrR+nr+Vh1saWkpJ06caCAyJCTkltVS7969efrp"
|
|
425
|
+
"pwFYuHAhW7Zs4fz5803av20CfB8pG8o9I+iXD9ZNRWw6M8seGPypEOJhf75ofn4+27ZtuyG8"
|
|
426
|
+
"SClxuVykpqZy+fJlHA4HY8eOxW6/9XYc0zQbys1Dhw4xa9asVik93ZqMS58QQUrBqaek1MaK"
|
|
427
|
+
"picCFUIKFBFCaiOEEH7fxtKlSxcAnn32WTIzMxkzZgx2u50DBw6QmJjIvn37ABg1ahTPP/98"
|
|
428
|
+
"o0QqpejRowfDhg1j586dXL16taHxup1x3+3ZUE23R0upTaeVERgYyNatWxk1ahSJiYkkJibe"
|
|
429
|
+
"9ExCQgJr1qxptoQMCQlh/PjxlJaWYhgG/fr149FHH72tpafbAgAu7gC4XC4efvhhdu/ezebN"
|
|
430
|
+
"mykqKuLYsWMEBgYyZMgQpk6dSv/+/RstQb/XpxAZGYndbqe6uprY2FhCQ0PvTA+40xAWFsb0"
|
|
431
|
+
"6dOZPn36TbHdXQJdLhcffvgh1dXVDaVna+IHJUB9NeMtNE2jvLycwsJCAB5//HH69+/fKsn3"
|
|
432
|
+
"zhBAQLWzZWOvUqqhSy4vL2fMmDEN6wJr1651c3oFnLUuENrdJYAUcPSCAaLl4q+u69zqfEN2"
|
|
433
|
+
"djYRERFujX4pBIfPudBa4JxEqwtwrkpy4GQ1vbu2wTBuFKJdu3a0a9eOjh07+pw7AgIC0HWd"
|
|
434
|
+
"yMhIXnjhBV599VW3Q4+pTD79yokm77YQBNh1wX987qJDSC0dQmzUr0yapsnq1avJzs5G13Wv"
|
|
435
|
+
"y0TTNNm/fz9CCIQQOByOW3bKTQ2SXYdqOFHh2XqwXwRQSl1WSp1o+g+L+o7aAYR5I2pVDazf"
|
|
436
|
+
"Wc2YgYqOoTZ0DQSKkJBQ6rkyvN6SoOjQoeP3RFFujHpwGrDnqyts/8JEky2zS6lRst7+q2Jq"
|
|
437
|
+
"P/Fr4NfuGHp9/fEomyNwuBRyhpCyf938nnvrDEJAZY3gNzuq6dquhvZBGnorHgNXCmqcivIK"
|
|
438
|
+
"g8oaiU1ruS1ijQrw+/x8jwxlTu52DPjtgoIzBUKTv9M021hP84HUJKcvwelKkzsBQkhsLTwQ"
|
|
439
|
+
"dIBVOTnMvN7eZ6an6ygFlNF/yRI3X1SQnJLiqvvZrFYGU5QwHxRSDvCiMr2njoToK7OzmZmY"
|
|
440
|
+
"yMrs7J5S014S0MkbQ6tXrCh3Gcbbs8d1OQ7UpBaeW6Uh38E64tT8gMvJzAx3OBx/BB7ysenZ"
|
|
441
|
+
"oZR6bsacOZeScg93bhN63+d4KejdALd3xtnt9qeAKN9jpogxTbMDwLIXHzoLqsYa483kvusj"
|
|
442
|
+
"t9pvFcR39rkpZbosit0Q4FJFxRal1Ge+GjOVyhNKlQO8nPlxgBB3/w1aPifhFVlZzE5ONoAh"
|
|
443
|
+
"/7J8+S+BHl6FH6X2z0hM3FD//x898OOhIEIsit1IwhmLFzM/NdUvBhfkHWXv6YMMvH9gvpTa"
|
|
444
|
+
"hHuZXLeTsD/In/XWX1n7F8WSSfczoGv/1+518j3KAbccyRvPehbLbHbbiS/K26duOrtatweu"
|
|
445
|
+
"tqj1cSpiybjOpBScGSzrdjo0U/jgEIIHEGKkELKLRasfBACQmva4JrWlWBc23f4QdL2xUhb5"
|
|
446
|
+
"rSiABUsASwALlgD3tADWXH5rlqG1NVc2SU3f16yKUrMLIXpLTf+FEPIR/Hgu4J4VYF7+CZZO"
|
|
447
|
+
"jDwKHHXT1gdA9oKCMz/TdFumkHKIRa8PIWjpRM/uak1aV3dCZsn48O0u57XRyjT3WvS64QH1"
|
|
448
|
+
"C/KrcnJsQogwvP1egPNFV4FLAOkTupxO3Xg2VZP2zVj3jjYJAbA8KyvYZrPlAL8AQvDsjiJB"
|
|
449
|
+
"3WU++5VSc2fMmbOj/oO09y7uF0L2vlfJdXs6Wtf1F4UQCUKIcCFEkBAi2IN/QUKIECHEECnE"
|
|
450
|
+
"0tU5OSGvrz8GgDKNj6wx3kwOmDx+vBBCdPePP4nuSBmQOTnqugDqqEVxMwKsLyhQwD5/GFNK"
|
|
451
|
+
"/c00zSsJWR/XC9LeotiNMvT86dPrOoSHRwgh0oQQuhfEA+wyTDNhVmLi1W/7A/mURXEzAixb"
|
|
452
|
+
"soTA4GD12syZGUCGrwZX5CxnduIc5uWf6Cuk1suiuBkBkhYs8JuxOW8fYPbUuqLHbg9MpW67"
|
|
453
|
+
"ugVvGjFvsPw6+ambzhUI60t/fJuKAJi/4VSAlDKouTUxhUAIYTcN1yDd5nhDSGtd2C8CSE2f"
|
|
454
|
+
"qEn5Os1uGBcgRGdNtwVblPpTACnDhNQesmhqvRxgfYHDDykJW7AEsASwYAlwVwlgbYvzAe58"
|
|
455
|
+
"CUfTApim06qEfIG66psAgouA0yLSS/qVOuaTAIZhHAask45ewnWtZqdPAhzZu/0zlKq0qPRm"
|
|
456
|
+
"9JvXlk6J2uO1AMnrysjLnug0DGeBRafnMF2ubICEFdt9q3Lil5TYHvjxT6uFEJpFq9ux/xvT"
|
|
457
|
+
"cPZb/ELnkz6FoBlvfMa6BT93Gs5r8RatHghgGqs/31Xs1tXDzXpAyoZyTOXSbLY2uVLTJ1v0"
|
|
458
|
+
"Nkv+9suVF59b/g8PX/GLAPVIfPtgYFBYh/8UUouxaG6sbTJKr1Zd+FnOS71qFr1fxcLnQ/wj"
|
|
459
|
+
"wOvrviIzvm7rUOqmc+s13TaOH+Cdoy1ZcZqG8UHlqcNxkf2GGIf+bxdrX3sMv3rAzLX/y6qE"
|
|
460
|
+
"vwNgwcYzP9c0/RWEfErcy4f4lEIpc6dpGG8sHtc5D2Ba1ibeSn7Bk+kK9xEXl0ZhYVpdbig4"
|
|
461
|
+
"FaQUXTWpjRVSPiOE7CukDLv7OTerlFL7lWl+ZBqu95RShzMmRlwGmJ93goxJnu0q/3/q6IVg"
|
|
462
|
+
"+Sj0hAAAAABJRU5ErkJggg==")
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
def test_00():
|
|
466
|
+
main(['--debug'])
|
|
467
|
+
|
|
468
|
+
if __name__ == '__main__':
|
|
469
|
+
main(sys.argv[1:])
|
pnb/__main__.py
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pnb
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: A beautiful and lightweight notebook interface for the python interpreter.
|
|
5
|
+
License-File: LICENSE
|
|
6
|
+
Author: C. Ecker
|
|
7
|
+
Author-email: textmodelview@gmail.com
|
|
8
|
+
Requires-Python: >=3.10,<4.0
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
16
|
+
Provides-Extra: wx
|
|
17
|
+
Requires-Dist: pynotebook (>=0.4.0,<0.5.0)
|
|
18
|
+
Requires-Dist: wxPython (>=4.2) ; extra == "wx"
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
pnb
|
|
23
|
+
====
|
|
24
|
+
|
|
25
|
+

|
|
26
|
+
|
|
27
|
+
[More screenshots](https://codeberg.org/chrisecker/pynotebook/src/branch/master/screenshots/)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
Introduction
|
|
31
|
+
------------
|
|
32
|
+
|
|
33
|
+
Pnb is an interactive environment for Python programming inspired by
|
|
34
|
+
the notebook concept of Mathematica and Jupyter. Notebooks offer a
|
|
35
|
+
nice way to develop and document Python code while organizing
|
|
36
|
+
ideas.
|
|
37
|
+
|
|
38
|
+
Unlike heavier applications like Jupyter or Spyder, pnb is
|
|
39
|
+
exceptionally lightweight and easy to install. It has only two
|
|
40
|
+
dependencies: wxPython and pynotebook, which itself only needs the
|
|
41
|
+
small pure-Python package cerealizer to read files of older versions.
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
Features
|
|
45
|
+
--------
|
|
46
|
+
|
|
47
|
+
- Code cells: run with Shift+Return, the output appears right below
|
|
48
|
+
- Syntax highlighting, completion (Tab) and help on the word at the
|
|
49
|
+
cursor (Ctrl-H)
|
|
50
|
+
- Text cells with formatted text for notes and documentation
|
|
51
|
+
- Plots via matplotlib and built-in vector graphics, right in the
|
|
52
|
+
document
|
|
53
|
+
- Safe, readable file format: notebooks are saved as JSON, and opening
|
|
54
|
+
a file never runs code
|
|
55
|
+
- BSD license
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
Quick start
|
|
59
|
+
-----------
|
|
60
|
+
|
|
61
|
+
Start `pnb`, type some Python code and press Shift+Return:
|
|
62
|
+
|
|
63
|
+
| Key | Action |
|
|
64
|
+
|---|---|
|
|
65
|
+
| Shift+Return | Execute the cell |
|
|
66
|
+
| Tab | Complete |
|
|
67
|
+
| Ctrl-H | Help on the word at the cursor |
|
|
68
|
+
| Ctrl-T | Insert a text cell |
|
|
69
|
+
| Ctrl-B | Split the cell at the cursor |
|
|
70
|
+
| Ctrl-I / Ctrl-U | Indent / dedent |
|
|
71
|
+
|
|
72
|
+
Examples: `demo/*.pnb` in the sources; the Linux packages install them
|
|
73
|
+
to `/usr/share/pnb/examples/`.
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
Installation
|
|
77
|
+
------------
|
|
78
|
+
|
|
79
|
+
Linux (Debian, Ubuntu, Fedora, ...): download the `.deb` or `.rpm`
|
|
80
|
+
from the [Releases page](https://github.com/chrisecker/pynotebook/releases)
|
|
81
|
+
and install it, e.g.:
|
|
82
|
+
|
|
83
|
+
sudo apt install ./pnb_*_all.deb
|
|
84
|
+
sudo dnf install ./pnb-*.noarch.rpm
|
|
85
|
+
|
|
86
|
+
The packages use the wxPython of the distribution and add pnb to the
|
|
87
|
+
application menu.
|
|
88
|
+
|
|
89
|
+
With pip and the wxPython of your Linux distribution (4.2 or newer):
|
|
90
|
+
|
|
91
|
+
sudo apt install python3-wxgtk4.0 # or: sudo dnf install python3-wxpython4
|
|
92
|
+
python3 -m venv --system-site-packages ~/pnb-env
|
|
93
|
+
~/pnb-env/bin/pip install pnb
|
|
94
|
+
|
|
95
|
+
With pip including wxPython (Windows, macOS; on Linux, pip has to build
|
|
96
|
+
wxPython from source):
|
|
97
|
+
|
|
98
|
+
pip install "pnb[wx]"
|
|
99
|
+
|
|
100
|
+
From the source folder:
|
|
101
|
+
|
|
102
|
+
pip install .
|
|
103
|
+
|
|
104
|
+
Desktop integration after a pip installation (pnb must be on the PATH):
|
|
105
|
+
|
|
106
|
+
mkdir -p ~/.local/share/applications ~/.local/share/icons/hicolor/96x96/apps
|
|
107
|
+
cp pnb.desktop ~/.local/share/applications/
|
|
108
|
+
cp pnb.png ~/.local/share/icons/hicolor/96x96/apps/
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
Run without installing
|
|
112
|
+
----------------------
|
|
113
|
+
|
|
114
|
+
In this folder:
|
|
115
|
+
|
|
116
|
+
python runtests.py pnb
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
Links
|
|
120
|
+
-----
|
|
121
|
+
- [pynotebook module](https://pypi.org/project/pynotebook/)
|
|
122
|
+
- [source code](https://codeberg.org/chrisecker/pynotebook/src/branch/master/pnb)
|
|
123
|
+
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
pnb/__init__.py,sha256=sh-H_DFdBaDn2kTgkzXg9nmYm6qRrCglVDUZM9O0m4Q,18907
|
|
2
|
+
pnb/__main__.py,sha256=p21AEJ2qxs3EOirzql409Qv_W6xI1ftyMEPQSKR-0Mo,51
|
|
3
|
+
pnb-0.4.0.dist-info/METADATA,sha256=2WWJ9_9X4hRZdzj9ELOTgWG1ki29DP5qeIozzsimkJI,3568
|
|
4
|
+
pnb-0.4.0.dist-info/WHEEL,sha256=L-WvLdSBvJWHXv5zrQxDvNRnIgoBzLUpcbuiw19KCuQ,88
|
|
5
|
+
pnb-0.4.0.dist-info/entry_points.txt,sha256=YE9toorwLeyvp14C6ovcOiCYwtQhwX_LMESMZ5TI-Y4,32
|
|
6
|
+
pnb-0.4.0.dist-info/licenses/LICENSE,sha256=xee71kXj-vLqCavUcdt-zm3DdwiW9ciOMAwvCPwexYg,1935
|
|
7
|
+
pnb-0.4.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
Copyright (c) 2013, 2014, 2015, 2016, 2017, 2018 C. Ecker
|
|
2
|
+
All rights reserved.
|
|
3
|
+
|
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
|
6
|
+
1. Redistributions of source code must retain the above copyright
|
|
7
|
+
notice, this list of conditions and the following disclaimer.
|
|
8
|
+
2. Redistributions in binary form must reproduce the above copyright
|
|
9
|
+
notice, this list of conditions and the following disclaimer in the
|
|
10
|
+
documentation and/or other materials provided with the distribution.
|
|
11
|
+
3. Neither the name of the <organization> nor the
|
|
12
|
+
names of its contributors may be used to endorse or promote products
|
|
13
|
+
derived from this software without specific prior written permission.
|
|
14
|
+
|
|
15
|
+
THIS SOFTWARE IS PROVIDED BY C. Ecker ''AS IS'' AND ANY
|
|
16
|
+
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
17
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
18
|
+
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
|
19
|
+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
20
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
21
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
22
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
23
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
24
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
25
|
+
|
|
26
|
+
-----------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
Pynb includes cerealizer which is released under the python
|
|
29
|
+
license. Copyright details can be found here:
|
|
30
|
+
|
|
31
|
+
http://www.lesfleursdunormal.fr/static/informatique/cerealizer/index_en.html
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
Pynb includes parts of pygui which is according to the readme free
|
|
35
|
+
software and can be used however you want it. Details can be found
|
|
36
|
+
here:
|
|
37
|
+
|
|
38
|
+
http://www.cosc.canterbury.ac.nz/greg.ewing/python_gui/
|