diff options
author | Federico Igne <git@federicoigne.com> | 2021-10-03 22:09:32 +0100 |
---|---|---|
committer | Federico Igne <git@federicoigne.com> | 2021-10-03 22:09:32 +0100 |
commit | 830b8d9f8902a4a3fc536f40843352e9ff905020 (patch) | |
tree | 0ab95df791f1ce82c5b9f96a36ff7ec39dbe5f41 | |
parent | c45035e9e50d92e85b851724466a950fc1788a02 (diff) | |
download | RSAComb-830b8d9f8902a4a3fc536f40843352e9ff905020.tar.gz RSAComb-830b8d9f8902a4a3fc536f40843352e9ff905020.zip |
Rework CLI configs
-rw-r--r-- | src/main/scala/uk/ac/ox/cs/rsacomb/Main.scala | 134 | ||||
-rw-r--r-- | src/main/scala/uk/ac/ox/cs/rsacomb/RSAConfig.scala | 154 |
2 files changed, 164 insertions, 124 deletions
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/Main.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/Main.scala index 24bda1f..121c65f 100644 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/Main.scala +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/Main.scala | |||
@@ -19,7 +19,6 @@ package uk.ac.ox.cs.rsacomb | |||
19 | import java.io.{File, PrintWriter} | 19 | import java.io.{File, PrintWriter} |
20 | import java.nio.file.{Path, Paths, InvalidPathException} | 20 | import java.nio.file.{Path, Paths, InvalidPathException} |
21 | import java.util.HashMap | 21 | import java.util.HashMap |
22 | import scala.collection.mutable.Map | ||
23 | import scala.collection.JavaConverters._ | 22 | import scala.collection.JavaConverters._ |
24 | import tech.oxfordsemantic.jrdfox.client.UpdateType | 23 | import tech.oxfordsemantic.jrdfox.client.UpdateType |
25 | import tech.oxfordsemantic.jrdfox.logic.expression.{IRI, Term} | 24 | import tech.oxfordsemantic.jrdfox.logic.expression.{IRI, Term} |
@@ -32,129 +31,15 @@ import uk.ac.ox.cs.rsacomb.ontology.Ontology | |||
32 | import uk.ac.ox.cs.rsacomb.converter.Normalizer | 31 | import uk.ac.ox.cs.rsacomb.converter.Normalizer |
33 | import uk.ac.ox.cs.rsacomb.approximation.{Upperbound, Lowerbound} | 32 | import uk.ac.ox.cs.rsacomb.approximation.{Upperbound, Lowerbound} |
34 | 33 | ||
35 | case class RSAOption[+T](opt: T) { | ||
36 | def get[T]: T = opt.asInstanceOf[T] | ||
37 | } | ||
38 | |||
39 | object RSAConfig { | ||
40 | type Config = Map[Symbol, RSAOption[Any]] | ||
41 | |||
42 | private implicit def toRSAOption[T](opt: T) = RSAOption[T](opt) | ||
43 | |||
44 | /** Help message */ | ||
45 | private val help: String = """ | ||
46 | rsacomb - combined approach for CQ answering for RSA ontologies. | ||
47 | |||
48 | USAGE | ||
49 | rsacomb [OPTIONS] <ontology> [<data> ...] | ||
50 | |||
51 | -h | -? | --help | ||
52 | print this help message | ||
53 | |||
54 | -l | --logger <level> | ||
55 | specify the logger verbosity. Values are: quiet, normal (default), | ||
56 | debug, verbose. | ||
57 | |||
58 | -o | --output <file> | ||
59 | path to the output file for the answers to the query (in JSON | ||
60 | format) | ||
61 | |||
62 | -q | --queries <file> | ||
63 | path to a file containing a single SPARQL query. If no query | ||
64 | is provided, only the approximation to RSA will be performed. | ||
65 | |||
66 | <ontology> | ||
67 | file containing the ontology | ||
68 | |||
69 | <data> | ||
70 | one or more data files | ||
71 | |||
72 | """ | ||
73 | |||
74 | /** Default config values */ | ||
75 | private val default: Config = Map.empty | ||
76 | |||
77 | /** Utility to exit the program with a custom message on stderr. | ||
78 | * | ||
79 | * The program will exit with error code 1 after printing the help | ||
80 | * message. | ||
81 | * | ||
82 | * @param msg message printed to stderr. | ||
83 | */ | ||
84 | private def exit(msg: String): Nothing = { | ||
85 | System.err.println(msg) | ||
86 | System.err.println() | ||
87 | System.err.println(help) | ||
88 | sys.exit(1) | ||
89 | } | ||
90 | |||
91 | private def getPath(str: String): os.Path = | ||
92 | try { | ||
93 | os.Path(str, base = os.pwd) | ||
94 | } catch { | ||
95 | case e: IllegalArgumentException => | ||
96 | exit(s"'$str' is not a well formed path.") | ||
97 | } | ||
98 | |||
99 | /** Parse arguments with default options | ||
100 | * | ||
101 | * @param args arguments list | ||
102 | * @return map of config options | ||
103 | */ | ||
104 | def parse(args: List[String]): Config = parse(args, default) | ||
105 | |||
106 | /** Parse arguments | ||
107 | * | ||
108 | * @param args arguments list | ||
109 | * @param config default configuration | ||
110 | * @return map of config options | ||
111 | */ | ||
112 | def parse(args: List[String], config: Config): Config = { | ||
113 | args match { | ||
114 | case flag @ ("-h" | "-?" | "--help") :: _ => { | ||
115 | println(help) | ||
116 | sys.exit(0) | ||
117 | } | ||
118 | case flag @ ("-l" | "--logger") :: _level :: tail => { | ||
119 | val level = _level match { | ||
120 | case "quiet" => Logger.QUIET | ||
121 | case "debug" => Logger.DEBUG | ||
122 | case "verbose" => Logger.VERBOSE | ||
123 | case _ => Logger.NORMAL | ||
124 | } | ||
125 | parse(tail, config += ('logger -> level)) | ||
126 | } | ||
127 | case flag @ ("-o" | "--output") :: output :: tail => | ||
128 | parse(tail, config += ('output -> getPath(output))) | ||
129 | case flag @ ("-q" | "--queries") :: _query :: tail => { | ||
130 | val query = getPath(_query) | ||
131 | if (!os.isFile(query)) | ||
132 | exit(s"'${_query}' is not a valid filename.") | ||
133 | parse(tail, config += ('queries -> query)) | ||
134 | } | ||
135 | case _ontology :: _data => { | ||
136 | val ontology = getPath(_ontology) | ||
137 | val data = _data map getPath | ||
138 | (ontology :: data) foreach { (path) => | ||
139 | if (!os.isFile(path)) | ||
140 | exit(s"'$path' is not a valid filename.") | ||
141 | } | ||
142 | finalise(config += ('ontology -> ontology) += ('data -> data)) | ||
143 | } | ||
144 | case a => exit(s"Invalid sequence of arguments '${a.mkString(" ")}'.") | ||
145 | } | ||
146 | } | ||
147 | |||
148 | /** Perform final checks on parsed options */ | ||
149 | private def finalise(config: Config): Config = config | ||
150 | } | ||
151 | |||
152 | /** Main entry point to the program */ | 34 | /** Main entry point to the program */ |
153 | object RSAComb extends App { | 35 | object RSAComb extends App { |
154 | 36 | ||
155 | /* Command-line options */ | 37 | /* Command-line options */ |
156 | val config = RSAConfig.parse(args.toList) | 38 | val config = RSAConfig.parse(args.toList) |
157 | Logger.level = config('logger).get[Logger.Level] | 39 | |
40 | /* Set logger level */ | ||
41 | if (config.contains('logger)) | ||
42 | Logger.level = config('logger).get[Logger.Level] | ||
158 | 43 | ||
159 | /* Load original ontology and normalize it */ | 44 | /* Load original ontology and normalize it */ |
160 | val ontology = Ontology( | 45 | val ontology = Ontology( |
@@ -177,11 +62,12 @@ object RSAComb extends App { | |||
177 | val answers = rsa ask queries | 62 | val answers = rsa ask queries |
178 | 63 | ||
179 | /* Write answers to output file */ | 64 | /* Write answers to output file */ |
180 | os.write( | 65 | if (config.contains('answers)) |
181 | config('output).get[os.Path], | 66 | os.write( |
182 | ujson.write(ujson.Arr(answers.map(_.toJSON)), indent = 4), | 67 | config('answers).get[os.Path], |
183 | createFolders = true | 68 | ujson.write(ujson.Arr(answers.map(_.toJSON)), indent = 4), |
184 | ) | 69 | createFolders = true |
70 | ) | ||
185 | 71 | ||
186 | // Logger.print(s"$answers", Logger.VERBOSE) | 72 | // Logger.print(s"$answers", Logger.VERBOSE) |
187 | // Logger print s"Number of answers: ${answers.length} (${answers.lengthWithMultiplicity})" | 73 | // Logger print s"Number of answers: ${answers.length} (${answers.lengthWithMultiplicity})" |
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/RSAConfig.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/RSAConfig.scala new file mode 100644 index 0000000..4d96850 --- /dev/null +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/RSAConfig.scala | |||
@@ -0,0 +1,154 @@ | |||
1 | /* | ||
2 | * Copyright 2020, 2021 KRR Oxford | ||
3 | * | ||
4 | * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | * you may not use this file except in compliance with the License. | ||
6 | * You may obtain a copy of the License at | ||
7 | * | ||
8 | * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | * | ||
10 | * Unless required by applicable law or agreed to in writing, software | ||
11 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | * See the License for the specific language governing permissions and | ||
14 | * limitations under the License. | ||
15 | */ | ||
16 | |||
17 | package uk.ac.ox.cs.rsacomb | ||
18 | |||
19 | import scala.collection.mutable.Map | ||
20 | import util.Logger | ||
21 | |||
22 | case class RSAOption[+T](opt: T) { | ||
23 | def get[T]: T = opt.asInstanceOf[T] | ||
24 | } | ||
25 | |||
26 | object RSAConfig { | ||
27 | type Config = Map[Symbol, RSAOption[Any]] | ||
28 | |||
29 | private implicit def toRSAOption[T](opt: T) = RSAOption[T](opt) | ||
30 | |||
31 | /** Help message */ | ||
32 | private val help: String = """ | ||
33 | rsacomb - combined approach for CQ answering for RSA ontologies. | ||
34 | |||
35 | USAGE | ||
36 | rsacomb [OPTIONS] <ontology> [<data> ...] | ||
37 | |||
38 | -h | -? | --help | ||
39 | print this help message | ||
40 | |||
41 | -l | --logger <level> | ||
42 | specify the logger verbosity. Values are: quiet, normal (default), | ||
43 | debug, verbose. | ||
44 | |||
45 | -a | --answers <file> | ||
46 | path to the output file for the answers to the query (in JSON | ||
47 | format) | ||
48 | |||
49 | -q | --queries <file> | ||
50 | path to a file containing a single SPARQL query. If no query | ||
51 | is provided, only the approximation to RSA will be performed. | ||
52 | |||
53 | -o | --ontology <file> | ||
54 | ontology file in OWL format. | ||
55 | |||
56 | -d | --data <file> | ||
57 | data files to be used alongside the ontology file. If a | ||
58 | directory is provided, all files in the directory (recursively) | ||
59 | will be considered. | ||
60 | |||
61 | """ | ||
62 | |||
63 | /** Default config values */ | ||
64 | private val default: Config = Map.empty | ||
65 | |||
66 | /** Utility to exit the program with a custom message on stderr. | ||
67 | * | ||
68 | * The program will exit with error code 1 after printing the help | ||
69 | * message. | ||
70 | * | ||
71 | * @param msg message printed to stderr. | ||
72 | */ | ||
73 | private def exit(msg: String): Nothing = { | ||
74 | System.err.println(msg) | ||
75 | System.err.println() | ||
76 | System.err.println(help) | ||
77 | sys.exit(1) | ||
78 | } | ||
79 | |||
80 | private def getPath(str: String): os.Path = | ||
81 | try { | ||
82 | os.Path(str, base = os.pwd) | ||
83 | } catch { | ||
84 | case e: IllegalArgumentException => | ||
85 | exit(s"'$str' is not a well formed path.") | ||
86 | } | ||
87 | |||
88 | /** Parse arguments with default options | ||
89 | * | ||
90 | * @param args arguments list | ||
91 | * @return map of config options | ||
92 | */ | ||
93 | def parse(args: List[String]): Config = parse(args, default) | ||
94 | |||
95 | /** Parse arguments | ||
96 | * | ||
97 | * @param args arguments list | ||
98 | * @param config default configuration | ||
99 | * @return map of config options | ||
100 | */ | ||
101 | def parse(args: List[String], config: Config): Config = { | ||
102 | args match { | ||
103 | case Nil => finalise(config) | ||
104 | case flag @ ("-h" | "-?" | "--help") :: _ => { | ||
105 | println(help) | ||
106 | sys.exit(0) | ||
107 | } | ||
108 | case flag @ ("-l" | "--logger") :: _level :: tail => { | ||
109 | val level = _level match { | ||
110 | case "quiet" => Logger.QUIET | ||
111 | case "debug" => Logger.DEBUG | ||
112 | case "verbose" => Logger.VERBOSE | ||
113 | case _ => Logger.NORMAL | ||
114 | } | ||
115 | parse(tail, config += ('logger -> level)) | ||
116 | } | ||
117 | case flag @ ("-a" | "--answers") :: answers :: tail => | ||
118 | parse(tail, config += ('answers -> getPath(answers))) | ||
119 | case flag @ ("-q" | "--queries") :: _query :: tail => { | ||
120 | val query = getPath(_query) | ||
121 | if (!os.isFile(query)) | ||
122 | exit(s"'${_query}' is not a valid filename.") | ||
123 | parse(tail, config += ('queries -> query)) | ||
124 | } | ||
125 | case flag @ ("-o" | "--ontology") :: _ontology :: tail => { | ||
126 | val ontology = getPath(_ontology) | ||
127 | if (!os.isFile(ontology)) | ||
128 | exit(s"'${_ontology}' is not a valid filename.") | ||
129 | parse(tail, config += ('ontology -> ontology)) | ||
130 | } | ||
131 | case flag @ ("-d" | "--data") :: _data :: tail => { | ||
132 | val data = getPath(_data) | ||
133 | val files = | ||
134 | if (os.isFile(data)) | ||
135 | Seq(data) | ||
136 | else if (os.isDir(data)) | ||
137 | os.walk(data).filter(os.isFile) | ||
138 | else | ||
139 | exit(s"'${_data}' is not a valid path.") | ||
140 | parse(tail, config += ('data -> files)) | ||
141 | } | ||
142 | case a => exit(s"Invalid sequence of arguments '${a.mkString(" ")}'.") | ||
143 | } | ||
144 | } | ||
145 | |||
146 | /** Perform final checks on parsed options */ | ||
147 | private def finalise(config: Config): Config = { | ||
148 | if (!config.contains('ontology)) | ||
149 | exit("The following flag is mandatory: '-o' or '--ontology'.") | ||
150 | if (!config.contains('data)) | ||
151 | config += ('data -> List.empty[os.Path]) | ||
152 | config | ||
153 | } | ||
154 | } | ||