Пример #1
0
def get_recent_courses() -> ([Path], [str], [int]):
    """
    Return the list of (recent course, title) found in the user_config dict
    """

    recent_courses = cvars.get("course.recent_courses", [])
    courses_titles = cvars.get("course.recent_courses_titles", [])
    exercises_numbers = cvars.get("course.exercise_numbers", [])

    courses_paths = list(map(Path, recent_courses))
    exercises_numbers = exercises_numbers or ([-1] * len(recent_courses))

    #if recent_courses:
    #    recent_courses_list = recent_courses.split(',')
    #    courses_paths = list(map(Path, recent_courses_list))
    #    courses_titles = titles.split(',')
    #    exercises_numbers = [-1] * len(recent_courses_list)
    #else:
    #    courses_paths = []
    #    courses_titles = []
    #    exercises_numbers = []

    #if exercises_numbers:
    #    try:
    #        exercises_numbers = list(map(int, numbers.split(',')))
    #    except ValueError:
    #        pass
    return courses_paths, courses_titles, exercises_numbers
    def __init__(self, parent):
        super().__init__(parent)

        # Icon
        self.iconWidget = QLabel(self)
        icons_base_dir = cvars.get("icons.path")
        error_icon_path = fs.path_helper(icons_base_dir) / 'cancel.png'
        success_icon_path = fs.path_helper(icons_base_dir) / 'checked.png'
        self.error_pixmap = QPixmap(str(error_icon_path.resolve()))
        self.success_pixmap = QPixmap(str(success_icon_path.resolve()))
        self.iconWidget.setScaledContents(True)
        self.iconWidget.setMaximumSize(self.height(), self.height())

        # Message
        self.messageWidget = QLabel("", self)

        # Insert icon and message
        self.insertWidget(0, self.iconWidget)
        self.insertWidget(1, self.messageWidget)
        self.show_success_icon()  # Trick: the status bar adapts its height
        self.hide_icon()

        # Verbose mode
        self.display_success_msgs = cvars.get(
            'display.display_success_messages', True)
    def __init__(self, tag: str):
        """
        Init self with a tag given as a str. See self.__doc__.

        :param tag: One of '+', '=', '≠'.
        """

        icons_base_dir = cvars.get('icons.path')
        icons_type = cvars.get('icons.context')  # e.g. 'blue'
        icons_dir = path_helper(icons_base_dir) / icons_type

        if tag not in ['=', '+', '≠']:
            # TODO: catch the exception below?
            raise ValueError('tag must be one of "=", "+", "≠". tag: {tag}.')
        elif tag == '=':
            super().__init__('')  # No icon, empty icon trick
            return None
        elif tag == '+':
            icon_path = icons_dir / 'tag_plus.png'
        elif tag == '≠':
            icon_path = icons_dir / 'tag_different.png'

        # TODO: this overwrites previous code
        icon_path = path_helper(icons_base_dir) / 'checked.png'
        super().__init__(str(icon_path.resolve()))
Пример #4
0
def init():
    global packages

    # Load package config
    # merge platform-agnostic packages with platform-specific ones.
    plist = {
        **cvars.get("package.all"),
        **cvars.get(f"package.{__platform_os}")
    }

    for name, conf in plist.items():
        packages[name] = package.from_config(conf)
    def __init__(self):
        super().__init__(_('Toolbar'))
        icons_base_dir = cvars.get("icons.path")
        icons_dir = fs.path_helper(icons_base_dir)
        self.rewind = QAction(
                QIcon(str((icons_dir / 'goback-begining.png').resolve())),
                _('Go back to beginning of proof'), self)
        self.undo_action = QAction(
                QIcon(str((icons_dir / 'undo_action.png').resolve())),
                _('Undo action'), self)
        self.redo_action = QAction(
                QIcon(str((icons_dir / 'redo_action.png').resolve())),
                _('Redo action'), self)

        self.toggle_lean_editor_action = QAction(
                QIcon(str((icons_dir / 'lean_editor.png').resolve())),
                _('Toggle L∃∀N'), self)

        self.change_exercise_action = QAction(
                QIcon(str((icons_dir / 'change_exercise.png').resolve())),
                _('Change exercise'), self)

        self.addAction(self.rewind)
        self.addAction(self.undo_action)
        self.addAction(self.redo_action)
        self.addAction(self.toggle_lean_editor_action)
        self.addSeparator()
        self.addAction(self.change_exercise_action)
Пример #6
0
    def save_exercise_for_autotest(self, emw):
        """

        :param emw: ExerciseMainWindow instance
        """
        save = cvars.get('functionality.save_solved_exercises_for_autotest',
                         False)
        if not save:
            return

        auto_steps = [
            entry.misc_info.get("proof_step").auto_step
            for entry in self.history
        ]
        auto_steps = [step for step in auto_steps if step is not None]

        exercise = emw.exercise
        exercise.refined_auto_steps = auto_steps
        filename = ('test_' + exercise.lean_short_name).replace('.', '_') \
            + '.pkl'
        file_path = cdirs.test_exercises / filename
        check_dir(cdirs.test_exercises, create=True)

        total_string = 'AutoTest\n'
        for step in auto_steps:
            total_string += '    ' + step.raw_string + ',\n'
        print(total_string)

        log.debug(f"Saving auto_steps in {file_path}")
        with open(file_path, mode='wb') as output:
            dump(exercise, output, HIGHEST_PROTOCOL)
