Skip to content

Commit 0b1bd10

Browse files
authored
Config file parsing (#1)
* config file parsing * refactor + rename matchArgs -> startsWith * remove indivual arguments, add 'args' key to config
1 parent fcf8b4a commit 0b1bd10

File tree

5 files changed

+1477
-0
lines changed

5 files changed

+1477
-0
lines changed

Makefile

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
CXXFLAGS=-Wall -Wextra -Wpedantic
2+
SRC=main.cpp\
3+
config.cpp
4+
OBJ=$(SRC:%.cpp=%.o)
5+
6+
run-cppcheck: $(OBJ)
7+
$(CXX) $(LDFLAGS) -o $@ $^
8+
9+
%.o: %.cpp
10+
$(CXX) $(CXXFLAGS) -o $@ -c $<
11+
12+
clean:
13+
rm -f run-cppcheck *.o
14+
15+
.PHONY: clean

config.cpp

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
#include "config.h"
2+
#include "picojson.h"
3+
4+
#include <fstream>
5+
#include <vector>
6+
#include <cstring>
7+
#include <cerrno>
8+
9+
std::string Config::load(const std::filesystem::path &path)
10+
{
11+
// Read config file
12+
std::ifstream ifs(path);
13+
if (ifs.fail())
14+
return std::strerror(errno);
15+
16+
std::streampos length;
17+
ifs.seekg(0, std::ios::end);
18+
length = ifs.tellg();
19+
ifs.seekg(0, std::ios::beg);
20+
21+
std::vector<char> buffer(length);
22+
ifs.read(&buffer[0], length);
23+
24+
if (ifs.fail())
25+
return std::strerror(errno);
26+
27+
std::string text = buffer.data();
28+
29+
// Parse JSON
30+
picojson::value data;
31+
std::string err = picojson::parse(data, text);
32+
if (!err.empty()) {
33+
return err;
34+
}
35+
36+
if (!data.is<picojson::object>())
37+
return "Invalid config format";
38+
39+
const picojson::object &obj = data.get<picojson::object>();
40+
41+
// Read settings
42+
for (auto [key, value] : obj) {
43+
44+
if (key == "project_file") {
45+
if (!value.is<std::string>()) {
46+
return "Invalid value type for '" + key + "'";
47+
}
48+
m_projectFilePath = value.get<std::string>();
49+
continue;
50+
}
51+
52+
if (key == "cppcheck") {
53+
if (!value.is<std::string>()) {
54+
return "Invalid value type for '" + key + "'";
55+
}
56+
m_cppcheck = value.get<std::string>();
57+
continue;
58+
}
59+
60+
if (key == "args") {
61+
if (!value.is<picojson::array>())
62+
return "Invalid value type for '" + key + "'";
63+
64+
for (auto arg : value.get<picojson::array>()) {
65+
if (!arg.is<std::string>())
66+
return "Invalid value type for array element in '" + key + "'";
67+
m_args.push_back(arg.get<std::string>());
68+
}
69+
continue;
70+
}
71+
72+
return "Invalid config key '" + key + "'";
73+
}
74+
75+
return "";
76+
}
77+
78+
std::string Config::command() const
79+
{
80+
std::string cmd;
81+
82+
cmd += m_cppcheck;
83+
84+
for (auto arg : m_args)
85+
cmd += " " + arg;
86+
87+
if (!m_projectFilePath.empty()) {
88+
89+
std::string filter = m_filename;
90+
if (std::strchr(filter.c_str(), ' '))
91+
filter = "\"" + filter + "\"";
92+
93+
cmd += " --project=" + m_projectFilePath.string() + " --file-filter=" + filter;
94+
95+
} else {
96+
cmd += " " + m_filename;
97+
}
98+
99+
cmd += " 2>&1";
100+
101+
return cmd;
102+
}
103+
104+
static const char *startsWith(const char *arg, const char *start) {
105+
if (std::strncmp(arg, start, std::strlen(start)) != 0)
106+
return NULL;
107+
return arg + std::strlen(start);
108+
}
109+
110+
std::string Config::parseArgs(int argc, char **argv)
111+
{
112+
(void) argc;
113+
114+
++argv;
115+
116+
for (; *argv; ++argv) {
117+
const char *arg = *argv;
118+
const char *value;
119+
120+
if ((value = startsWith(arg, "--config="))) {
121+
std::string err = load(value);
122+
if (!err.empty())
123+
return "Failed to load config file '" + std::string(value) + "': " + err;
124+
continue;
125+
}
126+
127+
if (arg[0] == '-')
128+
return "Invalid option '" + std::string(arg) + "'";
129+
130+
if (!m_filename.empty())
131+
return "Multiple filenames provided";
132+
133+
m_filename = arg;
134+
}
135+
136+
if (m_filename.empty())
137+
return "Missing filename";
138+
139+
return "";
140+
}

config.h

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#ifndef CONFIG_H
2+
#define CONFIG_H
3+
4+
#include <filesystem>
5+
#include <vector>
6+
#include <string>
7+
8+
class Config {
9+
public:
10+
Config()
11+
: m_projectFilePath("")
12+
, m_cppcheck("cppcheck")
13+
, m_filename("")
14+
, m_args({})
15+
{
16+
}
17+
18+
/* Load config file.
19+
* Returns an empty string on success, or an error message
20+
* on failure. */
21+
std::string load(const std::filesystem::path &path);
22+
23+
/* Construct cppcheck command string */
24+
std::string command() const;
25+
26+
/* Read command line arguments.
27+
* Returns an empty string on success, or an error message
28+
* on failure. */
29+
std::string parseArgs(int argc, char **argv);
30+
31+
private:
32+
std::filesystem::path m_projectFilePath = "";
33+
std::string m_cppcheck;
34+
std::string m_filename;
35+
std::vector<std::string> m_args;
36+
};
37+
38+
#endif

main.cpp

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
2+
#include <iostream>
3+
#include <fstream>
4+
#include <string>
5+
#include <cstring>
6+
#include <cstdlib>
7+
#include <filesystem>
8+
9+
#include <sys/stat.h> // stat
10+
11+
#include "config.h"
12+
13+
std::ofstream logfile;
14+
15+
16+
/**
17+
* Execute a shell command and read the output from it. Returns true if command terminated successfully.
18+
*/
19+
static int executeCommand(const std::string& cmd, std::string &output)
20+
{
21+
output.clear();
22+
23+
#ifdef _WIN32
24+
FILE* p = _popen(cmd.c_str(), "r");
25+
#else
26+
FILE *p = popen(cmd.c_str(), "r");
27+
#endif
28+
if (!p) {
29+
// TODO: how to provide to caller?
30+
const int err = errno;
31+
logfile << "popen() errno " << std::to_string(err) << std::endl;
32+
return EXIT_FAILURE;
33+
}
34+
char buffer[1024];
35+
while (fgets(buffer, sizeof(buffer), p) != nullptr)
36+
output += buffer;
37+
38+
#ifdef _WIN32
39+
const int res = _pclose(p);
40+
#else
41+
const int res = pclose(p);
42+
#endif
43+
if (res == -1) { // error occurred
44+
// TODO: how to provide to caller?
45+
const int err = errno;
46+
logfile << "pclose() errno " << std::to_string(err) << std::endl;
47+
return res;
48+
}
49+
#if !defined(WIN32) && !defined(__MINGW32__)
50+
if (WIFEXITED(res)) {
51+
return WEXITSTATUS(res);
52+
}
53+
if (WIFSIGNALED(res)) {
54+
return WTERMSIG(res);
55+
}
56+
#endif
57+
return res;
58+
}
59+
60+
int main(int argc, char** argv) {
61+
62+
const std::string log_path = std::filesystem::current_path() / "log.txt";
63+
logfile.open(log_path, std::ios_base::app);
64+
65+
Config config;
66+
67+
const std::string err = config.parseArgs(argc, argv);
68+
if (!err.empty()) {
69+
std::cerr << "error: " << err << std::endl;
70+
return EXIT_FAILURE;
71+
}
72+
73+
const std::string cmd = config.command();
74+
75+
logfile << "CMD:" << cmd << std::endl;
76+
77+
std::string output;
78+
int res = executeCommand(cmd, output);
79+
80+
std::cerr << output;
81+
logfile << output;
82+
83+
return res;
84+
}

0 commit comments

Comments
 (0)