Basic support for multi-valued options
[alexxy/gromacs.git] / src / python / sip / options / pyoptionsholder.sip
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2014, by the GROMACS development team, led by
5  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6  * and including many others, as listed in the AUTHORS file in the
7  * top-level source directory and at http://www.gromacs.org.
8  *
9  * GROMACS is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public License
11  * as published by the Free Software Foundation; either version 2.1
12  * of the License, or (at your option) any later version.
13  *
14  * GROMACS is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with GROMACS; if not, see
21  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
23  *
24  * If you want to redistribute modifications to GROMACS, please
25  * consider that scientific software is very special. Version
26  * control is crucial - bugs must be traceable. We will be happy to
27  * consider code for inclusion in the official distribution, but
28  * derived work must not be called official GROMACS. Details are found
29  * in the README & COPYING files - if they are missing, get the
30  * official version at http://www.gromacs.org.
31  *
32  * To help us fund GROMACS development, we humbly ask that you cite
33  * the research papers on the package. Check out http://www.gromacs.org.
34  */
35
36 %ModuleHeaderCode
37 #include <exception>
38 #include "gromacs/options/basicoptions.h"
39 #include "gromacs/options/filenameoption.h"
40 #include "gromacs/selection/selectionoption.h"
41 #include <map>
42 #include <string>
43
44 class PyOptionsHolder {
45 public:
46     class DuplicateOption : public std::exception {
47     public:
48         virtual const char* what() const noexcept;
49     };
50     class StringList {
51     public:
52         StringList(const std::vector<std::string>*);
53         StringList(const std::string*, size_t);
54         const char* operator[] (size_t);
55     private:
56         const std::string *list;
57         size_t size;
58         const std::vector<std::string> *vector;
59     };
60     gmx::DoubleOption doubleOption(const char*, size_t = 1, double = 0);
61     gmx::IntegerOption integerOption(const char*, size_t = 1, int = 0);
62     gmx::StringOption stringOption(const char*, size_t = 1, const char* = "");
63     gmx::BooleanOption booleanOption(const char*, size_t = 1, bool = 0);
64     gmx::SelectionOption selectionOption(const char*, size_t = 1);
65     gmx::FileNameOption fileNameOption(const char*, size_t = 1, const char* = 0);
66     PyObject* get_value(const char*);
67     ~PyOptionsHolder();
68 private:
69     struct option_value {
70         void *value;
71         const char *type;
72         int *count;
73         bool vector;
74     };
75     std::map<std::string, option_value> storage;
76     template<typename T, typename U> option_value createStorage(gmx::OptionTemplate<T, U>&, const char*, int);
77     template<typename T> PyObject* buildValue(const char*, const option_value&, const sipTypeDef* = NULL, size_t = 0);
78     template<typename T> void deleteStorage(const option_value&);
79 };
80 %End
81
82 %ModuleCode
83 #include "gromacs/utility/gmxassert.h"
84
85 const char* PyOptionsHolder::DuplicateOption::what() const noexcept {
86     return "This option is already defined";
87 }
88
89 PyOptionsHolder::StringList::StringList(const std::vector<std::string> *vector) :
90     list(NULL), size(vector->size()), vector(vector)
91 {}
92
93 PyOptionsHolder::StringList::StringList(const std::string *list, size_t size) :
94     list(list), size(size), vector(NULL)
95 {}
96
97 const char* PyOptionsHolder::StringList::operator[](size_t i) {
98     if (i >= size)
99         return NULL;
100     return vector ? (*vector)[i].data() : list[i].data();
101 }
102
103 template<typename T, typename U> PyOptionsHolder::option_value PyOptionsHolder::createStorage(gmx::OptionTemplate<T, U> &option, const char *type, int count) {
104     if (count == 0) { // std::vector of values
105         auto *store = new std::vector<T>();
106         option.storeVector(store);
107         option.multiValue();
108
109         auto *count = new int;
110         option.storeCount(count);
111
112         return { store, type, count, true };
113     } else { // exactly `count` values
114         auto *store = new T[count];
115         option.store(store);
116         if (count > 1)
117             option.valueCount(count);
118
119         auto *count = new int;
120         option.storeCount(count);
121
122         return { store, type, count, false };
123     }
124 }
125
126 gmx::DoubleOption PyOptionsHolder::doubleOption(const char *name, size_t count, double def) {
127     if (storage.count(name))
128         throw DuplicateOption();
129
130     gmx::DoubleOption option(name);
131     storage[name] = createStorage(option, "d", count);
132
133     return option;
134 }
135
136 gmx::IntegerOption PyOptionsHolder::integerOption(const char *name, size_t count, int def) {
137     if (storage.count(name))
138         throw DuplicateOption();
139
140     gmx::IntegerOption option(name);
141     storage[name] = createStorage(option, "i", count);
142
143     return option;
144 }
145
146 gmx::StringOption PyOptionsHolder::stringOption(const char *name, size_t count, const char* def) {
147     if (storage.count(name))
148         throw DuplicateOption();
149
150     gmx::StringOption option(name);
151     storage[name] = createStorage(option, "A", count);
152     // FIXME: following does not work
153     //option.defaultValue(std::string(def));
154
155     return option;
156 }
157
158 gmx::BooleanOption PyOptionsHolder::booleanOption(const char *name, size_t count, bool def) {
159     if (storage.count(name))
160         throw DuplicateOption();
161
162     bool *store = new bool;
163     *store = def;
164     gmx::BooleanOption option(name);
165     option.store(store);
166     storage[name] = {store, "b"};
167
168     return option;
169 }
170
171 gmx::SelectionOption PyOptionsHolder::selectionOption(const char *name, size_t count) {
172     if (storage.count(name))
173         throw DuplicateOption();
174
175     gmx::SelectionOption option(name);
176     storage[name] = createStorage(option, "S", count);
177
178     return option;
179 }
180
181 gmx::FileNameOption PyOptionsHolder::fileNameOption(const char *name, size_t count, const char* def) {
182     if (storage.count(name))
183         throw DuplicateOption();
184
185     gmx::FileNameOption option(name);
186     storage[name] = createStorage(option, "f", count);
187     option.defaultBasename(def); // Docs say to set default value like this
188
189     return option;
190 }
191
192 template<typename T> PyObject* PyOptionsHolder::buildValue(const char *sipType, const PyOptionsHolder::option_value &value, const sipTypeDef *td, size_t size) {
193     int count = *value.count;
194     T *store = !value.vector ? (T*) value.value : ((std::vector<T>*) value.value)->data();
195
196     if (count == 1 && !value.vector) {
197         if (td)
198             return sipConvertFromType(store, td, NULL);
199         else
200             return sipBuildResult(NULL, sipType, *store);
201     } else {
202         if (td)
203             return sipConvertToTypedArray(store, td, NULL, size, count, SIP_READ_ONLY);
204         else
205             return sipConvertToArray(store, sipType, count, SIP_READ_ONLY);
206     }
207 }
208
209
210 PyObject* PyOptionsHolder::get_value(const char *name) {
211     if (!storage.count(name))
212         return NULL;
213
214     option_value v = storage[name];
215     switch (*v.type) {
216     case 'd': // double
217         return buildValue<double>("d", v);
218         break;
219     case 'i': // int
220         return buildValue<int>("i", v);
221         break;
222     case 'A': // std::string
223     case 'f': // FileNameOption
224         if (v.vector)
225             return sipConvertFromType(new PyOptionsHolder::StringList(((std::vector<std::string>*) v.value)), sipType_PyOptionsHolder_StringList, Py_None);
226         else if (*v.count == 1)
227             return sipBuildResult(NULL, "A", ((std::string*) v.value)->data());
228         else
229             return sipConvertFromType(new PyOptionsHolder::StringList((std::string*) v.value, *v.count), sipType_PyOptionsHolder_StringList, Py_None);
230         break;
231     case 'b': // bool
232         // TODO: support vector bool options
233         return sipBuildResult(NULL, v.type, *((bool*) v.value));
234         break;
235     case 'S': // Selection
236         return buildValue<gmx::Selection>(NULL, v, sipType_Selection, sizeof(gmx::Selection));
237         break;
238     }
239
240     GMX_ASSERT(false, "Some type is not handled in PyOptionsHolder.get_value");
241     return NULL;
242 }
243
244 template<typename T> void PyOptionsHolder::deleteStorage(const PyOptionsHolder::option_value &value) {
245     if (value.vector)
246         delete (std::vector<T> *) value.value;
247     else
248         delete[] (T*) value.value;
249
250     delete value.count;
251 }
252
253 PyOptionsHolder::~PyOptionsHolder() {
254     for (auto e : storage)
255         switch (*e.second.type) {
256             case 'd': // double
257                 deleteStorage<double>(e.second);
258                 break;
259             case 'i': // int
260                 deleteStorage<int>(e.second);
261                 break;
262             case 'A': // std::string
263             case 'f': // FileNameOption (the storage type is still std::string)
264                 deleteStorage<std::string>(e.second);
265                 break;
266             case 'b': // bool
267                 delete (bool*) e.second.value;
268                 break;
269             case 'S': // Selection
270                 deleteStorage<gmx::Selection>(e.second);
271                 break;
272         }
273 }
274 %End
275
276 %Exception PyOptionsHolder::DuplicateOption {
277 %RaiseCode
278         SIP_BLOCK_THREADS;
279         PyErr_SetString(PyExc_ValueError, sipExceptionRef.what());
280         SIP_UNBLOCK_THREADS;
281 %End
282 };
283
284 class PyOptionsHolder {
285 %TypeHeaderCode
286 #include "gromacs/options/basicoptions.h"
287 %End
288
289 public:
290     class StringList /NoDefaultCtors/ {
291     public:
292         const char* operator[] (int);
293         %MethodCode
294             sipRes = (*sipCpp)[a0];
295
296             if (!sipRes) {
297                 SIP_BLOCK_THREADS;
298                 PyErr_SetString(PyExc_IndexError, "Index out of range");
299                 SIP_UNBLOCK_THREADS;
300                 sipIsErr = 1;
301             }
302         %End
303     };
304     DoubleOption doubleOption(const char *name, int count = 1, double def = 0) throw (PyOptionsHolder::DuplicateOption);
305     IntegerOption integerOption(const char *name, int count = 1, int def = 0) throw (PyOptionsHolder::DuplicateOption);
306     StringOption stringOption(const char *name, int count = 1, const char *def = 0) throw (PyOptionsHolder::DuplicateOption);
307     BooleanOption booleanOption(const char *name, int count = 1, bool def = 0) throw (PyOptionsHolder::DuplicateOption);
308     SelectionOption selectionOption(const char *name, int count = 1) throw (PyOptionsHolder::DuplicateOption);
309     FileNameOption fileNameOption(const char *name, int count = 1, const char *def = 0) throw (PyOptionsHolder::DuplicateOption);
310     SIP_PYOBJECT __getitem__(const char *name);
311     %MethodCode
312         sipRes = sipCpp->get_value(a0);
313
314         if (!sipRes) {
315             SIP_BLOCK_THREADS;
316             PyErr_SetString(PyExc_KeyError, "Invalid option name");
317             SIP_UNBLOCK_THREADS;
318             sipIsErr = 1;
319         }
320     %End
321 };