synapse 2.222.0__py311-none-any.whl → 2.223.0__py311-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 synapse might be problematic. Click here for more details.

synapse/cortex.py CHANGED
@@ -61,6 +61,7 @@ import synapse.lib.stormlib.gen as s_stormlib_gen # NOQA
61
61
  import synapse.lib.stormlib.gis as s_stormlib_gis # NOQA
62
62
  import synapse.lib.stormlib.hex as s_stormlib_hex # NOQA
63
63
  import synapse.lib.stormlib.log as s_stormlib_log # NOQA
64
+ import synapse.lib.stormlib.pkg as s_stormlib_pkg # NOQA
64
65
  import synapse.lib.stormlib.xml as s_stormlib_xml # NOQA
65
66
  import synapse.lib.stormlib.auth as s_stormlib_auth # NOQA
66
67
  import synapse.lib.stormlib.cell as s_stormlib_cell # NOQA
@@ -72,6 +73,7 @@ import synapse.lib.stormlib.mime as s_stormlib_mime # NOQA
72
73
  import synapse.lib.stormlib.pack as s_stormlib_pack # NOQA
73
74
  import synapse.lib.stormlib.smtp as s_stormlib_smtp # NOQA
74
75
  import synapse.lib.stormlib.stix as s_stormlib_stix # NOQA
76
+ import synapse.lib.stormlib.task as s_stormlib_task # NOQA
75
77
  import synapse.lib.stormlib.yaml as s_stormlib_yaml # NOQA
76
78
  import synapse.lib.stormlib.basex as s_stormlib_basex # NOQA
77
79
  import synapse.lib.stormlib.cache as s_stormlib_cache # NOQA
@@ -2262,6 +2264,7 @@ class Cortex(s_oauth.OAuthMixin, s_cell.Cell): # type: ignore
2262
2264
  self.onfini(slab.fini)
2263
2265
 
2264
2266
  self.multiqueue = await slab.getMultiQueue('cortex:queue', nexsroot=self.nexsroot)
2267
+ self.stormpkgqueue = await slab.getMultiQueue('storm:pkg:queue', nexsroot=self.nexsroot)
2265
2268
 
2266
2269
  async def _initStormGraphs(self):
2267
2270
  path = os.path.join(self.dirn, 'slabs', 'graphs.lmdb')
@@ -3062,8 +3065,8 @@ class Cortex(s_oauth.OAuthMixin, s_cell.Cell): # type: ignore
3062
3065
  if not ok:
3063
3066
  break
3064
3067
 
3065
- curvers = vers
3066
- await self.setStormPkgVar(name, varname, vers)
3068
+ curvers = max(vers, await self.getStormPkgVar(name, varname, default=-1))
3069
+ await self.setStormPkgVar(name, varname, curvers)
3067
3070
  logger.info(f'{name} finished init vers={vers}: {vname}', extra=logextra)
3068
3071
 
3069
3072
  if onload is not None:
@@ -3348,6 +3351,91 @@ class Cortex(s_oauth.OAuthMixin, s_cell.Cell): # type: ignore
3348
3351
  for item in pkgvars.items():
3349
3352
  yield item
3350
3353
 
