interview_kernel.py 7.8 KB
Newer Older
1
2
#!/usr/bin/env python3

Theresa Pollinger's avatar
Theresa Pollinger committed
3
4
import sys

5
from ipykernel.kernelbase import Kernel
6
7
8
from ipykernel.comm import CommManager

from metakernel import MetaKernel
9

10
11
12
# 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
13
import getpass
14
from pde_state_machine import *
15
16
17
18
19



# 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
20
class Interview(MetaKernel):
21
    implementation = 'Interview'
22
    implementation_version = '1.0'
Theresa Pollinger's avatar
Theresa Pollinger committed
23
    language = 'text'
24
25
    language_version = '0.1'
    language_info = {
Theresa Pollinger's avatar
Theresa Pollinger committed
26
        'name': 'text',
27
28
        'mimetype': 'text/plain',
        'file_extension': '.txt',
Theresa Pollinger's avatar
Theresa Pollinger committed
29
        'help_links': MetaKernel.help_links,
30
    }
31
    banner = "Interview kernel\n\n" \
32
             "Hello, " + getpass.getuser() + "! I am " + "TheInterview" + ", your partial differential equations and simulations expert. " \
33
34
                                                                           "Let's set up a simulation together.\n" \
             "Please enter anything to start the interview."
Theresa Pollinger's avatar
Theresa Pollinger committed
35
36
37
38
39
40
41
42

    #kernel_json = {
    #    "argv": [
    #        sys.executable, "-m", "interview_kernel", "-f", "{connection_file}"],
    #    "display_name": "Interview Kernel",
    #    "language": "text",
    #    "name": "interview_kernel"
    #}
43

44
    def __init__(self, **kwargs):
45
46

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

48
        # call superclass constructor
49
50
51
52
53
54
55
        super(Interview, self).__init__(**kwargs)
        # cf.  https://github.com/ipython/ipykernel/blob/7d91110f8f471dbdd3ea65099abcb96a99179557/ipykernel/ipkernel.py
        # self.comm_manager = CommManager(parent=self, kernel=self)

        from IPython import get_ipython
        from metakernel import register_ipython_magics
        # register_ipython_magics()
56
57
58

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

61
    def poutput(self, text, outstream_name='stdout'):
62
        """Accumulate the output here"""
63
        self.poutstring += str(text) + "\n"
64
        self.outstream_name = outstream_name
65
66

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

73
74
75
        if not self.keyword_handling(arg):
            if not self.prompt_input_handling(arg):
                self.state_input_handling(arg)
76
77

        if not silent:
78
            stream_content = {'name': self.outstream_name, 'text': self.poutstring}
79
80
81
            self.send_response(self.iopub_socket, 'stream', stream_content)

        self.poutstring = ""
82
        self.outstream_name = 'stdout'
83
84
85
86
87
88

        return {'status': 'ok',
                # The base class increments the execution count
                'execution_count': self.execution_count,
                'payload': [],
                'user_expressions': {},
89
                }
90
91
92
93
94

    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:
95
            self.state_machine.stateDependentInputHandling[self.state_machine.state](arg)
96
        except Exception as error:
97
            #self.state_machine.exaout.create_output(self.simdata)
98
99
            raise

100
    def please_prompt(self, query, if_yes, if_no=None, pass_other=False):
101
102
103
104
        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
105
        self.state_machine.pass_other = pass_other
106
107
108
109

    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."""
110
        if self.state_machine.prompted:
111
112
113
114
            if arg == "":
                ret = True
            else:
                try:
115
                    ret = strtobool(str(arg).strip().lower())
116
                except ValueError:
117
118
                    if self.state_machine.pass_other:
                        return False
119
                    # or use as input to callback an input processing fcn..?
120
                    self.poutput("Please answer with y/n")
121
                    return True
122
            self.state_machine.prompted = False
123
            if ret:
124
125
                if self.state_machine.if_yes is not None:
                    self.state_machine.if_yes()
126
127
            elif self.state_machine.if_no is not None:
                self.state_machine.if_no()
128
129
130
            return True
        return False

131
132
133
134
135
136
137
138
139
140
141
    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

142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
    # 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):
167
        self.prompt = "(" + self.state_machine.state + ")" #TODO
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185

    # 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'],
            }
186
            return self.stateDependentDefaultInput[self.state_machine.state]
187
188
189
190
191
192
193
194
195
196
197
        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__':
198
199
200
    # from ipykernel.kernelapp import IPKernelApp
    # IPKernelApp.launch_instance(kernel_class=Interview)
    Interview.run_as_main()