├── makefile ├── .gitattributes ├── .gitignore ├── vc ├── mnisten.sln └── mnisten.vcxproj ├── README.md ├── include ├── util.h └── cmdline.h └── src └── main.cpp /makefile: -------------------------------------------------------------------------------- 1 | mnisten: src/main.cpp 2 | g++ -Wall -O2 -lboost_filesystem-mt -std=c++0x -o mnisten src/main.cpp -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear on external disk 35 | .Spotlight-V100 36 | .Trashes 37 | 38 | # Directories potentially created on remote AFP share 39 | .AppleDB 40 | .AppleDesktop 41 | Network Trash Folder 42 | Temporary Items 43 | .apdisk 44 | 45 | # Visual Studio Files 46 | *.pdb 47 | *.exe 48 | vc/Debug 49 | vc/Release 50 | *.filters 51 | *.user 52 | *.suo 53 | *.sdf 54 | -------------------------------------------------------------------------------- /vc/mnisten.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.21005.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mnisten", "mnisten.vcxproj", "{21433125-077A-4D62-9081-19F2F0F5C1D2}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Win32 = Debug|Win32 11 | Release|Win32 = Release|Win32 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {21433125-077A-4D62-9081-19F2F0F5C1D2}.Debug|Win32.ActiveCfg = Debug|Win32 15 | {21433125-077A-4D62-9081-19F2F0F5C1D2}.Debug|Win32.Build.0 = Debug|Win32 16 | {21433125-077A-4D62-9081-19F2F0F5C1D2}.Release|Win32.ActiveCfg = Release|Win32 17 | {21433125-077A-4D62-9081-19F2F0F5C1D2}.Release|Win32.Build.0 = Release|Win32 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mnisten : image files to idx format converter 2 | mnisten is a library to convert image files to [idx format](http://yann.lecun.com/exdb/mnist/) binaries. 3 | - assign label id automatically by directory-name (lexicographical order) 4 | - auto resizing 5 | - random-shuffling 6 | - generate train/test file 7 | 8 | #example 9 | 10 | image files: 11 | ``` 12 | .\ 13 | |--foo 14 | | |--a.bmp 15 | | |--b.bmp 16 | | +--1.txt 17 | +--bar 18 | |--c.bmp 19 | |--d.bmp 20 | +--buzz 21 | +--e.bmp 22 | ``` 23 | - a and b.bmp => label 1 24 | - c and d.bmp => label 0 25 | - 1.txt and e.bmp => ignored 26 | 27 | #usage 28 | 29 | ``` 30 | mnisten -d .\ -o prefix -s 32x24 31 | ``` 32 | 33 | then create: 34 | - prefix_train_images.idx3 35 | - prefix_train_labels.idx1 36 | - label.txt 37 | - log directory-name => label-id 38 | 39 | |option|parameters|meaning|required|default| 40 | |---|---|---|---|---| 41 | |--dir,-d|directory-name(string)|root directory of images|yes|-| 42 | |--output,-o|prefix(string)|prefix of output idx files|no|""| 43 | |--num-tests,-n|num(int)|number of test data (if 0, all images are treated as training data)|no|0| 44 | |--size,-s|WxH(string)|size of each image data|no|32x32| 45 | |--without-shuffle,-w|(none)|disable data shuffling|no|false| 46 | 47 | #prerequisites 48 | - OpenCV 49 | - boost (boost/filesystem) 50 | 51 | #build 52 | ``` 53 | make 54 | ``` 55 | or build vc/mnisten.sln 56 | -------------------------------------------------------------------------------- /include/util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | using namespace std; // oops 10 | namespace fs = boost::filesystem; 11 | 12 | template 13 | T* reverse_endian(T* p) { 14 | reverse(reinterpret_cast(p), reinterpret_cast(p) +sizeof(T)); 15 | return p; 16 | } 17 | 18 | inline map make_table_from_subdir_to_label(const string& basedir) 19 | { 20 | map res; 21 | fs::path path(basedir); 22 | 23 | if (!fs::exists(path)) { 24 | throw std::runtime_error("path not exist"); 25 | } 26 | 27 | BOOST_FOREACH(const fs::path& p, std::make_pair(fs::directory_iterator(path), fs::directory_iterator())) { 28 | if (fs::is_directory(p)) 29 | res[p.string()] = 0; 30 | } 31 | 32 | // assign label by alphabetical order (0-origin) 33 | int n = 0; 34 | for (auto& p : res) 35 | p.second = static_cast(n++); 36 | 37 | if (n > numeric_limits::max()) 38 | throw runtime_error("idx1 format doesn't support >255 classes"); 39 | 40 | return res; 41 | } 42 | 43 | // "32x24" => [32, 24] 44 | inline pair parse_size(const string& size) 45 | { 46 | size_t idx = size.find_first_of('x'); 47 | if (idx == string::npos) 48 | throw runtime_error("invalid size format"); 49 | 50 | string w = size.substr(0, idx); 51 | string h = size.substr(idx + 1, size.length() - idx - 1); 52 | return make_pair(stoi(w), stoi(h)); 53 | } 54 | -------------------------------------------------------------------------------- /vc/mnisten.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | {21433125-077A-4D62-9081-19F2F0F5C1D2} 22 | mnisten 23 | 24 | 25 | 26 | Application 27 | true 28 | v120 29 | MultiByte 30 | 31 | 32 | Application 33 | false 34 | v120 35 | true 36 | MultiByte 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | Level3 52 | Disabled 53 | true 54 | ..\include 55 | 56 | 57 | true 58 | opencv_core300.lib;opencv_highgui300.lib;opencv_imgproc300.lib;opencv_imgcodecs300.lib;%(AdditionalDependencies) 59 | 60 | 61 | 62 | 63 | Level3 64 | MaxSpeed 65 | true 66 | true 67 | true 68 | ..\include 69 | 70 | 71 | true 72 | true 73 | true 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | #include "cmdline.h" 15 | #include "util.h" 16 | 17 | using namespace std; 18 | namespace fs = boost::filesystem; 19 | 20 | struct image { 21 | cv::Mat data; 22 | uint8_t label; 23 | }; 24 | 25 | template 26 | void gen_mnist_labels(const string& label_file, ImageIterator begin, ImageIterator end) 27 | { 28 | ofstream ofs(label_file.c_str(), ios::binary|ios::out); 29 | 30 | if (ofs.bad() || ofs.fail()) 31 | throw runtime_error("failed to open file:" + label_file); 32 | 33 | uint32_t magic_number = 0x00000801; 34 | uint32_t num_items = distance(begin, end); 35 | 36 | #if defined(BOOST_LITTLE_ENDIAN) 37 | reverse_endian(&magic_number); 38 | reverse_endian(&num_items); 39 | #endif 40 | 41 | ofs.write((char*) &magic_number, 4); 42 | ofs.write((char*) &num_items, 4); 43 | 44 | for (; begin != end; ++begin) 45 | ofs.write((char*) &begin->label, 1); 46 | } 47 | 48 | template 49 | void gen_mnist_images(const string& image_file, ImageIterator begin, ImageIterator end) 50 | { 51 | ofstream ofs(image_file.c_str(), ios::binary | ios::out); 52 | 53 | if (ofs.bad() || ofs.fail()) 54 | throw runtime_error("failed to open file:" + image_file); 55 | 56 | uint32_t magic_number = 0x00000803; 57 | uint32_t num_items = distance(begin, end); 58 | uint32_t num_rows = begin->data.rows; 59 | uint32_t num_cols = begin->data.cols; 60 | 61 | #if defined(BOOST_LITTLE_ENDIAN) 62 | reverse_endian(&magic_number); 63 | reverse_endian(&num_items); 64 | reverse_endian(&num_rows); 65 | reverse_endian(&num_cols); 66 | #endif 67 | 68 | ofs.write((char*) &magic_number, 4); 69 | ofs.write((char*) &num_items, 4); 70 | ofs.write((char*) &num_rows, 4); 71 | ofs.write((char*) &num_cols, 4); 72 | 73 | for (; begin != end; ++begin) { 74 | cv::Mat_& m = (cv::Mat_&)begin->data; 75 | 76 | for (auto v : m) 77 | ofs.write((const char*)&v, 1); 78 | } 79 | } 80 | 81 | int read_images(const fs::path& path, uint8_t label, vector& images, int w, int h) 82 | { 83 | int num_images = 0; 84 | BOOST_FOREACH(const fs::path& p, std::make_pair(fs::directory_iterator(path), fs::directory_iterator())) { 85 | if (fs::is_directory(p)) continue; 86 | 87 | image img; 88 | cv::Mat srcimg = cv::imread(p.string(), cv::IMREAD_GRAYSCALE); 89 | if (srcimg.data == nullptr) 90 | continue; 91 | 92 | cv::resize(srcimg, img.data, cv::Size(w, h)); // gray, linear interpolation 93 | 94 | //cv::imshow("data", img.data); 95 | //cv::waitKey(5); 96 | 97 | img.label = label; 98 | images.push_back(img); 99 | num_images++; 100 | } 101 | return num_images; 102 | } 103 | 104 | void dump_map(const map& m, const string& filename) 105 | { 106 | ofstream ofs(filename.c_str()); 107 | if (ofs.fail() || ofs.bad()) 108 | throw runtime_error("failed to create file:" + filename); 109 | 110 | for (auto v : m) 111 | ofs << v.first << "," << v.second << endl; 112 | } 113 | 114 | string add_prefix(const string& prefix, const string& base) 115 | { 116 | return prefix.empty() ? base : prefix + "_" + base; 117 | } 118 | 119 | void exec(const string& dir, const string& output_prefix, int num_tests, int w, int h, bool data_shuffle = true) 120 | { 121 | auto map = make_table_from_subdir_to_label(dir); 122 | vector images; 123 | fs::path path(dir); 124 | 125 | BOOST_FOREACH(const fs::path& p, std::make_pair(fs::directory_iterator(path), fs::directory_iterator())) { 126 | if (fs::is_directory(p)) { 127 | int n = read_images(p, map[p.string()], images, w, h); 128 | cout << (int)map[p.string()] << ":" << p.stem().string() << " " << n << "images" << endl; 129 | } 130 | } 131 | 132 | // shuffle 133 | if (data_shuffle) 134 | shuffle(images.begin(), images.end(), default_random_engine(0)); 135 | 136 | // split train/test data 137 | if ((int)images.size() <= num_tests) 138 | throw runtime_error("total images are less than num_tests"); 139 | 140 | cout << "total " << images.size() << "images found." << endl; 141 | 142 | string train_img = add_prefix(output_prefix, "train_images.idx3"); 143 | string train_label = add_prefix(output_prefix, "train_labels.idx1"); 144 | string test_img = add_prefix(output_prefix, "test_images.idx3"); 145 | string test_label = add_prefix(output_prefix, "test_labels.idx1"); 146 | 147 | if (num_tests > 0) { 148 | gen_mnist_images(test_img, images.begin(), images.begin() + num_tests); 149 | gen_mnist_labels(test_label, images.begin(), images.begin() + num_tests); 150 | } 151 | gen_mnist_images(train_img, images.begin() + num_tests, images.end()); 152 | gen_mnist_labels(train_label, images.begin() + num_tests, images.end()); 153 | 154 | dump_map(map, "label.txt"); 155 | } 156 | 157 | int main(int argc, char *argv[]) 158 | { 159 | cmdline::parser a; 160 | a.add("dir", 'd', "target directory", true); 161 | a.add("output", 'o', "output file prefix", false); 162 | a.add("num-tests", 'n', "number of test data", false, 0); 163 | a.add("size", 's', "size of output data (WxH)", false, "32x32"); 164 | a.add("without-shuffle", 'w', "create data without shuffling"); 165 | 166 | a.parse_check(argc, argv); 167 | 168 | try { 169 | auto size = parse_size(a.get("size")); 170 | exec(a.get("dir"), a.get("output"), a.get("num-tests"), size.first, size.second, !a.exist("without-shuffle")); 171 | } 172 | catch (const exception& e) { 173 | cout << "error:" << e.what() << endl; 174 | } 175 | catch (...) { 176 | cout << "error:unknown exception" << endl; 177 | } 178 | 179 | return 0; 180 | } 181 | -------------------------------------------------------------------------------- /include/cmdline.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2009, Hideyuki Tanaka 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * Neither the name of the nor the 13 | names of its contributors may be used to endorse or promote products 14 | derived from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY ''AS IS'' AND ANY 17 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #pragma once 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | 41 | namespace cmdline{ 42 | 43 | namespace detail{ 44 | 45 | template 46 | class lexical_cast_t{ 47 | public: 48 | static Target cast(const Source &arg){ 49 | Target ret; 50 | std::stringstream ss; 51 | if (!(ss<>ret && ss.eof())) 52 | throw std::bad_cast(); 53 | 54 | return ret; 55 | } 56 | }; 57 | 58 | template 59 | class lexical_cast_t{ 60 | public: 61 | static Target cast(const Source &arg){ 62 | return arg; 63 | } 64 | }; 65 | 66 | template 67 | class lexical_cast_t{ 68 | public: 69 | static std::string cast(const Source &arg){ 70 | std::ostringstream ss; 71 | ss< 77 | class lexical_cast_t{ 78 | public: 79 | static Target cast(const std::string &arg){ 80 | Target ret; 81 | std::istringstream ss(arg); 82 | if (!(ss>>ret && ss.eof())) 83 | throw std::bad_cast(); 84 | return ret; 85 | } 86 | }; 87 | 88 | template 89 | struct is_same { 90 | static const bool value = false; 91 | }; 92 | 93 | template 94 | struct is_same{ 95 | static const bool value = true; 96 | }; 97 | 98 | template 99 | Target lexical_cast(const Source &arg) 100 | { 101 | return lexical_cast_t::value>::cast(arg); 102 | } 103 | 104 | static inline std::string demangle(const std::string &name) 105 | { 106 | /*int status=0; 107 | char *p=abi::__cxa_demangle(name.c_str(), 0, 0, &status); 108 | std::string ret(p); 109 | free(p); 110 | return ret;*/ 111 | return name; 112 | } 113 | 114 | template 115 | std::string readable_typename() 116 | { 117 | return demangle(typeid(T).name()); 118 | } 119 | 120 | template 121 | std::string default_value(T def) 122 | { 123 | return detail::lexical_cast(def); 124 | } 125 | 126 | template <> 127 | inline std::string readable_typename() 128 | { 129 | return "string"; 130 | } 131 | 132 | } // detail 133 | 134 | //----- 135 | 136 | class cmdline_error : public std::exception { 137 | public: 138 | cmdline_error(const std::string &msg): msg(msg){} 139 | ~cmdline_error() throw() {} 140 | const char *what() const throw() { return msg.c_str(); } 141 | private: 142 | std::string msg; 143 | }; 144 | 145 | template 146 | struct default_reader{ 147 | T operator()(const std::string &str){ 148 | return detail::lexical_cast(str); 149 | } 150 | }; 151 | 152 | template 153 | struct range_reader{ 154 | range_reader(const T &low, const T &high): low(low), high(high) {} 155 | T operator()(const std::string &s) const { 156 | T ret=default_reader()(s); 157 | if (!(ret>=low && ret<=high)) throw cmdline::cmdline_error("range_error"); 158 | return ret; 159 | } 160 | private: 161 | T low, high; 162 | }; 163 | 164 | template 165 | range_reader range(const T &low, const T &high) 166 | { 167 | return range_reader(low, high); 168 | } 169 | 170 | template 171 | struct oneof_reader{ 172 | T operator()(const std::string &s){ 173 | T ret=default_reader()(s); 174 | if (std::find(alt.begin(), alt.end(), ret)==alt.end()) 175 | throw cmdline_error(""); 176 | return ret; 177 | } 178 | void add(const T &v){ alt.push_back(v); } 179 | private: 180 | std::vector alt; 181 | }; 182 | 183 | template 184 | oneof_reader oneof(T a1) 185 | { 186 | oneof_reader ret; 187 | ret.add(a1); 188 | return ret; 189 | } 190 | 191 | template 192 | oneof_reader oneof(T a1, T a2) 193 | { 194 | oneof_reader ret; 195 | ret.add(a1); 196 | ret.add(a2); 197 | return ret; 198 | } 199 | 200 | template 201 | oneof_reader oneof(T a1, T a2, T a3) 202 | { 203 | oneof_reader ret; 204 | ret.add(a1); 205 | ret.add(a2); 206 | ret.add(a3); 207 | return ret; 208 | } 209 | 210 | template 211 | oneof_reader oneof(T a1, T a2, T a3, T a4) 212 | { 213 | oneof_reader ret; 214 | ret.add(a1); 215 | ret.add(a2); 216 | ret.add(a3); 217 | ret.add(a4); 218 | return ret; 219 | } 220 | 221 | template 222 | oneof_reader oneof(T a1, T a2, T a3, T a4, T a5) 223 | { 224 | oneof_reader ret; 225 | ret.add(a1); 226 | ret.add(a2); 227 | ret.add(a3); 228 | ret.add(a4); 229 | ret.add(a5); 230 | return ret; 231 | } 232 | 233 | template 234 | oneof_reader oneof(T a1, T a2, T a3, T a4, T a5, T a6) 235 | { 236 | oneof_reader ret; 237 | ret.add(a1); 238 | ret.add(a2); 239 | ret.add(a3); 240 | ret.add(a4); 241 | ret.add(a5); 242 | ret.add(a6); 243 | return ret; 244 | } 245 | 246 | template 247 | oneof_reader oneof(T a1, T a2, T a3, T a4, T a5, T a6, T a7) 248 | { 249 | oneof_reader ret; 250 | ret.add(a1); 251 | ret.add(a2); 252 | ret.add(a3); 253 | ret.add(a4); 254 | ret.add(a5); 255 | ret.add(a6); 256 | ret.add(a7); 257 | return ret; 258 | } 259 | 260 | template 261 | oneof_reader oneof(T a1, T a2, T a3, T a4, T a5, T a6, T a7, T a8) 262 | { 263 | oneof_reader ret; 264 | ret.add(a1); 265 | ret.add(a2); 266 | ret.add(a3); 267 | ret.add(a4); 268 | ret.add(a5); 269 | ret.add(a6); 270 | ret.add(a7); 271 | ret.add(a8); 272 | return ret; 273 | } 274 | 275 | template 276 | oneof_reader oneof(T a1, T a2, T a3, T a4, T a5, T a6, T a7, T a8, T a9) 277 | { 278 | oneof_reader ret; 279 | ret.add(a1); 280 | ret.add(a2); 281 | ret.add(a3); 282 | ret.add(a4); 283 | ret.add(a5); 284 | ret.add(a6); 285 | ret.add(a7); 286 | ret.add(a8); 287 | ret.add(a9); 288 | return ret; 289 | } 290 | 291 | template 292 | oneof_reader oneof(T a1, T a2, T a3, T a4, T a5, T a6, T a7, T a8, T a9, T a10) 293 | { 294 | oneof_reader ret; 295 | ret.add(a1); 296 | ret.add(a2); 297 | ret.add(a3); 298 | ret.add(a4); 299 | ret.add(a5); 300 | ret.add(a6); 301 | ret.add(a7); 302 | ret.add(a8); 303 | ret.add(a9); 304 | ret.add(a10); 305 | return ret; 306 | } 307 | 308 | //----- 309 | 310 | class parser{ 311 | public: 312 | parser(){ 313 | } 314 | ~parser(){ 315 | for (std::map::iterator p=options.begin(); 316 | p!=options.end(); p++) 317 | delete p->second; 318 | } 319 | 320 | void add(const std::string &name, 321 | char short_name=0, 322 | const std::string &desc=""){ 323 | if (options.count(name)) throw cmdline_error("multiple definition: "+name); 324 | options[name]=new option_without_value(name, short_name, desc); 325 | ordered.push_back(options[name]); 326 | } 327 | 328 | template 329 | void add(const std::string &name, 330 | char short_name=0, 331 | const std::string &desc="", 332 | bool need=true, 333 | const T def=T()){ 334 | add(name, short_name, desc, need, def, default_reader()); 335 | } 336 | 337 | template 338 | void add(const std::string &name, 339 | char short_name=0, 340 | const std::string &desc="", 341 | bool need=true, 342 | const T def=T(), 343 | F reader=F()){ 344 | if (options.count(name)) throw cmdline_error("multiple definition: "+name); 345 | options[name]=new option_with_value_with_reader(name, short_name, need, def, desc, reader); 346 | ordered.push_back(options[name]); 347 | } 348 | 349 | void footer(const std::string &f){ 350 | ftr=f; 351 | } 352 | 353 | void set_program_name(const std::string &name){ 354 | prog_name=name; 355 | } 356 | 357 | bool exist(const std::string &name) const { 358 | if (options.count(name)==0) throw cmdline_error("there is no flag: --"+name); 359 | return options.find(name)->second->has_set(); 360 | } 361 | 362 | template 363 | const T &get(const std::string &name) const { 364 | if (options.count(name)==0) throw cmdline_error("there is no flag: --"+name); 365 | const option_with_value *p=dynamic_cast*>(options.find(name)->second); 366 | if (p==NULL) throw cmdline_error("type mismatch flag '"+name+"'"); 367 | return p->get(); 368 | } 369 | 370 | const std::vector &rest() const { 371 | return others; 372 | } 373 | 374 | bool parse(const std::string &arg){ 375 | std::vector args; 376 | 377 | std::string buf; 378 | bool in_quote=false; 379 | for (std::string::size_type i=0; i=arg.length()){ 394 | errors.push_back("unexpected occurrence of '\\' at end of string"); 395 | return false; 396 | } 397 | } 398 | 399 | buf+=arg[i]; 400 | } 401 | 402 | if (in_quote){ 403 | errors.push_back("quote is not closed"); 404 | return false; 405 | } 406 | 407 | if (buf.length()>0) 408 | args.push_back(buf); 409 | 410 | for (size_t i=0; i &args){ 417 | int argc=static_cast(args.size()); 418 | std::vector argv(argc); 419 | 420 | for (int i=0; i lookup; 438 | for (std::map::iterator p=options.begin(); 439 | p!=options.end(); p++){ 440 | if (p->first.length()==0) continue; 441 | char initial=p->second->short_name(); 442 | if (initial){ 443 | if (lookup.count(initial)>0){ 444 | lookup[initial]=""; 445 | errors.push_back(std::string("short option '")+initial+"' is ambiguous"); 446 | return false; 447 | } 448 | else lookup[initial]=p->first; 449 | } 450 | } 451 | 452 | for (int i=1; i &args){ 534 | if (!options.count("help")) 535 | add("help", '?', "print this message"); 536 | check(args.size(), parse(args)); 537 | } 538 | 539 | void parse_check(int argc, char *argv[]){ 540 | if (!options.count("help")) 541 | add("help", '?', "print this message"); 542 | check(argc, parse(argc, argv)); 543 | } 544 | 545 | std::string error() const{ 546 | return errors.size()>0?errors[0]:""; 547 | } 548 | 549 | std::string error_full() const{ 550 | std::ostringstream oss; 551 | for (size_t i=0; imust()) 561 | oss<short_description()<<" "; 562 | } 563 | 564 | oss<<"[options] ... "<name().length()); 570 | } 571 | for (size_t i=0; ishort_name()){ 573 | oss<<" -"<short_name()<<", "; 574 | } 575 | else{ 576 | oss<<" "; 577 | } 578 | 579 | oss<<"--"<name(); 580 | for (size_t j=ordered[i]->name().length(); jdescription()<set()){ 607 | errors.push_back("option needs value: --"+name); 608 | return; 609 | } 610 | } 611 | 612 | void set_option(const std::string &name, const std::string &value){ 613 | if (options.count(name)==0){ 614 | errors.push_back("undefined option: --"+name); 615 | return; 616 | } 617 | if (!options[name]->set(value)){ 618 | errors.push_back("option value is invalid: --"+name+"="+value); 619 | return; 620 | } 621 | } 622 | 623 | class option_base{ 624 | public: 625 | virtual ~option_base(){} 626 | 627 | virtual bool has_value() const=0; 628 | virtual bool set()=0; 629 | virtual bool set(const std::string &value)=0; 630 | virtual bool has_set() const=0; 631 | virtual bool valid() const=0; 632 | virtual bool must() const=0; 633 | 634 | virtual const std::string &name() const=0; 635 | virtual char short_name() const=0; 636 | virtual const std::string &description() const=0; 637 | virtual std::string short_description() const=0; 638 | }; 639 | 640 | class option_without_value : public option_base { 641 | public: 642 | option_without_value(const std::string &name, 643 | char short_name, 644 | const std::string &desc) 645 | :nam(name), snam(short_name), desc(desc), has(false){ 646 | } 647 | ~option_without_value(){} 648 | 649 | bool has_value() const { return false; } 650 | 651 | bool set(){ 652 | has=true; 653 | return true; 654 | } 655 | 656 | bool set(const std::string &){ 657 | return false; 658 | } 659 | 660 | bool has_set() const { 661 | return has; 662 | } 663 | 664 | bool valid() const{ 665 | return true; 666 | } 667 | 668 | bool must() const{ 669 | return false; 670 | } 671 | 672 | const std::string &name() const{ 673 | return nam; 674 | } 675 | 676 | char short_name() const{ 677 | return snam; 678 | } 679 | 680 | const std::string &description() const { 681 | return desc; 682 | } 683 | 684 | std::string short_description() const{ 685 | return "--"+nam; 686 | } 687 | 688 | private: 689 | std::string nam; 690 | char snam; 691 | std::string desc; 692 | bool has; 693 | }; 694 | 695 | template 696 | class option_with_value : public option_base { 697 | public: 698 | option_with_value(const std::string &name, 699 | char short_name, 700 | bool need, 701 | const T &def, 702 | const std::string &desc) 703 | : nam(name), snam(short_name), need(need), has(false) 704 | , def(def), actual(def) { 705 | this->desc=full_description(desc); 706 | } 707 | ~option_with_value(){} 708 | 709 | const T &get() const { 710 | return actual; 711 | } 712 | 713 | bool has_value() const { return true; } 714 | 715 | bool set(){ 716 | return false; 717 | } 718 | 719 | bool set(const std::string &value){ 720 | try{ 721 | actual=read(value); 722 | has=true; 723 | } 724 | catch(const std::exception &e){ 725 | return false; 726 | } 727 | return true; 728 | } 729 | 730 | bool has_set() const{ 731 | return has; 732 | } 733 | 734 | bool valid() const{ 735 | if (need && !has) return false; 736 | return true; 737 | } 738 | 739 | bool must() const{ 740 | return need; 741 | } 742 | 743 | const std::string &name() const{ 744 | return nam; 745 | } 746 | 747 | char short_name() const{ 748 | return snam; 749 | } 750 | 751 | const std::string &description() const { 752 | return desc; 753 | } 754 | 755 | std::string short_description() const{ 756 | return "--"+nam+"="+detail::readable_typename(); 757 | } 758 | 759 | protected: 760 | std::string full_description(const std::string &desc){ 761 | return 762 | desc+" ("+detail::readable_typename()+ 763 | (need?"":" [="+detail::default_value(def)+"]") 764 | +")"; 765 | } 766 | 767 | virtual T read(const std::string &s)=0; 768 | 769 | std::string nam; 770 | char snam; 771 | bool need; 772 | std::string desc; 773 | 774 | bool has; 775 | T def; 776 | T actual; 777 | }; 778 | 779 | template 780 | class option_with_value_with_reader : public option_with_value { 781 | public: 782 | option_with_value_with_reader(const std::string &name, 783 | char short_name, 784 | bool need, 785 | const T def, 786 | const std::string &desc, 787 | F reader) 788 | : option_with_value(name, short_name, need, def, desc), reader(reader){ 789 | } 790 | 791 | private: 792 | T read(const std::string &s){ 793 | return reader(s); 794 | } 795 | 796 | F reader; 797 | }; 798 | 799 | std::map options; 800 | std::vector ordered; 801 | std::string ftr; 802 | 803 | std::string prog_name; 804 | std::vector others; 805 | 806 | std::vector errors; 807 | }; 808 | 809 | } // cmdline 810 | --------------------------------------------------------------------------------