mwxlib 1.4.0__py3-none-any.whl → 1.4.5__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.

Potentially problematic release.


This version of mwxlib might be problematic. Click here for more details.

mwx/framework.py CHANGED
@@ -1,7 +1,7 @@
1
1
  #! python3
2
2
  """mwxlib framework.
3
3
  """
4
- __version__ = "1.4.0"
4
+ __version__ = "1.4.5"
5
5
  __author__ = "Kazuya O'moto <komoto@jeol.co.jp>"
6
6
 
7
7
  from contextlib import contextmanager
@@ -1460,7 +1460,8 @@ class ShellFrame(MiniFrame):
1460
1460
  if not evt.Active:
1461
1461
  ## Reset autoload when active focus going outside.
1462
1462
  self.__autoload = True
1463
- elif evt.GetActivationReason() == evt.Reason_Mouse and self.__autoload:
1463
+ ## elif evt.GetActivationReason() == evt.Reason_Mouse and self.__autoload:
1464
+ elif self.__autoload:
1464
1465
  ## Check all buffers that need to be loaded.
1465
1466
  verbose = 1
1466
1467
  for book in self.get_all_editors():
mwx/graphman.py CHANGED
@@ -192,12 +192,12 @@ class Thread:
192
192
  except BdbQuit:
193
193
  pass
194
194
  except KeyboardInterrupt as e:
195
- print("- Thread:execution stopped:", e)
195
+ print("- Thread execution stopped:", e)
196
196
  except AssertionError as e:
197
- print("- Thread:execution failed:", e)
197
+ print("- Thread execution failed:", e)
198
198
  except Exception as e:
199
199
  traceback.print_exc()
200
- print("- Thread:exception:", e)
200
+ print("- Thread exception:", e)
201
201
  self.handler('thread_error', self)
202
202
  finally:
203
203
  self.active = 0
@@ -437,7 +437,7 @@ class LayerInterface(CtrlInterface):
437
437
  def OnDestroy(self, evt):
438
438
  if evt.EventObject is self:
439
439
  if self.thread and self.thread.active:
440
- self.thread.active = 0
440
+ ## self.thread.active = 0
441
441
  self.thread.Stop()
442
442
  del self.Arts
443
443
  evt.Skip()
@@ -882,28 +882,28 @@ class Frame(mwx.Frame):
882
882
  elif ret == wx.ID_CANCEL:
883
883
  evt.Veto()
884
884
  return
885
- for name in self.plugins:
886
- plug = self.get_plug(name)
887
- if plug.thread and plug.thread.active:
888
- if not plug.thread.pause( # Confirm closing the thread.
889
- "The thread is running.\n\n"
890
- "Continue closing?",
891
- "Close {!r}".format(plug.Name)):
892
- self.message("The close has been canceled.")
893
- evt.Veto()
894
- return
895
- ## self.Quit()
896
- for frame in self.graph.get_all_frames():
897
- if frame.pathname is None:
898
- if wx.MessageBox( # Confirm closing the frame.
899
- "You are closing unsaved frame.\n\n"
900
- "Continue closing?",
901
- "Close {!r}".format(frame.name),
902
- style=wx.YES_NO|wx.ICON_INFORMATION) != wx.YES:
903
- self.message("The close has been canceled.")
904
- evt.Veto()
905
- return
906
- break
885
+ n = sum(bool(plug.thread and plug.thread.active)
886
+ for plug in (self.get_plug(name) for name in self.plugins))
887
+ if n:
888
+ s = 's' if n > 1 else ''
889
+ if wx.MessageBox( # Confirm closing the thread.
890
+ f"Currently running {n} thread{s}.\n\n"
891
+ "Continue closing?",
892
+ style=wx.YES_NO|wx.ICON_INFORMATION) != wx.YES:
893
+ self.message("The close has been canceled.")
894
+ evt.Veto()
895
+ return
896
+ self.Quit()
897
+ n = sum(frame.pathname is None for frame in self.graph.all_frames)
898
+ if n:
899
+ s = 's' if n > 1 else ''
900
+ if wx.MessageBox( # Confirm closing the frame.
901
+ f"You are closing {n} unsaved frame{s}.\n\n"
902
+ "Continue closing?",
903
+ style=wx.YES_NO|wx.ICON_INFORMATION) != wx.YES:
904
+ self.message("The close has been canceled.")
905
+ evt.Veto()
906
+ return
907
907
  evt.Skip()
908
908
 
909
909
  def Destroy(self):
@@ -1383,7 +1383,7 @@ class Frame(mwx.Frame):
1383
1383
  plug = self.get_plug(name)
1384
1384
  thread = plug.thread # Note: thread can be None or shared.
1385
1385
  if thread and thread.active:
1386
- thread.active = 0
1386
+ ## thread.active = 0
1387
1387
  thread.Stop()
1388
1388
 
1389
1389
  ## --------------------------------