3354
+ async def addStormPkgQueue(self, pkgname, name):
3355
+ guid = s_common.guid((pkgname, name))
3356
+ if self.stormpkgqueue.exists(guid):
3357
+ mesg = f'Queue named {name} already exists for package {pkgname}!'
3358
+ raise s_exc.DupName(mesg=mesg)
3359
+
3360
+ info = {
3361
+ 'iden': guid,
3362
+ 'name': name,
3363
+ 'pkgname': pkgname,
3364
+ 'created': s_common.now()
3365
+ }
3366
+
3367
+ await self._push('storm:pkg:queue:add', pkgname, name, info)
3368
+
3369
+ @s_nexus.Pusher.onPush('storm:pkg:queue:add')
3370
+ async def _addStormPkgQueue(self, pkgname, name, info):
3371
+ guid = s_common.guid((pkgname, name))
3372
+ if self.stormpkgqueue.exists(guid):
3373
+ return
3374
+ await self.stormpkgqueue.add(guid, info)
3375
+
3376
+ async def listStormPkgQueues(self, pkgname=None):
3377
+ for pkginfo in self.stormpkgqueue.list():
3378
+ if pkgname is None or pkginfo['meta'].get('pkgname') == pkgname:
3379
+ yield pkginfo
3380
+
3381
+ async def getStormPkgQueue(self, pkgname, name):
3382
+ guid = s_common.guid((pkgname, name))
3383
+ return self.stormpkgqueue.status(guid)
3384
+
3385
+ async def delStormPkgQueue(self, pkgname, name):
3386
+ guid = s_common.guid((pkgname, name))
3387
+ if not self.stormpkgqueue.exists(guid):
3388
+ mesg = f'No queue named {name} exists for package {pkgname}!'
3389
+ raise s_exc.NoSuchName(mesg=mesg)
3390
+
3391
+ await self._push('storm:pkg:queue:del', pkgname, name)
3392
+
3393
+ @s_nexus.Pusher.onPush('storm:pkg:queue:del')
3394
+ async def _delStormPkgQueue(self, pkgname, name):
3395
+ guid = s_common.guid((pkgname, name))
3396
+ if not self.stormpkgqueue.exists(guid):
3397
+ return
3398
+ await self.stormpkgqueue.rem(guid)
3399
+
3400
+ async def stormPkgQueueGet(self, pkgname, name, offs=0, wait=False):
3401
+ guid = s_common.guid((pkgname, name))
3402
+ async for item in self.stormpkgqueue.gets(guid, offs, cull=False, wait=wait):
3403
+ return item
3404
+
3405
+ async def stormPkgQueueGets(self, pkgname, name, offs=0, wait=False, size=None):
3406
+ count = 0
3407
+ guid = s_common.guid((pkgname, name))
3408
+ async for item in self.stormpkgqueue.gets(guid, offs, cull=False, wait=wait):
3409
+
3410
+ yield item
3411
+
3412
+ count += 1
3413
+ if size is not None and count >= size:
3414
+ return
3415
+
3416
+ async def stormPkgQueuePuts(self, pkgname, name, items):
3417
+ return await self._push('storm:pkg:queue:puts', pkgname, name, items)
3418
+
3419
+ @s_nexus.Pusher.onPush('storm:pkg:queue:puts', passitem=True)
3420
+ async def _stormPkgQueuePuts(self, pkgname, name, items, nexsitem):
3421
+ nexsoff, nexsmesg = nexsitem
3422
+ guid = s_common.guid((pkgname, name))
3423
+ return await self.stormpkgqueue.puts(guid, items, reqid=nexsoff)
3424
+
3425
+ @s_nexus.Pusher.onPushAuto('storm:pkg:queue:cull')
3426
+ async def stormPkgQueueCull(self, pkgname, name, offs):
3427
+ guid = s_common.guid((pkgname, name))
3428
+ await self.stormpkgqueue.cull(guid, offs)
3429
+
3430
+ @s_nexus.Pusher.onPushAuto('storm:pkg:queue:pop')
3431
+ async def stormPkgQueuePop(self, pkgname, name, offs):
3432
+ guid = s_common.guid((pkgname, name))
3433
+ return await self.stormpkgqueue.pop(guid, offs)
3434
+
3435
+ async def stormPkgQueueSize(self, pkgname, name):
3436
+ guid = s_common.guid((pkgname, name))
3437
+ return self.stormpkgqueue.size(guid)
3438
+
3351
3439
  async def _cortexHealth(self, health):
3352
3440
  health.update('cortex', 'nominal')
3353
3441
 
@@ -4568,34 +4656,25 @@ class Cortex(s_oauth.OAuthMixin, s_cell.Cell): # type: ignore
4568
4656
  self.addStormCmd(s_stormlib_cortex.StormPoolGetCmd)
