# - - - P l a n . l o o k u p S h o r t N a m e - - - #### Case 1: shortName contains no "#", and is a key in self.shortNameMap #-- 1 -- # [ if shortName contains a '#' -> ... # else -> # pathPart := shortName # anchor := None ] pathPart: shortName anchor: None #-- 2 -- # [ if pathPart is a key in self.shortNameMap -> # return a Target whose topic is the corresponding value # and whose anchor is (anchor) # else -> raise KeyError ] pathPart: shortName anchor: None return a Target with topic=self.shortNameMap[shortName] and anchor=None #-- TARGET -- OK [ if shortName is a string -> if shortName (up to but not including any "#" anchor name) matches the short name of a topic in self -> return a Target object representing that topic, and that anchor name if given else -> raise KeyError ] # - - - P l a n . l o o k u p S h o r t N a m e - - - #### Case 2: shortName contains a "#", and part up to first "#" is a key #### in self.shortNameMap #-- 1 -- # [ if shortName contains a '#' -> # pathPart := shortName up to the first '#' # anchor := shortName from the first '#' on # else -> ... ] pathPart: (shortName up to first "#") anchor: (from first "#" to end of shortName) #-- 2 -- # [ if pathPart is a key in self.shortNameMap -> # return a Target whose topic is the corresponding value # and whose anchor is (anchor) # else -> raise KeyError ] pathPart: (shortName up to first "#") anchor: (from first "#" to end of shortName) return a Target with topic=self.shortName[(shortName up to first "#")] and anchor=(from first "#" to end of shortName) #-- TARGET -- OK [ if shortName is a string -> if shortName (up to but not including any "#" anchor name) matches the short name of a topic in self -> return a Target object representing that topic, and that anchor name if given else -> raise KeyError ] # - - - P l a n . l o o k u p S h o r t N a m e - - - #### Case 3: shortName contains no "#", and shortName is not a key #### in self.shortNameMap #-- 1 -- # [ if shortName contains a '#' -> ... # else -> # pathPart := shortName # anchor := None ] pathPart: shortName anchor: None #-- 2 -- # [ if pathPart is a key in self.shortNameMap -> # return a Target whose topic is the corresponding value # and whose anchor is (anchor) # else -> raise KeyError ] pathPart: shortName anchor: None raise KeyError #-- TARGET -- OK [ if shortName is a string -> if shortName (up to but not including any "#" anchor name) matches the short name of a topic in self -> ... else -> raise KeyError ] # - - - P l a n . l o o k u p S h o r t N a m e - - - #### Case 4: shortName contains a "#", but part up to first "#" is #### not a key in self.shortNameMap #-- 1 -- # [ if shortName contains a '#' -> # pathPart := shortName up to the first '#' # anchor := shortName from the first '#' on # else -> ... ] pathPart: (shortName up to the first "#") anchor: (from first "#" to end of shortName) #-- 2 -- # [ if pathPart is a key in self.shortNameMap -> ... # else -> raise KeyError ] raise KeyError #-- TARGET -- OK [ if shortName is a string -> if shortName (up to but not including any "#" anchor name) matches the short name of a topic in self -> ... else -> raise KeyError ] # - - - P l a n . l o o k u p T e m p l a t e - - - #### Case 1: fileName is None, self.tmplPool has no Template for #### key=None, and DEFAULT_TEMPLATE does not name a readable, valid #### template file fileName: None return self.tmplPool [ fileName ] Log(): @+(error msg(s)) raise KeyError #-- TARGET -- OK [ if fileName is a string -> if (fileName is None) and (the default template file is valid) -> return a Template object representing the default template file else if fileName names a valid template file relative to the starting directory -> return a Template object representing that file else -> Log() +:= error message(s) raise KeyError ] # - - - P l a n . l o o k u p T e m p l a t e - - - #### Case 2: fileName is None, self.tmplPool has no Template for #### key=None, and DEFAULT_TEMPLATE names a readable, valid template file self.tmplPool: @+(a Template representing the file named by DEFAULT_TEMPLATE) return a Template representing the file named by DEFAULT_TEMPLATE #-- TARGET -- OK [ if fileName is a string or None -> if (fileName is None) and (the default template file is valid) -> return a Template object representing the default template file else if fileName names a valid template file relative to the starting directory -> return a Template object representing that file else -> Log() +:= error message(s) raise KeyError ] # - - - P l a n . l o o k u p T e m p l a t e - - - #### Case 3: fileName is None, but self.tmplPool has a Template for #### key=None return a Template for self.tmplPool[None] #-- TARGET -- OK [ if fileName is a string or None -> if (fileName is None) and (the default template file is valid) -> return a Template object representing the default template file else if fileName names a valid template file relative to the starting directory -> return a Template object representing that file else -> Log() +:= error message(s) raise KeyError ] # - - - P l a n . l o o k u p T e m p l a t e - - - #### Case 4: (fileName is a string) #### and (self.tmplPool has no entry for fileName) #### and (fileName does not name a readable, valid template file) Log(): @+(error msg(s)) raise KeyError #-- TARGET -- OK [ if fileName is a string or None -> if (fileName is None) and (the default template file is valid) -> return a Template object representing the default template file else if fileName names a valid template file relative to the starting directory -> return a Template object representing that file else -> Log() +:= error message(s) raise KeyError ] # - - - P l a n . l o o k u p T e m p l a t e - - - #### Case 5: (fileName is a string) #### and (self.tmplPool has no entry for fileName) #### and (fileName names a readable, valid template file) self.tmplPool: @+(a Template object representing the file named by fileName) return a Template object representing the file named by fileName #-- TARGET -- OK [ if fileName is a string or None -> if (fileName is None) and (the default template file is valid) -> ... else if fileName names a valid template file relative to the starting directory -> return a Template object representing that file else -> ... ] # - - - P l a n . l o o k u p T e m p l a t e - - - #### Case 6: (fileName is a string) #### and (self.tmplPool has an entry for fileName) return self.tmplPool[fileName] #-- TARGET -- OK [ if fileName is a string or None -> if (fileName is None) and (the default template file is valid) -> ... else if fileName names a valid template file relative to the starting directory -> return a Template object representing that file else -> Log() +:= error message(s) raise KeyError ] # - - - P l a n . _ _ i n i t _ _ - - - #### Case 1: fileName is not readable or not valid #-- 1 -- self.pathMap = pathMap self.nTopics = 0 self.tmplStack = [] self.pathMap: pathMap self.nTopics: 0 self.tmplStack: [] #-- 2 -- # [ self.tmplPool := a new, empty TmplPool object ] self.pathMap: pathMap self.nTopics: 0 self.tmplPool: an empty TmplPool self.tmplStack: [] #-- 3 -- if fileName is None: fileName = DEFAULT_PLAN_NAME self.pathMap: pathMap self.nTopics: 0 self.tmplPool: an empty TmplPool self.tmplStack: [] fileName: fileName, defaulting to DEFAULT_PLAN_NAME #-- 4 -- # [ if fileName names a readable, valid plan file -> # self.root := a tree of topics from the file # self.shortNameMap := as invariant # self.nTopics +:= number of topics from the file # else -> raise IOError ] self.pathMap: pathMap self.nTopics: 0 self.tmplPool: an empty TmplPool self.tmplStack: [] fileName: fileName, defaulting to DEFAULT_PLAN_NAME raise IOError #-- TARGET -- OK [ if (fileName is a string, defaulting to DEFAULT_PLAN_NAME) and (pathMap is a PathMap object) -> if (fileName names a readable, valid plan file in the context of pathMap) -> return a new Plan object representing that file else -> raise IOError ] # - - - P l a n . _ _ i n i t _ _ - - - #### Case 2: fileName is readable and valid #-- 1 -- self.pathMap = pathMap self.nTopics = 0 self.pathMap: pathMap self.nTopics: 0 #-- 2 -- # [ self.tmplPool := a new, empty TmplPool object ] self.pathMap: pathMap self.nTopics: 0 self.tmplPool: an empty TmplPool #-- 3 -- if fileName is None: fileName = DEFAULT_PLAN_NAME self.pathMap: pathMap self.nTopics: 0 self.tmplPool: an empty TmplPool fileName: fileName, defaulting to DEFAULT_PLAN_NAME if None #-- 4 -- # [ if fileName names a readable, valid plan file -> # self.root := a tree of topics from the file # self.shortNameMap := as invariant # self.nTopics +:= number of topics from the file # else -> raise IOError ] self.pathMap: pathMap self.nTopics: number of topics from fileName self.tmplPool: a TmplPool with Templates added for templates mentioned in fileName fileName: fileName, defaulting to DEFAULT_PLAN_NAME if None self.root: a tree of topics from fileName self.shortNameMap: a dictionary mapping N |-> T, where N is each short name from fileName and T is the corresponding Topic object #-- TARGET -- OK [ if (fileName is a string, defaulting to DEFAULT_PLAN_NAME) and (pathMap is a PathMap object) -> if (fileName names a readable, valid plan file in the context of pathMap) -> return a new Plan object representing that file else -> raise IOError ] # - - - P l a n . _ _ r e a d - - - #### Case 1: fileName is not readable #-- 1 -- topicStack = [] self.root = None errCount = Log().count() topicStack: [] self.root: None errCount: @(Log().count()) #-- 2 -- # [ if fileName can be opened for reading -> # scan := a new Scan object pointing to the start of that file # else -> # raise IOError ] topicStack: [] self.root: None errCount: @(Log().count()) raise IOError #-- TARGET -- OK [ if fileName names a readable, valid plan file -> self.root := a tree of topics from the file self.shortNameMap := as invariant self.nTopics +:= number of topics from the file else -> raise IOError ] # - - - P l a n . _ _ r e a d - - - #### Case 2: fileName is readable and valid but $template lines are #### out of balance #-- 1 -- topicStack = [] self.root = None errCount = Log().count() topicStack: [] self.root: None errCount: @(Log().count()) #-- 2 -- # [ if fileName can be opened for reading -> # scan := a new Scan object pointing to the start of that file # else -> ... ] topicStack: [] self.root: None errCount: @(Log().count()) scan: a Scan object pointing at start of fileName #-- 3 -- # [ if topicStack is a list of the path of nodes in the topic tree # from the root (if any) to the previous topic (if any) -> # scan := scan advanced to end of file # self.root +:= valid topics from the remainder of scan # self.shortNameMap +:= new entries added mapping shortName # |-> topic for valid topics from the # remainder of scan # topicStack := list of path of nodes in topic tree # from root to last topic from scan # self.tmplStack := self.tmplStack adjusted for # control lines in scan # self.nTopics +:= number of topic lines in scan # Log() +:= error message(s), if any, about # invalid lines from scan ] topicStack: list of path of nodes in topic tree from root to last topic from fileName self.root: valid topics from fileName errCount: @(Log().count()) scan: a Scan object pointing at end of fileName self.shortNameMap: @+(entries from fileName) self.tmplStack: @, adjusted for control lines from fileName self.nTopics: number of topic lines in scan #-- 4 -- # [ if self.tmplStack is empty -> I # else -> # Log() +:= error message(s) ] topicStack: [] self.root: valid topics from fileName errCount: @(Log().count()) scan: a Scan object pointing at end of fileName self.shortNameMap: @+(entries from fileName) self.tmplStack: @, adjusted for control lines from fileName Log(): @+(error msg(s)) #-- 5 -- scan.close() if errCount < Log().count(): raise IOError, "The plan file contained one or more errors." raise IOError #-- TARGET -- OK [ if fileName names a readable, valid plan file -> self.root := a tree of topics from the file self.shortNameMap := as invariant self.nTopics +:= number of topics from the file else -> raise IOError ] # - - - P l a n . _ _ r e a d - - - #### Case 3: fileName is readable but has invalid lines #-- 1 -- topicStack = [] self.root = None errCount = Log().count() topicStack: [] self.root: None errCount: @(Log().count()) #-- 2 -- # [ if fileName can be opened for reading -> # scan := a new Scan object pointing to the start of that file # else -> # raise IOError ] topicStack: [] self.root: None errCount: @(Log().count()) scan: a Scan object pointing at start of fileName #-- 3 -- # [ if topicStack is a list of the path of nodes in the topic tree # from the root (if any) to the previous topic (if any) -> # scan := scan advanced to end of file # self.root +:= valid topics from the remainder of scan # self.shortNameMap +:= new entries added mapping shortName # |-> topic for valid topics from the # remainder of scan # topicStack := path-to-topic(last topic from scan) # self.tmplStack := self.tmplStack adjusted for # control lines in scan # self.nTopics +:= number of topic lines in scan # Log() +:= error message(s), if any, about # invalid lines from scan ] topicStack: path-to-topic(last topic from scan) self.root: None errCount: @(Log().count()) scan: a Scan object pointing at end of fileName self.shortNameMap: @+(entries mapping shortName |-> topic from fileName) self.tmplStack: @, adjusted for control lines in fileName -> empty self.nTopics: number of topic lines in fileName Log(): @+(error msg(s)) #-- 4 -- # [ if self.tmplStack is empty -> I # else -> # Log() +:= error message(s) ] topicStack: path-to-topic(last topic from scan) self.root: None errCount: @(Log().count()) scan: a Scan object pointing at end of fileName self.shortNameMap: @+(entries mapping shortName |-> topic from fileName) self.tmplStack: @, adjusted for control lines in fileName -> empty Log(): @+(error msg(s)) #-- 5 -- scan.close() if errCount < Log().count(): raise IOError, "The plan file contained one or more errors." topicStack: path-to-topic(last topic from scan) self.root: None errCount: @(Log().count()) scan: a Scan object pointing at end of fileName self.shortNameMap: @+(entries mapping shortName |-> topic from fileName) self.tmplStack: @, adjusted for control lines in fileName -> empty Log(): @+(error msg(s)) raise IOError #-- TARGET -- OK [ if fileName names a readable, valid plan file -> self.root := a tree of topics from the file self.shortNameMap := as invariant self.nTopics +:= number of topics from the file else -> raise IOError ] # - - - P l a n . _ _ r e a d - - - #### Case 4: fileName is readable but empty #-- 1 -- topicStack = [] self.root = None errCount = Log().count() topicStack: [] self.root: None errCount: @(Log().count()) #-- 2 -- # [ if fileName can be opened for reading -> # scan := a new Scan object pointing to the start of that file # else -> # raise IOError ] topicStack: [] self.root: None errCount: @(Log().count()) scan: a Scan object pointing at fileName #-- 3 -- # [ if topicStack is a list of the path of nodes in the topic tree # from the root (if any) to the previous topic (if any) -> # scan := scan advanced to end of file # self.root +:= valid topics from the remainder of scan # self.shortNameMap +:= new entries added mapping shortName # |-> topic for valid topics from the # remainder of scan # topicStack := path-to-topic(last topic from scan) # self.tmplStack := self.tmplStack adjusted for # control lines in scan # self.nTopics +:= number of topic lines in scan # Log() +:= error message(s), if any, about # invalid lines from scan ] topicStack: [] self.root: None errCount: @(Log().count()) scan: a Scan object pointing at end of fileName self.shortNameMap: @ self.tmplStack: @ self.nTopics: @ #-- 4 -- # [ if self.tmplStack is empty -> I # else -> # Log() +:= error message(s) ] topicStack: [] self.root: None errCount: @(Log().count()) scan: a Scan object pointing at end of fileName self.shortNameMap: @ self.tmplStack: @ self.nTopics: @ #-- 5 -- if self.root is None: Log().error ( "The `%s' file contains no topics." ) topicStack: [] self.root: None errCount: @(Log().count()) scan: a Scan object pointing at end of fileName self.shortNameMap: @ self.tmplStack: @ self.nTopics: @ Log(): @+(error msg) #-- 6 -- scan.close() if errCount < Log().count(): raise IOError, "The plan file contained one or more errors." raise IOError #-- TARGET -- OK [ if fileName names a readable, valid plan file -> self.root := a tree of topics from the file self.shortNameMap := as invariant self.nTopics +:= number of topics from the file else -> raise IOError ] # - - - P l a n . _ _ r e a d - - - #### Case 5: fileName is readable and valid #-- 1 -- topicStack = [] self.root = None errCount = Log().count() topicStack: [] self.root: None errCount: @(Log().count()) #-- 2 -- # [ if fileName can be opened for reading -> # scan := a new Scan object pointing to the start of that file # else -> # raise IOError ] topicStack: [] self.root: None errCount: @(Log().count()) scan: a Scan object pointing at start of fileName #-- 3 -- # [ if topicStack is a list of the path of nodes in the topic tree # from the root (if any) to the previous topic (if any) -> # scan := scan advanced to end of file # self.root +:= valid topics from the remainder of scan # self.shortNameMap +:= new entries added mapping shortName # |-> topic for valid topics from the # remainder of scan # topicStack := path-to-topic(last topic from scan) # self.tmplStack := self.tmplStack adjusted for # control lines in scan # self.nTopics +:= number of topic lines in scan # Log() +:= error message(s), if any, about # invalid lines from scan ] topicStack: path-to-topic(last topic from fileName) self.root: topic tree from fileName errCount: @(Log().count()) scan: a Scan object pointing at start of fileName self.shortNameMap: @+(entries mapping short name |-> topic from fileName) self.tmplStack: [] (by case assumption---valid means $template lines balance) self.nTopics: number of topic lines in fileName #-- 4 -- # [ if self.tmplStack is empty -> I # else -> # Log() +:= error message(s) ] topicStack: path-to-topic(last topic from fileName) self.root: topic tree from fileName errCount: @(Log().count()) scan: a Scan object pointing at start of fileName self.shortNameMap: @+(entries mapping short name |-> topic from fileName) self.tmplStack: [] (by case assumption---valid means $template lines balance) self.nTopics: number of topic lines in fileName #-- 5 -- scan.close() if errCount < Log().count(): raise IOError, "The plan file contained one or more errors." topicStack: path-to-topic(last topic from fileName) self.root: topic tree from fileName errCount: @(Log().count()) scan: a Scan object pointing at start of fileName self.shortNameMap: @+(entries mapping short name |-> topic from fileName) self.tmplStack: [] (by case assumption---valid means $template lines balance) self.nTopics: number of topic lines in fileName #-- TARGET -- OK [ if fileName names a readable, valid plan file -> self.root := a tree of topics from the file self.shortNameMap := as invariant self.nTopics +:= number of topics from the file else -> raise IOError ] # - - - P l a n . _ _ r e a d - - - #### While/termination: #### OK because scan is advanced to next line or EOF in all cases, #### and loop terminates on scan.atEndFile # - - - P l a n . _ _ r e a d - - - #### While/false: file is empty #-- 1 -- topicStack = [] self.root = None errCount = Log().count() topicStack: [] self.root: None errCount: @(Log().count()) #-- 2 -- # [ if fileName can be opened for reading -> # scan := a new Scan object pointing to the start of that file # else -> # raise IOError ] topicStack: [] self.root: None errCount: @(Log().count()) scan: a Scan object pointing to the empty file #-- 3 -- # [ if topicStack is a list of the path of nodes in the topic tree # from the root (if any) to the previous topic (if any) -> # scan := scan advanced to end of file # self.root +:= valid topics from the remainder of scan # self.shortNameMap +:= new entries added mapping shortName # |-> topic for valid topics from the # remainder of scan # topicStack := path-to-topic(last topic from scan) # self.tmplStack := self.tmplStack adjusted for # control lines in scan # self.nTopics +:= number of topic lines in scan # Log() +:= error message(s), if any, about # invalid lines from scan ] topicStack: [] self.root: None errCount: @(Log().count()) scan: a Scan object pointing to the empty file #-- 4 -- # [ if self.tmplStack is empty -> I # else -> # Log() +:= error message(s) ] topicStack: [] self.root: None errCount: @(Log().count()) scan: a Scan object pointing to the empty file #-- 5 -- if self.root is None: Log().error ( "The `%s' file contains no topics." ) topicStack: [] self.root: None errCount: @(Log().count()) scan: a Scan object pointing to the empty file Log(): @+(error msg) #-- 6 -- scan.close() if errCount < Log().count(): raise IOError, "The plan file contained one or more errors." raise IOError #-- TARGET -- OK #-- 3 -- # [ if topicStack is a list of the path of nodes in the topic tree # from the root (if any) to the previous topic (if any) -> # scan := scan advanced to end of file # self.root +:= valid topics from the remainder of scan # self.shortNameMap +:= new entries added mapping shortName # |-> topic for valid topics from the # remainder of scan # topicStack := path-to-topic(last topic from scan) # self.tmplStack := self.tmplStack adjusted for # control lines in scan # self.nTopics +:= number of topic lines in scan # Log() +:= error message(s), if any, about # invalid lines from scan ] # - - - P l a n . _ _ r e a d - - - #### While/true, case 1: line is empty # -- 3 body -- # [ if the line in scan is empty -> I # else ... ] I #-- 3 -- # [ if topicStack is a list of the path of nodes in the topic tree # from the root (if any) to the previous topic (if any) -> # scan := scan advanced to end of file # self.root +:= valid topics from the remainder of scan # self.shortNameMap +:= new entries added mapping shortName # |-> topic for valid topics from the # remainder of scan # topicStack := path-to-topic(last topic from scan) # self.tmplStack := self.tmplStack adjusted for # control lines in scan # self.nTopics +:= number of topic lines in scan # Log() +:= error message(s), if any, about # invalid lines from scan ] scan: @, advanced to EOF self.root: @+(valid topics from rest of fileName) self.shortNameMap: @+(new entries mapping shortName |-> topic for valid topics from rest of fileName topicStack: path-to-topic(last topic from rest of fileName) self.tmplStack: @, adjusted for control lines in rest of fileName self.nTopics: @+(number of topic lines in rest of fileName) Log(): @+(errors from rest of fileName, if any) #-- TARGET -- OK #-- 3 -- # [ if topicStack is a list of the path of nodes in the topic tree # from the root (if any) to the previous topic (if any) -> # scan := scan advanced to end of file # self.root +:= valid topics from the remainder of scan # self.shortNameMap +:= new entries added mapping shortName # |-> topic for valid topics from the # remainder of scan # topicStack := path-to-topic(last topic from scan) # self.tmplStack := self.tmplStack adjusted for # control lines in scan # self.nTopics +:= number of topic lines in scan # Log() +:= error message(s), if any, about # invalid lines from scan ] # - - - P l a n . _ _ r e a d - - - #### While/true, case 2: line is an invalid control line # -- 3 body -- # [ if the line in scan is empty -> ... # else if line in scan is a valid control line -> ... # else if line in scan is a valid topic in the context of # topicStack -> ... # else -> ... # Log() +:= error message(s) # In all cases -> # scan := scan advanced to the next line or EOF ] Log(): @+(error message) scan: @, advanced after next line #-- 3 -- # [ if topicStack is a list of the path of nodes in the topic tree # from the root (if any) to the previous topic (if any) -> # scan := scan advanced to end of file # self.root +:= valid topics from the remainder of scan # self.shortNameMap +:= new entries added mapping shortName # |-> topic for valid topics from the # remainder of scan # topicStack := path-to-topic(last topic from scan) # self.tmplStack := self.tmplStack adjusted for # control lines in scan # self.nTopics +:= number of topic lines in scan # Log() +:= error message(s), if any, about # invalid lines from scan ] Log(): @+(error message)+(errors from rest of fileName, if any) scan: @, advanced to EOF self.root: @+(topics from rest of fileName) self.shortNameMap: @+(entries mapping shortName |-> topic for valid topics in rest of fileName) topicStack: path-to-topic(last valid topic in rest of fileName) self.tmplStack: @, adjusted for control lines in rest of fileName self.nTopics: @+(number of topics in rest of fileName) #-- TARGET -- OK #-- 3 -- # [ if topicStack is a list of the path of nodes in the topic tree # from the root (if any) to the previous topic (if any) -> # scan := scan advanced to end of file # self.root +:= valid topics from the remainder of scan # self.shortNameMap +:= new entries added mapping shortName # |-> topic for valid topics from the # remainder of scan # topicStack := path-to-topic(last topic from scan) # self.tmplStack := self.tmplStack adjusted for # control lines in scan # self.nTopics +:= number of topic lines in scan # Log() +:= error message(s), if any, about # invalid lines from scan ] # - - - P l a n . _ _ r e a d - - - #### While/true, case 3: line is a valid control line # -- 3 body -- # [ if the line in scan is empty -> I # else if line in scan is a valid control line -> # self := self modified to reflect that control line # else ... ] self: @, modified to reflect the control line next in fileName #-- 3 -- # [ if topicStack is a list of the path of nodes in the topic tree # from the root (if any) to the previous topic (if any) -> # scan := scan advanced to end of file # self.root +:= valid topics from the remainder of scan # self.shortNameMap +:= new entries added mapping shortName # |-> topic for valid topics from the # remainder of scan # topicStack := path-to-topic(last topic from scan) # self.tmplStack := self.tmplStack adjusted for # control lines in scan # self.nTopics +:= number of topic lines in scan # Log() +:= error message(s), if any, about # invalid lines from scan ] scan: @, advanced to EOF self.root: @+(valid topics from rest of fileName) self.shortNameMap: @+(entries mapping shortName |-> topic for valid topics in rest of fileName) topicStack: path-to-topic(last topic in rest of fileName) self.tmplStack: @, adjusted to reflect control line in first line of fileName, then adjusted to reflect control lines in rest of fileName self.nTopics: @+(number of topics in rest of fileName) Log(): @+(errors from rest of fileName, if any) #-- TARGET -- OK #-- 3 -- # [ if topicStack is a list of the path of nodes in the topic tree # from the root (if any) to the previous topic (if any) -> # scan := scan advanced to end of file # self.root +:= valid topics from the remainder of scan # self.shortNameMap +:= new entries added mapping shortName # |-> topic for valid topics from the # remainder of scan # topicStack := path-to-topic(last topic from scan) # self.tmplStack := self.tmplStack adjusted for # control lines in scan # self.nTopics +:= number of topic lines in scan # Log() +:= error message(s), if any, about # invalid lines from scan ] # - - - P l a n . _ _ r e a d - - - #### While/true, case 4: line is a valid topic line # -- 3 body -- # [ if the line in scan is empty -> I # else if line in scan is a valid control line -> ... # else if line in scan is a valid topic in the context of # topicStack -> # self.root := self.root with a Topic object representing # that topic # self.shortNameMap +:= an entry mapping that topic's # short name |-> the Topic object # topicStack := path-to-topic(that new Topic) # self.nTopics +:= 1 # else -> ... # In all cases -> # scan := scan advanced to the next line or EOF ] self.root: @+(topic from next line in fileName) self.shortNameMap: @+(entry mapping short name from next line in fileName |-> topic from next line) self.nTopics: @+1 scan: @, advanced one line #-- 3 -- # [ if topicStack is a list of the path of nodes in the topic tree # from the root (if any) to the previous topic (if any) -> # scan := scan advanced to end of file # self.root +:= valid topics from the remainder of scan # self.shortNameMap +:= new entries added mapping shortName # |-> topic for valid topics from the # remainder of scan # topicStack := path-to-topic(last topic from scan) # self.tmplStack := self.tmplStack adjusted for # control lines in scan # self.nTopics +:= number of topic lines in scan # Log() +:= error message(s), if any, about # invalid lines from scan ] self.root: @+(topic from next line in fileName)+(topics from remaining lines in fileName) self.shortNameMap: @+(entry mapping short name from next line in fileName |-> topic from next line)+(entries mapping short names |-> topics from remaining valid topics in fileName) self.nTopics: @+1+(# of valid topics in rest of fileName) scan: @, advanced to EOF topicStack: path-to-topic(last valid topic in fileName) self.tmplStack: @, adjusted for control lines in rest of fileName #-- TARGET -- OK #-- 3 -- # [ if topicStack is a list of the path of nodes in the topic tree # from the root (if any) to the previous topic (if any) -> # scan := scan advanced to end of file # self.root +:= valid topics from the remainder of scan # self.shortNameMap +:= new entries added mapping shortName # |-> topic for valid topics from the # remainder of scan # topicStack := path-to-topic(last topic from scan) # self.tmplStack := self.tmplStack adjusted for # control lines in scan # self.nTopics +:= number of topic lines in scan # Log() +:= error message(s), if any, about # invalid lines from scan ] # - - - P l a n . _ _ r e a d - - - #### While/true, case 4: line is not a valid topic line # -- 3 body -- # [ if the line in scan is empty -> ... # else if line in scan is a valid control line -> ... # else if line in scan is a valid topic in the context of # topicStack -> ... # else -> # Log() +:= error message(s) # In all cases -> # scan := scan advanced to the next line or EOF ] Log(): @+(error msg(s)) scan: @, advanced one line #-- 3 -- # [ if topicStack is a list of the path of nodes in the topic tree # from the root (if any) to the previous topic (if any) -> # scan := scan advanced to end of file # self.root +:= valid topics from the remainder of scan # self.shortNameMap +:= new entries added mapping shortName # |-> topic for valid topics from the # remainder of scan # topicStack := path-to-topic(last topic from scan) # self.tmplStack := self.tmplStack adjusted for # control lines in scan # self.nTopics +:= number of topic lines in scan # Log() +:= error message(s), if any, about # invalid lines from scan ] Log(): @+(error msg(s))+(error(s) in rest of fileName, if any) scan: @, advanced to EOF self.root: @+(valid topics from rest of fileName) self.shortNameMap: @+(entries mapping shortName |-> topics for valid topics in rest of fileName) topicStack: path-to-topic(last valid topic in rest of fileName) self.tmplStack: @, adjusted for control lines in rest of fileName self.nTopics: @+(# valid topics in rest of fileName) #-- TARGET -- OK #-- 3 -- # [ if topicStack is a list of the path of nodes in the topic tree # from the root (if any) to the previous topic (if any) -> # scan := scan advanced to end of file # self.root +:= valid topics from the remainder of scan # self.shortNameMap +:= new entries added mapping shortName # |-> topic for valid topics from the # remainder of scan # topicStack := path-to-topic(last topic from scan) # self.tmplStack := self.tmplStack adjusted for # control lines in scan # self.nTopics +:= number of topic lines in scan # Log() +:= error message(s), if any, about # invalid lines from scan ] # - - - P l a n . _ _ c o n t r o l L i n e - - - #### Case 1: line in scan doesn't start with a syntactically valid #### control word #-- 1 -- # [ if the line in scan starts with a syntactically valid # control word -> ... # else -> # scan := scan advanced past the valid part, if any # Log() +:= error message(s) # return ] scan: scan advanced valid part, if any Log(): @+(error msg) return #-- TARGET -- OK [ if scan is a Scan object -> if (CONTROL_LINE_CHAR+(the line in scan)) is a valid control line in the context of self -> ... else -> scan := scan advanced past the valid part, if any Log() +:= error message(s) ] # - - - P l a n . _ _ c o n t r o l L i n e - - - #### Case 2: line in scan starts with a syntactically valid #### control word but it's not one of the legal ones #-- 1 -- # [ if the line in scan starts with a syntactically valid # control word -> # scan := scan advanced past that word and any trailing # blanks # word := the control word, lowercased # else -> ... ] scan: @, advanced past control word word: control word from start of @(scan) #-- 2 -- # NB: If someday there are multiple control words, use a # dictionary to dispatch them. # [ if (word is not a valid control word) # or (word is valid but remainder of line in scan is not) -> # scan := scan advanced past the valid part, if any # Log() +:= error message(s) # else -> # scan := scan advanced to end of line # self := self modified as per the command from line in scan ] scan: @, advanced past control word word: control word from start of @(scan) Log(): @+(error msg) #-- TARGET -- OK [ if scan is a Scan object -> if (CONTROL_LINE_CHAR+(the line in scan)) is a valid control line in the context of self -> ... else -> scan := scan advanced past the valid part, if any Log() +:= error message(s) ] # - - - P l a n . _ _ c o n t r o l L i n e - - - #### Case 3: ("$update"+line in scan) is a valid $update line #-- 1 -- # [ if the line in scan starts with a syntactically valid # control word -> # scan := scan advanced past that word and any trailing # blanks # word := the control word, lowercased # else -> # scan := scan advanced past the valid part, if any # Log() +:= error message(s) # return ] scan: @, advanced past control word & any trailing blanks word: control word from @(scan) #-- 2 -- # NB: If someday there are multiple control words, use a # dictionary to dispatch them. # [ if (word is not a valid control word) # or (word is valid but remainder of line in scan is not) -> ... # else -> # scan := scan advanced to end of line # self := self modified as per the command from line in scan ] scan: @, advanced to end of line word: control word from @(scan) self: @, modified as per command line from scan #-- TARGET -- OK [ if scan is a Scan object -> if (CONTROL_LINE_CHAR+(the line in scan)) is a valid control line in the context of self -> scan := scan advanced to end of line self := self modified to reflect that control line else -> ... ] # - - - P l a n . _ _ c o n t r o l T e m p l a t e - - - #### Case 1: nothing remains on line in scan, but self.tmplStack has #### fewer than two templates #-- 1 -- if scan.atEndLine(): #-- 1.1 -- # [ if len(self.tmplStack) < 2 -> # Log() +:= error message # else -> # self.tmplStack := self.tmplStack with last element # removed ] Log(): @+(error msg) #-- TARGET -- OK [ if "$template"+(line in scan) is a valid $template line -> ... else -> scan := scan advanced past valid parts, if any Log() +:= error message(s) ] # - - - P l a n . _ _ c o n t r o l T e m p l a t e - - - #### Case 2: nothing remains on line in scan, and self.tmplStack has #### at least two templates #-- 1 -- if scan.atEndLine(): #-- 1.1 -- # [ if len(self.tmplStack) < 2 -> # Log() +:= error message # else -> # self.tmplStack := self.tmplStack with last element # removed ] self.tmplStack: @ with last element removed #-- TARGET -- OK [ if "$template"+(line in scan) is a valid $template line -> scan := scan advanced to end of line self := self modified to reflect that line else -> ... ] # - - - P l a n . _ _ c o n t r o l T e m p l a t e - - - #### Case 3: line in scan is not the name of a readable, valid template file #-- 1 -- if scan.atEndLine(): ... else: #-- 1.2 -- # [ if line in scan is the name of a readable, valid # template file -> ... # else -> # scan := scan advanced past valid part, if any # Log() +:= error message ] scan: @, advanced past valid part, if any Log(): @+(error msg) #-- TARGET -- OK [ if "$template"+(line in scan) is a valid $template line -> ... else -> scan := scan advanced past valid parts, if any Log() +:= error message(s) ] # - - - P l a n . _ _ c o n t r o l T e m p l a t e - - - #### Case 5: line in scan is the name of a readable, valid template file #-- 1 -- if scan.atEndLine(): ... else: #-- 1.2 -- # [ if line in scan is the name of a readable, valid # template file -> # scan := scan advanced to end of line # self.tmplStack := self.tmplStack + (a new Template # object representing that file) # else -> ... ] scan: @, advanced to end of line self.tmplStack: @+(a new Template object representing the template file named by the line in scan) #-- TARGET -- OK [ if "$template"+(line in scan) is a valid $template line -> scan := scan advanced to end of line self := self modified to reflect that line else -> ... ] # - - - P l a n . _ _ p u s h T e m p l a t e - - - #### Case 1: remainder of line in scan, minus any trailing blanks, #### does not name a template in self.tmplPool or a readable, valid #### template file #-- 1 -- # [ scan := scan advanced to end of line # tmplName := remainder of line in scan, trailing blanks trimmed ] scan: @, advanced to EOL tmplName: remainder of line in scan, minus any trailing blanks #-- 2 -- # [ if self.tmplPool contains a Template object for tmplName -> # template := that Template object # else if tmplName names a readable, valid template file -> # self.tmplPool := self.tmplPool + (a new Template object # representing that file) # else -> # Log() +:= error message(s) # return ] scan: @, advanced to EOL tmplName: remainder of line in scan, minus any trailing blanks Log(): @+(error msg) #-- TARGET -- OK [ if line in scan is the name of a readable, valid template file -> ... else -> scan := scan advanced past valid part, if any Log() +:= error message ] # - - - P l a n . _ _ p u s h T e m p l a t e - - - #### Case 2: remainder of line in scan, minus any trailing blanks, #### names a template found in self.tmplPool, or a readable, valid #### template file #-- 1 -- # [ scan := scan advanced to end of line # tmplName := remainder of line in scan, trailing blanks trimmed ] scan: @, advanced to end of line tmplName: remainder of line in @(scan), minus trailing blanks #-- 2 -- # [ if self.tmplPool contains a Template object for tmplName -> # template := that Template object # else ... ] scan: @, advanced to end of line tmplName: remainder of line in @(scan), minus trailing blanks template: Template from self.tmplPool whose name is remainder of line in @(scan), minus trailing blanks #-- 3 -- self.tmplStack.append ( template ) scan: @, advanced to end of line tmplName: remainder of line in @(scan), minus trailing blanks template: Template from self.tmplPool whose name is remainder of line in @(scan), minus trailing blanks self.tmplStack: @+(Template from self.tmplPool whose name is remainder of line in @(scan), minus trailing blanks #-- TARGET -- OK [ if line in scan is the name of a readable, valid template file -> scan := scan advanced to end of line self.tmplStack := self.tmplStack + (a new Template object representing that file) else -> ... ] # - - - P l a n . _ _ b u i l d T o p i c - - - #### Case 1: line in scan is not syntactically valid #-- 1 -- # [ if the line in scan is a syntactically valid topic line -> ... # else -> # Log() +:= error message(s) # return Log(): @+(error msg) #-- TARGET -- OK [ if (self.root is the tree of existing topics) and (scan is a Scan object) and (topicStack is path-to-topic(previous topic, if any) -> if the line in scan is a valid topic line -> ... else -> Log() +:= error message(s) ] # - - - P l a n . _ _ b u i l d T o p i c - - - #### Case 2: line in scan is syntactically valid, but its level #### is more than one larger than topicStack #-- 1 -- # [ if the line in scan is a syntactically valid topic line -> # rawTopic := a RawTopic object containing the fields # from that line # else -> ... ] rawTopic: a RawTopic object representing @(scan) #-- 2 -- # [ if (rawTopic.depth - 1) > (size of topicStack) -> # Log() +:= error message about missing levels # return # else -> ... ] rawTopic: a RawTopic object representing @(scan) Log(): @+(error msg) #-- TARGET -- OK [ if (self.root is the tree of existing topics) and (scan is a Scan object) and (topicStack is path-to-topic(previous topic, if any) -> if the line in scan is a valid topic line -> ... else -> Log() +:= error message(s) ] # - - - P l a n . _ _ b u i l d T o p i c - - - #### Case 3: line in scan is syntactically valid, and its level #### is no more than one larger than topicStack, but line is invalid #-- 1 -- # [ if the line in scan is a syntactically valid topic line -> # rawTopic := a RawTopic object containing the fields # from that line # else -> # Log() +:= error message(s) # return rawTopic: a RawTopic representing line in @(scan) #-- 2 -- # [ if (rawTopic.depth - 1) > (size of topicStack) -> # Log() +:= error message about missing levels # return # else -> # topicStack := topicStack with all entries removed # whose depth is >= rawTopic.depth ] rawTopic: a RawTopic representing line in @(scan) topicStack: @ with all entries removed whose depth is >= rawTopic.depth #-- 3 -- # [ if rawTopic is invalid -> # Log() +:= error message(s) # else ... ] rawTopic: a RawTopic representing line in @(scan) topicStack: @ with all entries removed whose depth is >= rawTopic.depth Log(): @+(error msg) #-- TARGET -- OK [ if (self.root is the tree of existing topics) and (scan is a Scan object) and (topicStack is path-to-topic(previous topic, if any) -> if the line in scan is a valid topic line -> ... else -> topicStack := topicStack, possibly trimmed of elements not shallower than new topic line Log() +:= error message(s) ] # - - - P l a n . _ _ b u i l d T o p i c - - - #### Case 5: line in scan is valid and topicStack is empty #-- 1 -- # [ if the line in scan is a syntactically valid topic line -> # scan := scan advanced to end of line # rawTopic := a RawTopic object containing the fields # from that line # else -> # Log() +:= error message(s) # return scan: @, advanced to EOL rawTopic: a RawTopic representing line in @(scan) #-- 2 -- # [ if (rawTopic.depth - 1) > (size of topicStack) -> # Log() +:= error message about missing levels # return # else -> # topicStack := topicStack with all entries removed # whose depth is >= rawTopic.depth ] scan: @, advanced to EOL rawTopic: a RawTopic representing line in @(scan) topicStack: @-(entries with depth >= depth of line in @(scan)) #-- 3 -- # [ if rawTopic is invalid -> ... # else if rawTopic is valid and topicStack is empty -> # self.root := a new Topic made from rawTopic, and # no parent # self.shortNameMap +:= an entry mapping rawTopic.shortName # |-> that new Topic # else -> ... ] scan: @, advanced to EOL rawTopic: a RawTopic representing line in @(scan) topicStack: @-(entries with depth >= depth of line in @(scan)) self.root: a new Topic made from line in @(scan), with no parent self.shortNameMap: @+(entry mapping short name from line in @(scan) |-> Topic made from line in @(scan)) #-- TARGET -- OK [ if (self.root is the tree of existing topics) and (scan is a Scan object) and (topicStack is path-to-topic(previous topic, if any) -> if the line in scan is a valid topic line -> scan := scan advanced to end of line self.root := self.root with that topic added self.shortNameMap +:= a new entry mapping the short name from that line |-> the new Topic made from that line topicStack := path-to-topic(the new Topic made from that line) else -> ... ] # - - - P l a n . _ _ b u i l d T o p i c - - - #### Case 5: line in scan is valid and topicStack is not empty #-- 1 -- # [ if the line in scan is a syntactically valid topic line -> # scan := scan advanced to end of line # rawTopic := a RawTopic object containing the fields # from that line # else -> # Log() +:= error message(s) # return scan: @, advanced to EOL rawTopic: a RawTopic object made from the line in @(scan) #-- 2 -- # [ if (rawTopic.depth - 1) > (size of topicStack) -> ... # else -> # topicStack := topicStack with all entries removed # whose depth is >= rawTopic.depth ] scan: @, advanced to EOL rawTopic: a RawTopic object made from the line in @(scan) topicStack: @-(entries as deep as or deeper than topic line in @(scan)) #-- 3 -- # [ if rawTopic is invalid -> ... # else if rawTopic is valid and topicStack is empty -> ... # else -> # self.root := self.root with a new Topic, made # from rawTopic, added as the next # child of topicStack[-1] # topicStack := topicStack + (that new Topic) # self.shortNameMap +:= an entry mapping rawTopic.shortName # |-> that new Topic ] scan: @, advanced to EOL rawTopic: a RawTopic object made from the line in @(scan) topicStack: @-(entries as deep as or deeper than topic line in @(scan))+(a new Topic made from the line in @(scan)) self.root: @ with a new Topic, made from the line in @(scan), added as the next child of the (N-1)st element in @(topicStack) where n is the depth of the line in @(scan) self.shortNameMap: @+(a new entry mapping the topic from the line in scan |-> (a new Topic made from the line in @(scan))) #-- TARGET -- OK [ if (self.root is the tree of existing topics) and (scan is a Scan object) and (topicStack is path-to-topic(previous topic, if any) -> if the line in scan is a valid topic line -> scan := scan advanced to end of line self.root := self.root with that topic added self.shortNameMap +:= a new entry mapping the short name from that line |-> the new Topic made from that line topicStack := path-to-topic(the new Topic made from that line) else -> ... ] # - - - P l a n . _ _ p a r s e T o p i c - - - #### Case 1: line in scan does not match topicPat #-- 1 -- # [ if the line in scan matches topicPat -> ... # else -> # Log() +:= error message # return None ] Log(): @+(error msg) return None #-- TARGET -- OK [ if the line in scan is a syntactically valid topic line -> ... else -> Log() +:= error message(s) return None ] # - - - P l a n . _ _ p a r s e T o p i c - - - #### Case 2: line in scan matches topicPat #-- 1 -- # [ if the line in scan matches topicPat -> # scan := scan advanced to end of line # m := a Match object containing the matched fields # else -> # Log() +:= error message # return None ] scan: @, advanced to EOL m: a Match object with fields "stars", "title", and "short" #-- 2 -- # [ depth := number of characters in m.match("stars") # title := m.match("title") with trailing whitespace trimmed # short := m.match("short") with trailing whitespace trimmed ] scan: @, advanced to EOL m: a Match object with fields "stars", "title", and "short" depth: leading asterisks from line in @(scan) title: title field from line in @(scan), without trailing whitespace short: short field from line in @(scan), without trailing whitespace #-- 3 -- return RawTopic ( depth, title, short ) scan: @, advanced to EOL m: a Match object with fields "stars", "title", and "short" depth: number of leading asterisks from line in @(scan) title: title field from line in @(scan), without trailing whitespace short: short field from line in @(scan), without trailing whitespace return RawTopic((number of leading asterisks from line in @(scan)), (title field from line in @(scan), without trailing whitespace), (short field from line in @(scan), without trailing whitespace)) #-- TARGET -- OK [ if the line in scan is a syntactically valid topic line -> scan := scan advanced to end of line return a RawTopic object containing the fields from that line else -> ... ] # - - - P l a n . _ _ a d j u s t T o p i c S t a c k - - - #### Case 1: newDepth > (len(topicStack)+1) #-- 1 -- # [ if newDepth > ( len ( topicStack ) + 1 ) -> # Log() +:= error message # return 0 # else -> I ] Log(): @+(error message) return 0 #-- TARGET -- OK [ if (topicStack is a stack of Topic objects) and (newDepth is a positive integer) -> if newDepth > (size of topicStack+1) -> Log() +:= error message about missing levels return 0 else -> ... ] # - - - P l a n . _ _ a d j u s t T o p i c S t a c k - - - #### Case 2: newDepth <= (len(topicStack)+1) #-- 1 -- # [ if newDepth > ( len ( topicStack ) + 1 ) -> # Log() +:= error message # return 0 # else -> I ] I #-- 2 -- # [ topicStack := topicStack shortened to newDepth - 1 entries ] topicStack: the first newDepth-1 entries of @ #-- 3 -- return 1 topicStack: the first newDepth-1 entries of @ return 1 #-- TARGET -- OK [ if (topicStack is a stack of Topic objects) and (newDepth is a positive integer) -> if newDepth > (size of topicStack+1) -> ... else -> topicStack := topicStack with all entries removed whose depth is >= newDepth return 1 ] # - - - P l a n . _ _ a d d T o p i c - - - #### Case 1: rawTopic.shortName is not a valid short name in self.pathMap #-- 1 -- # [ if rawTopic.shortName is a valid short name in self.pathMap -> # path := the path from the web root fo the topic's # directory, expanded according to plan.pathMap # else -> # Log() +:= error message # return ] Log(): @+(error msg) return #-- TARGET -- OK [ if (topicStack is a stack of Topic objects of size rawTopic.depth-1) and (rawTopic is a RawTopic object) -> if rawTopic is invalid -> Log() +:= error message(s) else ... ] # - - - P l a n . _ _ a d d T o p i c - - - #### Case 2: self.shortNameMap already has a key like rawTopic.shortName #-- 1 -- # [ if rawTopic.shortName is a valid short name in self.pathMap -> # path := the path from the web root to the topic's # directory, expanded according to plan.pathMap # else -> ... ] path: (path from plan.root to rawTopic.shortName's directory) #-- 2 -- # [ if self.shortNameMap already has a key like rawTopic.shortName -> # Log() +:= error message # return # else -> I ] path: (path from plan.root to rawTopic.shortName's directory) Log(): @+(error msg) #-- TARGET -- OK [ if (topicStack is a stack of Topic objects of size rawTopic.depth-1) and (rawTopic is a RawTopic object) -> if rawTopic is invalid -> Log() +:= error message(s) else ... ] # - - - P l a n . _ _ a d d T o p i c - - - #### Case 3: topicStack is empty #-- 1 -- # [ if rawTopic.shortName is a valid short name in self.pathMap -> # path := the path from the web root to the topic's # directory, expanded according to plan.pathMap # else -> ... ] path: (path from plan.root to rawTopic.shortName's directory, expanded according to plan.pathMap #-- 2 -- # [ if self.shortNameMap already has a key like rawTopic.shortName -> # Log() +:= error message # return # else -> I ] path: (path from plan.root to rawTopic.shortName's directory, expanded according to plan.pathMap #-- 3 -- # [ if topicStack is empty -> # parent := None # else -> # parent := last element of topicStack ] path: (path from plan.root to rawTopic.shortName's directory, expanded according to plan.pathMap parent: None #-- 4 -- # [ topic := a new Topic with plan=self, parent=parent, # title=rawTopic.title, shortName=rawTopic.shortName, # and path=path ] path: (path from plan.root to rawTopic.shortName's directory, expanded according to plan.pathMap parent: None topic: a new Topic with plan=self, parent=None, title=rawTopic.title, shortName=rawTopic.shortName, and path=(path from plan.root to rawTopic.shortName's directory, expanded according to plan.pathMap #-- 5 -- if parent is None: self.root = topic path: (path from plan.root to rawTopic.shortName's directory, expanded according to plan.pathMap parent: None topic: a new Topic with plan=self, parent=None, title=rawTopic.title, shortName=rawTopic.shortName, and path=(path from plan.root to rawTopic.shortName's directory, expanded according to plan.pathMap self.root: a new Topic with plan=self, parent=None, title=rawTopic.title, shortName=rawTopic.shortName, and path=(path from plan.root to rawTopic.shortName's directory, expanded according to plan.pathMap #-- 6 -- # [ self.shortNameMap +:= an entry mapping topic.shortName |-> # topic ] path: (path from plan.root to rawTopic.shortName's directory, expanded according to plan.pathMap parent: None topic: a new Topic with plan=self, parent=None, title=rawTopic.title, shortName=rawTopic.shortName, and path=(path from plan.root to rawTopic.shortName's directory, expanded according to plan.pathMap self.root: a new Topic with plan=self, parent=None, title=rawTopic.title, shortName=rawTopic.shortName, and path=(path from plan.root to rawTopic.shortName's directory, expanded according to plan.pathMap self.shortNameMap: @+(entry mapping rawTopic.shortName |-> a new Topic with plan=self, parent=None, title=rawTopic.title, shortName=rawTopic.shortName, and path=(path from plan.root to rawTopic.shortName's directory, expanded according to plan.pathMap #-- 7 -- # [ topicStack := topicStack with topic appended # self.nTopics +:= 1 ] path: (path from plan.root to rawTopic.shortName's directory, expanded according to plan.pathMap parent: None topic: a new Topic with plan=self, parent=None, title=rawTopic.title, shortName=rawTopic.shortName, and path=(path from plan.root to rawTopic.shortName's directory, expanded according to plan.pathMap self.root: a new Topic with plan=self, parent=None, title=rawTopic.title, shortName=rawTopic.shortName, and path=(path from plan.root to rawTopic.shortName's directory, expanded according to plan.pathMap self.shortNameMap: @+(entry mapping rawTopic.shortName |-> a new Topic with plan=self, parent=None, title=rawTopic.title, shortName=rawTopic.shortName, and path=(path from plan.root to rawTopic.shortName's directory, expanded according to plan.pathMap) topicStack: @+(a new Topic with plan=self, parent=None, title=rawTopic.title, shortName=rawTopic.shortName, and path=(path from plan.root to rawTopic.shortName's directory, expanded according to plan.pathMap)) self.nTopics: @+1 #-- TARGET -- OK [ if (topicStack is a stack of Topic objects of size rawTopic.depth-1) and (rawTopic is a RawTopic object) -> if rawTopic is invalid -> ... else if rawTopic is valid and topicStack is empty -> self.root := a new Topic made from rawTopic, and no parent self.shortNameMap +:= an entry mapping rawTopic.shortName |-> that new Topic else -> ... ] # - - - P l a n . _ _ a d d T o p i c - - - #### Case 4: topicStack is not empty #-- 1 -- # [ if rawTopic.shortName is a valid short name in self.pathMap -> # path := the path from the web root to the topic's # directory, expanded according to plan.pathMap # else -> # Log() +:= error message # return ] path: (path from web root to topic's directory, expanded by plan.pathMap) #-- 2 -- # [ if self.shortNameMap already has a key like rawTopic.shortName -> # Log() +:= error message # return # else -> I ] path: (path from web root to topic's directory, expanded by plan.pathMap) #-- 3 -- # [ if topicStack is empty -> # parent := None # else -> # parent := last element of topicStack ] path: (path from web root to topic's directory, expanded by plan.pathMap) parent: topicStack[-1] #-- 4 -- # [ topic := a new Topic with plan=self, parent=parent, # title=rawTopic.title, shortName=rawTopic.shortName, # and path=path ] path: (path from web root to topic's directory, expanded by plan.pathMap) parent: topicStack[-1] topic: a new Topic with plan=self, parent=topicStack[-1], title=rawTopic.title, shortName=rawTopic.shortName, path=(path from web root to topic's directory, expanded by plan.pathMap) #-- 5 -- if parent is None: self.root = topic path: (path from web root to topic's directory, expanded by plan.pathMap) parent: topicStack[-1] topic: a new Topic with plan=self, parent=topicStack[-1], title=rawTopic.title, shortName=rawTopic.shortName, path=(path from web root to topic's directory, expanded by plan.pathMap) #-- 6 -- # [ self.shortNameMap +:= an entry mapping topic.shortName |-> # topic ] path: (path from web root to topic's directory, expanded by plan.pathMap) parent: topicStack[-1] topic: a new Topic with plan=self, parent=topicStack[-1], title=rawTopic.title, shortName=rawTopic.shortName, path=(path from web root to topic's directory, expanded by plan.pathMap) self.shortNameMap: @+(entry mapping rawTopic.shortName |-> a new Topic with plan=self, parent=topicStack[-1], title=rawTopic.title, shortName=rawTopic.shortName, path=(path from web root to topic's directory, expanded by plan.pathMap) #-- 7 -- # [ topicStack := topicStack with topic appended # self.nTopics +:= 1 ] path: (path from web root to topic's directory, expanded by plan.pathMap) parent: topicStack[-1] topic: a new Topic with plan=self, parent=topicStack[-1], title=rawTopic.title, shortName=rawTopic.shortName, path=(path from web root to topic's directory, expanded by plan.pathMap) self.shortNameMap: @+(entry mapping rawTopic.shortName |-> a new Topic with plan=self, parent=topicStack[-1], title=rawTopic.title, shortName=rawTopic.shortName, path=(path from web root to topic's directory, expanded by plan.pathMap) topicStack: @+(a new Topic with plan=self, parent=topicStack[-1], title=rawTopic.title, shortName=rawTopic.shortName, path=(path from web root to topic's directory, expanded by plan.pathMap)) self.nTopics: @+1 #-- TARGET -- OK [ if (topicStack is a stack of Topic objects of size rawTopic.depth-1) and (rawTopic is a RawTopic object) -> if rawTopic is invalid -> ... else if rawTopic is valid and topicStack is empty -> ... else -> topicStack := topicStack with a new Topic pushed, made from rawTopic, as a new child of the last element of topicStack self.shortNameMap +:= an entry mapping rawTopic.shortName |-> that new Topic ] # - - - T o p i c . _ _ i n i t _ _ - - - #### Case 1: parent is None #-- 1 -- self.plan = plan self.parent = parent self.depth = depth self.title = title self.shortName = shortName self.path = path self.template = plan.tmplStack[-1] self.linkVarList = [] self.plan: plan self.parent: parent self.depth: depth self.title: title self.shortName: shortName self.path: path self.template: plan.tmplStack[-1] self.linkVarList: an empty list #-- 2 -- # [ if parent is None -> # self.__tree := a new Tree object with no parent # else -> ... ] self.plan: plan self.parent: parent self.depth: depth self.title: title self.shortName: shortName self.path: path self.template: plan.tmplStack[-1] self.linkVarList: an empty list self._tree: a new Tree object with no parent self.parent: None #-- TARGET -- OK [ if (plan is a Plan object) and (parent is a Topic whose depth is one less than (depth), or None for the root topic) and (depth is a positive integer) and (title is the topic's title as a nonempty string) and (shortName is the topic's short name as a nonempty string) and (path is a relative path as a string) -> return a new Topic object with those values and an empty list of LinkVar objects ] # - - - T o p i c . _ _ i n i t _ _ - - - #### Case 2: parent is not None #-- 1 -- self.plan = plan self.parent = parent self.depth = depth self.title = title self.shortName = shortName self.path = path self.template = plan.tmplStack[-1] self.linkVarList = [] self.plan: plan self.parent: parent self.depth: depth self.title: title self.shortName: shortName self.path: path self.template: plan.tmplStack[-1] self.linkVarList: an empty list #-- 2 -- # [ if parent is None -> # self.__tree := a new Tree object with no parent # else -> # self.__tree := a new Tree object with parent.__tree as its # parent ] self.plan: plan self.parent: parent self.depth: depth self.title: title self.shortName: shortName self.path: path self.template: plan.tmplStack[-1] self.linkVarList: an empty list self.__tree: a new Tree object with parent=parent.__tree #-- TARGET -- OK [ if (plan is a Plan object) and (parent is a Topic whose depth is one less than (depth), or None for the root topic) and (depth is a positive integer) and (title is the topic's title as a nonempty string) and (shortName is the topic's short name as a nonempty string) and (path is a relative path as a string) -> return a new Topic object with those values and an empty list of LinkVar objects ] # - - - T o p i c . r e l P a t h - - - #-- 1 -- # [ fromList := list of strings a1, a2, ..., am, fa from self's path # toList := list of strings b1, b2, ..., bn, fb from # toTarget's path ] fromList: list of slash-separated strings a1, a2, ..., am, fa from self's path toList: list of slash-separated strings b1, b2, ..., bn, fb from toTarget's path #-- 2 -- # [ fromList := elements of fromList not in common prefix # toList := elements of toList not in common prefix ] fromList: list of strings [aj, ..., am, fa] from self's path such that [a1, ..., a(j-1)] match the first j-1 elements of toTarget's path toList: list of strings [bj, ..., bn, fb] from toTarget's path such that [b1, ..., b(j-1)] match the first j-1 elements of self's path #-- 3 -- # [ return (one "../" for each element of fromList but the last) + # (elements of toList except the last, each followed by "/") + # ".html" + (anchor from toTarget, if any) ] fromList: list of strings [aj, ..., am, fa] from self's path such that [a1, ..., a(j-1)] match the first j-1 elements of toTarget's path toList: list of strings [bj, ..., bn, fb] from toTarget's path such that [b1, ..., b(j-1)] match the first j-1 elements of self's path return a string containing "../" repeated k times, followed by [aj + "/" + ... + "/" + am + "/" + fa] from self's path, plus the anchor from toTarget if any, where k is the number of elements [aj, ..., am] where [a1, ..., a(j-1)] is the largest common prefix of the slash-separated elements of self's path and toTarget's path, and fa is the last element of self's path #-- TARGET -- OK by inspection # - - - T o p i c . e x p a n d T r e e - - - #### Case 1: (not args.force) and (self's output page exists and #### is up to date) #-- 1 -- # [ if (not args.force) # and (self's output page exists and is up to date) -> # I # else (if self's body page exists and is valid and its # template exists and is valid) -> # self's output page := expansion of self's body page # in context of plan and the body page's template # else -> # Log() +:= error message(s) ] I #-- 2 -- # [ same intended function as above recursively applied to # all self's descendants ] subtree-output-pages(self's descendants, args, pathMap, plan): subtree-contents(self's descendants, args, pathMap, plan, subtree-files(self, pathMap, plan)) Log() +:= errors, if any, from expansion of self's descendants plan := plan with text variants added from elements in self's descendants #-- TARGET -- OK [ subtree-output-pages(self, args, pathMap, plan) := subtree-contents(self, args, pathMap, plan, subtree-files(self, pathMap, plan)) Log() +:= errors, if any, from that expansion plan := plan with text variants added from elements in subtree-files ] # - - - T o p i c . e x p a n d T r e e - - - #### Case 2: ((args.force) or (self's output page doesn't exist) or #### (self's page is not up to date)) and (self's body page exists #### and is valid) and (self's template exists and is valid) #-- 1 -- # [ if (not args.force) # and (self's output page exists and is up to date) -> # I # else (if self's body page exists and is valid and its # template exists and is valid) -> # self's output page := expansion of self's body page # in context of plan and the body page's template # else -> # Log() +:= error message(s) ] self.expand () self's output page: expansion of self's body page in context of plan and the body page's template #-- 2 -- # [ same intended function as above recursively applied to # all self's descendants ] self's output page: expansion of self's body page in context of plan and the body page's template subtree-output-pages(self's descendants, args, pathMap, plan): subtree-contents(self's descendants, args, pathMap, plan, subtree-files(self, pathMap, plan)) Log() +:= errors, if any, from expansion of self's descendants plan := plan with text variants added from elements in self's descendants #-- TARGET -- OK [ subtree-output-pages(self, args, pathMap, plan) := subtree-contents(self, args, pathMap, plan, subtree-files(self, pathMap, plan)) Log() +:= errors, if any, from that expansion plan := plan with text variants added from elements in subtree-files ] # - - - T o p i c . e x p a n d T r e e - - - #### Case 2: ((args.force) or (self's output page doesn't exist) or #### (self's page is not up to date)) and ((self's body page doesn't #### exist) or (self's body page is not valid) or (self's template #### doesn't exist) or (self's template is not valid) #-- 1 -- # [ if (not args.force) # and (self's output page exists and is up to date) -> # I # else (if self's body page exists and is valid and its # template exists and is valid) -> # self's output page := expansion of self's body page # in context of plan and the body page's template # else -> # Log() +:= error message(s) ] Log(): @+(error message(s)) #-- 2 -- # [ same intended function as above recursively applied to # all self's descendants ] Log(): @+(error message(s)) subtree-output-pages(self's descendants, args, pathMap, plan): subtree-contents(self's descendants, args, pathMap, plan, subtree-files(self, pathMap, plan)) Log() +:= errors, if any, from expansion of self's descendants plan := plan with text variants added from elements in self's descendants #-- TARGET -- OK [ subtree-output-pages(self, args, pathMap, plan) := subtree-contents(self, args, pathMap, plan, subtree-files(self, pathMap, plan)) Log() +:= errors, if any, from that expansion plan := plan with text variants added from elements in subtree-files ] # - - - T o p i c . e x p a n d - - - #### Case 1: (not args.force) and (self's body page exists) and #### (self's output page exists and is up to date) #-- 1 -- # [ bodyName := name of self's body file # htmlName := name of self's output file ] bodyName: name of self's body file htmlName: name of self's output file #-- 2 -- # [ if (self's output page exists and is up to date) # and (not args.force) -> # return # else -> I ] return #-- TARGET -- OK [ if (not args.force) and (self's body page exists) and (self's output page exists and is up to date) -> I else ... ] # - - - T o p i c . e x p a n d - - - #### Case 2: ((args.force) or (self's body page does not exists) or #### (self's output page does not exists or is not up to date)) and #### (self's body page exists and is valid and its template exists #### and is valid) and (output file can be created anew) #-- 1 -- # [ bodyName := name of self's body file # htmlName := name of self's output file ] bodyName: name of self's body file htmlName: name of self's output file #-- 2 -- # [ if (self's output page exists and is up to date) # and (not args.force) -> ... # else -> I ] bodyName: name of self's body file htmlName: name of self's output file #-- 3 -- # [ if bodyName is readable and valid and its # effective template is readable and valid -> # body := a new Body object representing that file # self.plan := self.plan with that effective template # added if not already present # else -> ... ] bodyName: name of self's body file htmlName: name of self's output file body: a Body object representing bodyName plan: @+(self's body's effective template, if not already present) #-- 4 -- # [ if self's output file can be created anew, with any # directories between output-root(args) and that file # created if necessary -> # outFile := a writeable handle for that new, empty file # else -> # Log() +:= error message(s) # return ] bodyName: name of self's body file htmlName: name of self's output file body: a Body object representing bodyName plan: @+(self's body's effective template, if not already present) self's output page: empty outFile: handle to self's new, empty page #-- 5 -- # [ outFile +:= expansion of body using body.etemplate as # its template ] bodyName: name of self's body file htmlName: name of self's output file body: a Body object representing bodyName plan: @+(self's body's effective template, if not already present) self's output page: expansion of a Body object representing bodyName and using bodyName's file's effective template outFile: @+(expansion of a Body object representing bodyName and using bodyName's file's effective template) #-- TARGET -- OK [ if (not args.force) and (self's body page exists) and (self's output page exists and is up to date) -> I else (if self's body page exists and is valid and its template exists and is valid) -> self's output page := expansion of self's body page in context of plan and the body page's template plan := plan with self's effective template added if not already present else -> ... ] # - - - T o p i c . e x p a n d - - - #### Case 3: ((args.force) or (self's body page does not exists) or #### (self's output page does not exists or is not up to date)) and #### (self's body page exists and is valid and its template exists #### and is valid) and (output file cannot be created anew) #-- 1 -- # [ bodyName := name of self's body file # htmlName := name of self's output file ] bodyName: name of self's body file htmlName: name of self's output file #-- 2 -- # [ if (self's output page exists and is up to date) # and (not args.force) -> ... # else -> I ] bodyName: name of self's body file htmlName: name of self's output file #-- 3 -- # [ if bodyName is readable and valid and its # effective template is readable and valid -> # body := a new Body object representing that file # self.plan := self.plan with that effective template # added if not already present # else -> ... ] bodyName: name of self's body file htmlName: name of self's output file body: a Body object representing bodyName plan: @+(self's body's effective template, if not already present) #-- 4 -- # [ if self's output file can be created anew, with any # directories between output-root(args) and that file # created if necessary -> ... # else -> # Log() +:= error message(s) # return ] Log(): @+(err msg) return #-- TARGET -- OK [ if (not args.force) and (self's body page exists) and (self's output page exists and is up to date) -> ... else (if self's body page exists and is valid and its template exists and is valid) -> ... else -> Log() +:= error message(s) ] # - - - T o p i c . _ _ p a g e U p T o D a t e - - - #### Case 1: (self's body file does not exist) and (self's output page #### does not exist) #-- 1 -- # [ if htmlName names an existing file -> # oldModTime := its modification timestamp as epoch time # else -> # oldModTime := None ] oldModTime: None #-- 2 -- # [ if bodyName names an existing file -> # bodyModTime := its modification epoch time # else -> # return 0 ] return 0 #-- TARGET -- OK [ if (bodyName is the name of self's body page as a string) and (htmlName is the name of self's output page as a string) -> if (self's output page exists and is up to date) and (not args.force) -> return 1 else -> return 0 ] # - - - T o p i c . _ _ p a g e U p T o D a t e - - - #### Case 2: (self's body file does not exist) and #### (self's output page does not exist) #-- 1 -- # [ if htmlName names an existing file -> # oldModTime := its modification timestamp as epoch time # else -> # oldModTime := None ] oldModTime: None #-- 2 -- # [ if bodyName names an existing file -> # bodyModTime := its modification epoch time # else -> # return 0 ] return 0 #-- TARGET -- OK [ if (bodyName is the name of self's body page as a string) and (htmlName is the name of self's output page as a string) -> if (self's output page exists and is up to date) and (self's body page exists) and (not args.force) -> return 1 else -> return 0 ] # - - - T o p i c . _ _ p a g e U p T o D a t e - - - #### Case 3: (self's body file exists) and (self's output file exists #### and is NOT up to date) #-- 1 -- # [ if htmlName names an existing file -> # oldModTime := its modification timestamp as epoch time # else -> # oldModTime := None ] oldModTime: mod-time of file named by htmlName #-- 2 -- # [ if bodyName names an existing file -> # bodyModTime := its modification epoch time # else -> # return 0 ] oldModTime: mod-time of file named by htmlName bodyModTime: mod-time of file named by bodyName, which is <= oldModTime by case assumption #-- 3 -- if ( ( not args.force ) and ( oldModTime ) and ( oldModTime > bodyModTime ) ): return 1 else: return 0 return 0 (since oldModTime <= bodyModTime) #-- TARGET -- OK [ if (bodyName is the name of self's body page as a string) and (htmlName is the name of self's output page as a string) -> if (self's output page exists and is up to date) and (self's body page exists) and (not args.force) -> return 1 else -> return 0 ] # - - - T o p i c . _ _ p a g e U p T o D a t e - - - #### Case 4: (self's body file exists) and (self's output file exists #### and is up to date) and (not args.force) #-- 1 -- # [ if htmlName names an existing file -> # oldModTime := its modification timestamp as epoch time # else -> # oldModTime := None ] oldModTime: mod-time of file named by htmlName #-- 2 -- # [ if bodyName names an existing file -> # bodyModTime := its modification epoch time # else -> # return 0 ] oldModTime: mod-time of file named by htmlName bodyModTime: mod-time of file named by bodyName, <= oldModTime by case assumption #-- 3 -- if ( ( oldModTime ) and ( oldModTime > bodyModTime ) and ( not args.force ) ): return 1 else: return 0 return 1 #-- TARGET -- OK [ if (bodyName is the name of self's body page as a string) and (htmlName is the name of self's output page as a string) -> if (self's output page exists and is up to date) and (self's body page exists) and (not args.force) -> return 1 else -> return 0 ] # - - - T o p i c . _ _ p a g e U p T o D a t e - - - #### Case 5: (self's body file exists) and (self's output file exists #### and is up to date) and (args.force) #-- 1 -- # [ if htmlName names an existing file -> # oldModTime := its modification timestamp as epoch time # else -> # oldModTime := None ] oldModTime: mod-time of file named by htmlName #-- 2 -- # [ if bodyName names an existing file -> # bodyModTime := its modification epoch time # else -> # return 0 ] oldModTime: mod-time of file named by htmlName bodyModTime: mod-time of file named by bodyName, <= oldModTime by case assumption #-- 3 -- if ( ( oldModTime ) and ( oldModTime > bodyModTime ) and ( not args.force ) ): return 1 else: return 0 return 0 #-- TARGET -- OK [ if (bodyName is the name of self's body page as a string) and (htmlName is the name of self's output page as a string) -> if (self's output page exists and is up to date) and (self's body page exists) and (not args.force) -> return 1 else -> return 0 ] # - - - T o p i c . _ _ o u t P a t h - - - #### Case 1: outRoot is None #-- 1 -- relPath = self.path + HTML_EXT relPath: (self.path + HTML_EXT) #-- 2 -- # [ if outRoot is None -> I # else -> # relPath := outRoot + ( "/" if necessary ) + relPath ] relPath: (self.path + HTML_EXT) #-- 3 -- return relPath return (self.path + HTML_EXT) #-- TARGET -- OK [ if outRoot is None or a string -> if outRoot is None -> return self.path + HTML_EXT else -> return outRoot + ("/" if needed) + self.path + HTML_EXT ] # - - - T o p i c . _ _ o u t P a t h - - - #### Case 2: outRoot is None #-- 1 -- relPath = self.path + HTML_EXT relPath: (self.path + HTML_EXT) #-- 2 -- # [ if outRoot is None -> I # else -> # relPath := outRoot + ( "/" if necessary ) + relPath ] relPath: (outRoot) + ("/" if necessary) + (self.path + HTML_EXT) #-- 3 -- return relPath return (outRoot) + ("/" if necessary) + (self.path + HTML_EXT) #-- TARGET -- OK [ if outRoot is None or a string -> if outRoot is None -> return self.path + HTML_EXT else -> return outRoot + ("/" if needed) + self.path + HTML_EXT ] # - - - T o p i c . _ _ o p e n O u t - - - #### Case 1: all directories in outName's path exist, and #### the file can be opened anew #-- 1 -- # [ if outName can opened anew -> # return a writeable file handle to that file, empty # else -> I ] return a writeable file handle to a new empty file named outName #-- TARGET -- OK [ if outName is a string -> if outName can be created anew, with any directories between output-root(args) and that file created if necessary -> return a writeable handle for that new, empty file else -> Log() +:= error message(s) return ] # - - - T o p i c . _ _ o p e n O u t - - - #### Case 2: not all directories in outName's path exist, #### but the missing ones can be created and the file can be opened anew #-- 1 -- # [ if outName can opened anew -> ... # else -> I ] I #-- 2 -- # [ if all directories in outName's path exist -> I # else if all nonexistent directories in outName's path can be # created top to bottom -> # create those directories # else -> # Log() +:= error message(s) # return None ] create all missing directories from outName's path #-- 3 -- # [ if outName can opened anew -> # return a writeable file handle to that file, empty # else -> # Log() +:= error message(s) # return None ] create all missing directories from outName's path return a writeable file handle to a new, empty file named outName #-- TARGET -- OK [ if outName is a string -> if outName can be created anew, with any directories between output-root(args) and that file created if necessary -> return a writeable handle for that new, empty file else -> Log() +:= error message(s) return ] # - - - T o p i c . _ _ o p e n O u t - - - #### Case 3: (not all directories in outName's path exist but the #### missing ones can be created) and (outName cannot be created new) #-- 1 -- # [ if outName can opened anew -> # return a writeable file handle to that file, empty # else -> I ] I #-- 2 -- # [ if all directories in outName's path exist -> I # else if all nonexistent directories in outName's path can be # created top to bottom -> # create those directories # else -> # Log() +:= error message(s) # return None ] create all missing directories in outName's path #-- 3 -- # [ if outName can opened anew -> # return a writeable file handle to that file, empty # else -> # Log() +:= error message(s) # return None ] create all missing directories in outName's path Log(): @+(error message(s)) return None #-- TARGET -- OK [ if outName is a string -> if outName can be created anew, with any directories between output-root(args) and that file created if necessary -> return a writeable handle for that new, empty file else -> possibly create some of the directories in outName's path Log() +:= error message(s) return None ] # - - - T o p i c . _ _ o p e n O u t - - - #### Case 4: (not all directories in outName's path exist) and (at #### least one cannot be created) #-- 1 -- # [ if outName can opened anew -> # return a writeable file handle to that file, empty # else -> I ] I #-- 2 -- # [ if all directories in outName's path exist -> I # else if all nonexistent directories in outName's path can be # created top to bottom -> # create those directories # else -> # possibly create some directories # Log() +:= error message(s) # return None ] possibly create some directories in outName's path Log() +:= error message(s) return None #-- TARGET -- OK [ if outName is a string -> if outName can be created anew, with any directories between output-root(args) and that file created if necessary -> return a writeable handle for that new, empty file else -> possibly create some of the directories in outName's path Log() +:= error message(s) return None ] # - - - T o p i c . _ _ m a k e D i r s - - - #### Case 1: All directories in path exist #-- 1 -- # [ dirList := a list of the names of each directory in path, # left to right ] dirList: list of names of each directory in path, left to right #-- 2 -- # [ if all elements of dirList are existing directories -> # I # else if all nonexistent elements of dirList can be created # as directories -> # create those directories # else -> # create directories up to the first one that can't be created # Log() +:= error message(s) # return 0 ] dirList: list of names of each directory in path, left to right #-- 3 -- return 1 return 1 #-- TARGET -- OK [ if path is a path name as a string -> if all directories in path exist -> return 1 else ... ] # - - - T o p i c . _ _ m a k e D i r s - - - #### Case 2: Some directories don't exist but they can be created #-- 1 -- # [ dirList := a list of the names of each directory in path, # left to right ] dirList: list of names of each directory in path, left to right #-- 2 -- # [ if all elements of dirList are existing directories -> # I # else if all nonexistent elements of dirList can be created # as directories -> # create those directories # else -> # create directories up to the first one that can't be created # Log() +:= error message(s) # return 0 ] dirList: list of names of each directory in path, left to right create nonexistent directories in path, left to right #-- 3 -- return 1 dirList: list of names of each directory in path, left to right create nonexistent directories in path, left to right return 1 #-- TARGET -- OK [ if path is a path name as a string -> if all directories in path exist -> ... else if all nonexistent directories in path can be created top to bottom -> create those directories return 1 else -> ... ] # - - - T o p i c . _ _ m a k e D i r s - - - #### Case 3: some element U of dirList can't be created #-- 1 -- # [ dirList := a list of the names of each directory in path, # left to right ] dirList: a list of the names of each directory in path, left to right #-- 2 -- # [ if all elements of dirList are existing directories -> # I # else if all nonexistent elements of dirList can be created # as directories -> # create those directories # else -> # create directories up to the first one that can't be created # Log() +:= error message(s) # return 0 ] dirList: a list of the names of each directory in path, left to right create any nonexistent directories before U in path Log(): @+(err msg(s)) return 0 #-- TARGET -- OK [ if path is a path name as a string -> if all directories in path exist -> ... else if all nonexistent directories in path can be created top to bottom -> ... else -> possibly create some of those directories Log() +:= error message(s) return 0 ] # - - - T o p i c . _ _ b u i l d D i r L i s t - - - #### Main sequence #### NB: The construct `list of slash-separated strings in s' is assumed to #### correspond to the operation of string.split(s, "/"). In particular, #### if s starts with a slash, the result list will start with an empty #### string, and if s ends with a slash, the result list will end with #### an empty string. So, e.g., string.split("/", "/") -> ["", ""] #-- 1 -- # [ if path contains no "/" -> # dirPart := "" # else -> # dirPart := text from path up to the last "/" ] dirPart: text from path up to last "/" #-- 2 -- # [ L := list of strings in dirPart separated by "/" # result := a new, empty list # sep := "" ] dirPart: text from path up to last "/" L: list of slash-separated strings in (path up to last "/") result: [] sep: "" #-- 3 -- # # [ result := result + nonempty members of the sequence # [ L[0], L[0]+"/"+L[1], ..., # L[0]+"/"+L[1]+"/"+...+"/"+L[-1]] # curPath := L[0]+"/"+L[1]+"/"+...+"/"+L[-1] # sep := "/" ] dirPart: text from path up to last "/" L: list of slash-separated strings in (path up to last "/") result: nonempty members of sequence [L[0], L[0]+"/"+L[1], ...] curPath: L[0]+"/"+L[1]+"/"+...+"/"+L[-1] sep: "/" #-- 4 -- return result return nonempty members of sequence [L[0], L[0]+"/"+L[1], ...] where L is list of slash-separated in strings in (path up to last "/") #-- TARGET -- OK [ if path is a string containing a path name -> if path has the form "/d0/d1/.../dn/f" -> return a list ["/d0", "/d0/d1", ..., "/d0/d1/.../dn"] else -> return a list ["d0", "d0/d1", ..., "d0/d1/.../dn" ] # - - - T o p i c . _ _ b u i l d D i r L i s t - - - #### Definite iteration of prime 3: false case #-- TARGET -- OK # [ result := result + nonempty members of the sequence # [ L[0], L[0]+"/"+L[1], ..., # L[0]+"/"+L[1]+"/"+...+"/"+L[-1]] # curPath := curPath+L[0]+"/"+L[1]+"/"+...+"/"+L[-1] # sep := "/" if len(L)>0, else unchanged ] # - - - T o p i c . _ _ b u i l d D i r L i s t - - - #### Definite iteration of prime 3: true case 1, L[0] is "" path: "" #-- 3 body -- # [ if part is "" -> # curPath := curPath+sep+part # sep := "/" # else -> ... ] curPath: @+@(sep) sep: "/" # [ result := result + nonempty members of the sequence # [ curPath+sep+L[0], curPath+sep+L[0]+"/"+L[1], ..., # curPath+sep+L[0]+"/"+L[1]+"/"+...+"/"+L[-1]] # curPath := curPath+sep+L[0]+"/"+L[1]+"/"+...+"/"+L[-1] # sep := "/" if len(L)>0, else unchanged ] result: @+nonempty members of the sequence [@(curPath)+@(sep), @(curPath)+@(sep)+"/"+L[1], @(curPath)+@(sep)+"/"+L[1]+"/"+L[2], ...] curPath: @+@(sep)+"/"+L[1]+"/"+L[2]+"/"+...+"/"+L[-1] sep: "/" #-- TARGET -- OK # [ result := result + nonempty members of the sequence # [ curPath+sep+L[0], curPath+sep+L[0]+"/"+L[1], ..., # curPath+sep+L[0]+"/"+L[1]+"/"+...+"/"+L[-1]] # curPath := curPath+sep+L[0]+"/"+L[1]+"/"+...+"/"+L[-1] # sep := "/" if len(L)>0, else unchanged ] # - - - T o p i c . _ _ b u i l d D i r L i s t - - - #### Definite iteration of prime 3: true case 2, L[0] is not "" path: nonempty L[0] #-- 3 body -- # [ if part is "" -> ... # else -> # result := result + [curPath+sep+part] # curPath := curPath+sep+part # sep := "/" ] result: @+[@(curPath)+@(sep)+L[0]] curPath: @+@(sep)+L[0] sep: "/" # [ result := result + nonempty members of the sequence # [ curPath+sep+L[0], curPath+sep+L[0]+"/"+L[1], ..., # curPath+sep+L[0]+"/"+L[1]+"/"+...+"/"+L[-1]] # curPath := curPath+sep+L[0]+"/"+L[1]+"/"+...+"/"+L[-1] # sep := "/" if len(L)>0, else unchanged ] result: @+[@(curPath)+@(sep)+L[0], @(curPath)+@(sep)+L[0]+"/"+L[1], ...] curPath: @+@(sep)+L[0]+"/"+L[1]+"/"+...+"/"+L[-1] sep: "/" #-- TARGET -- OK # [ result := result + nonempty members of the sequence # [ curPath+sep+L[0], curPath+sep+L[0]+"/"+L[1], ..., # curPath+sep+L[0]+"/"+L[1]+"/"+...+"/"+L[-1]] # curPath := curPath+sep+L[0]+"/"+L[1]+"/"+...+"/"+L[-1] # sep := "/" if len(L)>0, else unchanged ] # - - - T o p i c . _ _ c r e D i r - - - #### Case 1: path names an existing directory #-- 1 -- # [ if path can be statted -> # info := a PathInfo object representing that path # else -> ... ] info: a PathInfo object describing path #-- 2 -- # [ if info is None -> I # else if info is a directory -> return 1 # else -> # Log() +:= error message(s) # return 0 ] return 1 #-- TARGET -- OK [ if path is a string -> if path names an existing directory -> return 1 else ... ] # - - - T o p i c . _ _ c r e D i r - - - #### Case 2: (path names a nonexistent path) and (we can create #### a directory there) #-- 1 -- # [ if path can be statted -> # info := a PathInfo object representing that path # else -> # info := None ] info: None #-- 2 -- # [ if info is None -> I # else ... ] info: None #-- 3 -- # [ if a directory named path can be created -> # create it # return 1 # else -> # Log() +:= error message(s) # return 0 ] create path as an empty directory return 1 #-- TARGET -- OK [ if path is a string -> if path names an existing directory -> ... else if path names a directory that can be created -> create that directory return 1 else -> ... ] # - - - T o p i c . _ _ c r e D i r - - - #### Case 3: (path names an existing path that is not a directory) #-- 1 -- # [ if path can be statted -> # info := a PathInfo object representing that path # else -> # info := None ] info: a PathInfo object describing what is at path #-- 2 -- # [ if info is None -> I # else if info is a directory -> return 1 # else -> # Log() +:= error message(s) # return 0 ] Log(): @+(err msg(s)) return 0 #-- TARGET -- OK [ if path is a string -> if path names an existing directory -> ... else if path names a directory that can be created -> ... else -> Log() +:= error message(s) return 0 ] # - - - T o p i c . _ _ c r e D i r - - - #### Case 4: (path names a nonexistent path but we can't create a #### directory there) #-- 1 -- # [ if path can be statted -> ... # else -> # info := None ] info: None #-- 2 -- # [ if info is None -> I # else ... ] info: None #-- 3 -- # [ if a directory named path can be created -> # create it # return 1 # else -> # Log() +:= error message(s) # return 0 ] Log(): @+(err msg(s)) return 0 #-- TARGET -- [ if path is a string -> if path names an existing directory -> ... else if path names a directory that can be created -> ... else -> Log() +:= error message(s) return 0 ]