visidata 2.11.1__py3-none-any.whl → 3.0.1__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 (256) hide show
  1. visidata/__init__.py +72 -91
  2. visidata/_input.py +259 -42
  3. visidata/_open.py +84 -29
  4. visidata/_types.py +21 -3
  5. visidata/_urlcache.py +17 -4
  6. visidata/aggregators.py +78 -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. {vgit → visidata/apps/vgit}/blame.py +5 -2
  18. {vgit → visidata/apps/vgit}/branch.py +31 -16
  19. {vgit → visidata/apps/vgit}/config.py +3 -3
  20. visidata/apps/vgit/diff.py +169 -0
  21. visidata/apps/vgit/gitsheet.py +161 -0
  22. {vgit → visidata/apps/vgit}/grep.py +6 -5
  23. visidata/apps/vgit/log.py +81 -0
  24. {vgit → visidata/apps/vgit}/main.py +18 -5
  25. {vgit → visidata/apps/vgit}/remote.py +8 -4
  26. visidata/apps/vgit/repos.py +71 -0
  27. {vgit → visidata/apps/vgit}/setup.py +6 -4
  28. visidata/apps/vgit/stash.py +69 -0
  29. visidata/apps/vgit/status.py +204 -0
  30. {vgit → visidata/apps/vgit}/statusbar.py +2 -0
  31. visidata/basesheet.py +63 -51
  32. visidata/canvas.py +208 -93
  33. visidata/choose.py +6 -6
  34. visidata/clean_names.py +29 -0
  35. visidata/clipboard.py +73 -17
  36. visidata/cliptext.py +220 -46
  37. visidata/cmdlog.py +88 -114
  38. visidata/color.py +142 -56
  39. visidata/column.py +121 -129
  40. visidata/ddw/input.ddw +74 -79
  41. visidata/ddw/regex.ddw +57 -0
  42. visidata/ddwplay.py +33 -14
  43. visidata/deprecated.py +77 -3
  44. visidata/desktop/visidata.desktop +7 -0
  45. visidata/editor.py +12 -6
  46. visidata/errors.py +6 -2
  47. visidata/experimental/__init__.py +0 -0
  48. visidata/experimental/diff_sheet.py +29 -0
  49. visidata/experimental/digit_autoedit.py +6 -0
  50. visidata/experimental/gdrive.py +89 -0
  51. visidata/experimental/google.py +37 -0
  52. visidata/experimental/gsheets.py +79 -0
  53. visidata/experimental/live_search.py +37 -0
  54. visidata/experimental/liveupdate.py +45 -0
  55. visidata/experimental/mark.py +133 -0
  56. visidata/experimental/noahs_tapestry/__init__.py +1 -0
  57. visidata/experimental/noahs_tapestry/tapestry.py +147 -0
  58. visidata/experimental/rownum.py +73 -0
  59. visidata/experimental/slide_cells.py +26 -0
  60. visidata/expr.py +8 -4
  61. visidata/extensible.py +22 -4
  62. visidata/features/__init__.py +0 -0
  63. visidata/features/addcol_audiometadata.py +42 -0
  64. visidata/features/addcol_histogram.py +34 -0
  65. visidata/features/canvas_save_svg.py +69 -0
  66. visidata/features/change_precision.py +46 -0
  67. visidata/features/cmdpalette.py +197 -0
  68. visidata/features/colorbrewer.py +363 -0
  69. visidata/{colorsheet.py → features/colorsheet.py} +17 -16
  70. visidata/features/command_server.py +105 -0
  71. visidata/features/currency_to_usd.py +70 -0
  72. visidata/{customdate.py → features/customdate.py} +2 -0
  73. visidata/features/dedupe.py +132 -0
  74. visidata/{describe.py → features/describe.py} +17 -15
  75. visidata/features/errors_guide.py +26 -0
  76. visidata/features/expand_cols.py +202 -0
  77. visidata/{fill.py → features/fill.py} +3 -1
  78. visidata/{freeze.py → features/freeze.py} +11 -6
  79. visidata/features/graph_seaborn.py +79 -0
  80. visidata/features/helloworld.py +10 -0
  81. visidata/features/hint_types.py +17 -0
  82. visidata/{incr.py → features/incr.py} +5 -0
  83. visidata/{join.py → features/join.py} +107 -53
  84. visidata/features/known_cols.py +21 -0
  85. visidata/features/layout.py +62 -0
  86. visidata/{melt.py → features/melt.py} +32 -21
  87. visidata/features/normcol.py +118 -0
  88. visidata/features/open_config.py +7 -0
  89. visidata/features/open_syspaste.py +18 -0
  90. visidata/features/ping.py +157 -0
  91. visidata/features/procmgr.py +208 -0
  92. visidata/features/random_sample.py +6 -0
  93. visidata/{regex.py → features/regex.py} +47 -31
  94. visidata/features/reload_every.py +55 -0
  95. visidata/features/rename_col_cascade.py +30 -0
  96. visidata/features/scroll_context.py +60 -0
  97. visidata/features/select_equal_selected.py +11 -0
  98. visidata/features/setcol_fake.py +65 -0
  99. visidata/{slide.py → features/slide.py} +77 -21
  100. visidata/features/sparkline.py +48 -0
  101. visidata/features/status_source.py +20 -0
  102. visidata/{sysedit.py → features/sysedit.py} +2 -1
  103. visidata/features/sysopen_mailcap.py +46 -0
  104. visidata/features/term_extras.py +13 -0
  105. visidata/{transpose.py → features/transpose.py} +5 -4
  106. visidata/features/type_ipaddr.py +73 -0
  107. visidata/features/type_url.py +11 -0
  108. visidata/{unfurl.py → features/unfurl.py} +9 -9
  109. visidata/{window.py → features/window.py} +2 -2
  110. visidata/form.py +50 -21
  111. visidata/freqtbl.py +81 -33
  112. visidata/fuzzymatch.py +414 -0
  113. visidata/graph.py +105 -33
  114. visidata/guide.py +200 -0
  115. visidata/help.py +75 -44
  116. visidata/hint.py +39 -0
  117. visidata/indexsheet.py +109 -0
  118. visidata/input_history.py +55 -0
  119. visidata/interface.py +58 -0
  120. visidata/keys.py +20 -16
  121. visidata/loaders/__init__.py +9 -0
  122. visidata/loaders/_pandas.py +61 -21
  123. visidata/loaders/api_airtable.py +70 -0
  124. visidata/loaders/api_bitio.py +102 -0
  125. visidata/loaders/api_matrix.py +148 -0
  126. visidata/loaders/api_reddit.py +306 -0
  127. visidata/loaders/api_zulip.py +249 -0
  128. visidata/loaders/archive.py +41 -7
  129. visidata/loaders/arrow.py +7 -7
  130. visidata/loaders/conll.py +49 -0
  131. visidata/loaders/csv.py +25 -7
  132. visidata/loaders/eml.py +3 -4
  133. visidata/loaders/f5log.py +1204 -0
  134. visidata/loaders/fec.py +325 -0
  135. visidata/loaders/fixed_width.py +2 -4
  136. visidata/loaders/frictionless.py +3 -3
  137. visidata/loaders/geojson.py +8 -5
  138. visidata/loaders/google.py +48 -0
  139. visidata/loaders/graphviz.py +4 -4
  140. visidata/loaders/hdf5.py +4 -4
  141. visidata/loaders/html.py +54 -12
  142. visidata/loaders/http.py +84 -30
  143. visidata/loaders/imap.py +20 -10
  144. visidata/loaders/jrnl.py +52 -0
  145. visidata/loaders/json.py +83 -29
  146. visidata/loaders/jsonla.py +74 -0
  147. visidata/loaders/lsv.py +15 -11
  148. visidata/loaders/mailbox.py +40 -0
  149. visidata/loaders/markdown.py +1 -3
  150. visidata/loaders/mbtiles.py +4 -5
  151. visidata/loaders/mysql.py +11 -13
  152. visidata/loaders/npy.py +7 -7
  153. visidata/loaders/odf.py +4 -1
  154. visidata/loaders/orgmode.py +428 -0
  155. visidata/loaders/pandas_freqtbl.py +14 -20
  156. visidata/loaders/parquet.py +62 -6
  157. visidata/loaders/pcap.py +3 -3
  158. visidata/loaders/pdf.py +4 -3
  159. visidata/loaders/png.py +19 -13
  160. visidata/loaders/postgres.py +9 -8
  161. visidata/loaders/rec.py +7 -3
  162. visidata/loaders/s3.py +342 -0
  163. visidata/loaders/sas.py +5 -5
  164. visidata/loaders/scrape.py +186 -0
  165. visidata/loaders/shp.py +6 -5
  166. visidata/loaders/spss.py +5 -6
  167. visidata/loaders/sqlite.py +68 -28
  168. visidata/loaders/texttables.py +1 -1
  169. visidata/loaders/toml.py +60 -0
  170. visidata/loaders/tsv.py +61 -19
  171. visidata/loaders/ttf.py +19 -7
  172. visidata/loaders/unzip_http.py +6 -5
  173. visidata/loaders/usv.py +1 -1
  174. visidata/loaders/vcf.py +16 -16
  175. visidata/loaders/vds.py +10 -7
  176. visidata/loaders/vdx.py +30 -5
  177. visidata/loaders/xlsb.py +8 -1
  178. visidata/loaders/xlsx.py +145 -25
  179. visidata/loaders/xml.py +6 -3
  180. visidata/loaders/xword.py +4 -4
  181. visidata/loaders/yaml.py +15 -5
  182. visidata/macros.py +129 -42
  183. visidata/main.py +119 -94
  184. visidata/mainloop.py +101 -155
  185. visidata/man/parse_options.py +2 -2
  186. visidata/man/vd.1 +302 -149
  187. visidata/man/vd.txt +291 -154
  188. visidata/memory.py +3 -3
  189. visidata/menu.py +104 -423
  190. visidata/metasheets.py +59 -141
  191. visidata/modify.py +78 -23
  192. visidata/motd.py +3 -3
  193. visidata/mouse.py +137 -0
  194. visidata/movement.py +43 -35
  195. visidata/optionssheet.py +99 -0
  196. visidata/path.py +113 -32
  197. visidata/pivot.py +73 -47
  198. visidata/plugins.py +65 -192
  199. visidata/pyobj.py +55 -205
  200. visidata/rename_col.py +20 -0
  201. visidata/save.py +37 -20
  202. visidata/search.py +54 -10
  203. visidata/selection.py +84 -5
  204. visidata/settings.py +162 -25
  205. visidata/sheets.py +239 -260
  206. visidata/shell.py +51 -21
  207. visidata/sidebar.py +162 -0
  208. visidata/sort.py +11 -4
  209. visidata/statusbar.py +114 -104
  210. visidata/stored_list.py +43 -0
  211. visidata/stored_prop.py +38 -0
  212. visidata/tests/benchmark.csv +52 -0
  213. visidata/tests/conftest.py +3 -3
  214. visidata/tests/test_cliptext.py +39 -0
  215. visidata/tests/test_commands.py +65 -7
  216. visidata/tests/test_edittext.py +2 -2
  217. visidata/tests/test_features.py +28 -0
  218. visidata/tests/test_menu.py +14 -0
  219. visidata/tests/test_path.py +13 -4
  220. visidata/text_source.py +53 -0
  221. visidata/textsheet.py +10 -3
  222. visidata/theme.py +44 -0
  223. visidata/themes/__init__.py +0 -0
  224. visidata/themes/ascii8.py +84 -0
  225. visidata/themes/asciimono.py +84 -0
  226. visidata/themes/light.py +17 -0
  227. visidata/threads.py +89 -40
  228. visidata/tuiwin.py +22 -0
  229. visidata/type_currency.py +22 -3
  230. visidata/type_date.py +31 -9
  231. visidata/type_floatsi.py +5 -1
  232. visidata/undo.py +17 -5
  233. visidata/utils.py +106 -23
  234. visidata/vdobj.py +28 -17
  235. visidata/windows.py +10 -0
  236. visidata/wrappers.py +9 -3
  237. visidata-3.0.1.data/data/share/applications/visidata.desktop +7 -0
  238. {visidata-2.11.1.data → visidata-3.0.1.data}/data/share/man/man1/vd.1 +302 -149
  239. {visidata-2.11.1.data → visidata-3.0.1.data}/data/share/man/man1/visidata.1 +302 -149
  240. visidata-3.0.1.data/scripts/vd2to3.vdx +9 -0
  241. {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/METADATA +12 -8
  242. visidata-3.0.1.dist-info/RECORD +258 -0
  243. {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/WHEEL +1 -1
  244. vgit/__init__.py +0 -1
  245. vgit/gitsheet.py +0 -164
  246. visidata/layout.py +0 -44
  247. visidata/misc.py +0 -5
  248. visidata-2.11.1.data/scripts/vgit +0 -9
  249. visidata-2.11.1.dist-info/RECORD +0 -155
  250. {vgit → visidata/apps/vgit}/__main__.py +0 -0
  251. {vgit → visidata/apps/vgit}/abort.py +0 -0
  252. /visidata/{repeat.py → features/repeat.py} +0 -0
  253. {visidata-2.11.1.data → visidata-3.0.1.data}/scripts/vd +0 -0
  254. {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/LICENSE.gpl3 +0 -0
  255. {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/entry_points.txt +0 -0
  256. {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/top_level.txt +0 -0
visidata/_types.py CHANGED
@@ -3,10 +3,19 @@
3
3
  import locale
4
4
  from visidata import options, TypedWrapper, vd, VisiData
5
5
 
6
- #__all__ = ['anytype', 'vdtype', ]
6
+ vd.help_float_fmt = '''
7
+ - fmt starting with `'%'` (like `%0.2f`) will use [:onclick https://docs.python.org/3.6/library/locale.html#locale.format_string]locale.format_string[/]
8
+ - other fmt (like `{:.02f}` is passed to Python [:onclick https://docs.python.org/3/library/string.html#custom-string-formatting)]string.format][/]
9
+ '''
10
+
11
+ vd.help_int_fmt = '''
12
+ - fmt starting with `'%'` (like `%04d`) will use [:onclick https://docs.python.org/3.6/library/locale.html#locale.format_string]locale.format_string[/]
13
+ - other fmt (like `{:4d}` is passed to Python [:onclick https://docs.python.org/3/library/string.html#custom-string-formatting)]string.format[/]
14
+ '''
15
+
16
+ vd.option('disp_float_fmt', '{:.02f}', 'default fmtstr to format float values', replay=True, help=vd.help_float_fmt)
17
+ vd.option('disp_int_fmt', '{:d}', 'default fmtstr to format int values', replay=True, help=vd.help_int_fmt)
7
18
 
8
- vd.option('disp_float_fmt', '{:.02f}', 'default fmtstr to format for float values', replay=True)
9
- vd.option('disp_int_fmt', '{:d}', 'default fmtstr to format for int values', replay=True)
10
19
 
11
20
  vd.numericTypes = [int,float]
12
21
 
@@ -97,6 +106,11 @@ vdtype(list, '')
97
106
  def isNumeric(vd, col):
98
107
  return col.type in vd.numericTypes
99
108
 
109
+ def deduceType(v):
110
+ if isinstance(v, (float, int)):
111
+ return type(v)
112
+ else:
113
+ return anytype
100
114
  ##
101
115
 
102
116
  @vd.numericType('%')
@@ -118,3 +132,7 @@ class vlen(int):
118
132
 
119
133
  def __len__(self):
120
134
  return self
135
+
136
+ vd.addGlobals(anytype=anytype,
137
+ vdtype=vdtype,
138
+ deduceType=deduceType)
visidata/_urlcache.py CHANGED
@@ -1,15 +1,17 @@
1
1
  import os
2
2
  import os.path
3
3
  import time
4
- from urllib.request import Request, urlopen
5
- import urllib.parse
6
4
 
7
- from visidata import vd, VisiData, Path, options, modtime
5
+ from visidata import vd, VisiData, Path, modtime
8
6
  from visidata.settings import _get_cache_dir
9
7
 
8
+
10
9
  @VisiData.global_api
11
10
  def urlcache(vd, url, days=1, text=True, headers={}):
12
11
  'Return Path object to local cache of url contents.'
12
+ from urllib.request import Request, urlopen
13
+ import urllib.parse
14
+
13
15
  cache_dir = _get_cache_dir()
14
16
  os.makedirs(cache_dir, exist_ok=True)
15
17
 
@@ -27,7 +29,7 @@ def urlcache(vd, url, days=1, text=True, headers={}):
27
29
  ret = fp.read()
28
30
  if text:
29
31
  ret = ret.decode('utf-8').strip()
30
- with p.open_text(mode='w', encoding='utf-8') as fpout:
32
+ with p.open(mode='w', encoding='utf-8') as fpout:
31
33
  fpout.write(ret)
32
34
  else:
33
35
  with p.open_bytes(mode='w') as fpout:
@@ -36,4 +38,15 @@ def urlcache(vd, url, days=1, text=True, headers={}):
36
38
  return p
37
39
 
38
40
 
41
+ @VisiData.api
42
+ def enable_requests_cache(vd):
43
+ try:
44
+ import requests
45
+ import requests_cache
46
+
47
+ requests_cache.install_cache(str(Path(os.path.join(vd.options.visidata_dir, 'httpcache'))), backend='sqlite', expire_after=24*60*60)
48
+ except ModuleNotFoundError:
49
+ vd.warning('install requests_cache for less intrusive scraping')
50
+
51
+
39
52
  vd.addGlobals({'urlcache': urlcache})
visidata/aggregators.py CHANGED
@@ -1,10 +1,28 @@
1
+ import sys
1
2
  import math
2
3
  import functools
3
4
  import collections
4
- from statistics import mode, stdev
5
+ import statistics
5
6
 
6
- from visidata import Progress, Column
7
- from visidata import *
7
+ from visidata import Progress, Sheet, Column, ColumnsSheet, VisiData
8
+ from visidata import vd, anytype, vlen, asyncthread, wrapply, AttrDict
9
+
10
+ vd.help_aggregators = '''# Choose Aggregators
11
+ Start typing an aggregator name or description.
12
+ Multiple aggregators can be added by separating spaces.
13
+
14
+ - `Enter` to select top aggregator.
15
+ - `Tab` to highlight top aggregator.
16
+
17
+ ## When Aggregator Highlighted
18
+
19
+ - `Tab`/`Shift+Tab` to cycle highlighted aggregator.
20
+ - `Enter` to select aggregators.
21
+ - `Space` to add highlighted aggregator.
22
+ - `0-9` to add numbered aggregator.
23
+ '''
24
+
25
+ vd.option('null_value', None, 'a value to be counted as null', replay=True)
8
26
 
9
27
 
10
28
  @Column.api
@@ -53,10 +71,11 @@ Column.aggregators = property(aggregators_get, aggregators_set)
53
71
 
54
72
 
55
73
  class Aggregator:
56
- def __init__(self, name, type, func, helpstr='foo'):
74
+ def __init__(self, name, type, funcRows, funcValues=None, helpstr='foo'):
57
75
  'Define aggregator `name` that calls func(col, rows)'
58
76
  self.type = type
59
- self.func = func
77
+ self.func = funcRows # funcRows(col, rows)
78
+ self.funcValues = funcValues # funcValues(values, *args)
60
79
  self.helpstr = helpstr
61
80
  self.name = name
62
81
 
@@ -66,19 +85,18 @@ class Aggregator:
66
85
  _defaggr = Aggregator
67
86
 
68
87
  @VisiData.api
69
- def aggregator(vd, name, func, helpstr='', *args, type=None):
70
- 'Define simple aggregator *name* that calls ``func(values, *args)`` to aggregate *values*. Use *type* to force the default type of the aggregated column.'
71
- def _func(col, rows): # wrap builtins so they can have a .type
88
+ def aggregator(vd, name, funcValues, helpstr='', *args, type=None):
89
+ 'Define simple aggregator *name* that calls ``funcValues(values, *args)`` to aggregate *values*. Use *type* to force the default type of the aggregated column.'
90
+ def _funcRows(col, rows): # wrap builtins so they can have a .type
72
91
  vals = list(col.getValues(rows))
73
92
  try:
74
- return func(vals, *args)
93
+ return funcValues(vals, *args)
75
94
  except Exception as e:
76
95
  if len(vals) == 0:
77
96
  return None
78
97
  return e
79
98
 
80
- vd.aggregators[name] = _defaggr(name, type, _func, helpstr)
81
- vd.addGlobals({name: func})
99
+ vd.aggregators[name] = _defaggr(name, type, _funcRows, funcValues=funcValues, helpstr=helpstr) # accepts a srccol + list of rows
82
100
 
83
101
  ## specific aggregator implementations
84
102
 
@@ -87,9 +105,11 @@ def mean(vals):
87
105
  if vals:
88
106
  return float(sum(vals))/len(vals)
89
107
 
90
- def median(values):
91
- L = sorted(values)
92
- return L[len(L)//2]
108
+ def _vsum(vals):
109
+ return sum(vals, start=type(vals[0] if len(vals) else 0)()) #1996
110
+
111
+ # start parameter in sum() added in Python 3.8
112
+ vsum = _vsum if sys.version_info[:2] >= (3, 8) else sum
93
113
 
94
114
  # http://code.activestate.com/recipes/511478-finding-the-percentile-of-the-values/
95
115
  def _percentile(N, percent, key=lambda x:x):
@@ -115,7 +135,7 @@ def _percentile(N, percent, key=lambda x:x):
115
135
 
116
136
  @functools.lru_cache(100)
117
137
  def percentile(pct, helpstr=''):
118
- return _defaggr('p%s'%pct, None, lambda col,rows,pct=pct: _percentile(sorted(col.getValues(rows)), pct/100), helpstr)
138
+ return _defaggr('p%s'%pct, None, lambda col,rows,pct=pct: _percentile(sorted(col.getValues(rows)), pct/100), helpstr=helpstr)
119
139
 
120
140
  def quantiles(q, helpstr):
121
141
  return [percentile(round(100*i/q), helpstr) for i in range(1, q)]
@@ -124,13 +144,13 @@ vd.aggregator('min', min, 'minimum value')
124
144
  vd.aggregator('max', max, 'maximum value')
125
145
  vd.aggregator('avg', mean, 'arithmetic mean of values', type=float)
126
146
  vd.aggregator('mean', mean, 'arithmetic mean of values', type=float)
127
- vd.aggregator('median', median, 'median of values')
128
- vd.aggregator('mode', mode, 'mode of values')
129
- vd.aggregator('sum', sum, 'sum of values')
147
+ vd.aggregator('median', statistics.median, 'median of values')
148
+ vd.aggregator('mode', statistics.mode, 'mode of values')
149
+ vd.aggregator('sum', vsum, 'sum of values')
130
150
  vd.aggregator('distinct', set, 'distinct values', type=vlen)
131
151
  vd.aggregator('count', lambda values: sum(1 for v in values), 'number of values', type=int)
132
152
  vd.aggregator('list', list, 'list of values')
133
- vd.aggregator('stdev', stdev, 'standard deviation of values', type=float)
153
+ vd.aggregator('stdev', statistics.stdev, 'standard deviation of values', type=float)
134
154
 
135
155
  vd.aggregators['q3'] = quantiles(3, 'tertiles (33/66th pctile)')
136
156
  vd.aggregators['q4'] = quantiles(4, 'quartiles (25/50/75th pctile)')
@@ -143,13 +163,14 @@ for pct in (10, 20, 25, 30, 33, 40, 50, 60, 67, 70, 75, 80, 90, 95, 99):
143
163
  vd.aggregators[f'p{pct}'] = percentile(pct, f'{pct}th percentile')
144
164
 
145
165
  # returns keys of the row with the max value
146
- vd.aggregators['keymax'] = _defaggr('keymax', anytype, lambda col, rows: col.sheet.rowkey(max(col.getValueRows(rows))[1]), 'key of the maximum value')
166
+ vd.aggregators['keymax'] = _defaggr('keymax', anytype, lambda col, rows: col.sheet.rowkey(max(col.getValueRows(rows))[1]), helpstr='key of the maximum value')
147
167
 
148
168
 
149
169
  ColumnsSheet.columns += [
150
170
  Column('aggregators',
151
171
  getter=lambda c,r:r.aggstr,
152
- setter=lambda c,r,v:setattr(r, 'aggregators', v))
172
+ setter=lambda c,r,v:setattr(r, 'aggregators', v),
173
+ help='change the metrics calculated in every Frequency or Pivot derived from the source sheet')
153
174
  ]
154
175
 
155
176
  @Sheet.api
@@ -186,10 +207,42 @@ def memo_aggregate(col, agg, rows):
186
207
  @VisiData.property
187
208
  def aggregator_choices(vd):
188
209
  return [
189
- {'key': agg, 'desc': v[0].helpstr if isinstance(v, list) else v.helpstr} for agg, v in vd.aggregators.items()
210
+ AttrDict(key=agg, desc=v[0].helpstr if isinstance(v, list) else v.helpstr)
211
+ for agg, v in vd.aggregators.items()
212
+ if not agg.startswith('p') # skip all the percentiles, user should use q# instead
190
213
  ]
191
214
 
192
215
 
193
- Sheet.addCommand('+', 'aggregate-col', 'addAggregators([cursorCol], chooseMany(aggregator_choices))', 'Add aggregator to current column')
194
- Sheet.addCommand('z+', 'memo-aggregate', 'for agg in chooseMany(aggregator_choices): cursorCol.memo_aggregate(aggregators[agg], selectedRows or rows)', 'memo result of aggregator over values in selected rows for current column')
195
- ColumnsSheet.addCommand('g+', 'aggregate-cols', 'addAggregators(selectedRows or source[0].nonKeyVisibleCols, chooseMany(aggregator_choices))', 'add aggregators to selected source columns')
216
+ @VisiData.api
217
+ def chooseAggregators(vd):
218
+ prompt = 'choose aggregators: '
219
+ def _fmt_aggr_summary(match, row, trigger_key):
220
+ formatted_aggrname = match.formatted.get('key', row.key) if match else row.key
221
+ r = ' '*(len(prompt)-3)
222
+ r += f'[:keystrokes]{trigger_key}[/] '
223
+ r += formatted_aggrname
224
+ if row.desc:
225
+ r += ' - '
226
+ r += match.formatted.get('desc', row.desc) if match else row.desc
227
+ return r
228
+
229
+ r = vd.activeSheet.inputPalette(prompt,
230
+ vd.aggregator_choices,
231
+ value_key='key',
232
+ formatter=_fmt_aggr_summary,
233
+ type='aggregators',
234
+ help=vd.help_aggregators,
235
+ multiple=True)
236
+
237
+ aggrs = r.split()
238
+ for aggr in aggrs:
239
+ vd.usedInputs[aggr] += 1
240
+ return aggrs
241
+
242
+ Sheet.addCommand('+', 'aggregate-col', 'addAggregators([cursorCol], chooseAggregators())', 'Add aggregator to current column')
243
+ Sheet.addCommand('z+', 'memo-aggregate', 'for agg in chooseAggregators(): cursorCol.memo_aggregate(aggregators[agg], selectedRows or rows)', 'memo result of aggregator over values in selected rows for current column')
244
+ ColumnsSheet.addCommand('g+', 'aggregate-cols', 'addAggregators(selectedRows or source[0].nonKeyVisibleCols, chooseAggregators())', 'add aggregators to selected source columns')
245
+
246
+ vd.addMenuItems('''
247
+ Column > Add aggregator > aggregate-col
248
+ ''')
File without changes
@@ -0,0 +1,8 @@
1
+ __all__ = '__title__ __author__ __version__ __description__ __license__ __copyright__'.split()
2
+
3
+ __title__ = 'vdsql'
4
+ __author__ = 'Saul Pwanson <code@saul.pw>'
5
+ __version__ = '0.3dev'
6
+ __description__ = 'VisiData for database queries'
7
+ __license__ = 'Apache License, Version 2.0'
8
+ __copyright__ = 'Copyright 2022 ' + __author__
@@ -0,0 +1,5 @@
1
+ from .__about__ import *
2
+ from ._ibis import *
3
+ from .bigquery import *
4
+ from .snowflake import *
5
+ from .clickhouse import *
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env python3
2
+
3
+ def main():
4
+ import ibis
5
+ import visidata
6
+ from visidata import main, vd
7
+
8
+ from . import __version__
9
+ visidata.__version_info__ = f'vdsql {__version__}'
10
+
11
+ for ext in "db ddb duckdb sqlite sqlite3".split():
12
+ setattr(vd, f"open_{ext}", vd.open_vdsql)
13
+
14
+ for entry_point in ibis.util.backend_entry_points():
15
+ if entry_point.name in ['bigquery', 'clickhouse', 'snowflake']:
16
+ # these have their own custom openurl_ funcs already installed
17
+ continue
18
+
19
+ attrname = f"openurl_{entry_point.name}"
20
+ # when running vdsql directly, override visidata builtin loader with vdsql loader #1929
21
+ setattr(vd, attrname, vd.open_vdsql)
22
+
23
+ main.vd_cli()
24
+
25
+
26
+ if __name__ == "__main__":
27
+ main()