visidata 2.11.dev0__py3-none-any.whl → 3.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.
Files changed (253) hide show
  1. visidata/__init__.py +72 -91
  2. visidata/_input.py +263 -44
  3. visidata/_open.py +84 -29
  4. visidata/_types.py +22 -4
  5. visidata/_urlcache.py +17 -4
  6. visidata/aggregators.py +65 -25
  7. visidata/apps/__init__.py +0 -0
  8. visidata/apps/vdsql/__about__.py +8 -0
  9. visidata/apps/vdsql/__init__.py +5 -0
  10. visidata/apps/vdsql/__main__.py +27 -0
  11. visidata/apps/vdsql/_ibis.py +748 -0
  12. visidata/apps/vdsql/bigquery.py +61 -0
  13. visidata/apps/vdsql/clickhouse.py +53 -0
  14. visidata/apps/vdsql/setup.py +40 -0
  15. visidata/apps/vdsql/snowflake.py +67 -0
  16. visidata/apps/vgit/__init__.py +13 -0
  17. visidata/apps/vgit/__main__.py +3 -0
  18. visidata/apps/vgit/abort.py +23 -0
  19. visidata/apps/vgit/blame.py +76 -0
  20. visidata/apps/vgit/branch.py +153 -0
  21. visidata/apps/vgit/config.py +95 -0
  22. visidata/apps/vgit/diff.py +169 -0
  23. visidata/apps/vgit/gitsheet.py +161 -0
  24. visidata/apps/vgit/grep.py +37 -0
  25. visidata/apps/vgit/log.py +81 -0
  26. visidata/apps/vgit/main.py +55 -0
  27. visidata/apps/vgit/remote.py +57 -0
  28. visidata/apps/vgit/repos.py +71 -0
  29. visidata/apps/vgit/setup.py +37 -0
  30. visidata/apps/vgit/stash.py +69 -0
  31. visidata/apps/vgit/status.py +204 -0
  32. visidata/apps/vgit/statusbar.py +34 -0
  33. visidata/basesheet.py +59 -50
  34. visidata/canvas.py +251 -99
  35. visidata/choose.py +15 -11
  36. visidata/clean_names.py +29 -0
  37. visidata/clipboard.py +84 -18
  38. visidata/cliptext.py +220 -46
  39. visidata/cmdlog.py +89 -114
  40. visidata/color.py +142 -56
  41. visidata/column.py +134 -131
  42. visidata/ddw/input.ddw +74 -79
  43. visidata/ddw/regex.ddw +57 -0
  44. visidata/ddwplay.py +33 -14
  45. visidata/deprecated.py +77 -3
  46. visidata/desktop/visidata.desktop +7 -0
  47. visidata/editor.py +12 -6
  48. visidata/errors.py +5 -1
  49. visidata/experimental/__init__.py +0 -0
  50. visidata/experimental/diff_sheet.py +29 -0
  51. visidata/experimental/digit_autoedit.py +6 -0
  52. visidata/experimental/gdrive.py +89 -0
  53. visidata/experimental/google.py +37 -0
  54. visidata/experimental/gsheets.py +79 -0
  55. visidata/experimental/live_search.py +37 -0
  56. visidata/experimental/liveupdate.py +45 -0
  57. visidata/experimental/mark.py +133 -0
  58. visidata/experimental/noahs_tapestry/__init__.py +1 -0
  59. visidata/experimental/noahs_tapestry/tapestry.py +147 -0
  60. visidata/experimental/rownum.py +73 -0
  61. visidata/experimental/slide_cells.py +26 -0
  62. visidata/expr.py +8 -4
  63. visidata/extensible.py +32 -6
  64. visidata/features/__init__.py +0 -0
  65. visidata/features/addcol_audiometadata.py +42 -0
  66. visidata/features/addcol_histogram.py +34 -0
  67. visidata/features/canvas_save_svg.py +69 -0
  68. visidata/features/change_precision.py +46 -0
  69. visidata/features/cmdpalette.py +163 -0
  70. visidata/features/colorbrewer.py +363 -0
  71. visidata/{colorsheet.py → features/colorsheet.py} +17 -16
  72. visidata/features/command_server.py +105 -0
  73. visidata/features/currency_to_usd.py +70 -0
  74. visidata/{customdate.py → features/customdate.py} +2 -0
  75. visidata/features/dedupe.py +132 -0
  76. visidata/{describe.py → features/describe.py} +17 -15
  77. visidata/features/errors_guide.py +26 -0
  78. visidata/features/expand_cols.py +202 -0
  79. visidata/{fill.py → features/fill.py} +4 -2
  80. visidata/{freeze.py → features/freeze.py} +11 -6
  81. visidata/features/graph_seaborn.py +79 -0
  82. visidata/features/helloworld.py +10 -0
  83. visidata/features/hint_types.py +17 -0
  84. visidata/{incr.py → features/incr.py} +5 -0
  85. visidata/{join.py → features/join.py} +107 -53
  86. visidata/features/known_cols.py +21 -0
  87. visidata/features/layout.py +62 -0
  88. visidata/{melt.py → features/melt.py} +33 -21
  89. visidata/features/normcol.py +118 -0
  90. visidata/features/open_config.py +7 -0
  91. visidata/features/open_syspaste.py +18 -0
  92. visidata/features/ping.py +157 -0
  93. visidata/features/procmgr.py +208 -0
  94. visidata/features/random_sample.py +6 -0
  95. visidata/{regex.py → features/regex.py} +47 -31
  96. visidata/features/reload_every.py +55 -0
  97. visidata/features/rename_col_cascade.py +30 -0
  98. visidata/features/scroll_context.py +60 -0
  99. visidata/features/select_equal_selected.py +11 -0
  100. visidata/features/setcol_fake.py +65 -0
  101. visidata/{slide.py → features/slide.py} +75 -21
  102. visidata/features/sparkline.py +48 -0
  103. visidata/features/status_source.py +20 -0
  104. visidata/{sysedit.py → features/sysedit.py} +2 -1
  105. visidata/features/sysopen_mailcap.py +46 -0
  106. visidata/features/term_extras.py +13 -0
  107. visidata/{transpose.py → features/transpose.py} +5 -4
  108. visidata/features/type_ipaddr.py +73 -0
  109. visidata/features/type_url.py +11 -0
  110. visidata/{unfurl.py → features/unfurl.py} +9 -9
  111. visidata/{window.py → features/window.py} +2 -2
  112. visidata/form.py +50 -21
  113. visidata/freqtbl.py +81 -33
  114. visidata/fuzzymatch.py +414 -0
  115. visidata/graph.py +105 -33
  116. visidata/guide.py +180 -0
  117. visidata/help.py +75 -44
  118. visidata/hint.py +39 -0
  119. visidata/indexsheet.py +109 -0
  120. visidata/input_history.py +55 -0
  121. visidata/interface.py +58 -0
  122. visidata/keys.py +17 -16
  123. visidata/loaders/__init__.py +9 -0
  124. visidata/loaders/_pandas.py +61 -21
  125. visidata/loaders/api_airtable.py +70 -0
  126. visidata/loaders/api_bitio.py +102 -0
  127. visidata/loaders/api_matrix.py +148 -0
  128. visidata/loaders/api_reddit.py +306 -0
  129. visidata/loaders/api_zulip.py +249 -0
  130. visidata/loaders/archive.py +41 -7
  131. visidata/loaders/arrow.py +7 -7
  132. visidata/loaders/conll.py +49 -0
  133. visidata/loaders/csv.py +25 -7
  134. visidata/loaders/eml.py +3 -4
  135. visidata/loaders/f5log.py +1204 -0
  136. visidata/loaders/fec.py +325 -0
  137. visidata/loaders/fixed_width.py +3 -5
  138. visidata/loaders/frictionless.py +3 -3
  139. visidata/loaders/geojson.py +8 -5
  140. visidata/loaders/google.py +48 -0
  141. visidata/loaders/graphviz.py +4 -4
  142. visidata/loaders/hdf5.py +4 -4
  143. visidata/loaders/html.py +48 -10
  144. visidata/loaders/http.py +84 -30
  145. visidata/loaders/imap.py +20 -10
  146. visidata/loaders/jrnl.py +52 -0
  147. visidata/loaders/json.py +83 -29
  148. visidata/loaders/jsonla.py +74 -0
  149. visidata/loaders/lsv.py +15 -11
  150. visidata/loaders/mailbox.py +40 -0
  151. visidata/loaders/markdown.py +1 -3
  152. visidata/loaders/mbtiles.py +4 -5
  153. visidata/loaders/mysql.py +11 -13
  154. visidata/loaders/npy.py +7 -7
  155. visidata/loaders/odf.py +4 -1
  156. visidata/loaders/orgmode.py +428 -0
  157. visidata/loaders/pandas_freqtbl.py +14 -20
  158. visidata/loaders/parquet.py +62 -6
  159. visidata/loaders/pcap.py +3 -3
  160. visidata/loaders/pdf.py +4 -3
  161. visidata/loaders/png.py +19 -13
  162. visidata/loaders/postgres.py +9 -8
  163. visidata/loaders/rec.py +7 -3
  164. visidata/loaders/s3.py +342 -0
  165. visidata/loaders/sas.py +5 -5
  166. visidata/loaders/scrape.py +186 -0
  167. visidata/loaders/shp.py +6 -5
  168. visidata/loaders/spss.py +5 -6
  169. visidata/loaders/sqlite.py +68 -28
  170. visidata/loaders/texttables.py +1 -1
  171. visidata/loaders/toml.py +60 -0
  172. visidata/loaders/tsv.py +61 -19
  173. visidata/loaders/ttf.py +19 -7
  174. visidata/loaders/unzip_http.py +6 -5
  175. visidata/loaders/usv.py +1 -1
  176. visidata/loaders/vcf.py +16 -16
  177. visidata/loaders/vds.py +10 -7
  178. visidata/loaders/vdx.py +30 -5
  179. visidata/loaders/xlsb.py +8 -1
  180. visidata/loaders/xlsx.py +145 -25
  181. visidata/loaders/xml.py +6 -3
  182. visidata/loaders/xword.py +4 -4
  183. visidata/loaders/yaml.py +15 -5
  184. visidata/macos.py +1 -1
  185. visidata/macros.py +130 -41
  186. visidata/main.py +119 -94
  187. visidata/mainloop.py +101 -154
  188. visidata/man/parse_options.py +2 -2
  189. visidata/man/vd.1 +302 -147
  190. visidata/man/vd.txt +291 -151
  191. visidata/memory.py +3 -3
  192. visidata/menu.py +104 -423
  193. visidata/metasheets.py +59 -141
  194. visidata/modify.py +79 -23
  195. visidata/motd.py +3 -3
  196. visidata/mouse.py +137 -0
  197. visidata/movement.py +43 -35
  198. visidata/optionssheet.py +99 -0
  199. visidata/path.py +131 -43
  200. visidata/pivot.py +74 -47
  201. visidata/plugins.py +65 -192
  202. visidata/pyobj.py +50 -201
  203. visidata/rename_col.py +20 -0
  204. visidata/save.py +42 -20
  205. visidata/search.py +54 -10
  206. visidata/selection.py +84 -5
  207. visidata/settings.py +162 -24
  208. visidata/sheets.py +229 -257
  209. visidata/shell.py +51 -21
  210. visidata/sidebar.py +162 -0
  211. visidata/sort.py +11 -4
  212. visidata/statusbar.py +113 -104
  213. visidata/stored_list.py +43 -0
  214. visidata/stored_prop.py +38 -0
  215. visidata/tests/conftest.py +3 -3
  216. visidata/tests/test_cliptext.py +39 -0
  217. visidata/tests/test_commands.py +62 -7
  218. visidata/tests/test_edittext.py +2 -2
  219. visidata/tests/test_features.py +17 -0
  220. visidata/tests/test_menu.py +14 -0
  221. visidata/tests/test_path.py +13 -4
  222. visidata/text_source.py +53 -0
  223. visidata/textsheet.py +10 -3
  224. visidata/theme.py +44 -0
  225. visidata/themes/__init__.py +0 -0
  226. visidata/themes/ascii8.py +84 -0
  227. visidata/themes/asciimono.py +84 -0
  228. visidata/themes/light.py +17 -0
  229. visidata/threads.py +87 -39
  230. visidata/tuiwin.py +22 -0
  231. visidata/type_currency.py +22 -3
  232. visidata/type_date.py +31 -9
  233. visidata/type_floatsi.py +5 -1
  234. visidata/undo.py +18 -6
  235. visidata/utils.py +106 -23
  236. visidata/vdobj.py +28 -17
  237. visidata/windows.py +10 -0
  238. visidata/wrappers.py +9 -3
  239. visidata-3.0.data/data/share/applications/visidata.desktop +7 -0
  240. {visidata-2.11.dev0.data → visidata-3.0.data}/data/share/man/man1/vd.1 +302 -147
  241. {visidata-2.11.dev0.data → visidata-3.0.data}/data/share/man/man1/visidata.1 +302 -147
  242. visidata-3.0.data/scripts/vd2to3.vdx +9 -0
  243. {visidata-2.11.dev0.dist-info → visidata-3.0.dist-info}/METADATA +13 -11
  244. visidata-3.0.dist-info/RECORD +257 -0
  245. {visidata-2.11.dev0.dist-info → visidata-3.0.dist-info}/WHEEL +1 -1
  246. {visidata-2.11.dev0.dist-info → visidata-3.0.dist-info}/entry_points.txt +0 -1
  247. visidata/layout.py +0 -44
  248. visidata/misc.py +0 -5
  249. visidata-2.11.dev0.dist-info/RECORD +0 -142
  250. /visidata/{repeat.py → features/repeat.py} +0 -0
  251. {visidata-2.11.dev0.data → visidata-3.0.data}/scripts/vd +0 -0
  252. {visidata-2.11.dev0.dist-info → visidata-3.0.dist-info}/LICENSE.gpl3 +0 -0
  253. {visidata-2.11.dev0.dist-info → visidata-3.0.dist-info}/top_level.txt +0 -0