@@ -1432,7 +1432,7 @@ class Frame(mwx.Frame):
1432
1432
  """
1433
1433
  view = self.selected_view
1434
1434
  if not frames:
1435
- frames = view.get_all_frames()
1435
+ frames = view.all_frames
1436
1436
  if not frames:
1437
1437
  return None
1438
1438
 
@@ -1698,7 +1698,7 @@ class Frame(mwx.Frame):
1698
1698
  def save_buffers_as_tiffs(self, path=None, frames=None):
1699
1699
  """Save buffers to a file as a multi-page tiff."""
1700
1700
  if not frames:
1701
- frames = self.selected_view.get_all_frames()
1701
+ frames = self.selected_view.all_frames
1702
1702
  if not frames:
1703
1703
  return None
1704
1704
 
@@ -1817,7 +1817,7 @@ class Frame(mwx.Frame):
1817
1817
 
1818
1818
  def _save(view):
1819
1819
  name = view.Name
1820
- paths = [x.pathname for x in view.get_all_frames() if x.pathname]
1820
+ paths = [x.pathname for x in view.all_frames if x.pathname]
1821
1821
  o.write(f"self.{name}.unit = {view.unit:g}\n")
1822
1822
  o.write(f"self.load_frame({paths!r}, self.{name})\n")
1823
1823
  try:
mwx/matplot2g.py CHANGED
@@ -783,9 +783,14 @@ class GraphPlot(MatplotPanel):
783
783
  else:
784
784
  return self.__Arts[j] # j:int -> frame
785
785
 
786
- def get_all_frames(self):
786
+ def get_all_frames(self, j=None):
787
787
  """List of arts <matplotlib.image.AxesImage>."""
788
- return self.__Arts
788
+ if j is None:
789
+ yield from self.__Arts
790
+ elif isinstance(j, str):
791
+ yield from (art for art in self.__Arts if j in art.name)
792
+ elif isinstance(j, np.ndarray):
793
+ yield from (art for art in self.__Arts if j is art.buffer)
789
794
 
790
795
  ## --------------------------------
791
796
  ## Property of frame / drawer
@@ -798,7 +803,7 @@ class GraphPlot(MatplotPanel):
798
803
  score_percentile = 0.005
799
804
 
800
805
  @property
801
- def all_frames(self): # (deprecated) for backward compatibility
806
+ def all_frames(self):
802
807
  """List of arts <matplotlib.image.AxesImage>."""
803
808
  return self.__Arts
804
809
 
mwx/nutshell.py CHANGED
@@ -28,10 +28,23 @@ from wx.py.editwindow import EditWindow
28
28
 
29
29
  from .utilus import funcall as _F
30
30
  from .utilus import ignore, typename
31
- from .utilus import split_words, split_paren, split_tokens, find_modules
31
+ from .utilus import split_words, split_parts, split_tokens, find_modules
32
32
  from .framework import CtrlInterface, AuiNotebook, Menu
33
33
 
34
34
 
35
+ ## Monkey-patch for wx.py.introspect.getRoot with terminator '('.
36
+ getRoot = introspect.getRoot
37
+ def _getRoot(command, terminator=None):
38
+ """Return the rightmost root portion of an arbitrary Python command."""
39
+ if terminator == '(':
40
+ words = split_words(command, reverse=1)
41
+ for word in words:
42
+ if word == terminator:
43
+ return next(words, '')
44
+ return getRoot(command, terminator) # original
45
+ introspect.getRoot = _getRoot
46
+
47
+
35
48
  ## URL pattern (flag = re.M | re.A)
36
49
  ## url_re = r"https?://[\w/:%#$&?()~.=+-]+"
37
50
  url_re = r"https?://[\w/:%#$&?!@~.,;=+-]+" # excluding ()
@@ -210,8 +223,8 @@ class AutoCompInterfaceMixin:
210
223
  dispatcher.send(signal='Shell.calltip', sender=self, calltip=tip)
211
224
  p = self.cpos
212
225
  if argspec and insertcalltip:
213
- self.AddText(argspec + ')') # 挿入後のカーソル位置は変化しない
214
- self.SetSelection(self.cpos, p) # selection back
226
+ self.AddText(argspec + ')')
227
+ self.cpos = p # selection backward to the point
215
228
  if tip:
216
229
  ## In case there isn't enough room, only go back to bol fallback.
217
230
  tippos = max(self.bol, p - len(name) - 1)
@@ -269,7 +282,7 @@ class AutoCompInterfaceMixin:
269
282
  self.anchor = q
270
283
  with self.off_undocollection():
271
284
  self.ReplaceSelection(word[n:])
272
- self.cpos = p # backward selection to the point
285
+ self.cpos = p # selection backward to the point
273
286
  self.__comp_ind = j
274
287
  except IndexError:
275
288
  self.message("No completion words")
@@ -641,8 +654,8 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
641
654
  self.IndicatorSetStyle(11, stc.STC_INDIC_STRAIGHTBOX)
642
655
  self.IndicatorSetForeground(11, "yellow")
643
656
  self.IndicatorSetUnder(11, True)
644
- self.IndicatorSetAlpha(11, 0xe8)
645
- self.IndicatorSetOutlineAlpha(11, 0)
657
+ ## self.IndicatorSetAlpha(11, 0xe8)
658
+ ## self.IndicatorSetOutlineAlpha(11, 0)
646
659
 
647
660
  self.IndicatorSetStyle(2, stc.STC_INDIC_DOTS)
648
661
  self.IndicatorSetForeground(2, "light gray")
@@ -863,7 +876,7 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
863
876
  return sty
864
877
 
865
878
  def get_char(self, pos):
866
- """Returns the character at the position."""
879
+ """Returns the character at the given position."""
867
880
  return chr(self.GetCharAt(pos))
868
881
 
869
882
  def get_text(self, start, end):
@@ -944,10 +957,12 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
944
957
  return topic
945
958
  with self.save_excursion():
946
959
  p = q = self.cpos
947
- if self.get_char(p-1).isalnum():
960
+ ## if self.get_char(p-1).isidentifier():
961
+ if self.GetTextRange(self.PositionBefore(p), p).isidentifier():
948
962
  self.WordLeft()
949
963
  p = self.cpos
950
- if self.get_char(q).isalnum():
964
+ ## if self.get_char(q).isidentifier():
965
+ if self.GetTextRange(q, self.PositionAfter(q)).isidentifier():
951
966
  self.WordRightEnd()
952
967
  q = self.cpos
953
968
  return self.GetTextRange(p, q)
@@ -1376,32 +1391,34 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
1376
1391
  def grep(self, pattern, flags=re.M):
1377
1392
  yield from re.finditer(pattern.encode(), self.TextRaw, flags)
1378
1393
 
1379
- def search_text(self, text):
1380
- """Yields raw-positions where `text` is found."""
1381
- word = text.encode()
1382
- raw = self.TextRaw
1394
+ def search_text(self, text, mode=True):
1395
+ """Yields positions where `text` is found.
1396
+ If mode is True, search by word; otherwise, search by string.
1397
+ """
1398
+ text = text.encode()
1383
1399
  pos = -1
1400
+ p = re.compile(r"[a-zA-Z0-9_]")
1384
1401
  while 1:
1385
- pos = raw.find(word, pos+1)
1402
+ pos = self.TextRaw.find(text, pos+1)
1386
1403
  if pos < 0:
1387
1404
  break
1405
+ if mode and p.search(self.get_char(pos-1) + self.get_char(pos+len(text))):
1406
+ continue
1388
1407
  yield pos
1389
1408
 
1390
- def filter_text(self, text=None):
1409
+ def filter_text(self):
1391
1410
  """Show indicators for the selected text."""
1392
1411
  self.__itextlines = []
1393
1412
  for i in (10, 11,):
1394
1413
  self.SetIndicatorCurrent(i)
1395
1414
  self.IndicatorClearRange(0, self.TextLength)
1396
- if text is None:
1397
- text = self.topic_at_caret
1415
+ text = self.topic_at_caret
1398
1416
  if not text:
1399
1417
  self.message("No words")
1400
1418
  return
1401
-
1402
1419
  lw = len(text.encode()) # for multi-byte string
1403
1420
  lines = []
1404
- for p in self.search_text(text):
1421
+ for p in self.search_text(text, mode=(not self.SelectedText)):
1405
1422
  lines.append(self.LineFromPosition(p))
1406
1423
  for i in (10, 11,):
1407
1424
  self.SetIndicatorCurrent(i)
@@ -1833,12 +1850,8 @@ class Buffer(EditorInterface, EditWindow):
1833
1850
  self.message("")
1834
1851
 
1835
1852
  def clear_autocomp(evt):
1836
- ## """Clear autocomp, selection, and message."""
1837
1853
  if self.AutoCompActive():
1838
1854
  self.AutoCompCancel()
1839
- if self.CanEdit():
1840
- with self.off_undocollection():
1841
- self.ReplaceSelection("")
1842
1855
  self.message("")
1843
1856
 
1844
1857
  def fork(evt):
@@ -1882,7 +1895,7 @@ class Buffer(EditorInterface, EditWindow):
1882
1895
  '* pressed' : (0, clear_autocomp, fork),
1883
1896
  'tab pressed' : (0, clear, skip),
1884
1897
  'enter pressed' : (0, clear, skip),
1885
- 'escape pressed' : (0, clear_autocomp),
1898
+ 'escape pressed' : (0, clear, skip),
1886
1899
  'up pressed' : (2, skip, self.on_completion_backward),
1887
1900
  'down pressed' : (2, skip, self.on_completion_forward),
1888
1901
  '*left pressed' : (2, skip),
@@ -1909,7 +1922,7 @@ class Buffer(EditorInterface, EditWindow):
1909
1922
  '* pressed' : (0, clear_autocomp, fork),
1910
1923
  'tab pressed' : (0, clear, skip),
1911
1924
  'enter pressed' : (0, clear, skip),
1912
- 'escape pressed' : (0, clear_autocomp),
1925
+ 'escape pressed' : (0, clear, skip),
1913
1926
  'up pressed' : (3, skip, self.on_completion_backward),
1914
1927
  'down pressed' : (3, skip, self.on_completion_forward),
1915
1928
  '*left pressed' : (3, skip),
@@ -2099,8 +2112,15 @@ class Buffer(EditorInterface, EditWindow):
2099
2112
  if self.CallTipActive():
2100
2113
  self.CallTipCancel()
2101
2114
 
2102
- text = self.SelectedText or self.expr_at_caret
2103
- if text:
2115
+ def _gen_text():
2116
+ text = self.SelectedText
2117
+ if text:
2118
+ yield text
2119
+ else:
2120
+ yield self.line_at_caret
2121
+ yield self.expr_at_caret
2122
+
2123
+ for text in _gen_text():
2104
2124
  try:
2105
2125
  obj = eval(text, self.globals, self.locals)
2106
2126
  except Exception as e:
@@ -2108,7 +2128,8 @@ class Buffer(EditorInterface, EditWindow):
2108
2128
  else:
2109
2129
  self.CallTipShow(self.cpos, pformat(obj))
2110
2130
  self.message(text)
2111
- else:
2131
+ return
2132
+ if not text:
2112
2133
  self.message("No words")
2113
2134
 
2114
2135
  def exec_region(self):
@@ -2610,22 +2631,6 @@ class Interpreter(interpreter.Interpreter):
2610
2631
  self.parent.handler('interp_error', v)
2611
2632
  except AttributeError:
2612
2633
  pass
2613
-
2614
- @ignore(DeprecationWarning)
2615
- def getCallTip(self, command='', *args, **kwargs):
2616
- """Return call tip text for a command.
2617
-
2618
- (override) Ignore DeprecationWarning: for function,
2619
- `formatargspec` is deprecated since Python 3.5.
2620
- (override) Ignore ValueError: no signature found for builtin
2621
- if the unwrapped function is a builtin function.
2622
- """
2623
- ## In 4.2.1, DeprecationWarning was fixed.
2624
- ## In 4.2.2, ValueError was fixed.
2625
- try:
2626
- return interpreter.Interpreter.getCallTip(self, command, *args, **kwargs)
2627
- except ValueError:
2628
- return interpreter.Interpreter.getCallTip(self) # dummy
2629
2634
 
2630
2635
 
2631
2636
  class Nautilus(EditorInterface, Shell):
@@ -2796,12 +2801,8 @@ class Nautilus(EditorInterface, Shell):
2796
2801
  self.message("")
2797
2802
 
2798
2803
  def clear_autocomp(evt):
2799
- ## """Clear autocomp, selection, and message."""
2800
2804
  if self.AutoCompActive():
2801
2805
  self.AutoCompCancel()
2802
- if self.CanEdit():
2803
- with self.off_undocollection():
2804
- self.ReplaceSelection("")
2805
2806
  self.message("")
2806
2807
 
2807
2808
  def fork(evt):
@@ -2900,7 +2901,7 @@ class Nautilus(EditorInterface, Shell):
2900
2901
  '* pressed' : (0, clear_autocomp, fork),
2901
2902
  'tab pressed' : (0, clear, skip),
2902
2903
  'enter pressed' : (0, clear, skip),
2903
- 'escape pressed' : (0, clear_autocomp),
2904
+ 'escape pressed' : (0, clear, skip),
2904
2905
  'up pressed' : (2, skip, self.on_completion_backward),
2905
2906
  'down pressed' : (2, skip, self.on_completion_forward),
2906
2907
  '*left pressed' : (2, skip),
@@ -2928,7 +2929,7 @@ class Nautilus(EditorInterface, Shell):
2928
2929
  '* pressed' : (0, clear_autocomp, fork),
2929
2930
  'tab pressed' : (0, clear, skip),
2930
2931
  'enter pressed' : (0, clear, skip),
2931
- 'escape pressed' : (0, clear_autocomp),
2932
+ 'escape pressed' : (0, clear, skip),
2932
2933
  'up pressed' : (3, skip, self.on_completion_backward),
2933
2934
  'down pressed' : (3, skip, self.on_completion_forward),
2934
2935
  '*left pressed' : (3, skip),
@@ -2956,7 +2957,7 @@ class Nautilus(EditorInterface, Shell):
2956
2957
  '* pressed' : (0, clear_autocomp, fork),
2957
2958
  'tab pressed' : (0, clear, skip),
2958
2959
  'enter pressed' : (0, clear, skip),
2959
- 'escape pressed' : (0, clear_autocomp),
2960
+ 'escape pressed' : (0, clear, skip),
2960
2961
  'up pressed' : (4, skip, self.on_completion_backward),
2961
2962
  'down pressed' : (4, skip, self.on_completion_forward),
2962
2963
  '*left pressed' : (4, skip),
@@ -2984,7 +2985,7 @@ class Nautilus(EditorInterface, Shell):
2984
2985
  '* pressed' : (0, clear_autocomp, fork),
2985
2986
  'tab pressed' : (0, clear, skip),
2986
2987
  'enter pressed' : (0, clear, skip),
2987
- 'escape pressed' : (0, clear_autocomp),
2988
+ 'escape pressed' : (0, clear, skip),
2988
2989
  'up pressed' : (5, skip, self.on_completion_backward),
2989
2990
  'down pressed' : (5, skip, self.on_completion_forward),
2990
2991
  '*left pressed' : (5, skip),
@@ -3135,7 +3136,7 @@ class Nautilus(EditorInterface, Shell):
3135
3136
  rst = self.get_style(p)
3136
3137
  if p == self.bolc:
3137
3138
  self.ReplaceSelection('self') # replace [.] --> [self.]
3138
- elif st in ('nil', 'space', 'op', 'sep', 'lparen'):
3139
+ elif st in ('space', 'sep', 'lparen'):
3139
3140
  self.ReplaceSelection('self')
3140
3141
  elif st not in ('moji', 'word', 'rparen') or rst == 'word':
3141
3142
  self.handler('quit', evt) # don't enter autocomp
@@ -3238,16 +3239,21 @@ class Nautilus(EditorInterface, Shell):
3238
3239
  ## func(a,b,c) @debug --> func,a,b,c @debug
3239
3240
  if rhs in ("debug", "profile", "timeit"):
3240
3241
  if lhs[-1] in ')':
3241
- L, R = split_paren(lhs, reverse=1)
3242
+ R = next(split_parts(lhs, reverse=1))
3243
+ L = lhs[:-len(R)]
3242
3244
  if not L:
3243
3245
  lhs = "{!r}".format(R[1:-1])
3244
- elif R:
3246
+ else:
3245
3247
  lhs = "{}, {}".format(L, R[1:-1])
3246
3248
 
3247
3249
  ## @(y1,,,yn) --> @partial(y1,,,yn)
3248
3250
  elif rhs.startswith('('):
3249
3251
  rhs = re.sub(r"^\((.*)\)", r"partial(\1)", rhs, flags=re.S)
3250
3252
 
3253
+ ## obj @.method --> (obj).method
3254
+ elif rhs.startswith('.'):
3255
+ return self.magic_interpret([f"({lhs}){rhs}"] + rest)
3256
+
3251
3257
  return self.magic_interpret([f"{rhs}({lhs})"] + rest)
3252
3258
 
3253
3259
  if c == '`':
@@ -3516,7 +3522,7 @@ class Nautilus(EditorInterface, Shell):
3516
3522
  def write(self, text, pos=None):
3517
3523
  """Display text in the shell.
