def directive_factory(src_path): """Create directive instance out of docutils/sphinx process. Used in testing or command-line process """ from sphinx_vcs_changelog.changelog import ChangelogWriter state_machine = statemachine.StateMachine( state_classes=[statemachine.StateWS], initial_state='StateWS') base_dir = path.dirname(src_path) _instance = object.__new__(ChangelogWriter) _instance.name = DIRECTIVE_CHANGELOG _instance.arguments = [] _instance.options = {} _instance.content = "" _instance.lineno = 123 _instance.content_offset = 123 _instance.block_text = "" _instance.state = state_machine.get_state() _instance.state_machine = state_machine document = new_document(src_path) document.settings.update({'env': Values(defaults={'srcdir': base_dir})}, frontend.OptionParser()) setattr(_instance.state, 'document', document) _instance.prepare() return _instance
def process_mathdef_nodes(app, doctree, fromdocname): """ process_mathdef_nodes """ if not app.config['mathdef_include_mathsext']: for node in doctree.traverse(mathdef_node): node.parent.remove(node) # Replace all mathdeflist nodes with a list of the collected mathsext. # Augment each mathdef with a backlink to the original location. env = app.builder.env if hasattr(env, "settings") and hasattr(env.settings, "language_code"): lang = env.settings.language_code else: lang = "en" orig_entry = TITLES[lang]["original entry"] mathmes = TITLES[lang]["mathmes"] if not hasattr(env, 'mathdef_all_mathsext'): env.mathdef_all_mathsext = [] for ilist, node in enumerate(doctree.traverse(mathdeflist)): if 'ids' in node: node['ids'] = [] if not app.config['mathdef_include_mathsext']: node.replace_self([]) continue nbmath = 0 content = [] mathtag = node["mathtag"] add_contents = node["mathcontents"] mathdocname = node["docname"] if add_contents: bullets = nodes.enumerated_list() content.append(bullets) double_list = [(info.get('mathtitle', ''), info) for info in env.mathdef_all_mathsext] double_list.sort(key=lambda x: x[:1]) for n, mathdef_info_ in enumerate(double_list): mathdef_info = mathdef_info_[1] if mathdef_info["mathtag"] != mathtag: continue nbmath += 1 para = nodes.paragraph(classes=['mathdef-source']) if app.config['mathdef_link_only']: description = _('<<%s>>' % orig_entry) else: description = ( _(mathmes) % (orig_entry, os.path.split(mathdef_info['source'])[-1], mathdef_info['lineno']) ) desc1 = description[:description.find('<<')] desc2 = description[description.find('>>') + 2:] para += nodes.Text(desc1, desc1) # Create a reference newnode = nodes.reference('', '', internal=True) innernode = nodes.emphasis('', _(orig_entry)) try: newnode['refuri'] = app.builder.get_relative_uri( fromdocname, mathdef_info['docname']) try: newnode['refuri'] += '#' + mathdef_info['target']['refid'] except Exception as e: # pragma: no cover raise KeyError("refid in not present in '{0}'".format( mathdef_info['target'])) from e except NoUri: # pragma: no cover # ignore if no URI can be determined, e.g. for LaTeX output pass newnode.append(innernode) para += newnode para += nodes.Text(desc2, desc2) # (Recursively) resolve references in the mathdef content mathdef_entry = mathdef_info['mathdef'] idss = ["index-mathdef-%d-%d" % (ilist, n)] # Insert into the mathreflist if add_contents: title = mathdef_info['mathtitle'] item = nodes.list_item() p = nodes.paragraph() item += p newnode = nodes.reference('', '', internal=True) innernode = nodes.paragraph(text=title) try: newnode['refuri'] = app.builder.get_relative_uri( fromdocname, mathdocname) newnode['refuri'] += '#' + idss[0] except NoUri: # pragma: no cover # ignore if no URI can be determined, e.g. for LaTeX output pass newnode.append(innernode) p += newnode bullets += item mathdef_entry["ids"] = idss if not hasattr(mathdef_entry, "settings"): mathdef_entry.settings = Values() mathdef_entry.settings.env = env # If an exception happens here, see blog 2017-05-21 from the # documentation. env.resolve_references(mathdef_entry, mathdef_info['docname'], app.builder) # Insert into the mathdeflist content.append(mathdef_entry) content.append(para) node.replace_self(content)
def process_todoext_nodes(app, doctree, fromdocname): """ process_todoext_nodes """ if not app.config['todoext_include_todosext']: for node in doctree.traverse(todoext_node): node.parent.remove(node) # Replace all todoextlist nodes with a list of the collected todosext. # Augment each todoext with a backlink to the original location. env = app.builder.env if hasattr(env, "settings") and hasattr(env.settings, "language_code"): lang = env.settings.language_code else: lang = "en" orig_entry = TITLES[lang]["original entry"] todomes = TITLES[lang]["todomes"] allowed_tsort = {'date', 'prio', 'title', 'release', 'source'} if not hasattr(env, 'todoext_all_todosext'): env.todoext_all_todosext = [] for ilist, node in enumerate(doctree.traverse(todoextlist)): if 'ids' in node: node['ids'] = [] if not app.config['todoext_include_todosext']: node.replace_self([]) continue nbtodo = 0 fcost = 0 content = [] todotag = node["todotag"] tsort = node["todosort"] if tsort == '': tsort = 'source' if tsort not in allowed_tsort: raise ValueError("option sort must in {0}, '{1}' is not".format( allowed_tsort, tsort)) double_list = [(info.get('todo%s' % tsort, ''), info.get('todotitle', ''), info) for info in env.todoext_all_todosext] double_list.sort(key=lambda x: x[:2]) for n, todoext_info_ in enumerate(double_list): todoext_info = todoext_info_[2] if todoext_info["todotag"] != todotag: continue nbtodo += 1 fcost += todoext_info.get("todocost", 0.0) para = nodes.paragraph(classes=['todoext-source']) if app.config['todoext_link_only']: description = locale_('<<%s>>' % orig_entry) else: description = ( locale_(todomes) % (orig_entry, os.path.split( todoext_info['source'])[-1], todoext_info['lineno'])) desc1 = description[:description.find('<<')] desc2 = description[description.find('>>') + 2:] para += nodes.Text(desc1, desc1) # Create a reference newnode = nodes.reference('', '', internal=True) innernode = nodes.emphasis(locale_(orig_entry), locale_(orig_entry)) try: newnode['refuri'] = app.builder.get_relative_uri( fromdocname, todoext_info['docname']) try: newnode['refuri'] += '#' + todoext_info['target']['refid'] except Exception as e: raise KeyError("refid in not present in '{0}'".format( todoext_info['target'])) from e except NoUri: # ignore if no URI can be determined, e.g. for LaTeX output pass newnode.append(innernode) para += newnode para += nodes.Text(desc2, desc2) # (Recursively) resolve references in the todoext content todoext_entry = todoext_info.get('todoext_copy', None) if todoext_entry is None: todoext_entry = todoext_info['todoext'] todoext_entry["ids"] = ["index-todoext-%d-%d" % (ilist, n)] # it apparently requires an attributes ids if not hasattr(todoext_entry, "settings"): todoext_entry.settings = Values() todoext_entry.settings.env = env # If an exception happens here, see blog 2017-05-21 from the # documentation. env.resolve_references(todoext_entry, todoext_info['docname'], app.builder) # Insert into the todoextlist content.append(todoext_entry) content.append(para) if fcost > 0: cost = nodes.paragraph() lab = "{0} items, cost: {1}".format(nbtodo, fcost) cost += nodes.Text(lab) content.append(cost) else: cost = nodes.paragraph() lab = "{0} items".format(nbtodo) cost += nodes.Text(lab) content.append(cost) node.replace_self(content)
def process_blocref_nodes_generic(app, doctree, fromdocname, class_name, entry_name, class_node, class_node_list): """ process_blocref_nodes and other kinds of nodes, If the configuration file specifies a variable ``blocref_include_blocrefs`` equals to False, all nodes are removed. """ # logging cont = info_blocref(app, doctree, fromdocname, class_name, entry_name, class_node, class_node_list) if not cont: return # check this is something to process env = app.builder.env attr_name = '%s_all_%ss' % (class_name, class_name) if not hasattr(env, attr_name): setattr(env, attr_name, []) bloc_list_env = getattr(env, attr_name) if len(bloc_list_env) == 0: return # content incconf = '%s_include_%ss' % (class_name, class_name) if app.config[incconf] and not app.config[incconf]: for node in doctree.traverse(class_node): node.parent.remove(node) # Replace all blocreflist nodes with a list of the collected blocrefs. # Augment each blocref with a backlink to the original location. if hasattr(env, "settings"): settings = env.settings if hasattr(settings, "language_code"): lang = env.settings.language_code else: lang = "en" else: settings = None lang = "en" orig_entry = TITLES[lang]["original entry"] brefmes = TITLES[lang][entry_name] for ilist, node in enumerate(doctree.traverse(class_node_list)): if 'ids' in node: node['ids'] = [] if not app.config[incconf]: node.replace_self([]) continue nbbref = 0 content = [] breftag = node["breftag"] brefsort = node["brefsort"] add_contents = node["brefcontents"] brefdocname = node["docname"] if add_contents: bullets = nodes.enumerated_list() content.append(bullets) # sorting if brefsort == 'title': double_list = [(info.get('breftitle', ''), info) for info in bloc_list_env if info['breftag'] == breftag] double_list.sort(key=lambda x: x[:1]) elif brefsort == 'file': double_list = [((info.get('breffile', ''), info.get('brefline', '')), info) for info in bloc_list_env if info['breftag'] == breftag] double_list.sort(key=lambda x: x[:1]) elif brefsort == 'number': double_list = [(info.get('brefmid', ''), info) for info in bloc_list_env if info['breftag'] == breftag] double_list.sort(key=lambda x: x[:1]) else: raise ValueError("sort option should be file, number, title") # printing for n, blocref_info_ in enumerate(double_list): blocref_info = blocref_info_[1] nbbref += 1 para = nodes.paragraph(classes=['%s-source' % class_name]) # Create a target? int_ids = [ 'index%s-%s' % (blocref_info['target']['refid'], env.new_serialno(blocref_info['target']['refid'])) ] int_targetnode = nodes.target(blocref_info['breftitle'], '', ids=int_ids) para += int_targetnode # rest of the content if app.config['%s_link_only' % class_name]: description = _('<<%s>>' % orig_entry) else: description = ( _(brefmes) % (orig_entry, os.path.split( blocref_info['source'])[-1], blocref_info['lineno'])) desc1 = description[:description.find('<<')] desc2 = description[description.find('>>') + 2:] para += nodes.Text(desc1, desc1) # Create a reference newnode = nodes.reference('', '', internal=True) newnode['name'] = _(orig_entry) try: newnode['refuri'] = app.builder.get_relative_uri( fromdocname, blocref_info['docname']) if blocref_info['target'] is None: raise NoUri # pragma: no cover try: newnode['refuri'] += '#' + blocref_info['target']['refid'] except Exception as e: # pragma: no cover raise KeyError("refid in not present in '{0}'".format( blocref_info['target'])) from e except NoUri: # pragma: no cover # ignore if no URI can be determined, e.g. for LaTeX output pass newnode.append(nodes.Text(newnode['name'])) # para is duplicate of the content of the bloc para += newnode para += nodes.Text(desc2, desc2) blocref_entry = blocref_info['blocref'] idss = ["index-%s-%d-%d" % (class_name, ilist, n)] # Inserts into the blocreflist # in the list of links at the beginning of the page. if add_contents: title = blocref_info['breftitle'] item = nodes.list_item() p = nodes.paragraph() item += p newnode = nodes.reference('', title, internal=True) try: newnode['refuri'] = app.builder.get_relative_uri( fromdocname, brefdocname) newnode['refuri'] += '#' + idss[0] except NoUri: # pragma: no cover # ignore if no URI can be determined, e.g. for LaTeX output pass p += newnode bullets += item # Adds the content. blocref_entry["ids"] = idss if not hasattr(blocref_entry, "settings"): blocref_entry.settings = Values() blocref_entry.settings.env = env # If an exception happens here, see blog 2017-05-21 from the # documentation. env.resolve_references(blocref_entry, blocref_info['docname'], app.builder) content.append(blocref_entry) content.append(para) node.replace_self(content)
class SceneScript: """Gives access to a Turberfield scene script (.rst) file. This class allows discovery and classification of scene files prior to loading them in memory. Once loaded, it allows entity selection based on the role definitions in the file. Casting a selection permits the script to be iterated as a sequence of dialogue items. """ Folder = namedtuple( "Folder", ["pkg", "description", "metadata", "paths", "interludes"]) settings = Values(defaults=dict( character_level_inline_markup=False, debug=False, error_encoding="utf-8", error_encoding_error_handler="backslashreplace", halt_level=4, auto_id_prefix="", id_prefix="", language_code="en", pep_references=1, report_level=2, rfc_references=1, strict_visitor=False, tab_width=4, warning_stream=sys.stderr, raw_enabled=True, file_insertion_enabled=True, input_encoding="utf-8", input_encoding_error_handler="replace", line_length_limit=float("inf"), )) docutils.parsers.rst.directives.register_directive("entity", EntityDirective) docutils.parsers.rst.directives.register_directive("property", PropertyDirective) docutils.parsers.rst.directives.register_directive("fx", FXDirective) docutils.parsers.rst.directives.register_directive("memory", MemoryDirective) docutils.parsers.rst.directives.register_directive("condition", ConditionDirective) @classmethod def scripts(cls, pkg, metadata, paths=[], **kwargs): """This class method is the preferred way to create SceneScript objects. :param str pkg: The dotted name of the package containing the scripts. :param metadata: A mapping or data object. This parameter permits searching among scripts against particular criteria. Its use is application specific. :param list(str) paths: A sequence of file paths to the scripts relative to the package. You can satisfy all parameter requirements by passing in a :py:class:`~turberfield.dialogue.model.SceneScript.Folder` object like this:: SceneScript.scripts(**folder._asdict()) The method generates a sequence of :py:class:`~turberfield.dialogue.model.SceneScript` objects. """ log_manager = LogManager() log = log_manager.clone(log_manager.get_logger("main"), "turberfield.dialogue.model.scenescript") for path in paths: try: fP = pkg_resources.resource_filename(pkg, path) except ImportError: log.warning("No package called {}".format(pkg)) else: if not os.path.isfile(fP): log.warning("No script file at {}".format( os.path.join(*pkg.split(".") + [path]))) else: yield cls(fP, metadata) @staticmethod def read(text, name=None): """Read a block of text as a docutils document. :param str text: Scene script text. :param str name: An optional name for the document. :return: A document object. """ doc = docutils.utils.new_document(name, SceneScript.settings) parser = docutils.parsers.rst.Parser() parser.parse(text, doc) return doc def __init__(self, fP, metadata=None, doc=None): self.log_manager = LogManager() self.log = self.log_manager.get_logger( "turberfield.dialogue.model.scenescript") self.fP = fP self.metadata = metadata self.doc = doc def __enter__(self): with open(self.fP, "r") as script: self.doc = self.read(script.read()) return self def __exit__(self, exc_type, exc_value, traceback): return False def select(self, personae, relative=False, roles=1): """Select a persona for each entity declared in the scene. :param personae: A sequence of Personae. :param bool relative: Affects imports from namespace packages. Used for testing only. :param int roles: The maximum number of roles allocated to each persona. :return: An OrderedDict of {Entity: Persona}. """ def constrained(entity): return (len(entity["options"].get("types", [])) + len(entity["options"].get("states", []))) rv = OrderedDict() performing = defaultdict(set) pool = list(personae) self.log.debug(pool, {"path": self.fP}) entities = OrderedDict([("".join(entity.attributes["names"]), entity) for entity in sorted(group_by_type(self.doc)[ EntityDirective.Declaration], key=constrained, reverse=True)]) for e in entities.values(): types = tuple( filter(None, (e.string_import(t, relative) for t in e["options"].get("types", [])))) states = tuple( filter( None, (int(t) if t.isdigit() else e.string_import(t, relative) for t in e["options"].get("states", [])))) otherRoles = {i.lower() for i in e["options"].get("roles", [])} typ = types or object persona = next( (i for i in pool if isinstance(i, typ) and getattr(i, "get_state", not states) and all( str(i.get_state(type(s))).startswith(str(s)) for s in states) and (performing[i].issubset(otherRoles) or not otherRoles)), None) rv[e] = persona performing[persona].update(set(e.attributes["names"])) if not otherRoles or list(rv.values()).count(persona) == roles: try: pool.remove(persona) except ValueError: self.log.debug( "No persona for type {0} and states {1} with {2} {3}.". format(typ, states, roles, "role" if roles == 1 else "roles"), {"path": self.fP}) return rv def cast(self, mapping): """Allocate the scene script a cast of personae for each of its entities. :param mapping: A dictionary of {Entity, Persona} :return: The SceneScript object. """ # See 'citation' method in # http://docutils.sourceforge.net/docutils/parsers/rst/states.py for c, p in mapping.items(): self.doc.note_citation(c) self.doc.note_explicit_target(c, c) c.persona = p self.log.debug( "{0} to be played by {1}".format(c["names"][0].capitalize(), p), {"path": self.fP}) return self def run(self): """Parse the script file. :rtype: :py:class:`~turberfield.dialogue.model.Model` """ model = Model(self.fP, self.doc) self.doc.walkabout(model) return model