aridity 92__tar.gz → 94__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: aridity
3
- Version: 92
3
+ Version: 94
4
4
  Summary: DRY config and template system, easily extensible with Python
5
5
  Home-page: https://pypi.org/project/aridity/
6
6
  Author: foyono
@@ -35,7 +35,7 @@ Process the given template to stdout using config from stdin.
35
35
  #### ConfigCtrl Objects
36
36
 
37
37
  ```python
38
- class ConfigCtrl()
38
+ class ConfigCtrl(Forkable)
39
39
  ```
40
40
 
41
41
  High level scope API.
@@ -191,6 +191,17 @@ def plusequals(prefix, suffix, scope)
191
191
 
192
192
  Assign expression to prefix plus an opaque key, i.e. add to list.
193
193
 
194
+ <a id="aridity.directives.commaequals"></a>
195
+
196
+ ###### commaequals
197
+
198
+ ```python
199
+ @prime
200
+ def commaequals(prefix, suffix, scope)
201
+ ```
202
+
203
+ Split expression on whitespace and make a list out of the parts.
204
+
194
205
  <a id="aridity.functions"></a>
195
206
 
196
207
  ### aridity.functions
@@ -461,20 +472,20 @@ Attempt to wrap the given value in a model object of the most specific type.
461
472
 
462
473
  ### aridity.scope
463
474
 
464
- <a id="aridity.scope.AbstractScope"></a>
475
+ <a id="aridity.scope.Scope"></a>
465
476
 
466
- #### AbstractScope Objects
477
+ #### Scope Objects
467
478
 
468
479
  ```python
469
- class AbstractScope(Resolvable)
480
+ class Scope(Resolvable)
470
481
  ```
471
482
 
472
- <a id="aridity.scope.AbstractScope.resolved"></a>
483
+ <a id="aridity.scope.Scope.resolved"></a>
473
484
 
474
485
  ###### resolved
475
486
 
476
487
  ```python
477
- def resolved(*path, **kwargs)
488
+ def resolved(*path)
478
489
  ```
479
490
 
480
491
  Follow the given path to get an expression, evaluate it (resolving any paths it requires, recursively), and return the resulting model object.
@@ -25,7 +25,7 @@ Process the given template to stdout using config from stdin.
25
25
  #### ConfigCtrl Objects
26
26
 
27
27
  ```python
28
- class ConfigCtrl()
28
+ class ConfigCtrl(Forkable)
29
29
  ```
30
30
 
31
31
  High level scope API.
@@ -181,6 +181,17 @@ def plusequals(prefix, suffix, scope)
181
181
 
182
182
  Assign expression to prefix plus an opaque key, i.e. add to list.
183
183
 
184
+ <a id="aridity.directives.commaequals"></a>
185
+
186
+ ###### commaequals
187
+
188
+ ```python
189
+ @prime
190
+ def commaequals(prefix, suffix, scope)
191
+ ```
192
+
193
+ Split expression on whitespace and make a list out of the parts.
194
+
184
195
  <a id="aridity.functions"></a>
185
196
 
186
197
  ### aridity.functions
@@ -451,20 +462,20 @@ Attempt to wrap the given value in a model object of the most specific type.
451
462
 
452
463
  ### aridity.scope
453
464
 
454
- <a id="aridity.scope.AbstractScope"></a>
465
+ <a id="aridity.scope.Scope"></a>
455
466
 
456
- #### AbstractScope Objects
467
+ #### Scope Objects
457
468
 
458
469
  ```python
459
- class AbstractScope(Resolvable)
470
+ class Scope(Resolvable)
460
471
  ```
461
472
 
462
- <a id="aridity.scope.AbstractScope.resolved"></a>
473
+ <a id="aridity.scope.Scope.resolved"></a>
463
474
 
464
475
  ###### resolved
465
476
 
466
477
  ```python
467
- def resolved(*path, **kwargs)
478
+ def resolved(*path)
468
479
  ```
469
480
 
470
481
  Follow the given path to get an expression, evaluate it (resolving any paths it requires, recursively), and return the resulting model object.
@@ -1,6 +1,6 @@
1
1
  'Interactive REPL.'
2
2
  from .repl import CommandReader
3
- from .scope import Scope
3
+ from .scope import StaticScope
4
4
  from .util import NoSuchPathException
5
5
  from traceback import print_exc
6
6
  import sys
