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
@@ -0,0 +1,43 @@
1
+ import json
2
+
3
+ from visidata import vd, VisiData, Path, AttrDict
4
+
5
+
6
+ @VisiData.api
7
+ class StoredList(list):
8
+ 'Read existing persisted list from filesystem, and append new elements to .jsonl in .visidata'
9
+ def __init__(self, *args, name:str='', **kwargs):
10
+ super().__init__(*args, **kwargs)
11
+ self.name = name
12
+
13
+ @property
14
+ def path(self):
15
+ vdpath = Path(vd.options.visidata_dir)
16
+ if vdpath.exists():
17
+ return vdpath/(self.name + '.jsonl')
18
+
19
+ def reload(self):
20
+ p = self.path
21
+ if not p or not p.exists():
22
+ return
23
+
24
+ ret = []
25
+ with p.open(encoding='utf-8-sig') as fp:
26
+ for line in fp:
27
+ value = vd.callNoExceptions(json.loads, line)
28
+ if value is not None:
29
+ if isinstance(value, dict):
30
+ value = AttrDict(value)
31
+ ret.append(value)
32
+
33
+ self[:] = ret # replace without using .append
34
+
35
+ def append(self, v):
36
+ super().append(v)
37
+
38
+ p = self.path
39
+ if p is None:
40
+ return
41
+
42
+ with p.open(encoding='utf-8', mode='a') as fp:
43
+ fp.write(json.dumps(v) + '\n')
@@ -0,0 +1,38 @@
1
+ from functools import wraps
2
+ import json
3
+ import atexit
4
+
5
+ from visidata import vd, VisiData, Path
6
+
7
+ vd.stored_properties = {}
8
+
9
+ @VisiData.class_api
10
+ @classmethod
11
+ def stored_property(vdcls, f):
12
+ def _save_on_exit():
13
+ if vd.stored_properties.get(f, None):
14
+ p = Path(vd.options.visidata_dir)/(f.__name__ + '.json')
15
+ with p.open(mode='w', encoding='utf-8') as fp:
16
+ fp.write(json.dumps(vd.stored_properties[f]))
17
+
18
+ @property
19
+ @wraps(f)
20
+ def _decorator(*args, **kwargs):
21
+ 'Read persisted value from filesystem if available; otherwise call the decorated function to create a new instance.'
22
+ value = vd.stored_properties.get(f, None)
23
+ if value is not None:
24
+ return value
25
+
26
+ p = Path(vd.options.visidata_dir)/(f.__name__ + '.json')
27
+ if p.exists():
28
+ value = json.loads(p.open(encoding='utf-8-sig').read())
29
+
30
+ if value is None:
31
+ value = f(*args, **kwargs)
32
+
33
+ vd.stored_properties[f] = value
34
+ return value
35
+
36
+ atexit.register(_save_on_exit)
37
+ setattr(vdcls, f.__name__, _decorator)
38
+ return _decorator
@@ -10,7 +10,7 @@ def curses_setup():
10
10
  import visidata
11
11
 
12
12
  curses.curs_set = lambda v: None
13
- visidata.options.confirm_overwrite = False
13
+ visidata.options.overwrite = 'always'
14
14
 
15
15
 
16
16
  @pytest.fixture(scope="function")
@@ -18,8 +18,8 @@ def mock_screen():
18
18
  """Set up and return a mock curses screen object."""
19
19
 
20
20
  scr = Mock()
21
- scr.addstr = Mock()
22
- scr.move = Mock()
21
+ scr.addstr = lambda *args, **kwargs: None
22
+ scr.move = lambda *args, **kwargs: None
23
23
  scr.getmaxyx = lambda: (25, 80)
24
24
 
25
25
  return scr