3518
3524
 
3519
- (override) Append text if it is writable at the position.
3525
+ (override) Append text if it is writable at the given position.
3520
3526
  """
3521
3527
  if pos is not None:
3522
3528
  if pos < 0:
@@ -3603,22 +3609,20 @@ class Nautilus(EditorInterface, Shell):
3603
3609
  ## Autocomp actions of the shell
3604
3610
  ## --------------------------------
3605
3611
 
3606
- def autoCallTipShow(self, command, insertcalltip=True, forceCallTip=False):
3607
- """Display argument spec and docstring in a popup window.
3608
-
3609
- (override) Swap anchors to not scroll to the end of the line.
3610
- """
3611
- Shell.autoCallTipShow(self, command, insertcalltip, forceCallTip)
3612
- self.cpos, self.anchor = self.anchor, self.cpos
3613
- self.EnsureCaretVisible()
3614
-
3615
3612
  def eval_line(self):
3616
3613
  """Evaluate the selected word or line."""
3617
3614
  if self.CallTipActive():
3618
3615
  self.CallTipCancel()
3619
3616
 
3620
- text = self.SelectedText or self.expr_at_caret
3621
- if text:
3617
+ def _gen_text():
3618
+ text = self.SelectedText
3619
+ if text:
3620
+ yield text
3621
+ else:
3622
+ yield self.line_at_caret
3623
+ yield self.expr_at_caret
3624
+
3625
+ for text in _gen_text():
3622
3626
  tokens = split_words(text)
3623
3627
  try:
3624
3628
  cmd = self.magic_interpret(tokens)
@@ -3629,7 +3633,8 @@ class Nautilus(EditorInterface, Shell):
3629
3633
  else:
3630
3634
  self.CallTipShow(self.cpos, pformat(obj))
3631
3635
  self.message(cmd)
3632
- else:
3636
+ return
3637
+ if not text:
3633
3638
  self.message("No words")
3634
3639
 
3635
3640
  def exec_region(self):
mwx/utilus.py CHANGED
@@ -283,24 +283,28 @@ if pp:
283
283
  pp.sort_dicts = True
284
284
 
285
285
 
286
- def split_paren(text, reverse=False):
287
- """Split text into a head parenthesis and the rest, including the tail.
288
- If reverse is True, search from tail to head.
286
+ def split_words(text, reverse=False):
287
+ """Generates words (python phrase) extracted from text.
288
+ If reverse is True, process from tail to head.
289
289
  """