@@ -8,7 +8,7 @@ import sys
8
8
  assert NoSuchPathException
9
9
 
10
10
  def main():
11
- scope = Scope()
11
+ scope = StaticScope.createchild()
12
12
  for command in CommandReader(sys.stdin):
13
13
  try:
14
14
  scope.execute(command)
@@ -1,7 +1,8 @@
1
1
  'Print given config (with optional path in config) as shell snippet.'
2
+ from .functions import OpaqueKey
2
3
  from .model import Boolean, Entry, Locator, Number, Text
3
- from .scope import Scope
4
- import os, sys
4
+ from .scope import Scope, StaticScope
5
+ import os, shlex, sys
5
6
 
6
7
  def _configpath(configname):
7
8
  if os.sep in configname:
@@ -12,20 +13,24 @@ def _configpath(configname):
12
13
  return path
13
14
  raise Exception(f"Not found: {configname}")
14
15
 
16
+ def _bashforeval(scope):
17
+ return ''.join(f"{name}={obj.tobash()}\n" for name, obj in scope.resolveditems())
18
+
15
19
  def _scopetobash(self, toplevel = False):
16
20
  if toplevel:
17
- return ''.join(f"{name}={obj.resolve(self).tobash()}\n" for name, obj in self.resolvables.items())
18
- if self.islist:
19
- return f"({' '.join(x.resolve(self).tobash() for _, x in self.resolvables.items())})"
20
- return Text(self.tobash(True)).tobash()
21
+ return _bashforeval(self)
22
+ d = dict(self.resolveditems())
23
+ if all(map(OpaqueKey.isopaque, d)):
24
+ return f"({' '.join(x.tobash() for x in d.values())})"
25
+ return shlex.quote(_bashforeval(self))
21
26
 
22
27
  Scope.tobash = _scopetobash
23
28
  Boolean.tobash = lambda self, toplevel: 'true' if self.booleanvalue else 'false'
24
29
  Number.tobash = lambda self: str(self.numbervalue)
25
- Text.tobash = lambda self: "'{}'".format(self.textvalue.replace("'", r"'\''"))
30
+ Text.tobash = lambda self: shlex.quote(self.textvalue)
26
31
 
27
32
  def main():
28
- scope = Scope()
33
+ scope = StaticScope.createchild()
29
34
  Locator(_configpath(sys.argv[1])).source(scope, Entry([]))
30
35
  sys.stdout.write(scope.resolved(*sys.argv[2:]).tobash(True))
31
36
 
@@ -1,8 +1,8 @@
1
1
  from .functions import OpaqueKey
2
2
  from .model import Entry, Locator, Resource, Stream, Text, wrap
3
- from .scope import Scope
3
+ from .scope import StaticScope
4
4
  from .search import resolvedscopeornone
5
- from .util import dotpy, NoSuchPathException, qualname, selectentrypoints, solo
5
+ from .util import dotpy, Forkable, NoSuchPathException, qualname, selectentrypoints, solo
6
6
  from io import StringIO
7
7
  from parabject import Parabject, register
8
8
  from pathlib import Path
@@ -31,16 +31,12 @@ def _processmainfunction(mainfunction):
31
31
  def _wrappathorstream(pathorstream):
32
32
  return (Stream if getattr(pathorstream, 'readable', lambda: False)() else Locator)(pathorstream)
33
33
 
34
- class ConfigCtrl:
34
+ class ConfigCtrl(Forkable):
35
35
  'High level scope API.'
36
36
 
37
37
  if 'HOME' in os.environ:
38
38
  settingsopenable = Locator(Path.home() / '.settings.arid')
39
39
 
40
- @classmethod
41
- def _of(cls, *args, **kwargs):
42
- return cls(*args, **kwargs)
43
-
44
40
  @property
45
41
  def node(self):
46
42
  return register(self, Config)
@@ -56,7 +52,7 @@ class ConfigCtrl:
56
52
  return register(self, WConfig)
57
53
 
58
54
  def __init__(self, basescope = None, prefix = None):
59
- self.basescope = Scope() if basescope is None else basescope
55
+ self.basescope = StaticScope.createchild() if basescope is None else basescope
60
56
  self.prefix = [] if prefix is None else prefix
61
57
 
62
58
  def loadappconfig(self, mainfunction, moduleresource, encoding = 'ascii', settingsoptional = False):
@@ -30,7 +30,7 @@ def source(prefix, suffix, scope):
30
30
  # XXX: Use full algo to get phrasescope?