@@ -0,0 +1,39 @@
1
+ import pytest
2
+ from unittest.mock import Mock, call
3
+
4
+ import visidata
5
+
6
+
7
+ class TestClipText:
8
+ @pytest.mark.parametrize('s, dispw', [
9
+ ('abcdef', 6),
10
+ ('桜 高橋', 7),
11
+ ('[:onclick sidebar-toggle][:reverse] b to toggle sidebar [:]', 21),
12
+ ])
13
+ def test_dispwidth(self, s, dispw):
14
+ assert visidata.dispwidth(s) == dispw
15
+
16
+ @pytest.mark.parametrize('s, w, clippeds, clippedw', [
17
+ ('b to', 4, 'b to', 4),
18
+ ('abcde', 8, 'abcde', 5),
19
+ (' jsonl', 5, ' jso…', 5),
20
+ ('abcdで', 6, 'abcdで', 6),
21
+ ('abcdで', 5, 'abcd…', 5),
22
+ ])
23
+ def test_clipstr(self, s, w, clippeds, clippedw):
24
+ clips, clipw = visidata.clipstr(s, w)
25
+ assert clips == clippeds
26
+ assert clipw == clippedw
27
+
28
+ def test_clipdraw_chunks(self):
29
+ prechunks = [
30
+ ('', 'x'),
31
+ ('', 'jsonl'),
32
+ ]
33
+ scr = Mock()
34
+ scr.getmaxyx.return_value = (80,25)
35
+ visidata.clipdraw_chunks(scr, 0, 0, prechunks, visidata.ColorAttr(), w=5)
36
+ scr.addstr.assert_has_calls([
37
+ call(0, 0, 'x', 0),
38
+ call(0, 1, 'jso…', 0),
39
+ ], any_order=True)
@@ -1,9 +1,9 @@
1
- import pkg_resources
2
1
  import pytest
3
2
  from unittest.mock import Mock
4
3
 
5
4
  import itertools
6
5
  import visidata
6
+ from pathlib import Path
7
7
 
8
8
  # test separately as needed
9
9
 
@@ -11,11 +11,22 @@ import visidata
11
11
  # commands that require curses, and are not
12
12
  # replayable
