1"""
2    Flask server for hosting Lua Interpreter tool.
3"""
4
5import ctypes
6import json
7import os
8import flask
9
10app = flask.Flask(__name__)
11lua_lib = ctypes.cdll.LoadLibrary('./liblua_engine.so')
12lua_lib.NewLuaEngine.argtypes = None
13lua_lib.NewLuaEngine.restype = ctypes.c_void_p
14lua_lib.ExecuteScript.argtypes = [
15    ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p, ctypes.c_char_p,
16    ctypes.c_char_p
17]
18lua_lib.ExecuteScript.restype = ctypes.c_void_p
19engine = lua_lib.NewLuaEngine()
20
21
22class LuaOutput(ctypes.Structure):
23  """Python wrapper class around LuaOutput struct as defined in lua_engine.h.
24  """
25  _fields_ = [('output', ctypes.POINTER(ctypes.c_char_p)),
26              ('size', ctypes.c_int), ('saved_state', ctypes.c_char_p)]
27
28
29@app.route('/')
30def index():
31  """Renders the main page of the tool.
32
33  Returns:
34    A JSON response with a string of the rendered index.html page.
35  """
36  return flask.render_template('index.html')
37
38
39@app.route('/get_published_data_file_names_and_content', methods=['POST'])
40def get_published_data_file_names_and_content():
41  """Returns the list of all the JSON file names under the data
42  directory without their file extensions. Also returns the
43  JSON of each published data file under the data directory as a string.
44
45  Returns:
46    A JSON response with file names under the key "file_names" and each
47    of the published data strings under the key of their file name.
48  """
49  file_path = os.path.join(os.path.dirname(__file__), "data")
50  all_json_files = filter(lambda file: file.endswith('.json'),
51                          os.listdir(file_path))
52  file_names = list(
53      # lamda function leverages splitext, which produces a tuple of
54      # the file name and the extension.
55      map(lambda file: os.path.splitext(file)[0], all_json_files))
56
57  response = {"file_names": file_names}
58
59  for file_name in file_names:
60    json_file_path = os.path.join(file_path, file_name + '.json')
61    json_file = open(json_file_path)
62    response[file_name] = json.dumps(json.load(json_file), indent=2)
63    json_file.close()
64
65  return response
66
67
68@app.route('/execute_script', methods=['POST'])
69def execute_script():
70  """Executes the Lua script from the request
71  re-rendering the home page with the output.
72
73  Returns:
74    A JSON response containing the string of the rendered index.html
75    page with output, script, published data, and the saved state specified.
76  """
77  script = flask.request.form['script']
78  function_name = flask.request.form['function-name']
79  published_data = flask.request.form['published-data']
80  saved_state = flask.request.form['saved-state']
81  # ctypes requires that strings are encoded to bytes
82  lua_output = LuaOutput.from_address(
83      lua_lib.ExecuteScript(engine, script.encode(), function_name.encode(),
84                            published_data.encode(), saved_state.encode()))
85
86  # ctypes encodes strings as bytes so they must be decoded back to strings.
87  decoded_output = []
88  for i in range(lua_output.size):
89    decoded_output.append(prettify_json(lua_output.output[i].decode()))
90
91  new_saved_state = prettify_json(
92      ctypes.string_at(lua_output.saved_state).decode())
93  saved_state = new_saved_state if new_saved_state else saved_state
94
95  lua_lib.FreeLuaOutput(ctypes.byref(lua_output))
96
97  return flask.render_template('index.html',
98                               script=script,
99                               output=decoded_output,
100                               function_name=function_name,
101                               published_data=published_data,
102                               saved_state=saved_state)
103
104
105def prettify_json(string):
106  """Prettifies the string if it represents a JSON with an indent of 2.
107
108  Args:
109    string (str): String to prettify
110
111  Returns:
112    A string of the formatted JSON. If the string does not represent a
113    JSON, the string is returned back with no changes.
114  """
115  try:
116    # If the string cannot be loaded into a JSON,
117    # json.loads throws a JSONDecodeError exception.
118    json_object = json.loads(string)
119    json_formatted_str = json.dumps(json_object, indent=2)
120    return json_formatted_str
121  except json.JSONDecodeError as e:
122    return string
123
124
125if __name__ == '__main__':
126  app.run()