31
31
  def rootscopes():
32
32
  for s in chain.from_iterable(phrasescope.scopedepths()):
33
- if [staticscope] == s.parents:
33
+ if [staticscope] == s.parents: # XXX: Slow?
34
34
  yield s
35
35
  staticscope = scope.staticscope()
36
36
  phrasescope = scope
@@ -74,8 +74,16 @@ def colonequals(prefix, suffix, scope):
74
74
  def plusequals(prefix, suffix, scope):
75
75
  'Assign expression to prefix plus an opaque key, i.e. add to list.'
76
76
  from .functions import OpaqueKey
77
- phrase = suffix.tophrase()
78
- scope[prefix.topath(scope) + (OpaqueKey(),)] = phrase
77
+ scope[prefix.topath(scope) + (OpaqueKey(),)] = suffix.tophrase()
78
+
79
+ @prime
80
+ def commaequals(prefix, suffix, scope):
81
+ 'Split expression on whitespace and make a list out of the parts.'
82
+ from .functions import OpaqueKey
83
+ basepath = prefix.topath(scope)
84
+ scope.getorcreatesubscope(basepath)
85
+ for r in suffix.words():
86
+ scope[basepath + (OpaqueKey(),)] = r
79
87
 
80
88
  @prime
81
89
  def _cat(prefix, suffix, scope):
@@ -92,4 +100,5 @@ def coredirectives():
92
100
  yield '=', equals
93
101
  yield ':=', colonequals
94
102
  yield '+=', plusequals
103
+ yield ',=', commaequals
95
104
  yield '<', _cat
@@ -103,7 +103,7 @@ def map_(scope, objsresolvable, *args):
103
103
  kname, vname, resolvable = args
104
104
  kname = kname.resolve(scope).textvalue
105
105
  vname = vname.resolve(scope).textvalue
106
- result = Scope(islist = True) # XXX: Really no parent?
106
+ result = Scope([])
107
107
  for k, v in objs.resolveditems():
108
108
  result.resolvables.put(k, resolvable.resolve(context(k, v)))
109
109
  return result
@@ -116,9 +116,9 @@ def map1(scope, contextresolvable, resultresolvable):
116
116
 
117
117
  def _flat(scope, listsresolvable):
118
118
  from .scope import Scope
119
- s = Scope(islist = True) # XXX: Really no parent?
120
- for lk, l in listsresolvable.resolve(scope).resolvables.items():
121
- for ok, obj in l.resolvables.items():
119
+ s = Scope([])
120
+ for lk, l in listsresolvable.resolve(scope).resolveditems():
121
+ for ok, obj in l.resolveditems():
122
122
  s.resolvables.put((lk, ok), obj)
123
123
  return s
124
124
 
@@ -129,16 +129,13 @@ def join(scope, partsresolvable, sepresolvable = None):
129
129
  'Concatenate the given list, using optional separator. Frequently used with `map`.'
130
130
  return Join(scope, partsresolvable).execute(sepresolvable)
131
131
 
132
- def _aslist(scope, *resolvables):
133
- return scope.resolved(*(r.resolve(scope).textvalue for r in resolvables), **{'aslist': True})
134
-
135
132
  def str_(scope, resolvable):
136
133
  'Coerce to string.'
137
134
  return resolvable.resolve(scope).totext()
138
135
 
139
136
  def list_(scope, *resolvables):
140
137
  'Create a list.'
141
- v = scope.createchild(islist = True)
138
+ v = scope.createchild()
142
139
  for r in resolvables:
143
140
  v[OpaqueKey(),] = r
144
141
  return v
@@ -250,8 +247,8 @@ class Join:
250
247
  break
251
248
  return resobj
252
249
 
253
- def getimpl(scope, *resolvables, **kwargs):
254
- return scope.resolved(*(r.resolve(scope).textvalue for r in resolvables), **kwargs)
250
+ def getimpl(scope, *resolvables):
251
+ return scope.resolved(*(r.resolve(scope).textvalue for r in resolvables))
255
252
 
256
253
  def corefunctions():
257
254
  yield 'gpg', gpg
@@ -272,7 +269,6 @@ def corefunctions():
272
269
  yield 'flat', _flat
273
270
  yield 'label', _label
274
271
  yield 'join', join
275
- yield ',', _aslist # XXX: Really comma?
276
272
  yield 'str', str_