Пример #7
0
def init():
    global _

    log.info("Init i18n")

    available_languages = cvars.get("i18n.available_languages", "en")
    #available_languages = available_languages.split(", ")

    select_language = cvars.get('i18n.select_language', "en")

    log.info(f"available_languages = {available_languages}")
    log.info(f"select_language     = {select_language}")

    language_dir_path = fs.path_helper(cdirs.share / "locales")
    language = gettext.translation('deaduction',
                                   localedir=str(language_dir_path),
                                   languages=[select_language])
    language.install()

    _ = language.gettext
    def __init__(self, statement: Statement):
        """
        Init self with an instance of the class (or child of) Statement.

        :param statement: The instance of the class (or child) one wants
            to associate to self.
        """
        if StatementsTreeWidget.show_lean_name_for_statements:
            to_display = [statement.pretty_name, statement.lean_name]
        else:
            to_display = [statement.pretty_name]

        super().__init__(None, to_display)

        self.statement = statement

        # Print second col. in gray
        self.setForeground(1, QBrush(QColor('gray')))
        # TODO: use mono font for lean name column (column 1)

        # Print icon (D for definition, T for theorem, etc)
        icons_base_dir = cvars.get("icons.path")
        icons_type = cvars.get("icons.letter_type")

        icons_dir = fs.path_helper(icons_base_dir) / icons_type
        if isinstance(statement, Definition):
            path = icons_dir / 'd.png'
        elif isinstance(statement, Exercise):
            path = icons_dir / 'e.png'
        elif isinstance(statement, Theorem):
            path = icons_dir / 't.png'
        self.setIcon(0, QIcon(str(path.resolve())))

        # Set tooltip
        self.setToolTip(0, statement.caption)
        # These tooltips contain maths
        # math_font_name = 'Default'  # FIXME!!
        math_font_name = cvars.get('display.mathematics_font', 'Default')
        QToolTip.setFont(math_font_name)
Пример #9
0
    def save_exercise_with_proof_steps(self, emw):
        """
        (1) Incorporate journal as auto_steps attribute to emw.exercise,
        and save this to cdirs.journal as a 'pkl' file.
        (2) Compute a text version of each proof step, and save the result
        in a 'txt' file.

        :param emw: ExerciseMainWindow instance
        """

        save = cvars.get('journal.save', False)
        if not save:
            return

        # Building auto_steps
        auto_steps = [
            proof_step.auto_step for proof_step in self.memory
            if proof_step.auto_step is not None
        ]

        # Saving
        date = time.strftime("%d%b%Hh%M")
        exercise = emw.exercise
        exercise.refined_auto_steps = auto_steps
        filename = 'journal_' \
                   + exercise.lean_short_name.replace('.', '_') \
                   + date \
                   + '.pkl'
        check_dir(cdirs.journal, create=True)
        file_path = cdirs.journal / filename

        total_string = 'ProofSteps\n'
        for step in auto_steps:
            total_string += '    ' + step.raw_string + ',\n'
        print(total_string)

        log.debug(f"Saving auto_steps in {file_path}")
        with open(file_path, mode='xb') as output:
            pickle.dump(exercise, output, pickle.HIGHEST_PROTOCOL)

        file_path = file_path.with_suffix('.txt')
        log.debug(f"Saving journal in {file_path}")
        txt = self.display()
        print(txt)
        with open(file_path, mode='xt') as output:
            output.write(txt)
Пример #10
0
def add_to_recent_courses(course_path: Path,
                          course_type: str = ".lean",
                          title: str = "",
                          exercise_number: int = -1):
    """
    Add course_path to the list of recent courses in cvars["course"]
    """

    max_ = cvars.get("course.max_recent_courses", 5)

    if course_type == ".pkl" and course_path.suffix == ".lean":
        course_path = course_path.with_suffix(".pkl")

    course_path = course_path.resolve()
    courses_paths, courses_titles, exercises_numbers = get_recent_courses()
    if course_path in courses_paths:
        # We want the course to appear in pole position
        # 0 = newest, last = oldest
        n = courses_paths.index(course_path)
        courses_paths.pop(n)
        courses_titles.pop(n)
        exercises_numbers.pop(n)
    courses_paths.insert(0, course_path)
    courses_titles.insert(0, title)
    exercises_numbers.insert(0, exercise_number)

    if len(courses_paths) > max_:
        # Remove oldest
        courses_paths.pop()
        courses_titles.pop()
        exercises_numbers.pop()

    # Turn each list into a single string
    courses_paths_strings = [str(path.resolve()) for path in courses_paths]

    #courses_paths_string   = ','.join(courses_paths_strings)
    #courses_titles_string = ','.join(courses_titles)
    #exercises_numbers_string = ','.join(map(str, exercises_numbers))

    cvars.set("course.recent_courses", courses_paths_strings)
    cvars.set("course.recent_courses_titles", courses_titles)
    cvars.set("course.exercise_numbers", exercises_numbers)

    # Save config file
    cvars.save()
    def __init__(self, tagged_mathobjects: [Tuple[MathObject, str]] = []):
        """
        Init self an ordered list of tuples (mathobject, tag), where
        mathobject is an instance of the class MathObject (not
        MathObjectWidgetItem!) and tag is mathobject's tag a str,
        (see _TagIcon.__doc__).

        :param tagged_mathobjects: The list of tagged instances of the
            class MathObject.
        """

        super().__init__()

        # TODO: make self.items a property?
        self.items = []
        # set fonts for maths display
        math_font_name = cvars.get('display.mathematics_font', 'Default')
        self.setFont(QFont(math_font_name))
        for mathobject, tag in tagged_mathobjects:
            item = MathObjectWidgetItem(mathobject, tag)
            self.addItem(item)
            self.items.append(item)

        self.itemDoubleClicked.connect(self._emit_apply_math_object)
