Initial commit
This commit is contained in:
11
.clang-format
Normal file
11
.clang-format
Normal file
@@ -0,0 +1,11 @@
|
||||
AlignTrailingComments: true
|
||||
BasedOnStyle: WebKit
|
||||
BreakConstructorInitializersBeforeComma: true
|
||||
ColumnLimit: 80
|
||||
Cpp11BracedListStyle: true
|
||||
IndentCaseLabels: true
|
||||
NamespaceIndentation: None
|
||||
PointerBindsToType: false
|
||||
Standard: Cpp11
|
||||
BreakBeforeBinaryOperators: false
|
||||
BreakBeforeBraces: Stroustrup
|
||||
58
.clanguml-class.example
Normal file
58
.clanguml-class.example
Normal file
@@ -0,0 +1,58 @@
|
||||
glob:
|
||||
- src/config/*.cc
|
||||
compilation_database_dir: build
|
||||
output_directory: puml
|
||||
containers:
|
||||
vector:
|
||||
- std::vector
|
||||
map:
|
||||
- std::map
|
||||
diagrams:
|
||||
config_class_diagram:
|
||||
type: class
|
||||
glob:
|
||||
- src/config/*.cc
|
||||
- src/config/*.h
|
||||
using_namespace: clanguml::config
|
||||
namespaces:
|
||||
- clanguml::config
|
||||
classes:
|
||||
- config
|
||||
- diagram
|
||||
- class_diagram
|
||||
methods:
|
||||
- public
|
||||
- protected
|
||||
- private
|
||||
members:
|
||||
- public
|
||||
puml:
|
||||
- 'note top of diagram: Aggregate template'
|
||||
class_diagram_model_class_diagram:
|
||||
type: class
|
||||
glob:
|
||||
- src/uml/class_diagram_model.h
|
||||
using_namespace: clanguml::model::class_diagram
|
||||
namespaces:
|
||||
- clanguml::model::class_diagram
|
||||
classes:
|
||||
- diagram
|
||||
- element
|
||||
- class_member
|
||||
- class_element
|
||||
- method_argument
|
||||
- class_method
|
||||
- class_parent
|
||||
- class_relationship
|
||||
- class_
|
||||
- enum_
|
||||
- scope_t
|
||||
- relationship_t
|
||||
methods:
|
||||
- public
|
||||
- protected
|
||||
- private
|
||||
members:
|
||||
- public
|
||||
puml:
|
||||
- 'note top of diagram: Aggregate template'
|
||||
61
.clanguml.example
Normal file
61
.clanguml.example
Normal file
@@ -0,0 +1,61 @@
|
||||
glob:
|
||||
- src/*.cc
|
||||
compilation_database_dir: build
|
||||
diagrams:
|
||||
communication:
|
||||
type: class
|
||||
glob:
|
||||
- src/communication/*.cc
|
||||
- src/communication/codec/*.cc
|
||||
- src/communication/layers/*.cc
|
||||
using_namespace: one::communication
|
||||
namespaces:
|
||||
- one::communication
|
||||
- one::communication::layers
|
||||
- one::communication::codec
|
||||
classes:
|
||||
- ConnectionPool
|
||||
- Communicator
|
||||
- layers::AsyncResponder
|
||||
- layers::BinaryTranslator
|
||||
- layers::Inbox
|
||||
- layers::Replier
|
||||
- layers::Retrier
|
||||
- layers::Sequencer
|
||||
- layers::Translator
|
||||
- codec::PacketLogger
|
||||
- codec::PacketEncoder
|
||||
- codec::PacketDecoder
|
||||
methods:
|
||||
- public
|
||||
- private
|
||||
members:
|
||||
- public
|
||||
puml:
|
||||
- 'note top of Communicator : Aggregate template'
|
||||
http_helper:
|
||||
type: class
|
||||
glob:
|
||||
- src/httpHelper.cc
|
||||
- src/httpHelperParams.cc
|
||||
classes:
|
||||
- FsLogic
|
||||
args:
|
||||
- '-I./src'
|
||||
- '-I./include'
|
||||
storage_helper:
|
||||
type: class
|
||||
classes:
|
||||
- FileHandle
|
||||
- StorageHelper
|
||||
- KeyValueAdapter
|
||||
- KeyValueHelper
|
||||
storage_detection:
|
||||
type: sequence
|
||||
begin:
|
||||
class: aaa
|
||||
method: bbb
|
||||
end:
|
||||
class: ccc
|
||||
method: ddd
|
||||
|
||||
17
.github/workflows/build.yml
vendored
Normal file
17
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
|
||||
name: build
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build-ubuntu:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: build and run
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release
|
||||
make -j
|
||||
ctest
|
||||
17
.gitignore
vendored
Normal file
17
.gitignore
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
CMakeLists.txt.user
|
||||
CMakeCache.txt
|
||||
CMakeFiles
|
||||
CMakeScripts
|
||||
Testing
|
||||
Makefile
|
||||
cmake_install.cmake
|
||||
install_manifest.txt
|
||||
compile_commands.json
|
||||
CTestTestfile.cmake
|
||||
_deps
|
||||
build/
|
||||
lib/
|
||||
bin/
|
||||
*.swp
|
||||
|
||||
puml
|
||||
56
CMakeLists.txt
Normal file
56
CMakeLists.txt
Normal file
@@ -0,0 +1,56 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
project(clang-uml)
|
||||
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
set(CMAKE_INSTALL_PREFIX ${PROJECT_SOURCE_DIR})
|
||||
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/")
|
||||
|
||||
set(CLANG_UML_INSTALL_INCLUDE_DIR ${PROJECT_SOURCE_DIR}/include)
|
||||
set(CLANG_UML_INSTALL_BIN_DIR ${PROJECT_SOURCE_DIR}/bin)
|
||||
|
||||
set(UML_HEADERS_DIR ${PROJECT_SOURCE_DIR}/src/uml)
|
||||
|
||||
message(STATUS "Checking for spdlog...")
|
||||
find_package(spdlog REQUIRED)
|
||||
|
||||
message(STATUS "Checking for yaml-cpp...")
|
||||
find_package(yaml-cpp REQUIRED)
|
||||
|
||||
message(STATUS "Checking for libclang...")
|
||||
find_package(LibClang REQUIRED)
|
||||
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -O3 ${LIBCLANG_CXXFLAGS}")
|
||||
|
||||
# Thirdparty sources
|
||||
set(THIRDPARTY_HEADERS_DIR ${PROJECT_SOURCE_DIR}/thirdparty/)
|
||||
|
||||
find_package(LLVM REQUIRED CONFIG)
|
||||
set(CLANG_INCLUDE_DIRS "llvm/clang/include")
|
||||
set(CLANG_LIBS clang)
|
||||
|
||||
include_directories(${CLANG_UML_INSTALL_INCLUDE_DIR})
|
||||
include_directories(${YAML_CPP_INCLUDE_DIR})
|
||||
include_directories(${UML_HEADERS_DIR})
|
||||
include_directories(${THIRDPARTY_HEADERS_DIR})
|
||||
include_directories(${PROJECT_SOURCE_DIR}/src/)
|
||||
|
||||
add_subdirectory(tests)
|
||||
|
||||
file(GLOB_RECURSE SOURCES src/*.cc include/*.h)
|
||||
set(MAIN_SOURCE_FILE ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cc)
|
||||
list(REMOVE_ITEM SOURCES ${MAIN_SOURCE_FILE})
|
||||
|
||||
add_executable(clang-uml ${SOURCES} ${MAIN_SOURCE_FILE})
|
||||
install(TARGETS clang-uml DESTINATION ${CLANG_UML_INSTALL_BIN_DIR})
|
||||
target_link_libraries(clang-uml ${LIBCLANG_LIBRARIES} ${YAML_CPP_LIBRARIES} spdlog::spdlog)
|
||||
|
||||
install(
|
||||
FILES
|
||||
# add include after DESTINATION, then it works
|
||||
DESTINATION include ${CMAKE_INSTALL_INCLUDEDIR}
|
||||
)
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Bartek Kryza <bkryza@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
25
README.md
Normal file
25
README.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# clang-uml - Clang based UML generator 
|
||||
|
||||
|
||||
### Compiling, installing and running your code
|
||||
To compile the project, run the following in the project root:
|
||||
```
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
make
|
||||
```
|
||||
To install the project in `/usr/local/`, run the following in the `build/`
|
||||
directory created above:
|
||||
```
|
||||
make install
|
||||
```
|
||||
To run unit tests via CTest, again run the following in the `build/` directory:
|
||||
```
|
||||
make test
|
||||
```
|
||||
or
|
||||
```
|
||||
ctest
|
||||
```
|
||||
|
||||
139
cmake/FindLibClang.cmake
Normal file
139
cmake/FindLibClang.cmake
Normal file
@@ -0,0 +1,139 @@
|
||||
# FindLibClang
|
||||
#
|
||||
# This module searches libclang and llvm-config, the llvm-config tool is used to
|
||||
# get information about the installed llvm/clang package to compile LLVM based
|
||||
# programs.
|
||||
#
|
||||
# It defines the following variables
|
||||
#
|
||||
# ``LIBCLANG_LLVM_CONFIG_EXECUTABLE``
|
||||
# the llvm-config tool to get various information.
|
||||
# ``LIBCLANG_LIBRARIES``
|
||||
# the clang libraries to link against to use Clang/LLVM.
|
||||
# ``LIBCLANG_LIBDIR``
|
||||
# the directory where the clang libraries are located.
|
||||
# ``LIBCLANG_FOUND``
|
||||
# true if libclang was found
|
||||
# ``LIBCLANG_VERSION_STRING``
|
||||
# version number as a string
|
||||
# ``LIBCLANG_CXXFLAGS``
|
||||
# the compiler flags for files that include LLVM headers
|
||||
#
|
||||
#=============================================================================
|
||||
# Copyright (C) 2011, 2012, 2013 Jan Erik Hanssen and Anders Bakken
|
||||
# Copyright (C) 2015 Christian Schwarzgruber <c.schwarzgruber.cs@gmail.com>
|
||||
#
|
||||
# This file is part of RTags (https://github.com/Andersbakken/rtags).
|
||||
#
|
||||
# RTags is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# RTags is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with RTags. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
if (NOT LIBCLANG_ROOT_DIR)
|
||||
set(LIBCLANG_ROOT_DIR $ENV{LIBCLANG_ROOT_DIR})
|
||||
endif ()
|
||||
|
||||
if (NOT LIBCLANG_LLVM_CONFIG_EXECUTABLE)
|
||||
set(LIBCLANG_LLVM_CONFIG_EXECUTABLE $ENV{LIBCLANG_LLVM_CONFIG_EXECUTABLE})
|
||||
if (NOT LIBCLANG_LLVM_CONFIG_EXECUTABLE)
|
||||
find_program(LIBCLANG_LLVM_CONFIG_EXECUTABLE "llvm-config")
|
||||
endif ()
|
||||
if (NOT LIBCLANG_LLVM_CONFIG_EXECUTABLE)
|
||||
if (APPLE)
|
||||
execute_process(COMMAND brew --prefix llvm OUTPUT_VARIABLE BREW_LLVM_PATH RESULT_VARIABLE BREW_LLVM_RESULT)
|
||||
if (NOT ${BREW_LLVM_RESULT} EQUAL 0)
|
||||
set(BREW_LLVM_PATH "/usr/local/opt/llvm")
|
||||
endif ()
|
||||
string(STRIP ${BREW_LLVM_PATH} BREW_LLVM_PATH)
|
||||
find_program(LIBCLANG_LLVM_CONFIG_EXECUTABLE NAMES llvm-config PATHS "${BREW_LLVM_PATH}/bin")
|
||||
else ()
|
||||
set(llvm_config_names llvm-config)
|
||||
foreach(major RANGE 11 3)
|
||||
list(APPEND llvm_config_names "llvm-config${major}" "llvm-config-${major}")
|
||||
foreach(minor RANGE 9 0)
|
||||
list(APPEND llvm_config_names "llvm-config${major}${minor}" "llvm-config-${major}.${minor}" "llvm-config-mp-${major}.${minor}")
|
||||
endforeach ()
|
||||
endforeach ()
|
||||
find_program(LIBCLANG_LLVM_CONFIG_EXECUTABLE NAMES ${llvm_config_names})
|
||||
endif ()
|
||||
endif ()
|
||||
if (LIBCLANG_LLVM_CONFIG_EXECUTABLE)
|
||||
message(STATUS "llvm-config executable found: ${LIBCLANG_LLVM_CONFIG_EXECUTABLE}")
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
if (NOT LIBCLANG_CXXFLAGS)
|
||||
if (NOT LIBCLANG_LLVM_CONFIG_EXECUTABLE)
|
||||
message(FATAL_ERROR "Could NOT find llvm-config executable and LIBCLANG_CXXFLAGS is not set ")
|
||||
endif ()
|
||||
execute_process(COMMAND ${LIBCLANG_LLVM_CONFIG_EXECUTABLE} --cxxflags OUTPUT_VARIABLE LIBCLANG_CXXFLAGS OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
if (NOT LIBCLANG_CXXFLAGS)
|
||||
find_path(LIBCLANG_CXXFLAGS_HACK_CMAKECACHE_DOT_TEXT_BULLSHIT clang-c/Index.h HINTS ${LIBCLANG_ROOT_DIR}/include NO_DEFAULT_PATH)
|
||||
if (NOT EXISTS ${LIBCLANG_CXXFLAGS_HACK_CMAKECACHE_DOT_TEXT_BULLSHIT})
|
||||
find_path(LIBCLANG_CXXFLAGS clang-c/Index.h)
|
||||
if (NOT EXISTS ${LIBCLANG_CXXFLAGS})
|
||||
message(FATAL_ERROR "Could NOT find clang include path. You can fix this by setting LIBCLANG_CXXFLAGS in your shell or as a cmake variable.")
|
||||
endif ()
|
||||
else ()
|
||||
set(LIBCLANG_CXXFLAGS ${LIBCLANG_CXXFLAGS_HACK_CMAKECACHE_DOT_TEXT_BULLSHIT})
|
||||
endif ()
|
||||
set(LIBCLANG_CXXFLAGS "-I${LIBCLANG_CXXFLAGS}")
|
||||
endif ()
|
||||
string(REGEX MATCHALL "-(D__?[a-zA-Z_]*|I([^\" ]+|\"[^\"]+\"))" LIBCLANG_CXXFLAGS "${LIBCLANG_CXXFLAGS}")
|
||||
string(REGEX REPLACE ";" " " LIBCLANG_CXXFLAGS "${LIBCLANG_CXXFLAGS}")
|
||||
set(LIBCLANG_CXXFLAGS ${LIBCLANG_CXXFLAGS} CACHE STRING "The LLVM C++ compiler flags needed to compile LLVM based applications.")
|
||||
unset(LIBCLANG_CXXFLAGS_HACK_CMAKECACHE_DOT_TEXT_BULLSHIT CACHE)
|
||||
endif ()
|
||||
|
||||
if (NOT EXISTS ${LIBCLANG_LIBDIR})
|
||||
if (NOT LIBCLANG_LLVM_CONFIG_EXECUTABLE)
|
||||
message(FATAL_ERROR "Could NOT find llvm-config executable and LIBCLANG_LIBDIR is not set ")
|
||||
endif ()
|
||||
execute_process(COMMAND ${LIBCLANG_LLVM_CONFIG_EXECUTABLE} --libdir OUTPUT_VARIABLE LIBCLANG_LIBDIR OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
if (NOT EXISTS ${LIBCLANG_LIBDIR})
|
||||
message(FATAL_ERROR "Could NOT find clang libdir. You can fix this by setting LIBCLANG_LIBDIR in your shell or as a cmake variable.")
|
||||
endif ()
|
||||
set(LIBCLANG_LIBDIR ${LIBCLANG_LIBDIR} CACHE STRING "Path to the clang library.")
|
||||
endif ()
|
||||
|
||||
if (NOT LIBCLANG_LIBRARIES)
|
||||
find_library(LIBCLANG_LIB_HACK_CMAKECACHE_DOT_TEXT_BULLSHIT NAMES clang libclang HINTS ${LIBCLANG_LIBDIR} ${LIBCLANG_ROOT_DIR}/lib NO_DEFAULT_PATH)
|
||||
if (LIBCLANG_LIB_HACK_CMAKECACHE_DOT_TEXT_BULLSHIT)
|
||||
set(LIBCLANG_LIBRARIES "${LIBCLANG_LIB_HACK_CMAKECACHE_DOT_TEXT_BULLSHIT}")
|
||||
else ()
|
||||
find_library(LIBCLANG_LIBRARIES NAMES clang libclang)
|
||||
if (NOT EXISTS ${LIBCLANG_LIBRARIES})
|
||||
set (LIBCLANG_LIBRARIES "-L${LIBCLANG_LIBDIR}" "-lclang" "-Wl,-rpath,${LIBCLANG_LIBDIR}")
|
||||
endif ()
|
||||
endif ()
|
||||
unset(LIBCLANG_LIB_HACK_CMAKECACHE_DOT_TEXT_BULLSHIT CACHE)
|
||||
endif ()
|
||||
set(LIBCLANG_LIBRARY ${LIBCLANG_LIBRARIES} CACHE FILEPATH "Path to the libclang library")
|
||||
|
||||
if (NOT LIBCLANG_SYSTEM_LIBS)
|
||||
execute_process(COMMAND ${LIBCLANG_LLVM_CONFIG_EXECUTABLE} --system-libs OUTPUT_VARIABLE LIBCLANG_SYSTEM_LIBS OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
if (LIBCLANG_SYSTEM_LIBS)
|
||||
set (LIBCLANG_LIBRARIES ${LIBCLANG_LIBRARIES} ${LIBCLANG_SYSTEM_LIBS})
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
if (LIBCLANG_LLVM_CONFIG_EXECUTABLE)
|
||||
execute_process(COMMAND ${LIBCLANG_LLVM_CONFIG_EXECUTABLE} --version OUTPUT_VARIABLE LIBCLANG_VERSION_STRING OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
else ()
|
||||
set(LIBCLANG_VERSION_STRING "Unknown")
|
||||
endif ()
|
||||
message("-- Using Clang version ${LIBCLANG_VERSION_STRING} from ${LIBCLANG_LIBDIR} with CXXFLAGS ${LIBCLANG_CXXFLAGS}")
|
||||
|
||||
# Handly the QUIETLY and REQUIRED arguments and set LIBCLANG_FOUND to TRUE if all listed variables are TRUE
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(LibClang DEFAULT_MSG LIBCLANG_LIBRARY LIBCLANG_CXXFLAGS LIBCLANG_LIBDIR)
|
||||
mark_as_advanced(LIBCLANG_CXXFLAGS LIBCLANG_LIBRARY LIBCLANG_LLVM_CONFIG_EXECUTABLE LIBCLANG_LIBDIR)
|
||||
16
include/CMakeLists.txt
Normal file
16
include/CMakeLists.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
# Make an explicit list of all source files in CLANGUML_INC. This is important
|
||||
# because CMake is not a build system: it is a build system generator. Suppose
|
||||
# you add a file foo.cpp to src/ after running cmake .. . If you set
|
||||
# CLANGUML_INC with `file(GLOB ... )`, this is not passed to the makefile; it
|
||||
# doesn't know that foo.cpp exists and will not re-run cmake. Your
|
||||
# collaborator's builds will fail and it will be unclear why. Whether you use
|
||||
# file(GLOB ...) or not, you will need to re-run cmake, but with an explicit
|
||||
# file list, you know beforehand why your code isn't compiling.
|
||||
set(CLANGUML_INC source_file.hpp)
|
||||
|
||||
# Form the full path to the source files...
|
||||
PREPEND(CLANGUML_INC)
|
||||
|
||||
# ... and pass the variable to the parent scope.
|
||||
set(CLANGUML_INC ${CLANGUML_INC} PARENT_SCOPE)
|
||||
7
include/source_file.hpp
Normal file
7
include/source_file.hpp
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
template <class T>
|
||||
T add(T a, T b){
|
||||
return a + b;
|
||||
}
|
||||
6
src/CMakeLists.txt
Normal file
6
src/CMakeLists.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
set(SOURCE_FILES "
|
||||
config/config.cc
|
||||
")
|
||||
|
||||
|
||||
14
src/config/config.cc
Normal file
14
src/config/config.cc
Normal file
@@ -0,0 +1,14 @@
|
||||
#include "config.h"
|
||||
|
||||
namespace clanguml {
|
||||
namespace config {
|
||||
|
||||
config load(const std::string &config_file)
|
||||
{
|
||||
YAML::Node doc = YAML::LoadFile(config_file);
|
||||
|
||||
return doc.as<config>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
110
src/config/config.h
Normal file
110
src/config/config.h
Normal file
@@ -0,0 +1,110 @@
|
||||
#pragma once
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <yaml-cpp/yaml.h>
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace clanguml {
|
||||
namespace config {
|
||||
|
||||
struct diagram {
|
||||
virtual ~diagram() = default;
|
||||
|
||||
std::string name;
|
||||
std::vector<std::string> glob;
|
||||
std::vector<std::string> puml;
|
||||
std::string using_namespace;
|
||||
};
|
||||
|
||||
enum class class_scopes { public_, protected_, private_ };
|
||||
|
||||
struct class_diagram : public diagram {
|
||||
virtual ~class_diagram() = default;
|
||||
|
||||
std::vector<std::string> classes;
|
||||
std::vector<class_scopes> methods;
|
||||
std::vector<class_scopes> members;
|
||||
|
||||
bool has_class(std::string clazz)
|
||||
{
|
||||
spdlog::debug("CHECKING IF {} IS WHITE LISTED", clazz);
|
||||
for (const auto &c : classes) {
|
||||
std::string prefix{};
|
||||
if (!using_namespace.empty()) {
|
||||
prefix = using_namespace + "::";
|
||||
}
|
||||
if (prefix + c == clazz)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
struct config {
|
||||
// the glob list is additive and relative to the current
|
||||
// directory
|
||||
std::vector<std::string> glob;
|
||||
std::string compilation_database_dir{"."};
|
||||
std::map<std::string, std::unique_ptr<diagram>> diagrams;
|
||||
};
|
||||
|
||||
config load(const std::string &config_file);
|
||||
}
|
||||
}
|
||||
|
||||
namespace YAML {
|
||||
using clanguml::config::class_diagram;
|
||||
using clanguml::config::config;
|
||||
//
|
||||
// class_diagram Yaml decoder
|
||||
//
|
||||
template <> struct convert<class_diagram> {
|
||||
static bool decode(const Node &node, class_diagram &rhs)
|
||||
{
|
||||
rhs.using_namespace = node["using_namespace"].as<std::string>();
|
||||
rhs.glob = node["glob"].as<std::vector<std::string>>();
|
||||
rhs.puml = node["puml"].as<std::vector<std::string>>();
|
||||
rhs.classes = node["classes"].as<std::vector<std::string>>();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// config Yaml decoder
|
||||
//
|
||||
template <> struct convert<config> {
|
||||
static bool decode(const Node &node, config &rhs)
|
||||
{
|
||||
rhs.glob = node["glob"].as<std::vector<std::string>>();
|
||||
if (node["compilation_database_dir"])
|
||||
rhs.compilation_database_dir =
|
||||
node["compilation_database_dir"].as<std::string>();
|
||||
|
||||
auto diagrams = node["diagrams"];
|
||||
|
||||
assert(diagrams.Type() == NodeType::Map);
|
||||
|
||||
for (const auto &d : diagrams) {
|
||||
const auto diagram_type = d.second["type"].as<std::string>();
|
||||
if (diagram_type == "class") {
|
||||
rhs.diagrams[d.first.as<std::string>()] =
|
||||
std::make_unique<class_diagram>(
|
||||
d.second.as<class_diagram>());
|
||||
}
|
||||
else {
|
||||
spdlog::warn(
|
||||
"Diagrams of type {} are not supported at the moment... ",
|
||||
diagram_type);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
108
src/main.cc
Normal file
108
src/main.cc
Normal file
@@ -0,0 +1,108 @@
|
||||
#include "config/config.h"
|
||||
#include "puml/class_diagram_generator.h"
|
||||
#include "uml/class_diagram_model.h"
|
||||
#include "uml/class_diagram_visitor.h"
|
||||
#include "uml/compilation_database.h"
|
||||
|
||||
#include <cli11/CLI11.hpp>
|
||||
#include <glob/glob.hpp>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <limits.h>
|
||||
#include <malloc.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
using clanguml::config::config;
|
||||
using clanguml::cx::compilation_database;
|
||||
|
||||
int main(int argc, const char *argv[])
|
||||
{
|
||||
spdlog::set_pattern("[%l] %v");
|
||||
|
||||
CLI::App app{"Clang-based PlantUML generator from C++ sources"};
|
||||
|
||||
std::string config_path{".clanguml"};
|
||||
std::string compilation_database_dir{'.'};
|
||||
bool verbose{false};
|
||||
|
||||
app.add_option(
|
||||
"-c,--config", config_path, "Location of configuration file");
|
||||
app.add_option("-d,--compile-database", compilation_database_dir,
|
||||
"Location of configuration file");
|
||||
app.add_flag("-v,--verbose", verbose, "Verbose logging");
|
||||
|
||||
CLI11_PARSE(app, argc, argv);
|
||||
|
||||
if (verbose)
|
||||
spdlog::set_level(spdlog::level::debug);
|
||||
|
||||
spdlog::info("Loading clang-uml config from {}", config_path);
|
||||
|
||||
auto config = clanguml::config::load(config_path);
|
||||
|
||||
spdlog::info("Loading compilation database from {} directory",
|
||||
config.compilation_database_dir);
|
||||
|
||||
auto db =
|
||||
compilation_database::from_directory(config.compilation_database_dir);
|
||||
|
||||
for (const auto &[name, diagram] : config.diagrams) {
|
||||
spdlog::info("Generating diagram {}.puml", name);
|
||||
clanguml::model::class_diagram::diagram d;
|
||||
d.name = name;
|
||||
|
||||
// Get all translation units matching the glob from diagram
|
||||
// configuration
|
||||
std::vector<std::filesystem::path> translation_units{};
|
||||
for (const auto &g : diagram->glob) {
|
||||
spdlog::debug("Processing glob: {}", g);
|
||||
const auto matches = glob::glob(g);
|
||||
std::copy(matches.begin(), matches.end(),
|
||||
std::back_inserter(translation_units));
|
||||
}
|
||||
|
||||
// Process all matching translation units
|
||||
for (const auto &tu_path : translation_units) {
|
||||
spdlog::debug("Processing translation unit: {}",
|
||||
std::filesystem::canonical(tu_path).c_str());
|
||||
|
||||
auto tu = db.parse_translation_unit(tu_path);
|
||||
|
||||
auto cursor = clang_getTranslationUnitCursor(tu);
|
||||
|
||||
if (clang_Cursor_isNull(cursor)) {
|
||||
spdlog::debug("CURSOR IS NULL");
|
||||
}
|
||||
|
||||
spdlog::debug("Cursor kind: {}",
|
||||
clang_getCString(clang_getCursorKindSpelling(cursor.kind)));
|
||||
spdlog::debug("Cursor name: {}",
|
||||
clang_getCString(clang_getCursorDisplayName(cursor)));
|
||||
|
||||
clanguml::visitor::class_diagram::tu_context ctx(d);
|
||||
auto res = clang_visitChildren(cursor,
|
||||
clanguml::visitor::class_diagram::translation_unit_visitor,
|
||||
&ctx);
|
||||
|
||||
spdlog::debug("Processing result: {}", res);
|
||||
}
|
||||
|
||||
std::filesystem::path path{"puml/" + d.name + ".puml"};
|
||||
std::ofstream ofs;
|
||||
ofs.open(path, std::ofstream::out | std::ofstream::trunc);
|
||||
|
||||
auto generator = clanguml::generators::class_diagram::puml::generator(
|
||||
dynamic_cast<clanguml::config::class_diagram &>(*diagram), d);
|
||||
|
||||
ofs << generator;
|
||||
|
||||
ofs.close();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
88
src/uml/class_diagram_model.h
Normal file
88
src/uml/class_diagram_model.h
Normal file
@@ -0,0 +1,88 @@
|
||||
#pragma once
|
||||
|
||||
#include <clang-c/CXCompilationDatabase.h>
|
||||
#include <clang-c/Index.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
|
||||
namespace clanguml {
|
||||
namespace model {
|
||||
namespace class_diagram {
|
||||
|
||||
enum class scope_t { kPublic, kProtected, kPrivate };
|
||||
|
||||
enum class relationship_t {
|
||||
kExtension,
|
||||
kComposition,
|
||||
kAggregation,
|
||||
kContainment,
|
||||
kOwnership,
|
||||
kAssociation
|
||||
};
|
||||
|
||||
struct element {
|
||||
std::string name;
|
||||
std::vector<std::string> namespace_;
|
||||
};
|
||||
|
||||
struct class_element {
|
||||
scope_t scope;
|
||||
std::string name;
|
||||
std::string type;
|
||||
};
|
||||
|
||||
struct class_member : public class_element {
|
||||
bool is_relationship{false};
|
||||
};
|
||||
|
||||
struct method_argument {
|
||||
std::string type;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
struct class_method : public class_element {
|
||||
std::vector<method_argument> arguments;
|
||||
};
|
||||
|
||||
struct class_parent {
|
||||
enum class access_t { kPublic, kProtected, kPrivate };
|
||||
std::string name;
|
||||
bool is_virtual{false};
|
||||
access_t access;
|
||||
};
|
||||
|
||||
struct class_relationship {
|
||||
relationship_t type{relationship_t::kAssociation};
|
||||
std::string destination;
|
||||
std::string cardinality_source;
|
||||
std::string cardinality_destination;
|
||||
std::string label;
|
||||
};
|
||||
|
||||
struct class_ : public element {
|
||||
bool is_struct{false};
|
||||
bool is_template{false};
|
||||
std::vector<class_member> members;
|
||||
std::vector<class_method> methods;
|
||||
std::vector<class_parent> bases;
|
||||
std::vector<std::string> inner_classes;
|
||||
|
||||
std::vector<class_relationship> relationships;
|
||||
};
|
||||
|
||||
struct enum_ : public element {
|
||||
std::vector<std::string> constants;
|
||||
};
|
||||
|
||||
struct diagram {
|
||||
std::string name;
|
||||
std::vector<class_> classes;
|
||||
std::vector<enum_> enums;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
353
src/uml/class_diagram_visitor.h
Normal file
353
src/uml/class_diagram_visitor.h
Normal file
@@ -0,0 +1,353 @@
|
||||
#pragma once
|
||||
|
||||
#include "class_diagram_model.h"
|
||||
|
||||
#include <clang-c/CXCompilationDatabase.h>
|
||||
#include <clang-c/Index.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace clanguml {
|
||||
namespace visitor {
|
||||
namespace class_diagram {
|
||||
|
||||
using clanguml::model::class_diagram::class_;
|
||||
using clanguml::model::class_diagram::class_member;
|
||||
using clanguml::model::class_diagram::class_method;
|
||||
using clanguml::model::class_diagram::class_parent;
|
||||
using clanguml::model::class_diagram::class_relationship;
|
||||
using clanguml::model::class_diagram::diagram;
|
||||
using clanguml::model::class_diagram::enum_;
|
||||
using clanguml::model::class_diagram::relationship_t;
|
||||
using clanguml::model::class_diagram::scope_t;
|
||||
|
||||
template <typename T> struct element_visitor_context {
|
||||
element_visitor_context(T &e)
|
||||
: element(e)
|
||||
{
|
||||
}
|
||||
CXCursorKind current_cursor_kind;
|
||||
std::vector<std::string> namespace_;
|
||||
|
||||
T &element;
|
||||
};
|
||||
|
||||
struct class_visitor_context : element_visitor_context<class_> {
|
||||
class_visitor_context(class_ &c)
|
||||
: element_visitor_context<class_>(c)
|
||||
{
|
||||
}
|
||||
scope_t scope;
|
||||
};
|
||||
|
||||
struct tu_context {
|
||||
tu_context(diagram &d)
|
||||
: diagram(d)
|
||||
{
|
||||
}
|
||||
|
||||
std::vector<std::string> namespace_;
|
||||
diagram &diagram;
|
||||
};
|
||||
|
||||
enum CXChildVisitResult visit_if_cursor_valid(
|
||||
CXCursor cursor, std::function<void(CXCursor)> f)
|
||||
{
|
||||
enum CXChildVisitResult ret = CXChildVisit_Break;
|
||||
CXString cursorName = clang_getCursorSpelling(cursor);
|
||||
if (clang_isCursorDefinition(cursor)) {
|
||||
if (*clang_getCString(cursorName)) {
|
||||
f(cursor);
|
||||
ret = CXChildVisit_Continue;
|
||||
}
|
||||
else {
|
||||
ret = CXChildVisit_Recurse;
|
||||
}
|
||||
}
|
||||
else {
|
||||
ret = CXChildVisit_Continue;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static enum CXChildVisitResult enum_visitor(
|
||||
CXCursor cursor, CXCursor parent, CXClientData client_data)
|
||||
{
|
||||
auto e = (struct enum_ *)client_data;
|
||||
|
||||
CXString cursorName = clang_getCursorSpelling(cursor);
|
||||
std::string cursor_name_str = clang_getCString(cursorName);
|
||||
|
||||
spdlog::info(
|
||||
"Visiting enum {}: {} - {}:{}", e->name, cursor_name_str, cursor.kind);
|
||||
|
||||
enum CXChildVisitResult ret = CXChildVisit_Break;
|
||||
switch (cursor.kind) {
|
||||
case CXCursor_EnumConstantDecl:
|
||||
visit_if_cursor_valid(
|
||||
cursor, [e, cursor_name_str](CXCursor cursor) {
|
||||
spdlog::info("Adding enum constant {}::{}", e->name,
|
||||
cursor_name_str);
|
||||
|
||||
e->constants.emplace_back(cursor_name_str);
|
||||
});
|
||||
|
||||
ret = CXChildVisit_Continue;
|
||||
break;
|
||||
default:
|
||||
ret = CXChildVisit_Continue;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static enum CXChildVisitResult class_visitor(
|
||||
CXCursor cursor, CXCursor parent, CXClientData client_data)
|
||||
{
|
||||
auto c = (struct class_ *)client_data;
|
||||
|
||||
CXString cursorName = clang_getCursorSpelling(cursor);
|
||||
std::string cursor_name_str = clang_getCString(cursorName);
|
||||
|
||||
spdlog::info("Visiting {}: {} - {}:{}", c->is_struct ? "struct" : "class",
|
||||
c->name, cursor_name_str, cursor.kind);
|
||||
|
||||
enum CXChildVisitResult ret = CXChildVisit_Break;
|
||||
switch (cursor.kind) {
|
||||
case CXCursor_CXXMethod:
|
||||
case CXCursor_Constructor:
|
||||
case CXCursor_Destructor:
|
||||
case CXCursor_FunctionTemplate: {
|
||||
visit_if_cursor_valid(
|
||||
cursor, [c, cursor_name_str](CXCursor cursor) {
|
||||
class_method m;
|
||||
m.name = cursor_name_str;
|
||||
m.type = clang_getCString(clang_getTypeSpelling(
|
||||
clang_getResultType(clang_getCursorType(cursor))));
|
||||
|
||||
spdlog::info("Adding method {} {}::{}()", m.type, c->name,
|
||||
cursor_name_str);
|
||||
|
||||
c->methods.emplace_back(std::move(m));
|
||||
});
|
||||
ret = CXChildVisit_Continue;
|
||||
break;
|
||||
}
|
||||
case CXCursor_FieldDecl: {
|
||||
visit_if_cursor_valid(cursor, [c, cursor_name_str](CXCursor cursor) {
|
||||
auto t = clang_getCursorType(cursor);
|
||||
class_member m;
|
||||
m.name = cursor_name_str;
|
||||
m.type = clang_getCString(clang_getTypeSpelling(t));
|
||||
|
||||
spdlog::info("Adding member {} {}::{}", m.type, c->name,
|
||||
cursor_name_str);
|
||||
|
||||
relationship_t relationship_type = relationship_t::kOwnership;
|
||||
|
||||
// Parse the field declaration to determine the relationship
|
||||
// type
|
||||
if (!clang_isPODType(t)) {
|
||||
while (true) {
|
||||
if (t.kind == CXType_Pointer) {
|
||||
relationship_type = relationship_t::kAssociation;
|
||||
t = clang_getPointeeType(t);
|
||||
continue;
|
||||
}
|
||||
else if (t.kind == CXType_LValueReference) {
|
||||
relationship_type = relationship_t::kAggregation;
|
||||
t = clang_getPointeeType(t);
|
||||
continue;
|
||||
}
|
||||
else if (t.kind == CXType_RValueReference) {
|
||||
relationship_type = relationship_t::kAssociation;
|
||||
t = clang_getPointeeType(t);
|
||||
continue;
|
||||
}
|
||||
/*else if(t.kind == CXType_Elaborated) {
|
||||
t = clang_Type_getNamedType(t);
|
||||
continue;
|
||||
}*/
|
||||
else /*if (t.kind == CXType_Record) */ {
|
||||
spdlog::error("UNKNOWN CXTYPE: {}", t.kind);
|
||||
class_relationship r;
|
||||
auto template_argument_count =
|
||||
clang_Type_getNumTemplateArguments(t);
|
||||
std::string name{
|
||||
clang_getCString(clang_getTypeSpelling(t))};
|
||||
|
||||
if (template_argument_count > 0) {
|
||||
std::vector<CXType> template_arguments;
|
||||
for (int i = 0; i < template_argument_count;
|
||||
i++) {
|
||||
auto tt =
|
||||
clang_Type_getTemplateArgumentAsType(
|
||||
t, i);
|
||||
template_arguments.push_back(tt);
|
||||
}
|
||||
|
||||
if (name.rfind("vector") == 0 ||
|
||||
name.rfind("std::vector") == 0) {
|
||||
r.type = relationship_t::kAggregation;
|
||||
r.destination =
|
||||
clang_getCString(clang_getTypeSpelling(
|
||||
template_arguments[0]));
|
||||
}
|
||||
if (name.rfind("map") == 0 ||
|
||||
name.rfind("std::map") == 0) {
|
||||
r.type = relationship_t::kAggregation;
|
||||
r.destination =
|
||||
clang_getCString(clang_getTypeSpelling(
|
||||
template_arguments[1]));
|
||||
}
|
||||
r.label = m.name;
|
||||
c->relationships.emplace_back(std::move(r));
|
||||
}
|
||||
else {
|
||||
r.destination = name;
|
||||
r.type = relationship_type;
|
||||
r.label = m.name;
|
||||
c->relationships.emplace_back(std::move(r));
|
||||
}
|
||||
|
||||
spdlog::debug(
|
||||
"Adding relationship to: {}", r.destination);
|
||||
}
|
||||
// else {
|
||||
// spdlog::error("UNKNOWN CXTYPE: {}", t.kind);
|
||||
//}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
c->members.emplace_back(std::move(m));
|
||||
});
|
||||
ret = CXChildVisit_Continue;
|
||||
break;
|
||||
}
|
||||
case CXCursor_CXXBaseSpecifier: {
|
||||
CXCursor ref_cursor = clang_getCursorReferenced(cursor);
|
||||
CXString display_name = clang_getCursorDisplayName(ref_cursor);
|
||||
|
||||
auto base_access = clang_getCXXAccessSpecifier(cursor);
|
||||
|
||||
spdlog::error("Found base specifier: {} - {}", cursor_name_str,
|
||||
clang_getCString(display_name));
|
||||
|
||||
class_parent cp;
|
||||
cp.name = clang_getCString(display_name);
|
||||
cp.is_virtual = false;
|
||||
switch (base_access) {
|
||||
case CX_CXXAccessSpecifier::CX_CXXPrivate:
|
||||
cp.access = class_parent::access_t::kPrivate;
|
||||
break;
|
||||
case CX_CXXAccessSpecifier::CX_CXXPublic:
|
||||
cp.access = class_parent::access_t::kPublic;
|
||||
break;
|
||||
case CX_CXXAccessSpecifier::CX_CXXProtected:
|
||||
cp.access = class_parent::access_t::kProtected;
|
||||
break;
|
||||
default:
|
||||
cp.access = class_parent::access_t::kPublic;
|
||||
}
|
||||
|
||||
c->bases.emplace_back(std::move(cp));
|
||||
|
||||
ret = CXChildVisit_Continue;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ret = CXChildVisit_Continue;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
static enum CXChildVisitResult translation_unit_visitor(
|
||||
CXCursor cursor, CXCursor parent, CXClientData client_data)
|
||||
{
|
||||
if (clang_Location_isFromMainFile(clang_getCursorLocation(cursor)) == 0) {
|
||||
return CXChildVisit_Continue;
|
||||
}
|
||||
|
||||
struct tu_context *ctx = (struct tu_context *)client_data;
|
||||
|
||||
enum CXChildVisitResult ret = CXChildVisit_Break;
|
||||
|
||||
CXString cursorName = clang_getCursorSpelling(cursor);
|
||||
std::string cursor_name_str = clang_getCString(cursorName);
|
||||
|
||||
spdlog::debug("Visiting cursor: {}", cursor_name_str);
|
||||
|
||||
bool is_struct{false};
|
||||
auto scope{scope_t::kPrivate};
|
||||
switch (cursor.kind) {
|
||||
case CXCursor_StructDecl:
|
||||
spdlog::debug("Found struct declaration: {}", cursor_name_str);
|
||||
|
||||
is_struct = true;
|
||||
|
||||
[[fallthrough]];
|
||||
case CXCursor_ClassTemplate:
|
||||
[[fallthrough]];
|
||||
case CXCursor_ClassDecl: {
|
||||
spdlog::debug("Found class or class template declaration: {}",
|
||||
cursor_name_str);
|
||||
|
||||
scope = scope_t::kPublic;
|
||||
|
||||
visit_if_cursor_valid(
|
||||
cursor, [ctx, is_struct, cursor_name_str](CXCursor cursor) {
|
||||
class_ c{};
|
||||
c.is_struct = is_struct;
|
||||
c.name = cursor_name_str;
|
||||
c.namespace_ = ctx->namespace_;
|
||||
|
||||
clang_visitChildren(cursor, class_visitor, &c);
|
||||
|
||||
ctx->diagram.classes.emplace_back(std::move(c));
|
||||
});
|
||||
|
||||
ret = CXChildVisit_Continue;
|
||||
break;
|
||||
}
|
||||
case CXCursor_EnumDecl: {
|
||||
spdlog::debug("Found enum declaration: {}", cursor_name_str);
|
||||
|
||||
visit_if_cursor_valid(
|
||||
cursor, [ctx, is_struct, cursor_name_str](CXCursor cursor) {
|
||||
enum_ e{};
|
||||
e.name = cursor_name_str;
|
||||
e.namespace_ = ctx->namespace_;
|
||||
|
||||
clang_visitChildren(cursor, enum_visitor, &e);
|
||||
|
||||
ctx->diagram.enums.emplace_back(std::move(e));
|
||||
});
|
||||
ret = CXChildVisit_Continue;
|
||||
|
||||
break;
|
||||
}
|
||||
case CXCursor_Namespace: {
|
||||
spdlog::debug("Found namespace specifier: {}", cursor_name_str);
|
||||
|
||||
ret = CXChildVisit_Recurse;
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
spdlog::debug("Found cursor: {}", cursor_name_str);
|
||||
|
||||
ret = CXChildVisit_Recurse;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
88
src/uml/compilation_database.cc
Normal file
88
src/uml/compilation_database.cc
Normal file
@@ -0,0 +1,88 @@
|
||||
#include "compilation_database.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <spdlog/fmt/fmt.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace clanguml {
|
||||
namespace cx {
|
||||
compilation_database::compilation_database(CXCompilationDatabase &&d)
|
||||
: m_database{std::move(d)}
|
||||
, m_index{clang_createIndex(0, 1)}
|
||||
{
|
||||
}
|
||||
|
||||
compilation_database::~compilation_database()
|
||||
{
|
||||
clang_CompilationDatabase_dispose(m_database);
|
||||
}
|
||||
|
||||
compilation_database compilation_database::from_directory(
|
||||
const std::string &dir)
|
||||
{
|
||||
CXCompilationDatabase_Error error;
|
||||
auto path = std::filesystem::path{dir};
|
||||
CXCompilationDatabase cdb =
|
||||
clang_CompilationDatabase_fromDirectory(path.c_str(), &error);
|
||||
|
||||
if (error != CXCompilationDatabase_Error::CXCompilationDatabase_NoError) {
|
||||
throw std::runtime_error(fmt::format(
|
||||
"Cannot load compilation database database from: {}", dir));
|
||||
}
|
||||
|
||||
return compilation_database{std::move(cdb)};
|
||||
}
|
||||
|
||||
CXTranslationUnit compilation_database::parse_translation_unit(
|
||||
const std::string &path)
|
||||
{
|
||||
const auto p = std::filesystem::canonical(path);
|
||||
|
||||
CXCompileCommands compile_commands =
|
||||
clang_CompilationDatabase_getCompileCommands(m_database, p.c_str());
|
||||
|
||||
unsigned int compile_commands_count =
|
||||
clang_CompileCommands_getSize(compile_commands);
|
||||
|
||||
int i;
|
||||
// for (i = 0; i < compile_commands_count; i++) {
|
||||
CXCompileCommand compile_command =
|
||||
clang_CompileCommands_getCommand(compile_commands, 0);
|
||||
|
||||
auto cc_filename = clang_CompileCommand_getFilename(compile_command);
|
||||
spdlog::debug(
|
||||
"Processing compile command file: {}", clang_getCString(cc_filename));
|
||||
|
||||
auto num_args = clang_CompileCommand_getNumArgs(compile_command);
|
||||
|
||||
char **arguments = NULL;
|
||||
if (num_args) {
|
||||
int j;
|
||||
arguments = (char **)malloc(sizeof(char *) * num_args);
|
||||
for (j = 0; j < num_args; ++j) {
|
||||
CXString arg = clang_CompileCommand_getArg(compile_command, j);
|
||||
spdlog::debug("Processing argument: {}", clang_getCString(arg));
|
||||
arguments[j] = strdup(clang_getCString(arg));
|
||||
clang_disposeString(arg);
|
||||
}
|
||||
}
|
||||
|
||||
CXTranslationUnit tu = clang_parseTranslationUnit(m_index, nullptr,
|
||||
(const char *const *)arguments, num_args, NULL, 0,
|
||||
CXTranslationUnit_None);
|
||||
|
||||
if (num_args) {
|
||||
int j;
|
||||
for (j = 0; j < num_args; ++j) {
|
||||
free(arguments[j]);
|
||||
}
|
||||
free(arguments);
|
||||
}
|
||||
//}
|
||||
|
||||
return tu;
|
||||
}
|
||||
|
||||
CXCompilationDatabase &compilation_database::db() { return m_database; }
|
||||
}
|
||||
}
|
||||
32
src/uml/compilation_database.h
Normal file
32
src/uml/compilation_database.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <clang-c/CXCompilationDatabase.h>
|
||||
#include <clang-c/Index.h>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace clanguml {
|
||||
namespace cx {
|
||||
|
||||
class compilation_database {
|
||||
public:
|
||||
compilation_database(CXCompilationDatabase &&d);
|
||||
~compilation_database();
|
||||
|
||||
CXCompilationDatabase &db();
|
||||
|
||||
CXIndex &index();
|
||||
|
||||
CXTranslationUnit parse_translation_unit(
|
||||
const std::string &path);
|
||||
|
||||
static compilation_database from_directory(const std::string &dir);
|
||||
|
||||
private:
|
||||
CXCompilationDatabase m_database;
|
||||
CXIndex m_index;
|
||||
};
|
||||
}
|
||||
}
|
||||
21
tests/CMakeLists.txt
Normal file
21
tests/CMakeLists.txt
Normal file
@@ -0,0 +1,21 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
|
||||
# Explicitly list the test source code and headers. The Catch header-only unit
|
||||
# test framework is stored in with the test source.
|
||||
set(CLANG_UML_TEST_EXAMPLE_SRC
|
||||
test_example.cpp
|
||||
)
|
||||
set(CLANG_UML_TEST_EXAMPLE_HEADER
|
||||
catch.hpp
|
||||
)
|
||||
|
||||
# Make an executable target that depends on the test source code we specified
|
||||
# above.
|
||||
add_executable(test-example ${CLANG_UML_TEST_EXAMPLE_SRC} ${CLANG_UML_TEST_EXAMPL_HEADER})
|
||||
|
||||
# Enable testing via CTest
|
||||
enable_testing()
|
||||
|
||||
# Add our test as runnable via CTest
|
||||
add_test(NAME test-example COMMAND test-example)
|
||||
17618
tests/catch.hpp
Normal file
17618
tests/catch.hpp
Normal file
File diff suppressed because it is too large
Load Diff
19
tests/test_example.cpp
Normal file
19
tests/test_example.cpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#define CATCH_CONFIG_MAIN
|
||||
|
||||
#include "catch.hpp"
|
||||
#include <source_file.hpp>
|
||||
#include <string>
|
||||
#include <complex>
|
||||
|
||||
TEST_CASE("Test add", "[unit-test]"){
|
||||
// not very good tests, but oh well...
|
||||
REQUIRE(add(2, 3) == 5);
|
||||
REQUIRE(add(2., 3.) == 5.);
|
||||
REQUIRE(add(0, 0) == 0);
|
||||
std::cout << "RUNNING TEST" << std::endl;
|
||||
std::complex<double> a(2., 3.);
|
||||
std::complex<double> b(-1., 20.);
|
||||
std::complex<double> c(1., 23.);
|
||||
REQUIRE(add(a,b) == c);
|
||||
|
||||
}
|
||||
8258
thirdparty/cli11/CLI11.hpp
vendored
Normal file
8258
thirdparty/cli11/CLI11.hpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
25
thirdparty/cli11/LICENSE
vendored
Normal file
25
thirdparty/cli11/LICENSE
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
CLI11 1.8 Copyright (c) 2017-2019 University of Cincinnati, developed by Henry
|
||||
Schreiner under NSF AWARD 1414736. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms of CLI11, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
3. Neither the name of the copyright holder nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
21
thirdparty/glob/LICENSE
vendored
Normal file
21
thirdparty/glob/LICENSE
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Pranav
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
437
thirdparty/glob/glob.hpp
vendored
Normal file
437
thirdparty/glob/glob.hpp
vendored
Normal file
@@ -0,0 +1,437 @@
|
||||
|
||||
#pragma once
|
||||
#include <cassert>
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <regex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace glob {
|
||||
|
||||
namespace {
|
||||
|
||||
static inline bool string_replace(
|
||||
std::string &str, const std::string &from, const std::string &to)
|
||||
{
|
||||
std::size_t start_pos = str.find(from);
|
||||
if (start_pos == std::string::npos)
|
||||
return false;
|
||||
str.replace(start_pos, from.length(), to);
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline std::string translate(const std::string &pattern)
|
||||
{
|
||||
std::size_t i = 0, n = pattern.size();
|
||||
std::string result_string;
|
||||
|
||||
while (i < n) {
|
||||
auto c = pattern[i];
|
||||
i += 1;
|
||||
if (c == '*') {
|
||||
result_string += ".*";
|
||||
}
|
||||
else if (c == '?') {
|
||||
result_string += ".";
|
||||
}
|
||||
else if (c == '[') {
|
||||
auto j = i;
|
||||
if (j < n && pattern[j] == '!') {
|
||||
j += 1;
|
||||
}
|
||||
if (j < n && pattern[j] == ']') {
|
||||
j += 1;
|
||||
}
|
||||
while (j < n && pattern[j] != ']') {
|
||||
j += 1;
|
||||
}
|
||||
if (j >= n) {
|
||||
result_string += "\\[";
|
||||
}
|
||||
else {
|
||||
auto stuff =
|
||||
std::string(pattern.begin() + i, pattern.begin() + j);
|
||||
if (stuff.find("--") == std::string::npos) {
|
||||
string_replace(
|
||||
stuff, std::string{"\\"}, std::string{R"(\\)"});
|
||||
}
|
||||
else {
|
||||
std::vector<std::string> chunks;
|
||||
std::size_t k = 0;
|
||||
if (pattern[i] == '!') {
|
||||
k = i + 2;
|
||||
}
|
||||
else {
|
||||
k = i + 1;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
k = pattern.find("-", k, j);
|
||||
if (k == std::string::npos) {
|
||||
break;
|
||||
}
|
||||
chunks.push_back(std::string(
|
||||
pattern.begin() + i, pattern.begin() + k));
|
||||
i = k + 1;
|
||||
k = k + 3;
|
||||
}
|
||||
|
||||
chunks.push_back(
|
||||
std::string(pattern.begin() + i, pattern.begin() + j));
|
||||
// Escape backslashes and hyphens for set difference (--).
|
||||
// Hyphens that create ranges shouldn't be escaped.
|
||||
bool first = false;
|
||||
for (auto &s : chunks) {
|
||||
string_replace(
|
||||
s, std::string{"\\"}, std::string{R"(\\)"});
|
||||
string_replace(
|
||||
s, std::string{"-"}, std::string{R"(\-)"});
|
||||
if (first) {
|
||||
stuff += s;
|
||||
first = false;
|
||||
}
|
||||
else {
|
||||
stuff += "-" + s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Escape set operations (&&, ~~ and ||).
|
||||
std::string result;
|
||||
std::regex_replace(std::back_inserter(result), // ressult
|
||||
stuff.begin(), stuff.end(), // string
|
||||
std::regex(std::string{R"([&~|])"}), // pattern
|
||||
std::string{R"(\\\1)"}); // repl
|
||||
stuff = result;
|
||||
i = j + 1;
|
||||
if (stuff[0] == '!') {
|
||||
stuff = "^" + std::string(stuff.begin() + 1, stuff.end());
|
||||
}
|
||||
else if (stuff[0] == '^' || stuff[0] == '[') {
|
||||
stuff = "\\\\" + stuff;
|
||||
}
|
||||
result_string = result_string + "[" + stuff + "]";
|
||||
}
|
||||
}
|
||||
else {
|
||||
// SPECIAL_CHARS
|
||||
// closing ')', '}' and ']'
|
||||
// '-' (a range in character set)
|
||||
// '&', '~', (extended character set operations)
|
||||
// '#' (comment) and WHITESPACE (ignored) in verbose mode
|
||||
static std::string special_characters =
|
||||
"()[]{}?*+-|^$\\.&~# \t\n\r\v\f";
|
||||
static std::map<int, std::string> special_characters_map;
|
||||
if (special_characters_map.empty()) {
|
||||
for (auto &c : special_characters) {
|
||||
special_characters_map.insert(
|
||||
std::make_pair(static_cast<int>(c),
|
||||
std::string{"\\"} + std::string(1, c)));
|
||||
}
|
||||
}
|
||||
|
||||
if (special_characters.find(c) != std::string::npos) {
|
||||
result_string += special_characters_map[static_cast<int>(c)];
|
||||
}
|
||||
else {
|
||||
result_string += c;
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::string{"(("} + result_string + std::string{R"()|[\r\n])$)"};
|
||||
}
|
||||
|
||||
static inline std::regex compile_pattern(const std::string &pattern)
|
||||
{
|
||||
return std::regex(translate(pattern), std::regex::ECMAScript);
|
||||
}
|
||||
|
||||
static inline bool fnmatch_case(
|
||||
const fs::path &name, const std::string &pattern)
|
||||
{
|
||||
return std::regex_match(name.string(), compile_pattern(pattern));
|
||||
}
|
||||
|
||||
static inline std::vector<fs::path> filter(
|
||||
const std::vector<fs::path> &names, const std::string &pattern)
|
||||
{
|
||||
// std::cout << "Pattern: " << pattern << "\n";
|
||||
std::vector<fs::path> result;
|
||||
for (auto &name : names) {
|
||||
// std::cout << "Checking for " << name.string() << "\n";
|
||||
if (fnmatch_case(name, pattern)) {
|
||||
result.push_back(name);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static inline fs::path expand_tilde(fs::path path)
|
||||
{
|
||||
if (path.empty())
|
||||
return path;
|
||||
|
||||
const char *home = std::getenv("HOME");
|
||||
if (home == nullptr) {
|
||||
throw std::invalid_argument(
|
||||
"error: Unable to expand `~` - HOME environment variable not set.");
|
||||
}
|
||||
|
||||
std::string s = path.string();
|
||||
if (s[0] == '~') {
|
||||
s = std::string(home) + s.substr(1, s.size() - 1);
|
||||
return fs::path(s);
|
||||
}
|
||||
else {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool has_magic(const std::string &pathname)
|
||||
{
|
||||
static const auto magic_check = std::regex("([*?[])");
|
||||
return std::regex_search(pathname, magic_check);
|
||||
}
|
||||
|
||||
static inline bool is_hidden(const std::string &pathname)
|
||||
{
|
||||
return pathname[0] == '.';
|
||||
}
|
||||
|
||||
static inline bool is_recursive(const std::string &pattern)
|
||||
{
|
||||
return pattern == "**";
|
||||
}
|
||||
|
||||
static inline std::vector<fs::path> iter_directory(
|
||||
const fs::path &dirname, bool dironly)
|
||||
{
|
||||
std::vector<fs::path> result;
|
||||
|
||||
auto current_directory = dirname;
|
||||
if (current_directory.empty()) {
|
||||
current_directory = fs::current_path();
|
||||
}
|
||||
|
||||
if (fs::exists(current_directory)) {
|
||||
try {
|
||||
for (auto &entry : fs::directory_iterator(current_directory,
|
||||
fs::directory_options::follow_directory_symlink |
|
||||
fs::directory_options::skip_permission_denied)) {
|
||||
if (!dironly || entry.is_directory()) {
|
||||
if (dirname.is_absolute()) {
|
||||
result.push_back(entry.path());
|
||||
}
|
||||
else {
|
||||
result.push_back(fs::relative(entry.path()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
// not a directory
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Recursively yields relative pathnames inside a literal directory.
|
||||
static inline std::vector<fs::path> rlistdir(
|
||||
const fs::path &dirname, bool dironly)
|
||||
{
|
||||
std::vector<fs::path> result;
|
||||
auto names = iter_directory(dirname, dironly);
|
||||
for (auto &x : names) {
|
||||
if (!is_hidden(x.string())) {
|
||||
result.push_back(x);
|
||||
for (auto &y : rlistdir(x, dironly)) {
|
||||
result.push_back(y);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// This helper function recursively yields relative pathnames inside a literal
|
||||
// directory.
|
||||
static inline std::vector<fs::path> glob2(
|
||||
const fs::path &dirname, const std::string &pattern, bool dironly)
|
||||
{
|
||||
std::vector<fs::path> result;
|
||||
assert(is_recursive(pattern));
|
||||
for (auto &dir : rlistdir(dirname, dironly)) {
|
||||
result.push_back(dir);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// These 2 helper functions non-recursively glob inside a literal directory.
|
||||
// They return a list of basenames. _glob1 accepts a pattern while _glob0
|
||||
// takes a literal basename (so it only has to check for its existence).
|
||||
static inline std::vector<fs::path> glob1(
|
||||
const fs::path &dirname, const std::string &pattern, bool dironly)
|
||||
{
|
||||
auto names = iter_directory(dirname, dironly);
|
||||
std::vector<fs::path> filtered_names;
|
||||
for (auto &n : names) {
|
||||
if (!is_hidden(n.string())) {
|
||||
filtered_names.push_back(n.filename());
|
||||
// if (n.is_relative()) {
|
||||
// filtered_names.push_back(fs::relative(n));
|
||||
// } else {
|
||||
// filtered_names.push_back(n.filename());
|
||||
// }
|
||||
}
|
||||
}
|
||||
return filter(filtered_names, pattern);
|
||||
}
|
||||
|
||||
static inline std::vector<fs::path> glob0(
|
||||
const fs::path &dirname, const fs::path &basename, bool /*dironly*/)
|
||||
{
|
||||
std::vector<fs::path> result;
|
||||
if (basename.empty()) {
|
||||
// 'q*x/' should match only directories.
|
||||
if (fs::is_directory(dirname)) {
|
||||
result = {basename};
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (fs::exists(dirname / basename)) {
|
||||
result = {basename};
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static inline std::vector<fs::path> glob(
|
||||
const std::string &pathname, bool recursive = false, bool dironly = false)
|
||||
{
|
||||
std::vector<fs::path> result;
|
||||
|
||||
auto path = fs::path(pathname);
|
||||
|
||||
if (pathname[0] == '~') {
|
||||
// expand tilde
|
||||
path = expand_tilde(path);
|
||||
}
|
||||
|
||||
auto dirname = path.parent_path();
|
||||
const auto basename = path.filename();
|
||||
|
||||
if (!has_magic(pathname)) {
|
||||
assert(!dironly);
|
||||
if (!basename.empty()) {
|
||||
if (fs::exists(path)) {
|
||||
result.push_back(path);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Patterns ending with a slash should match only directories
|
||||
if (fs::is_directory(dirname)) {
|
||||
result.push_back(path);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
if (dirname.empty()) {
|
||||
if (recursive && is_recursive(basename.string())) {
|
||||
return glob2(dirname, basename, dironly);
|
||||
}
|
||||
else {
|
||||
return glob1(dirname, basename, dironly);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<fs::path> dirs;
|
||||
if (dirname != fs::path(pathname) && has_magic(dirname.string())) {
|
||||
dirs = glob(dirname, recursive, true);
|
||||
}
|
||||
else {
|
||||
dirs = {dirname};
|
||||
}
|
||||
|
||||
std::function<std::vector<fs::path>(
|
||||
const fs::path &, const fs::path &, bool)>
|
||||
glob_in_dir;
|
||||
if (has_magic(basename.string())) {
|
||||
if (recursive && is_recursive(basename.string())) {
|
||||
glob_in_dir = glob2;
|
||||
}
|
||||
else {
|
||||
glob_in_dir = glob1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
glob_in_dir = glob0;
|
||||
}
|
||||
|
||||
for (auto &d : dirs) {
|
||||
for (auto &name : glob_in_dir(d, basename, dironly)) {
|
||||
fs::path subresult = name;
|
||||
if (name.parent_path().empty()) {
|
||||
subresult = d / name;
|
||||
}
|
||||
result.push_back(subresult);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace end
|
||||
|
||||
static inline std::vector<fs::path> glob(const std::string &pathname)
|
||||
{
|
||||
return glob(pathname, false);
|
||||
}
|
||||
|
||||
static inline std::vector<fs::path> rglob(const std::string &pathname)
|
||||
{
|
||||
return glob(pathname, true);
|
||||
}
|
||||
|
||||
static inline std::vector<std::filesystem::path> glob(
|
||||
const std::vector<std::string> &pathnames)
|
||||
{
|
||||
std::vector<std::filesystem::path> result;
|
||||
for (auto &pathname : pathnames) {
|
||||
for (auto &match : glob(pathname, false)) {
|
||||
result.push_back(std::move(match));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static inline std::vector<std::filesystem::path> rglob(
|
||||
const std::vector<std::string> &pathnames)
|
||||
{
|
||||
std::vector<std::filesystem::path> result;
|
||||
for (auto &pathname : pathnames) {
|
||||
for (auto &match : glob(pathname, true)) {
|
||||
result.push_back(std::move(match));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static inline std::vector<std::filesystem::path> glob(
|
||||
const std::initializer_list<std::string> &pathnames)
|
||||
{
|
||||
return glob(std::vector<std::string>(pathnames));
|
||||
}
|
||||
|
||||
static inline std::vector<std::filesystem::path> rglob(
|
||||
const std::initializer_list<std::string> &pathnames)
|
||||
{
|
||||
return rglob(std::vector<std::string>(pathnames));
|
||||
}
|
||||
|
||||
} // namespace glob
|
||||
Reference in New Issue
Block a user