-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Moved Idiom, etc. from Bastion.CARP to Bastion.Idiomatic
- Loading branch information
Showing
2 changed files
with
172 additions
and
108 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
""" | ||
Bastion.Idiomatic | ||
I provide tools for matching patterns to commands. | ||
""" | ||
import sys | ||
|
||
from Bastion.Common import freeze | ||
from Bastion.CARP import Request | ||
|
||
|
||
|
||
class Idiom(tuple): | ||
def __new__(cls, idiom): | ||
if isinstance(idiom, Idiom): | ||
return idiom | ||
elif isinstance(idiom, str): | ||
tokens = [token.strip() for token in idiom.split()] | ||
return tuple.__new__(cls, tokens) | ||
else: | ||
raise ValueError | ||
|
||
def slots(self): | ||
if not hasattr(self, "_slots"): | ||
slots = [ ] | ||
for token in self: | ||
if (token[0] == '_') and (token[-1] == '_'): | ||
slots.append(token[1:-1]) | ||
self._slots = tuple(slots) | ||
return self._slots | ||
|
||
@property | ||
def priority(self): | ||
weight = len(self) | ||
density = len([token for token in self if ((token[0] != '_') and (token[0] != '...'))]) | ||
return (weight, density) | ||
|
||
def __str__(self): | ||
return " ".join(self) | ||
|
||
def __repr__(self): | ||
return "🧠:{}".format(str(self)) | ||
|
||
def __mod__(self, other): | ||
""" | ||
congruence operator. | ||
i.e. Idiom("_ help") % "get help" -> (Idiom("_help"), ["get"]) | ||
""" | ||
words = [word.strip() for word in other.split()] | ||
|
||
if len(self) > len(words): | ||
#-- I have more tokens than other, I can't possibly be a match for other. | ||
return None | ||
|
||
planks = [ ] | ||
vargs = { } | ||
varnum = 0 | ||
|
||
for i, token in enumerate(self): | ||
word = words.pop(0) | ||
if token == '...': | ||
trailer = [word] + words | ||
planks.extend(trailer) | ||
vargs['...'] = trailer | ||
words = [ ] #-- we've consumed the remainder of the words in 'other' | ||
elif token == '_': | ||
planks.append(word) | ||
vargs[varnum] = word | ||
varnum += 1 | ||
elif (len(token) > 1) and (token[0] == '_') and (token[-1] == '_'): | ||
slot = token[1:-1] | ||
vargs[slot] = word | ||
planks.append(word) | ||
elif token != word: | ||
return None | ||
|
||
vargs = freeze(vargs) | ||
remainder = tuple(words) | ||
planks = tuple(planks) | ||
|
||
return (self, vargs, planks, remainder) | ||
|
||
|
||
class Prompt(Idiom): | ||
""" | ||
Prompt is an idiom coupled with a callable action function or method. | ||
""" | ||
def __init__(self, triggers, callable = None): | ||
self.triggers = [ ] | ||
self.action = callable | ||
|
||
self.matched = False | ||
self.idiom = None | ||
self.kwargs = { } | ||
self.planks = [ ] | ||
self.remainder = [ ] | ||
|
||
if isinstance(triggers, (Idiom, str)): | ||
triggers = [ triggers ] | ||
for item in triggers: | ||
self.triggers.append( Idiom(item) ) | ||
|
||
@property | ||
def matched(self): | ||
return (self.idiom is not None) | ||
|
||
def __mod__(self, other): | ||
for idiom in self.triggers: | ||
matched = idiom % other | ||
if matched: | ||
i, vargs, planks, remainder = matched | ||
self.matched = True | ||
self.idiom = idiom | ||
self.kwargs = vargs | ||
self.planks = planks | ||
self.remainder = remainder | ||
return (idiom, vargs, planks, remainder, self.action) | ||
return None | ||
|
||
|
||
#-- Some ideas for testing Idiom... | ||
#menu = [Idiom(m) for m in ["help", "help ...", "bank asset _ark_", "update asset _ark_"]] | ||
#menu = sorted(menu, key = lambda i: i.priority, reverse = True) | ||
|
||
def grokContext(optls): | ||
""" | ||
given a list of tokens, I interpret the tokens as either var:val, flag!, or bareword | ||
""" | ||
flags = set() | ||
vargs = { } | ||
bares = [ ] | ||
for word in optls: | ||
if word[-1] == '!': | ||
flag = word[:-1] | ||
flags.add(flag) | ||
vargs[flag] = True | ||
elif ':' in word: | ||
j = word.index(':') | ||
var = word[:j] | ||
val = word[j+1:] | ||
vargs[var] = val | ||
else: | ||
bares.append(word) | ||
return (vargs, flags, bares) | ||
|
||
|
||
|
||
def grokCLI(idioms, argls = None): | ||
""" | ||
Analyzes a sequence of tokens, assuming the tokens are sys.argv. | ||
Answers an instance of Bastion.CARP.Request | ||
""" | ||
if argls is None: | ||
argls = sys.argv[1:] | ||
|
||
menu = [ ] | ||
for i in idioms: | ||
if isinstance(i, (Idiom, Prompt)): | ||
menu.append(i) | ||
else: | ||
menu.append( Idiom(i) ) | ||
|
||
for idiom in menu: | ||
matched = idiom % opts | ||
if matched: | ||
i, vargs, planks, remainder = matched[:4] | ||
ctx, flagset, barewords = grokContext(remainder) | ||
vargs.update(ctx) | ||
return Request(str(idiom), planks, context = vargs, flags = flagset) | ||
|
||
return None |