Пример #12
0
    def __init__(self,
                 choices,
                 title          = "",
                 output         = "",
                 cancel_button  = False,
                 choice         = None,
                 parent         = None):
        """
        :param choices:  list of couples (caption, text) where caption
                             will be the text in the button, and text will
                             be displayed as the corresponding choice
        :param title:        window title
        :param output:     output, appears inside window
        :type cancel_button: if True then a Cancel button will appear
        :type choice:        contains either None or the number of the
                             chosen button
        """
        super(ButtonsDialog, self).__init__(parent)
        self.setWindowTitle(title)

        self.output = output
        self.buttons = []
        self.choices = []
        self.choice = choice

        # Display #
        layout = QVBoxLayout()

        # Output line
        output_line = QLabel(output, self, StyleSheet='font-weight: bold;')
        math_font_name = cvars.get('display.mathematics_font', 'Default')
        output_line.setFont(QFont(math_font_name))
        output_layout = QHBoxLayout()
        output_layout.addWidget(output_line)
        output_layout.addStretch(1)

        # Filling the lines
        layout.addLayout(output_layout)  # 1st line
        layout.addSpacing(5)

        # Buttons and corresponding texts, one new_layout per line
        for caption, choice in choices:
            new_button = QPushButton(caption)
            self.buttons.append(new_button)
            self.choices.append(QLabel(choice, self))
            new_layout = QHBoxLayout()

            new_layout.addWidget(self.buttons[-1])
            new_layout.addWidget(self.choices[-1])
            new_layout.addStretch(1)
            layout.addLayout(new_layout)
            layout.addSpacing(-5)

        # Cancel button
        if cancel_button:
            self.cancel = QPushButton("Cancel")
            new_layout = QHBoxLayout()
            new_layout.addStretch(1)
            new_layout.addWidget(self.cancel)
            new_layout.addStretch(1)
            layout.addLayout(new_layout)

        layout.setContentsMargins(10, 10, 10, 20)
        self.setLayout(layout)

        # Signals
        if cancel_button:
            self.cancel.clicked.connect(self.reject)
        for button in self.buttons:
            # call the button_clicked function with the right number
            button.clicked.connect(partial(self.button_clicked,
                                           number=self.buttons.index(button)))