277
273
  yield 'list', list_
278
274
  yield 'fork', _fork
@@ -1,4 +1,4 @@
1
- from .util import dotpy
1
+ from .util import dotpy, Forkable
2
2
  from contextlib import contextmanager
3
3
  from importlib import import_module
4
4
  from importlib.resources import files
@@ -37,8 +37,8 @@ class Resolvable(Struct):
37
37
 
38
38
  class Resolved(Resolvable):
39
39
 
40
- def resolve(self, scope, aslist = False):
41
- return List([self]) if aslist else self
40
+ def resolve(self, scope):
41
+ return self
42
42
 
43
43
  nullmonitor = lambda text: None
44
44
 
@@ -80,9 +80,7 @@ class Concat(Resolvable): # TODO: Ban in path components.
80
80
  self.parts = parts
81
81
  self.monitor = monitor
82
82
 
83
- def resolve(self, scope, aslist = False):
84
- if aslist:
85
- return List([part.resolve(scope) for part in self.parts if not part.ignorable])
83
+ def resolve(self, scope):
86
84
  result = Indeterminate
87
85
  negbuffer = NegBuffer()
88
86
  for part in self.parts:
@@ -111,7 +109,7 @@ class BaseSimpleValue(Resolved):
111
109
  value, = t
112
110
  return cls(value)
113
111
 
114
- def unravel(self):
112
+ def unravel(self, listmode = None):
115
113
  return self.scalar
116
114
 
117
115
  class Blank(BaseSimpleValue):
@@ -156,16 +154,12 @@ class Scalar(BaseScalar):
156
154
  def __init__(self, scalar):
157
155
  self.scalar = scalar
158
156
 
159
- class Text(BaseScalar):
157
+ class Text(BaseScalar, Forkable):
160
158
 
161
159
  @classmethod
162
160
  def joinpa(cls, s, l, t):
163
161
  return cls(''.join(t))
164
162
 
165
- @classmethod
166
- def _of(cls, textvalue):
167
- return cls(textvalue)
168
-
169
163
  @property
170
164
  def scalar(self):
171
165
  return self.textvalue
@@ -245,11 +239,7 @@ class Openable:
245
239
  with self.pushopen(scope) as f:
246
240
  return Stream(f).processtemplate(scope)
247
241
 
248
- class Locator(Resolved, Openable):
249
-
250
- @classmethod
251
- def _of(cls, pathvalue):
252
- return cls(pathvalue)
242
+ class Locator(Resolved, Openable, Forkable):
253
243
 
254
244
  @property
255
245
  def scalar(self):
@@ -267,11 +257,7 @@ class Locator(Resolved, Openable):
267
257
  def modulenameornone(self):
268
258
  pass
269
259
 
270
- class Resource(Resolved, Openable):
271
-
272
- @classmethod
273
- def _of(cls, *args):
274
- return cls(*args)
260
+ class Resource(Resolved, Openable, Forkable):
275
261
 
276
262
  def __init__(self, package_or_requirement, resource_name, encoding = 'ascii'):
277
263
  self.package_or_requirement = package_or_requirement
@@ -304,11 +290,7 @@ class Resource(Resolved, Openable):
304
290
  if self.resource_name.endswith(dotpy):
305
291
  return f"{self._packagename()}.{self.resource_name[:-len(dotpy)].replace('/', '.')}"
306
292
 
307
- class Binary(BaseScalar):
308
-
309
- @classmethod
310
- def _of(cls, *args):
311
- return cls(*args)
293
+ class Binary(BaseScalar, Forkable):
312
294
 
313
295
  @property
314
296
  def scalar(self):
@@ -375,9 +357,8 @@ class Call(Resolvable):
375
357
  if not a.ignorable:
376
358
  yield a
377
359
 
378
- def resolve(self, scope, aslist = False):
379
- result = self._functionvalue(scope)(scope.getresolvecontext(), *self._resolvables())
380
- return List([result]) if aslist else result
360
+ def resolve(self, scope):
361
+ return self._functionvalue(scope)(scope.getresolvecontext(), *self._resolvables())
381
362
 
382
363
  def resolvemulti(self, j, scope):
383
364
  f = self._functionvalue(scope.getresolvecontext())
@@ -388,13 +369,6 @@ class Call(Resolvable):
388
369
  for k, o in resolvable.resolve(scope).resolveditems():
389
370
  yield (j, k), o
390
371
 