290
290
  tokens = list(split_tokens(text))
291
291
  if reverse:
292
292
  tokens = tokens[::-1]
293
- words = _extract_paren_from_tokens(tokens, reverse)
294
- paren = ''.join(reversed(words) if reverse else words)
295
- rest = ''.join(reversed(tokens) if reverse else tokens)
296
- if reverse:
297
- return rest, paren
298
- else:
299
- return paren, rest
293
+ while tokens:
294
+ words = []
295
+ while 1:
296
+ word = _extract_words_from_tokens(tokens, reverse)
297
+ if not word:
298
+ break
299
+ words += word
300
+ if words:
301
+ yield ''.join(reversed(words) if reverse else words)
302
+ if tokens:
303
+ yield tokens.pop(0) # sep-token
300
304
 
301
305
 
302
- def split_words(text, reverse=False):
303
- """Generates words extracted from text.
306
+ def split_parts(text, reverse=False):
307
+ """Generates portions (words and parens) extracted from text.
304
308
  If reverse is True, process from tail to head.
305
309
  """
306
310
  tokens = list(split_tokens(text))
@@ -345,10 +349,6 @@ def split_tokens(text, comment=True):
345
349
  def _extract_words_from_tokens(tokens, reverse=False):
346
350
  """Extracts pythonic expressions from tokens.