class StatementsTreeWidget(QTreeWidget):
    """
    This class renders an ordered list of instances of the class
    Statement in a tree (inherits from QTreeWidget) presentation. The
    nodes correspond to hierarchical elements (e.g. chapter/sections/…)
    and the leaves to statements in this hierarchy. This class is
    instantiated with a list of instances of the class Statement and
    what we call an outline, which is just a Dict[str, str] (most of
    the time Exercise.outline attribute) of which keys are hierarchy
    levels (e.g. 'groups.finite_groups') and values are pretty names
    for display (e.g. 'Finite groups'). Furthermore, those instances
    contain all L∃∀N-understandable data. In order to instantiate
    StatementsTreeWidget, one only needs an instance of the class
    Statement and an outline (see above). 

    Here is an example of what the tree looks like given an ordered
    list of three statements with the following lean names:
        groups.first_definitions.definition.group,
        groups.first_definitions.theorem.Lagrange,
        rings.introduction.definition.ring;
    and the following outline:
        { 'groups': 'Groups',
          'groups.first_definitions': 'First definitions',
          'rings': 'Rings'
          'rings.introduction': 'Introduction'},
    we end up with the following tree:
        Groups (StatementsTreeWidgetNode)
        └─  First definitions (StatementsTreeWidgetNode)
            └─  (D) Definition (StatementsTreeWidgetItem)
            └─  (T) Lagrange's theorem (StatementsTreeWidgetItem)
        Rings (StatementsTreeWidgetNode)
        └─  Introduction (StatementsTreeWidgetNode)
            └─  (D) Definition (StatementsTreeWidgetItem)
    (D) and (T) represent icons which enable to distinguish definitions,
    theorems and exercises. Note that statements pretty names (which
    enable to display 'Lagranges's theorem instead of 'lagrange') are
    not in the outline, they are already coded in the instances of the
    class Statement themselves with the attribute pretty_name.
    """

    # TODO: Put this in self.__init__
    # Config
    depth_of_unfold_statements = \
                        cvars.get("display.depth_of_unfold_statements")

    show_lean_name_for_statements = \
                    cvars.get("display.show_lean_name_for_statements")

    tooltips_font_size = cvars.get('display.tooltips_font_size', 10)

    # TODO: show lean names only when lean console is on
    # (even if show_lean_name_for_statements == TRUE)

    def _init_tree_branch(self, extg_tree, branch: [str],
                          expanded_flags: [bool], statement: Statement,
                          parent):
        """
        Add branch to extg_tree and StatementsTreeWidgetItem(statement)
        at the end of branch. This function is recursive.

        :param extg_tree: A tree implemented as a recursive
            dictionary, see self._init_tree.__doc__.
        :param branch: A tree branch (new or already existing), e.g.
            ['Chapter', 'Section', 'Sub-section']. Those are not
            instances of the class StatementsTreeWidgetNode!
        :param expanded_flags: list of booleans corresponding to branch,
        expanded_flags[n] = True if branch[n] must be expanded
        :param statement: The instance of the
            class Statement one wants to represent.
        :param parent: Either extg_tree itself or one of its nodes
            (StatementsTreeWidgetNode). At the first call of the method,
            it should be self. The recursion takes care of the rest.
        """

        # If branch is empty, put statement at the end. This occurs when
        # the branch is already created.
        if not branch:
            item = StatementsTreeWidgetItem(statement)
            root = item.text(0)
            extg_tree[root] = (item, dict())
            parent.add_child(item)
            return None

        # Else go through the already existing branch and create
        # children nodes if necessary.
        root = branch[0]  # 'rings'
        branch = branch[1:]  # ['ideals', 'def']
        flag = expanded_flags[0]
        expanded_flags = expanded_flags[1:]

        if root not in extg_tree:
            node = StatementsTreeWidgetNode(root)
            extg_tree[root] = (node, dict())
            parent.add_child(node)
            node.setExpanded(flag)  # Must be done AFTER node is added

        self._init_tree_branch(extg_tree[root][1], branch, expanded_flags,
                               statement, extg_tree[root][0])

    def _init_tree(self, statements: [Statement], outline: Dict[str, str]):
        """
        Initiate self's tree given an ordered list of instances of the
        class Statement and the so-called outline (see self.__doc__).
        All the work is done by calling self._init_tree_branch for each
        statement with the right arguments.

        All the tree (structure, nodes, items, titles, etc) is encoded
        as a recursive dictionary. Keys are titles to be displayed and
        values are tuple t, where:
            - t[0] is either an instance of the class
              StatementsTreeWidgetNode or StatementsTreeWidgetItem;
            - t[1] is a dictionary on the same principle.
        Here is an example:

        {'Groups': (StatementsTreeWidgetNode('Groups'),
            {'Subgroups': (StatementsTreeWidgetNode(None, 'Subgroups'),
                {statement.text(0): (statement, dict())})})}

        :param statements: The ordered list of instances of the class
            (or child of) Statement one wants to display.
        :param outline: A Dict[str, str] in which keys are
            hierarchy levels (e.g.  'rings_and_ideals') and values are
            their pretty names (e.g. 'Rings and ideals'), see
            self.__doc__. 
        """

        self._tree = dict()

        # set flags for expandedness: branches will be expanded until
        # a certain depth, and unexpanded after
        depth = StatementsTreeWidget.depth_of_unfold_statements

        for statement in statements:
            branch = statement.pretty_hierarchy(outline)
            # set expanded_flags to True until depth and False after:
            length = len(branch)
            if length >= depth:
                expanded_flags = [True] * depth + [False] * (length - depth)
            else:
                expanded_flags = [True] * length
            self._init_tree_branch(self._tree, branch, expanded_flags,
                                   statement, self)

    def __init__(self, statements: [Statement], outline: Dict[str, str]):
        """
        Init self with a list of instances of the class Statement (or
        child of) and an outline (see self.__doc__). This method
        automatically calls self._init_tree, which itself instantiates
        all the nodes and items in self._init_tree_branch.

        :param statements: The ordered list of instances of the class
            (or child of) Statement one wants to display.
        :param outline: A Dict[str, str] in which keys are
            hierarchy levels (e.g.  'rings_and_ideals') and values are
            their pretty names (e.g. 'Rings and ideals'), see
            self.__doc__. 
        """

        # TODO: get rid of self._init_tree ?

        super().__init__()
        self._init_tree(statements, outline)

        # Cosmetics
        self.setWindowTitle('StatementsTreeWidget')
        if StatementsTreeWidget.show_lean_name_for_statements:
            self.setHeaderLabels([_('Statements'), _('L∃∀N name')])
            self.resizeColumnToContents(0)
            self.resizeColumnToContents(1)
        else:
            self.resizeColumnToContents(0)
            self.setHeaderLabels([_('Statements')])

    def add_child(self, item):
        """
        Called in self._init_tree_branch, do not delete!  Useful not to
        have to make a difference between self.addTopLevelItem when we
        add an item to the tree itself or parent.add_child when we add
        an item to a parent which is a tree item.

        :param item: Either a StatementsTreeWidgetItem or a
            StatementsTreeWidgetNode.
        """

        self.addTopLevelItem(item)

    def goto_statement(self, statement: Statement):
        """
        Go to to the Statement statement (as if usr clicked on it).

        :param statement: Statement to go to.
        """

        # Thanks @mcosta from https://forum.qt.io/topic/54640/
        # how-to-traverse-all-the-items-of-qlistwidget-qtreewidget/3
        def traverse_node(item: StatementsTreeWidgetItem):
            # Do something with item
            if isinstance(item, StatementsTreeWidgetItem):
                if item.statement == statement:
                    item.setSelected(True)
            for i in range(0, item.childCount()):
                traverse_node(item.child(i))

        for i in range(self.topLevelItemCount()):
            item = self.topLevelItem(i)
            traverse_node(item)

    def from_name(self, lean_name: str) \
            -> StatementsTreeWidgetItem:
        """
        Return the StatementsTreeWidgetItem whose statement's pretty name is
        pretty_name.
        """

        items = []

        def traverse_node(item: StatementsTreeWidgetItem):
            if isinstance(item, StatementsTreeWidgetItem):
                if item.statement.has_name(lean_name):
                    items.append(item)
            for i in range(0, item.childCount()):
                traverse_node(item.child(i))

        for i in range(self.topLevelItemCount()):
            item = self.topLevelItem(i)
            traverse_node(item)
        if items:
            return items[0]
        else:
            return None
    def __init__(self, exercise: Exercise):
        """
        Init self with an instance of the class Exercise. See
        self.__doc__.

        :param exercise: The instance of the Exercise class representing
            the exercise to be solved by the user.
        """

        super().__init__()

        # ───────────── Init layouts and boxes ───────────── #
        # I wish none of these were class atributes, but we need at
        # least self.__main_lyt and self.__context_lyt in the method
        # self.update_goal.

        self.__main_lyt     = QVBoxLayout()
        self.__context_lyt  = QVBoxLayout()
        context_actions_lyt = QHBoxLayout()
        actions_lyt         = QVBoxLayout()
        action_btns_lyt     = QVBoxLayout()

        action_btns_lyt.setContentsMargins(0, 0, 0, 0)
        action_btns_lyt.setSpacing(0)

        actions_gb = QGroupBox(_('Actions and statements (transform context '
                                 'and target)'))
        context_gb = QGroupBox(_('Context (objects and properties)'))

        # ──────────────── Init Actions area ─────────────── #

        self.logic_btns = ActionButtonsWidget(exercise.available_logic)
        self.proof_btns = ActionButtonsWidget(exercise.available_proof)
        self.magic_btns = ActionButtonsWidget(exercise.available_magic)

        # Search for ActionButton corresponding to action_apply
        # (which will be called by double-click):
        apply_buttons = [button for button in self.proof_btns.buttons
                         if button.action.run == action_apply]
        if apply_buttons:
            self.action_apply_button = apply_buttons[0]

        statements           = exercise.available_statements
        outline              = exercise.course.outline
        self.statements_tree = StatementsTreeWidget(statements, outline)

        # ─────── Init goal (Context area and target) ────── #

        self.objects_wgt = MathObjectWidget()
        self.props_wgt   = MathObjectWidget()
        self.target_wgt  = TargetWidget()

        # ───────────── Put widgets in layouts ───────────── #

        # Actions
        action_btns_lyt.addWidget(self.logic_btns)
        action_btns_lyt.addWidget(self.proof_btns)
        if exercise.available_magic:
            action_btns_lyt.addWidget(self.magic_btns)
        actions_lyt.addLayout(action_btns_lyt)
        actions_lyt.addWidget(self.statements_tree)
        actions_gb.setLayout(actions_lyt)

        # Context
        self.__context_lyt.addWidget(self.objects_wgt)
        self.__context_lyt.addWidget(self.props_wgt)
        context_gb.setLayout(self.__context_lyt)

        # https://i.kym-cdn.com/photos/images/original/001/561/446/27d.jpg
        context_actions_lyt.addWidget(context_gb)
        context_actions_lyt.addWidget(actions_gb)

        target_display_on_top = cvars.get('display.target_display_on_top',
                                          True)
        if target_display_on_top:
            self.__main_lyt.addWidget(self.target_wgt)
            self.__main_lyt.addLayout(context_actions_lyt)
        else:
            self.__main_lyt.addLayout(context_actions_lyt)
            self.__main_lyt.addWidget(self.target_wgt)

        self.setLayout(self.__main_lyt)