13
13
  nonTested = (
14
+ 'toggle-profile',
14
15
  'syscopy',
15
16
  'syspaste',
17
+ 'open-syspaste',
16
18
  'macro',
17
19
  'mouse',
20
+ 'add-subreddits',
21
+ 'add-submissions',
22
+ 'open-zulip',
18
23
  'suspend',
24
+ 'open-memstats', # TODO add testing support
25
+ 'plot-column-ext',
26
+ 'plot-numerics-ext',
27
+ 'reload-every',
28
+ 'reload-modified',
29
+ 'reload-rows',
19
30
  'breakpoint',
20
31
  'redraw',
21
32
  'menu',
@@ -36,13 +47,14 @@ inputLines = { 'save-sheet': 'jetsam.csv', # save to some tmp file
36
47
  'save-col-keys': 'debris.csv',
37
48
  'pyobj-expr': '2+2', # open the python object for '4'
38
49
  'edit-cell': '3',
50
+ 'search-keys': 'foo',
39
51
  'search-col': 'foo',
40
52
  'searchr-col': 'bar',
41
53
  'select-col-regex': '.',
42
54
  'select-cols-regex': '.',
43
55
  'unselect-col-regex': '.',
56
+ 'exec-python': 'import time',
44
57
  'unselect-cols-regex': '.',
45
- 'edit-cell': '', # no change should not error
46
58
  'go-col-regex': 'Units', # column name in sample
47
59
  'go-col-number': '2',
48
60
  'go-row-number': '5', # go to row 5
@@ -50,24 +62,54 @@ inputLines = { 'save-sheet': 'jetsam.csv', # save to some tmp file
50
62
  'addcol-expr': 'Units', # just copy the column
51
63
  'addcol-incr-step': '2',
52
64
  'setcol-incr-step': '2',
65
+ 'setcol-iter': 'range(1, 100)',
53
66
  'setcol-format-enum': '1=cat',
54
- 'split-col': '-',
67
+ 'setcol-input': '5',
55
68
  'show-expr': 'OrderDate',
56
69
  'setcol-expr': 'OrderDate',
70
+ 'open-ping': 'localhost',
57
71
  'setcell-expr': 'OrderDate',
58
72
  'setcol-range': 'range(100)',
59
73
  'repeat-input-n': '1',
60
- 'capture-col': '(.)(.*)',
61
- 'addcol-subst': r'Units/(\w)/\1', # the first character
74
+ 'addcol-regex-subst': dict(before=r'Units/(\w)', after=r'\1'), # the first character
62
75
  'search-cols': 'foo',
63
76
  'searchr-cols': 'bar',
64
77
  'select-cols-regex': '.',
65
78
  'select-expr': 'OrderDate',
79
+ 'setcol-fake': 'name',
66
80
  'unselect-expr': 'OrderDate',
67
81
  'unselect-cols-regex': '.',
68
82
  'random-rows': '3',
83
+ 'select-random': '3',
69
84
  'import-python': 'math',
70
85
  'pyobj-expr-row': 'Units + "s"', # open the python object for '4'
86
+ 'expand-col-depth': '0',
87
+ 'contract-col-depth': '0',
88
+ 'contract-cols-depth': '0',
89
+ 'expand-cols-depth': '0',
90
+ 'save-cmdlog': 'test_commands.vdj',
91
+ 'aggregate-col': 'mean',
92
+ 'memo-aggregate': 'mean',
93
+ 'addcol-shell': '',
94
+ 'theme-input': 'light',
95
+ 'add-rows': '1',
96
+ 'join-sheets-top2': 'append',
97
+ 'join-sheets-all': 'append',
98
+ 'resize-col-input': '10',
99
+ 'resize-cols-input': '10',
100
+ 'resize-height-input': '10',
101
+ 'melt-regex': '(.*)_(.*)',
102
+ 'addcol-split': '-',
103
+ 'addcol-capture': '(.*)_(.*)',
104
+ 'slide-left-n': '2',
105
+ 'slide-right-n': '1',
106
+ 'slide-down-n': '1',
107
+ 'slide-up-n': '1',
108
+ 'addcol-window': '0 2',
109
+ 'select-around-n': '1',
110
+ 'sheet': '',
111
+ 'col': 'Units',
112
+ 'row': '5',
71
113
  }
72
114
 
73
115
  @pytest.mark.usefixtures('curses_setup')
@@ -86,6 +128,9 @@ class TestCommands:
86
128
  nerrs = 0
87
129
  ntotal = 0
88
130
  for longname in cmdlist.keys():
131
+ cmd = vs.getCommand(longname)
132
+ if cmd and cmd.deprecated:
133
+ continue
89
134
  if not isTestableCommand(longname, cmdlist):
90
135
  continue
91
136
  ntotal += 1
@@ -102,6 +147,12 @@ class TestCommands:
102
147
  if nerrs > 0:
103
148
  assert False
104
149
 
150
+ # cleanup
151
+ for f in ['flotsam.csv', 'debris.csv', 'jetsam.csv', 'lagan.csv', 'test_commands.vdj']:
152
+ pf = Path(f)
153
+ if pf.exists: pf.unlink()
154
+
155
+
105
156
  def runOneTest(self, mock_screen, longname):
106
157
  visidata.vd.clearCaches() # we want vd to return a new VisiData object for each command
107
158
  vd = visidata.vd
@@ -114,7 +165,7 @@ class TestCommands:
114
165
  else:
115
166
  vd.getkeystroke = Mock(side_effect=['^J'])
116
167
 
117
- sample_file = pkg_resources.resource_filename('visidata', 'tests/sample.tsv')
168
+ sample_file = vd.pkg_resources_files(visidata) / 'tests/sample.tsv'
118
169
  vs = visidata.TsvSheet('test_commands', source=visidata.Path(sample_file))
119
170
  vs.reload.__wrapped__(vs)
120
171
  vs.vd = vd
@@ -122,4 +173,8 @@ class TestCommands:
122
173
  vd.allSheets = [vs]
123
174
  vs.mouseX, vs.mouseY = (4, 4)
124
175
  vs.draw(mock_screen)
125
- vs.execCommand(longname, vdglobals=vars(visidata))
176
+ if longname in inputLines:
177
+ vd.currentReplayRow = vd.cmdlog.newRow(longname=longname, input=inputLines[longname])
178
+ else:
179
+ vd.currentReplayRow = vd.cmdlog.newRow(longname=longname)
180
+ vs.execCommand(longname, vdglobals=vd.getGlobals())
@@ -48,7 +48,7 @@ class TestEditText:
48
48
  exception = kwargs.pop('exception', None)
49
49
  if exception:
50
50
  with pytest.raises(exception):
51
- visidata.vd.editline(mock_screen, 0, 0, 0, **kwargs)
51
+ visidata.vd.editline(mock_screen, 0, 0, 0, attr=visidata.ColorAttr(), **kwargs)
52
52
  else:
53
- r = visidata.vd.editline(mock_screen, 0, 0, 0, **kwargs)
53
+ r = visidata.vd.editline(mock_screen, 0, 0, 0, attr=visidata.ColorAttr(), **kwargs)
54
54
  assert r == result
@@ -0,0 +1,17 @@
1
+ import pytest
2
+ import visidata
3
+
4
+ @pytest.mark.usefixtures('curses_setup')
5
+ class TestFeatures:
6
+ def test_features(self, mock_screen):
7
+ tests = [
8
+ (mod, getattr(mod, k))
9
+ for mod in visidata.vd.importedModules
10
+ for k in dir(mod)
11
+ if k.startswith('test_')
12
+ ]
13
+ for mod, testfunc in tests:
14
+ print(mod, testfunc.__name__)
15
+ visidata.vd.resetVisiData()
16
+ visidata.vd.scr = mock_screen
17
+ testfunc(visidata.vd)
@@ -0,0 +1,14 @@
1
+
2
+ from visidata import vd, TableSheet
3
+ import pytest
4
+
5
+
6
+ class TestMenu:
7
+ def test_menuitems(self):
8
+ vd.addMenuItems('''Column > Add column > foobar > hello-world''')
9
+
10
+ m = TableSheet().getMenuItem(['Column', 'Add column', 'foobar'])
11
+ assert m
12
+
13
+ with pytest.raises(AssertionError):
14
+ vd.addMenuItems('''Column > Add column > non-command''')
@@ -1,3 +1,4 @@
1
+ import io
1
2
  import pytest
2
3
 
3
4
  from visidata import Path
@@ -16,11 +17,19 @@ class TestVisidataPath:
16
17
  assert "https://visidata.org/hello/b.tsv" == str(url_path.with_name('b.tsv')), '{} should be https://visidata.org/hello/b.tsv'.format(url_path.with_name('b.tsv'))
17
18
  assert "https://visidata.org/hello/a/b.tsv" == str(url_path.with_name('a/b.tsv')), '{} should be https://visidata.org/hello/a/b.tsv'.format(url_path.with_name('a/b.tsv'))
18
19
 
19
- assert Path('foo.a.b').name == 'foo.a'
20
+ assert Path('foo.a.b').base_stem == 'foo.a'
20
21
  assert Path('foo.a.b').ext == 'b'
21
22
  assert Path('foo').ext == ''
22
- assert Path('foo').name == 'foo'
23
+ assert Path('foo').base_stem == 'foo'
23
24
  assert Path('foo.').ext == ''
24
- assert Path('foo.').name == 'foo.'
25
+ assert Path('foo.').base_stem == 'foo.'
25
26
  assert Path('.foo').ext == ''
26
- assert Path('.foo').name == '.foo'
27
+ assert Path('.foo').base_stem == '.foo'
28
+
29
+
30
+ def test_opentwice(self):
31
+ 'fresh iterator for each open'
32
+ p = Path('test', fptext=io.StringIO('<html>'))
33
+ a = next(p.open())
34
+ b = next(p.open())
35
+ assert a == b
@@ -0,0 +1,53 @@
1
+ import re
2
+
3
+ from visidata import vd, BaseSheet
4
+
5
+ vd.option('regex_skip', '', 'regex of lines to skip in text sources', help='regex')
6
+ vd.option('regex_flags', 'I', 'flags to pass to re.compile() [AILMSUX]', replay=True)
7
+
8
+ @BaseSheet.api
9
+ def regex_flags(sheet):
10
+ 'Return flags to pass to regex functions from options'
11
+ return sum(getattr(re, f.upper()) for f in sheet.options.regex_flags)
12
+
13
+
14
+ class FilterFile:
15
+ def __init__(self, fp, regex:str, regex_flags:int=0):
16
+ import re
17
+ self._fp = fp
18
+ self._regex_skip = re.compile(regex, regex_flags)
19
+
20
+ def readline(self) -> str:
21
+ while True:
22
+ line = self._fp.readline()
23
+ if self._regex_skip.match(line):
24
+ continue
25
+ return line
26
+
27
+ def __getattr__(self, k):
28
+ return getattr(self._fp, k)
29
+
30
+ def __iter__(self):
31
+ return self
32
+
33
+ def __next__(self):
34
+ line = self.readline()
35
+ if not line:
36
+ raise StopIteration
37
+ return line
38
+
39
+ def __enter__(self):
40
+ return self
41
+
42
+ def __exit__(self, *args, **kwargs):
43
+ return self._fp.__exit__(*args, **kwargs)
44
+
45
+
46
+ @BaseSheet.api
47
+ def open_text_source(sheet):
48
+ 'Open sheet source as text, using sheet options for encoding and regex_skip.'
49
+ fp = sheet.source.open(encoding=sheet.options.encoding, encoding_errors=sheet.options.encoding_errors)
50
+ regex_skip = sheet.options.regex_skip
51
+ if regex_skip:
52
+ return FilterFile(fp, regex_skip, sheet.regex_flags())
53
+ return fp
visidata/textsheet.py CHANGED
@@ -6,7 +6,7 @@ from visidata import globalCommand, VisiData
6
6
  import visidata
7
7
 
8
8
 
9
- vd.option('wrap', False, 'wrap text to fit window width on TextSheet')
9
+ vd.option('wrap', False, 'wrap text to fit window width on TextSheet', max_help=0)
10
10
  vd.option('save_filetype', 'tsv', 'specify default file type to save as', replay=True)
11
11
 
12
12
 
@@ -32,7 +32,7 @@ class TextSheet(Sheet):
32
32
  for i, L in enumerate(textwrap.wrap(str(text), width=winWidth)):
33
33
  yield [startingLine+i+1, L]
34
34
  else:
35
- yield [startingLine+1, text]
35
+ yield [startingLine+1, text.strip()]
36
36
 
37
37
  def sysopen(sheet, linenum=0):
38
38
  @asyncthread
@@ -44,6 +44,7 @@ class TextSheet(Sheet):
44
44
 
45
45
  import tempfile
46
46
  with tempfile.NamedTemporaryFile() as temp:
47
+ temp.close() #2118
47
48
  writelines(sheet, temp.name)
48
49
  vd.launchEditor(temp.name, '+%s' % linenum)
49
50
  sheet.rows = []
@@ -82,7 +83,7 @@ def recentErrorsSheet(self):
82
83
  BaseSheet.addCommand('^E', 'error-recent', 'vd.lastErrors and vd.push(recentErrorsSheet) or status("no error")', 'view traceback for most recent error')
83
84
  BaseSheet.addCommand('g^E', 'errors-all', 'vd.push(vd.allErrorsSheet)', 'view traceback for most recent errors')
84
85
 
85
- Sheet.addCommand(None, 'view-cell', 'vd.push(ErrorSheet("%s[%s].%s" % (name, cursorRowIndex, cursorCol.name), sourceSheet=sheet, source=cursorDisplay.splitlines()))', 'view contents of current cell in a new sheet'),
86
+ Sheet.addCommand(None, 'view-cell', 'vd.push(ErrorSheet("%s[%s].%s" % (name, cursorRowIndex, cursorCol.name), sourceSheet=sheet, source=cursorDisplay.splitlines()))', 'view contents of current cell in a new sheet')
86
87
  Sheet.addCommand('z^E', 'error-cell', 'vd.push(ErrorSheet(sheet.name+"_cell_error", sourceSheet=sheet, source=getattr(cursorCell, "error", None) or fail("no error this cell")))', 'view traceback for error in current cell')
87
88
 
88
89
  TextSheet.addCommand('^O', 'sysopen-sheet', 'sheet.sysopen(sheet.cursorRowIndex)', 'open copy of text sheet in $EDITOR and reload on exit')
@@ -91,3 +92,9 @@ TextSheet.addCommand('^O', 'sysopen-sheet', 'sheet.sysopen(sheet.cursorRowIndex)
91
92
  TextSheet.options.save_filetype = 'txt'
92
93
 
93
94
  vd.addGlobals({'TextSheet': TextSheet, 'ErrorSheet': ErrorSheet})
95
+
96
+ vd.addMenuItems('''
97
+ View > Errors > recent > error-recent
98
+ View > Errors > all > errors-all
99
+ View > Errors > in cell > error-cell
100
+ ''')
visidata/theme.py ADDED
@@ -0,0 +1,44 @@
1
+ 'Switch between packaged themes (colors and display characters)'
2
+
3
+ from visidata import vd, VisiData, Sheet, BaseSheet
4
+
5
+
6
+ vd.option('theme', '', 'display/color theme to use')
7
+
8
+ vd.themes = {}
9
+
10
+
11
+ @VisiData.before
12
+ def run(vd, *args, **kwargs):
13
+ t = vd.options.theme
14
+ if t:
15
+ vd.set_theme(t)
16
+
17
+
18
+ @Sheet.api
19
+ @VisiData.api
20
+ def set_theme(obj, theme=''):
21
+ if theme and theme not in vd.themes:
22
+ vd.warning(f'no "{theme}" theme available')
23
+ return
24
+
25
+ # unset everything first
26
+ for k in vd.options.keys():
27
+ if k.startswith(tuple('color_ disp_ note_'.split())):
28
+ obj.options.unset(k)
29
+
30
+ if not theme:
31
+ return
32
+
33
+ if isinstance(theme, str):
34
+ theme = vd.themes[theme]
35
+
36
+ for k, v in theme.items():
37
+ obj.options[k] = v
38
+
39
+
40
+ BaseSheet.addCommand('', 'theme-input', 'vd.set_theme(chooseOne([dict(key=k) for k in themes.keys()], type="theme"))', 'choose from available themes')
41
+ BaseSheet.addCommand('', 'theme-default', 'vd.set_theme()', 'reset theme to VisiData defaults')
42
+
43
+ vd.addMenuItem('View', 'Set theme', 'choose', 'theme-input')
44
+ vd.addMenuItem('View', 'Set theme', 'default', 'theme-default')
File without changes
@@ -0,0 +1,84 @@
1
+ 'ASCII theme using the first 8 colors'
2
+
3
+ from visidata import vd
4
+
5
+
6
+ vd.themes['ascii8'] = dict(
7
+ disp_note_none='',
8
+ disp_truncator='>',
9
+ disp_oddspace='.',
10
+ disp_more_left='<',
11
+ disp_more_right='>',
12
+ disp_error_val='',
13
+ disp_ambig_width=1,
14
+
15
+ disp_pending='',
16
+ note_pending=':',
17
+ note_format_exc='?',
18
+ note_getter_exc='!',
19
+ note_type_exc='!',
20
+
21
+ color_note_pending='bold magenta',
22
+ color_note_type='yellow',
23
+ color_note_row='yellow',
24
+
25
+ disp_column_sep='|',
26
+ disp_keycol_sep='|',
27
+ disp_rowtop_sep='|',
28
+ disp_rowmid_sep='|',
29
+ disp_rowbot_sep='|',
30
+ disp_rowend_sep='|',
31
+ disp_keytop_sep='|',
32
+ disp_keymid_sep='|',
33
+ disp_keybot_sep='|',
34
+ disp_endtop_sep='|',
35
+ disp_endmid_sep='|',
36
+ disp_endbot_sep='|',
37
+ disp_selected_note='+',
38
+ disp_sort_asc='^^^^^^',
39
+ disp_sort_desc='vvvvvv',
40
+ color_default='white on black',
41
+ color_default_hdr='bold',
42
+ color_bottom_hdr='underline',
43
+ color_current_row='reverse',
44
+ color_current_col='bold',
45
+ color_current_hdr='bold reverse',
46
+ color_column_sep='blue',
47
+ color_key_col='cyan',
48
+ color_hidden_col='8',
49
+ color_selected_row='yellow',
50
+ color_edit_cell='white',
51
+ color_graph_hidden='blue',
52
+ color_graph_selected='bold',
53
+ color_status_replay='green',
54
+
55
+ color_graph_axis='bold',
56
+ color_sidebar='black on blue',
57
+ color_add_pending='green',
58
+ color_change_pending='reverse yellow',
59
+ color_delete_pending='red',
60
+ disp_rstatus_fmt=' {sheet.longname} {sheet.nRows:9d} {sheet.rowtype} {sheet.modifiedStatus} {sheet.options.disp_selected_note}{sheet.nSelectedRows}',
61
+ disp_status_fmt='{sheet.shortcut}> {sheet.name}| ',
62
+ disp_lstatus_max=0,
63
+ disp_status_sep=' | ',
64
+ color_keystrokes='bold black on cyan',
65
+ color_status='bold black on cyan',
66
+ color_error='red',
67
+ color_warning='yellow',
68
+ color_top_status='underline',
69
+ color_active_status='black on cyan',
70
+ color_inactive_status='8 on black',
71
+ color_working='green',
72
+
73
+ color_menu='black on cyan',
74
+ color_menu_active='yellow on black',
75
+ color_menu_spec='black on green',
76
+ color_menu_help='black on cyan',
77
+ disp_menu_boxchars='||-- ||',
78
+ disp_menu_more='>',
79
+ disp_menu_push='+',
80
+ disp_menu_input='_',
81
+ disp_menu_fmt='7-bit ASCII 3-bit color',
82
+ plot_colors = 'white',
83
+ disp_histogram='*'
84
+ )
@@ -0,0 +1,84 @@
1
+ 'ASCII monochrome theme; default colors only'
2
+
3
+ from visidata import vd
4
+
5
+
6
+ vd.themes['asciimono'] = dict(
7
+ disp_note_none='',
8
+ disp_truncator='>',
9
+ disp_oddspace='.',
10
+ disp_more_left='<',
11
+ disp_more_right='>',
12
+ disp_error_val='',
13
+ disp_ambig_width=1,
14
+
15
+ disp_pending='',
16
+ note_pending=':',
17
+ note_format_exc='?',
18
+ note_getter_exc='!',
19
+ note_type_exc='!',
20
+
21
+ color_note_pending='bold',
22
+ color_note_type='',
23
+ color_note_row='',
24
+
25
+ disp_column_sep='|',
26
+ disp_keycol_sep='|',
27
+ disp_rowtop_sep='|',
28
+ disp_rowmid_sep='|',
29
+ disp_rowbot_sep='|',
30
+ disp_rowend_sep='|',
31
+ disp_keytop_sep='|',
32
+ disp_keymid_sep='|',
33
+ disp_keybot_sep='|',
34
+ disp_endtop_sep='|',
35
+ disp_endmid_sep='|',
36
+ disp_endbot_sep='|',
37
+ disp_selected_note='+',
38
+ disp_sort_asc='^^^^^^',
39
+ disp_sort_desc='vvvvvv',
40
+ color_default='',
41
+ color_default_hdr='bold',
42
+ color_bottom_hdr='underline',
43
+ color_current_row='reverse',
44
+ color_current_col='bold',
45
+ color_current_hdr='bold reverse',
46
+ color_column_sep='',
47
+ color_key_col='bold',
48
+ color_hidden_col='8',
49
+ color_selected_row='',
50
+ color_edit_cell='',
51
+ color_graph_hidden='',
52
+ color_graph_selected='bold',
53
+ color_status_replay='',
54
+
55
+ color_graph_axis='bold',
56
+ color_sidebar='reverse',
57
+ color_change_pending='reverse',
58
+ color_delete_pending='underline',
59
+
60
+ disp_rstatus_fmt=' {sheet.longname} {sheet.nRows:9d} {sheet.rowtype} {sheet.modifiedStatus} {sheet.options.disp_selected_note}{sheet.nSelectedRows}',
61
+ disp_status_fmt='{sheet.shortcut}> {sheet.name}| ',
62
+ disp_lstatus_max=0,
63
+ disp_status_sep=' | ',
64
+ color_keystrokes='bold reverse',
65
+ color_status='bold reverse',
66
+ color_error='bold',
67
+ color_warning='bold',
68
+ color_top_status='underline',
69
+ color_active_status='reverse',
70
+ color_inactive_status='8',
71
+ color_working='',
72
+
73
+ color_menu='reverse',
74
+ color_menu_active='',
75
+ color_menu_spec='reverse',
76
+ color_menu_help='reverse',
77
+ color_add_pending='',
78
+ disp_menu_boxchars='||-- ||',
79
+ disp_menu_more='>',
80
+ disp_menu_push='+',
81
+ disp_menu_input='_',
82
+ disp_menu_fmt='_.;"`\\ ASCII mono /\':._',
83
+ plot_colors = 'white',
84
+ )