<commit_before>import logging
from enum import Enum
import graphviz as gv
import subprocess


# generic hierarchial state machine class
# states can have substates.  If the machine is in a state, then it is also implicitly in that state's parent state
# this basically provides for polymorphism/subclassing of state machines
class StateMachine:

    def __init__(self, start_state):
        # stores all states in the form _state_hierarchy[state] = parent_state
        self._state_hierarchy = {}
        self._transitions = {}
        self._start_state = start_state
        self._state = None


    @property
    def start_state(self):
        return self._start_state


    def add_state(self, state, parent_state=None):
        if not isinstance(state, Enum):
            raise TypeError("State should be an Enum type")
        self._state_hierarchy[state] = parent_state


    # checks transition conditions for all edges leading away from the current state
    # if one evaluates to true, we transition to it
    # if more than one evaluates to true, we throw a RuntimeError
    def run(self):
        s1 = self.state

        # call execute_STATENAME
        if self.state != None:
            for state in self.ancestors_of_state(self.state) + [self.state]:
                method_name = "execute_" + state.name
                try:
                    state_method = getattr(self, method_name)()
                except AttributeError:
                    pass

        if self.state == None:
            self.transition(self.start_state)
        else:
            # transition if an 'event' fires
            next_states = []
            if self.state in self._transitions:
                for next_state, transition in self._transitions[self.state].items():
                    if transition['condition']():
                        next_states += [next_state]

            if len(next_states) > 1:
                raise RuntimeError("Ambiguous fsm transitions from state'" + str(self.state) + "'.  The following states are reachable now: " + str(next_states))
            elif len(next_states) == 1:
                self.transition(next_states[0])

        # if a transition occurred during the run, we'll run again
        # note: this could potentially cause infinite recursion (although it shouldn't)
        if s1 != self.state:
            self.run()




    # if you add a transition that already exists, the old one will be overwritten
    def add_transition(self, from_state, to_state, condition, event_name):
        if from_state not in self._transitions:
            self._transitions[from_state] = {}

        self._transitions[from_state][to_state] = {'condition': condition, 'name': event_name}


    # sets @state to the new_state given
    # calls 'on_exit_STATENAME()' if it exists<commit_msg>closes #43 - ambiguous fsm transitions warn, instead of erroring<commit_after>import logging
from enum import Enum
import graphviz as gv
import subprocess


# generic hierarchial state machine class
# states can have substates.  If the machine is in a state, then it is also implicitly in that state's parent state
# this basically provides for polymorphism/subclassing of state machines
class StateMachine:

    def __init__(self, start_state):
        # stores all states in the form _state_hierarchy[state] = parent_state
        self._state_hierarchy = {}
        self._transitions = {}
        self._start_state = start_state
        self._state = None


    @property
    def start_state(self):
        return self._start_state


    def add_state(self, state, parent_state=None):
        if not isinstance(state, Enum):
            raise TypeError("State should be an Enum type")
        self._state_hierarchy[state] = parent_state


    # checks transition conditions for all edges leading away from the current state
    # if one evaluates to true, we transition to it
    # if more than one evaluates to true, we throw a RuntimeError
    def run(self):
        s1 = self.state

        # call execute_STATENAME
        if self.state != None:
            for state in self.ancestors_of_state(self.state) + [self.state]:
                method_name = "execute_" + state.name
                try:
                    state_method = getattr(self, method_name)()
                except AttributeError:
                    pass

        if self.state == None:
            self.transition(self.start_state)
        else:
            # transition if an 'event' fires
            next_states = []
            if self.state in self._transitions:
                for next_state, transition in self._transitions[self.state].items():
                    if transition['condition']():
                        next_states += [next_state]

            if len(next_states) > 1:
                logging.warn("Ambiguous fsm transitions from state'" + str(self.state) + "'.  The following states are reachable now: " + str(next_states) + ";  Proceeding by taking the first option.")
            self.transition(next_states[0])

        # if a transition occurred during the run, we'll run again
        # note: this could potentially cause infinite recursion (although it shouldn't)
        if s1 != self.state:
            self.run()




    # if you add a transition that already exists, the old one will be overwritten
    def add_transition(self, from_state, to_state, condition, event_name):
        if from_state not in self._transitions:
            self._transitions[from_state] = {}

        self._transitions[from_state][to_state] = {'condition': condition, 'name': event_name}


    # sets @state to the new_state given
    # calls 'on_exit_STATENAME()' if it exists
{'fraction_non_alphanumeric': 0.07631440126604537, 'fraction_numerical': 0.0019342359767891683, 'mean_word_length': 2.383700178465199, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1842740', 'n_tokens_mistral': 1541, 'n_tokens_neox': 1449, 'n_words': 517}