mwxlib 1.4.0__py3-none-any.whl → 1.4.6__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.6"
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,7 +28,7 @@ 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
 
@@ -210,8 +210,8 @@ class AutoCompInterfaceMixin:
210
210
  dispatcher.send(signal='Shell.calltip', sender=self, calltip=tip)
211
211
  p = self.cpos
212
212
  if argspec and insertcalltip:
213
- self.AddText(argspec + ')') # 挿入後のカーソル位置は変化しない
214
- self.SetSelection(self.cpos, p) # selection back
213
+ self.AddText(argspec + ')')
214
+ self.cpos = p # selection backward to the point
215
215
  if tip:
216
216
  ## In case there isn't enough room, only go back to bol fallback.
217
217
  tippos = max(self.bol, p - len(name) - 1)
@@ -269,7 +269,7 @@ class AutoCompInterfaceMixin:
269
269
  self.anchor = q
270
270
  with self.off_undocollection():
271
271
  self.ReplaceSelection(word[n:])
272
- self.cpos = p # backward selection to the point
272
+ self.cpos = p # selection backward to the point
273
273
  self.__comp_ind = j
274
274
  except IndexError:
275
275
  self.message("No completion words")
@@ -641,8 +641,8 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
641
641
  self.IndicatorSetStyle(11, stc.STC_INDIC_STRAIGHTBOX)
642
642
  self.IndicatorSetForeground(11, "yellow")
643
643
  self.IndicatorSetUnder(11, True)
644
- self.IndicatorSetAlpha(11, 0xe8)
645
- self.IndicatorSetOutlineAlpha(11, 0)
644
+ ## self.IndicatorSetAlpha(11, 0xe8)
645
+ ## self.IndicatorSetOutlineAlpha(11, 0)
646
646
 
647
647
  self.IndicatorSetStyle(2, stc.STC_INDIC_DOTS)
648
648
  self.IndicatorSetForeground(2, "light gray")
@@ -863,7 +863,7 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
863
863
  return sty
864
864
 
865
865
  def get_char(self, pos):
866
- """Returns the character at the position."""
866
+ """Returns the character at the given position."""
867
867
  return chr(self.GetCharAt(pos))
868
868
 
869
869
  def get_text(self, start, end):
@@ -944,10 +944,12 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
944
944
  return topic
945
945
  with self.save_excursion():
946
946
  p = q = self.cpos
947
- if self.get_char(p-1).isalnum():
947
+ ## if self.get_char(p-1).isidentifier():
948
+ if self.GetTextRange(self.PositionBefore(p), p).isidentifier():
948
949
  self.WordLeft()
949
950
  p = self.cpos
950
- if self.get_char(q).isalnum():
951
+ ## if self.get_char(q).isidentifier():
952
+ if self.GetTextRange(q, self.PositionAfter(q)).isidentifier():
951
953
  self.WordRightEnd()
952
954
  q = self.cpos
953
955
  return self.GetTextRange(p, q)
@@ -1376,32 +1378,34 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
1376
1378
  def grep(self, pattern, flags=re.M):
1377
1379
  yield from re.finditer(pattern.encode(), self.TextRaw, flags)
1378
1380
 
1379
- def search_text(self, text):
1380
- """Yields raw-positions where `text` is found."""
1381
- word = text.encode()
1382
- raw = self.TextRaw
1381
+ def search_text(self, text, mode=True):
1382
+ """Yields positions where `text` is found.
1383
+ If mode is True, search by word; otherwise, search by string.
1384
+ """
1385
+ text = text.encode()
1383
1386
  pos = -1
1387
+ p = re.compile(r"[a-zA-Z0-9_]")
1384
1388
  while 1:
1385
- pos = raw.find(word, pos+1)
1389
+ pos = self.TextRaw.find(text, pos+1)
1386
1390
  if pos < 0:
1387
1391
  break
1392
+ if mode and p.search(self.get_char(pos-1) + self.get_char(pos+len(text))):
1393
+ continue
1388
1394
  yield pos
1389
1395
 
1390
- def filter_text(self, text=None):
1396
+ def filter_text(self):
1391
1397
  """Show indicators for the selected text."""