391
- def List(objs):
392
- from .scope import Scope
393
- s = Scope(islist = True)
394
- for obj in objs:
395
- s.resolvables.put(object(), obj) # XXX: Not OpaqueKey?
396
- return s
397
-
398
372
  class Directive(Resolved):
399
373
 
400
374
  @property
@@ -438,25 +412,18 @@ class Stream(Resolved):
438
412
  with scope.staticscope().indent.push() as monitor:
439
413
  return templateparser(monitor)(self.streamvalue.read()).resolve(scope)
440
414
 
441
- class Entry(Struct):
415
+ class Entry(Struct, Forkable):
442
416
 
443
417
  @classmethod
444
418
  def pa(cls, s, l, t):
445
419
  return cls(t.asList())
446
420
 
447
- @classmethod
448
- def _of(cls, *args):
449
- return cls(*args)
450
-
451
421
  def __init__(self, resolvables):
452
422
  self.resolvables = resolvables
453
423
 
454
424
  def size(self):
455
425
  return sum(1 for r in self.resolvables if not r.ignorable)
456
426
 
457
- def resolve(self):
458
- return List([r.resolve(None) for r in self.resolvables])
459
-
460
427
  def word(self, i):
461
428
  word, = islice((r for r in self.resolvables if not r.ignorable), i, i + 1)
462
429
  return word
@@ -1,6 +1,5 @@
1
1
  from .search import Query
2
- from .util import CycleException, NoSuchPathException, TreeNoSuchPathException
3
- from itertools import chain
2
+ from .util import CycleException, Forkable, NoSuchPathException, TreeNoSuchPathException
4
3
 
5
4
  class BaseResolveContext:
6
5
 
@@ -16,8 +15,8 @@ class BaseResolveContext:
16
15
  def resolvables(self):
17
16
  return self.leafscope().resolvables
18
17
 
19
- def createchild(self, **kwargs):
20
- return self.leafscope().createchild(**kwargs)
18
+ def createchild(self):
19
+ return self.leafscope().createchild()
21
20
 
22
21
  def getresolvecontext(self):
23
22
  return self
@@ -25,8 +24,8 @@ class BaseResolveContext:
25
24
  def resolvableornone(self, key):
26
25
  return self.leafscope().resolvableornone(key)
27
26
 
28
- def resolved(self, *path, **kwargs):
29
- return self.resolvedimpl(path, kwargs) if path else self.leafscope()
27
+ def resolved(self, *path):
28
+ return self.resolvedimpl(path) if path else self.leafscope()
30
29
 
31
30
  def staticscope(self):
32
31
  return self.leafscope().staticscope()
@@ -39,15 +38,11 @@ class AnchorResolveContext(BaseResolveContext):
39
38
  def leafscope(self):
40
39
  return self.anchorscope
41
40
 
42
- def resolvedimpl(self, path, kwargs):
41
+ def resolvedimpl(self, path):
43
42
  hit = Query([], path).search(self.anchorscope)
44
- return hit.resolvable.resolve(ResolveContext(self.anchorscope, path, [hit.address]), **kwargs)
43
+ return hit.resolvable.resolve(ResolveContext(self.anchorscope, path, [hit.address]))
45
44
 
46
- class ResolveContext(BaseResolveContext):
47
-
48
- @classmethod
49
- def _of(cls, *args):
50
- return cls(*args)
45
+ class ResolveContext(BaseResolveContext, Forkable):
51
46
 
52
47
  def __init__(self, anchorscope, exprpath, addresses):
53
48
  self.anchorscope = anchorscope
@@ -58,11 +53,11 @@ class ResolveContext(BaseResolveContext):
58
53
  def leafscope(self):
59
54
  return Query([], self.scopepath).search(self.anchorscope).naiveresolve() if self.scopepath else self.anchorscope # XXX: Is naiveresolve correct here?
60
55
 
61
- def resolvedimpl(self, path, kwargs):
56
+ def resolvedimpl(self, path):
62
57
  try:
63
58
  hit = Query(self.scopepath, path).search(self.anchorscope)
64
59
  if hit.address in self.addresses: # XXX: Could it be valid to resolve the same address recursively with 2 different contexts?
65
60
  raise CycleException(path)
66
- return hit.resolvable.resolve(self._of(self.anchorscope, list(chain(self.scopepath, path)), self.addresses + [hit.address]), **kwargs)
61
+ return hit.resolvable.resolve(self._of(self.anchorscope, [*self.scopepath, *path], [*self.addresses, hit.address]))
67
62
  except NoSuchPathException as e:
