interview_kernel.py 7.25 KB
Newer Older
1
2
3
4
5
#!/usr/bin/env python3

# http://cmd2.readthedocs.io
#import cmd2 as cmd
from ipykernel.kernelbase import Kernel
6

7
8
9
# http://mattoc.com/python-yes-no-prompt-cli.html
# https://github.com/phfaist/pylatexenc for directly converting Latex commands to unicode
from pylatexenc.latex2text import LatexNodes2Text
10
import getpass
11
from pde_state_machine import *
12
13
14
15
16
17



# This "main class" is two things: a REPL loop, by subclassing the cmd2 Cmd class
# and a state machine as given by the pytransitions package
class Interview(Kernel):
18
    implementation = 'Interview'
19
20
21
22
23
24
25
26
    implementation_version = '1.0'
    language = 'no-op'
    language_version = '0.1'
    language_info = {
        'name': 'Any text',
        'mimetype': 'text/plain',
        'file_extension': '.txt',
    }
27
    banner = "Interview kernel\n\n" \
28
             "Hello, " + getpass.getuser() + "! I am " + "TheInterview" + ", your partial differential equations and simulations expert. " \
29
30
31
                                                                           "Let's set up a simulation together.\n" \
             "Please enter anything to start the interview."
    #                                                                       "How many dimensions does your model have?" #TODO this never shows in the notebook
32
33

    def __init__(self, *args, **kwargs):
34
35

        self.state_machine = PDE_States(self.poutput, self.update_prompt, self.please_prompt)
36

37
        # call superclass constructor
38
39
40
41
        super(Interview, self).__init__(*args, **kwargs)

        self.update_prompt()
        self.poutstring = ""# to collect string output to send
42
        self.outstream_name = 'stdout'
43

44
    def poutput(self, text, outstream_name='stdout'):
45
        """Accumulate the output here"""
46
        self.poutstring += str(text) + "\n"
47
        self.outstream_name = outstream_name
48
49
50
51
52
53
54

    ############# input processing if not explain or undo
    def do_execute(self, code, silent, store_history=True, user_expressions=None,
                   allow_stdin=False):
        """This is where the user input enters our code"""
        arg = LatexNodes2Text().latex_to_text(code)

55
56
57
        if not self.keyword_handling(arg):
            if not self.prompt_input_handling(arg):
                self.state_input_handling(arg)
58
59

        if not silent:
60
            stream_content = {'name': self.outstream_name, 'text': self.poutstring}
61
62
63
            self.send_response(self.iopub_socket, 'stream', stream_content)

        self.poutstring = ""
64
        self.outstream_name = 'stdout'
65
66
67
68
69
70

        return {'status': 'ok',
                # The base class increments the execution count
                'execution_count': self.execution_count,
                'payload': [],
                'user_expressions': {},
71
                }
72
73
74
75
76

    def state_input_handling(self, arg):
        """The standard input handling, depending on which state we are in"""
        # pythonic switch-case, cf. https://bytebaker.com/2008/11/03/switch-case-statement-in-python/
        try:
77
            self.state_machine.stateDependentInputHandling[self.state_machine.state](arg)
78
        except Exception as error:
79
            #self.state_machine.exaout.create_output(self.simdata)
80
81
            raise

82
    def please_prompt(self, query, if_yes, if_no=None, pass_other=False):
83
84
85
86
        self.poutput(str(query) + " [y/n]? ")
        self.state_machine.prompted = True
        self.state_machine.if_yes = if_yes
        self.state_machine.if_no = if_no
87
        self.state_machine.pass_other = pass_other
88
89
90
91

    def prompt_input_handling(self, arg):
        """ If we asked for a yes-no answer, execute what was specified in please_prompt.
        return true if the input was handled here, and false if not."""
92
        if self.state_machine.prompted:
93
94
95
96
            if arg == "":
                ret = True
            else:
                try:
97
                    ret = strtobool(str(arg).strip().lower())
98
                except ValueError:
99
100
                    if self.state_machine.pass_other:
                        return False
101
                    # or use as input to callback an input processing fcn..?
102
                    self.poutput("Please answer with y/n")
103
                    return True
104
            self.state_machine.prompted = False
105
            if ret:
106
107
                if self.state_machine.if_yes is not None:
                    self.state_machine.if_yes()
108
109
            elif self.state_machine.if_no is not None:
                self.state_machine.if_no()
110
111
112
            return True
        return False

113
114
115
116
117
118
119
120
121
122
123
    def keyword_handling(self, arg):
        """ If keywords for special meta-functions are given,
        executes the corresponding functions and returns true if it did."""
        if arg.startswith("explain"):
            self.state_machine.explain(arg)
            return True
        if arg.startswith("recap"):
            self.state_machine.recap(arg)
            return True
        return False

124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
    # called when user types 'explain [expression]'
    def do_explain(self, expression):
        "Explain an expression or the theoretical background to what we are currently looking for"
        if expression:
            explanation = "hello, " + expression  # TODO query flexiformal content through mmt
        else:
            explanation = 'hello'
        self.poutput(explanation)

    def help_explain(self):
        self.poutput('\n'.join(['explain [expression]',
                                'explain the expression given or the theory currently used',
                                ]))

    # called when user types 'undo'
    def do_undo(self, expression):
        "Go back to the last question"
        self.trigger('last_state')

    def help_undo(self):
        self.poutput('\n'.join(['undo',
                                'Go back to the last question',
                                ]))

    def update_prompt(self):
149
        self.prompt = "(" + self.state_machine.state + ")" #TODO
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167

    # tab completion for empty lines
    def completenames(self, text, line, begidx, endidx):
        """Override of cmd2 method which completes command names both for command completion and help."""
        command = text
        if self.case_insensitive:
            command = text.lower()
        if not command:
            # define the "default" input for the different states we can be in
            self.stateDependentDefaultInput = {
                'dimensions': '1',
                'domain': ['Ω = [ 0 ; 1 ]'],
                'unknowns': ['u : Ω → ℝ'],
                'parameters': ['f :  ℝ → ℝ = [x: ℝ] x '],  # ['f : Ω → ℝ = [x:Ω] x ⋅ x'],
                'pdes': ['∆u = f(x_1)'],
                'bcs': ['u (0) = 0'],  # ,'u (1) = x_1**2'],
                'sim': ['FD'],
            }
168
            return self.stateDependentDefaultInput[self.state_machine.state]
169
170
171
172
173
174
175
176
177
178
179
180
181
        else:
            # Call super class method.  Need to do it this way for Python 2 and 3 compatibility
            cmd_completion = cmd.Cmd.completenames(self, command)

            # If we are completing the initial command name and get exactly 1 result and are at end of line, add a space
            if begidx == 0 and len(cmd_completion) == 1 and endidx == len(line):
                cmd_completion[0] += ' '
            return cmd_completion


if __name__ == '__main__':
    from ipykernel.kernelapp import IPKernelApp
    IPKernelApp.launch_instance(kernel_class=Interview)