visidata/expr.py CHANGED
@@ -38,10 +38,8 @@ def setValuesFromExpr(self, rows, expr):
38
38
  for row in Progress(rows, 'setting'):
39
39
  # Note: expressions that are only calculated once, do not need to pass column identity
40
40
  # they can reference their "previous selves" once without causing a recursive problem
41
- try:
42
- self.setValueSafe(row, self.sheet.evalExpr(compiledExpr, row))
43
- except Exception as e:
44
- vd.exceptionCaught(e)
41
+ v = vd.callNoExceptions(self.sheet.evalExpr, compiledExpr, row)
42
+ vd.callNoExceptions(self.setValue, row, v)
45
43
  self.recalc()
46
44
  vd.status('set %d values = %s' % (len(rows), expr))
47
45
 
@@ -59,3 +57,9 @@ Sheet.addCommand('gz=', 'setcol-iter', 'cursorCol.setValues(someSelectedRows, *l
59
57
  Sheet.addCommand(None, 'show-expr', 'status(evalExpr(inputExpr("show expr="), cursorRow))', 'evaluate Python expression on current row and show result on status line')
60
58
 
61
59
  vd.addGlobals({'CompleteExpr': CompleteExpr})
60
+
61
+ vd.addMenuItems('''
62
+ Edit > Modify > current cell > Python expression > setcell-expr
63
+ Edit > Modify > selected cells > Python sequence > setcol-expr
64
+ Column > Add column > Python expr > addcol-expr
65
+ ''')
visidata/extensible.py CHANGED
@@ -8,7 +8,7 @@ class Extensible:
8
8
 
9
9
  @classmethod
10
10
  def init(cls, membername, initfunc=lambda: None, copy=False):
11
- 'Append equivalent of ``self.<membername> = initfunc()`` to ``<cls>.__init__``. If *copy* is True, <membername> will be copied when object is copied.'
11
+ 'Prepend equivalent of ``self.<membername> = initfunc()`` to ``<cls>.__init__``. If *copy* is True, <membername> will be copied when object is copied.'
12
12
 
13
13
  def thisclass_hasattr(cls, k):
14
14
  return getattr(cls, k, None) is not getattr(cls.__bases__[0], k, None)
@@ -16,12 +16,12 @@ class Extensible:
16
16
  # must check hasattr first or else this might be parent's __init__
17
17
  oldinit = thisclass_hasattr(cls, '__init__') and getattr(cls, '__init__')
18
18
  def newinit(self, *args, **kwargs):
19
+ if not hasattr(self, membername): # can be overridden by a subclass
20
+ setattr(self, membername, initfunc())
19
21
  if oldinit:
20
22
  oldinit(self, *args, **kwargs)
21
23
  else:
22
24
  super(cls, self).__init__(*args, **kwargs)
23
- if not hasattr(self, membername): # can be overridden by a subclass
24
- setattr(self, membername, initfunc())
25
25
  cls.__init__ = wraps(oldinit)(newinit) if oldinit else newinit
26
26
 
27
27
  oldcopy = thisclass_hasattr(cls, '__copy__') and getattr(cls, '__copy__')
@@ -30,15 +30,32 @@ class Extensible:
30
30
  ret = oldcopy(self, *args, **kwargs)
31
31
  else:
32
32
  ret = super(cls, self).__copy__(*args, **kwargs)
33
- setattr(ret, membername, getattr(self, membername) if copy and hasattr(self, membername) else initfunc())
33
+
34
+ if not hasattr(ret, membername):
35
+ if copy and hasattr(self, membername):
36
+ v = getattr(self, membername)
37
+ else:
38
+ v = initfunc()
39
+ setattr(ret, membername, v)
40
+
34
41
  return ret
35
42
  cls.__copy__ = wraps(oldcopy)(newcopy) if oldcopy else newcopy
36
43
 
44
+ @classmethod
45
+ def superclasses(cls):
46
+ yield cls
47
+ yield from cls.__bases__
48
+ for b in cls.__bases__:
49
+ if hasattr(b, 'superclasses'):
50
+ yield from b.superclasses()
51
+
37
52
  @classmethod
38
53
  def api(cls, func):
39
54
  oldfunc = getattr(cls, func.__name__, None)
40
55
  if oldfunc:
41
56
  func = wraps(oldfunc)(func)
57
+ from visidata import vd
58
+ func.importingModule = vd.importingModule
42
59
  setattr(cls, func.__name__, func)
43
60
  return func
44
61
 
@@ -75,6 +92,14 @@ class Extensible:
75
92
 
76
93
  @classmethod
77
94
  def class_api(cls, func):
95
+ '''`@Class.class_api` works much like `@Class.api`, but for class methods. This method is used internally but may not be all that useful for plugin and module authors. Note that `@classmethod` must still be provided, and **the order of multiple decorators is crucial**, in that `@<class>.class_api` must come before `@classmethod`:
96
+
97
+ ::
98
+ @Sheet.class_api
99
+ @classmethod
100
+ def addCommand(cls, ...):
101
+ '''
102
+
78
103
  name = func.__get__(None, dict).__func__.__name__
79
104
  oldfunc = getattr(cls, name, None)
80
105
  if oldfunc:
@@ -94,11 +119,12 @@ class Extensible:
94
119
  @classmethod
95
120
  def lazy_property(cls, func):
96
121
  'Return ``func()`` on first access and cache result; return cached result thereafter.'
122
+ name = '_' + func.__name__
123
+ cls.init(name, lambda: None, copy=False)
97
124
  @property
98
125
  @wraps(func)
99
126
  def get_if_not(self):
100
- name = '_' + func.__name__
101
- if not hasattr(self, name):
127
+ if getattr(self, name, None) is None:
102
128
  setattr(self, name, func(self))
103
129
  return getattr(self, name)
104
130
  setattr(cls, func.__name__, get_if_not)
File without changes
@@ -0,0 +1,42 @@
1
+ # requirements: mutagen
2
+
3
+ import functools
4
+
5
+ from visidata import vd, Column, AttrColumn, DirSheet
6
+
7
+
8
+ @functools.lru_cache(None)
9
+ def get_mutagen_info(path):
10
+ mutagen = vd.importExternal('mutagen')
11
+ m = mutagen.File(path)
12
+ return m.info
13
+
14
+
15
+ class MutagenColumn(AttrColumn):
16
+ def calcValue(self, r):
17
+ md = get_mutagen_info(r)
18
+ return getattr(md, self.expr, None)
19
+
20
+
21
+ @DirSheet.api
22
+ def audiometadata_columns(sheet):
23
+ return [
24
+ Column('audio_info', width=0, getter=lambda c,r: get_mutagen_info(r)),
25
+ MutagenColumn('bitrate'),
26
+ MutagenColumn('channels'),
27
+ MutagenColumn('encoder_info'),
28
+ MutagenColumn('encoder_settings'),
29
+ MutagenColumn('frame_offset'),
30
+ MutagenColumn('length'),
31
+ MutagenColumn('mode'),
32
+ MutagenColumn('padding'),
33
+ MutagenColumn('protected'),
34
+ MutagenColumn('sample_rate'),
35
+ MutagenColumn('track_gain'),
36
+ ]
37
+
38
+
39
+ DirSheet.addCommand('', 'addcol-audiometadata', 'addColumn(*audiometadata_columns())', 'add metadata columns for audio files (MP3, FLAC, Ogg, etc)')
40
+
41
+
42
+ vd.addMenuItems('Column > Add column > audio metadata > addcol-audiometadata')
@@ -0,0 +1,34 @@
1
+ from visidata import vd, Column, Sheet, asyncthread, Progress
2
+
3
+
4
+ class HistogramColumn(Column):
5
+ def calcValue(col, row):
6
+ histogram = col.sheet.options.disp_histogram
7
+ histolen = (col.width-2)*col.sourceCol.getTypedValue(row)//(col.sourceCol.largest-col.sourceCol.smallest)
8
+ return histogram*histolen
9
+
10
+
11
+ @Sheet.api
12
+ def addcol_histogram(sheet, col): #2052
13
+ newcol = HistogramColumn(col.name+'_histogram', sourceCol=col)
14
+ col.smallest = None
15
+ col.largest = None
16
+ sheet.calc_histogram_bounds(col)
17
+ return newcol
18
+
19
+
20
+ @Sheet.api
21
+ @asyncthread
22
+ def calc_histogram_bounds(sheet, col):
23
+ for row in Progress(sheet.rows):
24
+ v = col.getTypedValue(row)
25
+ if col.smallest is None or v < col.smallest:
26
+ col.smallest = v
27
+ if col.largest is None or v > col.largest:
28
+ col.largest = v
29
+
30
+
31
+ Sheet.addCommand('', 'addcol-histogram', 'addColumnAtCursor(addcol_histogram(cursorCol))', 'add column with histogram of current column')
32
+
33
+
34
+ vd.addMenuItems('Column > Add column > histogram > addcol-histogram')
@@ -0,0 +1,69 @@
1
+ '''
2
+ Add svg saver to Canvas.
3
+
4
+ Requires matplotlib.pyplot
5
+ '''
6
+
7
+
8
+ import collections
9
+
10
+ from visidata import VisiData, Canvas, vd, Progress
11
+
12
+ vd.option('plt_marker', '.', 'matplotlib.markers')
13
+
14
+
15
+ @Canvas.api
16
+ def plot_sheet(self, ax):
17
+ plt = vd.importExternal('matplotlib.pyplot', 'matplotlib')
18
+ nerrors = 0
19
+ nplotted = 0
20
+
21
+ self.reset()
22
+
23
+ vd.status('loading data points')
24
+ catcols = [c for c in self.xcols if not vd.isNumeric(c)]
25
+ numcols = [c for c in self.xcols if vd.isNumeric(c)]
26
+ for ycol in self.ycols:
27
+ xpts = collections.defaultdict(list)
28
+ ypts = collections.defaultdict(list)
29
+ for rownum, row in enumerate(Progress(self.sourceRows, 'plotting')): # rows being plotted from source
30
+ try:
31
+ k = tuple(c.getValue(row) for c in catcols) if catcols else (ycol.name,)
32
+
33
+ # convert deliberately to float (to e.g. linearize date)
34
+ graph_x = numcols[0].type(numcols[0].getValue(row)) if numcols else rownum
35
+ graph_y = ycol.type(ycol.getValue(row))
36
+
37
+ xpts[k].append(graph_x)
38
+ ypts[k].append(graph_y)
39
+
40
+ nplotted += 1
41
+ except Exception:
42
+ nerrors += 1
43
+ if vd.options.debug:
44
+ raise
45
+ lines = []
46
+ for k in xpts:
47
+ line = ax.scatter(xpts[k], ypts[k], label=' '.join(str(x) for x in k), **vd.options.getall('plt_'))
48
+ lines.append(line)
49
+
50
+ ax.legend(handles=lines)
51
+ ax.set_xlabel(','.join(xcol.name for xcol in self.xcols if vd.isNumeric(xcol)) or 'row#')
52
+ ax.xaxis.set_major_locator(plt.MaxNLocator(4))
53
+ ax.yaxis.set_major_locator(plt.MaxNLocator(4))
54
+
55
+
56
+ @VisiData.api
57
+ def save_svg(vd, p, *sheets):
58
+ plt = vd.importExternal('matplotlib.pyplot', 'matplotlib')
59
+ fig_, ax = plt.subplots()
60
+ for vs in sheets:
61
+ if not isinstance(vs, Canvas):
62
+ vd.warning(f'{vs.name} not a Canvas')
63
+ continue
64
+ vs.plot_sheet(ax)
65
+
66
+ ax.grid()
67
+ ax.set_title('\n'.join(vs.name for vs in sheets))
68
+ plt.xticks()
69
+ plt.savefig(p, format='svg')
@@ -0,0 +1,46 @@
1
+ __author__ = 'Andy Craig, andycraig (https://github.com/andycraig)'
2
+
3
+
4
+ import re
5
+
6
+ from visidata import vd, Sheet, Column, floatsi, currency, date
7
+
8
+ date_fmtstrs = [
9
+ '%Y',
10
+ '%Y-%m',
11
+ # '%Y-W%U',
12
+ '%Y-%m-%d',
13
+ '%Y-%m-%d %H',
14
+ '%Y-%m-%d %H:%M',
15
+ '%Y-%m-%d %H:%M:%S',
16
+ '%Y-%m-%d %H:%M:%S.%f',
17
+ ]
18
+
19
+ @Column.api
20
+ def setcol_precision(col, amount:int):
21
+ if col.type is date:
22
+ try:
23
+ i = date_fmtstrs.index(col.fmtstr)
24
+ except ValueError:
25
+ i = 2
26
+ col.fmtstr = date_fmtstrs[(i+amount)%len(date_fmtstrs)]
27
+ elif col.type in (float, floatsi, currency):
28
+ if col.fmtstr == '':
29
+ col.fmtstr = f'%.{2 + amount}f'
30
+ else:
31
+ precision_str = re.match(r'%.([0-9]+)f', col.fmtstr)
32
+ if not precision_str is None:
33
+ col.fmtstr = f'%.{max(0, int(precision_str[1]) + amount)}f'
34
+ else:
35
+ col.type = float
36
+ if col.fmtstr == '':
37
+ col.fmtstr = '%.2f'
38
+
39
+
40
+ vd.addMenuItems('''
41
+ Column > Set precision > more > setcol-precision-more
42
+ Column > Set precision > less > setcol-precision-less
43
+ ''')
44
+
45
+ Sheet.addCommand('Alt+-', 'setcol-precision-less', 'cursorCol.setcol_precision(-1)', 'show less precision in current column')
46
+ Sheet.addCommand('Alt++', 'setcol-precision-more', 'cursorCol.setcol_precision(1)', 'show more precision in current column')
@@ -0,0 +1,163 @@
1
+ import collections
2
+ from functools import partial
3
+ from visidata import DrawablePane, BaseSheet, vd, VisiData, CompleteKey, clipdraw, HelpSheet, colors, AcceptInput, AttrDict, drawcache_property
4
+
5
+
6
+ vd.theme_option('color_cmdpalette', 'black on 72', 'base color of command palette')
7
+ vd.theme_option('disp_cmdpal_max', 10, 'max number of suggestions for command palette')
8
+
9
+ def add_to_input(v, i, value=''):
10
+ items = list(v.split())
11
+ if not v or v.endswith(' '):
12
+ items.append(value)
13
+ else:
14
+ items[-1] = value
15
+ v = ' '.join(items) + ' '
16
+ return v, len(v)
17
+
18
+
19
+ def accept_input(v, i, value=None):
20
+ raise AcceptInput(v if value is None else value)
21
+
22
+
23
+ @VisiData.lazy_property
24
+ def usedInputs(vd):
25
+ return collections.defaultdict(int)
26
+
27
+ @DrawablePane.after
28
+ def execCommand2(sheet, cmd, *args, **kwargs):
29
+ vd.usedInputs[cmd.longname] += 1
30
+
31
+ @BaseSheet.api
32
+ def inputPalette(sheet, prompt, items,
33
+ value_key='key',
34
+ formatter=lambda m, item, trigger_key: f'{trigger_key} {item}',
35
+ multiple=False,
36
+ **kwargs):
37
+ bindings = dict()
38
+
39
+ tabitem = -1
40
+
41
+ def tab(n, nitems):
42
+ nonlocal tabitem
43
+ tabitem = (tabitem + n) % nitems
44
+
45
+ def _draw_palette(value):
46
+ words = value.lower().split()
47
+
48
+ if multiple and words:
49
+ if value.endswith(' '):
50
+ finished_words = words
51
+ unfinished_words = []
52
+ else:
53
+ finished_words = words[:-1]
54
+ unfinished_words = [words[-1]]
55
+ else:
56
+ unfinished_words = words
57
+ finished_words = []
58
+
59
+ unuseditems = [item for item in items if item[value_key] not in finished_words]
60
+
61
+ matches = vd.fuzzymatch(unuseditems, unfinished_words)
62
+
63
+ h = sheet.windowHeight
64
+ w = min(100, sheet.windowWidth)
65
+ nitems = min(h-1, sheet.options.disp_cmdpal_max)
66
+
67
+ useditems = []
68
+ palrows = []
69
+
70
+ for m in matches[:nitems]:
71
+ useditems.append(m.match)
72
+ palrows.append((m, m.match))
73
+
74
+ favitems = sorted([item for item in unuseditems if item not in useditems],
75
+ key=lambda item: -vd.usedInputs.get(item[value_key], 0))
76
+
77
+ for item in favitems[:nitems-len(palrows)]:
78
+ palrows.append((None, item))
79
+
80
+ navailitems = min(len(palrows), nitems)
81
+
82
+ bindings['^I'] = lambda *args: tab(1, navailitems) or args
83
+ bindings['KEY_BTAB'] = lambda *args: tab(-1, navailitems) or args
84
+
85
+ for i in range(nitems-len(palrows)):
86
+ palrows.append((None, None))
87
+
88
+ for i, (m, item) in enumerate(palrows):
89
+ trigger_key = ' '
90
+ if tabitem >= 0 and item:
91
+ trigger_key = f'{i+1}'[-1]
92
+ bindings[trigger_key] = partial(add_to_input if multiple else accept_input, value=item[value_key])
93
+
94
+ attr = colors.color_cmdpalette
95
+
96
+ if tabitem < 0 and palrows:
97
+ _ , topitem = palrows[0]
98
+ bindings['^J'] = partial(accept_input, value=None)
99
+ if multiple:
100
+ bindings[' '] = partial(add_to_input, value=topitem[value_key])
101
+ elif item and i == tabitem:
102
+ bindings['^J'] = partial(accept_input, value=None)
103
+ if multiple:
104
+ bindings[' '] = partial(add_to_input, value=item[value_key])
105
+ attr = colors.color_menu_spec
106
+
107
+ match_summary = formatter(m, item, trigger_key) if item else ' '
108
+
109
+ clipdraw(sheet._scr, h-nitems-1+i, 0, match_summary, attr, w=w)
110
+
111
+ return None
112
+
113
+ completer = CompleteKey(sorted(item[value_key] for item in items))
114
+ return vd.input(prompt,
115
+ completer=completer,
116
+ updater=_draw_palette,
117
+ bindings=bindings,
118
+ **kwargs)
119
+
120
+
121
+ def cmdlist(sheet):
122
+ return [
123
+ AttrDict(longname=row.longname,
124
+ description=sheet.cmddict[(row.sheet, row.longname)].helpstr)
125
+ for row in sheet.rows
126
+ ]
127
+ HelpSheet.cmdlist = drawcache_property(cmdlist)
128
+
129
+
130
+ @BaseSheet.api
131
+ def inputLongname(sheet):
132
+ prompt = 'command name: '
133
+ # get set of commands possible in the sheet
134
+ this_sheets_help = HelpSheet('', source=sheet)
135
+ this_sheets_help.ensureLoaded()
136
+
137
+ def _fmt_cmdpal_summary(match, row, trigger_key):
138
+ keystrokes = this_sheets_help.revbinds.get(row.longname, [None])[0] or ' '
139
+ formatted_longname = match.formatted.get('longname', row.longname) if match else row.longname
140
+ formatted_name = f'[:onclick {row.longname}]{formatted_longname}[/]'
141
+ if vd.options.debug and match:
142
+ keystrokes = f'[{match.score}]'
143
+ r = f' [:keystrokes]{keystrokes.rjust(len(prompt)-5)}[/] '
144
+ r += f'[:keystrokes]{trigger_key}[/] {formatted_name}'
145
+ if row.description:
146
+ formatted_desc = match.formatted.get('description', row.description) if match else row.description
147
+ r += f' - {formatted_desc}'
148
+ return r
149
+
150
+ return sheet.inputPalette(prompt, this_sheets_help.cmdlist,
151
+ value_key='longname',
152
+ formatter=_fmt_cmdpal_summary,
153
+ type='longname')
154
+
155
+
156
+ @BaseSheet.api
157
+ def exec_longname(sheet, longname):
158
+ if not sheet.getCommand(longname):
159
+ vd.fail(f'no command {longname}')
160
+ sheet.execCommand(longname)
161
+
162
+
163
+ vd.addCommand('Space', 'exec-longname', 'exec_longname(inputLongname())', 'execute command by its longname')