68
63
  raise TreeNoSuchPathException(self.exprpath, [e])
@@ -4,9 +4,9 @@ from .model import Directive, Function, Hole, Resolvable, Scalar, star, Stream,
4
4
  from .resolve import AnchorResolveContext
5
5
  from .search import Query
6
6
  from .stacks import IndentStack, SimpleStack, ThreadLocalResolvable
7
- from .util import NoSuchPathException, OrderedDict, solo, UnsupportedEntryException
7
+ from .util import NoSuchPathException, solo, UnsupportedEntryException
8
8
  from itertools import chain
9
- import collections, os, sys, threading
9
+ import os, sys, threading
10
10
 
11
11
  class NotAPathException(Exception): pass
12
12
 
@@ -45,7 +45,7 @@ class Resolvables:
45
45
  return {}
46
46
 
47
47
  def __init__(self, scope):
48
- self.d = collections.OrderedDict()
48
+ self.d = {}
49
49
  self.scope = scope
50
50
 
51
51
  def put(self, key, resolvable):
@@ -69,7 +69,7 @@ class Resolvables:
69
69
  yield k, v
70
70
 
71
71
  # XXX: Isn't this Resolved rather than Resolvable?
72
- class AbstractScope(Resolvable): # TODO LATER: Some methods should probably be moved to Scope.
72
+ class Scope(Resolvable):
73
73
 
74
74
  nametypes = {str, type(None), OpaqueKey} # XXX: Is None still used by anything?
75
75
 
@@ -117,7 +117,7 @@ class AbstractScope(Resolvable): # TODO LATER: Some methods should probably be m
117
117
  def getresolvecontext(self):
118
118
  return AnchorResolveContext(self)
119
119
 
120
- def resolved(self, *path, **kwargs):
120
+ def resolved(self, *path):
121
121
  'Follow the given path to get an expression, evaluate it (resolving any paths it requires, recursively), and return the resulting model object.'
122
122
  # TODO LATER: API to filter out results unsuitable for context.
123
123
  for s in chain.from_iterable(self.scopedepths()):
@@ -127,7 +127,7 @@ class AbstractScope(Resolvable): # TODO LATER: Some methods should probably be m
127
127
  break
128
128
  except AttributeError:
129
129
  pass
130
- return g(self.getresolvecontext(), *map(Text, path), **kwargs)
130
+ return g(self.getresolvecontext(), *map(Text, path))
131
131
 
132
132
  def resolvableornone(self, key):
133
133
  return self.resolvables.getornone(key)
@@ -141,9 +141,12 @@ class AbstractScope(Resolvable): # TODO LATER: Some methods should probably be m
141
141
  yield scopes
142
142
  scopes = nextscopes
143
143
 
144
- def unravel(self):
145
- d = OrderedDict([k, o.unravel()] for k, o in self.resolveditems())
146
- return list(d) if self.islist or (d and all(OpaqueKey.isopaque(k) for k in d.keys())) else d
144
+ def unravel(self, listmode = 0):
145
+ def g():
146
+ args = [2] if 2 == listmode else []
147
+ for k, o in self.resolveditems():
148
+ yield k, o.unravel(*args)
149
+ return [v for _, v in g()] if listmode else dict(g())
147
150
 
148
151
  def staticscope(self):
149
152
  for s in chain.from_iterable(self.scopedepths()):
@@ -197,10 +200,40 @@ class AbstractScope(Resolvable): # TODO LATER: Some methods should probably be m
197
200
  for t in r.resolvemulti(k, self):
198
201
  yield t
199
202
 
200
- def createchild(self, **kwargs):
201
- return Scope([self], **kwargs)
203
+ def createchild(self):
204
+ return Scope([self])
205
+
206
+ def resolve(self, scope):
207
+ return self
208
+
209
+ def _slashfunction(scope, *resolvables):
210
+ path = None
211
+ for r in reversed(resolvables):
212
+ component = r.resolve(scope).textvalue
213
+ path = component if path is None else os.path.join(component, path)
214
+ if os.path.isabs(path):
215
+ break
216
+ return Text(os.path.join() if path is None else path)
217
+
218
+ class Slash(Text, Function):
219
+ 'As text, the platform slash. As function, join args using that slash, starting with the last absolute path (or using all args if all relative).'
220
+
221
+ def __init__(self):
222
+ Text.__init__(self, os.sep)
223
+ Function.__init__(self, _slashfunction)
224
+
225
+ class Star(Function, Directive):
202
226
 
