Coverage for /home/mattis/projects/websites/dighl/edictor/src/edictor/cli.py: 82%

1"""

2 Commandline Interface of EDICTOR

3"""

4

5 import webbrowser

6 from http.server import HTTPServer

7 import argparse

8 from pathlib import Path

9 import codecs

10 import json

11 import threading

12

13 from edictor.util import DATA

14

15 try:

16 import lingpy

17 except ImportError:

18 lingpy = False

19

20

21 class CommandMeta(type):

22 """

23 A metaclass which keeps track of subclasses, if they have all-lowercase names.

24 """

25

26 __instances = []

27

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)

32

33 def __iter__(self):

34 return iter(self.__instances)

35

36

37 class Command(metaclass=CommandMeta):

38 """Base class for subcommands of the EDICTOR command line interface."""

39

40 help = None

41

42 @classmethod

43 def subparser(cls, parser):

44 """Hook to define subcommand arguments."""

45 return

46

47 def __call__(self, args):

48 """Hook to run the subcommand."""

49 raise NotImplementedError

50

51

52 def add_option(parser, name_, default_, help_, short_opt=None, **kw):

53 names = ["--" + name_]

54 if short_opt:

55 names.append("-" + short_opt)

56

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)

66

67

68 def _cmd_by_name(name):

69 for cmd in Command:

70 if cmd.__name__ == name:

71 return cmd()

72

73

74 class server(Command):

75 """

76 Run EDICTOR 3 in a local server and access the application through your webbrowser.

77 """

78

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 )

111

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

131

132

133

134 class 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 )

154

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)

165

166

167 class wordlist(Command):

168 """

169 Convert a dataset to EDICTOR's SQLITE and TSV formats (requires LingPy).

170 """

171

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 )

204

205 def __call__(self, args):

206

207 from edictor.wordlist import get_wordlist

208 import importlib.util

209

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

215

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 )

237

238

239 def get_parser():

240 # basic parser for lingpy

241 parser = argparse.ArgumentParser(

242 description=main.__doc__,

243 formatter_class=argparse.ArgumentDefaultsHelpFormatter

244 )

245

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

257

258 return parser

259

260

261 def 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()