From 31afceeade6dd852a8b7a71805b21a20e502229a Mon Sep 17 00:00:00 2001 From: dec05eba Date: Tue, 5 Jun 2018 14:28:40 +0200 Subject: Add sibs init to init project in existing directory --- src/Conf.cpp | 4 + src/FileUtil.cpp | 12 +++ src/main.cpp | 260 +++++++++++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 240 insertions(+), 36 deletions(-) (limited to 'src') diff --git a/src/Conf.cpp b/src/Conf.cpp index 883022b..4424551 100644 --- a/src/Conf.cpp +++ b/src/Conf.cpp @@ -570,12 +570,16 @@ namespace sibs bool isProjectNameValid(const string &projectName) { + if(projectName.empty()) + return false; + for(int i = 0; i < projectName.size(); ++i) { char c = projectName[i]; if(!isalpha(c) && !isdigit(c) && c != '-' && c != '_') return false; } + return true; } diff --git a/src/FileUtil.cpp b/src/FileUtil.cpp index 53c1132..aca2778 100644 --- a/src/FileUtil.cpp +++ b/src/FileUtil.cpp @@ -229,6 +229,18 @@ namespace sibs fclose(file); return Result::Ok(StringView(result, fileSize)); } + + Result fileWrite(const _tinydir_char_t *filepath, StringView data) + { + if(getFileType(filepath) != FileType::FILE_NOT_FOUND) + { + string errMsg = "Failed to write to file: "; + errMsg += toUtf8(filepath); + errMsg += "; reason: file already exists"; + return Result::Err(errMsg); + } + return fileOverwrite(filepath, data); + } Result fileOverwrite(const _tinydir_char_t *filepath, StringView data) { diff --git a/src/main.cpp b/src/main.cpp index a3f09a8..aabf83e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -90,8 +90,10 @@ using namespace std::chrono; // TODO: Add program command for generating compile_commands.json without compiling code, without using Ninja #if OS_FAMILY == OS_FAMILY_POSIX +#define fout std::cout #define ferr std::cerr #else +#define fout std::wcout #define ferr std::wcerr #endif @@ -111,7 +113,7 @@ void usageBuild() printf("Usage: sibs build [project_path] [--debug|--release] [--sanitize]\n\n"); printf("Build a sibs project\n\n"); printf("Options:\n"); - printf(" project_path\t\tThe directory containing a project.conf file - Optional (default: current working directory)\n"); + printf(" project_path\t\tThe directory containing a project.conf file - Optional (default: current directory)\n"); printf(" --debug|--release\t\tOptimization level to build project and dependencies with (if not a system package) - Optional (default: --debug)\n"); printf(" --sanitize\t\tAdd runtime address/undefined behavior sanitization. Program can be up to 3 times slower and use 10 times as much RAM. Ignored if compiler doesn't support sanitization - Optional (default: disabled)\n"); printf("Examples:\n"); @@ -142,7 +144,7 @@ void usageTest() printf("Usage: sibs test [project_path] [--no-sanitize]\n\n"); printf("Build and run tests for a sibs project\n\n"); printf("Options:\n"); - printf(" project_path\t\tThe directory containing a project.conf file - Optional (default: current working directory)\n"); + printf(" project_path\t\tThe directory containing a project.conf file - Optional (default: current directory)\n"); printf(" --no-sanitize\t\tDisable runtime address/undefined behavior - Optional\n"); printf("Examples:\n"); printf(" sibs test\n"); @@ -150,6 +152,21 @@ void usageTest() exit(1); } +void usageInit() +{ + printf("Usage: sibs init [project_path] <--exec|--static|--dynamic>\n\n"); + printf("Create sibs project structure in an existing directory\n\n"); + printf("Options:\n"); + printf(" project_path\t\tThe directory where you want to initialize sibs project - Optional (default: current directory)\n"); + printf(" --exec\t\t\tProject compiles to an executable\n"); + printf(" --static\t\t\tProject compiles to a static library\n"); + printf(" --dynamic\t\t\tProject compiles to a dynamic library\n"); + printf("Examples:\n"); + printf(" sibs init --exec\n"); + printf(" sibs init dirA/dirB --dynamic"); + exit(1); +} + void validateDirectoryPath(const _tinydir_char_t *projectPath) { FileType projectPathFileType = getFileType(projectPath); @@ -369,7 +386,7 @@ int buildProject(int argc, const _tinydir_char_t **argv) int testProject(int argc, const _tinydir_char_t **argv) { - if(argc > 1) + if(argc > 2) usageTest(); FileString projectPath; @@ -431,37 +448,18 @@ int testProject(int argc, const _tinydir_char_t **argv) return buildProject(projectPath, projectConfFilePath, sibsConfig); } -void newProjectCreateMainDir(const FileString &projectPath) +// Returns nullptr if @charToFind is not found +const _tinydir_char_t* findLastOf(const _tinydir_char_t *str, const int strSize, const char charToFind) { - Result createProjectDirResult = createDirectoryRecursive(projectPath.c_str()); - if(createProjectDirResult.isErr()) + for(int i = strSize; i >= 0; --i) { - ferr << "Failed to create project main directory: " << toFileString(createProjectDirResult.getErrMsg()) << endl; - exit(20); + if(str[i] == charToFind) + return str + i; } + return nullptr; } -void createProjectSubDir(const FileString &dir) -{ - Result createProjectDirResult = createDirectoryRecursive(dir.c_str()); - if(createProjectDirResult.isErr()) - { - ferr << "Failed to create directory in project: " << toFileString(createProjectDirResult.getErrMsg()) << endl; - exit(20); - } -} - -void createProjectFile(const FileString &projectFilePath, const string &fileContent) -{ - Result fileOverwriteResult = fileOverwrite(projectFilePath.c_str(), fileContent.c_str()); - if(fileOverwriteResult.isErr()) - { - ferr << "Failed to create project file: " << toFileString(fileOverwriteResult.getErrMsg()) << endl; - exit(20); - } -} - -void newProjectCreateConf(const string &projectName, const string &projectType, const FileString &projectPath) +Result newProjectCreateConf(const string &projectName, const string &projectType, const FileString &projectPath) { string projectConfStr = "[package]\n"; projectConfStr += "name = \"" + projectName + "\"\n"; @@ -476,10 +474,20 @@ void newProjectCreateConf(const string &projectName, const string &projectType, FileString projectConfPath = projectPath; projectConfPath += TINYDIR_STRING("/project.conf"); - Result fileOverwriteResult = fileOverwrite(projectConfPath.c_str(), projectConfStr.c_str()); + return fileWrite(projectConfPath.c_str(), projectConfStr.c_str()); +} + +Result createDirectoryRecursive(const FileString &dir) +{ + return createDirectoryRecursive(dir.c_str()); +} + +void createProjectFile(const FileString &projectFilePath, const string &fileContent) +{ + Result fileOverwriteResult = fileOverwrite(projectFilePath.c_str(), fileContent.c_str()); if(fileOverwriteResult.isErr()) { - ferr << "Failed to create project.conf: " << toFileString(fileOverwriteResult.getErrMsg()) << endl; + ferr << "Failed to create project file: " << toFileString(fileOverwriteResult.getErrMsg()) << endl; exit(20); } } @@ -494,6 +502,169 @@ Result gitInitProject(const FileString &projectPath) return exec(cmd.c_str()); } +int initProject(int argc, const _tinydir_char_t **argv) +{ + if(argc > 2) + usageInit(); + + FileString projectPath; + const _tinydir_char_t *projectType = nullptr; + + for(int i = 0; i < argc; ++i) + { + const _tinydir_char_t *arg = argv[i]; + if(_tinydir_strcmp(arg, TINYDIR_STRING("--exec")) == 0) + { + if(projectType) + { + ferr << "Error: Project type was defined more than once. First as " << projectType << " then as " << arg << endl; + usageInit(); + } + projectType = arg; + } + else if(_tinydir_strcmp(arg, TINYDIR_STRING("--static")) == 0) + { + if(projectType) + { + ferr << "Error: Project type was defined more than once. First as " << projectType << " then as " << arg << endl; + usageInit(); + } + projectType = arg; + } + else if(_tinydir_strcmp(arg, TINYDIR_STRING("--dynamic")) == 0) + { + if(projectType) + { + ferr << "Error: Project type was defined more than once. First as " << projectType << " then as " << arg << endl; + usageInit(); + } + projectType = arg; + } + else if(_tinydir_strncmp(arg, TINYDIR_STRING("--"), 2) == 0) + { + ferr << "Error: Invalid argument " << arg << endl; + usageInit(); + } + else + { + if(!projectPath.empty()) + { + ferr << "Error: Project path was defined more than once. First defined as " << projectPath << " then as " << arg << endl; + usageInit(); + } + projectPath = arg; + } + } + + if(!projectType) + { + ferr << "Error: Project type not defined, expected to be either --exec, --static or --dynamic" << endl; + usageInit(); + } + + string projectTypeConf; + if(_tinydir_strcmp(projectType, TINYDIR_STRING("--exec")) == 0) + projectTypeConf = "executable"; + else if(_tinydir_strcmp(projectType, TINYDIR_STRING("--static")) == 0) + projectTypeConf = "static"; + else if(_tinydir_strcmp(projectType, TINYDIR_STRING("--dynamic")) == 0) + projectTypeConf = "dynamic"; + else + { + ferr << "Expected project type to be either --exec, --static or --dynamic; was: " << projectType << endl << endl; + usageInit(); + } + + // TODO: If projectPath is not defined and working directory does not contain project.conf, then search every parent directory until one is found + if(projectPath.empty()) + projectPath = TINYDIR_STRING("."); + + validateDirectoryPath(projectPath.c_str()); + if(projectPath.back() != '/') + projectPath += TINYDIR_STRING("/"); + + Result projectRealPathResult = getRealPath(projectPath.c_str()); + if(!projectRealPathResult) + { + ferr << "Failed to get real path for: '" << projectPath.c_str() << "': " << toFileString(projectRealPathResult.getErrMsg()) << endl; + exit(40); + } + projectPath = projectRealPathResult.unwrap(); + + FileType projectFileType = getFileType(projectPath.c_str()); + if(projectFileType == FileType::FILE_NOT_FOUND) + { + ferr << "Directory not found: '" << projectPath << "', unable to initialize project" << endl; + exit(20); + } + else if(projectFileType == FileType::REGULAR) + { + ferr << "Expected project path : '" << projectPath << "' to be a directory, was a file. Unable to initialize project" << endl; + exit(21); + } + + const _tinydir_char_t *projectNameForwardSlash = findLastOf(projectPath.c_str(), projectPath.size(), '/'); + const _tinydir_char_t *projectNameBackwardSlash = findLastOf(projectPath.c_str(), projectPath.size(), '\\'); + string projectName; + if(projectNameForwardSlash && projectNameBackwardSlash) + { + if(projectNameForwardSlash > projectNameBackwardSlash) + projectName = toUtf8(projectNameForwardSlash + 1); + else + projectName = toUtf8(projectNameBackwardSlash + 1); + } + else if(!projectNameForwardSlash && projectNameBackwardSlash) + projectName = toUtf8(projectNameBackwardSlash + 1); + else if(!projectNameBackwardSlash && projectNameForwardSlash) + projectName = toUtf8(projectNameForwardSlash + 1); + else + projectName = toUtf8(projectPath); + + if(!isProjectNameValid(projectName)) + { + ferr << "Project name can only contain alphanumerical characters, dash (-) or underscore (_) and has to be longer than 0 characters" << endl; + exit(20); + } + + auto createProjectConfResult = newProjectCreateConf(projectName, projectTypeConf, projectPath); + if(!createProjectConfResult) + { + ferr << "A project already exists in the directory " << projectPath << ". Error: failed to create project.conf, reason: " << createProjectConfResult.getErrMsg() << endl; + exit(20); + } + createDirectoryRecursive(projectPath + TINYDIR_STRING("/src")); + createDirectoryRecursive(projectPath + TINYDIR_STRING("/include")); + createDirectoryRecursive(projectPath + TINYDIR_STRING("/tests")); + if(projectTypeConf == "executable") + { + auto mainFilePath = projectPath + TINYDIR_STRING("/src/main.cpp"); + Result fileOverwriteResult = fileWrite(mainFilePath.c_str(), "#include \n\nint main(int argc, char **argv)\n{\n printf(\"hello, world!\\n\");\n return 0;\n}\n"); + if(!fileOverwriteResult) + fout << "Warning: Failed to create project file: " << toFileString(fileOverwriteResult.getErrMsg()) << endl; + } + gitInitProject(projectPath); + return 0; +} + +void newProjectCreateMainDir(const FileString &projectPath) +{ + Result createProjectDirResult = createDirectoryRecursive(projectPath.c_str()); + if(createProjectDirResult.isErr()) + { + ferr << "Failed to create project main directory: " << toFileString(createProjectDirResult.getErrMsg()) << endl; + exit(20); + } +} + +void checkFailCreateSubDir(Result createSubDirResult) +{ + if(!createSubDirResult) + { + ferr << "Failed to create directory in project: " << toFileString(createSubDirResult.getErrMsg()) << endl; + exit(20); + } +} + int newProject(int argc, const _tinydir_char_t **argv) { if(argc != 2) @@ -542,12 +713,25 @@ int newProject(int argc, const _tinydir_char_t **argv) } newProjectCreateMainDir(projectPath); - newProjectCreateConf(projectName, projectTypeConf, projectPath); - createProjectSubDir(projectPath + TINYDIR_STRING("/src")); - createProjectSubDir(projectPath + TINYDIR_STRING("/include")); - createProjectSubDir(projectPath + TINYDIR_STRING("/tests")); + auto createProjectConfResult = newProjectCreateConf(projectName, projectTypeConf, projectPath); + if(!createProjectConfResult) + { + ferr << "Failed to create project.conf: " << toFileString(createProjectConfResult.getErrMsg()) << endl; + exit(20); + } + createDirectoryRecursive(projectPath + TINYDIR_STRING("/src")); + createDirectoryRecursive(projectPath + TINYDIR_STRING("/include")); + createDirectoryRecursive(projectPath + TINYDIR_STRING("/tests")); if(projectTypeConf == "executable") - createProjectFile(projectPath + TINYDIR_STRING("/src/main.cpp"), "#include \n\nint main(int argc, char **argv)\n{\n printf(\"hello, world!\\n\");\n return 0;\n}\n"); + { + auto mainFilePath = projectPath + TINYDIR_STRING("/src/main.cpp"); + Result fileOverwriteResult = fileWrite(mainFilePath.c_str(), "#include \n\nint main(int argc, char **argv)\n{\n printf(\"hello, world!\\n\");\n return 0;\n}\n"); + if(!fileOverwriteResult) + { + ferr << "Failed to create project file: " << toFileString(fileOverwriteResult.getErrMsg()) << endl; + exit(20); + } + } // We are ignoring git init result on purpose. If it fails, just ignore it; not important gitInitProject(projectPath); return 0; @@ -579,6 +763,10 @@ int wmain(int argc, const _tinydir_char_t **argv) { return testProject(subCommandArgCount, subCommandArgPtr); } + else if(_tinydir_strcmp(arg, TINYDIR_STRING("init")) == 0) + { + return initProject(subCommandArgCount, subCommandArgPtr); + } else { ferr << "Expected command to be either 'build', 'new' or 'test', was: " << arg << endl << endl; -- cgit v1.2.3