203
- class StaticScope(AbstractScope):
227
+ protokey = object()
228
+
229
+ def __init__(self):
230
+ Function.__init__(self, star)
231
+ Directive.__init__(self, prime(self._star))
232
+
233
+ def _star(self, prefix, suffix, scope):
234
+ scope.getorcreatesubscope(prefix.topath(scope) + (self.protokey,)).execute(suffix)
235
+
236
+ class StaticScope(Scope):
204
237
 
205
238
  rootscopekey = OpaqueKey()
206
239
  stacktypes = dict(here = SimpleStack, indent = IndentStack)
@@ -234,44 +267,8 @@ class StaticScope(AbstractScope):
234
267
  setattr(threadlocals, name, stack)
235
268
  return stack
236
269
 
237
- class Slash(Text, Function):
238
- 'As text, the platform slash. As function, join args using that slash, starting with the last absolute path (or using all args if all relative).'
239
-
240
- def __init__(self):
241
- Text.__init__(self, os.sep)
242
- Function.__init__(self, slashfunction)
243
-
244
- def slashfunction(scope, *resolvables):
245
- path = None
246
- for r in reversed(resolvables):
247
- component = r.resolve(scope).textvalue
248
- path = component if path is None else os.path.join(component, path)
249
- if os.path.isabs(path):
250
- break
251
- return Text(os.path.join() if path is None else path)
252
-
253
- class Star(Function, Directive):
254
-
255
- protokey = object()
256
-
257
- def __init__(self):
258
- Function.__init__(self, star)
259
- Directive.__init__(self, prime(self._star))
260
-
261
- def _star(self, prefix, suffix, scope):
262
- scope.getorcreatesubscope(prefix.topath(scope) + (self.protokey,)).execute(suffix)
263
-
264
270
  StaticScope = StaticScope()
265
271
 
266
- class Scope(AbstractScope):
267
-
268
- def __init__(self, parents = None, islist = False):
269
- super().__init__([StaticScope] if parents is None else parents)
270
- self.islist = islist
271
-
272
- def resolve(self, scope):
273
- return self
274
-
275
272
  class ScalarScope(Scope):
276
273
 
277
274
  def __init__(self, parents, scalarobj):
@@ -73,20 +73,17 @@ def _lt(*depthspair):
73
73
  class Query:
74
74
 
75
75
  def __init__(self, prefix, path):
76
- assert path
77
76
  self.prefixlen = len(prefix)
78
77
  self.path = [*prefix, *path]
79
78
 
80
79
  def _search(self, scope):
81
- merges = [Iterator([], scope, self.path[0], self.prefixlen)]
80
+ merges = [[Hit([], None, scope)], *(Merge() for _ in self.path)]
82
81
  size = len(self.path)
83
- for _ in range(1, size):
84
- merges.append(Merge())
85
82
  for cursor, merge in enumerate(merges):
86
- zerocount = size - cursor - 1
83
+ zerocount = size - cursor
87
84
  yield None, zerocount
88
85
  for hit in merge:
89
- for x in range(cursor + 1, size):
86
+ for x in range(cursor, size):
90
87
  i = hit.iterornone(self.path[x], x < self.prefixlen)
91
88
  if i is None:
92
89
  break
@@ -94,7 +91,7 @@ class Query:
94
91
  hit = i.next()
95
92
  except StopIteration:
96
93
  break
97
- merges[x].add(i)
94
+ merges[1 + x].add(i)
98
95
  else:
99
96
  yield hit, zerocount
100
97
 
@@ -1,5 +1,5 @@
1
1
  from importlib.metadata import entry_points
2
- import collections, sys
2
+ import sys
3
3
 
4
4
  dotpy = '.py'
5
5
  inf = float('inf')
@@ -17,7 +17,7 @@ class TreeNoSuchPathException(NoSuchPathException):
17
17
 
18
18
  def __str__(self):
19
19
  path, causes = self.args # XXX: Also collect (and show) where in the tree the causes happened?
20
- causestrtocount = collections.OrderedDict()
20
+ causestrtocount = {}
21
21
  for causestr in map(str, causes):
22
22
  try:
23
23
  causestrtocount[causestr] += 1