Пример #15
0
import deaduction.pylib.config.dirs as cdirs
import deaduction.pylib.config.environ as cenv
import deaduction.pylib.config.site_installation as inst
import deaduction.pylib.config.vars as cvars
from deaduction.pylib.autotest import select_exercise

# (non-exhaustive) list of logger domains:
# ['lean', 'ServerInterface', 'Course', 'deaduction.dui',
#  'deaduction.pylib.coursedata', 'deaduction.pylib.mathobj', 'LeanServer']

###################
# Configuring log #
###################
# Change your own settings in .deaduction-dev/config.toml
log_domains = cvars.get("logs.domains", "")
log_level = cvars.get("logs.display_level", 'info')

if os.getenv("DEADUCTION_DEV_MODE", False):
    log_level = 'debug'
    log_domains = ["deaduction", "__main__", 'ServerInterface']

logger.configure(domains=log_domains, display_level=log_level)

log = logging.getLogger(__name__)

###########################
# Configuring args parser #
###########################

arg_parser = argparse.ArgumentParser("Start deaduction graphical interface "
Пример #16
0
                                      test_selection,
                                      action,
                                      CodeForLean,
                                      apply_exists,
                                      apply_and,
                                      apply_or)
from deaduction.pylib.mathobj import (MathObject,
                                      get_new_hyp,
                                      give_global_name,
                                      NO_MATH_TYPE,
                                      NUMBER_SETS_LIST)

log = logging.getLogger(__name__)

# Parameters
allow_proof_by_sorry = cvars.get('functionality.allow_proof_by_sorry')

# Turn proof_button_texts into a dictionary
proof_list = ['action_apply', 'proof_methods', 'new_object']
lbt = tooltips.get('proof_button_texts').split(', ')
proof_button_texts = {}
for key, value in zip(proof_list, lbt):
    proof_button_texts[key] = value


@action(tooltips.get('tooltip_proof_methods'),
        proof_button_texts['proof_methods'])
def action_use_proof_methods(proof_step,
                             selected_objects: [MathObject],
                             user_input: [str] = [],
                             target_selected: bool = True) -> CodeForLean:
    def __init__(self,
                 target: MathObject = None,
                 tag: str = None,
                 goal_count: str = ''):
        """"
        Init self with an target (an instance of the class ProofStatePO)
        and a tag. If those are None, display an empty tag and '…' in
        place of the target. A caption is added on top of the target.

        :param target: The target to be displayed.
        :param tag: The tag associated to target.
        :param goal_count: a string indicating the goal_count state,
        e.g. "  2 / 3" means the goal number 2 out of 3 is currently being
        studied
        """

        super().__init__()

        self.target = target
        self.tag = tag

        # ───────────────────── Widgets ──────────────────── #
        caption_label = QLabel(_('Target') + goal_count)
        self.setToolTip(_('To be proved'))
        # TODO: put the pre-set size of group boxes titles
        caption_label.setStyleSheet('font-size: 11pt;')

        # Display
        #   ∀ x ∈ X, ∃ ε, …
        # and not
        #   H : ∀ x ∈ X, ∃ ε, …
        # where H might be the lean name of the target. That's what
        # the .math_type is for.
        # 'is_math_type=True' triggers the bound variables naming
        if target:
            # log.debug("updating target")
            text = target.math_type.to_display(is_math_type=True)
        else:
            text = '…'
        self.target_label = QLabel(text)

        size = cvars.get('display.target_font_size')
        self.target_label.unselected_style = f'font-size: {size};'
        self.target_label.selected_style = self.target_label.unselected_style \
            + f'background-color: limegreen;'
        self.target_label.setStyleSheet(self.target_label.unselected_style)

        # Set fonts for maths display
        math_font_name = cvars.get('display.mathematics_font', 'Default')
        self.target_label.setFont(QFont(math_font_name))

        # ───────────────────── Layouts ──────────────────── #

        central_layout = QVBoxLayout()
        central_layout.addWidget(caption_label)
        central_layout.addWidget(self.target_label)

        main_layout = QHBoxLayout()
        main_layout.addStretch()
        main_layout.addLayout(central_layout)
        main_layout.addStretch()
        self.setLayout(main_layout)