347
351
 
348
- Extraction continues until the parenthesis is closed
349
- and the following token starts with a char in sep, where
350
- the sep includes `@, ops, delims, and whitespaces, etc.
351
-
352
352
  Returns:
353
353
  A token list extracted including the parenthesis.
354
354
  If reverse is True, the order of the tokens will be reversed.
@@ -372,45 +372,16 @@ def _extract_words_from_tokens(tokens, reverse=False):
372
372
  elif not stack and c[0] in sep: # ok; starts with a char in sep
373
373
  break
374
374
  words.append(c)
375
- else: # if stack: error("unclosed-paren")
375
+ if not stack: # ok
376
+ j += 1 # to remove current token
377
+ break
378
+ else:
379
+ ## if stack: error("unclosed-paren")
376
380
  j = None
377
381
  del tokens[:j] # remove extracted tokens (except the last one)
378
382
  return words
379
383
 
380
384
 
381
- def _extract_paren_from_tokens(tokens, reverse=False):
382
- """Extracts parenthesis from tokens.
383
-
384
- The first token must be a parenthesis.
385
- Returns:
386
- A token list extracted including the parenthesis,
387
- or an empty list if the parenthesis is not closed.
388
- If reverse is True, the order of the tokens will be reversed.
389
- """
390
- p, q = "({[", ")}]"
391
- if reverse:
392
- p, q = q, p
393
- stack = []
394
- words = []
395
- for j, c in enumerate(tokens):
396
- if not c:
397
- continue
398
- if c in p:
399
- stack.append(c)
400
- elif c in q:
401
- if not stack: # error("open-paren")
402
- break
403
- if c != q[p.index(stack.pop())]: # error("mismatch-paren")
404
- break
405
- elif j == 0:
406
- break # first char is not paren
407
- words.append(c)
408
- if not stack: # ok
409
- del tokens[:j+1] # remove extracted tokens
410
- return words
411
- return [] # error("unclosed-paren")
412
-
413
-
414
385
  def walk_packages_no_import(path=None, prefix=''):
415
386
  """Yields module info recursively for all submodules on path.
416
387
  If path is None, yields all top-level modules on sys.path.
@@ -979,26 +950,44 @@ class TreeList:
979
950
 
980
951
 
981
952
  def get_fullargspec(f):
982
- argv = []
983
- defaults = {}
984
- varargs = None
985
- varkwargs = None
986
- kwonlyargs = []
987
- kwonlydefaults = {}
953
+ """Get the names and default values of a callable object's parameters.
954
+ If the object is a built-in function, it tries to get argument
955
+ information from the docstring. If it fails, it returns None.
956
+
957
+ Returns:
958
+ args : a list of the parameter names.
959
+ varargs : the name of the * parameter or None.
960
+ varkwargs : the name of the ** parameter or None.
961
+ defaults : a dict mapping names from args to defaults.
962
+ kwonlyargs : a list of keyword-only parameter names.
963
+ kwonlydefaults : a dict mapping names from kwonlyargs to defaults.
964
+
965
+ Note:
966
+ `self` parameter is not reported for bound methods.
967
+
968
+ cf. inspect.getfullargspec
969
+ """
970
+ argv = [] # <before /> 0:POSITIONAL_ONLY
971
+ # <before *> 1:POSITIONAL_OR_KEYWORD
972
+ varargs = None # <*args> 2:VAR_POSITIONAL
973
+ varkwargs = None # <**kwargs> 4:VAR_KEYWORD
974
+ defaults = {} #
975
+ kwonlyargs = [] # <after *> 3:KEYWORD_ONLY
976
+ kwonlydefaults = {} #
988
977
  try:
989
978
  sig = inspect.signature(f)
990
979
  for k, v in sig.parameters.items():
991
- if v.kind <= 1: # POSITIONAL_ONLY / POSITIONAL_OR_KEYWORD
980
+ if v.kind in (v.POSITIONAL_ONLY, v.POSITIONAL_OR_KEYWORD):
992
981
  argv.append(k)
993
982
  if v.default != v.empty:
994
983
  defaults[k] = v.default
995
- elif v.kind == 2: # VAR_POSITIONAL (*args)
984
+ elif v.kind == v.VAR_POSITIONAL:
996
985
  varargs = k
997
- elif v.kind == 3: # KEYWORD_ONLY
986
+ elif v.kind == v.KEYWORD_ONLY:
998
987
  kwonlyargs.append(k)
999
988
  if v.default != v.empty:
1000
989
  kwonlydefaults[k] = v.default
1001
- elif v.kind == 4: # VAR_KEYWORD (**kwargs)
990
+ elif v.kind == v.VAR_KEYWORD:
1002
991
  varkwargs = k
1003
992
  except ValueError:
1004
993
  ## Builtin functions don't have an argspec that we can get.
@@ -1009,19 +998,33 @@ def get_fullargspec(f):
1009
998
  ##
1010
999
  ## ...(details)...
1011
1000
  ## ```