1392
1398
  self.__itextlines = []
1393
1399
  for i in (10, 11,):
1394
1400
  self.SetIndicatorCurrent(i)
1395
1401
  self.IndicatorClearRange(0, self.TextLength)
1396
- if text is None:
1397
- text = self.topic_at_caret
1402
+ text = self.topic_at_caret
1398
1403
  if not text:
1399
1404
  self.message("No words")
1400
1405
  return
1401
-
1402
1406
  lw = len(text.encode()) # for multi-byte string
1403
1407
  lines = []
1404
- for p in self.search_text(text):
1408
+ for p in self.search_text(text, mode=(not self.SelectedText)):
1405
1409
  lines.append(self.LineFromPosition(p))
1406
1410
  for i in (10, 11,):
1407
1411
  self.SetIndicatorCurrent(i)
@@ -1833,12 +1837,8 @@ class Buffer(EditorInterface, EditWindow):
1833
1837
  self.message("")
1834
1838
 
1835
1839
  def clear_autocomp(evt):
1836
- ## """Clear autocomp, selection, and message."""
1837
1840
  if self.AutoCompActive():
1838
1841
  self.AutoCompCancel()
1839
- if self.CanEdit():
1840
- with self.off_undocollection():
1841
- self.ReplaceSelection("")
1842
1842
  self.message("")
1843
1843
 
1844
1844
  def fork(evt):
@@ -1882,7 +1882,7 @@ class Buffer(EditorInterface, EditWindow):
1882
1882
  '* pressed' : (0, clear_autocomp, fork),
1883
1883
  'tab pressed' : (0, clear, skip),
1884
1884
  'enter pressed' : (0, clear, skip),
1885
- 'escape pressed' : (0, clear_autocomp),
1885
+ 'escape pressed' : (0, clear, skip),
1886
1886
  'up pressed' : (2, skip, self.on_completion_backward),
1887
1887
  'down pressed' : (2, skip, self.on_completion_forward),
1888
1888
  '*left pressed' : (2, skip),
@@ -1909,7 +1909,7 @@ class Buffer(EditorInterface, EditWindow):
1909
1909
  '* pressed' : (0, clear_autocomp, fork),
1910
1910
  'tab pressed' : (0, clear, skip),
1911
1911
  'enter pressed' : (0, clear, skip),
1912
- 'escape pressed' : (0, clear_autocomp),
1912
+ 'escape pressed' : (0, clear, skip),
1913
1913
  'up pressed' : (3, skip, self.on_completion_backward),
1914
1914
  'down pressed' : (3, skip, self.on_completion_forward),
1915
1915
  '*left pressed' : (3, skip),
@@ -2099,8 +2099,15 @@ class Buffer(EditorInterface, EditWindow):
2099
2099
  if self.CallTipActive():
2100
2100
  self.CallTipCancel()
2101
2101
 
2102
- text = self.SelectedText or self.expr_at_caret
2103
- if text:
2102
+ def _gen_text():
2103
+ text = self.SelectedText
2104
+ if text:
2105
+ yield text
2106
+ else:
2107
+ yield self.line_at_caret
2108
+ yield self.expr_at_caret
2109
+
2110
+ for text in _gen_text():
2104
2111
  try:
2105
2112
  obj = eval(text, self.globals, self.locals)
2106
2113
  except Exception as e:
@@ -2108,7 +2115,8 @@ class Buffer(EditorInterface, EditWindow):
2108
2115
  else:
2109
2116
  self.CallTipShow(self.cpos, pformat(obj))
2110
2117
  self.message(text)
2111
- else:
2118
+ return
2119
+ if not text:
2112
2120
  self.message("No words")
2113
2121
 
2114
2122
  def exec_region(self):
@@ -2610,22 +2618,6 @@ class Interpreter(interpreter.Interpreter):
2610
2618
  self.parent.handler('interp_error', v)
2611
2619
  except AttributeError:
2612
2620
  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
2621
 
2630
2622
 
2631
2623
  class Nautilus(EditorInterface, Shell):
@@ -2796,12 +2788,8 @@ class Nautilus(EditorInterface, Shell):
2796
2788
  self.message("")