Пример #18
0
class Journal:
    """
    A class to record events in the memory attribute. The events occurring
    during a proof step are stored in the proof_step attribute of
    ExerciseMainWindow, and then stored in Journal.memory.
    """

    memory: [ProofStep]

    __save_journal = cvars.get('journal.save')
    __journal_file_name = cdirs.local / 'journal.pkl'

    def __init__(self, display_message=None):
        self.memory = []

    def store(self, proof_step: ProofStep, emw):
        proof_step.time = time.localtime(time.time())
        self.memory.append(proof_step)
        # display = AutoStep.from_proof_step(proof_step, emw)
        # log.debug(f"Storing proof_step {display}")

    def save_exercise_with_proof_steps(self, emw):
        """
        (1) Incorporate journal as auto_steps attribute to emw.exercise,
        and save this to cdirs.journal as a 'pkl' file.
        (2) Compute a text version of each proof step, and save the result
        in a 'txt' file.

        :param emw: ExerciseMainWindow instance
        """

        save = cvars.get('journal.save', False)
        if not save:
            return

        # Building auto_steps
        auto_steps = [
            proof_step.auto_step for proof_step in self.memory
            if proof_step.auto_step is not None
        ]

        # Saving
        date = time.strftime("%d%b%Hh%M")
        exercise = emw.exercise
        exercise.refined_auto_steps = auto_steps
        filename = 'journal_' \
                   + exercise.lean_short_name.replace('.', '_') \
                   + date \
                   + '.pkl'
        check_dir(cdirs.journal, create=True)
        file_path = cdirs.journal / filename

        total_string = 'ProofSteps\n'
        for step in auto_steps:
            total_string += '    ' + step.raw_string + ',\n'
        print(total_string)

        log.debug(f"Saving auto_steps in {file_path}")
        with open(file_path, mode='xb') as output:
            pickle.dump(exercise, output, pickle.HIGHEST_PROTOCOL)

        file_path = file_path.with_suffix('.txt')
        log.debug(f"Saving journal in {file_path}")
        txt = self.display()
        print(txt)
        with open(file_path, mode='xt') as output:
            output.write(txt)

    def display(self):
        """
        Compute a txt version of the proof stored in journal.memory.
        :return:
        """
        display_txt = ""
        step_counter = 0
        time_deltas = [0]
        for counter in range(len(self.memory)):
            if counter < len(self.memory) - 1:
                t2 = self.memory[counter + 1].time
                t1 = self.memory[counter].time
                # Time diff in seconds
                delta = (t2.tm_min - t1.tm_min) * 60 + t2.tm_sec - t1.tm_sec
                time_deltas.append(delta)
        time_deltas.append(0)
        for step, time_delta in zip(self.memory, time_deltas):
            # Compute display of the proof step:
            step_txt = step.display()  # ProofStep.display()
            time_display = "#" * int(time_delta / 10)
            time_display2 = str(step.time.tm_min) + "'" + str(step.time.tm_sec)
            step_counter += 1
            more_txt = "------------------------------------\n"
            more_txt += time_display + "\n"
            more_txt += _("Step n°{} ").format(step_counter) \
                + "(" + time_display2 + ")" + "\n"
            more_txt += step_txt
            display_txt += more_txt
        return display_txt
Пример #19
0
                                      test_selection, CodeForLean)

from deaduction.pylib.mathobj import (MathObject, give_global_name,
                                      get_new_hyp, NUMBER_SETS_LIST)

import deaduction.pylib.config.vars as cvars

log = logging.getLogger("logic")

# Get buttons symbols from config file
action_list = [
    'action_and', 'action_or', 'action_negate', 'action_implicate',
    'action_iff', 'action_forall', 'action_exists'
]

if cvars.get('display.use_logic_button_symbols'):
    logic_button_texts = cvars.get('display.logic_button_symbols')
else:
    logic_button_texts = tooltips.get('logic_button_texts')
# Turn logic_button_texts into a dictionary
lbt = logic_button_texts.split(', ')
logic_button_texts = {}
for key, value in zip(action_list, lbt):
    logic_button_texts[key] = value

#######
# AND #
#######


def construct_and(proof_step, user_input: [str]) -> CodeForLean:
Пример #20
0
def action_apply(proof_step,
                 selected_objects: [MathObject],
                 user_input: [str] = [],
                 target_selected: bool = True):
    """
    Translate into string of lean code corresponding to the action
    Function explain_how_to_apply should reflect the actions

    Apply last selected item on the other selected

    test for last selected item l[-1], and call functions accordingly:
    - apply_function, if item is a function
    - apply_susbtitute, if item can_be_used_for_substitution
    ONLY if the option expanded_apply_button is set:
    - apply_implicate, if item is an implication or a universal property
        (or call action_forall if l[-1] is a universal property and none of
        the other actions apply)
    - apply_exists, if item is an existential property
    - apply_and
    - apply_or
    ...
    """

    # fixme: rewrite to provide meaningful error msgs

    if not selected_objects:
        raise WrongUserInput(error=_("no property selected"))

    # Now len(l) > 0
    prop = selected_objects[-1]  # property to be applied

    # (1)   If user wants to apply a function
    #       (note this is exclusive of other types of applications)
    if prop.is_function():
        if len(selected_objects) == 1:
            # TODO: ask user for element on which to apply the function
            #   (plus new name, cf apply_forall)
            error = _("select an element or an equality on which to "
                      "apply the function")
            raise WrongUserInput(error=error)
        else:
            return apply_function(proof_step, selected_objects)

    codes = CodeForLean.empty_code()
    error = ""
    # (2) If rewriting is possible
    test, equality = prop.can_be_used_for_substitution()
    if test:
        codes = codes.or_else(apply_substitute(proof_step,
                                               selected_objects,
                                               user_input,
                                               equality))

    expanded_apply_button = cvars.get('expanded_apply_button', False)
    if expanded_apply_button:
        # What follows applies only if expanded_apply_button
        # (4) Other easy applications
        if len(selected_objects) == 1 and user_can_apply(selected_objects[0]):
            if prop.is_exists():
                codes = codes.or_else(apply_exists(proof_step,
                                                   selected_objects))
            if prop.is_and():
                codes = codes.or_else(apply_and(proof_step, selected_objects))
            if prop.is_or():
                codes = codes.or_else(apply_or(proof_step,
                                               selected_objects,
                                               user_input))

    if not codes.is_empty():
        return codes
    else:
        error = _("I cannot apply this")  # fixme: be more precise
        raise WrongUserInput(error)