1012
- docs = [ln for ln in inspect.getdoc(f).splitlines() if ln]
1013
- m = re.search(r"(\w+)\((.*)\)", docs[0])
1014
- if m:
1015
- ## Note: Cannot process args containing commas.
1016
- ## e.g., v='Hello, world"
1001
+ doc = inspect.getdoc(f)
1002
+ if doc is None:
1003
+ return None
1004
+ m = re.match(r"(\w+)\s*\((.*?)\)", doc.strip(), re.S)
1005
+ if not m:
1006
+ return None
1007
+ else:
1017
1008
  name, argspec = m.groups()
1018
- for v in argspec.strip().split(','):
1019
- m = re.search(r"(\w+)", v)
1009
+ argparts = ['']
1010
+ for part in split_parts(argspec): # Separate argument parts with commas.
1011
+ if not part.strip():
1012
+ continue
1013
+ if part != ',':
1014
+ argparts[-1] += part
1015
+ else:
1016
+ argparts.append('')
1017
+ for v in argparts:
1018
+ m = re.match(r"(\w+):?", v) # argv + kwonlyargs
1020
1019
  if m:
1021
1020
  argv.append(m.group(1))
1022
- defaults = dict(re.findall(r"(\w+)\s*=\s*([\w' ]+)", argspec))
1023
- else:
1024
- return None
1021
+ m = re.match(r"(\w+)(?::\w+)?=(.+)", v) # defaults + kwonlydefaults
1022
+ if m:
1023
+ defaults.update([m.groups()])
1024
+ elif v.startswith('**'): # <**kwargs>
1025
+ varkwargs = v[2:]
1026
+ elif v.startswith('*'): # <*args>
1027
+ varargs = v[1:]
1025
1028
  return (argv, varargs, varkwargs,
1026
1029
  defaults, kwonlyargs, kwonlydefaults)
