1 // Copyright 2008-present Contributors to the OpenImageIO project.
2 // SPDX-License-Identifier: BSD-3-Clause
3 // https://github.com/OpenImageIO/oiio/blob/master/LICENSE.md
4 
5 
6 #include <cmath>
7 #include <cstdio>
8 #include <cstdlib>
9 #include <ctime>
10 #include <iostream>
11 #include <iterator>
12 #include <memory>
13 
14 #include <OpenImageIO/argparse.h>
15 #include <OpenImageIO/filesystem.h>
16 #include <OpenImageIO/imageio.h>
17 #include <OpenImageIO/strutil.h>
18 #include <OpenImageIO/sysutil.h>
19 
20 #ifdef USE_BOOST_REGEX
21 #    include <boost/regex.hpp>
22 using boost::regex;
23 using boost::regex_search;
24 using namespace boost::regex_constants;
25 #else
26 #    include <regex>
27 using std::regex;
28 using std::regex_search;
29 using namespace std::regex_constants;
30 #endif
31 
32 using namespace OIIO;
33 
34 static bool help          = false;
35 static bool invert_match  = false;
36 static bool list_files    = false;
37 static bool recursive     = false;
38 static bool file_match    = false;
39 static bool print_dirs    = false;
40 static bool all_subimages = false;
41 static std::string pattern;
42 static std::vector<std::string> filenames;
43 
44 
45 
46 static bool
grep_file(const std::string & filename,regex & re,bool ignore_nonimage_files=false)47 grep_file(const std::string& filename, regex& re,
48           bool ignore_nonimage_files = false)
49 {
50     if (!Filesystem::exists(filename)) {
51         std::cerr << "igrep: " << filename << ": No such file or directory\n";
52         return false;
53     }
54 
55     if (Filesystem::is_directory(filename)) {
56         if (!recursive)
57             return false;
58         if (print_dirs) {
59             std::cout << "(" << filename << "/)\n";
60             std::cout.flush();
61         }
62         bool r = false;
63         std::vector<std::string> directory_entries;
64         Filesystem::get_directory_entries(filename, directory_entries);
65         for (const auto& d : directory_entries)
66             r |= grep_file(d, re, true);
67         return r;
68     }
69 
70     auto in = ImageInput::open(filename);
71     if (!in.get()) {
72         if (!ignore_nonimage_files)
73             std::cerr << geterror() << "\n";
74         return false;
75     }
76     ImageSpec spec = in->spec();
77 
78     if (file_match) {
79         bool match = regex_search(filename, re);
80         if (match && !invert_match) {
81             std::cout << filename << "\n";
82             return true;
83         }
84     }
85 
86     bool found   = false;
87     int subimage = 0;
88     do {
89         if (!all_subimages && subimage > 0)
90             break;
91         for (auto&& p : spec.extra_attribs) {
92             TypeDesc t = p.type();
93             if (t.elementtype() == TypeDesc::STRING) {
94                 int n = t.numelements();
95                 for (int i = 0; i < n; ++i) {
96                     bool match = regex_search(((const char**)p.data())[i], re);
97                     found |= match;
98                     if (match && !invert_match) {
99                         if (list_files) {
100                             std::cout << filename << "\n";
101                             return found;
102                         }
103                         std::cout << filename << ": " << p.name() << " = "
104                                   << ((const char**)p.data())[i] << "\n";
105                     }
106                 }
107             }
108         }
109     } while (in->seek_subimage(++subimage, 0, spec));
110 
111     if (invert_match) {
112         found = !found;
113         if (found)
114             std::cout << filename << "\n";
115     }
116     return found;
117 }
118 
119 
120 
121 static int
parse_files(int argc,const char * argv[])122 parse_files(int argc, const char* argv[])
123 {
124     for (int i = 0; i < argc; i++) {
125         if (pattern.empty())
126             pattern = argv[i];
127         else
128             filenames.emplace_back(argv[i]);
129     }
130     return 0;
131 }
132 
133 
134 
135 int
main(int argc,const char * argv[])136 main(int argc, const char* argv[])
137 {
138     // Helpful for debugging to make sure that any crashes dump a stack
139     // trace.
140     Sysutil::setup_crash_stacktrace("stdout");
141 
142     Filesystem::convert_native_arguments(argc, argv);
143     // clang-format off
144     ArgParse ap;
145     ap.intro("igrep -- search images for matching metadata\n"
146              OIIO_INTRO_STRING)
147       .usage("igrep [options] pattern filename...");
148     ap.arg("filename")
149       .hidden()
150       .action(parse_files);
151     ap.arg("-i")
152       .help("Ignore upper/lower case distinctions");
153     ap.arg("-v", &invert_match)
154       .help("Invert match (select non-matching files)");
155     ap.arg("-E")
156       .help( "Pattern is an extended regular expression");
157     ap.arg("-f", &file_match)
158       .help("Match against file name as well as metadata");
159     ap.arg("-l", &list_files)
160       .help("List the matching files (no detail)");
161     ap.arg("-r", &recursive)
162       .help("Recurse into directories");
163     ap.arg("-d", &print_dirs)
164       .help("Print directories (when recursive)");
165     ap.arg("-a", &all_subimages)
166       .help("Search all subimages of each file");
167 
168     // clang-format on
169     ap.parse(argc, argv);
170     if (pattern.empty() || filenames.empty()) {
171         std::cerr << ap.geterror() << std::endl;
172         ap.usage();
173         return help ? EXIT_SUCCESS : EXIT_FAILURE;
174     }
175 
176 #if USE_BOOST_REGEX
177     boost::regex_constants::syntax_option_type flag
178         = boost::regex_constants::grep;
179     if (ap["E"].get<int>())
180         flag = boost::regex::extended;
181     if (ap["i"].get<int>())
182         flag |= boost::regex_constants::icase;
183 #else
184     auto flag = std::regex_constants::grep;
185     if (ap["E"].get<int>())
186         flag = std::regex_constants::extended;
187     if (ap["i"].get<int>())
188         flag |= std::regex_constants::icase;
189 #endif
190     regex re(pattern, flag);
191     for (auto&& s : filenames)
192         grep_file(s, re);
193 
194     return 0;
195 }
196