interview_kernel.py 6.84 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
10
11
12
# http://mattoc.com/python-yes-no-prompt-cli.html
from distutils.util import strtobool
# https://github.com/phfaist/pylatexenc for directly converting Latex commands to unicode
from pylatexenc.latex2text import LatexNodes2Text
import pyparsing as pp

13
from pde_state_machine import *
14
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
class Interview(Kernel):
20
    implementation = 'Interview'
21
22
23
24
25
26
27
28
    implementation_version = '1.0'
    language = 'no-op'
    language_version = '0.1'
    language_info = {
        'name': 'Any text',
        'mimetype': 'text/plain',
        'file_extension': '.txt',
    }
29
30
31
32
33
    banner = "Interview kernel\n\n" \
             "Hello, " + "user" + "! I am " + "TheInterview" + ", your partial differential equations and simulations expert. " \
                                                                           "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
34
35

    def __init__(self, *args, **kwargs):
36
37

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

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

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

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

    ############# 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)

57
58
        if not self.prompt_input_handling(arg):
            self.state_input_handling(arg)
59
60

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

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

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

    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:
78
            self.state_machine.stateDependentInputHandling[self.state_machine.state](arg)
79
        except Exception as error:
80
            #self.state_machine.exaout.create_output(self.simdata)
81
82
            raise

83
    def please_prompt(self, query, if_yes, if_no=None, pass_other=False):
84
85
86
87
        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
88
        self.state_machine.pass_other = pass_other
89
90
91
92

    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."""
93
        if self.state_machine.prompted:
94
95
96
97
            if arg == "":
                ret = True
            else:
                try:
98
                    ret = strtobool(str(arg).strip().lower())
99
                except ValueError:
100
101
                    if self.state_machine.pass_other:
                        return False
102
                    # or use as input to callback an input processing fcn..?
103
                    self.poutput("Please answer with y/n")
104
                    return True
105
            self.state_machine.prompted = False
106
            if ret:
107
108
                if self.state_machine.if_yes is not None:
                    self.state_machine.if_yes()
109
110
            elif self.state_machine.if_no is not None:
                self.state_machine.if_no()
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
            return True
        return False

    # 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):
139
        self.prompt = "(" + self.state_machine.state + ")" #TODO
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157

    # 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'],
            }
158
            return self.stateDependentDefaultInput[self.state_machine.state]
159
160
161
162
163
164
165
166
167
168
169
170
171
        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)