1027
1030
 
@@ -1038,10 +1041,10 @@ def funcall(f, *args, doc=None, alias=None, **kwargs):
1038
1041
  >>> Act1 = lambda *v,**kw: f(*(v+args), **(kwargs|kw))
1039
1042
  >>> Act2 = lambda *v,**kw: f(*args, **(kwargs|kw))
1040
1043
 
1041
- Returns Act1 (accepts event arguments) if event arguments
1044
+ `Act1` is returned (accepts event arguments) if event arguments
1042
1045
  cannot be omitted or if there are any remaining arguments
1043
1046
  that must be explicitly specified.
1044
- Otherwise, returns Act2 (ignores event arguments).
1047
+ Otherwise, `Act2` is returned (ignores event arguments).
1045
1048
  """
1046
1049
  assert callable(f)
1047
1050
  assert isinstance(doc, (str, type(None)))
@@ -1062,6 +1065,7 @@ def funcall(f, *args, doc=None, alias=None, **kwargs):
1062
1065
  (argv, varargs, varkwargs, defaults,
1063
1066
  kwonlyargs, kwonlydefaults) = get_fullargspec(f)
1064
1067
  except Exception:
1068
+ warn(f"Failed to get the signature of {f}.")
1065
1069
  return f
1066
1070
  if not varargs:
1067
1071
  N = len(argv)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: mwxlib
3
- Version: 1.4.0
3
+ Version: 1.4.5
4
4
  Summary: A wrapper of matplotlib and wxPython (phoenix)
5
5
  Home-page: https://github.com/komoto48g/mwxlib
6
6
  Author: Kazuya O'moto
@@ -1,16 +1,16 @@
1
1
  mwx/__init__.py,sha256=pS7ZG8QKRypiFFiaWAq_opBB6I_1viZ0zUMk2TbjzE0,667
2
2
  mwx/bookshelf.py,sha256=MXjrm_ryRGHvOFKu8wb10qz_oka70xW2yFbYjBMYfns,8140
3
3
  mwx/controls.py,sha256=RssTROprNfgnRMiWVOoSSd8Fy0qwZ8DCrYM2f5Wgr4E,48610
4
- mwx/framework.py,sha256=X7zjAxKXIYMbaXU7g1q2rfRFsnCh2bNiA8sMzEJN_8M,76796
5
- mwx/graphman.py,sha256=ICZ-3jtGEGcPR0zoHYgIVW3FM3UgIVHJslGjvlhAlqI,70648
4
+ mwx/framework.py,sha256=DO2VrF_SOS6Gdr22PZclHChWr2oRQfW__7-1xxr97Cc,76830
5
+ mwx/graphman.py,sha256=_8_DaG3qhhZByoh5wboyYQlAsm7GlPoR8wT-RNMx4Cw,70627
6
6
  mwx/images.py,sha256=oxCn0P-emiWujSS2gUgU5TUnr5cPjix2jBcjOBDr24I,48701
7
7
  mwx/matplot2.py,sha256=Htwegq6N5G7oKSRCuajik5Dixd93i8PKVbkL7Azy99M,33150
8
- mwx/matplot2g.py,sha256=H1PeLJk5P0KKMT6tmocpli5RSPNjnteA5GhbJTKEqIg,64869
8
+ mwx/matplot2g.py,sha256=4BBHlvfhg1796hu1nTCsTA82YRkZhwUnfcHCBf5ZGO8,65083
9
9
  mwx/matplot2lg.py,sha256=cb0EZXivccDQu4oFj5ddSUF9pEE4f5UuFJJK2ELItww,27404
10
10
  mwx/mgplt.py,sha256=8mXbHpCmm7lz3XbAxOg7IVC7DaSGBEby1UfTlMl9kjk,5604
11
- mwx/nutshell.py,sha256=OWCSRtghve7-95HmITPqVQSIhZ8ynn-ogT3x1ArF4-M,142457
11
+ mwx/nutshell.py,sha256=4GDfGCHvHptpLqJIUVAfCMyI6V2KFcgji8S0PWMviL0,142385
12
12
  mwx/testsuite.py,sha256=Zk75onPSEn2tf0swS3l-vIn6yTXGB7allIyvJsPHj20,1229
13
- mwx/utilus.py,sha256=bDeooo2bOcZwvkIdi0ElkT-qoblqzHNFsIveb72NFOo,37528
13
+ mwx/utilus.py,sha256=iAqtDbkVuFsSuS71leUd-mHH0ETRQ3GLnII7O2wcw3I,38065
14
14
  mwx/wxmon.py,sha256=yzWqrbY6LzpfRwQeytYUeqFhFuLVm_XEvrVAL_k0HBQ,12756
15
15
  mwx/wxpdb.py,sha256=ih2iLcOgYnUX849YXO4niIYue6amuoG7nWdpr-X1jFw,18839
16
16
  mwx/wxwil.py,sha256=hhyB1lPrF9ixeObxCOKQv0Theu-B-kpJg_yVU3EGSNg,5406
@@ -22,7 +22,7 @@ mwx/plugins/frame_listview.py,sha256=gowjQ-ARNonMkDSXkQgPKq4U9YBJ-vQ0jK2krBVOdCs
22
22
  mwx/plugins/line_profile.py,sha256=zzm6_7lnAnNepLbh07ordp3nRWDFQJtu719ZVjrVf8s,819
23
23
  mwx/py/__init__.py,sha256=xykgfOytOwNuvXsfkLoumFZSTN-iBsHOjczYXngjmUE,12
24
24
  mwx/py/filling.py,sha256=fumUG1F5M9TL-Dfqni4G85uk7TmvnUunTbdcPDV0vfo,16857
25
- mwxlib-1.4.0.dist-info/METADATA,sha256=F1uYpz9nBr7BREiBeITTadez9ugPpmwrw9L1S6Cdz_c,7433
26
- mwxlib-1.4.0.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
27
- mwxlib-1.4.0.dist-info/top_level.txt,sha256=SI1Mh118AstnUFGPNq5aMNKiAnVNmZk1S9Ij-OwAEpY,4
28
- mwxlib-1.4.0.dist-info/RECORD,,
25
+ mwxlib-1.4.5.dist-info/METADATA,sha256=004S_ou1NJHL8BynaX391SwQ5vamthk4oifMNz8yUrE,7433
26
+ mwxlib-1.4.5.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
27
+ mwxlib-1.4.5.dist-info/top_level.txt,sha256=SI1Mh118AstnUFGPNq5aMNKiAnVNmZk1S9Ij-OwAEpY,4
28
+ mwxlib-1.4.5.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.2)
2
+ Generator: setuptools (76.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5