2797
2789
 
2798
2790
  def clear_autocomp(evt):
2799
- ## """Clear autocomp, selection, and message."""
2800
2791
  if self.AutoCompActive():
2801
2792
  self.AutoCompCancel()
2802
- if self.CanEdit():
2803
- with self.off_undocollection():
2804
- self.ReplaceSelection("")
2805
2793
  self.message("")
2806
2794
 
2807
2795
  def fork(evt):
@@ -2900,7 +2888,7 @@ class Nautilus(EditorInterface, Shell):
2900
2888
  '* pressed' : (0, clear_autocomp, fork),
2901
2889
  'tab pressed' : (0, clear, skip),
2902
2890
  'enter pressed' : (0, clear, skip),
2903
- 'escape pressed' : (0, clear_autocomp),
2891
+ 'escape pressed' : (0, clear, skip),
2904
2892
  'up pressed' : (2, skip, self.on_completion_backward),
2905
2893
  'down pressed' : (2, skip, self.on_completion_forward),
2906
2894
  '*left pressed' : (2, skip),
@@ -2928,7 +2916,7 @@ class Nautilus(EditorInterface, Shell):
2928
2916
  '* pressed' : (0, clear_autocomp, fork),
2929
2917
  'tab pressed' : (0, clear, skip),
2930
2918
  'enter pressed' : (0, clear, skip),
2931
- 'escape pressed' : (0, clear_autocomp),
2919
+ 'escape pressed' : (0, clear, skip),
2932
2920
  'up pressed' : (3, skip, self.on_completion_backward),
2933
2921
  'down pressed' : (3, skip, self.on_completion_forward),
2934
2922
  '*left pressed' : (3, skip),
@@ -2956,7 +2944,7 @@ class Nautilus(EditorInterface, Shell):
2956
2944
  '* pressed' : (0, clear_autocomp, fork),
2957
2945
  'tab pressed' : (0, clear, skip),
2958
2946
  'enter pressed' : (0, clear, skip),
2959
- 'escape pressed' : (0, clear_autocomp),
2947
+ 'escape pressed' : (0, clear, skip),
2960
2948
  'up pressed' : (4, skip, self.on_completion_backward),
2961
2949
  'down pressed' : (4, skip, self.on_completion_forward),
2962
2950
  '*left pressed' : (4, skip),
@@ -2984,7 +2972,7 @@ class Nautilus(EditorInterface, Shell):
2984
2972
  '* pressed' : (0, clear_autocomp, fork),
2985
2973
  'tab pressed' : (0, clear, skip),
2986
2974
  'enter pressed' : (0, clear, skip),
2987
- 'escape pressed' : (0, clear_autocomp),
2975
+ 'escape pressed' : (0, clear, skip),
2988
2976
  'up pressed' : (5, skip, self.on_completion_backward),
2989
2977
  'down pressed' : (5, skip, self.on_completion_forward),
2990
2978
  '*left pressed' : (5, skip),
@@ -3135,7 +3123,7 @@ class Nautilus(EditorInterface, Shell):
3135
3123
  rst = self.get_style(p)
3136
3124
  if p == self.bolc:
3137
3125
  self.ReplaceSelection('self') # replace [.] --> [self.]
3138
- elif st in ('nil', 'space', 'op', 'sep', 'lparen'):
3126
+ elif st in ('space', 'sep', 'lparen'):
3139
3127
  self.ReplaceSelection('self')
3140
3128
  elif st not in ('moji', 'word', 'rparen') or rst == 'word':
3141
3129
  self.handler('quit', evt) # don't enter autocomp
@@ -3238,16 +3226,21 @@ class Nautilus(EditorInterface, Shell):
3238
3226
  ## func(a,b,c) @debug --> func,a,b,c @debug
3239
3227
  if rhs in ("debug", "profile", "timeit"):
3240
3228
  if lhs[-1] in ')':
3241
- L, R = split_paren(lhs, reverse=1)
3229
+ R = next(split_parts(lhs, reverse=1))
3230
+ L = lhs[:-len(R)]
3242
3231
  if not L:
3243
3232
  lhs = "{!r}".format(R[1:-1])
3244
- elif R:
3233
+ else:
3245
3234
  lhs = "{}, {}".format(L, R[1:-1])
3246
3235
 
3247
3236
  ## @(y1,,,yn) --> @partial(y1,,,yn)
3248
3237
  elif rhs.startswith('('):
3249
3238
  rhs = re.sub(r"^\((.*)\)", r"partial(\1)", rhs, flags=re.S)
3250
3239
 
3240
+ ## obj @.method --> (obj).method
3241
+ elif rhs.startswith('.'):
3242
+ return self.magic_interpret([f"({lhs}){rhs}"] + rest)
3243
+
3251
3244
  return self.magic_interpret([f"{rhs}({lhs})"] + rest)
3252
3245
 
3253
3246
  if c == '`':
@@ -3516,7 +3509,7 @@ class Nautilus(EditorInterface, Shell):
3516
3509
  def write(self, text, pos=None):
3517
3510
  """Display text in the shell.
3518
3511
 
3519
- (override) Append text if it is writable at the position.
3512
+ (override) Append text if it is writable at the given position.
3520
3513
  """
3521
3514
  if pos is not None:
3522
3515
  if pos < 0:
@@ -3603,22 +3596,20 @@ class Nautilus(EditorInterface, Shell):
3603
3596
  ## Autocomp actions of the shell
3604
3597
  ## --------------------------------
3605
3598
 
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
3599
  def eval_line(self):
3616
3600
  """Evaluate the selected word or line."""
3617
3601
  if self.CallTipActive():
3618
3602
  self.CallTipCancel()
3619
3603
 
3620
- text = self.SelectedText or self.expr_at_caret
3621
- if text:
3604
+ def _gen_text():
3605
+ text = self.SelectedText
3606
+ if text:
3607
+ yield text
3608
+ else:
3609
+ yield self.line_at_caret
3610
+ yield self.expr_at_caret
3611
+
3612
+ for text in _gen_text():
3622
3613
  tokens = split_words(text)
3623
3614
  try:
3624
3615
  cmd = self.magic_interpret(tokens)
@@ -3629,7 +3620,8 @@ class Nautilus(EditorInterface, Shell):
3629
3620
  else:
3630
3621
  self.CallTipShow(self.cpos, pformat(obj))
3631
3622
  self.message(cmd)
3632
- else:
3623
+ return
3624
+ if not text:
3633
3625
  self.message("No words")
3634
3626
 
3635
3627
  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"
1017
- name, argspec = m.groups()
1018
- for v in argspec.strip().split(','):
1019
- m = re.search(r"(\w+)", v)
1001
+ doc = inspect.getdoc(f)
1002
+ for word in split_parts(doc or ''): # Search pattern for `func(argspec)`.
1003
+ if word.startswith('('):
1004
+ argspec = word[1:-1]
1005
+ break
1006
+ else:
1007
+ return None # no argument spec information
1008
+ if argspec:
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.6
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=hP2mhnYPOrSVFvA3CgoOR9-axWx_WLd83aXioUHodEk,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=TNaZbMQcvSKDSHDnw3P8rpMM2yNG0HbVOej52L0EZgY,141903
12
12
  mwx/testsuite.py,sha256=Zk75onPSEn2tf0swS3l-vIn6yTXGB7allIyvJsPHj20,1229
13
- mwx/utilus.py,sha256=bDeooo2bOcZwvkIdi0ElkT-qoblqzHNFsIveb72NFOo,37528
13
+ mwx/utilus.py,sha256=ObXYWsDVn5DmjvwVQgP2HGAhrdxlDrwL7g9pMCDbU7U,38127
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.6.dist-info/METADATA,sha256=hqiNPf-eNSU1H6H5huSCCToKcRvpXYWipmwzJc1r_xw,7433
26
+ mwxlib-1.4.6.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
27
+ mwxlib-1.4.6.dist-info/top_level.txt,sha256=SI1Mh118AstnUFGPNq5aMNKiAnVNmZk1S9Ij-OwAEpY,4
28
+ mwxlib-1.4.6.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