4569
4657
  self.addStormCmd(s_stormlib_cortex.StormPoolSetCmd)
4570
4658
 
4571
- for cdef in s_stormsvc.stormcmds:
4572
- await self._trySetStormCmd(cdef.get('name'), cdef)
4573
-
4574
- for cdef in s_storm.stormcmds:
4575
- await self._trySetStormCmd(cdef.get('name'), cdef)
4576
-
4577
- for cdef in s_stormlib_aha.stormcmds:
4578
- await self._trySetStormCmd(cdef.get('name'), cdef)
4579
-
4580
- for cdef in s_stormlib_gen.stormcmds:
4581
- await self._trySetStormCmd(cdef.get('name'), cdef)
4582
-
4583
- for cdef in s_stormlib_auth.stormcmds:
4584
- await self._trySetStormCmd(cdef.get('name'), cdef)
4585
-
4586
- for cdef in s_stormlib_macro.stormcmds:
4587
- await self._trySetStormCmd(cdef.get('name'), cdef)
4588
-
4589
- for cdef in s_stormlib_model.stormcmds:
4590
- await self._trySetStormCmd(cdef.get('name'), cdef)
4591
-
4592
- for cdef in s_stormlib_cortex.stormcmds:
4593
- await self._trySetStormCmd(cdef.get('name'), cdef)
4594
-
4595
- for cdef in s_stormlib_vault.stormcmds:
4596
- await self._trySetStormCmd(cdef.get('name'), cdef)
4597
-
4598
- for cdef in s_stormlib_index.stormcmds:
4659
+ cmdmods = [
4660
+ s_storm,
4661
+ s_stormsvc,
4662
+ s_stormlib_aha,
4663
+ s_stormlib_auth,
4664
+ s_stormlib_cortex,
4665
+ s_stormlib_gen,
4666
+ s_stormlib_index,
4667
+ s_stormlib_macro,
4668
+ s_stormlib_model,
4669
+ s_stormlib_pkg,
4670
+ s_stormlib_vault,
4671
+ ]
4672
+
4673
+ for cmod in cmdmods:
4674
+ for cdef in cmod.stormcmds:
4675
+ await self._trySetStormCmd(cdef.get('name'), cdef)
4676
+
4677
+ for cdef in s_stormlib_task.stormcmds:
4599
4678
  await self._trySetStormCmd(cdef.get('name'), cdef)
4600
4679
 
4601
4680
  async def _initPureStormCmds(self):
synapse/cryotank.py CHANGED
@@ -39,7 +39,7 @@ class CryoTank(s_base.Base):
39
39
  A CryoTank implements a stream of structured data.