@@ -35,52 +35,6 @@ class CycleException(UnparseNoSuchPathException): pass
35
35
 
36
36
  class UnsupportedEntryException(Exception): pass
37
37
 
38
- class OrderedDictWrapper:
39
-
40
- def __init__(self, *args):
41
- self.d = collections.OrderedDict(*args)
42
-
43
- def __bool__(self):
44
- return bool(self.d)
45
-
46
- def __nonzero__(self):
47
- return self.__bool__()
48
-
49
- class OrderedDict(OrderedDictWrapper):
50
-
51
- def __setitem__(self, k, v):
52
- self.d[k] = v
53
-
54
- def __getitem__(self, k):
55
- return self.d[k]
56
-
57
- def __delitem__(self, k):
58
- del self.d[k]
59
-
60
- def get(self, k, default = None):
61
- return self.d.get(k, default)
62
-
63
- def keys(self):
64
- return self.d.keys()
65
-
66
- def values(self):
67
- return self.d.values()
68
-
69
- def items(self):
70
- return self.d.items()
71
-
72
- def __iter__(self):
73
- return iter(self.d.values())
74
-
75
- def __eq__(self, that):
76
- return self.d == that
77
-
78
- def __repr__(self):
79
- return repr(self.d)
80
-
81
- def update(self, other):
82
- return self.d.update(other)
83
-
84
38
  def openresource(package_or_name, resource_name, encoding = 'ascii'):
85
39
  'Like `pkg_resources.resource_stream` but text mode.'
86
40
  from .model import Resource
@@ -112,3 +66,9 @@ def popattr(obj, name):
112
66
  val = getattr(obj, name)
113
67
  delattr(obj, name)
114
68
  return val
69
+
70
+ class Forkable:
71
+
72
+ @classmethod
73
+ def _of(cls, *args, **kwargs):
74
+ return cls(*args, **kwargs)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: aridity
3
- Version: 92
3
+ Version: 94
4
4
  Summary: DRY config and template system, easily extensible with Python
5
5
  Home-page: https://pypi.org/project/aridity/
6
6
  Author: foyono
@@ -35,7 +35,7 @@ Process the given template to stdout using config from stdin.
35
35
  #### ConfigCtrl Objects
36
36
 
37
37
  ```python
38
- class ConfigCtrl()
38
+ class ConfigCtrl(Forkable)
39
39
  ```
40
40
 
41
41
  High level scope API.
@@ -191,6 +191,17 @@ def plusequals(prefix, suffix, scope)
191
191
 
192
192
  Assign expression to prefix plus an opaque key, i.e. add to list.
193
193
 
194
+ <a id="aridity.directives.commaequals"></a>
195
+
196
+ ###### commaequals
197
+
198
+ ```python
199
+ @prime
200
+ def commaequals(prefix, suffix, scope)
201
+ ```
202
+
203
+ Split expression on whitespace and make a list out of the parts.
204
+
194
205
  <a id="aridity.functions"></a>
195
206
 
196
207
  ### aridity.functions
@@ -461,20 +472,20 @@ Attempt to wrap the given value in a model object of the most specific type.
461
472
 
462
473
  ### aridity.scope
463
474
 
464
- <a id="aridity.scope.AbstractScope"></a>
475
+ <a id="aridity.scope.Scope"></a>
465
476
 
466
- #### AbstractScope Objects
477
+ #### Scope Objects
467
478
 
468
479
  ```python
469
- class AbstractScope(Resolvable)
480
+ class Scope(Resolvable)
470
481
  ```
471
482
 
472
- <a id="aridity.scope.AbstractScope.resolved"></a>
483
+ <a id="aridity.scope.Scope.resolved"></a>
473
484
 
474
485
  ###### resolved
475
486
 
476
487
  ```python
477
- def resolved(*path, **kwargs)
488
+ def resolved(*path)
478
489
  ```
479
490
 
480
491
  Follow the given path to get an expression, evaluate it (resolving any paths it requires, recursively), and return the resulting model object.
@@ -49,7 +49,7 @@ class SourceInfo:
49
49
  sourceinfo = SourceInfo('.')
50
50
  setup(
51
51
  name = 'aridity',
52
- version = '92',
52
+ version = '94',
53
53
  description = 'DRY config and template system, easily extensible with Python',
54
54
  url = 'https://pypi.org/project/aridity/',
55
55
  author = 'foyono',
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes