Coverage for /home/mattis/projects/websites/dighl/edictor/src/edictor/cli.py: 82%
113 statements
« prev ^ index » next coverage.py v7.6.1, created at 2024-08-07 06:52 +0200
« prev ^ index » next coverage.py v7.6.1, created at 2024-08-07 06:52 +0200
1"""
2Commandline Interface of EDICTOR
3"""
5import webbrowser
6from http.server import HTTPServer
7import argparse
8from pathlib import Path
9import codecs
10import json
11import threading
13from edictor.util import DATA
15try:
16 import lingpy
17except ImportError:
18 lingpy = False
21class CommandMeta(type):
22 """
23 A metaclass which keeps track of subclasses, if they have all-lowercase names.
24 """
26 __instances = []
28 def __init__(self, name, bases, dct): # noqa
29 super(CommandMeta, self).__init__(name, bases, dct)
30 if name == name.lower():
31 self.__instances.append(self)
33 def __iter__(self):
34 return iter(self.__instances)
37class Command(metaclass=CommandMeta):
38 """Base class for subcommands of the EDICTOR command line interface."""
40 help = None
42 @classmethod
43 def subparser(cls, parser):
44 """Hook to define subcommand arguments."""
45 return
47 def __call__(self, args):
48 """Hook to run the subcommand."""
49 raise NotImplementedError
52def add_option(parser, name_, default_, help_, short_opt=None, **kw):
53 names = ["--" + name_]
54 if short_opt:
55 names.append("-" + short_opt)
57 if isinstance(default_, bool):
58 kw["action"] = "store_false" if default_ else "store_true"
59 elif isinstance(default_, int):
60 kw["type"] = int
61 elif isinstance(default_, float):
62 kw["type"] = float
63 kw["default"] = default_
64 kw["help"] = help_
65 parser.add_argument(*names, **kw)
68def _cmd_by_name(name):
69 for cmd in Command:
70 if cmd.__name__ == name:
71 return cmd()
74class server(Command):
75 """
76 Run EDICTOR 3 in a local server and access the application through your webbrowser.
77 """
79 @classmethod
80 def subparser(cls, p):
81 """
82 EDICTOR 3 in local server.
83 """
84 add_option(
85 p,
86 "port",
87 9999,
88 "Port where the local server will serve the application.",
89 short_opt="p",
90 )
91 add_option(
92 p,
93 "browser",
94 "firefox",
95 "Select the webbrowser to open the application.",
96 short_opt="b"
97 )
98 add_option(
99 p,
100 "config",
101 "config.json",
102 "Name of the configuration file to be used.",
103 short_opt="c"
104 )
105 add_option(
106 p,
107 "no-window",
108 False,
109 "Do not open a window of the application.",
110 )
112 def __call__(self, args):
113 """
114 EDICTOR 3 Server Application.
115 """
116 DATA["config"] = args.config
117 from edictor.server import Handler
118 httpd = HTTPServer(("", args.port), Handler)
119 print("Serving EDICTOR 3 at port {0}...".format(args.port))
120 url = "http://localhost:" + str(args.port) + "/"
121 if not args.no_window: # pragma: no cover
122 try:
123 webbrowser.get(args.browser).open_new_tab(url)
124 except: # noqa
125 try:
126 webbrowser.open(url)
127 except: # noqa
128 print("Could not open webbrowser, please open locally "
129 "at http://localhost:" + str(args.port) + "/")
130 httpd.serve_forever()
134class fetch(Command):
135 """
136 Download a wordlist from an EDICTOR application.
137 """
138 @classmethod
139 def subparser(cls, p):
140 add_option(
141 p,
142 "dataset",
143 None,
144 "Name of the remote dataset you want to access.",
145 short_opt="d"
146 )
147 add_option(
148 p,
149 "name",
150 "dummy.tsv",
151 "Name of the file where you want to store the data.",
152 short_opt="n"
153 )
155 def __call__(self, args):
156 """
157 Download data.
158 """
159 from edictor.wordlist import fetch_wordlist
160 data = fetch_wordlist(
161 args.dataset
162 )
163 with codecs.open(args.name, "w", "utf-8") as f:
164 f.write(data)
167class wordlist(Command):
168 """
169 Convert a dataset to EDICTOR's SQLITE and TSV formats (requires LingPy).
170 """
172 @classmethod
173 def subparser(cls, p):
174 add_option(
175 p,
176 "dataset",
177 Path("cldf", "cldf-metadata.json"),
178 "Path to the CLDF metadata file.",
179 short_opt="d",
180 )
181 add_option(
182 p,
183 "preprocessing",
184 None,
185 "path to the module to preprocess the data",
186 short_opt="p",
187 )
188 add_option(
189 p,
190 "namespace",
191 '{"language_id": "doculect", "concept_name": "concept",'
192 '"value": "value", "form": "form", "segments": "tokens",'
193 '"comment": "note"}',
194 "namespace and columns you want to extract",
195 )
196 add_option(
197 p, "name", "dummy", "name of the dataset you want to create", short_opt="n"
198 )
199 add_option(p, "addon", None, "expand the namespace", short_opt="a")
200 add_option(p, "sqlite", False, "convert to SQLITE format")
201 add_option(
202 p, "custom", None, "custom field where arguments can be passed in JSON form"
203 )
205 def __call__(self, args):
207 from edictor.wordlist import get_wordlist
208 import importlib.util
210 namespace = json.loads(args.namespace)
211 if args.addon:
212 for row in args.addon.split(","):
213 s, t = row.split(":")
214 namespace[s] = t
216 columns = [x for x in list(namespace)]
217 if args.preprocessing:
218 spec = importlib.util.spec_from_file_location("prep", args.preprocessing)
219 prep = importlib.util.module_from_spec(spec)
220 spec.loader.exec_module(prep)
221 preprocessing = prep.run
222 else:
223 preprocessing = None
224 if args.custom:
225 custom_args = json.loads(args.custom)
226 else:
227 custom_args = None
228 get_wordlist(
229 args.dataset,
230 args.name,
231 columns=columns,
232 namespace=namespace,
233 preprocessing=preprocessing,
234 lexibase=args.sqlite,
235 custom_args=custom_args,
236 )
239def get_parser():
240 # basic parser for lingpy
241 parser = argparse.ArgumentParser(
242 description=main.__doc__,
243 formatter_class=argparse.ArgumentDefaultsHelpFormatter
244 )
246 subparsers = parser.add_subparsers(dest="subcommand")
247 for cmd in Command:
248 if not lingpy and cmd.__name__ == "wordlist":
249 continue
250 subparser = subparsers.add_parser(
251 cmd.__name__,
252 help=(cmd.__doc__ or "").strip().split("\n")[0],
253 formatter_class=argparse.ArgumentDefaultsHelpFormatter,
254 )
255 cmd.subparser(subparser)
256 cmd.help = subparser.format_help()
258 return parser
261def main(*args):
262 """
263 Computer-assisted language comparison with EDICTOR 3.
264 """
265 parser = get_parser()
266 args = parser.parse_args(args or None)
267 if args.subcommand:
268 return _cmd_by_name(args.subcommand)(args)
269 else:
270 parser.print_help()