Пример #21
0
def give_name(math_type,
              forbidden_vars: [MathObject],
              hints: [str] = [],
              proof_step=None) -> str:
    """
    Provide a name for a new variable.
    Roughly speaking,
        - look if math_type has a name which starts with an uppercase letter,
        and if so add the corresponding lowercase letter as the main hint
        - if the hint is not in forbidden_names then it will be the name ; in
        the opposite case we will try the letters in alphabetical order from
        the hint.

    If display.use_primes_for_variables_names is True, then will try to use
    prime: e.g. if hint = ["x"] but "x" is already used, if math_type equals
    the math_type of x, then "x'" will be tried, and even "x''" if
    EXERCISE.USE_SECONDS_FOR_VARIABLES_NAMES is True.

    Exception: if math_type = set xxx, (the variable denotes a subset),
    attribute an uppercase letter

    NB : if x : X but the property 'x belongs to A' is in context, then
    math_type could be A for a better hinting.

    :param math_type:       PropObj type of new variable
    :param forbidden_vars:  list of variables that must be avoided
    :param hints:           a hint for the future name
    :param proof_step:      current proof_step, useful only for naming props.
    :return:                a name for the new variable
    """

    # FIXME: choice of names needs to be improved!

    # List of forbidden names (with repeat)
    forbidden_names = [var.info['name'] for var in forbidden_vars]
    # log.debug(f"giving name to var, hints = {hints} type={math_type}")
    # log.debug(f"forbidden names: {forbidden_names}")

    ######################
    # special math types #
    ######################
    # Subsets will be named with uppercase letters
    if math_type.node in ['SET', 'TYPE', 'PROP']:
        upper_case_name = True
    else:
        upper_case_name = False

    # Properties are named 'Hn' where n is an integer
    if math_type.is_prop():
        if proof_step:  # For global prop names (-> H1, H2, ...)
            return get_new_hyp_from_forbidden_names(proof_step,
                                                    forbidden_names)
        else:  # For local prop names
            pass
    ##################
    # Managing hints #
    ##################
    # Avoid bad hints, e.g. for families where hints could be {E_i, i in I}
    # All hints have to be acceptable variable names!
    alphabet_lower = "abcdefghijklmnopqrstuvwxyz"
    alphabet_upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    alphabet_greek = "αβγδεζηθικλμνξοπρςστυφχψω" \
                     + "ΓΔΘΛΞΠΣΦΨΩ"
    alphabet = alphabet_lower + alphabet_upper + alphabet_greek
    for hint in hints:
        if hint not in alphabet:
            hints.remove(hint)

    if upper_case_name:
        hints = [hint[0].upper() for hint in hints]
        # Each hint is one uppercase letter
    else:
        hints = [hint[0].lower() for hint in hints]
        # Each hint is one lowercase letter

    # Lower case: add main hint
    # fixme: in some case main hint must be added in pole position,
    #  but not always...
    # according to math_type's name
    # e.g. math_type is "X" -> hint[0] = 'x'
    if (not upper_case_name) and 'name' in math_type.info:
        type_name = math_type.info["name"]
        if type_name[0] in alphabet_upper:
            hint = type_name[0].lower()
            # Insert iff hint is not already in hints
            # position = 0 --> pole position
            # position = 1 --> second position
            insert_maybe(hints, hint, position=0)

    # Standard hints
    standard_hints = ['A'] if math_type.node.startswith('SET') \
        else ['X'] if math_type.is_type(is_math_type=True) \
        else ['P'] if math_type.is_prop(is_math_type=True) \
        else ['f'] if math_type.is_function(is_math_type=True) \
        else ['n', 'm', 'p'] if math_type.is_nat(is_math_type=True) \
        else ['x']
    for standard_hint in standard_hints:
        insert_maybe(hints, standard_hint)

    ##########################################################
    # First trial: use hints, maybe with primes if permitted #
    ##########################################################
    for potential_name in hints:
        # Try each hints successively
        # log.debug(f"trying {potential_name}...")
        if potential_name not in forbidden_names:
            new_name = potential_name
            return new_name
        # If hint = "x" and this is already the name of a variable with the
        # same math_type as the variable we want to name,
        # then try to use "x'"
        elif cvars.get("display.use_primes_for_variables_names"):
            # here potential_name are assumed to be the name of some variable
            name = potential_name
            index_ = forbidden_names.index(name)
            variable = forbidden_vars[index_]
            potential_name = name + "'"
            # log.debug(f"Trying {potential_name}...")
            if math_type == variable.math_type:
                # Use "x'" only if "x" has the same type
                if potential_name not in forbidden_names:
                    return potential_name
                elif cvars.get('display.use_seconds_for_variables_names'):
                    name = potential_name
                    index_ = forbidden_names.index(name)
                    variable = forbidden_vars[index_]
                    potential_name = name + "'"
                    # log.debug(f"Trying {potential_name}...")
                    if math_type == variable.math_type \
                            and not potential_name.endswith("'''") \
                            and potential_name not in forbidden_names:
                        return potential_name

    ########################################################
    # Second trial: use alphabetical order from first hint #
    ########################################################
    starting_name = hints[0]
    counter = 0
    potential_name = starting_name
    max_letters = 3  # NB : must be ≤ 26 !
    while potential_name in forbidden_names and counter < max_letters + 1:
        potential_name = next_(potential_name)
        counter += 1
    if counter != max_letters:
        return potential_name
    #########################################
    # last trial: starting_name + subscript #
    #########################################
    # TODO: use index in utf8
    potential_name = starting_name
    counter = 0
    while potential_name + '_' + str(counter) in forbidden_names:
        counter += 1
    return potential_name + '_' + str(counter)
