I love to play with simple ideas to see where they lead. Recently, I've been experimenting with a file format and parsing algorithm for having a request "flow" from one approver to the next.
Here are the contents of an example file.
CEO -CFO --COO ---VP1 ----Director1 -----Manager1-1 -----Manager1-2 ----Director2 -----Manager2-1 -----Manager2-2 ---VP2 ----Director3 -----Manager3-1 -----Manager3-2 ----Director4 -----Manager4-1 -----Manager4-2
And here is some python output.
>>> from orgmodel import OrgModel >>> om = OrgModel('orgmodel.txt') >>> om.get_path('Manager4-2') ['Manager4-2', 'Director4', 'VP2', 'COO', 'CFO', 'CEO']
The approval chain is strictly linear: work upward from the starting node and fetch precisely one approver from any given level of indentation. The only exception is the zero level (no indentation), where all nodes are included.
Side note on "indentation": I originally was using tabs but switched to the uglier hyphen to keep things lean and simple. For starters, I don't want to deal with handling tab characters within an eventual input field on a browser form. Anyway, the actual character used is configurable, but a whitespace character will no longer work properly with the parser (because of preprocessing strip operations).
Here is a slightly different looking version of the file. Notice that CEO, CFO and COO are all now on the zero level.
CEO CFO COO -VP1 --Director1 ---Manager1-1 ---Manager1-2 --Director2 ---Manager2-1 ---Manager2-2 -VP2 --Director3 ---Manager3-1 ---Manager3-2 --Director4 ---Manager4-1 ---Manager4-2
This variation of the file is functionally identical to the first. Why? The parser cares about sequence, not hierarchical relationships. They are almost the same, but not quite. I don't know why, but that fascinates me, even if it is an insignificant detail. Does that sort of relationship have a name?
Anyhow, here is the code. It runs in both python 2 and 3.
class OrgModel: def __init__(self, model_file, indent_char='-'): self.node_names =  self.levels =  self.model_file = model_file with open(model_file) as f: for line in f: next_name_indented = line.strip() next_name = next_name_indented.lstrip(indent_char) depth = len(next_name_indented) - len(next_name) # append if line is non-blank if next_name: self.levels.append(depth) self.node_names.append(next_name) def validate(self): levels = self.levels # first row must have level 0 if levels != 0: return False for i in range(1, len(levels)): # if outdent, must be to a precedent level > 0 if levels[i] < levels [i - 1]: if levels[i] == 0: return False if levels[i] not in [levels[i] for i in range(0, i)]: return False # if indent, must be only one level elif levels[i] - levels[i - 1] > 1: return False return True def get_path(self, node_name): # start with the node itself node_index = self.node_names.index(node_name) out =  out.append(self.node_names[node_index]) # then walk upward toward the top of the model # (the "top" being defined as the first line in the model file) # the key rule is to take exactly one node from each sub-zero level, # plus all 0-levels "above" the "last" sub-zero level current_level = self.levels[node_index] for i in reversed(range(node_index)): level = self.levels[i] if level < current_level or level == 0: current_level = level out.append(self.node_names[i]) return out def main(): """ >>> om = OrgModel('orgmodel.txt') >>> om.get_path('Manager4-2') ['Manager4-2', 'Director4', 'VP2', 'COO', 'CFO', 'CEO'] """ if __name__ == '__main__': from doctest import testmod testmod()
I aim to keep playing with this idea to see how far I can push it. To be continued.
Contact: hello at escapefromsql.net