40
40
  '''
41
41
  async def __anit__(self, dirn, iden, conf=None):
42
-
42
+ s_common.deprecated('synapse.cryotank.CryoTank', curv='2.223.0')
43
43
  await s_base.Base.__anit__(self)
44
44
 
45
45
  if conf is None:
synapse/lib/ast.py CHANGED
@@ -4527,9 +4527,6 @@ class N1Walk(Oper):
4527
4527
 
4528
4528
  def buildfilter(self, runt, destforms, cmpr):
4529
4529
 
4530
- if not isinstance(destforms, (tuple, list)):
4531
- destforms = (destforms,)
4532
-
4533
4530
  if '*' in destforms:
4534
4531
  if cmpr is not None:
4535
4532
  mesg = 'Wild card walk operations do not support comparison.'
@@ -4614,6 +4611,11 @@ class N1Walk(Oper):
4614
4611
  dest = await self.kids[1].compute(runt, path)
4615
4612
  dest = await s_stormtypes.toprim(dest)
4616
4613
 
4614
+ if isinstance(dest, (tuple, list)):
4615
+ dest = [await s_stormtypes.tostr(form) for form in dest]
4616
+ else:
4617
+ dest = (await s_stormtypes.tostr(dest),)
4618
+
4617
4619
  destfilt = self.buildfilter(runt, dest, cmpr)
4618
4620
 
4619
4621
  for verb in verbs:
synapse/lib/nexus.py CHANGED
@@ -572,7 +572,7 @@ class NexsRoot(s_base.Base):
572
572
 
573
573
  offs, args = item
574
574
  if offs != self.nexslog.index():
575
- logger.error('Local Nexus offset is out of sync from remote cell! Aborting mirror sync')
575
+ logger.error(f'Local Nexus offset is out of sync from remote cell! Aborting mirror sync. Local offs={self.nexslog.index()}, Remote {offs=}')
576
576
  await self.fini()
577
577
  return
578
578
 
synapse/lib/snap.py CHANGED
@@ -1305,6 +1305,11 @@ class Snap(s_base.Base):
1305
1305
  mesg = 'The snapshot is in read-only mode.'
1306
1306
  raise s_exc.IsReadOnly(mesg=mesg)
1307
1307
 
1308
+ useriden = meta.get('user')
1309
+ if useriden is None:
1310
+ mesg = 'meta is missing user key. Cannot process edits.'
1311
+ raise s_exc.BadArg(mesg=mesg, name='user')
1312
+
1308
1313
  wlyr = self.wlyr
1309
1314
  nodes = []
1310
1315
  callbacks = []
@@ -1344,12 +1349,12 @@ class Snap(s_base.Base):
1344
1349
  if etyp == s_layer.EDIT_NODE_ADD:
1345
1350
  node.bylayer['ndef'] = wlyr.iden
1346
1351
  callbacks.append((node.form.wasAdded, (node,)))
1347
- callbacks.append((self.view.runNodeAdd, (node,)))
1352
+ callbacks.append((self.view.runNodeAdd, (node, useriden)))
1348
1353
  continue
1349
1354
 
1350
1355
  if etyp == s_layer.EDIT_NODE_DEL:
1351
1356
  callbacks.append((node.form.wasDeleted, (node,)))
1352
- callbacks.append((self.view.runNodeDel, (node,)))
1357
+ callbacks.append((self.view.runNodeDel, (node, useriden)))
1353
1358
  continue
1354
1359
 
1355
1360
  if etyp == s_layer.EDIT_PROP_SET:
@@ -1365,7 +1370,7 @@ class Snap(s_base.Base):
1365
1370
  node.bylayer['props'][name] = wlyr.iden
1366
1371
 
1367
1372
  callbacks.append((prop.wasSet, (node, oldv)))
1368
- callbacks.append((self.view.runPropSet, (node, prop, oldv)))
1373
+ callbacks.append((self.view.runPropSet, (node, prop, oldv, useriden)))
1369
1374
  continue
1370
1375
 
1371
1376
  if etyp == s_layer.EDIT_PROP_DEL:
@@ -1381,7 +1386,7 @@ class Snap(s_base.Base):
1381
1386
  node.bylayer['props'].pop(name, None)
1382
1387
 
1383
1388
  callbacks.append((prop.wasDel, (node, oldv)))
1384
- callbacks.append((self.view.runPropSet, (node, prop, oldv)))
1389
+ callbacks.append((self.view.runPropSet, (node, prop, oldv, useriden)))
1385
1390
  continue
1386
1391
 
1387
1392
  if etyp == s_layer.EDIT_TAG_SET:
@@ -1391,7 +1396,7 @@ class Snap(s_base.Base):
1391
1396
  node.tags[tag] = valu
1392
1397
  node.bylayer['tags'][tag] = wlyr.iden
1393
1398
 
1394
- callbacks.append((self.view.runTagAdd, (node, tag, valu)))
1399
+ callbacks.append((self.view.runTagAdd, (node, tag, valu, useriden,)))
1395
1400
  continue
1396
1401
 
1397
1402
  if etyp == s_layer.EDIT_TAG_DEL:
@@ -1401,7 +1406,7 @@ class Snap(s_base.Base):
1401
1406
  node.tags.pop(tag, None)
1402
1407
  node.bylayer['tags'].pop(tag, None)
1403
1408
 
1404
- callbacks.append((self.view.runTagDel, (node, tag, oldv)))
1409
+ callbacks.append((self.view.runTagDel, (node, tag, oldv, useriden)))
1405
1410
  continue
1406
1411
 
1407
1412
  if etyp == s_layer.EDIT_TAGPROP_SET:
@@ -1436,14 +1441,15 @@ class Snap(s_base.Base):
1436
1441
  if etyp == s_layer.EDIT_EDGE_ADD:
1437
1442
  verb, n2iden = parms
1438
1443
  n2 = await self.getNodeByBuid(s_common.uhex(n2iden))
1439
- callbacks.append((self.view.runEdgeAdd, (node, verb, n2)))
1444
+ callbacks.append((self.view.runEdgeAdd, (node, verb, n2, useriden)))
1440
1445
 
1441
1446
  if etyp == s_layer.EDIT_EDGE_DEL:
1442
1447
  verb, n2iden = parms
1443
1448
  n2 = await self.getNodeByBuid(s_common.uhex(n2iden))
1444
- callbacks.append((self.view.runEdgeDel, (node, verb, n2)))
1449
+ callbacks.append((self.view.runEdgeDel, (node, verb, n2, useriden)))
1445
1450
 
1446
- [await func(*args) for (func, args) in callbacks]
1451
+ for func, args in callbacks:
1452
+ await func(*args)
1447
1453
 
1448
1454
  if actualedits:
1449
1455
  await self.fire('node:edits', edits=actualedits)
synapse/lib/storm.py CHANGED
@@ -439,196 +439,6 @@ stormcmds = (
439
439
  }
440
440
  ''',
441
441
  },
442
- {
443
- 'name': 'pkg.list',
444
- 'descr': 'List the storm packages loaded in the cortex.',
445
- 'cmdargs': (
446
- ('--verbose', {'default': False, 'action': 'store_true',
447
- 'help': 'Display build time for each package.'}),
448
- ),
449
- 'storm': '''
450
- init {
451
- $conf = ({
452
- "columns": [
453
- {"name": "name", "width": 40},
454
- {"name": "vers", "width": 10},
455
- ],
456
- "separators": {
457
- "row:outline": false,
458
- "column:outline": false,
459
- "header:row": "#",
460
- "data:row": "",
461
- "column": "",
462
- },
463
- })
464
- if $cmdopts.verbose {
465
- $conf.columns.append(({"name": "time", "width": 20}))
466
- }
467
- $printer = $lib.tabular.printer($conf)
468
- }
469
-
470
- $pkgs = $lib.pkg.list()
471
-
472
- if $($pkgs.size() > 0) {
473
- $lib.print('Loaded storm packages:')
474
- $lib.print($printer.header())
475
- for $pkg in $pkgs {
476
- $row = (
477
- $pkg.name, $pkg.version,
478
- )
479
- if $cmdopts.verbose {
480
- try {
481
- $row.append($lib.time.format($pkg.build.time, '%Y-%m-%d %H:%M:%S'))
482
- } catch StormRuntimeError as _ {
483
- $row.append('not available')
484
- }
485
- }
486
- $lib.print($printer.row($row))
487
- }
488
- } else {
489
- $lib.print('No storm packages installed.')
490
- }
491
- '''
492
- },
493
- {
494
- 'name': 'pkg.perms.list',
495
- 'descr': 'List any permissions declared by the package.',
496
- 'cmdargs': (
497
- ('name', {'help': 'The name (or name prefix) of the package.', 'type': 'str'}),
498
- ),
499
- 'storm': '''
500
- $pdef = $lib.null
501
- for $pkg in $lib.pkg.list() {
502
- if $pkg.name.startswith($cmdopts.name) {
503
- $pdef = $pkg
504
- break
505
- }
506
- }
507
-
508
- if (not $pdef) {
509
- $lib.warn(`Package ({$cmdopts.name}) not found!`)
510
- } else {
511
- if $pdef.perms {
512
- $lib.print(`Package ({$cmdopts.name}) defines the following permissions:`)
513
- for $permdef in $pdef.perms {
514
- $defv = $permdef.default
515
- if ( $defv = $lib.null ) {
516
- $defv = $lib.false
517
- }
518
- $text = `{('.').join($permdef.perm).ljust(32)} : {$permdef.desc} ( default: {$defv} )`
519
- $lib.print($text)
520
- }
521
- } else {
522
- $lib.print(`Package ({$cmdopts.name}) contains no permissions definitions.`)
523
- }
524
- }
525
- '''
526
- },
527
- {
528
- 'name': 'pkg.del',
529
- 'descr': 'Remove a storm package from the cortex.',
530
- 'cmdargs': (
531
- ('name', {'help': 'The name (or name prefix) of the package to remove.'}),
532
- ),
533
- 'storm': '''
534
-
535
- $pkgs = $lib.set()
536
-
537
- for $pkg in $lib.pkg.list() {
538
- if $pkg.name.startswith($cmdopts.name) {
539
- $pkgs.add($pkg.name)
540
- }
541
- }
542
-
543
- if $($pkgs.size() = 0) {
544
-
545
- $lib.print('No package names match "{name}". Aborting.', name=$cmdopts.name)
546
-
547
- } elif $($pkgs.size() = 1) {
548
-
549
- $name = $pkgs.list().index(0)
550
- $lib.print('Removing package: {name}', name=$name)
551
- $lib.pkg.del($name)
552
-
553
- } else {
554
-
555
- $lib.print('Multiple package names match "{name}". Aborting.', name=$cmdopts.name)
556
-
557
- }
558
- '''
559
- },
560
- {
561
- 'name': 'pkg.docs',
562
- 'descr': 'Display documentation included in a storm package.',
563
- 'cmdargs': (
564
- ('name', {'help': 'The name (or name prefix) of the package.'}),
565
- ),
566
- 'storm': '''
567
- $pdef = $lib.null
568
- for $pkg in $lib.pkg.list() {
569
- if $pkg.name.startswith($cmdopts.name) {
570
- $pdef = $pkg
571
- break
572
- }
573
- }
574
-
575
- if (not $pdef) {
576
- $lib.warn("Package ({name}) not found!", name=$cmdopts.name)
577
- } else {
578
- if $pdef.docs {
579
- for $doc in $pdef.docs {
580
- $lib.print($doc.content)
581
- }
582
- } else {
583
- $lib.print("Package ({name}) contains no documentation.", name=$cmdopts.name)
584
- }
585
- }
586
- '''
587
- },
588
- {
589
- 'name': 'pkg.load',
590
- 'descr': 'Load a storm package from an HTTP URL.',
591
- 'cmdargs': (
592
- ('url', {'help': 'The HTTP URL to load the package from.'}),
593
- ('--raw', {'default': False, 'action': 'store_true',
594
- 'help': 'Response JSON is a raw package definition without an envelope.'}),
595
- ('--verify', {'default': False, 'action': 'store_true',
596
- 'help': 'Enforce code signature verification on the storm package.'}),
597
- ('--ssl-noverify', {'default': False, 'action': 'store_true',
598
- 'help': 'Specify to disable SSL verification of the server.'}),
599
- ),
600
- 'storm': '''
601
- init {
602
- $ssl = $lib.true
603
- if $cmdopts.ssl_noverify { $ssl = $lib.false }
604
-
605
- $headers = ({'X-Synapse-Version': ('.').join($lib.version.synapse())})
606
-
607
- $resp = $lib.inet.http.get($cmdopts.url, ssl_verify=$ssl, headers=$headers)
608
-
609
- if ($resp.code != 200) {
610
- $lib.warn("pkg.load got HTTP code: {code} for URL: {url}", code=$resp.code, url=$cmdopts.url)
611
- $lib.exit()
612
- }
613
-
614
- $reply = $resp.json()
615
- if $cmdopts.raw {
616
- $pkg = $reply
617
- } else {
618
- if ($reply.status != "ok") {
619
- $lib.warn("pkg.load got JSON error: {code} for URL: {url}", code=$reply.code, url=$cmdopts.url)
620
- $lib.exit()
621
- }
622
-
623
- $pkg = $reply.result
624
- }
625
-
626
- $pkd = $lib.pkg.add($pkg, verify=$cmdopts.verify)
627
-
628
- $lib.print("Loaded Package: {name} @{version}", name=$pkg.name, version=$pkg.version)
629
- }
630
- ''',
631
- },
632
442
  {
633
443
  'name': 'version',
634
444
  'descr': 'Show version metadata relating to Synapse.',
@@ -1810,7 +1810,7 @@ class LibUser(s_stormtypes.Lib):
1810
1810
  'returns': {'type': 'boolean',
1811
1811
  'desc': 'True if the user has the requested permission, false otherwise.', }}},