Пример #22
0
    def goal_to_text(self,
                     format_="utf8",
                     to_prove=True,
                     text_depth=5,
                     open_problem=False) -> str:
        """
        Compute a readable version of the goal as the statement of an
        exercise.

        :param format_:     parameter of MathObject.to_display method
        :param to_prove:    boolean.
            If True, the goal will be formulated as "Prove that..."
            If False, the goal will be formulated as "Then..." (useful if
            the goal comes from a Theorem or Definition)
        :param text_depth:  int
            A higher value entail a more verbose formulation (more symbols will
            be replaced by words).
        :param open_problem: if True, then display as "True or False?"

        :return: a text version of the goal
        """

        # fixme: depth>1 does not really work
        context = self.context
        target = self.target
        text = ""
        for math_object in context:
            math_type = math_object.math_type
            if math_type.is_prop():
                prop = math_object.math_type.to_display(text_depth=text_depth,
                                                        format_=format_,
                                                        is_math_type=True)
                new_sentence = _("Assume that") + " " + prop + "."
            else:
                name = math_object.to_display()
                name_type = math_type.to_display(is_math_type=True,
                                                 format_=format_,
                                                 text_depth=text_depth)
                if math_type.node == "FUNCTION" and text_depth == 0:
                    new_sentence = _("Let") + " " + name + ":" \
                                   + " " + name_type + "."
                else:
                    if cvars.get('i18n.select_language') == 'fr_FR':
                        # indispensable pour la gestion des espacements
                        # (le "be" anglais n'a pas d'équivalent en Français)
                        new_sentence = "Soit" + " " + name + " " \
                                       + name_type + "."
                    else:
                        new_sentence = _("Let") + " " + name + " " + _("be") \
                                   + " " + name_type + "."

            if text:
                text += "\n"
            text += new_sentence

        if text:
            text += "\n"
        target_text = target.math_type.to_display(text_depth=text_depth,
                                                  format_="utf8",
                                                  is_math_type=True)
        if to_prove and not open_problem:
            target_text = _("Prove that") + " " + target_text
        elif text:
            target_text = _("Then") + " " + target_text
        else:
            target_text = target_text.capitalize()
            # Little issue: if sentence starts with a lower case
            # variable. This should never happen though...
        if open_problem:
            text = _("True or False?") + "\n" + text

        text += target_text + "."
        return text
Пример #23
0
    def __init__(self, exercise: Exercise, servint: ServerInterface):
        """
        Init self with an instance of the exercise class and an instance of the
        class ServerInterface. Both those instances are created in
        deaduction.dui.__main__.py. See self.__doc__.

        :param exercise: The instance of the Exercise class representing
            the exercise to be solved by the user.
        :param servint: The instance of the ServerInterface class in charge of
            communicating with vim.
        """

        super().__init__()
        self.setWindowTitle(f'{exercise.pretty_name} — d∃∀duction')

        # ─────────────────── Attributes ─────────────────── #

        self.target_selected_by_default = cvars.get(
            'functionality.target_selected_by_default', False)
        self.exercise = exercise
        self.current_goal = None
        self.current_selection = []
        self._target_selected = False
        self.ecw = ExerciseCentralWidget(exercise)
        self.lean_editor = LeanEditor()
        self.servint = servint
        self.toolbar = ExerciseToolBar()
        self.journal = Journal()
        self.proof_step = ProofStep()
        self.displayed_proof_step = None
        self.exercise_solved = False
        self.test_mode = False
        self.double_clicked_item = None

        # ─────────────────────── UI ─────────────────────── #

        self.setCentralWidget(self.ecw)
        self.addToolBar(self.toolbar)
        self.toolbar.redo_action.setEnabled(False)  # No history at beginning
        self.toolbar.undo_action.setEnabled(False)  # same
        self.toolbar.rewind.setEnabled(False)  # same

        # Status Bar
        self.statusBar = ExerciseStatusBar(self)
        self.setStatusBar(self.statusBar)

        # ──────────────── Signals and slots ─────────────── #

        # Actions area
        for action_button in self.ecw.actions_buttons:
            action_button.action_triggered.connect(self.__action_triggered)
        self.ecw.statements_tree.itemClicked.connect(
            self.__statement_triggered)

        # UI
        self.toolbar.toggle_lean_editor_action.triggered.connect(
            self.lean_editor.toggle)

        # Server communication
        self.servint.proof_state_change.connect(self.update_proof_state)
        self.servint.lean_file_changed.connect(self.__update_lean_editor)
        self.servint.proof_no_goals.connect(self.fireworks)
        self.servint.effective_code_received.connect(
            self.servint.history_replace)
        self.servint.effective_code_received.connect(self.store_effective_code)
        self.toolbar.change_exercise_action.triggered.connect(
            self.change_exercise)
        self.servint.nursery.start_soon(self.server_task)  # Start server task