1812
1812
  {'name': 'vars', 'desc': "Get a dictionary representing the current user's persistent variables.",
1813
- 'type': 'auth:user:vars', },
1813
+ 'type': 'user:vars:dict', },
1814
1814
  {'name': 'profile', 'desc': "Get a dictionary representing the current user's profile information.",
1815
1815
  'type': 'auth:user:profile', },
1816
1816
  {'name': 'iden', 'desc': 'The user GUID for the current storm user.', 'type': 'str'},
@@ -3,9 +3,9 @@ import bs4
3
3
  import synapse.lib.coro as s_coro
4
4
  import synapse.lib.stormtypes as s_stormtypes
5
5
 
6
- def htmlToText(html):
6
+ def htmlToText(html, separator='\n', strip=True):
7
7
  soup = bs4.BeautifulSoup(html, 'html5lib')
8
- return soup.get_text(separator='\n', strip=True)
8
+ return soup.get_text(separator=separator, strip=strip)
9
9
 
10
10
  @s_stormtypes.registry.registerLib
11
11
  class LibMimeHtml(s_stormtypes.Lib):
@@ -17,8 +17,12 @@ class LibMimeHtml(s_stormtypes.Lib):
17
17
  'type': {'type': 'function', '_funcname': 'totext',
18
18
  'args': (
19
19
  {'name': 'html', 'type': 'str', 'desc': 'The HTML text to be parsed.'},
20
+ {'name': 'separator', 'type': 'str', 'default': '\n',
21
+ 'desc': 'The string used to join text.'},
22
+ {'name': 'strip', 'type': 'boolean', 'default': True,
23
+ 'desc': 'Strip whitespace from the beginning and end of tag text.'},
20
24
  ),
21
- 'returns': {'type': 'str', 'desc': 'The newline-joined inner HTML text.', }
25
+ 'returns': {'type': 'str', 'desc': 'The separator-joined inner HTML text.', }
22
26
  }},
23
27
  )
24
28
 
@@ -30,6 +34,12 @@ class LibMimeHtml(s_stormtypes.Lib):
30
34
  }
31
35
 
32
36
  @s_stormtypes.stormfunc(readonly=True)
33
- async def totext(self, html):
37
+ async def totext(self, html, separator='\n', strip=True):
34
38
  html = await s_stormtypes.tostr(html)
35
- return await s_coro.semafork(htmlToText, html)
39
+ separator = await s_stormtypes.tostr(separator, noneok=True)
40
+ strip = await s_stormtypes.tobool(strip)
41
+
42
+ if separator is None:
43
+ separator = ''
44
+
45
+ return await s_coro.semafork(htmlToText, html, separator, strip)