This commit is contained in:
呱呱呱 2025-06-12 20:55:12 +08:00
commit 7c502b360e
827 changed files with 783984 additions and 0 deletions

3
.codedocs Normal file
View File

@ -0,0 +1,3 @@
EXCLUDE = addon cmake doc examples jquery templates testing deps/iconv_winbuild src/logos.cpp src/lodepng.cpp
FILE_PATTERNS = *.h *.cpp *.md
USE_MDFILE_AS_MAINPAGE = src/doxygen.md

27
.dockerignore Normal file
View File

@ -0,0 +1,27 @@
*~
.*sw?
\#*
.DS_Store
*.rej
*.orig
*.pro
/packages/rpm/doxygen.spec
*.idb
*.pdb
/doxygen_docs
/doxygen.tag
/build*
/qtools_docs
/warnings.log
tags
.idea
/examples/html/*
/examples/latex/*
Dockerfile

9
.editorconfig Normal file
View File

@ -0,0 +1,9 @@
# See https://editorconfig.org/ for more information.
[*]
indent_style = space
indent_size = 2
charset = utf-8
[*.py]
indent_size = 4

9
.gitattributes vendored Normal file
View File

@ -0,0 +1,9 @@
* text=auto eol=lf
* text
### Protected
*.pdf binary
*.ico binary
*.jpg binary
*.png binary
*.lib binary

13
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,13 @@
# These are supported funding model platforms
github: doxygen
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View File

@ -0,0 +1,30 @@
---
name: Report a bug or issue
about: Create a report to help us improve doxygen
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
Describe what you see that (you think) is wrong.
**Screenshots**
If useful, add screenshots to help explain your problem.
**To Reproduce**
Attach a self contained example that allows us to reproduce the problem.
Such an example typically exist of some source code (can be dummy code) and a doxygen configuration file used (you can strip it using `doxygen -s -u`). After you verified the example demonstrates the problem, put it in a zip (or tarball) and attach it to the bug report. Try to avoid linking to external sources, since they might disappear in the future.
**Expected behavior**
Describe what you would have expected or think is correct.
**Version**
Mention the version of doxygen used (output of `doxygen --version`) and the platform on which you run doxygen (e.g. Windows 10, 64 bit). If you run doxygen under Linux please also mention the name and version of the distribution used (output of `lsb_release -a`) and mention if you compiled doxygen yourself or that you use a binary that comes with the distribution or from the doxygen website.
**Stack trace**
If you encounter a crash and can build doxygen from sources yourself with debug info (`-DCMAKE_BUILD_TYPE=Debug`), a stack trace can be very helpful (especially if it is not possible to capture the problem in a small example that can be shared).
**Additional context**
Add any other context about the problem here.

6
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"

463
.github/workflows/build_cmake.yml vendored Normal file
View File

@ -0,0 +1,463 @@
name: CMake Build for Doxygen
on: [push, pull_request]
jobs:
build:
permissions:
contents: write # to push pages branch (peaceiris/actions-gh-pages)
name: ${{ matrix.config.name }}
runs-on: ${{ matrix.config.os }}
strategy:
fail-fast: false
matrix:
config:
- {
name: "Ubuntu Latest GCC Release (Intel)",
os: ubuntu-22.04,
build_type: "Release", cc: "gcc", cxx: "g++",
build_gen: "Unix Makefiles",
cmake_extra_opts: "-Dbuild_search=YES -Dbuild_app=YES -Dbuild_parse=YES"
}
- {
name: "Ubuntu Latest GCC Debug (Intel)",
os: ubuntu-22.04,
build_type: "Debug", cc: "gcc", cxx: "g++",
build_gen: "Unix Makefiles",
cmake_extra_opts: "-Dbuild_search=YES -Dbuild_app=YES -Dbuild_parse=YES"
}
- {
name: "Ubuntu Latest Clang Release (Intel)",
os: ubuntu-22.04,
build_type: "Release", cc: "clang", cxx: "clang++",
build_gen: "Unix Makefiles",
cmake_extra_opts: "-Duse_libclang=YES -Dstatic_libclang=YES -Duse_libc++=NO"
}
- {
name: "Ubuntu Latest Clang Debug (Intel)",
os: ubuntu-22.04,
build_type: "Debug", cc: "clang", cxx: "clang++",
build_gen: "Unix Makefiles",
cmake_extra_opts: "-Duse_libclang=YES -Dstatic_libclang=YES -Duse_libc++=NO"
}
- {
name: "Ubuntu Latest GCC Release (Arm)",
os: ubuntu-24.04-arm,
build_type: "Release", cc: "gcc", cxx: "g++",
build_gen: "Unix Makefiles",
cmake_extra_opts: "-Dbuild_search=YES -Dbuild_app=YES -Dbuild_parse=YES"
}
- {
name: "Ubuntu Latest GCC Debug (Arm)",
os: ubuntu-24.04-arm,
build_type: "Debug", cc: "gcc", cxx: "g++",
build_gen: "Unix Makefiles",
cmake_extra_opts: "-Dbuild_search=YES -Dbuild_app=YES -Dbuild_parse=YES"
}
- {
name: "Ubuntu Latest Clang Release (Arm)",
os: ubuntu-24.04-arm,
build_type: "Release", cc: "clang", cxx: "clang++",
build_gen: "Unix Makefiles",
cmake_extra_opts: "-Duse_libclang=YES -Dstatic_libclang=YES -Duse_libc++=NO"
}
- {
name: "Ubuntu Latest Clang Debug (Arm)",
os: ubuntu-24.04-arm,
build_type: "Debug", cc: "clang", cxx: "clang++",
build_gen: "Unix Makefiles",
cmake_extra_opts: "-Duse_libclang=YES -Dstatic_libclang=YES -Duse_libc++=NO"
}
- {
name: "macOS Latest Release (Intel)",
os: macos-13,
build_type: "Release", cc: "clang", cxx: "clang++",
build_gen: "Unix Makefiles"
}
- {
name: "macOS Latest Debug (Intel)",
os: macos-13,
build_type: "Debug", cc: "clang", cxx: "clang++",
build_gen: "Unix Makefiles"
}
- {
name: "macOS Latest Release (Apple Silicon)",
os: macos-14,
build_type: "Release", cc: "clang", cxx: "clang++",
build_gen: "Unix Makefiles"
}
- {
name: "macOS Latest Debug (Apple Silicon)",
os: macos-14,
build_type: "Debug", cc: "clang", cxx: "clang++",
build_gen: "Unix Makefiles"
}
- {
name: "Windows Latest MSVC Debug",
os: windows-latest,
build_type: "Debug", cc: "cl", cxx: "cl",
build_gen: "NMake Makefiles"
}
- {
name: "Windows Latest MSVC Release",
os: windows-latest,
build_type: "Release", cc: "cl", cxx: "cl",
build_gen: "NMake Makefiles"
}
steps:
- name: Checkout doxygen
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install libiconv (Windows)
uses: suisei-cn/actions-download-file@v1
with:
url: "https://github.com/pffang/libiconv-for-Windows/releases/download/v1.16/libiconv-for-Windows_1.16.7z"
target: .
if: matrix.config.os == 'windows-latest'
- name: Install LaTeX (Linux)
run: |
sudo apt update --fix-missing
sudo apt upgrade
sudo apt update
sudo apt-get install texlive texlive-latex-recommended texlive-extra-utils texlive-latex-extra texlive-font-utils
if: startsWith(matrix.config.os,'ubuntu-')
- name: Install LaTeX (MacOS)
run: |
brew update || true
brew install --cask mactex || true
echo "/Library/TeX/texbin/" >> $GITHUB_PATH
if: startsWith(matrix.config.os,'macos-')
- name: Install libclang (Ubuntu 22.04)
run: |
sudo apt update
sudo apt remove llvm-13 llvm-13-dev llvm-13-tools llvm-13-runtime clang-13 clang-format-13 libclang-common-13-dev libclang-cpp13 libclang1-13 libllvm13
sudo apt remove llvm-15 llvm-15-dev llvm-15-tools llvm-15-runtime clang-15 clang-format-15 libclang-common-15-dev libclang-cpp15 libclang1-15 libllvm15
sudo apt-get autoremove
sudo apt-get clean
sudo apt install libclang-common-14-dev libclang-14-dev
apt list --installed | egrep '(clang|llvm)'
ls -d /usr/lib/llvm-*/include/
sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-14 100
sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-14 100
ls -al /usr/bin/clang++
ls -al /etc/alternatives/clang++
which clang++
clang++ -v
if: matrix.config.cc == 'clang' && matrix.config.os == 'ubuntu-22.04'
- name: Install libclang (Ubuntu 24.04 ARM)
run: |
sudo apt update
sudo apt remove llvm-14 llvm-14-dev llvm-14-tools llvm-14-runtime clang-14 clang-format-14 libclang-common-14-dev libclang-cpp14 libclang1-14 libllvm14
sudo apt remove llvm-15 llvm-15-dev llvm-15-tools llvm-15-runtime clang-15 clang-format-15 libclang-common-15-dev libclang-cpp15 libclang1-15 libllvm15
sudo apt remove llvm-16 llvm-16-dev llvm-16-tools llvm-16-runtime clang-16 clang-format-16 libclang-common-16-dev libclang-cpp16 libclang1-16 libllvm16
sudo apt-get autoremove
sudo apt-get clean
sudo apt install -y libclang-common-18-dev libclang-18-dev clang-18 llvm-18 llvm-18-dev
apt list --installed | egrep '(clang|llvm)'
ls -d /usr/lib/llvm-*/include/
sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-18 100
sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-18 100
ls -al /usr/bin/clang++
ls -al /etc/alternatives/clang++
which clang++
clang++ -v
if: matrix.config.os == 'ubuntu-24.04-arm'
- name: Install libxapian (Ubuntu 22.04)
run: |
sudo apt update
sudo apt install libxapian-dev
if: matrix.config.os == 'ubuntu-22.04' || matrix.config.os == 'ubuntu-24.04-arm'
- name: Install LaTeX (Windows)
uses: teatimeguest/setup-texlive-action@v3
with:
packages: >-
scheme-medium
collection-latexextra
babel-dutch
cjk
bibtex
if: matrix.config.os == 'windows-latest'
- name: Install Ghostscript (Linux)
run: |
sudo apt update
sudo apt-get install ghostscript
if: startsWith(matrix.config.os,'ubuntu-')
- name: Install Ghostscript (Windows)
run:
choco install ghostscript
if: matrix.config.os == 'windows-latest'
- name: Setting Ghostscript paths (Windows)
shell: bash
run: |
export GSpath=`find /c/Prog*/gs -name gswin\*c.exe | sed -e "s/gswin.*c.exe//"`
export PATH="$GSpath:$PATH"
export GSpath=`echo "$GSpath" | sed -e "s%/c%C:%"`
echo "$GSpath" >> $GITHUB_PATH
if: matrix.config.os == 'windows-latest'
- name: Install xmllint (Linux)
run: |
sudo apt-get update
sudo apt-get install libxml2-utils
if: startsWith(matrix.config.os,'ubuntu-')
- name: Install xmllint (MacOS)
run: |
brew update || true
brew upgrade || true
brew install libxml2
if: startsWith(matrix.config.os,'macos-')
- name: Install bison (MacOS)
run: |
brew update
brew install bison;
echo "$(brew --prefix bison)/bin" >> $GITHUB_PATH
#echo "/usr/local/opt/bison/bin" >> $GITHUB_PATH
if: startsWith(matrix.config.os,'macos-')
- name: Install bison/flex (Windows)
run: |
#Choco-Install -PackageName winflexbison
choco install winflexbison3
if: matrix.config.os == 'windows-latest'
- name: Install Graphviz (Linux)
run: |
sudo apt update
sudo apt-get install graphviz
if: startsWith(matrix.config.os,'ubuntu-')
- name: Install Graphviz (MacOS)
run: |
if ! brew install graphviz; then
# Workaround issue with unexpected symlinks: https://github.com/actions/runner-images/issues/6817
for f in 2to3 idle3 pydoc3 python3 python3-config; do
rm /usr/local/bin/$f || true
done
# Try again
brew install graphviz
fi
if: startsWith(matrix.config.os,'macos-')
- name: Install Graphviz (Windows)
run:
choco install graphviz.portable
if: matrix.config.os == 'windows-latest'
- name: Setup VS Environment (Windows)
uses: seanmiddleditch/gha-setup-vsdevenv@master
if: matrix.config.os == 'windows-latest'
- name: Refresh Env (Windows)
run: |
Import-Module $env:ChocolateyInstall\helpers\chocolateyProfile.psm1
refreshenv
if: matrix.config.os == 'windows-latest'
- name: Install Qt 6.8
uses: jdpurcell/install-qt-action@v5
with:
version: 6.8.*
if: matrix.config.os == 'ubuntu-24.04-arm'
- name: Install Qt 6.2
uses: jdpurcell/install-qt-action@v5
with:
version: 6.2.*
if: startsWith(matrix.config.os,'macos-')
- name: Install Qt 5
uses: jdpurcell/install-qt-action@v5
with:
version: 5.*
if: startsWith(matrix.config.os,'macos-')!=true && matrix.config.os != 'ubuntu-24.04-arm'
- name: Check tool versions (Linux / MacOS)
shell: bash
run: |
echo "=== perl ===";
perl --version;
echo "=== python ===";
python --version;
echo "=== cmake ===";
cmake --version;
echo "=== latex ===";
latex --version;
echo "=== bibtex ===";
bibtex --version
echo "=== dvips ===";
dvips --version
echo "=== bison ===";
bison --version;
echo "=== flex ===";
flex --version;
echo "=== dot ===";
dot -V;
echo "=== ghostscript ===";
gs --version;
if: matrix.config.os != 'windows-latest'
- name: Check tool versions (Windows)
shell: bash
run: |
echo "=== perl ===";
perl --version;
echo "=== python ===";
python --version;
echo "=== cmake ===";
cmake --version;
echo "=== latex ===";
latex --version;
echo "=== bibtex ===";
bibtex --version
echo "=== dvips ===";
dvips --version
echo "=== bison ===";
win_bison --version;
echo "=== flex ===";
win_flex --version;
echo "=== dot ===";
dot -V;
echo "=== ghostscript ===";
gswin64c --version;
if: matrix.config.os == 'windows-latest'
- name: Configure
shell: cmake -P {0}
run: |
set(ENV{CC} ${{ matrix.config.cc }})
set(ENV{CXX} ${{ matrix.config.cxx }})
execute_process(
COMMAND cmake
-S .
-B build
-D CMAKE_BUILD_TYPE=${{ matrix.config.build_type }}
-G "${{ matrix.config.build_gen }}"
-Dbuild_doc=YES
-Dbuild_wizard=YES
${{ matrix.config.cmake_extra_opts }}
RESULT_VARIABLE result
)
if (NOT result EQUAL 0)
message(FATAL_ERROR "Bad exit status")
endif()
- name: Build
shell: cmake -P {0}
run: |
include(ProcessorCount)
ProcessorCount(N)
execute_process(
COMMAND cmake --build build --parallel ${N}
RESULT_VARIABLE result
OUTPUT_VARIABLE output
ERROR_VARIABLE output
ECHO_OUTPUT_VARIABLE ECHO_ERROR_VARIABLE
)
if (NOT result EQUAL 0)
string(REGEX MATCH "FAILED:.*$" error_message "${output}")
string(REPLACE "\n" "%0A" error_message "${error_message}")
message("::error::${error_message}")
message(FATAL_ERROR "Build failed")
endif()
- name: Archive build artifacts
uses: actions/upload-artifact@v4
with:
name: "${{ matrix.config.name }} build artifacts"
path: build/bin/
- name: Run tests (Linux / MacOS)
shell: cmake -P {0}
run: |
set(ENV{CTEST_OUTPUT_ON_FAILURE} "ON")
execute_process(
COMMAND
cmake -E env TEST_FLAGS="--xml --xmlxsd --xhtml --qhp --docbook --rtf --man"
cmake --build build --target tests
RESULT_VARIABLE result
)
if (NOT result EQUAL 0)
message(FATAL_ERROR "Running tests failed!")
endif()
if: matrix.config.os != 'windows-latest'
- name: Run tests (Windows)
shell: cmake -P {0}
run: |
set(ENV{CTEST_OUTPUT_ON_FAILURE} "ON")
execute_process(
COMMAND
cmake -E env TEST_FLAGS="--xml --xmlxsd --xhtml --qhp --docbook --rtf --man --pdf"
cmake --build build --target tests
RESULT_VARIABLE result
)
if (NOT result EQUAL 0)
message(FATAL_ERROR "Running tests failed!")
endif()
if: matrix.config.os == 'windows-latest'
- name: Generate documentation
shell: cmake -P {0}
run: |
execute_process(
COMMAND cmake --build build --target docs
RESULT_VARIABLE result
)
if (NOT result EQUAL 0)
message(FATAL_ERROR "Building documentation failed")
endif()
- name: Archive html documentation artifacts
uses: actions/upload-artifact@v4
with:
name: "Html documentation artifacts"
path: build/html/
if: matrix.config.name == 'Ubuntu Latest GCC Release (Intel)'
- name: Archive Latex documentation artifacts
uses: actions/upload-artifact@v4
with:
name: "Latex documentation artifacts"
path: build/latex/doxygen_manual.pdf
if: matrix.config.name == 'Ubuntu Latest GCC Release (Intel)'
- name: Generate Internal documentation
shell: cmake -P {0}
run: |
execute_process(
COMMAND cmake --build build --target docs_internal
RESULT_VARIABLE result
)
if (NOT result EQUAL 0)
message(FATAL_ERROR "Building internal documentation failed")
endif()
if: matrix.config.name == 'Ubuntu Latest GCC Release (Intel)'
- name: Publish Internal documentation to Github pages
uses: peaceiris/actions-gh-pages@v4
with:
deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY }}
external_repository: doxygen/doxygen-docs
publish_dir: build/doxygen_docs/html
force_orphan: true
if: ${{ github.event_name == 'push' && matrix.config.name == 'Ubuntu Latest GCC Release (Intel)' }}

80
.github/workflows/coverity.yml vendored Normal file
View File

@ -0,0 +1,80 @@
name: Coverity for doxygen
# Just for tests
#on: [push, pull_request]
# The right schedule
on:
schedule:
- cron: '30 2 * * *' # Run once per day, to avoid Coverity's submission limits
permissions:
contents: read
jobs:
check_date:
runs-on: ubuntu-22.04
name: Check latest commit
outputs:
should_run: ${{ steps.should_run.outputs.should_run }}
steps:
- uses: actions/checkout@v4
- name: print latest_commit
run: echo ${{ github.sha }}
- id: should_run
continue-on-error: true
name: check latest commit is less than a day
if: ${{ github.event_name == 'schedule' }}
run: test -z $(git rev-list --after="24 hours" ${{ github.sha }}) && echo "::set-output name=should_run::false"
scan:
needs: check_date
if: ${{ needs.check_date.outputs.should_run != 'false' }}
runs-on: ubuntu-22.04
env:
CC: gcc
DEBIAN_FRONTEND: noninteractive
steps:
- name: Checkout doxygen
uses: actions/checkout@v4
- name: Download Coverity
run: |
wget -q https://scan.coverity.com/download/cxx/linux64 --post-data "token=$TOKEN&project=doxygen%2Fdoxygen" -O coverity_tool.tgz
mkdir cov-scan
tar ax -f coverity_tool.tgz --strip-components=1 -C cov-scan
env:
TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }}
- name: Setup environment
run: |
echo "$(pwd)/cov-scan/bin" >> $GITHUB_PATH
echo "NPROC=$(getconf _NPROCESSORS_ONLN)" >> $GITHUB_ENV
- name: Configure doxygen
run: |
mkdir build
cmake -S . -B build -D CMAKE_BUILD_TYPE=Release -G "Unix Makefiles"
- name: Run coverity build/scan
run: |
cd build && cov-build --dir cov-int make -j${NPROC}
- name: Submit results
run: |
cd build
tar zcf cov-scan.tgz cov-int
curl --form token=$TOKEN \
--form email=$EMAIL \
--form file=@cov-scan.tgz \
--form version="$(git rev-parse HEAD)" \
--form description="Automatic GHA scan" \
'https://scan.coverity.com/builds?project=doxygen%2Fdoxygen'
env:
TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }}
EMAIL: ${{ secrets.COVERITY_SCAN_EMAIL }}

41
.github/workflows/docker_publish.yml vendored Normal file
View File

@ -0,0 +1,41 @@
name: Publish Docker package
on:
release:
types: [published]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Log in to the Container registry
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Build and push Docker image
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

View File

@ -0,0 +1,34 @@
name: Upload Python Package
on:
release:
types: [published]
permissions:
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install build
- name: Build package
run: python -m build -w addon/doxmlparser/
- name: Publish package
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: addon/doxmlparser/dist/
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}

29
.gitignore vendored Normal file
View File

@ -0,0 +1,29 @@
*~
.*sw?
\#*
.DS_Store
*.rej
*.orig
*.pro
/packages/rpm/doxygen.spec
*.idb
*.pdb
/doxygen_docs
/doxygen.tag
/build*
/qtools_docs
/warnings.log
tags
.cache/
.idea
cmake-build-debug/
cmake-build-debug-event-trace/
.vscode/
/examples/html/*
/examples/latex/*

336
CMakeLists.txt Normal file
View File

@ -0,0 +1,336 @@
# vim:ts=4:sw=4:expandtab:autoindent:
#
# Copyright (C) 1997-2015 by Dimitri van Heesch.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation under the terms of the GNU General Public License is hereby
# granted. No representations are made about the suitability of this software
# for any purpose. It is provided "as is" without express or implied warranty.
# See the GNU General Public License for more details.
#
# Documents produced by Doxygen are derivative works derived from the
# input used in their production; they are not affected by this license.
cmake_minimum_required(VERSION 3.14)
project(doxygen)
option("ENABLE_CLANG_TIDY" "Enable static analysis with clang-tidy" OFF)
option(build_wizard "Build the GUI frontend for doxygen." OFF)
option(build_app "Example showing how to embed doxygen in an application." OFF)
option(build_parse "Parses source code and dumps the dependencies between the code elements." OFF)
option(build_search "Build external search tools (doxysearch and doxyindexer)" OFF)
option(build_doc "Build user manual (HTML and PDF)" OFF)
option(build_doc_chm "Build user manual (CHM)" OFF)
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
option(use_libc++ "Use libc++ as C++ standard library." ON)
endif()
option(use_libclang "Add support for libclang parsing." OFF)
option(use_sys_spdlog "Use system spdlog library instead of the one bundled." OFF)
option(use_sys_fmt "Use system fmt library instead of the one bundled." OFF)
option(use_sys_sqlite3 "Use system sqlite3 library instead of the one bundled." OFF)
option(static_libclang "Link to a statically compiled version of LLVM/libclang." OFF)
option(win_static "Link with /MT in stead of /MD on windows" OFF)
option(enable_console "Enable that executables on Windows get the CONSOLE bit set for the doxywizard executable [development]" OFF)
option(enable_coverage "Enable coverage reporting for gcc/clang [development]" OFF)
option(enable_tracing "Enable tracing option in release builds [development]" OFF)
option(enable_lex_debug "Enable debugging info for lexical scanners in release builds [development]" OFF)
include(CheckCXXCompilerFlag)
set(force_qt CACHE INTERNAL "Forces doxywizard to build using the specified major version, this can be Qt5 or Qt6")
set_property(CACHE force_qt PROPERTY STRINGS OFF Qt6 Qt5)
SET(enlarge_lex_buffers "262144" CACHE INTERNAL "Sets the lex input and read buffers to the specified size")
if(enable_coverage)
if ("${PROJECT_BINARY_DIR}" STREQUAL "${PROJECT_SOURCE_DIR}")
message(FATAL_ERROR "Doxygen cannot be generated in-place, the build directory (${PROJECT_BINARY_DIR}) has to differ from the doxygen main directory (${PROJECT_SOURCE_DIR})\nPlease don't forget to remove the already created file 'CMakeCache.txt' and the directory 'CMakeFiles'!")
endif()
endif()
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/Sanitizers")
set(TOP "${PROJECT_SOURCE_DIR}")
include(version)
message(STATUS "Using Cmake version ${CMAKE_VERSION}")
if (${CMAKE_VERSION} VERSION_LESS "3.21.0")
set(depfile_supported "0" CACHE INTERNAL "DEPFILE is not supported")
else()
set(depfile_supported "1" CACHE INTERNAL "DEPFILE is supported")
endif()
set(clang "0" CACHE INTERNAL "used in settings.h")
set(MACOS_VERSION_MIN 10.14)
if (use_libclang)
set(clang "1" CACHE INTERNAL "used in settings.h")
find_package(LLVM CONFIG REQUIRED)
find_package(Clang CONFIG REQUIRED)
endif()
if (use_sys_spdlog)
find_package(spdlog CONFIG REQUIRED)
endif()
if (use_sys_sqlite3)
find_package(SQLite3 REQUIRED)
endif()
if (build_wizard)
if (NOT force_qt STREQUAL "Qt5")
if (CMAKE_SYSTEM MATCHES "Darwin")
set(MACOS_VERSION_MIN 10.15)
endif()
endif()
endif()
# use C++17 standard for compiling (unless very new Clang is present)
if (
(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" AND
CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 17) OR
(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND
CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 19)
)
set(CMAKE_CXX_STANDARD 20)
else()
set(CMAKE_CXX_STANDARD 17)
endif()
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS ON)
if (ENABLE_CLANG_TIDY)
find_program("CLANGTIDY" "clang-tidy")
if (CLANGTIDY)
set(CMAKE_CXX_CLANG_TIDY clang-tidy;
-header-filter=.;
-checks=-*,cppcoreguidelines-special-member-functions
#-checks=-*,cppcoreguidelines-missing-std-forward
#-checks=-*,cppcoreguidelines-init-variables
#-checks=-*,cppcoreguidelines-misleading-capture-default-by-value
#-checks=-*,modernize-use-nullptr
#-checks=-*,modernize-use-override
#-checks=-*,modernize-use-emplace
)
else()
message(SEND_ERROR "clang-tidy requested but executable not found")
endif()
endif()
# produce compile_commands.json
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
if (CMAKE_SYSTEM MATCHES "Darwin")
set(CMAKE_OSX_DEPLOYMENT_TARGET "${MACOS_VERSION_MIN}" CACHE STRING "Minimum OS X deployment version" FORCE)
set(CMAKE_CXX_FLAGS "-Wno-deprecated-register -mmacosx-version-min=${MACOS_VERSION_MIN} ${CMAKE_CXX_FLAGS}")
set(CMAKE_C_FLAGS "-Wno-deprecated-register -mmacosx-version-min=${MACOS_VERSION_MIN} ${CMAKE_C_FLAGS}")
find_library(CORESERVICES_LIB CoreServices)
set(EXTRA_LIBS ${CORESERVICES_LIB})
endif()
check_cxx_source_compiles(
"
#include <algorithm>
#if !defined(__clang__) || !defined(_LIBCPP_VERSION)
# error \"This is not clang with libcxx by llvm\"
#endif
int main() {
return 0;
}
"
IS_CLANG_LIBCPP
FAIL_REGEX "This is not clang with libcxx by llvm"
)
add_compile_definitions(
# LLVM's clang in combination with libc++
$<$<AND:$<CONFIG:Debug>,$<BOOL:${IS_CLANG_LIBCPP}>>:_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG>
# LLVM's clang or gcc in combination with libstdc++ (GNU)
$<$<AND:$<CONFIG:Debug>,$<NOT:$<BOOL:${IS_CLANG_LIBCPP}>>>:_GLIBCXX_ASSERTIONS>
)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DSQLITE_OMIT_LOAD_EXTENSION=1")
# Use 64-bit off_t on 32-bit Linux
if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SIZEOF_VOID_P EQUAL 4)
# ensure 64bit offsets are used for filesystem accesses for 32bit compilation
add_compile_definitions(_FILE_OFFSET_BITS=64)
endif()
if (WIN32)
if (MSVC)
if (NOT ICONV_DIR)
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
list(APPEND CMAKE_PREFIX_PATH "${PROJECT_SOURCE_DIR}/deps/iconv_winbuild/include" "${PROJECT_SOURCE_DIR}/deps/iconv_winbuild/x64")
elseif(CMAKE_SIZEOF_VOID_P EQUAL 4)
list(APPEND CMAKE_PREFIX_PATH "${PROJECT_SOURCE_DIR}/deps/iconv_winbuild/include" "${PROJECT_SOURCE_DIR}/deps/iconv_winbuild/x86")
endif()
else()
list(APPEND CMAKE_PREFIX_PATH ${ICONV_DIR})
endif()
set(CMAKE_REQUIRED_DEFINITIONS "-DLIBICONV_STATIC")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj") # needed for language.cpp on 64bit
add_definitions(-DLIBICONV_STATIC -D_CRT_SECURE_NO_WARNINGS)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /LARGEADDRESSAWARE")
endif()
if (CMAKE_GENERATOR MATCHES "NMake Makefiles")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc")
endif()
endif()
if (CYGWIN OR MINGW)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Og -Wa,-mbig-obj")
if (CMAKE_BUILD_TYPE STREQUAL "")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O1")
endif()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wa,-mbig-obj")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wa,-mbig-obj")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wa,-mbig-obj")
endif()
if (WIN32 AND MSVC)
# workaround for GitHub runner, see https://github.com/actions/runner-images/issues/10004
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR")
endif()
if (CMAKE_SYSTEM_NAME MATCHES "Windows")
if ((CMAKE_GENERATOR MATCHES "MinGW Makefiles") OR
(CMAKE_GENERATOR MATCHES "MSYS Makefiles") OR
(CMAKE_GENERATOR MATCHES "Unix Makefiles"))
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Og")
if (CMAKE_BUILD_TYPE STREQUAL "")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O1")
endif()
endif()
endif()
# needed for JavaCC
if (CMAKE_CXX_STANDARD EQUAL 20)
set(JAVA_CC_EXTRA_FLAGS "-DJAVACC_CHAR_TYPE=\"char8_t\"")
else()
set(JAVA_CC_EXTRA_FLAGS "-DJAVACC_CHAR_TYPE=\"unsigned char\"")
endif()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${JAVA_CC_EXTRA_FLAGS}")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${JAVA_CC_EXTRA_FLAGS}")
if(POLICY CMP0063)
cmake_policy(SET CMP0063 NEW)
endif()
# when using mutrace comment the next 3 lines and uncomment the last 2
set(CMAKE_CXX_VISIBILITY_PRESET hidden)
set(CMAKE_C_VISIBILITY_PRESET hidden)
set(CMAKE_VISIBILITY_INLINES_HIDDEN 1)
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -rdynamic")
#set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -rdynamic")
if (CMAKE_GENERATOR MATCHES "Ninja")
set(LEX_FLAGS )
set(YACC_FLAGS )
set(JAVACC_FLAGS )
else ()
set(LEX_FLAGS $(LEX_FLAGS))
set(YACC_FLAGS $(YACC_FLAGS))
set(JAVACC_FLAGS $(JAVACC_FLAGS))
endif ()
find_program(DOT NAMES dot)
find_package(Python REQUIRED)
find_package(FLEX REQUIRED)
if (FLEX_VERSION VERSION_LESS 2.5.37)
message(SEND_ERROR "Doxygen requires at least flex version 2.5.37 (installed: ${FLEX_VERSION})")
endif()
if (FLEX_VERSION VERSION_LESS 2.6.0)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Dregister=")
if (MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_ALLOW_KEYWORD_MACROS=")
endif()
endif()
find_package(BISON REQUIRED)
if (BISON_VERSION VERSION_LESS 2.7)
message(SEND_ERROR "Doxygen requires at least bison version 2.7 (installed: ${BISON_VERSION})")
endif()
find_package(Threads)
find_package(Sanitizers)
if ((CMAKE_BUILD_TYPE STREQUAL "Debug") OR enable_lex_debug)
set(LEX_FLAGS "${LEX_FLAGS} -d")
endif()
find_package(Iconv REQUIRED)
include_directories(${Iconv_INCLUDE_DIRS})
#set(DOXYDOCS ${PROJECT_SOURCE_DIR}/doc CACHE INTERNAL "Path to doxygen docs")
set(DOXYDOCS ${PROJECT_BINARY_DIR}/doc)
set(ENV{DOXYGEN_DOCDIR} ${DOXYDOCS})
set(GENERATED_SRC "${PROJECT_BINARY_DIR}/generated_src" CACHE INTERNAL "Stores generated files")
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
# place binaries for all build types in the same directory, so we know where to find it
# when running tests or generating docs
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${EXECUTABLE_OUTPUT_PATH})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${EXECUTABLE_OUTPUT_PATH})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL ${EXECUTABLE_OUTPUT_PATH})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO ${EXECUTABLE_OUTPUT_PATH})
if (win_static)
set(CompilerFlags
CMAKE_CXX_FLAGS
CMAKE_CXX_FLAGS_DEBUG
CMAKE_CXX_FLAGS_RELEASE
CMAKE_CXX_FLAGS_MINSIZEREL
CMAKE_CXX_FLAGS_RELWITHDEBINFO
CMAKE_C_FLAGS
CMAKE_C_FLAGS_DEBUG
CMAKE_C_FLAGS_RELEASE
CMAKE_C_FLAGS_MINSIZEREL
CMAKE_C_FLAGS_RELWITHDEBINFO)
foreach(CompilerFlag ${CompilerFlags})
string(REPLACE "/MD" "/MT" ${CompilerFlag} "${${CompilerFlag}}")
endforeach()
endif()
include(cmake/CompilerWarnings.cmake)
include(cmake/Coverage.cmake)
include(cmake/WindowsEncoding.cmake)
add_subdirectory(deps)
add_subdirectory(libversion)
add_subdirectory(libxml)
add_subdirectory(vhdlparser)
add_subdirectory(src)
if (build_doc_chm)
if (WIN32)
find_package(HTMLHelp REQUIRED)
set(build_doc ON)
else ()
message(WARNING "CHM documentation generation not supported for this platform, ignoring setting.")
set(build_doc_chm OFF)
endif ()
endif ()
# always parse doc directory to at least install man pages
add_subdirectory(doc)
if (build_doc)
add_subdirectory(examples)
endif ()
add_subdirectory(doc_internal)
find_package(generateDS)
set(update_doxmlparser_dependency "")
if (GENERATEDS_FOUND)
set(update_doxmlparser_dependency "update_doxmlparser_files")
endif()
add_subdirectory(addon)
enable_testing()
add_subdirectory(testing)
include(cmake/packaging.cmake) # set CPACK_xxxx properties
include(CPack)

27
Dockerfile Normal file
View File

@ -0,0 +1,27 @@
FROM ubuntu:jammy AS builder
RUN apt-get update && apt-get install -y \
g++ \
python3 \
cmake \
flex \
bison \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /doxygen
COPY . .
RUN mkdir build \
&& cd build \
&& cmake -G "Unix Makefiles" .. \
&& make \
&& make install
FROM ubuntu:jammy
RUN apt-get update && apt-get install --no-install-recommends -y \
graphviz \
&& rm -rf /var/lib/apt/lists/*
COPY --from=builder /doxygen/build/bin/doxygen /usr/local/bin/
WORKDIR /doxygen
ENTRYPOINT ["doxygen"]

7
INSTALL Normal file
View File

@ -0,0 +1,7 @@
DOXYGEN
Please read the installation section of the manual
(https://www.doxygen.nl/manual/install.html) for instructions.
--------
Dimitri van Heesch

51
LANGUAGE.HOWTO Normal file
View File

@ -0,0 +1,51 @@
This short howto explains how to add support for a new language to Doxygen:
Just follow these steps:
1) Tell me which language you want to add support for. If no one else
is already working on support for that language, you will be
assigned as the maintainer for the language. I'll create a
list on Doxygen's homepage, so everyone knows who is doing what.
2) Edit src/config.xml:
- find the <option> with id='OUTPUT_LANGUAGE'
- add a new value with your language and an optional description:
<value name='YourLanguage' desc='(only for disambiguation)'/>
3) Create a copy of translator_en.h and name it
translator_<your_2_letter_country_code>.h
I'll use xx in the rest of this document.
4) Edit language.cpp:
- Add an include block:
#include "translator_xx.h"
- In setTranslator() add
case OUTPUT_LANGUAGE_t::YourLanguage: theTranslator = new TranslatorYourLanguage; break;
5) Edit translator_xx.h:
- Change TRANSLATOR_EN_H to TRANSLATOR_XX_H (in both the #include line and
the #define line).
- Change TranslatorEnglish to TranslatorYourLanguage
- In the member idLanguage() change "english" into the name of your
language (use lower case characters only). Set the trISOLang() and
getLanguageString() return values to match your language as well. Depending on the
language you may also wish to change the member function latexLanguageSupportCommand().
- Edit all the strings that are returned by the members that start
with tr. Try to match punctuation and capitals!
To enter special characters (with accents) you can:
a) Enter them directly and store the files using UTF-8 encoding.
b) Use html codes like &auml; for an a with an umlaut (i.e. ä).
See the HTML specification for the codes.
6) Change to your build directory and build again in order to regenerate the binary e.g.:
cd path/where/you/build
cmake --build .
7) Now you can use OUTPUT_LANGUAGE = your_language_name
in the config file to generate output in your language.
8) Send translator_xx.h to me so I can add it to doxygen.
Send also your name and e-mail address to be included in the
maintainers.txt list.
Good luck, and let me know if there are problems.

339
LICENSE Normal file
View File

@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program 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 2 of the License, or
(at your option) any later version.
This program 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 this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

68
README.md Normal file
View File

@ -0,0 +1,68 @@
Doxygen
===============
[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=9HHLRBCC8B2B8)
Doxygen is the de facto standard tool for generating documentation from
annotated C++ sources, but it also supports other popular programming
languages such as C, Objective-C, C#, PHP, Java, Python, IDL
(Corba, Microsoft, and UNO/OpenOffice flavors), Fortran,
and to some extent D. Doxygen also supports the hardware description language VHDL.
Doxygen can help you in three ways:
1. It can generate an on-line documentation browser (in HTML) and/or an
off-line reference manual (in LaTeX) from a set of documented source files.
There is also support for generating output in RTF (MS-Word), PostScript,
hyperlinked PDF, compressed HTML, DocBook and Unix man pages.
The documentation is extracted directly from the sources, which makes
it much easier to keep the documentation consistent with the source code.
2. You can configure doxygen to extract the code structure from undocumented
source files. This is very useful to quickly find your way in large
source distributions. Doxygen can also visualize the relations between
the various elements by means of include dependency graphs, inheritance
diagrams, and collaboration diagrams, which are all generated automatically.
3. You can also use doxygen for creating normal documentation (as I did for
the doxygen user manual and doxygen web-site).
Download
---------
The latest binaries and source of Doxygen can be downloaded from:
* https://www.doxygen.nl/
Developers
---------
* Linux & Windows and MacOS Build Status: <a href="https://github.com/doxygen/doxygen/actions"><img alt="Github Actions Build Status" src="https://github.com/doxygen/doxygen/workflows/CMake%20Build%20for%20Doxygen/badge.svg"></a>
* Coverity Scan Build Status: <a href="https://scan.coverity.com/projects/2860"> <img alt="Coverity Scan Build Status" src="https://scan.coverity.com/projects/2860/badge.svg"/> </a>
* Doxygen's <a href="https://doxygen.github.io/doxygen-docs/">internal source code documentation</a>
* Install: Please read the installation section of the manual (https://www.doxygen.nl/manual/install.html)
* Project stats: https://www.openhub.net/p/doxygen
Issues, bugs, requests, ideas
----------------------------------
Use the [issue](https://github.com/doxygen/doxygen/issues) tracker to report bugs.
Comms
----------------------------------
### Mailing Lists ###
There are three mailing lists:
* doxygen-announce@lists.sourceforge.net - Announcement of new releases only
* doxygen-users@lists.sourceforge.net - for doxygen users
* doxygen-develop@lists.sourceforge.net - for doxygen developers
* To subscribe follow the link to
* https://sourceforge.net/projects/doxygen/
Source Code
----------------------------------
In May 2013, Doxygen moved from
subversion to git hosted at GitHub
* https://github.com/doxygen/doxygen
Enjoy,
Dimitri van Heesch (doxygen at gmail.com)

1
VERSION Normal file
View File

@ -0,0 +1 @@
1.15.0

17
addon/CMakeLists.txt Normal file
View File

@ -0,0 +1,17 @@
add_subdirectory(doxmlparser)
if (build_app)
add_subdirectory(doxyapp)
endif ()
if (build_parse)
add_subdirectory(doxyparse)
endif ()
if (build_search)
add_subdirectory(doxysearch)
endif ()
if (build_wizard)
add_subdirectory(doxywizard)
endif ()

View File

@ -0,0 +1,27 @@
if (GENERATEDS_FOUND)
add_custom_command(
COMMENT "Updating index.py from index.xsd..."
COMMAND ${GENERATEDS_EXECUTABLE} --no-dates --no-versions -f -o ${PROJECT_SOURCE_DIR}/addon/doxmlparser/doxmlparser/index.py ${PROJECT_SOURCE_DIR}/templates/xml/index.xsd
COMMAND ${Python_EXECUTABLE} ${PROJECT_SOURCE_DIR}/addon/doxmlparser/generateDS_post.py ${PROJECT_SOURCE_DIR}/addon/doxmlparser/doxmlparser/index.py ${PROJECT_SOURCE_DIR}/addon/doxmlparser/doxmlparser/index_new.py
COMMAND ${CMAKE_COMMAND} -E remove ${PROJECT_SOURCE_DIR}/addon/doxmlparser/doxmlparser/index.py
COMMAND ${CMAKE_COMMAND} -E rename ${PROJECT_SOURCE_DIR}/addon/doxmlparser/doxmlparser/index_new.py ${PROJECT_SOURCE_DIR}/addon/doxmlparser/doxmlparser/index.py
DEPENDS ${PROJECT_SOURCE_DIR}/templates/xml/index.xsd ${PROJECT_SOURCE_DIR}/addon/doxmlparser/generateDS_post.py
OUTPUT ${PROJECT_SOURCE_DIR}/addon/doxmlparser/doxmlparser/index.py
)
add_custom_command(
COMMENT "Updating compound.py from compound.xsd..."
COMMAND ${GENERATEDS_EXECUTABLE} --no-dates --no-versions -f -o ${PROJECT_SOURCE_DIR}/addon/doxmlparser/doxmlparser/compound.py ${PROJECT_SOURCE_DIR}/templates/xml/compound.xsd
COMMAND ${Python_EXECUTABLE} ${PROJECT_SOURCE_DIR}/addon/doxmlparser/generateDS_post.py ${PROJECT_SOURCE_DIR}/addon/doxmlparser/doxmlparser/compound.py ${PROJECT_SOURCE_DIR}/addon/doxmlparser/doxmlparser/compound_new.py
COMMAND ${CMAKE_COMMAND} -E remove ${PROJECT_SOURCE_DIR}/addon/doxmlparser/doxmlparser/compound.py
COMMAND ${CMAKE_COMMAND} -E rename ${PROJECT_SOURCE_DIR}/addon/doxmlparser/doxmlparser/compound_new.py ${PROJECT_SOURCE_DIR}/addon/doxmlparser/doxmlparser/compound.py
DEPENDS ${PROJECT_SOURCE_DIR}/templates/xml/compound.xsd ${PROJECT_SOURCE_DIR}/addon/doxmlparser/generateDS_post.py
OUTPUT ${PROJECT_SOURCE_DIR}/addon/doxmlparser/doxmlparser/compound.py
)
add_custom_target(
${update_doxmlparser_dependency} ALL
DEPENDS ${PROJECT_SOURCE_DIR}/addon/doxmlparser/doxmlparser/index.py
DEPENDS ${PROJECT_SOURCE_DIR}/addon/doxmlparser/doxmlparser/compound.py
COMMENT "Updating doxmlparser module..."
)
endif()

19
addon/doxmlparser/LICENSE Normal file
View File

@ -0,0 +1,19 @@
Copyright 2021 Dimitri van Heesch
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.

View File

@ -0,0 +1,12 @@
Doxmlparser
===========
This is a python package to make it easier to parse the XML output produced by doxygen.
The API is generated from the index.xsd and compound.xsd XML schema files using
Dave Kuhlman's generateDS https://www.davekuhlman.org/generateDS.html
The current code is generated with generateDS version 2.37.15.
See the examples directory to get an idea how to use the python module

View File

@ -0,0 +1,2 @@
from .index import *
from .compound import *

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,31 @@
# An minimal example showing how to use the python doxmlparser module to read
# the XML output generated by doxygen for a project and dump it to the output again.
import sys
import doxmlparser
# process a compound file and export the results to stdout
def parse_compound(inDirName,baseName):
doxmlparser.compound.parse(inDirName+"/"+baseName+".xml",False)
# process the index file and export the results to stdout
def parse_index(inDirName):
rootObj = doxmlparser.index.parse(inDirName+"/index.xml",False)
for compound in rootObj.get_compound(): # for each compound defined in the index
parse_compound(inDirName,compound.get_refid())
def usage():
print("Usage {0} <xml_output_dir>".format(sys.argv[0]))
sys.exit(1)
def main():
args = sys.argv[1:]
if len(args)==1:
parse_index(args[0])
else:
usage()
if __name__ == '__main__':
main()

View File

@ -0,0 +1,225 @@
# An example showing how to use the python doxmlparser module to extract some metrics from
# the XML output generated by doxygen for a project.
import sys
import doxmlparser
from doxmlparser.compound import DoxCompoundKind, DoxMemberKind, DoxSectionKind, MixedContainer
class Metrics:
def __init__(self):
self.numClasses = 0
self.numDocClasses = 0
self.numStructs = 0
self.numUnions = 0
self.numInterfaces = 0
self.numExceptions = 0
self.numNamespaces = 0
self.numFiles = 0
self.numDocFiles = 0
self.numGroups = 0
self.numPages = 0
self.numPubMethods = 0
self.numDocPubMethods = 0
self.numProMethods = 0
self.numDocProMethods = 0
self.numPriMethods = 0
self.numDocPriMethods = 0
self.numAttributes = 0
self.numDocAttributes = 0
self.numFunctions = 0
self.numDocFunctions = 0
self.numVariables = 0
self.numDocVariables = 0
self.numParams = 0
def print(self):
numMethods = self.numPubMethods + self.numProMethods + self.numPriMethods
numDocMethods = self.numDocPubMethods + self.numDocProMethods + self.numDocPriMethods
print("Metrics:")
print("-----------------------------------")
if self.numClasses>0:
print("Classes: {:=10} ({} documented)".format(self.numClasses,self.numDocClasses))
if self.numStructs>0:
print("Structs: {:=10}".format(self.numStructs))
if self.numUnions>0:
print("Unions: {:=10}".format(self.numUnions))
if self.numExceptions>0:
print("Exceptions: {:=10}".format(self.numExceptions))
if self.numNamespaces>0:
print("Namespaces: {:=10}".format(self.numNamespaces))
if self.numFiles>0:
print("Files: {:=10} ({} documented)".format(self.numFiles,self.numDocFiles))
if self.numGroups>0:
print("Groups: {:=10}".format(self.numGroups))
if self.numPages>0:
print("Pages: {:=10}".format(self.numPages))
if numMethods>0:
print("Methods: {:=10} ({} documented)".format(numMethods,numDocMethods))
if self.numPubMethods>0:
print(" Public: {:=10} ({} documented)".format(self.numPubMethods,self.numDocPubMethods))
if self.numProMethods>0:
print(" Protected: {:=10} ({} documented)".format(self.numProMethods,self.numDocProMethods))
if self.numPriMethods>0:
print(" Private: {:=10} ({} documented)".format(self.numPriMethods,self.numDocPriMethods))
if self.numFunctions>0:
print("Functions: {:=10} ({} documented)".format(self.numFunctions,self.numDocFunctions))
if self.numAttributes>0:
print("Attributes: {:=10} ({} documented)".format(self.numAttributes,self.numDocAttributes))
if self.numVariables>0:
print("Variables: {:=10} ({} documented)".format(self.numVariables,self.numDocVariables))
if self.numParams>0:
print("Params: {:=10}".format(self.numParams))
print("-----------------------------------")
if self.numClasses>0:
print("Avg. #methods/compound: {:=10}".format(float(numMethods)/float(self.numClasses)))
if numMethods>0:
print("Avg. #params/method: {:=10}".format(float(self.numParams)/float(numMethods)))
print("-----------------------------------")
def description_is_empty(description):
for content in description.content_:
if content.getCategory()==MixedContainer.CategoryText:
if not content.getValue().isspace():
return False # non space-only text
elif not content.getCategory()==MixedContainer.CategoryNone:
return False # some internal object like a paragraph
return True
def is_documented(definition):
return not description_is_empty(definition.get_briefdescription()) or \
not description_is_empty(definition.get_detaileddescription())
def section_is_protected(sectionkind):
return sectionkind in [DoxSectionKind.PROTECTEDTYPE,
DoxSectionKind.PROTECTEDFUNC,
DoxSectionKind.PROTECTEDATTRIB,
DoxSectionKind.PROTECTEDSLOT,
DoxSectionKind.PROTECTEDSTATICFUNC,
DoxSectionKind.PROTECTEDSTATICATTRIB]
def section_is_private(sectionkind):
return sectionkind in [DoxSectionKind.PRIVATETYPE,
DoxSectionKind.PRIVATEFUNC,
DoxSectionKind.PRIVATEATTRIB,
DoxSectionKind.PRIVATESLOT,
DoxSectionKind.PRIVATESTATICFUNC,
DoxSectionKind.PRIVATESTATICATTRIB]
def section_is_public(sectionkind):
return not section_is_protected(sectionkind) and not section_is_private(sectionkind)
def linked_text_to_string(linkedtext):
str=''
if linkedtext:
for text_or_ref in linkedtext.content_:
if text_or_ref.getCategory()==MixedContainer.CategoryText:
str+=text_or_ref.getValue()
else:
str+=text_or_ref.getValue().get_valueOf_()
return str
def parse_members(compounddef,sectiondef,metrics):
functionLikeKind = [DoxMemberKind.FUNCTION,
DoxMemberKind.PROTOTYPE,
DoxMemberKind.SIGNAL,
DoxMemberKind.SLOT,
DoxMemberKind.DCOP]
variableLikeKind = [DoxMemberKind.VARIABLE, DoxMemberKind.PROPERTY]
for memberdef in sectiondef.get_memberdef():
if compounddef.get_kind() in [DoxCompoundKind.CLASS, DoxCompoundKind.STRUCT, DoxCompoundKind.INTERFACE]:
if memberdef.get_kind() in functionLikeKind:
if section_is_public(sectiondef.get_kind()):
metrics.numPubMethods+=1
if is_documented(memberdef):
metrics.numDocPubMethods+=1
elif section_is_protected(sectiondef.get_kind()):
metrics.numProMethods+=1
if is_documented(memberdef):
metrics.numDocProMethods+=1
elif section_is_private(sectiondef.get_kind()):
metrics.numPriMethods+=1
if is_documented(memberdef):
metrics.numDocPriMethods+=1
elif memberdef.get_kind() in variableLikeKind:
metrics.numAttributes+=1
if is_documented(memberdef):
metrics.numDocAttributes+=1
elif compounddef.get_kind() in [DoxCompoundKind.FILE, DoxCompoundKind.NAMESPACE]:
if memberdef.get_kind() in functionLikeKind:
metrics.numFunctions+=1
if is_documented(memberdef):
metrics.numDocFunctions+=1
elif memberdef.get_kind() in variableLikeKind:
metrics.numVariables+=1
if is_documented(memberdef):
metrics.numDocVariables+=1
#for param in memberdef.get_param():
# name = ''
# if param.get_defname():
# name = param.get_defname()
# if param.get_declname():
# name = param.get_declname()
# print("param '{}':'{}'".format(linked_text_to_string(param.get_type()),name))
metrics.numParams+=len(memberdef.get_param())
if memberdef.get_type() and memberdef.get_type()!="void" and linked_text_to_string(memberdef.get_type()):
metrics.numParams+=1 # count non-void return types as well
#print("returns '{}'".format(linked_text_to_string(memberdef.get_type())))
def parse_sections(compounddef,metrics):
for sectiondef in compounddef.get_sectiondef():
parse_members(compounddef,sectiondef,metrics)
def parse_compound(inDirName,baseName,metrics):
rootObj = doxmlparser.compound.parse(inDirName+"/"+baseName+".xml",True)
for compounddef in rootObj.get_compounddef():
kind = compounddef.get_kind()
if kind==DoxCompoundKind.CLASS:
metrics.numClasses+=1
if is_documented(compounddef):
metrics.numDocClasses+=1
elif kind==DoxCompoundKind.STRUCT:
metrics.numStructs+=1
elif kind==DoxCompoundKind.UNION:
metrics.numUnions+=1
elif kind==DoxCompoundKind.INTERFACE:
metrics.numInterfaces+=1
elif kind==DoxCompoundKind.EXCEPTION:
metrics.numExceptions+=1
elif kind==DoxCompoundKind.NAMESPACE:
metrics.numNamespaces+=1
elif kind==DoxCompoundKind.FILE:
metrics.numFiles+=1
if is_documented(compounddef):
metrics.numDocFiles+=1
elif kind==DoxCompoundKind.GROUP:
metrics.numGroups+=1
elif kind==DoxCompoundKind.PAGE:
metrics.numPages+=1
else:
continue
parse_sections(compounddef,metrics)
def parse_index(inDirName):
metrics = Metrics()
rootObj = doxmlparser.index.parse(inDirName+"/index.xml",True)
for compound in rootObj.get_compound(): # for each compound defined in the index
print("Processing {0}...".format(compound.get_name()))
parse_compound(inDirName,compound.get_refid(),metrics)
metrics.print()
def usage():
print("Usage {0} <xml_output_dir>".format(sys.argv[0]))
sys.exit(1)
def main():
args = sys.argv[1:]
if len(args)==1:
parse_index(args[0])
else:
usage()
if __name__ == '__main__':
main()

View File

@ -0,0 +1,40 @@
#!/usr/bin/env python
#
# Copyright (C) 1997-2022 by Dimitri van Heesch.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation under the terms of the GNU General Public License is hereby
# granted. No representations are made about the suitability of this software
# for any purpose. It is provided "as is" without express or implied warranty.
# See the GNU General Public License for more details.
#
# Documents produced by Doxygen are derivative works derived from the
# input used in their production; they are not affected by this license.
import sys
import re
def main():
inputFile = open(sys.argv[1], 'r')
outputFile = open(sys.argv[2], 'wb')
for line in inputFile:
line = line.rstrip()
line = re.sub(r'##','# #',line)
line = re.sub(r'# Python.*','#',line)
line = re.sub(r"('-o', ').*(/addon/doxmlparser/doxmlparser)","\\1...\\2",line)
# python 2 slips the u in ...
line = re.sub(r"u'","'",line)
if line.find("generateDS") == -1:
line = re.sub(r'(# ).*(/templates/xml/)','\\1...\\2',line)
else:
line = re.sub(r'(# ).*generateDS(.* -o ").*(/addon/doxmlparser/doxmlparser/.* ).*(/templates/xml/)',
'\\1.../generateDS\\2...\\3...\\4',line)
if line.find(" self") == 0:
line = " " + line
outputFile.write(str.encode(line))
outputFile.write(str.encode('\n'))
if __name__ == '__main__':
main()

View File

@ -0,0 +1,2 @@
lxml>=4.0.0
six>=1.0.0

View File

@ -0,0 +1,31 @@
import os
from pathlib import Path
from setuptools import setup, find_packages
topdir = Path(os.getcwd()).parent.parent
with open(topdir / 'VERSION') as f:
version = f.read()
with open('README.md') as f:
readme = f.read()
with open('LICENSE') as f:
license = f.read()
with open('requirements.txt') as f:
requirements = f.read().splitlines()
setup(
name='doxmlparser',
version=version,
description='Python API to access doxygen generated XML output',
long_description=readme,
author='Dimitri van Heesch',
author_email='doxygen@gmail.com',
url='https://github.com/doxygen/doxygen/addon/doxmlparser',
license=license,
packages=find_packages(exclude=('tests', 'docs')),
install_requires=requirements
)

View File

@ -0,0 +1,57 @@
find_package(Iconv)
include_directories(
${PROJECT_SOURCE_DIR}/src
${PROJECT_SOURCE_DIR}/libversion
${GENERATED_SRC}
${Iconv_INCLUDE_DIRS}
${CLANG_INCLUDEDIR}
)
add_executable(doxyapp
doxyapp.cpp
${PROJECT_SOURCE_DIR}/templates/icon/doxygen.rc
)
include(ApplyEditbin)
apply_editbin(doxyapp console)
add_sanitizers(doxyapp)
if (use_libclang)
find_package(LLVM REQUIRED CONFIG)
find_package(Clang REQUIRED CONFIG)
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
target_compile_features(doxyapp PRIVATE cxx_alignof)
if (use_libc++)
target_compile_options(doxyapp PRIVATE -stdlib=libc++)
endif()
endif()
include_directories(${LLVM_INCLUDE_DIRS})
add_definitions(${LLVM_DEFINITIONS})
if (static_libclang)
set(CLANG_LIBS libclang clangTooling)
else() # dynamically linked version of clang
llvm_config(doxymain USE_SHARED support)
set(CLANG_LIBS libclang clang-cpp)
endif()
target_compile_definitions(doxyapp PRIVATE ${LLVM_DEFINITIONS})
endif()
target_link_libraries(doxyapp
doxymain
md5
sqlite3
xml
lodepng
mscgen
doxygen_version
doxycfg
vhdlparser
${Iconv_LIBRARIES}
${CMAKE_THREAD_LIBS_INIT}
${SQLITE3_LIBRARIES}
${EXTRA_LIBS}
${CLANG_LIBS}
${COVERAGE_LINKER_FLAGS}
)
install(TARGETS doxyapp DESTINATION bin)

8
addon/doxyapp/README Normal file
View File

@ -0,0 +1,8 @@
This directory contains an example of how to use doxygen as
an "source parsing engine" in an application. It shows how to configure doxygen
from the application and shows how to run doxygen without generating output.
Practically, it allows you to extract the symbols found in the source code.
Note that if you use this approach your application should be licensed under the GPL.

446
addon/doxyapp/doxyapp.cpp Normal file
View File

@ -0,0 +1,446 @@
/******************************************************************************
*
* Copyright (C) 1997-2015 by Dimitri van Heesch.
*
* Permission to use, copy, modify, and distribute this software and its
* documentation under the terms of the GNU General Public License is hereby
* granted. No representations are made about the suitability of this software
* for any purpose. It is provided "as is" without express or implied warranty.
* See the GNU General Public License for more details.
*
* Documents produced by Doxygen are derivative works derived from the
* input used in their production; they are not affected by this license.
*
*/
/** @file
* @brief Example of how to use doxygen as part of another GPL applications
*
* This example shows how to configure and run doxygen programmatically from
* within an application without generating the usual output.
*
* If you are running a non-UNIX-like system you should specify `--tempdir`.
*
* This example shows how to use to code parser to get cross-references information
* and it also shows how to look up symbols in a program parsed by doxygen and
* show some information about them.
*/
#include <stdlib.h>
#include <map>
#include <string>
#include "dir.h"
#include "doxygen.h"
#include "outputgen.h"
#include "outputlist.h"
#include "parserintf.h"
#include "classdef.h"
#include "namespacedef.h"
#include "filedef.h"
#include "util.h"
#include "classlist.h"
#include "config.h"
#include "filename.h"
#include "version.h"
class XRefDummyCodeGenerator : public OutputCodeIntf
{
public:
XRefDummyCodeGenerator(FileDef *fd) : m_fd(fd) {}
~XRefDummyCodeGenerator() {}
// these are just null functions, they can be used to produce a syntax highlighted
// and cross-linked version of the source code, but who needs that anyway ;-)
OutputType type() const override { return OutputType::Extension; }
std::unique_ptr<OutputCodeIntf> clone() override { return std::make_unique<XRefDummyCodeGenerator>(m_fd); }
void codify(const QCString &) override {}
void stripCodeComments(bool) override {}
void startSpecialComment() override {}
void endSpecialComment() override {}
void setStripIndentAmount(size_t) override {}
void writeCodeLink(CodeSymbolType,const QCString &,const QCString &,const QCString &,const QCString &,const QCString &) override {}
void writeLineNumber(const QCString &,const QCString &,const QCString &,int,bool) override {}
virtual void writeTooltip(const QCString &,const DocLinkInfo &,
const QCString &,const QCString &,const SourceLinkInfo &,
const SourceLinkInfo &) override {}
void startCodeLine(int) override {}
void endCodeLine() override {}
void startFontClass(const QCString &) override {}
void endFontClass() override {}
void writeCodeAnchor(const QCString &) override {}
void startCodeFragment(const QCString &) override {}
void endCodeFragment(const QCString &) override {}
void startFold(int,const QCString &,const QCString &) override {}
void endFold() override {}
// here we are presented with the symbols found by the code parser
void linkableSymbol(int l, const char *sym,Definition *symDef,Definition *context)
{
QCString ctx;
if (context) // the context of the symbol is known
{
if (context->definitionType()==Definition::TypeMember) // it is inside a member
{
Definition *parentContext = context->getOuterScope();
if (parentContext && parentContext->definitionType()==Definition::TypeClass)
// it is inside a member of a class
{
ctx.sprintf("inside %s %s of %s %s",
(dynamic_cast<MemberDef*>(context))->memberTypeName().data(),
context->name().data(),
(dynamic_cast<ClassDef*>(parentContext))->compoundTypeString().data(),
parentContext->name().data());
}
else if (parentContext==Doxygen::globalScope) // it is inside a global member
{
ctx.sprintf("inside %s %s",
(dynamic_cast<MemberDef*>(context))->memberTypeName().data(),
context->name().data());
}
}
if (ctx.isEmpty()) // it is something else (class, or namespace member, ...)
{
ctx.sprintf("in %s",context->name().data());
}
}
printf("Found symbol %s at line %d of %s %s\n",
sym,l,m_fd->getDefFileName().data(),ctx.data());
if (symDef && context) // in this case the definition of the symbol is
// known to doxygen.
{
printf("-> defined at line %d of %s\n",
symDef->getDefLine(),symDef->getDefFileName().data());
}
}
private:
FileDef *m_fd;
};
static void findXRefSymbols(FileDef *fd)
{
// get the interface to a parser that matches the file extension
auto intf=Doxygen::parserManager->getCodeParser(fd->getDefFileExtension());
// get the programming language from the file name
SrcLangExt lang = getLanguageFromFileName(fd->name());
// reset the parsers state
intf->resetCodeParserState();
// create a new backend object
std::unique_ptr<OutputCodeIntf> xrefGen = std::make_unique<XRefDummyCodeGenerator>(fd);
OutputCodeList xrefList;
xrefList.add(std::move(xrefGen));
// parse the source code
intf->parseCode(xrefList,
QCString(),
fileToString(fd->absFilePath()),
lang,
FALSE,
FALSE,
QCString(),
fd);
}
static void listSymbol(Definition *d)
{
if (d!=Doxygen::globalScope && // skip the global namespace symbol
d->name().at(0)!='@' // skip anonymous stuff
)
{
printf("%s\n",
d->name().data());
}
}
static void listSymbols()
{
for (const auto &kv : *Doxygen::symbolMap)
{
for (const auto &def : kv.second)
{
listSymbol(def);
}
}
}
static void lookupSymbol(const Definition *d)
{
if (d!=Doxygen::globalScope && // skip the global namespace symbol
d->name().at(0)!='@' // skip anonymous stuff
)
{
printf("Symbol info\n");
printf("-----------\n");
printf("Name: %s\n",d->name().data());
printf("File: %s\n",d->getDefFileName().data());
printf("Line: %d\n",d->getDefLine());
// depending on the definition type we can case to the appropriate
// derived to get additional information
switch (d->definitionType())
{
case Definition::TypeClass:
{
const ClassDef *cd = dynamic_cast<const ClassDef*>(d);
printf("Kind: %s\n",cd->compoundTypeString().data());
}
break;
case Definition::TypeFile:
{
const FileDef *fd = dynamic_cast<const FileDef*>(d);
printf("Kind: File: #includes %zu other files\n",
fd->includeFileList().size());
}
break;
case Definition::TypeNamespace:
{
const NamespaceDef *nd = dynamic_cast<const NamespaceDef*>(d);
printf("Kind: Namespace: contains %zu classes and %zu namespaces\n",
nd->getClasses().size(),
nd->getNamespaces().size());
}
break;
case Definition::TypeMember:
{
const MemberDef *md = dynamic_cast<const MemberDef*>(d);
printf("Kind: %s\n",md->memberTypeName().data());
}
break;
default:
// ignore groups/pages/packages/dirs for now
break;
}
}
}
static void lookupSymbols(const QCString &sym)
{
if (!sym.isEmpty())
{
auto range = Doxygen::symbolMap->find(sym);
bool found=false;
for (const Definition *def : range)
{
lookupSymbol(def);
found=true;
}
if (!found)
{
printf("Unknown symbol\n");
}
}
}
template <typename Iter>
std::string join(Iter begin, Iter end, std::string const& separator)
{
std::ostringstream result;
if (begin != end)
result << *begin++;
while (begin != end)
result << separator << *begin++;
return result.str();
}
static auto symbolInfo(const Definition *def)
{
std::map<std::string, int> ret;
if (def->hasDocumentation())
{
if (def->hasBriefDescription())
ret["briefLine"] = def->briefLine();
ret["docLine"] = def->docLine();
}
ret["defLine"] = def->getDefLine();
ret["defColumn"] = def->getDefColumn();
ret["startDefLine"] = def->getStartDefLine();
ret["startBodyLine"] = def->getStartBodyLine();
ret["endBodyLine"] = def->getEndBodyLine();
ret["inbodyLine"] = def->inbodyLine();
return ret;
}
static void locateSymbols()
{
std::map<std::string, std::map<std::string, std::map<std::string, std::map<std::string, int>>>> ret;
for (const auto &kv : *Doxygen::symbolMap)
{
for (const auto &def : kv.second)
{
if (def == Doxygen::globalScope || def->name().at(0) == '@')
continue;
QCString args = "";
if (def->definitionType() == Definition::TypeMember)
{
const auto *md = dynamic_cast<MemberDef*>(def);
args = md->argsString();
}
ret[def->getDefFileName().data()][def->qualifiedName().data()][args.data()] = symbolInfo(def);
}
}
// print as json
std::vector<std::string> out;
for (const auto &[fname, qmap] : ret)
{
out.push_back(std::string(4, ' ') + "\"" + fname + "\": {\n");
std::vector<std::string> file;
for (const auto &[qname, arg_map] : qmap)
{
file.push_back(std::string(8, ' ') + "\"" + qname + "\": {\n");
std::vector<std::string> name;
for (const auto &[args, imap] : arg_map)
{
name.push_back(std::string(12, ' ') + "\"" + args + "\": {\n");
std::vector<std::string> item;
for (const auto &[key, value] : imap)
{
item.push_back(std::string(16, ' ') + "\"" + key + "\": " + std::to_string(value));
}
name.back() += join(item.begin(), item.end(), ",\n");
name.back() += "\n" + std::string(12, ' ') + "}";
}
file.back() += join(name.begin(), name.end(), ",\n");
file.back() += "\n" + std::string(8, ' ') + "}";
}
out.back() += join(file.begin(), file.end(), ",\n");
out.back() += "\n" + std::string(4, ' ') + "}";
}
std::cout << "{\n" << join(out.begin(), out.end(), ",\n") << "\n}\n";
}
int main(int argc,char **argv)
{
std::string tempdir = "/tmp/doxygen";
std::string usage = "Usage: %s [--version] [--help] [--list] [--locate] [--tempdir ARG] path [path...]\n";
StringVector inputList;
bool list = false;
bool locate = false;
for (size_t i = 1; i < argc; i++)
{
if (std::string(argv[i]) == "--version")
{
printf("%s version: %s\n",argv[0],getFullVersion().c_str());
exit(0);
}
if (std::string(argv[i]) == "--help")
{
printf(usage.c_str(), argv[0]);
exit(0);
}
if (std::string(argv[i]) == "--list")
{
if (locate)
{
printf(usage.c_str(), argv[0]);
exit(1);
}
list = true;
continue;
}
if (std::string(argv[i]) == "--locate")
{
if (list)
{
printf(usage.c_str(), argv[0]);
exit(1);
}
locate = true;
continue;
}
if (std::string(argv[i]) == "--tempdir")
{
if (i+1 >= argc)
{
printf(usage.c_str(), argv[0]);
exit(1);
}
tempdir = argv[i+1];
i++;
continue;
}
inputList.push_back(argv[i]);
}
// initialize data structures
initDoxygen();
// setup the non-default configuration options
checkConfiguration();
adjustConfiguration();
// we need a place to put intermediate files
Config_updateString(OUTPUT_DIRECTORY, tempdir.c_str());
// disable html output
Config_updateBool(GENERATE_HTML,FALSE);
// disable latex output
Config_updateBool(GENERATE_LATEX,FALSE);
// be quiet
Config_updateBool(QUIET,TRUE);
// turn off warnings
Config_updateBool(WARNINGS,FALSE);
Config_updateBool(WARN_IF_UNDOCUMENTED,FALSE);
Config_updateBool(WARN_IF_DOC_ERROR,FALSE);
Config_updateBool(WARN_IF_UNDOC_ENUM_VAL,FALSE);
// Extract as much as possible
Config_updateBool(EXTRACT_ALL,TRUE);
Config_updateBool(EXTRACT_STATIC,TRUE);
Config_updateBool(EXTRACT_PRIVATE,TRUE);
Config_updateBool(EXTRACT_LOCAL_METHODS,TRUE);
// Extract source browse information, needed
// to make doxygen gather the cross reference info
Config_updateBool(SOURCE_BROWSER,TRUE);
// In case of a directory take all files on directory and its subdirectories
Config_updateBool(RECURSIVE,TRUE);
// set the input
Config_updateList(INPUT,inputList);
// parse the files
parseInput();
// iterate over the input files
for (const auto &fn : *Doxygen::inputNameLinkedMap)
{
for (const auto &fd : *fn)
{
// get the references (linked and unlinked) found in this file
findXRefSymbols(fd.get());
}
}
// clean up after us
Dir().rmdir(tempdir.c_str());
if (list)
{
listSymbols();
exit(0);
}
if (locate)
{
locateSymbols();
exit(0);
}
char cmd[256];
while (1)
{
printf("> Type a symbol name or\n> .list for a list of symbols or\n> .quit to exit\n> ");
(void)fgets(cmd,256,stdin);
QCString s(cmd);
if (s.at(s.length()-1)=='\n') s=s.left(s.length()-1); // strip trailing \n
if (s==".list")
listSymbols();
else if (s==".locate")
locateSymbols();
else if (s==".quit")
exit(0);
else
lookupSymbols(s);
}
}

View File

@ -0,0 +1,38 @@
# Viewer for the content of a Doxygen style comment block
## Contents
The directory contains an index.html page and a python3 helper script.
The script can be used to start a local web server that can do life rendering of
the content of a doxygen comment block.
Similar to e.g. https://markdownlivepreview.com/ but using doxygen as render engine.
## To prepare the server
Place a doxygen.css in the same directory as the doxycommentview.py script.
This file can be generated running
doxygen -w html /tmp/header.html /tmp/footer.html doxygen.css path/to/Doxyfile
or, alternatively, copied from an existing HTML output directory generated by doxygen.
## To run the server invoke:
python3 doxycommentview.py --doxyfile /path/to/Doxyfile
The relevant settings, such as alias definitions, will be taken from the Doxyfile.
If desired you can set the port for the webserver using `--port` and
point to the location of the doxygen binary using `--doxygen`
Once the server is started, point your browser to the index page
firefox http://localhost:8000/index.html
You should see a panel to enter text on the left hand side and the output
rendered by doxygen on the right hand side of the page.
You can copy and paste the contents of this README.md file to test it quickly.

View File

@ -0,0 +1,75 @@
#!/usr/bin/env python3
#
# python3 helper script to start a web server that can do life rendering of doxygen comments.
#
# Copyright (C) 1997-2024 by Dimitri van Heesch.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation under the terms of the GNU General Public License is hereby
# granted. No representations are made about the suitability of this software
# for any purpose. It is provided "as is" without express or implied warranty.
# See the GNU General Public License for more details.
#
# Documents produced by Doxygen are derivative works derived from the
# input used in their production; they are not affected by this license.
#
import http.server
import socketserver
import json
import subprocess
import argparse
import signal
import threading
import html
def main():
# Set up argument parser
parser = argparse.ArgumentParser(description="Runs the doxygen comment viewer HTTP server.")
parser.add_argument('--port', type=int, default=8000, help='Port number to run the server on')
parser.add_argument('--doxygen', type=str, default='doxygen', help='Path to doxygen executable')
parser.add_argument('--doxyfile', type=str, default='Doxyfile', help='Path to Doxyfile to use')
args = parser.parse_args()
PORT = args.port
DOXYGEN = args.doxygen
DOXYFILE = args.doxyfile
VERSION_STR = subprocess.run([DOXYGEN, '-v'], capture_output=True, text=True, encoding="utf-8").stdout
class RequestHandler(http.server.SimpleHTTPRequestHandler):
def do_POST(self):
if self.path == '/process':
content_length = int(self.headers['Content-Length'])
post_data = self.rfile.read(content_length)
data = json.loads(post_data)
input_text = data['input']
# Run doxygen in single comment mode, reading from stdin and writing to stdout and stderr
result = subprocess.run([DOXYGEN, '-c', '-', DOXYFILE], \
input=input_text, capture_output=True, text=True, encoding="utf-8")
# Prepare the response
response = json.dumps({
'html_output': result.stdout,
'error_output': "<b>Doxygen version "+html.escape(VERSION_STR)+"</b><pre>"+html.escape(result.stderr)+"</pre>"
})
# Send the result to the requesting HTML page
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write(response.encode())
httpd = socketserver.TCPServer(("", PORT), RequestHandler)
def signal_handler(sig, frame):
print('Shutting down the web server...')
threading.Thread(target=httpd.shutdown).start()
signal.signal(signal.SIGINT, signal_handler)
print("Running web server on port", PORT)
httpd.serve_forever()
if __name__ == '__main__':
main()

View File

@ -0,0 +1,132 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Doxygen comment viewer</title>
<link rel="stylesheet" type="text/css" href="/doxygen.css"/>
<style>
body {
margin: 0;
padding: 0;
display: flex;
height: 100vh;
}
.panel {
display: flex;
width: 50%;
padding: 20px;
box-sizing: border-box;
}
.line-numbers {
background: #f0f0f0;
padding: 10px;
margin: 0px;
margin-top: 2px;
text-align: right;
user-select: none;
white-space: pre;
border-right: 1px solid #ccc;
font-family: monospace;
font-size: 14px;
line-height: 1.5;
overflow: hidden;
}
#input {
width: 100%;
height: 100%;
padding: 10px;
margin: 0px;
box-sizing: border-box;
resize: none;
font-family: monospace;
font-size: 14px;
line-height: 1.5;
}
#output {
border-left: 1px solid #ccc;
display: flex;
flex-direction: column;
overflow: hidden;
}
#output-area {
flex: 1;
overflow: auto;
}
#console-area {
height: 150px; /* Fixed height for the console area */
border-top: 1px solid #ccc;
overflow: auto;
background-color: #f9f9f9; /* Light grey background for console area */
padding: 10px;
box-sizing: border-box;
}
</style>
</head>
<body>
<div class="panel">
<div class="line-numbers" id="line-numbers">1</div>
<textarea id="input" oninput="updateLineNumbers()" onscroll="syncScroll()" onkeydown="updateLineNumbers()" onkeyup="updateLineNumbers()"></textarea>
</div>
<div class="panel" id="output">
<!-- Processed output will appear here -->
<div id="output-area">
<!-- Processed output will appear here -->
</div>
<div id="console-area">
<!-- Error messages will appear here -->
</div>
</div>
<script>
function updateLineNumbers() {
const textarea = document.getElementById('input');
const lineNumbers = document.getElementById('line-numbers');
const lines = textarea.value.split('\n').length;
let lineNumberString = '';
for (let i = 1; i <= lines; i++) {
lineNumberString += i + '\n';
}
lineNumbers.textContent = lineNumberString;
}
function syncScroll() {
const textarea = document.getElementById('input');
const lineNumbers = document.getElementById('line-numbers');
lineNumbers.scrollTop = textarea.scrollTop;
}
function processInput() {
const input = document.getElementById('input').value;
sessionStorage.setItem('userInput', input);
fetch('/process', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ input: input }),
})
.then(response => response.json())
.then(result => {
document.getElementById('output-area').innerHTML = result.html_output;
document.getElementById('console-area').innerHTML = result.error_output;
});
}
document.getElementById('input').addEventListener('input', () => {
processInput();
});
// Load previously saved input from local storage
window.onload = () => {
const savedInput = sessionStorage.getItem('userInput');
if (savedInput) {
document.getElementById('input').value = savedInput;
processInput();
}
// Initial line numbers update
updateLineNumbers();
};
</script>
</body>
</html>

View File

@ -0,0 +1,46 @@
find_package(Iconv)
include_directories(
${PROJECT_SOURCE_DIR}/src
${PROJECT_SOURCE_DIR}/libversion
${GENERATED_SRC}
${Iconv_INCLUDE_DIRS}
${CLANG_INCLUDEDIR}
)
add_executable(doxyparse
doxyparse.cpp
${PROJECT_SOURCE_DIR}/templates/icon/doxygen.rc
)
include(ApplyEditbin)
apply_editbin(doxyparse console)
add_sanitizers(doxyparse)
if (use_libclang)
if (static_libclang)
set(CLANG_LIBS libclang clangTooling ${llvm_libs})
else()
set(CLANG_LIBS libclang clang-cpp ${llvm_libs})
endif()
endif()
target_link_libraries(doxyparse
doxymain
md5
sqlite3
xml
lodepng
mscgen
doxygen_version
doxycfg
vhdlparser
${Iconv_LIBRARIES}
${CMAKE_THREAD_LIBS_INIT}
${SQLITE3_LIBRARIES}
${EXTRA_LIBS}
${CLANG_LIBS}
${COVERAGE_LINKER_FLAGS}
)
install(TARGETS doxyparse DESTINATION bin)

52
addon/doxyparse/README.md Normal file
View File

@ -0,0 +1,52 @@
# Doxyparse
This directory contains an "source parsing engine" based on doxyapp code.
Doxyparse modifies the default output of Doxygen and dumps the dependencies
among code elements in a YAML format, instead of output it in a human-readable
format, as Doxygen does Doxyparse's output is intended to produce a
machine-readable output.
Doxyparse has been used in many software engineering research (as a source-code
static analysis tool) regards on software metrics, quality metrics and so on,
Doxyparse was first used by the [Analizo](https://www.analizo.org) toolkit, a suite
of source code analysis tools, aimed at being language-independent and
extensible, able to extract and calculate a fair number of source code metrics,
generate dependency graphs, and other software evolution analysis.
Academic publications citing Doxyparse:
* https://scholar.google.com.br/scholar?q=doxyparse
## build dependencies
apt-get install flex bison cmake build-essential python
## build
cmake -G "Unix Makefiles" -Dbuild_parse=ON
make
## install
sudo make install
## release
* ensure analizo testsuite passing on newer doxyparse version
* update debian/changelog, commit, push
* create git tag, push to github analizo/doxyparse
* build on amd64 and i386 archs, upload tar.gz to github
* `tar -zcf doxyparse_<VERSION>_amd64.tar.gz -C bin/ doxyparse`
* `tar -zcf doxyparse_<VERSION>_i386.tar.gz -C bin/ doxyparse`
* build debian packages for amd64 and i386, update analizo.org repository
* (see analizo.github.io/README.md file for updating repository instructions)
* upload the deb files to github release tag also
* check if a alien-doxyparse release is necessary and do it on cpan
## Authors
* Antonio Terceiro <terceiro@softwarelivre.org>
* João M. Miranda <joaomm88@gmail.com>
* Joenio Costa <joenio@joenio.me>
* Paulo Meirelles <paulo@softwarelivre.org>
* Vinicius Daros <vkdaros@mercurio.eclipse.ime.usp.br>

View File

@ -0,0 +1,10 @@
.TH DOXYPARSE "1" "DATE" "doxyparse VERSION" "User Commands"
.SH NAME
doxyparse \- parse and dumps information about the code
.SH SYNOPSIS
.B doxyparse
[\fIsource file\fR...]
.SH DESCRIPTION
Parses source code and dumps the dependencies between the code elements.
.SH SEE ALSO
doxygen(1), doxytag(1), doxywizard(1).

View File

@ -0,0 +1,544 @@
/******************************************************************************
*
* Copyright (C) 2009-2015 by Joenio Costa.
*
* Permission to use, copy, modify, and distribute this software and its
* documentation under the terms of the GNU General Public License is hereby
* granted. No representations are made about the suitability of this software
* for any purpose. It is provided "as is" without express or implied warranty.
* See the GNU General Public License for more details.
*
* Documents produced by Doxygen are derivative works derived from the
* input used in their production; they are not affected by this license.
*
*/
/** @file
* @brief Code parse based on doxyapp by Dimitri van Heesch
*
*/
#include <stdlib.h>
#if !defined(_WIN32) || defined(__CYGWIN__)
#include <unistd.h>
#else
#include <windows.h>
#endif
#include "version.h"
#include "doxygen.h"
#include "outputgen.h"
#include "outputlist.h"
#include "parserintf.h"
#include "classlist.h"
#include "config.h"
#include "filedef.h"
#include "util.h"
#include "filename.h"
#include "arguments.h"
#include "memberlist.h"
#include "types.h"
#include <string>
#include <cstdlib>
#include <sstream>
#include <map>
#include "qcstring.h"
#include "namespacedef.h"
#include "portable.h"
#include "dir.h"
class Doxyparse : public OutputCodeIntf
{
public:
Doxyparse(const FileDef *fd) : m_fd(fd) {}
~Doxyparse() {}
// these are just null functions, they can be used to produce a syntax highlighted
// and cross-linked version of the source code, but who needs that anyway ;-)
OutputType type() const override { return OutputType::Extension; }
std::unique_ptr<OutputCodeIntf> clone() override { return std::make_unique<Doxyparse>(m_fd); }
void codify(const QCString &) override {}
void stripCodeComments(bool) override {}
void startSpecialComment() override {}
void endSpecialComment() override {}
void setStripIndentAmount(size_t) override {}
void writeCodeLink(CodeSymbolType,const QCString &,const QCString &,const QCString &,const QCString &,const QCString &) override {}
void startCodeLine(int) override {}
void endCodeLine() override {}
void writeCodeAnchor(const QCString &) override {}
void startFontClass(const QCString &) override {}
void endFontClass() override {}
void writeLineNumber(const QCString &,const QCString &,const QCString &,int,bool) override {}
virtual void writeTooltip(const QCString &,const DocLinkInfo &,
const QCString &,const QCString &,const SourceLinkInfo &,
const SourceLinkInfo &) override {}
void startCodeFragment(const QCString &) override {}
void endCodeFragment(const QCString &) override {}
void startFold(int,const QCString &,const QCString &) override {}
void endFold() override {}
void linkableSymbol(int l, const char *sym, Definition *symDef, Definition *context)
{
if (!symDef) {
// in this case we have a local or external symbol
// TODO record use of external symbols
// TODO must have a way to differentiate external symbols from local variables
}
}
private:
const FileDef *m_fd;
};
static bool is_c_code = true;
static void findXRefSymbols(FileDef *fd)
{
// get the interface to a parser that matches the file extension
auto intf=Doxygen::parserManager->getCodeParser(fd->getDefFileExtension());
// get the programming language from the file name
SrcLangExt lang = getLanguageFromFileName(fd->name());
// reset the parsers state
intf->resetCodeParserState();
// create a new backend object
std::unique_ptr<OutputCodeIntf> parse = std::make_unique<Doxyparse>(fd);
OutputCodeList parseList;
parseList.add(std::move(parse));
// parse the source code
intf->parseCode(parseList, QCString(), fileToString(fd->absFilePath()), lang,
FALSE, FALSE, QCString(), fd);
}
static bool ignoreStaticExternalCall(const MemberDef *context, const MemberDef *md) {
if (md->isStatic()) {
if(md->getFileDef() && context->getFileDef()) {
if(md->getFileDef()->getOutputFileBase() == context->getFileDef()->getOutputFileBase())
// TODO ignore prefix of file
return false;
else
return true;
}
else {
return false;
}
}
else {
return false;
}
}
static void startYamlDocument() {
printf("---\n");
}
static void printFile(std::string file) {
printf("%s:\n", file.c_str());
}
static void printModule(std::string module) {
printf(" \"%s\":\n", unescapeCharsInString(module.c_str()).data());
}
static void printClassInformation(std::string information) {
printf(" information: %s\n", information.c_str());
}
static void printInherits() {
printf(" inherits:\n");
}
static void printInheritance(std::string base_class) {
printf(" - \"%s\"\n", base_class.c_str());
}
static void printDefines() {
printf(" defines:\n");
}
static void printDefinition(std::string type, std::string signature, int line) {
printf(" - \"%s\":\n", signature.substr(0, 1022).c_str());
printf(" type: %s\n", type.c_str());
printf(" line: %d\n", line);
}
static void printProtection(std::string protection) {
printf(" protection: %s\n", protection.c_str());
}
static void printPrototypeYes() {
printf(" prototype: yes\n");
}
static void printNumberOfLines(int lines) {
printf(" lines_of_code: %d\n", lines);
}
static void printNumberOfArguments(size_t arguments) {
printf(" parameters: %zu\n", arguments);
}
static void printUses() {
printf(" uses:\n");
}
static void printReferenceTo(std::string type, std::string signature, std::string defined_in) {
printf(" - \"%s\":\n", signature.substr(0, 1022).c_str());
printf(" type: %s\n", type.c_str());
printf(" defined_in: \"%s\"\n", unescapeCharsInString(defined_in.c_str()).data());
}
static void printNumberOfConditionalPaths(const MemberDef* md) {
printf(" conditional_paths: %d\n", md->numberOfFlowKeyWords());
}
static int isPartOfCStruct(const MemberDef * md) {
return is_c_code && md->getClassDef() != nullptr;
}
std::string sanitizeString(std::string data) {
QCString new_data = QCString(data.c_str());
new_data = substitute(new_data,"\"", "");
new_data = substitute(new_data,"\'", ""); // https://github.com/analizo/analizo/issues/138
return !new_data.isEmpty() ? new_data.data() : "";
}
std::string argumentData(const Argument &argument) {
std::string data = "";
if (argument.type.size() > 1)
data = sanitizeString(argument.type.data());
else if (!argument.name.isEmpty())
data = sanitizeString(argument.name.data());
return data;
}
std::string functionSignature(const MemberDef* md) {
std::string signature = sanitizeString(md->name().data());
if(md->isFunction()){
const ArgumentList &argList = md->argumentList();
signature += "(";
auto it = argList.begin();
if(it!=argList.end()) {
signature += argumentData(*it);
for(++it; it!=argList.end(); ++it) {
signature += std::string(",") + argumentData(*it);
}
}
signature += ")";
}
return signature;
}
static void referenceTo(const MemberDef* md) {
std::string type = md->memberTypeName().data();
std::string defined_in = "";
std::string signature = "";
if (isPartOfCStruct(md)) {
signature = md->getClassDef()->name().data() + std::string("::") + functionSignature(md);
defined_in = md->getClassDef()->getFileDef()->getOutputFileBase().data();
}
else {
signature = functionSignature(md);
if (md->getClassDef()) {
defined_in = md->getClassDef()->name().data();
}
else if (md->getFileDef()) {
defined_in = md->getFileDef()->getOutputFileBase().data();
}
else if (md->getNamespaceDef()) {
defined_in = md->getNamespaceDef()->name().data();
}
}
printReferenceTo(type, signature, defined_in);
}
void protectionInformation(Protection protection) {
if (protection == Protection::Public) {
printProtection("public");
}
else if (protection == Protection::Protected) {
printProtection("protected");
}
else if (protection == Protection::Private) {
printProtection("private");
}
else if (protection == Protection::Package) {
printProtection("package");
}
}
void cModule(const ClassDef* cd) {
const MemberList* ml = cd->getMemberList(MemberListType::VariableMembers());
if (ml) {
const FileDef *fd = cd->getFileDef();
const MemberList *fd_ml = fd->getMemberList(MemberListType::AllMembersList());
if (!fd_ml || fd_ml->size() == 0) {
printModule(fd->getOutputFileBase().data());
printDefines();
}
for (const auto &md : *ml) {
printDefinition("variable", cd->name().data() + std::string("::") + md->name().data(), md->getDefLine());
protectionInformation(md->protection());
}
}
}
static bool checkOverrideArg(const ArgumentList &argList, const MemberDef *md) {
if(!md->isFunction() || argList.empty()){
return false;
}
for (const Argument &argument : argList) {
if(md->name() == argument.name) {
return true;
}
}
return false;
}
void functionInformation(const MemberDef* md) {
std::string temp = "";
int size = md->getEndBodyLine() - md->getStartBodyLine() + 1;
printNumberOfLines(size);
const ArgumentList &argList = md->argumentList();
if (!argList.empty())
{
temp = argumentData(argList.front());
// TODO: This is a workaround; better not include "void" in argList, in the first place.
if (temp!="void")
{
printNumberOfArguments(argList.size());
}
}
printNumberOfConditionalPaths(md);
auto refList = md->getReferencesMembers();
if (!refList.empty()) {
printUses();
for (const auto &rmd : refList) {
if (rmd->definitionType() == Definition::TypeMember && !ignoreStaticExternalCall(md, rmd) && !checkOverrideArg(argList, rmd)) {
referenceTo(rmd);
}
}
}
}
void prototypeInformation(const MemberDef* md) {
printPrototypeYes();
const ArgumentList &argList = md->argumentList();
printNumberOfArguments(argList.size());
}
static void lookupSymbol(const Definition *d) {
if (d->definitionType() == Definition::TypeMember) {
const MemberDef *md = dynamic_cast<const MemberDef*>(d);
std::string type = md->memberTypeName().data();
std::string signature = functionSignature(md);
printDefinition(type, signature, md->getDefLine());
protectionInformation(md->protection());
if (md->isFunction() && md->isPrototype()) {
prototypeInformation(md);
}
else if (md->isFunction()) {
functionInformation(md);
}
}
}
void listMembers(const MemberList *ml) {
if (ml) {
for (const auto &md : *ml) {
lookupSymbol((Definition*) md);
}
}
}
void listAllMembers(const ClassDef* cd) {
// methods
listMembers(cd->getMemberList(MemberListType::FunctionMembers()));
// constructors
listMembers(cd->getMemberList(MemberListType::Constructors()));
// attributes
listMembers(cd->getMemberList(MemberListType::VariableMembers()));
}
static void classInformation(const ClassDef* cd) {
if (is_c_code) {
cModule(cd);
} else {
printModule(cd->name().data());
if (!cd->baseClasses().empty()) {
printInherits();
for (const auto &bcd : cd->baseClasses()) {
printInheritance(sanitizeString(bcd.classDef->name().data()));
}
}
if(cd->isAbstract()) {
printClassInformation("abstract class");
}
printDefines();
listAllMembers(cd);
}
}
static bool checkLanguage(std::string& filename, std::string extension) {
if (filename.find(extension, filename.size() - extension.size()) != std::string::npos) {
return true;
} else {
return false;
}
}
/* Detects the programming language of the project. Actually, we only care
* about whether it is a C project or not. */
static void detectProgrammingLanguage(FileNameLinkedMap &fnli) {
for (const auto &fn : fnli) {
std::string filename = fn->fileName().str();
if (
checkLanguage(filename, ".cc") ||
checkLanguage(filename, ".cxx") ||
checkLanguage(filename, ".cpp") ||
checkLanguage(filename, ".java") ||
checkLanguage(filename, ".py") ||
checkLanguage(filename, ".pyw") ||
checkLanguage(filename, ".cs")
) {
is_c_code = false;
}
}
}
static void listSymbols() {
detectProgrammingLanguage(*Doxygen::inputNameLinkedMap);
// iterate over the input files
for (const auto &fn : *Doxygen::inputNameLinkedMap) {
for (const auto &fd : *fn) {
printFile(fd->absFilePath().data());
MemberList *ml = fd->getMemberList(MemberListType::AllMembersList());
if (ml && ml->size() > 0) {
printModule(fd->getOutputFileBase().data());
printDefines();
listMembers(ml);
}
ClassDefSet visitedClasses;
for (const auto &cd : fd->getClasses()) {
if (visitedClasses.find(cd)==visitedClasses.end()) {
classInformation(cd);
visitedClasses.insert(cd);
}
}
}
}
// TODO print external symbols referenced
}
int main(int argc,char **argv) {
int locArgc = argc;
if (locArgc == 2)
{
if (!strcmp(argv[1],"--help"))
{
printf("Usage: %s [source_file | source_dir]\n",argv[0]);
exit(0);
}
else if (!strcmp(argv[1],"--version"))
{
printf("%s version: %s\n",argv[0],getFullVersion().c_str());
exit(0);
}
}
if (locArgc!=2)
{
printf("Usage: %s [source_file | source_dir]\n",argv[0]);
exit(1);
}
// initialize data structures
initDoxygen();
// check and finalize the configuration
checkConfiguration();
adjustConfiguration();
// setup the non-default configuration options
// we need a place to put intermediate files
std::ostringstream tmpdir;
unsigned int pid = Portable::pid();
if (!Portable::getenv("TMP").isEmpty())
tmpdir << Portable::getenv("TMP") << "/doxyparse-" << pid;
else if (!Portable::getenv("TEMP").isEmpty())
tmpdir << Portable::getenv("TEMP") << "/doxyparse-" << pid;
else
tmpdir << "doxyparse-" << pid;
Config_updateString(OUTPUT_DIRECTORY,tmpdir.str().c_str());
// enable HTML (fake) output to omit warning about missing output format
Config_updateBool(GENERATE_HTML,TRUE);
// disable latex output
Config_updateBool(GENERATE_LATEX,FALSE);
// be quiet
Config_updateBool(QUIET,TRUE);
// turn off warnings
Config_updateBool(WARNINGS,FALSE);
Config_updateBool(WARN_IF_UNDOCUMENTED,FALSE);
Config_updateBool(WARN_IF_DOC_ERROR,FALSE);
Config_updateBool(WARN_IF_UNDOC_ENUM_VAL,FALSE);
// Extract as much as possible
Config_updateBool(EXTRACT_ALL,TRUE);
Config_updateBool(EXTRACT_STATIC,TRUE);
Config_updateBool(EXTRACT_PRIVATE,TRUE);
Config_updateBool(EXTRACT_LOCAL_METHODS,TRUE);
Config_updateBool(EXTRACT_PACKAGE,TRUE);
// Extract source browse information, needed
// to make doxygen gather the cross reference info
Config_updateBool(SOURCE_BROWSER,TRUE);
// find functions call between modules
Config_updateBool(CALL_GRAPH,TRUE);
// loop recursive over input files
Config_updateBool(RECURSIVE,TRUE);
// add file extensions
Config_updateList(FILE_PATTERNS, { "*.cc", "*.cxx", "*.cpp", "*.java",
"*.py", "*.pyw", "*.cs", "*.c", "*.h", "*.hh", "*.hpp"});
// set the input
StringVector inputList;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "-") == 0) {
char filename[1024];
while (1) {
(void)scanf("%s[^\n]", filename);
if (feof(stdin)) {
break;
}
inputList.push_back(filename);
}
} else {
inputList.push_back(argv[i]);
}
}
Config_updateList(INPUT,inputList);
if (inputList.empty()) {
exit(0);
}
// parse the files
parseInput();
// iterate over the input files
for (const auto &fn : *Doxygen::inputNameLinkedMap) {
for (const auto &fd : *fn) {
// get the references (linked and unlinked) found in this file
findXRefSymbols(fd.get());
}
}
Dir thisDir;
// remove temporary files
if (!Doxygen::filterDBFileName.isEmpty()) thisDir.remove(Doxygen::filterDBFileName.str());
// clean up after us
thisDir.rmdir(Config_getString(OUTPUT_DIRECTORY).str());
startYamlDocument();
listSymbols();
std::string cleanup_command = "rm -rf ";
cleanup_command += tmpdir.str();
(void)system(cleanup_command.c_str());
exit(0);
}

397
addon/doxypysql/search.py Executable file
View File

@ -0,0 +1,397 @@
#!/usr/bin/env python
# python script to search through doxygen_sqlite3.db
#
# Permission to use, copy, modify, and distribute this software and its
# documentation under the terms of the GNU General Public License is hereby
# granted. No representations are made about the suitability of this software
# for any purpose. It is provided "as is" without express or implied warranty.
# See the GNU General Public License for more details.
#
import sqlite3
import sys
import os
import getopt
import json
import re
class MemberType:
Define="macro definition"
Function="function"
Variable="variable"
Typedef="typedef"
Enumeration="enumeration"
EnumValue="enumvalue"
Signal="signal"
Slot="slot"
Friend="friend"
DCOP="dcop"
Property="property"
Event="event"
File="file"
class RequestType:
References="9901"
Struct="9902"
Includers="9903"
Includees="9904"
Members="9905"
BaseClasses="9906"
SubClasses="9907"
g_use_regexp=False
###############################################################################
# case-insensitive sqlite regexp function
def re_fn(expr, item):
reg = re.compile(expr, re.I)
return reg.search(item) is not None
def openDb(dbname):
if dbname is None:
dbname = "doxygen_sqlite3.db"
if not os.path.isfile(dbname):
raise BaseException("No such file %s" % dbname )
conn = sqlite3.connect(dbname)
conn.execute('PRAGMA temp_store = MEMORY;')
conn.row_factory = sqlite3.Row
conn.create_function("REGEXP", 2, re_fn)
return conn
###############################################################################
class Finder:
def __init__(self,cn,name,row_type=str):
self.cn=cn
self.name=name
self.row_type=row_type
def match(self,row):
if self.row_type is int:
return " rowid=?"
else:
if g_use_regexp:
return " REGEXP (?,%s)" %row
else:
return " %s=?" %row
def fileName(self,file_id):
if self.cn.execute("SELECT COUNT(*) FROM path WHERE rowid=?",[file_id]).fetchone()[0] > 1:
sys.stderr.write("WARNING: non-uniq fileid [%s]. Considering only the first match." % file_id)
for r in self.cn.execute("SELECT * FROM path WHERE rowid=?",[file_id]).fetchall():
return r['name']
return ""
def fileId(self,name):
if self.cn.execute("SELECT COUNT(*) FROM path WHERE"+self.match("name"),[name]).fetchone()[0] > 1:
sys.stderr.write("WARNING: non-uniq file name [%s]. Considering only the first match." % name)
for r in self.cn.execute("SELECT rowid FROM path WHERE"+self.match("name"),[name]).fetchall():
return r[0]
return -1
###############################################################################
def references(self):
o=[]
cur = self.cn.cursor()
cur.execute("SELECT rowid FROM memberdef WHERE"+self.match("name"),[self.name])
rowids = cur.fetchall()
if len(rowids) == 0:
return o
rowid = rowids[0]['rowid']
cur = self.cn.cursor()
#TODO:SELECT rowid from refid where refid=refid
for info in cur.execute("SELECT * FROM xrefs WHERE dst_rowid=?", [rowid]):
item={}
cur = self.cn.cursor()
for i2 in cur.execute("SELECT * FROM memberdef WHERE rowid=?",[info['src_rowid']]):
item['name']=i2['name']
item['src']=info['src_rowid']
# Below no longer directly supported on this entry; can be found from either memberdef
#item['file']=self.fileName(info['file_id'])
#item['line']=info['line']
o.append(item)
return o
###############################################################################
def function(self):
o=[]
c=self.cn.execute('SELECT * FROM memberdef WHERE'+self.match("name")+' AND kind=?',[self.name,MemberType.Function])
for r in c.fetchall():
item={}
item['name'] = r['name']
item['definition'] = r['definition']
item['argsstring'] = r['argsstring']
item['file'] = self.fileName(r['file_id'])
item['line'] = r['line']
item['detaileddescription'] = r['detaileddescription']
o.append(item)
return o
###############################################################################
def file(self):
o=[]
for r in self.cn.execute("SELECT rowid,name FROM local_file WHERE"+self.match("name"),[self.name]).fetchall():
item={}
item['name'] = r['name']
item['id'] = r['rowid']
o.append(item)
return o
###############################################################################
def macro(self):
o=[]
c=self.cn.execute('SELECT * FROM memberdef WHERE'+self.match("name")+' AND kind=?',[self.name,MemberType.Define])
for r in c.fetchall():
item={}
item['name'] = r['name']
if r['argsstring']:
item['argsstring'] = r['argsstring']
item['definition'] = r['initializer']
item['file'] = self.fileName(r['file_id'])
item['line'] = r['line']
o.append(item)
return o
###############################################################################
def typedef(self):
o=[]
c=self.cn.execute('SELECT * FROM memberdef WHERE'+self.match("name")+' AND kind=?',[self.name,MemberType.Typedef])
for r in c.fetchall():
item={}
item['name'] = r['name']
item['definition'] = r['definition']
item['file'] = self.fileName(r['file_id'])
item['line'] = r['line']
o.append(item)
return o
###############################################################################
def variable(self):
o=[]
c=self.cn.execute('SELECT * FROM memberdef WHERE'+self.match("name")+' AND kind=?',[self.name,MemberType.Variable])
for r in c.fetchall():
item={}
item['name'] = r['name']
item['definition'] = r['definition']
item['file'] = self.fileName(r['file_id'])
item['line'] = r['line']
o.append(item)
return o
###############################################################################
def params(self):
o=[]
c=self.cn.execute('SELECT rowid FROM memberdef WHERE'+self.match("name"),[self.name])
for r in c.fetchall():
#a=("SELECT * FROM param where id=(SELECT param_id FROM memberdef_param where memberdef_id=?",[memberdef_id])
item={}
item['id'] = r['id']
o.append(item)
return o
###############################################################################
def struct(self):
o=[]
c=self.cn.execute('SELECT * FROM compounddef WHERE'+self.match("name"),[self.name])
for r in c.fetchall():
item={}
item['name'] = r['name']
o.append(item)
return o
###############################################################################
def includers(self):
o=[]
fid = self.fileId(self.name)
c=self.cn.execute('SELECT * FROM includes WHERE dst_id=?',[fid])
for r in c.fetchall():
item={}
item['name'] = self.fileName(r['src_id'])
o.append(item)
return o
###############################################################################
def includees(self):
o=[]
fid = self.fileId(self.name)
c=self.cn.execute('SELECT * FROM includes WHERE src_id=?',[fid])
for r in c.fetchall():
item={}
item['name'] = self.fileName(r['dst_id'])
o.append(item)
return o
###############################################################################
def members(self):
o=[]
c=self.cn.execute('SELECT * FROM memberdef WHERE'+self.match("scope"),[self.name])
for r in c.fetchall():
item={}
item['name'] = r['name']
item['definition'] = r['definition']
item['argsstring'] = r['argsstring']
item['file'] = self.fileName(r['file_id'])
item['line'] = r['line']
#item['documentation'] = r['documentation']
o.append(item)
return o
###############################################################################
def baseClasses(self):
o=[]
c=self.cn.execute('SELECT compounddef.name FROM compounddef JOIN compoundref ON compounddef.rowid=compoundref.base_rowid WHERE compoundref.derived_rowid IN (SELECT rowid FROM compounddef WHERE'+self.match("name")+')',[self.name])
for r in c.fetchall():
item={}
item['name'] = r['name']
o.append(item)
return o
###############################################################################
def subClasses(self):
o=[]
c=self.cn.execute('SELECT compounddef.name FROM compounddef JOIN compoundref ON compounddef.rowid=compoundref.derived_rowid WHERE compoundref.base_rowid IN (SELECT rowid FROM compounddef WHERE'+self.match("name")+')',[self.name])
for r in c.fetchall():
item={}
item['name'] = r['name']
o.append(item)
return o
###############################################################################
def process(f,kind):
request_processors = {
MemberType.Function: f.function,
MemberType.File: f.file,
MemberType.Define: f.macro,
MemberType.Variable: f.variable,
MemberType.Typedef: f.typedef,
RequestType.References: f.references,
RequestType.Struct: f.struct,
RequestType.Includers: f.includers,
RequestType.Includees: f.includees,
RequestType.Members: f.members,
RequestType.BaseClasses: f.baseClasses,
RequestType.SubClasses: f.subClasses
}
return request_processors[kind]()
###############################################################################
# the -H option isn't documented. It's one of the more recent additions, but it's treating refids as if they would be a string. I'm just taking a stab at updating it for now, converting to use rowid, and making other edits necessary to get it to run.
def processHref(cn,ref):
j={}
# is it in memberdef ?
table="memberdef"
if ( cn.execute("SELECT count(*) from %s WHERE rowid=?"%table,[ref] ).fetchone()[0] > 0 ):
for r in cn.execute("SELECT kind,rowid FROM %s WHERE rowid=?" % table,[ref]).fetchall():
f=Finder(cn,r['rowid'],int)
j=process(f,str(r['kind']))
# is it in compounddef ?
table="compounddef"
if ( cn.execute("SELECT count(*) from %s WHERE rowid=?"%table,[ref]).fetchone()[0] > 0 ):
for r in cn.execute("SELECT rowid FROM %s WHERE rowid=?"%table,[ref] ).fetchall():
f=Finder(cn,r[0],int)
j=process(f,RequestType.Struct)
return j
###############################################################################
def serveCgi():
import cgi
print('Content-Type: application/json\n')
fieldStorage = cgi.FieldStorage()
form = dict((key, fieldStorage.getvalue(key)) for key in fieldStorage.keys())
if 'href' in form:
ref = form['href']
else:
print('{"result": null, "error": "no refid given"}')
sys.exit(0)
cn=openDb('doxygen_sqlite3.db')
j = processHref(cn,ref)
print(json.dumps({"result":j,"error":None}))
###############################################################################
def usage():
sys.stderr.write("""Usage: search.py [Options]
Options:
-h, --help
-d <D> Use database <D> for queries.
-f <F> Search for definition of function <F>.
-m <M> Search for definition of macro <M>.
-r <F> Search for references to function <F>.
-t <T> Search for definition of type <T>.
-v <V> Search for definition of variable <V>.
-I <I> What files are including <I>.
-i <i> What files are included by <i>.
-B <C> Get the base classes of class <C>.
-M <C> Get all members of class <C>.
-S <C> Get the sub classes of class <C>.
-R Consider the search <term> to be a regex.
""")
###############################################################################
def serveCli(argv):
try:
opts, args = getopt.getopt(argv, "hr:RI:i:d:f:m:t:v:H:M:B:S:F:",["help"])
except getopt.GetoptError:
usage()
sys.exit(1)
ref=None
dbname=None
j={}
global g_use_regexp
for a, o in opts:
if a in ('-h', '--help'):
usage()
sys.exit(0)
elif a in ('-d'):
dbname=o
continue
elif a in ('-r'):
kind=RequestType.References
elif a in ('-R'):
g_use_regexp=True
continue
elif a in ('-I'):
kind=RequestType.Includers
elif a in ('-i'):
kind=RequestType.Includees
elif a in ('-M'):
kind=RequestType.Members
elif a in ('-B'):
kind=RequestType.BaseClasses
elif a in ('-S'):
kind=RequestType.SubClasses
elif a in ('-f'):
kind=MemberType.Function
elif a in ('-F'):
# undocumented
# seems to fit with the lower case "search" patterns?
kind=MemberType.File
elif a in ('-m'):
kind=MemberType.Define
elif a in ('-t'):
kind=MemberType.Typedef
elif a in ('-v'):
kind=MemberType.Variable
elif a in ('-H'):
# undocumented
ref = o
cn=openDb(dbname)
f=Finder(cn,o)
if ref is not None:
j=processHref(cn,ref)
else:
j=process(f,kind)
print(json.dumps(j,indent=4))
def main(argv):
if 'REQUEST_METHOD' in os.environ:
serveCgi()
else:
serveCli(argv)
if __name__ == '__main__':
main(sys.argv[1:])

View File

@ -0,0 +1,45 @@
find_package(xapian REQUIRED)
find_package(ZLIB REQUIRED)
if (WIN32)
set(WIN_EXTRA_LIBS uuid rpcrt4 ws2_32)
endif()
include_directories(
${PROJECT_SOURCE_DIR}/libversion
${PROJECT_SOURCE_DIR}/libxml
${XAPIAN_INCLUDE_DIR}
${ZLIB_INCLUDE_DIRS}
)
add_executable(doxyindexer
doxyindexer.cpp
${PROJECT_SOURCE_DIR}/templates/icon/doxygen.rc
)
target_link_libraries(doxyindexer
${XAPIAN_LIBRARIES}
${ZLIB_LIBRARIES}
${WIN_EXTRA_LIBS}
${COVERAGE_LINKER_FLAGS}
doxygen_version
xml
)
add_executable(doxysearch.cgi
doxysearch.cpp
${PROJECT_SOURCE_DIR}/templates/icon/doxygen.rc
)
target_link_libraries(doxysearch.cgi
doxygen_version
${XAPIAN_LIBRARIES}
${ZLIB_LIBRARIES}
${WIN_EXTRA_LIBS}
)
include(ApplyEditbin)
apply_editbin(doxyindexer console)
apply_editbin(doxysearch.cgi console)
install(TARGETS doxyindexer doxysearch.cgi DESTINATION bin)

View File

@ -0,0 +1,384 @@
/******************************************************************************
*
* Copyright (C) 1997-2015 by Dimitri van Heesch.
*
* Permission to use, copy, modify, and distribute this software and its
* documentation under the terms of the GNU General Public License is hereby
* granted. No representations are made about the suitability of this software
* for any purpose. It is provided "as is" without express or implied warranty.
* See the GNU General Public License for more details.
*
* Documents produced by Doxygen are derivative works derived from the
* input used in their production; they are not affected by this license.
*
*/
// STL includes
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <string>
#include <sstream>
#include <fstream>
#include <iterator>
#include <regex>
#include <sys/stat.h>
// Xapian include
#include <xapian.h>
#include "version.h"
#include "xml.h"
#define MAX_TERM_LENGTH 245
#if defined(_WIN32) && !defined(__CYGWIN__)
static char pathSep = '\\';
#else
static char pathSep = '/';
#endif
static void safeAddTerm(const std::string &term,Xapian::Document &doc,int wfd)
{
if (term.length()<=MAX_TERM_LENGTH) doc.add_term(term,wfd);
}
/** trims \a whitespace characters from the start and end of string \a str. */
static std::string trim(const std::string& str,
const std::string& whitespace = " \t")
{
size_t strBegin = str.find_first_not_of(whitespace);
if (strBegin == std::string::npos)
return ""; // no content
size_t strEnd = str.find_last_not_of(whitespace);
size_t strRange = strEnd - strBegin + 1;
return str.substr(strBegin, strRange);
}
/** trims \a whitespace from start and end and replace occurrences of
* \a whitespace with \a fill.
*/
static std::string reduce(const std::string& str,
const std::string& fill = " ",
const std::string& whitespace = " \t")
{
// trim first
std::string result = trim(str, whitespace);
// replace sub ranges
size_t beginSpace = result.find_first_of(whitespace);
while (beginSpace != std::string::npos)
{
size_t endSpace = result.find_first_not_of(whitespace, beginSpace);
size_t range = endSpace - beginSpace;
result.replace(beginSpace, range, fill);
size_t newStart = beginSpace + fill.length();
beginSpace = result.find_first_of(whitespace, newStart);
}
return result;
}
/** Adds all words in \a s to document \a doc with weight \a wfd */
static void addWords(const std::string &s,Xapian::Document &doc,int wfd)
{
std::istringstream iss(s);
std::istream_iterator<std::string> begin(iss),end,it;
for (it=begin;it!=end;++it)
{
const std::string word = *it;
const std::string lword = Xapian::Unicode::tolower(word);
safeAddTerm(word,doc,wfd);
if (lword!=word)
{
safeAddTerm(lword,doc,wfd);
}
}
}
/** Adds all identifiers in \a s to document \a doc with weight \a wfd */
static void addIdentifiers(const std::string &s,Xapian::Document &doc,int wfd)
{
std::regex id_re("[A-Z_a-z][A-Z_a-z0-9]*");
auto id_begin = std::sregex_iterator(s.begin(), s.end(), id_re);
auto id_end = std::sregex_iterator();
for (auto i = id_begin; i!=id_end; ++i)
{
std::smatch match = *i;
safeAddTerm(match.str(),doc,wfd);
}
}
/** Replaces all occurrences of \a old with \a repl in string \a str */
static void replace_all(std::string& str, const std::string& old, const std::string& repl)
{
size_t pos = 0;
while ((pos = str.find(old, pos)) != std::string::npos)
{
str.replace(pos, old.length(), repl);
pos += repl.length();
}
}
/** Replaces all XML entities in \a s with their unescaped representation */
static std::string unescapeXmlEntities(const std::string &s)
{
std::string result=s;
replace_all(result,"&gt;",">");
replace_all(result,"&lt;","<");
replace_all(result,"&apos;","'");
replace_all(result,"&quot;","\"");
replace_all(result,"&amp;","&");
return result;
}
/** This class is a wrapper around SAX style XML parser, which
* parses the file without first building a DOM tree in memory.
*/
class XMLContentHandler
{
public:
/** Handler for parsing XML data */
XMLContentHandler(const std::string &path)
: m_db(path+"doxysearch.db",Xapian::DB_CREATE_OR_OVERWRITE),
m_stemmer("english")
{
m_curFieldName = UnknownField;
m_indexer.set_stemmer(m_stemmer);
m_indexer.set_document(m_doc);
}
/** Free data handler */
~XMLContentHandler()
{
m_db.commit();
}
enum FieldNames
{
UnknownField = 0,
TypeField = 1,
NameField = 2,
ArgsField = 3,
TagField = 4,
UrlField = 5,
KeywordField = 6,
TextField = 7
};
/** Handler for a start tag. Called for `<doc>` and `<field>` tags */
void startElement(const std::string &name, const XMLHandlers::Attributes &attrib)
{
m_data="";
if (name=="field")
{
std::string fieldName = XMLHandlers::value(attrib,"name");
if (fieldName=="type") m_curFieldName=TypeField;
else if (fieldName=="name") m_curFieldName=NameField;
else if (fieldName=="args") m_curFieldName=ArgsField;
else if (fieldName=="tag") m_curFieldName=TagField;
else if (fieldName=="url") m_curFieldName=UrlField;
else if (fieldName=="keywords") m_curFieldName=KeywordField;
else if (fieldName=="text") m_curFieldName=TextField;
else m_curFieldName=UnknownField;
}
}
/** Handler for an end tag. Called for `</doc>` and `</field>` tags */
void endElement(const std::string &name)
{
if (name=="doc") // </doc>
{
std::string term = m_doc.get_value(NameField);
std::string partTerm;
size_t pos = term.rfind("::");
if (pos!=std::string::npos)
{
partTerm = term.substr(pos+2);
}
if (m_doc.get_value(TypeField)=="class" ||
m_doc.get_value(TypeField)=="file" ||
m_doc.get_value(TypeField)=="namespace") // containers get highest prio
{
safeAddTerm(term,m_doc,1000);
if (!partTerm.empty())
{
safeAddTerm(partTerm,m_doc,500);
}
}
else // members and others get lower prio
{
safeAddTerm(m_doc.get_value(NameField),m_doc,100);
if (!partTerm.empty())
{
safeAddTerm(partTerm,m_doc,50);
}
}
m_db.add_document(m_doc);
m_doc.clear_values();
m_doc.clear_terms();
}
else if (name=="field" && m_curFieldName!=UnknownField) // </field>
{
// strip whitespace from m_data
m_data = reduce(m_data);
// replace XML entities
m_data = unescapeXmlEntities(m_data);
// add data to the document
m_doc.add_value(m_curFieldName,m_data);
switch (m_curFieldName)
{
case TypeField:
case NameField:
case TagField:
case UrlField:
// meta data that is not searchable
break;
case KeywordField:
addWords(m_data,m_doc,50);
break;
case ArgsField:
addIdentifiers(m_data,m_doc,10);
break;
case TextField:
addWords(m_data,m_doc,2);
break;
default:
break;
}
m_data="";
m_curFieldName=UnknownField;
}
// reset m_data
}
/** Handler for inline text */
void characters(const std::string& ch)
{
m_data += ch;
}
void error(const std::string &fileName,int lineNr,const std::string &msg)
{
std::cerr << "Fatal error at " << fileName << ":" << lineNr << ": " << msg << std::endl;
}
private:
// internal state
Xapian::WritableDatabase m_db;
Xapian::Document m_doc;
Xapian::TermGenerator m_indexer;
Xapian::Stem m_stemmer;
std::string m_data;
FieldNames m_curFieldName;
};
static void usage(const char *name, int exitVal = 1)
{
std::cerr << "Usage: " << name << " [-o output_dir] searchdata.xml [searchdata2.xml ...]" << std::endl;
exit(exitVal);
}
// return the contents of a file as a string
inline std::string fileToString(const std::string &fileName)
{
std::ifstream t(fileName);
std::string result;
t.seekg(0, std::ios::end);
result.reserve(t.tellg());
t.seekg(0, std::ios::beg);
result.assign(std::istreambuf_iterator<char>(t),
std::istreambuf_iterator<char>());
return result;
}
bool dirExists(const char *path)
{
struct stat info = {};
return stat(path,&info)==0 && (info.st_mode&S_IFDIR);
}
/** main function to index data */
int main(int argc,const char **argv)
{
if (argc<2)
{
usage(argv[0]);
}
std::string outputDir;
for (int i=1;i<argc;i++)
{
if (std::string(argv[i])=="-o")
{
if (i>=argc-1)
{
std::cerr << "Error: missing parameter for -o option" << std::endl;
usage(argv[0]);
}
else
{
i++;
outputDir=argv[i];
if (!dirExists(outputDir.c_str()))
{
std::cerr << "Error: specified output directory does not exist!" << std::endl;
usage(argv[0]);
}
}
}
else if (std::string(argv[i])=="-h" || std::string(argv[i])=="--help")
{
usage(argv[0],0);
}
else if (std::string(argv[i])=="-v" || std::string(argv[i])=="--version")
{
std::cerr << argv[0] << " version: " << getFullVersion() << std::endl;
exit(0);
}
}
try
{
if (!outputDir.empty() && outputDir.at(outputDir.length()-1)!=pathSep)
{
outputDir+=pathSep;
}
XMLContentHandler contentHandler(outputDir);
XMLHandlers handlers;
handlers.startElement = [&contentHandler](const std::string &name,const XMLHandlers::Attributes &attrs) { contentHandler.startElement(name,attrs); };
handlers.endElement = [&contentHandler](const std::string &name) { contentHandler.endElement(name); };
handlers.characters = [&contentHandler](const std::string &chars) { contentHandler.characters(chars); };
handlers.error = [&contentHandler](const std::string &fileName,int lineNr,const std::string &msg) { contentHandler.error(fileName,lineNr,msg); };
for (int i=1;i<argc;i++)
{
if (std::string(argv[i])=="-o")
{
i++;
}
else
{
std::cout << "Processing " << argv[i] << "..." << std::endl;
std::string inputStr = fileToString(argv[i]);
XMLParser parser(handlers);
parser.parse(argv[i],inputStr.c_str(),false,[](){},[](){});
}
}
}
catch(const Xapian::Error &e)
{
std::cerr << "Caught exception: " << e.get_description() << std::endl;
}
catch(...)
{
std::cerr << "Caught an unknown exception" << std::endl;
}
return 0;
}

View File

@ -0,0 +1,472 @@
/******************************************************************************
*
* Copyright (C) 1997-2022 by Dimitri van Heesch.
*
* Permission to use, copy, modify, and distribute this software and its
* documentation under the terms of the GNU General Public License is hereby
* granted. No representations are made about the suitability of this software
* for any purpose. It is provided "as is" without express or implied warranty.
* See the GNU General Public License for more details.
*
* Documents produced by Doxygen are derivative works derived from the
* input used in their production; they are not affected by this license.
*
*/
// STL includes
#include <cstdio>
#include <cstdlib>
#include <string>
#include <vector>
#include <sstream>
#include <iostream>
#include <fstream>
#include <string>
#include <algorithm>
// Xapian includes
#include <xapian.h>
#include "version.h"
#ifdef _WIN32
#include <windows.h>
#else
#include <sys/stat.h>
#endif
#define FIELD_TYPE 1
#define FIELD_NAME 2
#define FIELD_ARGS 3
#define FIELD_TAG 4
#define FIELD_URL 5
#define FIELD_KEYW 6
#define FIELD_DOC 7
#define HEX2DEC(x) (((x)>='0' && (x)<='9')?((x)-'0'):\
((x)>='a' && (x)<='f')?((x)-'a'+10):\
((x)>='A' && (x)<='F')?((x)-'A'+10):-1)
bool dirExists(const std::string& dirName)
{
#ifdef _WIN32
DWORD ftyp = GetFileAttributesA(dirName.c_str());
if (ftyp == INVALID_FILE_ATTRIBUTES)
return false; //something is wrong with your path!
if (ftyp & FILE_ATTRIBUTE_DIRECTORY)
return true; // this is a directory!
#else
struct stat sb;
if (stat(dirName.c_str(), &sb)==0 && S_ISDIR(sb.st_mode))
{
return true;
}
#endif
return false;
}
/** decodes a URI encoded string into a normal string. */
static std::string uriDecode(const std::string & sSrc)
{
// Note from RFC1630: "Sequences which start with a percent
// sign but are not followed by two hexadecimal characters
// (0-9, A-F) are reserved for future extension"
const unsigned char * pSrc = (const unsigned char *)sSrc.c_str();
const size_t SRC_LEN = sSrc.length();
const unsigned char * const SRC_END = pSrc + SRC_LEN;
// last decodable '%'
const unsigned char * const SRC_LAST_DEC = SRC_END - 2;
char * const pStart = new char[SRC_LEN];
char * pEnd = pStart;
while (pSrc < SRC_LAST_DEC)
{
if (*pSrc == '%') // replace %2A with corresponding ASCII character
{
char dec1, dec2;
unsigned char c1=*(pSrc+1);
unsigned char c2=*(pSrc+2);
if (-1 != (dec1 = HEX2DEC(c1))
&& -1 != (dec2 = HEX2DEC(c2)))
{
*pEnd++ = (dec1 << 4) + dec2;
pSrc += 3;
continue;
}
}
else if (*pSrc == '+') // replace '+' with space
{
*pEnd++ = ' '; pSrc++;
continue;
}
*pEnd++ = *pSrc++;
}
// the last 2- chars
while (pSrc < SRC_END) *pEnd++ = *pSrc++;
std::string sResult(pStart, pEnd);
delete [] pStart;
return sResult;
}
/** return list of strings that result when splitting \a s using
* delimiter \a delim
*/
static std::vector<std::string> split(const std::string &s, char delim)
{
std::vector<std::string> elems;
std::stringstream ss(s);
std::string item;
while (getline(ss, item, delim)) elems.push_back(item);
return elems;
}
/** Read type T from string \a s */
template<class T>
T fromString(const std::string& s)
{
std::istringstream stream (s);
T t;
stream >> t;
return t;
}
/** Class that holds the starting position of a word */
struct WordPosition
{
WordPosition(size_t s,size_t i) : start(s), index(i) {}
size_t start;
size_t index;
};
/** Class representing the '<' operator for WordPosition objects based on position. */
struct WordPosition_less
{
bool operator()(const WordPosition &p1,const WordPosition &p2) const
{
return p1.start<p2.start;
}
};
/** Class that holds a text fragment */
struct Fragment
{
Fragment(const std::string &t,int occ) : text(t), occurrences(occ) {}
std::string text;
int occurrences;
};
/** Class representing the '>' operator for Fragment objects based on occurrence. */
struct Fragment_greater
{
bool operator()(const Fragment &p1,const Fragment &p2) const
{
return p1.occurrences>p2.occurrences;
}
};
/** Class representing a range within a string */
struct Range
{
Range(size_t s,size_t e) : start(s), end(e) {}
size_t start;
size_t end;
};
/** Returns true if [start..start+len] is inside one of the \a ranges. */
static bool insideRange(const std::vector<Range> &ranges,size_t start,size_t len)
{
for (std::vector<Range>::const_iterator it = ranges.begin();
it!=ranges.end(); ++it
)
{
Range r = *it;
if (start>=r.start && start+len<r.end)
{
return true;
}
}
return false;
}
/** Returns a list of text \a fragments from \a s containing one or
* more \a words. The list is sorted according to the
* number of occurrences of words within the fragment.
*/
static void highlighter(const std::string &s,
const std::vector<std::string> &words,
std::vector<Fragment> &fragments)
{
const std::string spanStart="<span class=\"hl\">";
const std::string spanEnd="</span>";
const std::string dots="...";
const size_t fragLen = 60;
size_t sl=s.length();
// find positions of words in s
int j=0;
std::vector<WordPosition> positions;
for (std::vector<std::string>::const_iterator it=words.begin();
it!=words.end();
++it,++j
)
{
size_t pos=0;
size_t i;
std::string word = *it;
while ((i=s.find(word,pos))!=std::string::npos)
{
positions.push_back(WordPosition(i,j));
pos=i+word.length();
}
}
// sort on position
std::sort(positions.begin(),positions.end(),WordPosition_less());
// get fragments around words
std::vector<Range> ranges;
for (std::vector<WordPosition>::const_iterator it=positions.begin();
it!=positions.end();
++it)
{
WordPosition wp = *it;
std::string w = words[wp.index];
size_t i=wp.start;
size_t wl=w.length();
if (!insideRange(ranges,i,wl))
{
if (wl>fragLen)
{
fragments.push_back(Fragment(spanStart+w+spanEnd,1));
ranges.push_back(Range(i,i+wl));
}
else
{
std::string startFragment,endFragment;
int bi=static_cast<int>(i)-static_cast<int>((fragLen-wl)/2);
size_t ei=i+wl+(fragLen-wl)/2;
int occ=0;
if (bi<0) { ei-=bi; bi=0; } else startFragment=dots;
if (ei>sl) { ei=sl; } else endFragment=dots;
while (bi>0 && !isspace(s[bi])) bi--; // round to start of the word
while (ei<sl && !isspace(s[ei])) ei++; // round to end of the word
// highlight any word in s between indexes bi and ei
std::string fragment=startFragment;
size_t pos=bi;
for (std::vector<WordPosition>::const_iterator it2=positions.begin();
it2!=positions.end();
++it2)
{
WordPosition wp2 = *it2;
std::string w2 = words[wp2.index];
size_t wl2 = w2.length();
if (wp2.start>=bi && wp2.start+wl2<=ei) // word is inside the range!
{
fragment+=s.substr(pos,wp2.start-pos)+
spanStart+
s.substr(wp2.start,wl2)+
spanEnd;
pos=wp2.start+wl2;
occ++;
}
}
fragment+=s.substr(pos,ei-pos)+endFragment;
fragments.push_back(Fragment(fragment,occ));
ranges.push_back(Range(bi,ei));
}
}
}
std::sort(fragments.begin(),fragments.end(),Fragment_greater());
}
/** Escapes a string such that is can be included in a JSON structure */
static std::string escapeString(const std::string &s)
{
std::stringstream dst;
for (size_t i=0;i<s.length();i++)
{
char ch = s[i];
switch (ch)
{
case '\"': dst << "\\\""; break;
default: dst << ch; break;
}
}
return dst.str();
}
static void showError(const std::string &callback,const std::string &error)
{
std::cout << callback << "({\"error\":\"" << error << "\"})";
exit(0);
}
static void usage(const char *name, int exitVal = 1)
{
std::cerr << "Usage: " << name << "[query_string]" << std::endl;
std::cerr << " " << "alternatively the query string can be given by the environment variable QUERY_STRING" << std::endl;
exit(exitVal);
}
/** Main routine */
int main(int argc,char **argv)
{
// process inputs that were passed to us via QUERY_STRING
std::string callback;
try
{
std::string queryString;
if (argc == 1)
{
const char *queryEnv = getenv("QUERY_STRING");
if (queryEnv)
{
queryString = queryEnv;
}
else
{
usage(argv[0]);
}
}
else if (argc == 2)
{
if (std::string(argv[1])=="-h" || std::string(argv[1])=="--help")
{
usage(argv[0],0);
}
else if (std::string(argv[1])=="-v" || std::string(argv[1])=="--version")
{
std::cerr << argv[0] << " version: " << getFullVersion() << std::endl;
exit(0);
}
else
{
queryString = argv[1];
}
}
else
{
usage(argv[0]);
}
std::cout << "Content-Type:application/javascript;charset=utf-8\r\n\n";
// parse query string
std::vector<std::string> parts = split(queryString,'&');
std::string searchFor,callback;
int num=1,page=0;
for (std::vector<std::string>::const_iterator it=parts.begin();it!=parts.end();++it)
{
std::vector<std::string> kv = split(*it,'=');
if (kv.size()==2)
{
std::string val = uriDecode(kv[1]);
if (kv[0]=="q") searchFor = val;
else if (kv[0]=="n") num = fromString<int>(val);
else if (kv[0]=="p") page = fromString<int>(val);
else if (kv[0]=="cb") callback = val;
}
}
std::string indexDir = "doxysearch.db";
if (queryString=="test") // user test
{
bool dbOk = dirExists(indexDir);
if (dbOk)
{
std::cout << "Test successful.";
}
else
{
std::cout << "Test failed: cannot find search index " << indexDir;
}
exit(0);
}
// create query
Xapian::Database db(indexDir);
Xapian::Enquire enquire(db);
std::vector<std::string> words = split(searchFor,' ');
Xapian::QueryParser parser;
parser.set_database(db);
parser.set_default_op(Xapian::Query::OP_AND);
parser.set_stemming_strategy(Xapian::QueryParser::STEM_ALL);
Xapian::termcount max_expansion=100;
#if (XAPIAN_MAJOR_VERSION==1) && (XAPIAN_MINOR_VERSION==2)
parser.set_max_wildcard_expansion(max_expansion);
#else
parser.set_max_expansion(max_expansion,Xapian::Query::WILDCARD_LIMIT_MOST_FREQUENT);
#endif
Xapian::Query query=parser.parse_query(searchFor,
Xapian::QueryParser::FLAG_DEFAULT |
Xapian::QueryParser::FLAG_WILDCARD |
Xapian::QueryParser::FLAG_PHRASE |
Xapian::QueryParser::FLAG_PARTIAL
);
enquire.set_query(query);
// get results
Xapian::MSet matches = enquire.get_mset(page*num,num);
unsigned int hits = matches.get_matches_estimated();
unsigned int offset = page*num;
unsigned int pages = num>0 ? (hits+num-1)/num : 0;
if (offset>hits) offset=hits;
if (offset+num>hits) num=hits-offset;
// write results as JSONP
std::cout << callback.c_str() << "(";
std::cout << "{" << std::endl
<< " \"hits\":" << hits << "," << std::endl
<< " \"first\":" << offset << "," << std::endl
<< " \"count\":" << num << "," << std::endl
<< " \"page\":" << page << "," << std::endl
<< " \"pages\":" << pages << "," << std::endl
<< " \"query\": \"" << escapeString(searchFor) << "\"," << std::endl
<< " \"items\":[" << std::endl;
// foreach search result
unsigned int o = offset;
for (Xapian::MSetIterator i = matches.begin(); i != matches.end(); ++i,++o)
{
std::vector<Fragment> hl;
Xapian::Document doc = i.get_document();
highlighter(doc.get_value(FIELD_DOC),words,hl);
std::cout << " {\"type\": \"" << doc.get_value(FIELD_TYPE) << "\"," << std::endl
<< " \"name\": \"" << doc.get_value(FIELD_NAME) << escapeString(doc.get_value(FIELD_ARGS)) << "\"," << std::endl
<< " \"tag\": \"" << doc.get_value(FIELD_TAG) << "\"," << std::endl
<< " \"url\": \"" << doc.get_value(FIELD_URL) << "\"," << std::endl;
std::cout << " \"fragments\":[" << std::endl;
int c=0;
bool first=true;
for (std::vector<Fragment>::const_iterator it = hl.begin();it!=hl.end() && c<3;++it,++c)
{
if (!first) std::cout << "," << std::endl;
std::cout << " \"" << escapeString((*it).text) << "\"";
first=false;
}
if (!first) std::cout << std::endl;
std::cout << " ]" << std::endl;
std::cout << " }";
if (o<offset+num-1) std::cout << ",";
std::cout << std::endl;
}
std::cout << " ]" << std::endl << "})" << std::endl;
}
catch (const Xapian::Error &e) // Xapian exception
{
showError(callback,e.get_description());
}
catch (...) // Any other exception
{
showError(callback,"Unknown Exception!");
exit(1);
}
return 0;
}

View File

@ -0,0 +1,182 @@
# Try finding Qt6
if (force_qt STREQUAL "Qt6" OR NOT force_qt)
find_package(Qt6Core QUIET CONFIG)
if (Qt6Core_FOUND)
message(STATUS "Using Qt6")
find_package(Qt6 REQUIRED COMPONENTS Widgets Gui Xml)
macro(qt_wrap_cpp)
qt6_wrap_cpp(${ARGN})
endmacro()
macro(qt_add_resources)
qt6_add_resources(${ARGN})
endmacro()
elseif (force_qt STREQUAL "Qt6")
# no fallback to Qt5
message(FATAL_ERROR "Qt6 not found")
endif()
endif()
# Try finding Qt5
if (force_qt STREQUAL "Qt5" OR NOT Qt6_FOUND)
find_package(Qt5Core QUIET CONFIG)
if (Qt5Core_FOUND)
message(STATUS "Using Qt5")
find_package(Qt5 REQUIRED COMPONENTS Widgets Gui Xml)
macro(qt_wrap_cpp)
qt5_wrap_cpp(${ARGN})
endmacro()
macro(qt_add_resources)
qt5_add_resources(${ARGN})
endmacro()
elseif (force_qt STREQUAL "Qt5")
message(FATAL_ERROR "Qt5 not found")
else()
message(FATAL_ERROR "Qt5 nor Qt6 found")
endif()
endif()
include_directories(
.
${PROJECT_SOURCE_DIR}/libversion
${GENERATED_SRC}
)
set(GENERATED_SRC_WIZARD ${GENERATED_SRC}/doxywizard)
file(MAKE_DIRECTORY ${GENERATED_SRC_WIZARD})
add_definitions(-DQT_NO_CAST_FROM_ASCII -DQT_NO_CAST_TO_ASCII -DUNICODE)
if (NOT Qt6Core_FOUND AND NOT Qt5Core_FOUND)
include(${QT_USE_FILE})
endif()
# generate settings.h
file(GENERATE OUTPUT ${GENERATED_SRC_WIZARD}/settings.h
CONTENT "#ifndef SETTINGS_H
#define SETTINGS_H
#define USE_LIBCLANG ${clang}
#define IS_SUPPORTED(x) \\
( \\
(USE_LIBCLANG && strcmp(\"USE_LIBCLANG\",(x))==0) || \\
0)
#endif" )
set_source_files_properties(${GENERATED_SRC_WIZARD}/settings.h PROPERTIES GENERATED 1)
# generate configdoc.cpp
add_custom_command(
COMMAND ${Python_EXECUTABLE} ${PROJECT_SOURCE_DIR}/src/configgen.py -wiz ${PROJECT_SOURCE_DIR}/src/config.xml > ${GENERATED_SRC_WIZARD}/configdoc.cpp
DEPENDS ${PROJECT_SOURCE_DIR}/src/configgen.py ${PROJECT_SOURCE_DIR}/src/config.xml
OUTPUT ${GENERATED_SRC_WIZARD}/configdoc.cpp
)
set_source_files_properties(${GENERATED_SRC_WIZARD}/configdoc.cpp PROPERTIES GENERATED 1)
set(LEX_FILES config_doxyw)
if (NOT depfile_supported)
# In case the DEPFILE possibility is not supported the complete list of lex include files for the dependency has to be used
set(LEX_INC_FILES)
endif()
foreach(lex_file ${LEX_FILES})
if (depfile_supported)
add_custom_command(
COMMAND ${Python_EXECUTABLE} ${PROJECT_SOURCE_DIR}/src/pre_lex.py ${PROJECT_SOURCE_DIR}/addon/doxywizard/${lex_file}.l ${GENERATED_SRC_WIZARD}/${lex_file}.l ${GENERATED_SRC_WIZARD}/${lex_file}.corr ${GENERATED_SRC_WIZARD}/${lex_file}.d ${PROJECT_SOURCE_DIR}/src
DEPENDS ${PROJECT_SOURCE_DIR}/src/pre_lex.py ${PROJECT_SOURCE_DIR}/addon/doxywizard/${lex_file}.l
DEPFILE ${GENERATED_SRC_WIZARD}/${lex_file}.d
OUTPUT ${GENERATED_SRC_WIZARD}/${lex_file}.l ${GENERATED_SRC_WIZARD}/${lex_file}.corr ${GENERATED_SRC_WIZARD}/${lex_file}.d
)
else()
add_custom_command(
COMMAND ${Python_EXECUTABLE} ${PROJECT_SOURCE_DIR}/src/pre_lex.py ${PROJECT_SOURCE_DIR}/addon/doxywizard/${lex_file}.l ${GENERATED_SRC_WIZARD}/${lex_file}.l ${GENERATED_SRC_WIZARD}/${lex_file}.corr ${GENERATED_SRC_WIZARD}/${lex_file}.d ${PROJECT_SOURCE_DIR}/src
DEPENDS ${PROJECT_SOURCE_DIR}/src/pre_lex.py ${PROJECT_SOURCE_DIR}/addon/doxywizard/${lex_file}.l ${LEX_INC_FILES}
OUTPUT ${GENERATED_SRC_WIZARD}/${lex_file}.l ${GENERATED_SRC_WIZARD}/${lex_file}.corr ${GENERATED_SRC_WIZARD}/${lex_file}.d
)
endif()
set_source_files_properties(${GENERATED_SRC_WIZARD}/${lex_file}.l PROPERTIES GENERATED 1)
set_source_files_properties(${GENERATED_SRC_WIZARD}/${lex_file}.corr PROPERTIES GENERATED 1)
set_source_files_properties(${GENERATED_SRC_WIZARD}/${lex_file}.d PROPERTIES GENERATED 1)
if (depfile_supported)
add_custom_command(
COMMAND ${Python_EXECUTABLE} ${PROJECT_SOURCE_DIR}/src/pre_lex.py ${PROJECT_SOURCE_DIR}/addon/doxywizard/${lex_file}.l ${GENERATED_SRC_WIZARD}/${lex_file}.l ${GENERATED_SRC_WIZARD}/${lex_file}.corr ${GENERATED_SRC_WIZARD}/${lex_file}.d ${PROJECT_SOURCE_DIR}/src
DEPENDS ${PROJECT_SOURCE_DIR}/src/pre_lex.py ${PROJECT_SOURCE_DIR}/addon/doxywizard/${lex_file}.l
DEPFILE ${GENERATED_SRC_WIZARD}/${lex_file}.d
OUTPUT ${GENERATED_SRC_WIZARD}/${lex_file}.l ${GENERATED_SRC_WIZARD}/${lex_file}.corr ${GENERATED_SRC_WIZARD}/${lex_file}.d
)
else()
add_custom_command(
COMMAND ${Python_EXECUTABLE} ${PROJECT_SOURCE_DIR}/src/pre_lex.py ${PROJECT_SOURCE_DIR}/addon/doxywizard/${lex_file}.l ${GENERATED_SRC_WIZARD}/${lex_file}.l ${GENERATED_SRC_WIZARD}/${lex_file}.corr ${GENERATED_SRC_WIZARD}/${lex_file}.d ${PROJECT_SOURCE_DIR}/src
DEPENDS ${PROJECT_SOURCE_DIR}/src/pre_lex.py ${PROJECT_SOURCE_DIR}/addon/doxywizard/${lex_file}.l ${LEX_INC_FILES}
OUTPUT ${GENERATED_SRC_WIZARD}/${lex_file}.l ${GENERATED_SRC_WIZARD}/${lex_file}.corr ${GENERATED_SRC_WIZARD}/${lex_file}.d
)
endif()
set_source_files_properties(${GENERATED_SRC_WIZARD}/${lex_file}.l PROPERTIES GENERATED 1)
set_source_files_properties(${GENERATED_SRC_WIZARD}/${lex_file}.corr PROPERTIES GENERATED 1)
set_source_files_properties(${GENERATED_SRC_WIZARD}/${lex_file}.d PROPERTIES GENERATED 1)
add_custom_command(
COMMAND ${Python_EXECUTABLE} ${PROJECT_SOURCE_DIR}/src/scan_states.py ${GENERATED_SRC_WIZARD}/${lex_file}.l > ${GENERATED_SRC_WIZARD}/${lex_file}.l.h
DEPENDS ${PROJECT_SOURCE_DIR}/src/scan_states.py ${GENERATED_SRC_WIZARD}/${lex_file}.l
OUTPUT ${GENERATED_SRC_WIZARD}/${lex_file}.l.h
)
set_source_files_properties(${GENERATED_SRC_WIZARD}/${lex_file}.l.h PROPERTIES GENERATED 1)
FLEX_TARGET(${lex_file}
${GENERATED_SRC_WIZARD}/${lex_file}.l
${GENERATED_SRC_WIZARD}/${lex_file}_intermediate.cpp
COMPILE_FLAGS "${LEX_FLAGS}")
add_custom_command(
COMMAND ${Python_EXECUTABLE} ${PROJECT_SOURCE_DIR}/src/post_lex.py ${GENERATED_SRC_WIZARD}/${lex_file}_intermediate.cpp ${GENERATED_SRC_WIZARD}/${lex_file}.cpp ${GENERATED_SRC_WIZARD}/${lex_file}.corr ${PROJECT_SOURCE_DIR}/addon/doxywizard/${lex_file}.l ${GENERATED_SRC_WIZARD}/${lex_file}.l
DEPENDS ${PROJECT_SOURCE_DIR}/src/post_lex.py ${GENERATED_SRC_WIZARD}/${lex_file}_intermediate.cpp ${GENERATED_SRC_WIZARD}/${lex_file}.corr
OUTPUT ${GENERATED_SRC_WIZARD}/${lex_file}.cpp
)
endforeach()
qt_wrap_cpp(doxywizard_MOC
doxywizard.h
expert.h
helplabel.h
inputint.h
inputbool.h
inputstring.h
inputstrlist.h
wizard.h
)
qt_add_resources(doxywizard_RESOURCES_RCC doxywizard.qrc)
add_executable(doxywizard WIN32
config_msg.cpp
doxywizard.cpp
expert.cpp
wizard.cpp
inputbool.cpp
inputstring.cpp
inputint.cpp
inputstrlist.cpp
${GENERATED_SRC_WIZARD}/settings.h
${GENERATED_SRC_WIZARD}/config_doxyw.cpp
${GENERATED_SRC_WIZARD}/config_doxyw.l.h
${GENERATED_SRC_WIZARD}/configdoc.cpp
${doxywizard_MOC}
${doxywizard_RESOURCES_RCC}
${PROJECT_SOURCE_DIR}/templates/icon/doxygen.rc
)
set_property(TARGET doxywizard PROPERTY WIN32_EXECUTABLE true)
include(ApplyEditbin)
if (enable_console)
apply_editbin(doxywizard console)
else()
apply_editbin(doxywizard windows)
endif()
if(Qt5Core_FOUND)
target_link_libraries(doxywizard Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Xml doxygen_version)
elseif(Qt6Core_FOUND)
target_link_libraries(doxywizard Qt6::Core Qt6::Gui Qt6::Widgets Qt6::Xml doxygen_version)
else()
target_link_libraries(doxywizard ${QT_LIBRARIES} ${QT_QTMAIN_LIBRARY} doxygen_version)
endif()
install(TARGETS doxywizard DESTINATION bin)

3
addon/doxywizard/README Normal file
View File

@ -0,0 +1,3 @@
Doxywizard is a graphical front-end to read/edit/write doxygen configuration
files and to launch doxygen. It requires Qt version 4.3 or higher.

View File

@ -0,0 +1,86 @@
/******************************************************************************
*
* Copyright (C) 1997-2022 by Dimitri van Heesch.
*
* Permission to use, copy, modify, and distribute this software and its
* documentation under the terms of the GNU General Public License is hereby
* granted. No representations are made about the suitability of this software
* for any purpose. It is provided "as is" without express or implied warranty.
* See the GNU General Public License for more details.
*
*/
#ifndef ADAPTER_H
#define ADAPTER_H
#include <memory>
#include <QtGlobal>
#include <QString>
#include <QTextStream>
#include <QPointF>
#include <QMouseEvent>
/** @file
* @brief compatibility adapters for Qt5/Qt6 support.
*/
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
#include <QTextCodec>
class TextCodecAdapter
{
public:
TextCodecAdapter(const QByteArray &name)
{
m_codec = QTextCodec::codecForName(name);
if (m_codec==nullptr) // fallback: use UTF-8
{
m_codec = QTextCodec::codecForName("UTF-8");
}
}
QByteArray encode(const QString &input) { return m_codec ? m_codec->fromUnicode(input) : input.toLatin1(); }
QString decode(const QByteArray &input) { return m_codec ? m_codec->toUnicode(input) : QString::fromLatin1(input); }
void applyToStream(QTextStream &t) { t.setCodec(m_codec); }
bool isValid() const { return m_codec!=nullptr; }
private:
QTextCodec *m_codec = nullptr; // object is owned by Qt
};
#else // Qt6+
#include <QStringEncoder>
#include <QStringDecoder>
#include <QStringConverter>
class TextCodecAdapter
{
public:
TextCodecAdapter(const QByteArray &name)
{
auto encodingOpt = QStringConverter::encodingForName(name);
if (encodingOpt)
{
m_encoding = *encodingOpt;
}
m_encoder = std::make_unique<QStringEncoder>(m_encoding);
m_decoder = std::make_unique<QStringDecoder>(m_encoding);
}
QByteArray encode(const QString &input) { return m_encoder ? m_encoder->encode(input) : input.toLatin1(); }
QString decode(const QByteArray &input) { return m_decoder ? m_decoder->decode(input) : QString::fromLatin1(input); }
void applyToStream(QTextStream &t) { t.setEncoding(m_encoding); }
bool isValid() const { return m_decoder!=0; }
private:
std::unique_ptr<QStringEncoder> m_encoder;
std::unique_ptr<QStringDecoder> m_decoder;
QStringConverter::Encoding m_encoding = QStringConverter::Utf8;
};
#endif
inline qreal getMouseYPositionFromEvent(QMouseEvent *m)
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
return m->y();
#else
return m->position().y();
#endif
}
#endif

40
addon/doxywizard/config.h Normal file
View File

@ -0,0 +1,40 @@
/******************************************************************************
*
* Copyright (C) 1997-2019 by Dimitri van Heesch.
*
* Permission to use, copy, modify, and distribute this software and its
* documentation under the terms of the GNU General Public License is hereby
* granted. No representations are made about the suitability of this software
* for any purpose. It is provided "as is" without express or implied warranty.
* See the GNU General Public License for more details.
*
*/
#ifndef CONFIG_H
#define CONFIG_H
#include <QHash>
#include <QString>
#include "adapter.h"
class Input;
class QTextStream;
bool parseConfig(
const QString &fileName,
const QHash<QString,Input *> &options
);
void writeStringValue(QTextStream &t,TextCodecAdapter *codec,const QString &s,bool convert);
// directly copied from ../../src/config.h to be consistent
enum
{
/*! Maximum length of an option in the config file. Used for
* alignment purposes.
*/
MAX_OPTION_LENGTH = 23
};
#endif

View File

@ -0,0 +1,867 @@
/******************************************************************************
*
* Copyright (C) 1997-2019 by Dimitri van Heesch.
*
* Permission to use, copy, modify, and distribute this software and its
* documentation under the terms of the GNU General Public License is hereby
* granted. No representations are made about the suitability of this software
* for any purpose. It is provided "as is" without express or implied warranty.
* See the GNU General Public License for more details.
*
*/
%option never-interactive
%option prefix="config_doxywYY"
%top{
#include <stdint.h>
}
%{
/*
* includes
*/
#include "config.h"
#include "input.h"
#include "inputbool.h"
#include "inputstring.h"
#include "inputobsolete.h"
#include "config_msg.h"
#include <QString>
#include <QVariant>
#include <QStack>
#include <QByteArray>
#include <QFileInfo>
#include <QStringList>
#include <QRegularExpression>
#include <QTextStream>
#include <QMessageBox>
#define YY_NO_UNISTD_H 1
#define MAX_INCLUDE_DEPTH 10
#define USE_STATE2STRING 0
/* -----------------------------------------------------------------
*
* static variables
*/
struct ConfigFileState
{
int lineNr;
FILE *file;
YY_BUFFER_STATE oldState;
YY_BUFFER_STATE newState;
QString fileName;
};
static const QHash<QString,Input*> *g_options=nullptr;
static FILE *g_file=nullptr;
static QString g_yyFileName;
static QString g_includeName;
static QVariant g_includePathList;
static QStack<ConfigFileState*> g_includeStack;
static int g_includeDepth=0;
static QVariant *g_arg=nullptr;
static Input *g_curOption=nullptr;
static QByteArray g_str;
static std::unique_ptr<TextCodecAdapter> g_codec = std::make_unique<TextCodecAdapter>("UTF-8");
static QString g_codecName = QString::fromLatin1("UTF-8");
static QString g_cmd;
static bool g_isEnum=false;
static const char *stateToString(int state);
/* -----------------------------------------------------------------
*/
#undef YY_INPUT
#define YY_INPUT(buf,result,max_size) result=yyread(buf,max_size);
static int yyread(char *buf,int maxSize)
{
// no file included
if (g_includeStack.isEmpty())
{
return static_cast<int>(fread(buf,1,maxSize,g_file));
}
else
{
return static_cast<int>(fread(buf,1,maxSize,g_includeStack.top()->file));
}
}
static void substEnvVarsInStrList(QStringList &sl);
static void substEnvVarsInString(QString &s);
static void checkEncoding()
{
Input *option = g_options->value(QString::fromLatin1("DOXYFILE_ENCODING"));
if (option && option->value().toString()!=g_codecName)
{
auto newCodec = std::make_unique<TextCodecAdapter>(option->value().toString().toLatin1());
if (newCodec->isValid())
{
g_codec.swap(newCodec);
g_codecName = option->value().toString();
}
}
}
static QByteArray stripComment(const QByteArray &s)
{
// check if there is a comment at the end of the string
bool insideQuote=false;
int l = s.length();
for (int i=0;i<l;i++)
{
char c = s.at(i);
if (c=='\\') // skip over escaped characters
{
i++;
}
else if (c=='"') // toggle inside/outside quotation
{
insideQuote=!insideQuote;
}
else if (!insideQuote && c=='#') // found start of a comment
{
return s.left(i).trimmed();
}
}
return s;
}
static void processString()
{
// strip leading and trailing whitespace
QByteArray s = stripComment(g_str.trimmed());
int l = s.length();
// remove surrounding quotes if present (and not escaped)
if (l>=2 && s.at(0)=='"' && s.at(l-1)=='"' && // remove quotes
(s.at(l-2)!='\\' || (s.at(l-2)=='\\' && s.at(l-3)=='\\')))
{
s=s.mid(1,s.length()-2);
l=s.length();
}
// check for invalid and/or escaped quotes
bool warned=false;
QByteArray result;
for (int i=0;i<l;i++)
{
char c = s.at(i);
if (c=='\\') // escaped character
{
if (i<l-1 && s.at(i+1)=='"') // unescape the quote character
{
result+='"';
}
else // keep other escaped characters in escaped form
{
result+=c;
if (i<l-1)
{
result+=s.at(i+1);
}
}
i++; // skip over the escaped character
}
else if (c=='"') // unescaped quote
{
if (!warned)
{
std::string str = g_str.trimmed().data();
config_warn("Invalid value for '%s' tag at line %d, file %s: Value '%s' is not properly quoted\n",
qPrintable(g_cmd),yylineno-1,qPrintable(g_yyFileName),str.c_str());
}
warned=true;
}
else // normal character
{
result+=c;
}
}
// recode the string
if (g_isEnum)
{
InputString *cur = dynamic_cast<InputString *>(g_curOption);
*g_arg = cur->checkEnumVal(g_codec->decode(result));
}
else
{
*g_arg = QVariant(g_codec->decode(result));
}
// update encoding
checkEncoding();
//printf("Processed string '%s'\n",g_string->data());
}
static void processList()
{
bool allowCommaAsSeparator = g_cmd!=QString::fromLatin1("PREDEFINED");
const QByteArray s = stripComment(g_str.trimmed());
int l = s.length();
QByteArray elemStr;
// helper to push elemStr to the list and clear it
auto addElem = [&elemStr]()
{
if (!elemStr.isEmpty())
{
//printf("Processed list element '%s'\n",e.data());
*g_arg = QVariant(g_arg->toStringList() << g_codec->decode(elemStr));
elemStr="";
}
};
bool needsSeparator=false;
int insideQuote=false;
bool warned=false;
for (int i=0;i<l;i++)
{
char c = s.at(i);
if (!needsSeparator && c=='\\') // escaped character
{
if (i<l-1 && s.at(i+1)=='"') // unescape the quote character
{
elemStr+='"';
}
else // keep other escaped characters in escaped form
{
elemStr+=c;
if (i<l-1)
{
elemStr+=s.at(i+1);
}
}
i++; // skip over the escaped character
}
else if (!needsSeparator && c=='"') // quote character
{
if (!insideQuote)
{
insideQuote=true;
}
else // this quote ends an element
{
insideQuote=false;
needsSeparator=true;
}
}
else if (!insideQuote && ((c==',' && allowCommaAsSeparator) || isspace(c))) // separator
{
needsSeparator=false;
addElem();
}
else // normal content character
{
if (needsSeparator)
{
if (!warned)
{
std::string str = g_str.trimmed().data();
config_warn("Invalid value for '%s' tag at line %d, file %s: Values in list '%s' are not properly space %sseparated\n",
qPrintable(g_cmd),yylineno-1,qPrintable(g_yyFileName),str.c_str(),allowCommaAsSeparator?"or comma ":"");
warned=true;
}
needsSeparator=false;
i--; // try the character again as part of a new element
addElem();
}
else
{
elemStr+=c;
}
}
}
// add last part
addElem();
if (insideQuote)
{
std::string str = g_str.trimmed().data();
config_warn("Invalid value for '%s' tag at line %d, file %s: Values in list '%s' are not properly quoted\n",
qPrintable(g_cmd),yylineno-1,qPrintable(g_yyFileName),str.c_str());
}
}
static FILE *tryPath(const QString &path,const QString &fileName)
{
QString absName=!path.isEmpty() ? path+QString::fromLatin1("/")+fileName : fileName;
QFileInfo fi(absName);
if (fi.exists() && fi.isFile())
{
FILE *f = fopen(absName.toLocal8Bit(),"r");
if (f==nullptr)
config_err("could not open file %s for reading\n",qPrintable(absName));
else
return f;
}
return nullptr;
}
static FILE *findFile(const QString &fileName)
{
if (QFileInfo(fileName).isAbsolute()) // absolute path
{
return tryPath(QString(), fileName);
}
// relative path, try with include paths in the list
QStringList sl = g_includePathList.toStringList();
substEnvVarsInStrList(sl);
foreach (QString s, sl)
{
FILE *f = tryPath(s,fileName);
if (f) return f;
}
// try cwd if g_includePathList fails
return tryPath(QString::fromLatin1("."),fileName);
}
static void readIncludeFile(const QString &incName)
{
if (g_includeDepth==MAX_INCLUDE_DEPTH)
{
config_err("maximum include depth (%d) reached, %s is not included.",
MAX_INCLUDE_DEPTH,qPrintable(incName));
}
QString inc = incName;
substEnvVarsInString(inc);
inc = inc.trimmed();
uint incLen = inc.length();
if (inc.at(0)==QChar::fromLatin1('"') &&
inc.at(incLen-1)==QChar::fromLatin1('"')) // strip quotes
{
inc=inc.mid(1,incLen-2);
}
FILE *f = findFile(inc);
if (f) // see if the include file can be found
{
// For debugging
#if SHOW_INCLUDES
for (i=0;i<includeStack.count();i++) msg(" ");
msg("@INCLUDE = %s: parsing...\n",qPrintable(inc));
#endif
// store the state of the old file
ConfigFileState *fs=new ConfigFileState;
fs->oldState=YY_CURRENT_BUFFER;
fs->fileName=g_yyFileName;
fs->file=f;
// push the state on the stack
g_includeStack.push(fs);
// set the scanner to the include file
yy_switch_to_buffer(yy_create_buffer(f, YY_BUF_SIZE));
fs->newState=YY_CURRENT_BUFFER;
g_yyFileName=inc;
g_includeDepth++;
}
else
{
config_err("@INCLUDE = %s: not found!\n",qPrintable(inc));
}
}
#if USE_STATE2STRING
static const char *stateToString(int state);
#endif
%}
%option nounput
%option noyywrap
%option yylineno
%x Start
%x SkipInvalid
%x GetString
%x GetStrList
%x Include
%%
<*>\0x0d
/*-------------- Comments ---------------*/
<Start>"#".*\n { /* Skip comment */ }
/*-------------- TAG start ---------------*/
<Start>[a-z_A-Z][a-z_A-Z0-9]*[ \t]*"=" {
g_cmd = g_codec->decode(yytext);
g_cmd=g_cmd.left(g_cmd.length()-1).trimmed();
g_curOption = g_options->value(g_cmd);
if (g_curOption==nullptr) // oops not known
{
config_warn("ignoring unsupported tag '%s' at line %d, file %s\n",
qPrintable(g_cmd),yylineno,qPrintable(g_yyFileName));
BEGIN(SkipInvalid);
}
else // known tag
{
g_arg = &g_curOption->value();
g_str = QByteArray();
g_isEnum = false;
switch(g_curOption->kind())
{
case Input::StrList:
*g_arg = QStringList();
BEGIN(GetStrList);
break;
case Input::String:
g_isEnum = dynamic_cast<InputString *>(g_curOption)->stringMode() == InputString::StringFixed;
BEGIN(GetString);
break;
case Input::Int:
BEGIN(GetString);
break;
case Input::Bool:
BEGIN(GetString);
break;
case Input::Obsolete:
{
config_warn("Tag '%s' at line %d of file %s has become obsolete.\n"
"To avoid this warning please update your configuration "
"file using \"doxygen -u\"\n", qPrintable(g_cmd),
yylineno,qPrintable(g_yyFileName));
InputObsolete *obsoleteOpt = dynamic_cast<InputObsolete*>(g_curOption);
if (obsoleteOpt)
{
if (obsoleteOpt->orgKind()==Input::StrList)
{
*g_arg = QStringList();
BEGIN(GetStrList);
}
else
{
BEGIN(GetString);
}
}
else
{
BEGIN(SkipInvalid);
}
}
break;
}
}
}
<Start>[a-z_A-Z][a-z_A-Z0-9]*[ \t]*"+=" {
g_cmd=g_codec->decode(yytext);
g_cmd=g_cmd.left(g_cmd.length()-2).trimmed();
g_curOption = g_options->value(g_cmd);
if (g_curOption==nullptr) // oops not known
{
config_warn("ignoring unsupported tag '%s' at line %d, file %s\n",
yytext,yylineno,qPrintable(g_yyFileName));
BEGIN(SkipInvalid);
}
else // known tag
{
switch(g_curOption->kind())
{
case Input::StrList:
g_arg = &g_curOption->value();
g_str=QByteArray();
BEGIN(GetStrList);
break;
case Input::String:
case Input::Int:
case Input::Bool:
config_warn("operator += not supported for '%s'. Ignoring line %d, file %s\n",
qPrintable(g_cmd),yylineno,qPrintable(g_yyFileName));
BEGIN(SkipInvalid);
break;
case Input::Obsolete:
{
config_warn("Tag '%s' at line %d of file %s has become obsolete.\n"
"To avoid this warning please update your configuration "
"file using \"doxygen -u\"\n",
qPrintable(g_cmd),yylineno,qPrintable(g_yyFileName));
InputObsolete *obsoleteOpt = dynamic_cast<InputObsolete*>(g_curOption);
if (obsoleteOpt && obsoleteOpt->orgKind()==Input::StrList)
{
g_arg = &g_curOption->value();
g_str=QByteArray();
BEGIN(GetStrList);
}
else
{
BEGIN(SkipInvalid);
}
}
break;
}
}
}
/*-------------- INCLUDE* ---------------*/
<Start>"@INCLUDE_PATH"[ \t]*"=" { BEGIN(GetStrList); g_arg=&g_includePathList; *g_arg = QStringList(); g_str=QByteArray(); }
/* include a config file */
<Start>"@INCLUDE"[ \t]*"=" { BEGIN(Include);}
<Include>([^ \"\t\r\n]+)|("\""[^\n\"]+"\"") {
readIncludeFile(g_codec->decode(yytext));
BEGIN(Start);
}
<<EOF>> {
//printf("End of include file\n");
//printf("Include stack depth=%d\n",g_includeStack.count());
if (g_includeStack.isEmpty())
{
//printf("Terminating scanner!\n");
yyterminate();
}
else
{
ConfigFileState *fs = g_includeStack.pop();
fclose(fs->file);
YY_BUFFER_STATE oldBuf = YY_CURRENT_BUFFER;
yy_switch_to_buffer( fs->oldState );
yy_delete_buffer( oldBuf );
g_yyFileName=fs->fileName;
delete fs;
g_includeDepth--;
}
}
<Start>[a-z_A-Z0-9]+ { config_warn("ignoring unknown tag '%s' at line %d, file %s\n",yytext,yylineno,qPrintable(g_yyFileName)); }
/*-------------- GetString ---------------*/
<GetString>\n { // end of string
processString();
BEGIN(Start);
}
<GetString>\\[ \r\t]*\n { // line continuation
g_str+=' ';
}
<GetString>"\\" { // escape character
g_str+=yytext;
}
<GetString>[^\n\\]+ { // string part without escape characters
g_str+=yytext;
}
/*-------------- GetStrList ---------------*/
<GetStrList>\n { // end of list
processList();
BEGIN(Start);
}
<GetStrList>\\[ \r\t]*\n { // line continuation
g_str+=' ';
}
<GetStrList>"\\" { // escape character
g_str+=yytext;
}
<GetStrList>[^\n\\]+ { // string part without escape characters
g_str+=yytext;
}
/*-------------- SkipInvalid ---------------*/
<SkipInvalid>\n { // end of skipped part
BEGIN(Start);
}
<SkipInvalid>\\[ \r\t]*\n { // line continuation
g_str+=' ';
}
<SkipInvalid>"\\" { // escape character
g_str+=yytext;
}
<SkipInvalid>[^\n\\]+ { // string part without escape characters
g_str+=yytext;
}
/*-------------- fall through -------------*/
<*>\\[ \r\t]*\n { }
<*>[ \r\t] { }
<*>\n
<*>. { config_warn("ignoring unknown character '%c' at line %d, file %s\n",yytext[0],yylineno,qPrintable(g_yyFileName)); }
%%
/*@ ----------------------------------------------------------------------------
*/
static void substEnvVarsInString(QString &s)
{
static QRegularExpression re(QString::fromLatin1("\\$\\([a-z_A-Z0-9]+\\)"));
if (s.isEmpty()) return;
int p=0;
int i,l;
//printf("substEnvVarInString(%s) start\n",qPrintable(s));
QRegularExpressionMatch match;
while ((i=s.indexOf(re,p,&match))!=-1)
{
l = match.capturedLength();
//printf("Found environment var s.mid(%d,%d)='%s'\n",i+2,l-3,qPrintable(s.mid(i+2,l-3)));
QString env=g_codec->decode(getenv(s.mid(i+2,l-3).toLatin1()));
substEnvVarsInString(env); // recursively expand variables if needed.
s = s.left(i)+env+s.right(s.length()-i-l);
p=i+env.length(); // next time start at the end of the expanded string
}
s=s.trimmed(); // to strip the bogus space that was added when an argument
// has quotes
//printf("substEnvVarInString(%s) end\n",qPrintable(s));
}
static void substEnvVarsInStrList(QStringList &sl)
{
QStringList out;
foreach (QString result, sl)
{
// an argument with quotes will have an extra space at the end, so wasQuoted will be TRUE.
bool wasQuoted = (result.indexOf(QChar::fromLatin1(' '))!=-1) ||
(result.indexOf(QChar::fromLatin1('\t'))!=-1);
// here we strip the quote again
substEnvVarsInString(result);
//printf("Result %s was quoted=%d\n",qPrintable(result),wasQuoted);
if (!wasQuoted) /* as a result of the expansion, a single string
may have expanded into a list, which we'll
add to sl. If the original string already
contained multiple elements no further
splitting is done to allow quoted items with spaces! */
{
int l=result.length();
int i,p=0;
// skip spaces
// search for a "word"
for (i=0;i<l;i++)
{
QChar c;
// skip until start of new word
while (i<l && ((c=result.at(i))==QChar::fromLatin1(' ') || c==QChar::fromLatin1('\t'))) i++;
p=i; // p marks the start index of the word
// skip until end of a word
while (i<l && ((c=result.at(i))!=QChar::fromLatin1(' ') &&
c!=QChar::fromLatin1('\t') &&
c!=QChar::fromLatin1('"'))) i++;
if (i<l) // not at the end of the string
{
if (c==QChar::fromLatin1('"')) // word within quotes
{
p=i+1;
for (i++;i<l;i++)
{
c=result.at(i);
if (c==QChar::fromLatin1('"')) // end quote
{
out += result.mid(p,i-p);
p=i+1;
break;
}
else if (c==QChar::fromLatin1('\\')) // skip escaped stuff
{
i++;
}
}
}
else if (c==QChar::fromLatin1(' ') || c==QChar::fromLatin1('\t')) // separator
{
out += result.mid(p,i-p);
p=i+1;
}
}
}
if (p!=l) // add the leftover as a string
{
out += result.right(l-p);
}
}
else // just goto the next element in the list
{
out += result;
}
}
sl = out;
}
//--------------------------------------------------------------------------
static void upgradeConfig(const QHash<QString,Input*> &options)
{
auto it1 = options.find(QString::fromLatin1("CLASS_DIAGRAMS"));
auto it2 = options.find(QString::fromLatin1("HAVE_DOT"));
auto it3 = options.find(QString::fromLatin1("CLASS_GRAPH"));
if (it1!=options.end() && it2!=options.end() && it3!=options.end())
{
if ((*it1)->kind()==Input::Obsolete)
{
InputObsolete *optClassDiagram = dynamic_cast<InputObsolete*>(*it1);
InputBool *optHaveDot = dynamic_cast<InputBool*> (*it2);
InputString *optClassGraph = dynamic_cast<InputString*> (*it3);
if (optClassDiagram->orgKind()==Input::Bool)
{
const QVariant &v1 = optClassDiagram->value();
const QVariant &v2 = optHaveDot->value();
QVariant &v3 = optClassGraph->value();
bool isValid1=false,isValid2=false,isValid3=false;
bool classDiagrams = InputBool::convertToBool(v1,isValid1);
bool haveDot = InputBool::convertToBool(v2,isValid2);
bool classGraph = InputBool::convertToBool(v3,isValid3);
if (isValid1 && isValid2 && isValid3 && !classDiagrams && !haveDot && classGraph)
{
config_warn("Changing CLASS_GRAPH option to TEXT because obsolete option CLASS_DIAGRAM was found and set to NO.\n");
optClassGraph->setValue(QString::fromLatin1("TEXT"));
}
}
}
}
}
//--------------------------------------------------------------------------
bool parseConfig(
const QString &fileName,
const QHash<QString,Input *> &options
)
{
yylineno = 1;
config_open();
QHashIterator<QString, Input*> i(options);
g_file = fopen(fileName.toLocal8Bit(),"r");
if (g_file==nullptr) return false;
// reset all values
i.toFront();
while (i.hasNext())
{
i.next();
if (i.value())
{
i.value()->reset();
}
}
// parse config file
g_options = &options;
g_yyFileName = fileName;
g_includeStack.clear();
g_includeDepth = 0;
config_doxywYYrestart( config_doxywYYin );
BEGIN( Start );
config_doxywYYlex();
upgradeConfig(options);
// update the values in the UI
i.toFront();
while (i.hasNext())
{
i.next();
if (i.value())
{
//printf("Updating: %s\n",qPrintable(i.key()));
i.value()->update();
}
else
{
printf("Invalid option: %s\n",qPrintable(i.key()));
}
}
fclose(g_file);
config_finish();
return true;
}
void writeStringValue(QTextStream &t,TextCodecAdapter *codec,const QString &s,bool convert)
{
QChar c;
bool needsEscaping=false;
bool needsHashEscaping=false;
// convert the string back to it original encoding
codec->applyToStream(t);
const QChar *p=s.data();
if (!s.isEmpty() && !p->isNull())
{
if (*p != QChar::fromLatin1('"'))
{
while (!(c=*p++).isNull() && !needsEscaping)
{
needsEscaping = (c==QChar::fromLatin1(' ') ||
c==QChar::fromLatin1(',') ||
c==QChar::fromLatin1('\n') ||
c==QChar::fromLatin1('\t') ||
c==QChar::fromLatin1('"'));
}
p=s.data();
while (!(c=*p++).isNull() && !needsHashEscaping)
{
needsHashEscaping = (c==QChar::fromLatin1('#'));
}
}
if (needsHashEscaping || needsEscaping)
{
t << "\"";
}
if (needsEscaping)
{
p=s.data();
while (!p->isNull())
{
if (*p ==QChar::fromLatin1(' ') &&
*(p+1)==QChar::fromLatin1('\0')) break; // skip inserted space at the end
if (*p ==QChar::fromLatin1('"')) t << "\\"; // escape quotes
if (convert)
{
if (*p ==QChar::fromLatin1('<')) t << "&lt;";
else if (*p ==QChar::fromLatin1('>')) t << "&gt;";
else if (*p ==QChar::fromLatin1('&')) t << "&amp;";
else t << *p;
}
else
{
t << *p;
}
p++;
}
}
else
{
p=s.data();
while (!p->isNull())
{
if (convert)
{
if (*p ==QChar::fromLatin1('<')) t << "&lt;";
else if (*p ==QChar::fromLatin1('>')) t << "&gt;";
else if (*p ==QChar::fromLatin1('&')) t << "&amp;";
else t << *p;
}
else
{
t << *p;
}
p++;
}
}
if (needsHashEscaping || needsEscaping)
{
t << "\"";
}
}
}
#if USE_STATE2STRING
#include "config_doxyw.l.h"
#endif

View File

@ -0,0 +1,56 @@
#include <QString>
#include "config_msg.h"
#include "doxywizard.h"
static QString warning_str = QString::fromLatin1("warning: ");
static QString error_str = QString::fromLatin1("error: ");
void config_err(const char *fmt, ...)
{
QString msg = error_str;
msg.append(QString::fromLatin1(fmt));
va_list args;
va_start(args, fmt);
char debugOut[1000]; // this size should be sufficient
vsnprintf(debugOut, 1000,qPrintable(msg), args);
MainWindow::instance().outputLogText(QString::fromLatin1(debugOut));
va_end(args);
}
void config_term(const char *fmt, ...)
{
QString msg = error_str;
msg.append(QString::fromLatin1(fmt));
va_list args;
va_start(args, fmt);
char debugOut[1000]; // this size should be sufficient
vsnprintf(debugOut, 1000,qPrintable(msg), args);
MainWindow::instance().outputLogText(QString::fromLatin1(debugOut));
va_end(args);
exit(1);
}
void config_warn(const char *fmt, ...)
{
QString msg = warning_str;
msg.append(QString::fromLatin1(fmt));
va_list args;
va_start(args, fmt);
char debugOut[1000];
vsnprintf(debugOut, 1000,qPrintable(msg), args);
MainWindow::instance().outputLogText(QString::fromLatin1(debugOut));
va_end(args);
}
void config_open()
{
MainWindow::instance().outputLogStart();
}
void config_finish()
{
MainWindow::instance().outputLogFinish();
}

View File

@ -0,0 +1,10 @@
#ifndef DOXYW_MSG_H
#define DOXYW_MSG_H
void config_err(const char *fmt, ...);
void config_term(const char *fmt, ...);
void config_warn(const char *fmt, ...);
void config_open();
void config_finish();
#endif

View File

@ -0,0 +1,20 @@
/******************************************************************************
*
* Copyright (C) 1997-2019 by Dimitri van Heesch.
*
* Permission to use, copy, modify, and distribute this software and its
* documentation under the terms of the GNU General Public License is hereby
* granted. No representations are made about the suitability of this software
* for any purpose. It is provided "as is" without express or implied warranty.
* See the GNU General Public License for more details.
*
*/
#ifndef CONFIGDOC_H
#define CONFIGDOC_H
class DocIntf;
void addConfigDocs(DocIntf *doc);
#endif

View File

@ -0,0 +1,24 @@
/******************************************************************************
*
* Copyright (C) 1997-2019 by Dimitri van Heesch.
*
* Permission to use, copy, modify, and distribute this software and its
* documentation under the terms of the GNU General Public License is hereby
* granted. No representations are made about the suitability of this software
* for any purpose. It is provided "as is" without express or implied warranty.
* See the GNU General Public License for more details.
*
*/
#ifndef DOCINTF_H
#define DOCINTF_H
class DocIntf
{
public:
virtual ~DocIntf() {}
virtual void setHeader(const char *header) = 0;
virtual void add(const char *name,const char *docs) = 0;
};
#endif

View File

@ -0,0 +1,868 @@
/******************************************************************************
*
* Copyright (C) 1997-2021 by Dimitri van Heesch.
*
* Permission to use, copy, modify, and distribute this software and its
* documentation under the terms of the GNU General Public License is hereby
* granted. No representations are made about the suitability of this software
* for any purpose. It is provided "as is" without express or implied warranty.
* See the GNU General Public License for more details.
*
*/
#include "doxywizard.h"
#include "version.h"
#include "expert.h"
#include "wizard.h"
#include <QMenu>
#include <QMenuBar>
#include <QPushButton>
#include <QMessageBox>
#include <QVBoxLayout>
#include <QLineEdit>
#include <QLabel>
#include <QCheckBox>
#include <QTextBrowser>
#include <QStatusBar>
#include <QProcess>
#include <QTimer>
#include <QCloseEvent>
#include <QApplication>
#include <QDir>
#include <QFileDialog>
#include <QDesktopServices>
#include <QUrl>
#include <QTextStream>
#include <QRegularExpression>
#include <QDebug>
#include <QDate>
#include <QScrollBar>
#ifdef _WIN32
#include <windows.h>
#endif
#define MAX_RECENT_FILES 10
// globally accessible variables
bool DoxygenWizard::debugFlag = false;
const int messageTimeout = 5000; //!< status bar message timeout in milliseconds.
#define APPQT(x) QString::fromLatin1("<qt><pre>") + x + QString::fromLatin1("</pre></qt>")
MainWindow &MainWindow::instance()
{
static MainWindow *theInstance = new MainWindow;
return *theInstance;
}
MainWindow::MainWindow()
: m_settings(QString::fromLatin1("Doxygen.org"), QString::fromLatin1("Doxywizard"))
{
QMenu *file = menuBar()->addMenu(tr("File"));
file->addAction(tr("Open..."),
this, SLOT(openConfig()), Qt::CTRL|Qt::Key_O);
m_recentMenu = file->addMenu(tr("Open recent"));
file->addAction(tr("Save"),
this, SLOT(saveConfig()), Qt::CTRL|Qt::Key_S);
file->addAction(tr("Save as..."),
this, SLOT(saveConfigAs()), Qt::SHIFT|Qt::CTRL|Qt::Key_S);
file->addAction(tr("Quit"),
this, SLOT(quit()), Qt::CTRL|Qt::Key_Q);
QMenu *settings = menuBar()->addMenu(tr("Settings"));
m_resetDefault = settings->addAction(tr("Reset to factory defaults"),
this,SLOT(resetToDefaults()));
settings->addAction(tr("Use current settings at startup"),
this,SLOT(makeDefaults()));
m_clearRecent = settings->addAction(tr("Clear recent list"),
this,SLOT(clearRecent()));
settings->addSeparator();
m_runMenu = settings->addAction(tr("Run doxygen"),
this,SLOT(runDoxygenMenu()),Qt::CTRL|Qt::Key_R);
m_runMenu->setEnabled(false);
QMenu *help = menuBar()->addMenu(tr("Help"));
help->addAction(tr("Online manual"),
this, SLOT(manual()), Qt::Key_F1);
help->addAction(tr("About"),
this, SLOT(about()) );
m_expert = new Expert;
m_wizard = new Wizard(m_expert->modelData());
// ----------- top part ------------------
QWidget *mainPart = new QWidget;
QVBoxLayout *mainLayout = new QVBoxLayout(mainPart);
QWidget *topPart = new QWidget;
QVBoxLayout *rowLayout = new QVBoxLayout(topPart);
mainLayout->addWidget(topPart);
// select working directory
QHBoxLayout *dirLayout = new QHBoxLayout;
m_workingDir = new QLineEdit;
m_selWorkingDir = new QPushButton(tr("Select..."));
dirLayout->addWidget(m_workingDir);
dirLayout->addWidget(m_selWorkingDir);
//------------- bottom part --------------
m_runTab = new QWidget;
QVBoxLayout *runTabLayout = new QVBoxLayout(m_runTab);
// run doxygen
QHBoxLayout *runLayout = new QHBoxLayout;
m_run = new QPushButton(tr("Run doxygen"));
m_run->setEnabled(false);
m_runStatus = new QLabel(tr("Status: not running"));
m_saveLog = new QPushButton(tr("Save log..."));
m_saveLog->setEnabled(false);
QPushButton *showSettings = new QPushButton(tr("Show configuration"));
m_showCondensedSettings = new QCheckBox(this);
m_showCondensedSettings->setText(tr("Condensed"));
m_showCondensedSettings->setChecked(false);
m_showCondensedSettings->setToolTip(tr("Show only configuration settings different from default settings"));
// select extra run options
m_runOptions = new QLineEdit;
runTabLayout->addWidget(new QLabel(tr("Specify additional command line options for running doxygen")));
runTabLayout->addWidget(m_runOptions);
QVBoxLayout *runVLayout = new QVBoxLayout;
runLayout->addLayout(runVLayout);
QHBoxLayout *runVHLayout = new QHBoxLayout;
runVLayout->addLayout(runVHLayout);
runVHLayout->addWidget(m_run);
runVHLayout->addWidget(m_runStatus);
QHBoxLayout *runVH2Layout = new QHBoxLayout;
runVLayout->addLayout(runVH2Layout);
m_launchHtml = new QPushButton(tr("Show HTML output"));
runVH2Layout->addWidget(m_launchHtml);
runVH2Layout->addStretch(1); // to have launch button not being stretched
runLayout->addStretch(1);
QVBoxLayout *settingsLayout = new QVBoxLayout;
runLayout->addLayout(settingsLayout);
settingsLayout->addWidget(m_showCondensedSettings);
settingsLayout->addWidget(showSettings);
QVBoxLayout *saveLayout = new QVBoxLayout;
runLayout->addLayout(saveLayout);
saveLayout->addWidget(m_saveLog);
saveLayout->setAlignment(Qt::AlignTop);
// saveLayout->addWidget(new QWidget); // to have the save button at the top
// output produced by Doxygen
runTabLayout->addLayout(runLayout);
runTabLayout->addWidget(new QLabel(tr("Output produced by doxygen")));
QGridLayout *grid = new QGridLayout;
//m_outputLog = new QTextEdit;
m_outputLog = new QTextBrowser;
//m_outputLog = new QPlainTextEdit;
m_outputLog->setReadOnly(true);
m_outputLog->setFontFamily(QString::fromLatin1("courier"));
m_outputLog->setMinimumWidth(600);
grid->addWidget(m_outputLog,0,0);
grid->setColumnStretch(0,1);
grid->setRowStretch(0,1);
runTabLayout->addLayout(grid);
m_tabs = new QTabWidget;
m_tabs->addTab(m_wizard,tr("Wizard"));
m_tabs->addTab(m_expert,tr("Expert"));
m_tabs->addTab(m_runTab,tr("Run"));
rowLayout->addWidget(new QLabel(tr("Specify the working directory from which doxygen will run")));
rowLayout->addLayout(dirLayout);
rowLayout->addWidget(new QLabel(tr("Configure doxygen using the Wizard and/or Expert tab, then switch to the Run tab to generate the documentation")));
mainLayout->addWidget(m_tabs);
setCentralWidget(mainPart);
statusBar()->showMessage(tr("Welcome to Doxygen"),messageTimeout);
m_runProcess = new QProcess;
m_running = false;
m_timer = new QTimer;
// connect signals and slots
connect(m_tabs,SIGNAL(currentChanged(int)),SLOT(selectTab(int)));
connect(m_selWorkingDir,SIGNAL(clicked()),SLOT(selectWorkingDir()));
connect(m_recentMenu,SIGNAL(triggered(QAction*)),SLOT(openRecent(QAction*)));
connect(m_workingDir,SIGNAL(returnPressed()),SLOT(updateWorkingDir()));
connect(m_runProcess,SIGNAL(readyReadStandardOutput()),SLOT(readStdout()));
connect(m_runProcess,SIGNAL(finished(int, QProcess::ExitStatus)),SLOT(runComplete()));
connect(m_timer,SIGNAL(timeout()),SLOT(readStdout()));
connect(m_run,SIGNAL(clicked()),SLOT(runDoxygen()));
connect(m_launchHtml,SIGNAL(clicked()),SLOT(showHtmlOutput()));
connect(m_saveLog,SIGNAL(clicked()),SLOT(saveLog()));
connect(showSettings,SIGNAL(clicked()),SLOT(showSettings()));
connect(m_expert,SIGNAL(changed()),SLOT(configChanged()));
connect(m_wizard,SIGNAL(done()),SLOT(selectRunTab()));
connect(m_expert,SIGNAL(done()),SLOT(selectRunTab()));
loadSettings();
updateLaunchButtonState();
m_modified = false;
updateTitle();
m_wizard->refresh();
}
void MainWindow::closeEvent(QCloseEvent *event)
{
if (discardUnsavedChanges())
{
saveSettings();
event->accept();
}
else
{
event->ignore();
}
}
void MainWindow::quit()
{
if (discardUnsavedChanges())
{
saveSettings();
}
else
{
return;
}
QApplication::exit(0);
}
void MainWindow::setWorkingDir(const QString &dirName)
{
QDir::setCurrent(dirName);
m_workingDir->setText(dirName);
m_run->setEnabled(!dirName.isEmpty());
m_runMenu->setEnabled(!dirName.isEmpty());
}
void MainWindow::selectWorkingDir()
{
QString dirName = QFileDialog::getExistingDirectory(this,
tr("Select working directory"),m_workingDir->text());
if (!dirName.isEmpty())
{
setWorkingDir(dirName);
}
}
void MainWindow::updateWorkingDir()
{
setWorkingDir(m_workingDir->text());
}
void MainWindow::manual()
{
QDesktopServices::openUrl(QUrl(QString::fromLatin1("https://www.doxygen.org/manual/index.html")));
}
void MainWindow::about()
{
QString msg;
QTextStream t(&msg,QIODevice::WriteOnly);
t << QString::fromLatin1("<qt><center>A tool to configure and run doxygen version ")+
QString::fromLatin1(getDoxygenVersion().c_str())+
QString::fromLatin1(" on your source files.</center>")+
QString::fromLatin1("<center>(Created with Qt version ")+
QString::fromLatin1(QT_VERSION_STR);
if (qstrcmp(qVersion(),QT_VERSION_STR))
{
t << QString::fromLatin1(", running with ")+
QString::fromLatin1(qVersion());
}
t << QString::fromLatin1(")</center><p><br>"
"<center>Written by<br> Dimitri van Heesch<br>&copy; 2000-");
t << QDate::currentDate().year();
t << QString::fromLatin1("</center><p></qt>");
QMessageBox::about(this,tr("Doxygen GUI"),msg);
}
void MainWindow::openConfig()
{
if (discardUnsavedChanges(false))
{
QString fn = QFileDialog::getOpenFileName(this,
tr("Open configuration file"),
m_workingDir->text());
if (!fn.isEmpty())
{
loadConfigFromFile(fn);
}
}
}
void MainWindow::updateConfigFileName(const QString &fileName)
{
if (m_fileName!=fileName)
{
m_fileName = fileName;
QString curPath = QFileInfo(fileName).path();
setWorkingDir(curPath);
addRecentFile(fileName);
updateTitle();
}
}
void MainWindow::loadConfigFromFile(const QString & fileName)
{
// save full path info of original file
QString absFileName = QFileInfo(fileName).absoluteFilePath();
// updates the current directory
updateConfigFileName(fileName);
// open the specified configuration file
m_expert->loadConfig(absFileName);
m_wizard->refresh();
updateLaunchButtonState();
m_modified = false;
updateTitle();
}
void MainWindow::saveConfig(const QString &fileName)
{
if (fileName.isEmpty()) return;
QFile f(fileName);
if (!f.open(QIODevice::WriteOnly | QIODevice::Text ))
{
QMessageBox::warning(this,
tr("Error saving"),
QString(tr("Error: cannot open the file "))+fileName+tr(" for writing!\n")+
tr("Reason given: ")+QString::number(f.error()));
return;
}
QTextStream t(&f);
t.device()->setTextModeEnabled(false);
m_expert->writeConfig(t,false,false,false);
updateConfigFileName(fileName);
m_modified = false;
updateTitle();
}
bool MainWindow::saveConfig()
{
if (m_fileName.isEmpty())
{
return saveConfigAs();
}
else
{
saveConfig(m_fileName);
return true;
}
}
bool MainWindow::saveConfigAs()
{
QString fileName = QFileDialog::getSaveFileName(this, QString(),
m_workingDir->text()+QString::fromLatin1("/Doxyfile"));
if (fileName.isEmpty()) return false;
saveConfig(fileName);
return true;
}
void MainWindow::makeDefaults()
{
if (QMessageBox::question(this,tr("Use current setting at startup?"),
tr("Do you want to save the current settings "
"and use them next time Doxywizard starts?"),
QMessageBox::Save|
QMessageBox::Cancel)==QMessageBox::Save)
{
//printf("MainWindow:makeDefaults()\n");
m_expert->saveSettings(&m_settings);
m_settings.setValue(QString::fromLatin1("wizard/loadsettings"), true);
m_settings.sync();
}
}
void MainWindow::clearRecent()
{
if (QMessageBox::question(this,tr("Clear the list of recent files?"),
tr("Do you want to clear the list of recently "
"loaded configuration files?"),
QMessageBox::Yes|
QMessageBox::Cancel)==QMessageBox::Yes)
{
m_recentMenu->clear();
m_recentFiles.clear();
for (int i=0;i<MAX_RECENT_FILES;i++)
{
m_settings.setValue(QString::fromLatin1("recent/config%1").arg(i),QString::fromLatin1(""));
}
m_clearRecent->setEnabled(false);
m_recentMenu->setEnabled(false);
m_settings.sync();
}
}
void MainWindow::resetToDefaults()
{
if (QMessageBox::question(this,tr("Reset settings to their default values?"),
tr("Do you want to revert all settings back "
"to their original values?"),
QMessageBox::Reset|
QMessageBox::Cancel)==QMessageBox::Reset)
{
//printf("MainWindow:resetToDefaults()\n");
m_expert->resetToDefaults();
m_settings.setValue(QString::fromLatin1("wizard/loadsettings"), false);
m_settings.sync();
m_modified = false;
updateTitle();
m_wizard->refresh();
}
}
void MainWindow::loadSettings()
{
QVariant geometry = m_settings.value(QString::fromLatin1("main/geometry"));
QVariant state = m_settings.value(QString::fromLatin1("main/state"));
QVariant wizState = m_settings.value(QString::fromLatin1("wizard/state"));
QVariant loadSettings = m_settings.value(QString::fromLatin1("wizard/loadsettings"));
QVariant workingDir = m_settings.value(QString::fromLatin1("wizard/workingdir"));
if (!geometry.isNull()) restoreGeometry(geometry.toByteArray());
if (!state.isNull()) restoreState (state.toByteArray());
if (!wizState.isNull()) m_wizard->restoreState(wizState.toByteArray());
if (!loadSettings.isNull() && loadSettings.toBool())
{
m_expert->loadSettings(&m_settings);
if (!workingDir.isNull() && QDir(workingDir.toString()).exists())
{
setWorkingDir(workingDir.toString());
}
}
/* due to prepend use list in reversed order */
for (int i=MAX_RECENT_FILES;i>=0;i--)
{
QString entry = m_settings.value(QString::fromLatin1("recent/config%1").arg(i)).toString();
if (!entry.isEmpty() && QFileInfo(entry).exists())
{
addRecentFileList(entry);
}
}
updateRecentFile();
}
void MainWindow::saveSettings()
{
QSettings settings(QString::fromLatin1("Doxygen.org"), QString::fromLatin1("Doxywizard"));
m_settings.setValue(QString::fromLatin1("main/geometry"), saveGeometry());
m_settings.setValue(QString::fromLatin1("main/state"), saveState());
m_settings.setValue(QString::fromLatin1("wizard/state"), m_wizard->saveState());
m_settings.setValue(QString::fromLatin1("wizard/workingdir"), m_workingDir->text());
}
void MainWindow::selectTab(int id)
{
if (id==0) m_wizard->refresh();
else if (id==1) m_expert->refresh();
}
void MainWindow::selectRunTab()
{
m_tabs->setCurrentIndex(2);
}
void MainWindow::addRecentFile(const QString &fileName)
{
addRecentFileList(fileName);
updateRecentFile();
}
void MainWindow::addRecentFileList(const QString &fileName)
{
int i=m_recentFiles.indexOf(fileName);
if (i!=-1) m_recentFiles.removeAt(i);
// not found
if (m_recentFiles.count() < MAX_RECENT_FILES) // append
{
m_recentFiles.prepend(fileName);
}
else // add + drop last item
{
m_recentFiles.removeLast();
m_recentFiles.prepend(fileName);
}
m_clearRecent->setEnabled(m_recentFiles.count()>0);
m_recentMenu->setEnabled(m_recentFiles.count()>0);
m_settings.sync();
}
void MainWindow::updateRecentFile(void)
{
m_recentMenu->clear();
int i=0;
foreach( QString str, m_recentFiles )
{
m_recentMenu->addAction(str);
m_settings.setValue(QString::fromLatin1("recent/config%1").arg(i++),str);
}
for (;i<MAX_RECENT_FILES;i++)
{
m_settings.setValue(QString::fromLatin1("recent/config%1").arg(i),QString::fromLatin1(""));
}
m_clearRecent->setEnabled(m_recentFiles.count()>0);
m_recentMenu->setEnabled(m_recentFiles.count()>0);
m_settings.sync();
}
void MainWindow::openRecent(QAction *action)
{
if (discardUnsavedChanges(false))
{
loadConfigFromFile(action->text());
}
}
void MainWindow::runDoxygenMenu()
{
m_tabs->setCurrentWidget(m_runTab);
runDoxygen();
}
void MainWindow::runDoxygen()
{
if (!m_running)
{
QString doxygenPath;
#if defined(Q_OS_MACX)
doxygenPath = qApp->applicationDirPath()+QString::fromLatin1("/../Resources/");
qDebug() << tr("Doxygen path: ") << doxygenPath;
if ( !QFile(doxygenPath + QString::fromLatin1("doxygen")).exists() )
{
// No Doxygen binary in the resources, if there is a system Doxygen binary, use that instead
if ( QFile(QString::fromLatin1("/usr/local/bin/doxygen")).exists() )
{
doxygenPath = QString::fromLatin1("/usr/local/bin/");
}
else
{
qDebug() << tr("Can't find the doxygen command, make sure it's in your $$PATH");
doxygenPath = QString::fromLatin1("");
}
}
qDebug() << tr("Getting doxygen from: ") << doxygenPath;
#endif
m_runProcess->setReadChannel(QProcess::StandardOutput);
m_runProcess->setProcessChannelMode(QProcess::MergedChannels);
m_runProcess->setWorkingDirectory(m_workingDir->text());
QStringList env=QProcess::systemEnvironment();
// set PWD environment variable to m_workingDir
env.replaceInStrings(QRegularExpression(QString::fromLatin1("^PWD=(.*)"),QRegularExpression::CaseInsensitiveOption),
QString::fromLatin1("PWD=")+m_workingDir->text());
m_runProcess->setEnvironment(env);
QStringList args;
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
QStringList runOptions = m_runOptions->text().split(QLatin1Char(' '),QString::SkipEmptyParts);
#else
QStringList runOptions = m_runOptions->text().split(QLatin1Char(' '),Qt::SkipEmptyParts);
#endif
args << runOptions;
args << QString::fromLatin1("-b"); // make stdout unbuffered
args << QString::fromLatin1("-"); // read config from stdin
m_outputLog->clear();
m_runProcess->start(doxygenPath + QString::fromLatin1("doxygen"), args);
if (!m_runProcess->waitForStarted())
{
m_outputLog->append(APPQT(QString::fromLatin1("*** Failed to run doxygen\n")));
return;
}
QTextStream t(m_runProcess);
m_expert->writeConfig(t,false,false,false);
t.flush();
m_runProcess->closeWriteChannel();
if (m_runProcess->state() == QProcess::NotRunning)
{
m_outputLog->append(APPQT(QString::fromLatin1("*** Failed to run doxygen\n")));
}
else
{
m_saveLog->setEnabled(false);
m_running=true;
m_run->setText(tr("Stop doxygen"));
m_runMenu->setEnabled(false);
m_runStatus->setText(tr("Status: running"));
m_timer->start(1000);
}
}
else
{
m_running=false;
m_run->setText(tr("Run doxygen"));
m_runStatus->setText(tr("Status: not running"));
m_runProcess->kill();
m_timer->stop();
//updateRunnable(m_workingDir->text());
}
}
void MainWindow::readStdout()
{
if (m_running)
{
QByteArray data = m_runProcess->readAllStandardOutput();
QString text = QString::fromUtf8(data);
if (!text.isEmpty())
{
QScrollBar *vbar = m_outputLog->verticalScrollBar();
const QTextCursor old_cursor = m_outputLog->textCursor();
const bool is_scrolled_up = vbar->value() == vbar->maximum();
const int distanceFromBottom = vbar->minimum() - vbar->value();
m_outputLog->moveCursor(QTextCursor::End);
m_outputLog->insertPlainText(text);
if (old_cursor.hasSelection() || !is_scrolled_up)
{
m_outputLog->setTextCursor(old_cursor);
vbar->setValue(vbar->minimum() - distanceFromBottom);
}
else
{
m_outputLog->moveCursor(QTextCursor::End);
vbar->setValue(m_outputLog->verticalScrollBar()->maximum());
}
}
}
}
void MainWindow::runComplete()
{
if (m_running)
{
m_outputLog->append(APPQT(tr("*** Doxygen has finished\n")));
}
else
{
m_outputLog->append(APPQT(tr("*** Canceled by user\n")));
}
m_outputLog->ensureCursorVisible();
m_run->setText(tr("Run doxygen"));
m_runStatus->setText(tr("Status: not running"));
m_running=false;
m_runMenu->setEnabled(true);
updateLaunchButtonState();
//updateRunnable(m_workingDir->text());
m_saveLog->setEnabled(true);
}
void MainWindow::updateLaunchButtonState()
{
m_launchHtml->setEnabled(m_expert->htmlOutputPresent(m_workingDir->text()));
#if 0
m_launchPdf->setEnabled(m_expert->pdfOutputPresent(m_workingDir->text()));
#endif
}
void MainWindow::showHtmlOutput()
{
QString indexFile = m_expert->getHtmlOutputIndex(m_workingDir->text());
QFileInfo fi(indexFile);
// TODO: the following doesn't seem to work with IE
#ifdef _WIN32
//QString indexUrl(QString::fromLatin1("file:///"));
ShellExecute(nullptr, L"open", (LPCWSTR)fi.absoluteFilePath().utf16(), nullptr, nullptr, SW_SHOWNORMAL);
#else
QString indexUrl(QString::fromLatin1("file://"));
indexUrl+=fi.absoluteFilePath();
QDesktopServices::openUrl(QUrl(indexUrl));
#endif
}
void MainWindow::saveLog()
{
QString fn = QFileDialog::getSaveFileName(this, tr("Save log file"),
m_workingDir->text()+
QString::fromLatin1("/doxygen_log.txt"));
if (!fn.isEmpty())
{
QFile f(fn);
if (f.open(QIODevice::WriteOnly))
{
QTextStream t(&f);
t << m_outputLog->toPlainText();
statusBar()->showMessage(tr("Output log saved"),messageTimeout);
}
else
{
QMessageBox::warning(nullptr,tr("Warning"),
tr("Cannot open file ")+fn+tr(" for writing. Nothing saved!"),tr("ok"));
}
}
}
void MainWindow::showSettings()
{
QString text;
QTextStream t(&text);
if (m_showCondensedSettings->isChecked())
{
m_expert->writeConfig(t,true,true,true);
}
else
{
m_expert->writeConfig(t,true,false,true);
}
m_outputLog->clear();
m_outputLog->append(APPQT(text));
m_outputLog->ensureCursorVisible();
m_saveLog->setEnabled(true);
}
void MainWindow::configChanged()
{
m_modified = true;
updateTitle();
}
void MainWindow::updateTitle()
{
QString title = tr("Doxygen GUI frontend");
m_resetDefault->setEnabled(m_modified);
if (m_modified)
{
title+=QString::fromLatin1(" +");
}
if (!m_fileName.isEmpty())
{
title+=QString::fromLatin1(" (")+m_fileName+QString::fromLatin1(")");
}
setWindowTitle(title);
}
bool MainWindow::discardUnsavedChanges(bool saveOption)
{
if (m_modified)
{
QMessageBox::StandardButton button;
if (saveOption)
{
button = QMessageBox::question(this,
tr("Unsaved changes"),
tr("Unsaved changes will be lost! Do you want to save the configuration file?"),
QMessageBox::Save |
QMessageBox::Discard |
QMessageBox::Cancel
);
if (button==QMessageBox::Save)
{
return saveConfig();
}
}
else
{
button = QMessageBox::question(this,
tr("Unsaved changes"),
tr("Unsaved changes will be lost! Do you want to continue?"),
QMessageBox::Discard |
QMessageBox::Cancel
);
}
return button==QMessageBox::Discard;
}
return true;
}
void MainWindow::outputLogStart()
{
m_outputLogTextCount = 0;
m_outputLog->clear();
}
void MainWindow::outputLogText(QString text)
{
m_outputLogTextCount++;
m_outputLog->append(APPQT(text));
}
void MainWindow::outputLogFinish()
{
if (m_outputLogTextCount > 0)
{
selectRunTab();
}
m_outputLog->ensureCursorVisible();
m_saveLog->setEnabled(true);
}
//-----------------------------------------------------------------------
int main(int argc,char **argv)
{
static const char ENV_VAR_QT_DEVICE_PIXEL_RATIO[] = "QT_DEVICE_PIXEL_RATIO";
if (!qEnvironmentVariableIsSet(ENV_VAR_QT_DEVICE_PIXEL_RATIO)
&& !qEnvironmentVariableIsSet("QT_AUTO_SCREEN_SCALE_FACTOR")
&& !qEnvironmentVariableIsSet("QT_SCALE_FACTOR")
&& !qEnvironmentVariableIsSet("QT_SCREEN_SCALE_FACTORS")) {
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
}
QApplication a(argc,argv);
int locArgc = argc;
if (locArgc == 2)
{
if (!qstrcmp(argv[1],"--help"))
{
QMessageBox msgBox;
msgBox.setText(QString::fromLatin1("Usage: %1 [config file]").arg(QString::fromLatin1(argv[0])));
msgBox.exec();
exit(0);
}
else if (!qstrcmp(argv[1],"--version"))
{
QMessageBox msgBox;
if (!qstrcmp(qVersion(),QT_VERSION_STR))
{
msgBox.setText(QString::fromLatin1("Doxywizard version: %1, Qt version: %2").arg(QString::fromLatin1(getFullVersion().c_str()),QString::fromLatin1(QT_VERSION_STR)));
}
else
{
msgBox.setText(QString::fromLatin1("Doxywizard version: %1, Qt version: created with %2, running with %3").arg(QString::fromLatin1(getFullVersion().c_str()),QString::fromLatin1(QT_VERSION_STR),QString::fromLatin1(qVersion())));
}
msgBox.exec();
exit(0);
}
}
if (!qstrcmp(argv[1],"--debug") && ((locArgc == 2) || (locArgc == 3)))
{
DoxygenWizard::debugFlag = true;
locArgc--;
}
if (locArgc > 2)
{
QMessageBox msgBox;
msgBox.setText(QString::fromLatin1("Too many arguments specified\n\nUsage: %1 [config file]").arg(QString::fromLatin1(argv[0])));
msgBox.exec();
exit(1);
}
else
{
MainWindow &main = MainWindow::instance();
if (locArgc==2 && argv[argc-1][0]!='-') // name of config file as an argument
{
main.loadConfigFromFile(QString::fromLocal8Bit(argv[argc-1]));
}
main.show();
return a.exec();
}
}

View File

@ -0,0 +1,123 @@
/******************************************************************************
*
* Copyright (C) 1997-2019 by Dimitri van Heesch.
*
* Permission to use, copy, modify, and distribute this software and its
* documentation under the terms of the GNU General Public License is hereby
* granted. No representations are made about the suitability of this software
* for any purpose. It is provided "as is" without express or implied warranty.
* See the GNU General Public License for more details.
*
*/
#ifndef DOXYWIZARD_H
#define DOXYWIZARD_H
#include <QMainWindow>
#include <QSettings>
#include <QStringList>
class Expert;
class Wizard;
class QLabel;
class QCheckBox;
class QLineEdit;
class QPushButton;
class QTextBrowser;
class QMenu;
class QProcess;
class QTimer;
class QTabWidget;
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
static MainWindow &instance();
void loadConfigFromFile(const QString &fileName);
void loadSettings();
void saveSettings();
void closeEvent(QCloseEvent *event);
QString configFileName() const { return m_fileName; }
void updateTitle();
// access routines for outputLog pane
void outputLogStart();
void outputLogText(QString text);
void outputLogFinish();
public slots:
void manual();
void about();
void openConfig();
bool saveConfig();
bool saveConfigAs();
void makeDefaults();
void resetToDefaults();
void selectTab(int);
void quit();
private slots:
void openRecent(QAction *action);
void selectWorkingDir();
void updateWorkingDir();
void runDoxygen();
void runDoxygenMenu();
void readStdout();
void runComplete();
void showHtmlOutput();
void saveLog();
void showSettings();
void configChanged();
void clearRecent();
void selectRunTab();
private:
MainWindow();
void saveConfig(const QString &fileName);
void addRecentFile(const QString &fileName);
void addRecentFileList(const QString &fileName);
void updateRecentFile(void);
void updateConfigFileName(const QString &fileName);
void setWorkingDir(const QString &dirName);
void updateLaunchButtonState();
bool discardUnsavedChanges(bool saveOption=true);
QLineEdit *m_workingDir;
QLineEdit *m_runOptions;
QPushButton *m_selWorkingDir;
QPushButton *m_run;
QAction *m_runMenu;
QPushButton *m_saveLog;
QCheckBox *m_showCondensedSettings;
QPushButton *m_launchHtml;
QPushButton *m_launchPdf;
QTextBrowser *m_outputLog;
QLabel *m_runStatus;
Expert *m_expert;
Wizard *m_wizard;
QWidget *m_runTab;
QString m_fileName;
QSettings m_settings;
QMenu *m_recentMenu;
QStringList m_recentFiles;
QAction *m_resetDefault;
QAction *m_clearRecent;
QProcess *m_runProcess;
QTimer *m_timer;
QTabWidget *m_tabs;
int m_outputLogTextCount = 0;
bool m_running;
bool m_modified;
};
/*! \brief This class serves as a namespace for global variables used by the doxygen wizard.
*
* All fields in this class are public and static, so they can be used directly.
*/
class DoxygenWizard
{
public:
static bool debugFlag;
};
#endif

View File

@ -0,0 +1,11 @@
<!DOCTYPE RCC><RCC version="1.0">
<qresource prefix="/">
<file alias="config.xml">../../src/config.xml</file>
<file>images/add.png</file>
<file>images/del.png</file>
<file>images/file.png</file>
<file>images/folder.png</file>
<file>images/refresh.png</file>
<file>images/tunecolor.png</file>
</qresource>
</RCC>

1016
addon/doxywizard/expert.cpp Normal file

File diff suppressed because it is too large Load Diff

86
addon/doxywizard/expert.h Normal file
View File

@ -0,0 +1,86 @@
/******************************************************************************
*
* Copyright (C) 1997-2019 by Dimitri van Heesch.
*
* Permission to use, copy, modify, and distribute this software and its
* documentation under the terms of the GNU General Public License is hereby
* granted. No representations are made about the suitability of this software
* for any purpose. It is provided "as is" without express or implied warranty.
* See the GNU General Public License for more details.
*
*/
#ifndef EXPERT_H
#define EXPERT_H
#include <QSplitter>
#include <QDomElement>
#include <QHash>
#include "docintf.h"
#include "adapter.h"
class QTreeWidget;
class QTreeWidgetItem;
class QStackedWidget;
class QSettings;
class QTextBrowser;
class QPushButton;
class Input;
class Expert : public QSplitter, public DocIntf
{
Q_OBJECT
public:
Expert();
~Expert();
void loadSettings(QSettings *);
void saveSettings(QSettings *);
void loadConfig(const QString &fileName);
bool writeConfig(QTextStream &t,bool brief,bool condensed, bool convert);
QByteArray saveInnerState () const;
bool restoreInnerState ( const QByteArray & state );
const QHash<QString,Input*> &modelData() const { return m_options; }
void resetToDefaults();
bool htmlOutputPresent(const QString &workingDir) const;
bool pdfOutputPresent(const QString &workingDir) const;
QString getHtmlOutputIndex(const QString &workingDir) const;
// DocIntf methods
void setHeader(const char *name);
void add(const char *name,const char *doc);
public slots:
void activateTopic(QTreeWidgetItem *,QTreeWidgetItem *);
QWidget *createTopicWidget(QDomElement &elem);
void refresh();
private slots:
void showHelp(Input *);
void nextTopic();
void prevTopic();
signals:
void changed();
void done();
private:
void createTopics(const QDomElement &);
void saveTopic(QTextStream &t,QDomElement &elem,TextCodecAdapter *codec,bool brief,bool dondensed,bool convert);
QSplitter *m_splitter;
QTextBrowser *m_helper;
QTreeWidget *m_treeWidget;
QStackedWidget *m_topicStack;
QHash<QString,QWidget *> m_topics;
QHash<QString,QObject *> m_optionWidgets;
QHash<QString,Input *> m_options;
QPushButton *m_next;
QPushButton *m_prev;
QDomElement m_rootElement;
bool m_inShowHelp;
QString m_header;
};
#endif

View File

@ -0,0 +1,51 @@
/******************************************************************************
*
* Copyright (C) 1997-2019 by Dimitri van Heesch.
*
* Permission to use, copy, modify, and distribute this software and its
* documentation under the terms of the GNU General Public License is hereby
* granted. No representations are made about the suitability of this software
* for any purpose. It is provided "as is" without express or implied warranty.
* See the GNU General Public License for more details.
*
*/
#ifndef HELPLABEL_H
#define HELPLABEL_H
#include <QLabel>
#include <QMenu>
class HelpLabel : public QLabel
{
Q_OBJECT
public:
HelpLabel(const QString &text) : QLabel(text)
{ setContextMenuPolicy(Qt::CustomContextMenu);
connect(this,SIGNAL(customContextMenuRequested(const QPoint&)),
this,SLOT(showMenu(const QPoint&)));
}
signals:
void enter();
void reset();
private slots:
void showMenu(const QPoint &p)
{
QMenu menu(this);
QAction *a = menu.addAction(tr("Reset to default"));
if (menu.exec(mapToGlobal(p))==a)
{
reset();
}
}
protected:
void enterEvent(
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QEvent * event
#else
QEnterEvent * event
#endif
) { enter(); QLabel::enterEvent(event); }
};
#endif

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

51
addon/doxywizard/input.h Normal file
View File

@ -0,0 +1,51 @@
/******************************************************************************
*
* Copyright (C) 1997-2019 by Dimitri van Heesch.
*
* Permission to use, copy, modify, and distribute this software and its
* documentation under the terms of the GNU General Public License is hereby
* granted. No representations are made about the suitability of this software
* for any purpose. It is provided "as is" without express or implied warranty.
* See the GNU General Public License for more details.
*
*/
#ifndef INPUT_H
#define INPUT_H
#include <QVariant>
#include "adapter.h"
class QTextStream;
class QTextStream;
class Input
{
public:
enum Kind
{
Bool,
Int,
String,
StrList,
Obsolete
};
virtual ~Input() {}
virtual QVariant &value() = 0;
virtual void update() = 0;
virtual Kind kind() const = 0;
virtual QString docs() const = 0;
virtual QString id() const = 0;
virtual QString templateDocs() const = 0;
virtual void addDependency(Input *option) = 0;
virtual void setEnabled(bool) = 0;
virtual void updateDependencies() = 0;
virtual void reset() = 0;
virtual bool isDefault() = 0;
virtual void writeValue(QTextStream &t,TextCodecAdapter *codec,bool convert) = 0;
virtual void setTemplateDocs(const QString &docs) = 0;
virtual bool isEmpty() { return false; };
};
#endif

View File

@ -0,0 +1,149 @@
/******************************************************************************
*
* Copyright (C) 1997-2019 by Dimitri van Heesch.
*
* Permission to use, copy, modify, and distribute this software and its
* documentation under the terms of the GNU General Public License is hereby
* granted. No representations are made about the suitability of this software
* for any purpose. It is provided "as is" without express or implied warranty.
* See the GNU General Public License for more details.
*
*/
#include "inputbool.h"
#include "helplabel.h"
#include "config_msg.h"
#include <QCheckBox>
#include <QTextStream>
#include <QGridLayout>
InputBool::InputBool( QGridLayout *layout, int &row,
const QString &id, bool checked,
const QString &docs )
: m_default(checked), m_docs(docs), m_id(id)
{
m_lab = new HelpLabel(id);
m_cb = new QCheckBox;
layout->addWidget(m_lab,row, 0);
layout->addWidget(m_cb,row, 1);
m_enabled = true;
m_state=!checked; // force update
setValue(checked);
connect( m_cb, SIGNAL(toggled(bool)), SLOT(setValue(bool)) );
connect( m_lab, SIGNAL(enter()), SLOT(help()) );
connect( m_lab, SIGNAL(reset()), SLOT(reset()) );
row++;
}
void InputBool::help()
{
showHelp(this);
}
void InputBool::setEnabled(bool b)
{
m_enabled = b;
m_cb->setEnabled(b);
m_lab->setEnabled(b);
updateDefault();
updateDependencies();
}
void InputBool::updateDependencies()
{
for (int i=0;i<m_dependencies.count();i++)
{
m_dependencies[i]->setEnabled(m_enabled && m_state);
}
}
void InputBool::setValue( bool s )
{
if (m_state!=s)
{
m_state=s;
updateDefault();
updateDependencies();
m_cb->setChecked( s );
m_value = m_state;
emit changed();
}
}
void InputBool::updateDefault()
{
if (m_state==m_default || !m_lab->isEnabled())
{
m_lab->setText(QString::fromLatin1("<qt>")+m_id+QString::fromLatin1("</qt>"));
}
else
{
m_lab->setText(QString::fromLatin1("<qt><font color='red'>")+m_id+QString::fromLatin1("</font></qt>"));
}
}
QVariant &InputBool::value()
{
return m_value;
}
bool InputBool::convertToBool(const QVariant &value,bool &isValid)
{
QString v = value.toString().toLower();
if (v==QString::fromLatin1("yes") || v==QString::fromLatin1("true") ||
v==QString::fromLatin1("1") || v==QString::fromLatin1("all"))
{
isValid = true;
return true;
}
else if (v==QString::fromLatin1("no") || v==QString::fromLatin1("false") ||
v==QString::fromLatin1("0") || v==QString::fromLatin1("none"))
{
isValid = true;
return false;
}
else
{
isValid = false;
return false;
}
}
void InputBool::update()
{
bool isValid=false;
bool b = convertToBool(m_value,isValid);
if (isValid)
{
m_state = b;
}
else
{
config_warn("argument '%s' for option %s is not a valid boolean value."
" Using the default: %s!",qPrintable(m_value.toString()),qPrintable(m_id),m_default?"YES":"NO");
m_state = m_default;
}
m_cb->setChecked( m_state );
updateDefault();
updateDependencies();
}
void InputBool::reset()
{
setValue(m_default);
}
void InputBool::writeValue(QTextStream &t,TextCodecAdapter *codec,bool)
{
if (m_state)
t << codec->encode(QString::fromLatin1("YES"));
else
t << codec->encode(QString::fromLatin1("NO"));
}
bool InputBool::isDefault()
{
return m_state == m_default;
}

View File

@ -0,0 +1,72 @@
/******************************************************************************
*
* Copyright (C) 1997-2019 by Dimitri van Heesch.
*
* Permission to use, copy, modify, and distribute this software and its
* documentation under the terms of the GNU General Public License is hereby
* granted. No representations are made about the suitability of this software
* for any purpose. It is provided "as is" without express or implied warranty.
* See the GNU General Public License for more details.
*
*/
#ifndef INPUTBOOL_H
#define INPUTBOOL_H
#include "input.h"
#include <QObject>
class QCheckBox;
class QGridLayout;
class QLabel;
class InputBool : public QObject, public Input
{
Q_OBJECT
public:
InputBool(QGridLayout *layout,int &row,const QString &id,
bool enabled, const QString &docs );
// Input
QVariant &value();
void update();
Kind kind() const { return Bool; }
QString docs() const { return m_docs; }
QString id() const { return m_id; }
QString templateDocs() const { return m_tdocs; }
void addDependency(Input *option) { m_dependencies+=option; }
void setEnabled(bool);
void updateDependencies();
bool isDefault();
void writeValue(QTextStream &t,TextCodecAdapter *codec,bool convert);
void setTemplateDocs(const QString &docs) { m_tdocs = docs; }
static bool convertToBool(const QVariant &v,bool &isValid);
public slots:
void reset();
void setValue(bool);
signals:
void changed();
void toggle(QString,bool);
void showHelp(Input *);
private slots:
void help();
private:
void updateDefault();
bool m_state;
bool m_default;
bool m_enabled;
QVariant m_value;
QCheckBox *m_cb;
QString m_docs;
QList<Input*> m_dependencies;
QString m_id;
QLabel *m_lab;
QString m_tdocs;
};
#endif

View File

@ -0,0 +1,135 @@
/******************************************************************************
*
* Copyright (C) 1997-2019 by Dimitri van Heesch.
*
* Permission to use, copy, modify, and distribute this software and its
* documentation under the terms of the GNU General Public License is hereby
* granted. No representations are made about the suitability of this software
* for any purpose. It is provided "as is" without express or implied warranty.
* See the GNU General Public License for more details.
*
*/
#include "inputint.h"
#include "helplabel.h"
#include "config_msg.h"
#include <QSpinBox>
#include <QGridLayout>
#include <QWheelEvent>
#include <QTextStream>
class NoWheelSpinBox : public QSpinBox
{
protected:
void wheelEvent(QWheelEvent *e)
{
e->ignore();
}
};
InputInt::InputInt( QGridLayout *layout,int &row,
const QString & id,
int defVal, int minVal,int maxVal,
const QString & docs )
: m_default(defVal), m_minVal(minVal), m_maxVal(maxVal), m_docs(docs), m_id(id)
{
m_lab = new HelpLabel(id);
m_sp = new NoWheelSpinBox;
m_sp->setMinimum(minVal);
m_sp->setMaximum(maxVal);
m_sp->setSingleStep(1);
m_val=defVal-1; // force update
setValue(defVal);
layout->addWidget( m_lab, row, 0 );
layout->addWidget( m_sp, row, 1 );
connect(m_sp, SIGNAL(valueChanged(int)),
this, SLOT(setValue(int)) );
connect( m_lab, SIGNAL(enter()), SLOT(help()) );
connect( m_lab, SIGNAL(reset()), SLOT(reset()) );
row++;
}
void InputInt::help()
{
showHelp(this);
}
void InputInt::setValue(int val)
{
int newVal = val;
newVal = qMax(m_minVal,newVal);
newVal = qMin(m_maxVal,newVal);
if (val != newVal)
{
config_warn("argument '%d' for option %s is not a valid number in the range [%d..%d]!"
" Using the default: %d!\n",val,qPrintable(m_id),m_minVal,m_maxVal,m_default);
newVal = m_default;
}
if (newVal!=m_val)
{
m_val = newVal;
m_sp->setValue(newVal);
m_value = m_val;
updateDefault();
}
}
void InputInt::updateDefault()
{
{
if (m_val==m_default || !m_lab->isEnabled())
{
m_lab->setText(QString::fromLatin1("<qt>")+m_id+QString::fromLatin1("</qt>"));
}
else
{
m_lab->setText(QString::fromLatin1("<qt><font color='red'>")+m_id+QString::fromLatin1("</font></qt>"));
}
emit changed();
}
}
void InputInt::setEnabled(bool state)
{
m_lab->setEnabled(state);
m_sp->setEnabled(state);
updateDefault();
}
QVariant &InputInt::value()
{
return m_value;
}
void InputInt::update()
{
bool ok;
int newVal = m_value.toInt(&ok);
if (!ok)
{
config_warn("argument '%s' for option %s is not a valid number in the range [%d..%d]!"
" Using the default: %d!\n",qPrintable(m_value.toString()),qPrintable(m_id),m_minVal,m_maxVal,m_default);
newVal = m_default;
}
setValue(newVal);
}
void InputInt::reset()
{
setValue(m_default);
}
void InputInt::writeValue(QTextStream &t,TextCodecAdapter *,bool)
{
t << m_val;
}
bool InputInt::isDefault()
{
return m_val == m_default;
}

View File

@ -0,0 +1,73 @@
/******************************************************************************
*
* Copyright (C) 1997-2019 by Dimitri van Heesch.
*
* Permission to use, copy, modify, and distribute this software and its
* documentation under the terms of the GNU General Public License is hereby
* granted. No representations are made about the suitability of this software
* for any purpose. It is provided "as is" without express or implied warranty.
* See the GNU General Public License for more details.
*
*/
#ifndef INPUTINT_H
#define INPUTINT_H
#include "input.h"
#include <QObject>
class QGridLayout;
class QLabel;
class QSpinBox;
class InputInt : public QObject, public Input
{
Q_OBJECT
public:
InputInt( QGridLayout *layout,int &row,
const QString &id, int defVal,
int minVal, int maxVal,
const QString &docs );
~InputInt() = default;
// Input
QVariant &value();
void update();
Kind kind() const { return Int; }
QString docs() const { return m_docs; }
QString id() const { return m_id; }
QString templateDocs() const { return m_tdocs; }
void addDependency(Input *) { Q_ASSERT(false); }
void setEnabled(bool);
void updateDependencies() {}
bool isDefault();
void writeValue(QTextStream &t,TextCodecAdapter *codec,bool convert);
void setTemplateDocs(const QString &docs) { m_tdocs = docs; }
public slots:
void reset();
void setValue(int val);
private slots:
void help();
signals:
void changed();
void showHelp(Input *);
private:
void updateDefault();
QLabel *m_lab;
QSpinBox *m_sp;
int m_val;
int m_default;
int m_minVal;
int m_maxVal;
QVariant m_value;
QString m_docs;
QString m_id;
QString m_tdocs;
};
#endif

View File

@ -0,0 +1,44 @@
/******************************************************************************
*
* Copyright (C) 1997-2021 by Dimitri van Heesch.
*
* Permission to use, copy, modify, and distribute this software and its
* documentation under the terms of the GNU General Public License is hereby
* granted. No representations are made about the suitability of this software
* for any purpose. It is provided "as is" without express or implied warranty.
* See the GNU General Public License for more details.
*
*/
#ifndef INPUTOBSOLETE_H
#define INPUTOBSOLETE_H
#include "input.h"
class InputObsolete : public Input
{
public:
InputObsolete(const QString &id, Kind orgKind) : m_id(id), m_orgKind(orgKind) {}
QVariant &value() { return m_value; }
void update() {}
Kind kind() const { return Obsolete; }
QString docs() const { return QString(); }
QString id() const { return m_id; }
QString templateDocs() const { return QString(); }
void addDependency(Input *) {}
void setEnabled(bool) {}
void updateDependencies() {}
void reset() {}
bool isDefault() { return false; }
void writeValue(QTextStream &,TextCodecAdapter *,bool) {}
void setTemplateDocs(const QString &) {}
bool isEmpty() { return false; };
Kind orgKind() const { return m_orgKind; }
private:
QString m_id;
Kind m_orgKind;
QVariant m_value;
};
#endif

View File

@ -0,0 +1,279 @@
/******************************************************************************
*
* Copyright (C) 1997-2019 by Dimitri van Heesch.
*
* Permission to use, copy, modify, and distribute this software and its
* documentation under the terms of the GNU General Public License is hereby
* granted. No representations are made about the suitability of this software
* for any purpose. It is provided "as is" without express or implied warranty.
* See the GNU General Public License for more details.
*
*/
#include "inputstring.h"
#include "helplabel.h"
#include "doxywizard.h"
#include "config_msg.h"
#include "config.h"
#include <QComboBox>
#include <QLineEdit>
#include <QGridLayout>
#include <QWheelEvent>
#include <QToolBar>
#include <QFileInfo>
#include <QFileDialog>
class NoWheelComboBox : public QComboBox
{
protected:
void wheelEvent(QWheelEvent *e)
{
e->ignore();
}
};
InputString::InputString( QGridLayout *layout,int &row,
const QString & id, const QString &s,
StringMode m, const QString &docs,
const QString &absPath )
: m_default(s), m_sm(m), m_index(0), m_docs(docs), m_id(id),
m_absPath(absPath==QString::fromLatin1("1"))
{
m_lab = new HelpLabel(id);
m_brFile = nullptr;
m_brDir = nullptr;
if (m==StringFixed)
{
layout->addWidget( m_lab, row, 0 );
m_com = new NoWheelComboBox;
layout->addWidget( m_com, row, 1, 1, 3, Qt::AlignLeft );
m_le=nullptr;
m_br=nullptr;
m_im=nullptr;
row++;
}
else
{
layout->addWidget( m_lab, row, 0 );
m_le = new QLineEdit;
m_le->setText( s );
m_im = nullptr;
//layout->setColumnMinimumWidth(2,150);
if (m==StringFile || m==StringDir || m==StringImage || m==StringFileDir)
{
QHBoxLayout *rowLayout = new QHBoxLayout;
rowLayout->addWidget( m_le);
m_br = new QToolBar;
m_br->setIconSize(QSize(24,24));
if (m==StringFile || m==StringImage || m==StringFileDir)
{
m_brFile = m_br->addAction(QIcon(QString::fromLatin1(":/images/file.png")),QString(),this,SLOT(browseFile()));
m_brFile->setToolTip(tr("Browse to a file"));
if (m==StringImage)
{
m_im = new QLabel;
m_im->setMinimumSize(1,55);
m_im->setAlignment(Qt::AlignLeft|Qt::AlignTop);
row++;
layout->addWidget( m_im,row,1 );
}
}
if (m==StringDir || m==StringFileDir)
{
m_brDir = m_br->addAction(QIcon(QString::fromLatin1(":/images/folder.png")),QString(),this,SLOT(browseDir()));
m_brDir->setToolTip(tr("Browse to a folder"));
}
rowLayout->addWidget( m_br);
layout->addLayout( rowLayout, m==StringImage?row-1:row, 1, 1, 2 );
}
else
{
layout->addWidget( m_le, row, 1, 1, 2 );
m_br=nullptr;
m_im=nullptr;
}
m_com=nullptr;
row++;
}
if (m_le) connect( m_le, SIGNAL(textChanged(const QString&)),
this, SLOT(setValue(const QString&)) );
if (m_com) connect( m_com, SIGNAL(textActivated(const QString &)),
this, SLOT(setValue(const QString &)) );
m_str = s+QChar::fromLatin1('!'); // force update
setValue(s);
connect( m_lab, SIGNAL(enter()), SLOT(help()) );
connect( m_lab, SIGNAL(reset()), SLOT(reset()) );
}
void InputString::help()
{
showHelp(this);
}
InputString::~InputString()
{
}
void InputString::setValue(const QString &s)
{
if (m_str!=s)
{
m_str = s;
m_value = m_str;
updateDefault();
}
}
void InputString::updateDefault()
{
{
if (m_str==m_default || !m_lab->isEnabled())
{
m_lab->setText(QString::fromLatin1("<qt>")+m_id+QString::fromLatin1("</qt>"));
}
else
{
m_lab->setText(QString::fromLatin1("<qt><font color='red'>")+m_id+QString::fromLatin1("</font></qt>"));
}
if (m_im)
{
if (m_str.isEmpty())
{
m_im->setText(tr("No Project logo selected."));
}
else
{
QFile Fout(m_str);
if(!Fout.exists())
{
m_im->setText(tr("Sorry, cannot find file(")+m_str+QString::fromLatin1(");"));
}
else
{
QPixmap pm(m_str);
if (!pm.isNull())
{
m_im->setPixmap(pm.scaledToHeight(55,Qt::SmoothTransformation));
}
else
{
m_im->setText(tr("Sorry, no preview available (")+m_str+QString::fromLatin1(");"));
}
}
}
}
if (m_le && m_le->text()!=m_str) m_le->setText( m_str );
emit changed();
}
}
void InputString::setEnabled(bool state)
{
m_lab->setEnabled(state);
if (m_le) m_le->setEnabled(state);
if (m_im) m_im->setEnabled(state);
if (m_br) m_br->setEnabled(state);
if (m_brFile) m_brFile->setEnabled(state);
if (m_brDir) m_brDir->setEnabled(state);
if (m_com) m_com->setEnabled(state);
updateDefault();
}
void InputString::browseFile()
{
QString path = QFileInfo(MainWindow::instance().configFileName()).path();
QString fileName = QFileDialog::getOpenFileName(&MainWindow::instance(),
tr("Select file"),path);
if (!fileName.isNull())
{
QDir dir(path);
if (!MainWindow::instance().configFileName().isEmpty() && dir.exists())
{
fileName = m_absPath ? fileName : dir.relativeFilePath(fileName);
}
setValue(fileName);
}
}
void InputString::browseDir()
{
QString path = QFileInfo(MainWindow::instance().configFileName()).path();
{
QString dirName = QFileDialog::getExistingDirectory(&MainWindow::instance(),
tr("Select directory"),path);
if (!dirName.isNull())
{
QDir dir(path);
if (!MainWindow::instance().configFileName().isEmpty() && dir.exists())
{
dirName = m_absPath ? dirName : dir.relativeFilePath(dirName);
}
setValue(dirName);
}
}
}
void InputString::clear()
{
setValue(QString());
}
void InputString::addValue(QString s)
{
if (m_sm==StringFixed)
{
m_values.append(s);
m_com->addItem(s);
}
}
void InputString::setDefault()
{
int index = m_values.indexOf(m_str);
if (index!=-1 && m_com) m_com->setCurrentIndex(index);
}
QVariant &InputString::value()
{
return m_value;
}
void InputString::update()
{
setValue(m_value.toString().trimmed());
setDefault();
}
void InputString::reset()
{
setValue(m_default);
setDefault();
}
void InputString::writeValue(QTextStream &t,TextCodecAdapter *codec,bool convert)
{
writeStringValue(t,codec,m_str,convert);
}
bool InputString::isDefault()
{
return m_str == m_default;
}
QString InputString::checkEnumVal(const QString &value)
{
QString val = value.trimmed().toLower();
QStringList::Iterator it;
for ( it= m_values.begin(); it != m_values.end(); ++it )
{
QString enumVal = *it;
if (enumVal.toLower() == val) return enumVal;
}
config_warn("argument '%s' for option %s is not a valid enum value."
" Using the default: %s!",qPrintable(value),qPrintable(m_id),qPrintable(m_default));
return m_default;
}

View File

@ -0,0 +1,103 @@
/******************************************************************************
*
* Copyright (C) 1997-2019 by Dimitri van Heesch.
*
* Permission to use, copy, modify, and distribute this software and its
* documentation under the terms of the GNU General Public License is hereby
* granted. No representations are made about the suitability of this software
* for any purpose. It is provided "as is" without express or implied warranty.
* See the GNU General Public License for more details.
*
*/
#ifndef INPUTSTRING_H
#define INPUTSTRING_H
#include "input.h"
#include <QObject>
#include <QMap>
#include <QStringList>
class QLabel;
class QLineEdit;
class QToolBar;
class QComboBox;
class QGridLayout;
class QAction;
class InputString : public QObject, public Input
{
Q_OBJECT
public:
enum StringMode { StringFree=0,
StringFile=1,
StringDir=2,
StringFixed=3,
StringImage=4,
StringFileDir=5
};
InputString( QGridLayout *layout,int &row,
const QString &id, const QString &s,
StringMode m,
const QString &docs,
const QString &absPath = QString() );
~InputString();
void addValue(QString s);
void setDefault();
// Input
QVariant &value();
void update();
Kind kind() const { return String; }
StringMode stringMode() const { return m_sm; }
QString docs() const { return m_docs; }
QString id() const { return m_id; }
QString templateDocs() const { return m_tdocs; }
void addDependency(Input *) { Q_ASSERT(false); }
void setEnabled(bool);
void updateDependencies() {}
bool isDefault();
void writeValue(QTextStream &t,TextCodecAdapter *codec,bool convert);
void setTemplateDocs(const QString &docs) { m_tdocs = docs; }
bool isEmpty() { return m_str.isEmpty(); }
QString checkEnumVal(const QString &value);
public slots:
void reset();
void setValue(const QString&);
signals:
void changed();
void showHelp(Input *);
private slots:
void browseFile();
void browseDir();
void clear();
void help();
private:
void updateDefault();
QLabel *m_lab;
QLineEdit *m_le;
QLabel *m_im;
QToolBar *m_br;
QAction *m_brFile;
QAction *m_brDir;
QComboBox *m_com;
QString m_str;
QString m_default;
StringMode m_sm;
QStringList m_values;
int m_index;
QVariant m_value;
QString m_docs;
QString m_id;
bool m_absPath;
QString m_tdocs;
};
#endif

View File

@ -0,0 +1,320 @@
/******************************************************************************
*
* Copyright (C) 1997-2019 by Dimitri van Heesch.
*
* Permission to use, copy, modify, and distribute this software and its
* documentation under the terms of the GNU General Public License is hereby
* granted. No representations are made about the suitability of this software
* for any purpose. It is provided "as is" without express or implied warranty.
* See the GNU General Public License for more details.
*
*/
#include "inputstrlist.h"
#include "helplabel.h"
#include "doxywizard.h"
#include "config.h"
#include <QToolBar>
#include <QGridLayout>
#include <QLineEdit>
#include <QListWidget>
#include <QFileInfo>
#include <QFileDialog>
#include <QTextStream>
InputStrList::InputStrList( QGridLayout *layout,int &row,
const QString & id,
const QStringList &sl, ListMode lm,
const QString & docs)
: m_default(sl), m_strList(sl), m_docs(docs), m_id(id)
{
m_lab = new HelpLabel( id );
m_le = new QLineEdit;
m_le->clear();
QToolBar *toolBar = new QToolBar;
toolBar->setIconSize(QSize(24,24));
m_add = toolBar->addAction(QIcon(QString::fromLatin1(":/images/add.png")),QString(),
this,SLOT(addString()));
m_add->setToolTip(tr("Add item"));
m_del = toolBar->addAction(QIcon(QString::fromLatin1(":/images/del.png")),QString(),
this,SLOT(delString()));
m_del->setToolTip(tr("Delete selected item"));
m_upd = toolBar->addAction(QIcon(QString::fromLatin1(":/images/refresh.png")),QString(),
this,SLOT(updateString()));
m_upd->setToolTip(tr("Update selected item"));
m_lb = new QListWidget;
//m_lb->setMinimumSize(400,100);
foreach (QString s, m_strList) m_lb->addItem(s);
m_brFile=nullptr;
m_brDir=nullptr;
if (lm!=ListString)
{
if (lm&ListFile)
{
m_brFile = toolBar->addAction(QIcon(QString::fromLatin1(":/images/file.png")),QString(),
this,SLOT(browseFiles()));
m_brFile->setToolTip(tr("Browse to a file"));
}
if (lm&ListDir)
{
m_brDir = toolBar->addAction(QIcon(QString::fromLatin1(":/images/folder.png")),QString(),
this,SLOT(browseDir()));
m_brDir->setToolTip(tr("Browse to a folder"));
}
}
QHBoxLayout *rowLayout = new QHBoxLayout;
rowLayout->addWidget( m_le );
rowLayout->addWidget( toolBar );
layout->addWidget( m_lab, row,0 );
layout->addLayout( rowLayout, row,1,1,2 );
layout->addWidget( m_lb, row+1,1,1,2 );
row+=2;
m_value = m_strList;
connect(m_le, SIGNAL(returnPressed()),
this, SLOT(addString()) );
connect(m_lb, SIGNAL(currentTextChanged(const QString &)),
this, SLOT(selectText(const QString &)));
connect( m_lab, SIGNAL(enter()), SLOT(help()) );
connect( m_lab, SIGNAL(reset()), SLOT(reset()) );
}
void InputStrList::help()
{
showHelp(this);
}
void InputStrList::addString()
{
if (!m_le->text().isEmpty())
{
m_lb->addItem(m_le->text());
m_strList.append(m_le->text());
m_value = m_strList;
updateDefault();
emit changed();
m_le->clear();
}
}
void InputStrList::delString()
{
if (m_lb->currentRow()!=-1)
{
int itemIndex = m_lb->currentRow();
delete m_lb->currentItem();
m_strList.removeAt(itemIndex);
m_value = m_strList;
updateDefault();
emit changed();
}
}
void InputStrList::updateString()
{
if (m_lb->currentRow()!=-1 && !m_le->text().isEmpty())
{
m_lb->currentItem()->setText(m_le->text());
m_strList.insert(m_lb->currentRow(),m_le->text());
m_strList.removeAt(m_lb->currentRow()+1);
m_value = m_strList;
updateDefault();
emit changed();
}
}
void InputStrList::selectText(const QString &s)
{
m_le->setText(s);
}
void InputStrList::setEnabled(bool state)
{
m_lab->setEnabled(state);
m_le->setEnabled(state);
m_add->setEnabled(state);
m_del->setEnabled(state);
m_upd->setEnabled(state);
m_lb->setEnabled(state);
if (m_brFile) m_brFile->setEnabled(state);
if (m_brDir) m_brDir->setEnabled(state);
updateDefault();
}
void InputStrList::browseFiles()
{
QString path = QFileInfo(MainWindow::instance().configFileName()).path();
QStringList fileNames = QFileDialog::getOpenFileNames();
if (!fileNames.isEmpty())
{
QStringList::Iterator it;
for ( it= fileNames.begin(); it != fileNames.end(); ++it )
{
QString fileName;
QDir dir(path);
if (!MainWindow::instance().configFileName().isEmpty() && dir.exists())
{
fileName = dir.relativeFilePath(*it);
}
if (fileName.isEmpty())
{
fileName = *it;
}
m_lb->addItem(fileName);
m_strList.append(fileName);
m_value = m_strList;
updateDefault();
emit changed();
}
m_le->setText(m_strList[0]);
}
}
void InputStrList::browseDir()
{
QString path = QFileInfo(MainWindow::instance().configFileName()).path();
QString dirName = QFileDialog::getExistingDirectory();
if (!dirName.isNull())
{
QDir dir(path);
if (!MainWindow::instance().configFileName().isEmpty() && dir.exists())
{
dirName = dir.relativeFilePath(dirName);
}
if (dirName.isEmpty())
{
dirName=QString::fromLatin1(".");
}
m_lb->addItem(dirName);
m_strList.append(dirName);
m_value = m_strList;
updateDefault();
emit changed();
m_le->setText(dirName);
}
}
void InputStrList::setValue(const QStringList &sl)
{
m_le->clear();
m_lb->clear();
m_strList = sl;
for (int i=0;i<m_strList.size();i++)
{
m_lb->addItem(m_strList[i].trimmed());
}
updateDefault();
}
QVariant &InputStrList::value()
{
return m_value;
}
void InputStrList::update()
{
setValue(m_value.toStringList());
}
void InputStrList::updateDefault()
{
if (isDefault() || !m_lab->isEnabled())
{
m_lab->setText(QString::fromLatin1("<qt>")+m_id+QString::fromLatin1("</qt>"));
}
else
{
m_lab->setText(QString::fromLatin1("<qt><font color='red'>")+m_id+QString::fromLatin1("</font></qt>"));
}
}
void InputStrList::reset()
{
setValue(m_default);
}
void InputStrList::writeValue(QTextStream &t,TextCodecAdapter *codec,bool convert)
{
bool first=true;
foreach (QString s, m_strList)
{
if (!first)
{
t << " \\\n";
t << " ";
}
first=false;
writeStringValue(t,codec,s,convert);
}
}
bool InputStrList::isDefault()
{
if (m_strList==m_default) return true;
auto it1 = m_strList.begin();
auto it2 = m_default.begin();
while (it1!=m_strList.end() && (*it1).isEmpty())
{
++it1;
}
while (it2!=m_default.end() && (*it2).isEmpty())
{
++it2;
}
// both lists are empty
if (it1==m_strList.end() && it2==m_default.end()) return true;
// one list is empty but the other is not
if (it1==m_strList.end()) return false;
if (it2==m_default.end()) return false;
it1 = m_strList.begin();
it2 = m_default.begin();
while (it1!=m_strList.end() && it2!=m_default.end())
{
// skip over empty values
while (it1!=m_strList.end() && (*it1).isEmpty())
{
++it1;
}
while (it2!=m_default.end() && (*it2).isEmpty())
{
++it2;
}
if ((it1!=m_strList.end()) && (it2!=m_default.end()))
{
if ((*it1).trimmed()!= (*it2).trimmed()) // difference so not the default
{
return false;
}
++it1;
++it2;
}
else if ((it1!=m_strList.end()) || (it2!=m_default.end()))
{
// one list empty so cannot be the default
return false;
}
}
return true;
}
bool InputStrList::isEmpty()
{
foreach (QString s, m_strList)
{
if (!s.isEmpty()) return false;
}
return true;
}

View File

@ -0,0 +1,92 @@
/******************************************************************************
*
* Copyright (C) 1997-2019 by Dimitri van Heesch.
*
* Permission to use, copy, modify, and distribute this software and its
* documentation under the terms of the GNU General Public License is hereby
* granted. No representations are made about the suitability of this software
* for any purpose. It is provided "as is" without express or implied warranty.
* See the GNU General Public License for more details.
*
*/
#ifndef INPUTSTRLIST_H
#define INPUTSTRLIST_H
#include "input.h"
#include <QObject>
#include <QStringList>
class QLabel;
class QLineEdit;
class QPushButton;
class QListWidget;
class QGridLayout;
class QAction;
class InputStrList : public QObject, public Input
{
Q_OBJECT
public:
enum ListMode { ListString = 0,
ListFile = 1,
ListDir = 2,
ListFileDir = ListFile | ListDir
};
InputStrList( QGridLayout *layout,int &row,
const QString &id, const QStringList &sl,
ListMode v, const QString &docs);
void setValue(const QStringList &sl);
QVariant &value();
void update();
Kind kind() const { return StrList; }
QString docs() const { return m_docs; }
QString id() const { return m_id; }
QString templateDocs() const { return m_tdocs; }
void addDependency(Input *) { Q_ASSERT(false); }
void setEnabled(bool);
void updateDependencies() {}
bool isDefault();
void writeValue(QTextStream &t,TextCodecAdapter *codec,bool convert);
void setTemplateDocs(const QString &docs) { m_tdocs = docs; }
bool isEmpty();
public slots:
void reset();
signals:
void changed();
void showHelp(Input *);
private slots:
void addString();
void delString();
void updateString();
void selectText(const QString &s);
void browseFiles();
void browseDir();
void help();
private:
void updateDefault();
QLabel *m_lab;
QLineEdit *m_le;
QAction *m_add;
QAction *m_del;
QAction *m_upd;
QAction *m_brFile;
QAction *m_brDir;
QListWidget *m_lb;
QStringList m_default;
QStringList m_strList;
QVariant m_value;
QString m_docs;
QString m_id;
QString m_tdocs;
};
#endif

1431
addon/doxywizard/wizard.cpp Normal file

File diff suppressed because it is too large Load Diff

251
addon/doxywizard/wizard.h Normal file
View File

@ -0,0 +1,251 @@
/******************************************************************************
*
* Copyright (C) 1997-2019 by Dimitri van Heesch.
*
* Permission to use, copy, modify, and distribute this software and its
* documentation under the terms of the GNU General Public License is hereby
* granted. No representations are made about the suitability of this software
* for any purpose. It is provided "as is" without express or implied warranty.
* See the GNU General Public License for more details.
*
*/
#ifndef WIZARD_H
#define WIZARD_H
#include <QSplitter>
#include <QHash>
#include <QDialog>
class Input;
class QTreeWidget;
class QTreeWidgetItem;
class QStackedWidget;
class QCheckBox;
class QLineEdit;
class QPushButton;
class QRadioButton;
class QGroupBox;
class QButtonGroup;
class Wizard;
class QImage;
class QLabel;
class TuneColorDialog : public QDialog
{
Q_OBJECT
public:
TuneColorDialog(int hue,int sat,int gamma,QWidget *parent=nullptr);
int getHue() const;
int getSaturation() const;
int getGamma() const;
private slots:
void updateImage(int hue,int sat,int val);
private:
QImage *m_image = nullptr;
QLabel *m_imageLab = nullptr;
int m_hue = 0;
int m_sat = 0;
int m_gam = 0;
};
class ColorPicker : public QWidget
{
Q_OBJECT
public:
enum Mode { Hue, Saturation, Gamma };
ColorPicker(Mode m);
~ColorPicker();
public slots:
void setCol(int h, int s, int g);
//void setCol(int h, int s);
signals:
void newHsv(int h, int s, int g);
protected:
void paintEvent(QPaintEvent*);
void mouseMoveEvent(QMouseEvent *);
void mousePressEvent(QMouseEvent *);
private:
enum { foff = 3, coff = 4 }; //frame and contents offset
int y2hue(int y);
int y2sat(int y);
int y2gam(int y);
int hue2y(int hue);
int sat2y(int sat);
int gam2y(int gamma);
void setHue(int v);
void setSat(int v);
void setGam(int v);
QPixmap *m_pix = nullptr;
Mode m_mode = Hue;
int m_gam = 0;
int m_hue = 0;
int m_sat = 0;
};
class Step1 : public QWidget
{
Q_OBJECT
public:
Step1(Wizard *parent,const QHash<QString,Input*> &modelData);
void init();
private slots:
void selectSourceDir();
void selectDestinationDir();
void selectProjectIcon();
void setProjectName(const QString &name);
void setProjectBrief(const QString &desc);
void setProjectNumber(const QString &num);
void setSourceDir(const QString &dir);
void setDestinationDir(const QString &dir);
void setRecursiveScan(int);
private:
QLineEdit *m_projName = nullptr;
QLineEdit *m_projBrief = nullptr;
QLineEdit *m_projNumber = nullptr;
QLineEdit *m_sourceDir = nullptr;
QLineEdit *m_destDir = nullptr;
QLabel *m_projIconLab = nullptr;
QCheckBox *m_recursive = nullptr;
QPushButton *m_srcSelectDir = nullptr;
QPushButton *m_dstSelectDir = nullptr;
Wizard *m_wizard = nullptr;
const QHash<QString,Input *> &m_modelData;
};
class Step2 : public QWidget
{
Q_OBJECT
public:
Step2(Wizard *parent,const QHash<QString,Input*> &modelData);
void init();
private slots:
void optimizeFor(int choice);
void extractMode(int choice);
void changeCrossRefState(int choice);
private:
QGroupBox *m_extractMode = nullptr;
QGroupBox *m_optimizeLang = nullptr;
QButtonGroup *m_extractModeGroup = nullptr;
QButtonGroup *m_optimizeLangGroup = nullptr;
QCheckBox *m_crossRef = nullptr;
Wizard *m_wizard = nullptr;
const QHash<QString,Input *> &m_modelData;
};
class Step3 : public QWidget
{
Q_OBJECT
public:
Step3(Wizard *parent,const QHash<QString,Input*> &modelData);
void init();
private slots:
void setHtmlEnabled(bool);
void setLatexEnabled(bool);
void setManEnabled(int);
void setRtfEnabled(int);
void setXmlEnabled(int);
void setDocbookEnabled(int);
void setSearchEnabled(int);
void setHtmlOptions(int);
void setLatexOptions(int);
void tuneColorDialog();
private:
QGroupBox *m_texOptions = nullptr;
QButtonGroup *m_texOptionsGroup = nullptr;
QGroupBox *m_htmlOptions = nullptr;
QButtonGroup *m_htmlOptionsGroup = nullptr;
QCheckBox *m_htmlEnabled = nullptr;
QCheckBox *m_latexEnabled = nullptr;
QCheckBox *m_manEnabled = nullptr;
QCheckBox *m_rtfEnabled = nullptr;
QCheckBox *m_xmlEnabled = nullptr;
QCheckBox *m_docbookEnabled = nullptr;
QCheckBox *m_searchEnabled = nullptr;
QPushButton *m_tuneColor = nullptr;
Wizard *m_wizard = nullptr;
const QHash<QString,Input *> &m_modelData;
};
class Step4 : public QWidget
{
Q_OBJECT
public:
Step4(Wizard *parent,const QHash<QString,Input*> &modelData);
void init();
private slots:
void diagramModeChanged(int);
void setClassGraphEnabled(int state);
void setCollaborationGraphEnabled(int state);
void setGraphicalHierarchyEnabled(int state);
void setIncludeGraphEnabled(int state);
void setIncludedByGraphEnabled(int state);
void setCallGraphEnabled(int state);
void setCallerGraphEnabled(int state);
private:
QGroupBox *m_diagramMode = nullptr;
QButtonGroup *m_diagramModeGroup = nullptr;
QGroupBox *m_dotGroup = nullptr;
QCheckBox *m_dotClass = nullptr;
QCheckBox *m_dotCollaboration = nullptr;
QCheckBox *m_dotInclude = nullptr;
QCheckBox *m_dotIncludedBy = nullptr;
QCheckBox *m_dotInheritance = nullptr;
QCheckBox *m_dotCall = nullptr;
QCheckBox *m_dotCaller = nullptr;
Wizard *m_wizard = nullptr;
const QHash<QString,Input *> &m_modelData;
};
class Wizard : public QSplitter
{
Q_OBJECT
public:
Wizard(const QHash<QString,Input*> &modelData, QWidget *parent=nullptr);
~Wizard();
public slots:
void refresh();
private slots:
void activateTopic(QTreeWidgetItem *item,QTreeWidgetItem *);
void nextTopic();
void prevTopic();
signals:
void done();
private:
const QHash<QString,Input *> &m_modelData;
QTreeWidget *m_treeWidget = nullptr;
QStackedWidget *m_topicStack = nullptr;
Step1 *m_step1 = nullptr;
Step2 *m_step2 = nullptr;
Step3 *m_step3 = nullptr;
Step4 *m_step4 = nullptr;
QPushButton *m_next = nullptr;
QPushButton *m_prev = nullptr;
};
#endif

19
cmake/ApplyEditbin.cmake Normal file
View File

@ -0,0 +1,19 @@
# helper script for Windows to run editbin.exe on a generated executable
function(apply_editbin target_name target_type)
if (WIN32)
find_program(EDITBIN editbin)
if(EDITBIN)
set(EDITBIN_FLAGS /nologo /OSVERSION:5.1)
if (${target_type} STREQUAL "console")
set(EDITBIN_FLAGS ${EDITBIN_FLAGS} /SUBSYSTEM:CONSOLE,6.00)
elseif (${target_type} STREQUAL "windows")
set(EDITBIN_FLAGS ${EDITBIN_FLAGS} /SUBSYSTEM:WINDOWS,6.00)
endif()
add_custom_command(
TARGET ${target_name}
POST_BUILD
COMMAND "${EDITBIN}" ${EDITBIN_FLAGS} "$<TARGET_FILE:${target_name}>"
VERBATIM)
endif()
endif()
endfunction()

View File

@ -0,0 +1,127 @@
# adapted from
# https://github.com/lefticus/cppbestpractices/blob/master/02-Use_the_Tools_Available.md
function(set_project_warnings project_name)
set(MSVC_WARNINGS
#/W4 # Baseline reasonable warnings
/w14242 # 'identfier': conversion from 'type1' to 'type1', possible loss
# of data
/w14254 # 'operator': conversion from 'type1:field_bits' to
# 'type2:field_bits', possible loss of data
/w14263 # 'function': member function does not override any base class
# virtual member function
/w14265 # 'classname': class has virtual functions, but destructor is not
# virtual instances of this class may not be destructed correctly
/w14287 # 'operator': unsigned/negative constant mismatch
/we4289 # nonstandard extension used: 'variable': loop control variable
# declared in the for-loop is used outside the for-loop scope
/w14296 # 'operator': expression is always 'boolean_value'
/w14311 # 'variable': pointer truncation from 'type1' to 'type2'
/w14456 # declaration of 'name' hides previous local declaration
/w14457 # declaration of 'name' hides function parameter
/w14458 # declaration of 'name' hides class member
/w14459 # declaration of 'name' hides global declaration
/w14545 # expression before comma evaluates to a function which is missing
# an argument list
/w14546 # function call before comma missing argument list
/w14547 # 'operator': operator before comma has no effect; expected
# operator with side-effect
/w14549 # 'operator': operator before comma has no effect; did you intend
# 'operator'?
/w14555 # expression has no effect; expected expression with side- effect
/w14619 # pragma warning: there is no warning number 'number'
/w14640 # Enable warning on thread un-safe static member initialization
/w14826 # Conversion from 'type1' to 'type_2' is sign-extended. This may
# cause unexpected runtime behavior.
/w14905 # wide string literal cast to 'LPSTR'
/w14906 # string literal cast to 'LPWSTR'
/w14928 # illegal copy-initialization; more than one user-defined
# conversion has been implicitly applied
)
set(CLANG_WARNINGS
-Wall
-Wextra # reasonable and standard
-Wshadow # warn the user if a variable declaration shadows one from a
# parent context
$<$<COMPILE_LANGUAGE:CXX>:-Wnon-virtual-dtor>
# warn the user if a class with virtual functions has a
# non-virtual destructor. This helps catch hard to
# track down memory errors
# -Wold-style-cast # warn for c-style casts
-Wcast-align # warn for potential performance problem casts
-Wunused # warn on anything being unused
$<$<COMPILE_LANGUAGE:CXX>:-Woverloaded-virtual>
# warn if you overload (not override) a virtual function
-Wpedantic # warn if non-standard C++ is used
-Wconversion # warn on type conversions that may lose data
-Wnull-dereference # warn if a null dereference is detected
-Wdouble-promotion # warn if float is implicit promoted to double
-Wformat=2 # warn on security issues around functions that format output
# (ie printf)
-Wpedantic # warn if non-standard C++ is used
# turn off warning caused by generated code (flex)
-Wno-unused-parameter
-Wno-implicit-int-conversion
-Wno-sign-conversion
-Wno-format-nonliteral
-Wno-shorten-64-to-32
# allow comma removal for empty __VA_ARGS__ in ,##__VA_ARGS__
-Wno-gnu-zero-variadic-macro-arguments
# enable to turn warnings into errors
#-Werror
)
set(GCC_WARNINGS
-Wall
-Wextra # reasonable and standard
-Wshadow # warn the user if a variable declaration shadows one from a
# parent context
$<$<COMPILE_LANGUAGE:CXX>:-Wnon-virtual-dtor>
# warn the user if a class with virtual functions has a
# non-virtual destructor. This helps catch hard to
# track down memory errors
# -Wold-style-cast # warn for c-style casts
-Wcast-align # warn for potential performance problem casts
-Wunused # warn on anything being unused
$<$<COMPILE_LANGUAGE:CXX>:-Woverloaded-virtual>
# warn if you overload (not override) a virtual function
-Wpedantic # warn if non-standard C++ is used
#-Wconversion # warn on type conversions that may lose data
#-Wnull-dereference # warn if a null dereference is detected
-Wdouble-promotion # warn if float is implicit promoted to double
-Wformat=2 # warn on security issues around functions that format output
# (ie printf)
# turn off warning caused by generated code (flex)
-Wno-unused-parameter
-Wno-sign-conversion
-Wno-format-nonliteral
# enable to turn warnings into errors
#-Werror
)
if(MSVC)
set(PROJECT_WARNINGS ${MSVC_WARNINGS})
elseif(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") # e.g. Clang or AppleClang
set(PROJECT_WARNINGS ${CLANG_WARNINGS})
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER "7.0.0")
set(GCC_EXTRA_WARNINGS
-Wno-implicit-fallthrough
)
else()
set(GCC_EXTRA_WARNINGS
)
endif()
set(PROJECT_WARNINGS ${GCC_WARNINGS} ${GCC_EXTRA_WARNINGS})
endif()
target_compile_options(${project_name} PRIVATE ${PROJECT_WARNINGS})
endfunction()

39
cmake/Coverage.cmake Normal file
View File

@ -0,0 +1,39 @@
if(enable_coverage)
FIND_PROGRAM( LCOV_PATH lcov )
FIND_PROGRAM( GENHTML_PATH genhtml )
set(COVERAGE_COMPILER_FLAGS -g --coverage -O0
CACHE INTERNAL "")
set(COVERAGE_LINKER_FLAGS --coverage
CACHE INTERNAL "")
add_custom_target(coverage-clean
COMMAND ${LCOV_PATH} --rc lcov_branch_coverage=1 --directory . --zerocounters
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
)
add_custom_target(coverage
COMMAND ${LCOV_PATH} --rc lcov_branch_coverage=1 --directory . --capture --output-file cov.info
COMMAND ${LCOV_PATH} --rc lcov_branch_coverage=1 --remove cov.info '*/c++/*' '*/_ctype.h' '*/generated_src/*' --output-file cov.info.cleaned
COMMAND ${CMAKE_COMMAND} -Dsearch=${PROJECT_BINARY_DIR}
-Dreplace=${PROJECT_SOURCE_DIR}
-Dsrc=cov.info.cleaned
-Ddst=cov.info.final
-P ${PROJECT_SOURCE_DIR}/cmake/SearchReplace.cmake
COMMAND ${GENHTML_PATH} --rc genhtml_branch_coverage=1
--function-coverage --branch-coverage
--title "Doxygen Coverage Report" --num-spaces 2
--legend --prefix ${PROJECT_SOURCE_DIR} --demangle-cpp
--output-directory cov_output cov.info.final
COMMAND ${CMAKE_COMMAND} -E remove cov.info cov.info.cleaned cov.info.final
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
)
add_custom_command(TARGET coverage POST_BUILD
COMMAND ;
COMMENT "Open ./cov_output/index.html in your browser to view the coverage report"
)
endif()
function(set_project_coverage project_name)
if(enable_coverage)
target_compile_options(${project_name} PRIVATE ${COVERAGE_COMPILER_FLAGS})
endif()
endfunction()

25
cmake/FindJavacc.cmake Normal file
View File

@ -0,0 +1,25 @@
find_program(JAVACC_EXECUTABLE NAMES javacc javaCC Javacc JavaCC javacc.bat DOC "path to the javacc executable")
mark_as_advanced(JAVACC_EXECUTABLE)
set(JAVACC_FOUND 0)
if(JAVACC_EXECUTABLE)
execute_process(
COMMAND "${JAVACC_EXECUTABLE}" -version
OUTPUT_VARIABLE JAVACC_TEMP_VERSION
)
string(REGEX MATCH ".* ([0-9]+(\\.[0-9]+)+) .*" JAVACC_TEMP_VERSION2_UNUSED "${JAVACC_TEMP_VERSION}")
if(CMAKE_MATCH_1)
set(JAVACC_FOUND 1)
set(JAVACC_VERSION ${CMAKE_MATCH_1})
else()
string(REGEX MATCH "([0-9]+(\\.[0-9]+)+)" JAVACC_TEMP_VERSION3_UNUSED "${JAVACC_TEMP_VERSION}")
if(CMAKE_MATCH_1)
set(JAVACC_FOUND 1)
set(JAVACC_VERSION ${CMAKE_MATCH_1})
endif()
endif()
endif()
if(JAVACC_FOUND)
message(STATUS "The javacc executable: ${JAVACC_EXECUTABLE} (found version \"${JAVACC_VERSION}\")")
else()
message(STATUS "The javacc executable not found, using existing files")
endif()

View File

@ -0,0 +1,19 @@
find_program(GENERATEDS_EXECUTABLE NAMES generateDS generateDS.py DOC "path to the generateDS executable")
mark_as_advanced(GENERATEDS_EXECUTABLE)
set(GENERATEDS_FOUND 0)
if(GENERATEDS_EXECUTABLE)
execute_process(
COMMAND "${GENERATEDS_EXECUTABLE}" --version
OUTPUT_VARIABLE GENERATEDS_TEMP_VERSION
)
string(REGEX MATCH ".* ([0-9]+(\\.[0-9]+)+)" GENERATEDS_TEMP_VERSION_UNUSED "${GENERATEDS_TEMP_VERSION}")
if(CMAKE_MATCH_1)
set(GENERATEDS_FOUND 1)
set(GENERATEDS_VERSION ${CMAKE_MATCH_1})
endif()
endif()
if(GENERATEDS_FOUND)
message(STATUS "The generateDS executable: ${GENERATEDS_EXECUTABLE} (found version \"${GENERATEDS_VERSION}\")")
else()
message(STATUS "The generateDS executable not found, using existing files")
endif()

42
cmake/Findxapian.cmake Normal file
View File

@ -0,0 +1,42 @@
# Find xapian search engine library
#
# XAPIAN_FOUND - system has Xapian
# XAPIAN_INCLUDE_DIR - the Xapian include directory
# XAPIAN_LIBRARIES - the libraries needed to use Xapian
#
# Copyright © 2010 Harald Sitter <apachelogger@ubuntu.com>
#
# Redistribution and use is allowed according to the terms of the BSD license.
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
if(XAPIAN_INCLUDE_DIR AND XAPIAN_LIBRARIES)
# Already in cache, be silent
set(xapian_FIND_QUIETLY TRUE)
endif(XAPIAN_INCLUDE_DIR AND XAPIAN_LIBRARIES)
FIND_PATH(XAPIAN_INCLUDE_DIR xapian/version.h)
FIND_LIBRARY(XAPIAN_LIBRARIES NAMES xapian)
IF(XAPIAN_INCLUDE_DIR AND XAPIAN_LIBRARIES)
SET(XAPIAN_FOUND TRUE)
ELSE(XAPIAN_INCLUDE_DIR AND XAPIAN_LIBRARIES)
SET(XAPIAN_FOUND FALSE)
ENDIF(XAPIAN_INCLUDE_DIR AND XAPIAN_LIBRARIES)
IF(XAPIAN_FOUND)
IF(NOT xapian_FIND_QUIETLY)
MESSAGE(STATUS "Found xapian: ${XAPIAN_LIBRARIES}")
ENDIF(NOT xapian_FIND_QUIETLY)
ELSE(XAPIAN_FOUND)
IF(xapian_FIND_REQUIRED)
MESSAGE(FATAL_ERROR "Could not find xapian")
ENDIF(xapian_FIND_REQUIRED)
IF(NOT xapian_FIND_QUIETLY)
MESSAGE(STATUS "Could not find xapian")
ENDIF(NOT xapian_FIND_QUIETLY)
ENDIF(XAPIAN_FOUND)
# show the XAPIAN_INCLUDE_DIR and XAPIAN_LIBRARIES variables only in the advanced view
MARK_AS_ADVANCED(XAPIAN_INCLUDE_DIR XAPIAN_LIBRARIES)

21
cmake/QueryCodePage.py Normal file
View File

@ -0,0 +1,21 @@
import platform
def is_windows():
return platform.system().lower() == "windows"
if is_windows():
from winreg import OpenKey, QueryValueEx, HKEY_LOCAL_MACHINE, KEY_READ
if __name__ == '__main__':
if is_windows():
root = HKEY_LOCAL_MACHINE
subkey = R'SYSTEM\CurrentControlSet\Control\Nls\CodePage'
key = OpenKey(root, subkey, 0, KEY_READ)
name = 'ACP'
try:
codepage, _ = QueryValueEx(key, name)
print(codepage)
except WindowsError:
print('Failed to get code page')

View File

@ -0,0 +1,59 @@
# The MIT License (MIT)
#
# Copyright (c)
# 2013 Matthew Arsenault
# 2015-2016 RWTH Aachen University, Federal Republic of Germany
#
# 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.
option(SANITIZE_ADDRESS "Enable AddressSanitizer for sanitized targets." Off)
set(FLAG_CANDIDATES
# Clang 3.2+ use this version. The no-omit-frame-pointer option is optional.
"-g -fsanitize=address -fno-omit-frame-pointer"
"-g -fsanitize=address"
# Older deprecated flag for ASan
"-g -faddress-sanitizer"
)
if (SANITIZE_ADDRESS AND (SANITIZE_THREAD OR SANITIZE_MEMORY))
message(FATAL_ERROR "AddressSanitizer is not compatible with "
"ThreadSanitizer or MemorySanitizer.")
endif ()
include(sanitize-helpers)
if (SANITIZE_ADDRESS)
sanitizer_check_compiler_flags("${FLAG_CANDIDATES}" "AddressSanitizer"
"ASan")
find_program(ASan_WRAPPER "asan-wrapper" PATHS ${CMAKE_MODULE_PATH})
mark_as_advanced(ASan_WRAPPER)
endif ()
function (add_sanitize_address TARGET)
if (NOT SANITIZE_ADDRESS)
return()
endif ()
sanitizer_add_flags(${TARGET} "AddressSanitizer" "ASan")
endfunction ()

View File

@ -0,0 +1,57 @@
# The MIT License (MIT)
#
# Copyright (c)
# 2013 Matthew Arsenault
# 2015-2016 RWTH Aachen University, Federal Republic of Germany
#
# 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.
option(SANITIZE_MEMORY "Enable MemorySanitizer for sanitized targets." Off)
set(FLAG_CANDIDATES
"-g -fsanitize=memory"
)
include(sanitize-helpers)
if (SANITIZE_MEMORY)
if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
message(WARNING "MemorySanitizer disabled for target ${TARGET} because "
"MemorySanitizer is supported for Linux systems only.")
set(SANITIZE_MEMORY Off CACHE BOOL
"Enable MemorySanitizer for sanitized targets." FORCE)
elseif (NOT ${CMAKE_SIZEOF_VOID_P} EQUAL 8)
message(WARNING "MemorySanitizer disabled for target ${TARGET} because "
"MemorySanitizer is supported for 64bit systems only.")
set(SANITIZE_MEMORY Off CACHE BOOL
"Enable MemorySanitizer for sanitized targets." FORCE)
else ()
sanitizer_check_compiler_flags("${FLAG_CANDIDATES}" "MemorySanitizer"
"MSan")
endif ()
endif ()
function (add_sanitize_memory TARGET)
if (NOT SANITIZE_MEMORY)
return()
endif ()
sanitizer_add_flags(${TARGET} "MemorySanitizer" "MSan")
endfunction ()

View File

@ -0,0 +1,94 @@
# The MIT License (MIT)
#
# Copyright (c)
# 2013 Matthew Arsenault
# 2015-2016 RWTH Aachen University, Federal Republic of Germany
#
# 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.
# If any of the used compiler is a GNU compiler, add a second option to static
# link against the sanitizers.
option(SANITIZE_LINK_STATIC "Try to link static against sanitizers." Off)
set(FIND_QUIETLY_FLAG "")
if (DEFINED Sanitizers_FIND_QUIETLY)
set(FIND_QUIETLY_FLAG "QUIET")
endif ()
find_package(ASan ${FIND_QUIETLY_FLAG})
find_package(TSan ${FIND_QUIETLY_FLAG})
find_package(MSan ${FIND_QUIETLY_FLAG})
find_package(UBSan ${FIND_QUIETLY_FLAG})
function(sanitizer_add_blacklist_file FILE)
if(NOT IS_ABSOLUTE ${FILE})
set(FILE "${CMAKE_CURRENT_SOURCE_DIR}/${FILE}")
endif()
get_filename_component(FILE "${FILE}" REALPATH)
sanitizer_check_compiler_flags("-fsanitize-blacklist=${FILE}"
"SanitizerBlacklist" "SanBlist")
endfunction()
function(add_sanitizers ...)
# If no sanitizer is enabled, return immediately.
if (NOT (SANITIZE_ADDRESS OR SANITIZE_MEMORY OR SANITIZE_THREAD OR
SANITIZE_UNDEFINED))
return()
endif ()
foreach (TARGET ${ARGV})
# Check if this target will be compiled by exactly one compiler. Other-
# wise sanitizers can't be used and a warning should be printed once.
get_target_property(TARGET_TYPE ${TARGET} TYPE)
if (TARGET_TYPE STREQUAL "INTERFACE_LIBRARY")
message(WARNING "Can't use any sanitizers for target ${TARGET}, "
"because it is an interface library and cannot be "
"compiled directly.")
return()
endif ()
sanitizer_target_compilers(${TARGET} TARGET_COMPILER)
list(LENGTH TARGET_COMPILER NUM_COMPILERS)
if (NUM_COMPILERS GREATER 1)
message(WARNING "Can't use any sanitizers for target ${TARGET}, "
"because it will be compiled by incompatible compilers. "
"Target will be compiled without sanitizers.")
return()
# If the target is compiled by no or no known compiler, give a warning.
elseif (NUM_COMPILERS EQUAL 0)
message(WARNING "Sanitizers for target ${TARGET} may not be"
" usable, because it uses no or an unknown compiler. "
"This is a false warning for targets using only "
"object lib(s) as input.")
endif ()
# Add sanitizers for target.
add_sanitize_address(${TARGET})
add_sanitize_thread(${TARGET})
add_sanitize_memory(${TARGET})
add_sanitize_undefined(${TARGET})
endforeach ()
endfunction(add_sanitizers)

View File

@ -0,0 +1,65 @@
# The MIT License (MIT)
#
# Copyright (c)
# 2013 Matthew Arsenault
# 2015-2016 RWTH Aachen University, Federal Republic of Germany
#
# 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.
option(SANITIZE_THREAD "Enable ThreadSanitizer for sanitized targets." Off)
set(FLAG_CANDIDATES
"-g -fsanitize=thread"
)
# ThreadSanitizer is not compatible with MemorySanitizer.
if (SANITIZE_THREAD AND SANITIZE_MEMORY)
message(FATAL_ERROR "ThreadSanitizer is not compatible with "
"MemorySanitizer.")
endif ()
include(sanitize-helpers)
if (SANITIZE_THREAD)
if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" AND
NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
message(WARNING "ThreadSanitizer disabled for target ${TARGET} because "
"ThreadSanitizer is supported for Linux systems and macOS only.")
set(SANITIZE_THREAD Off CACHE BOOL
"Enable ThreadSanitizer for sanitized targets." FORCE)
elseif (NOT ${CMAKE_SIZEOF_VOID_P} EQUAL 8)
message(WARNING "ThreadSanitizer disabled for target ${TARGET} because "
"ThreadSanitizer is supported for 64bit systems only.")
set(SANITIZE_THREAD Off CACHE BOOL
"Enable ThreadSanitizer for sanitized targets." FORCE)
else ()
sanitizer_check_compiler_flags("${FLAG_CANDIDATES}" "ThreadSanitizer"
"TSan")
endif ()
endif ()
function (add_sanitize_thread TARGET)
if (NOT SANITIZE_THREAD)
return()
endif ()
sanitizer_add_flags(${TARGET} "ThreadSanitizer" "TSan")
endfunction ()

View File

@ -0,0 +1,46 @@
# The MIT License (MIT)
#
# Copyright (c)
# 2013 Matthew Arsenault
# 2015-2016 RWTH Aachen University, Federal Republic of Germany
#
# 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.
option(SANITIZE_UNDEFINED
"Enable UndefinedBehaviorSanitizer for sanitized targets." Off)
set(FLAG_CANDIDATES
"-g -fsanitize=undefined"
)
include(sanitize-helpers)
if (SANITIZE_UNDEFINED)
sanitizer_check_compiler_flags("${FLAG_CANDIDATES}"
"UndefinedBehaviorSanitizer" "UBSan")
endif ()
function (add_sanitize_undefined TARGET)
if (NOT SANITIZE_UNDEFINED)
return()
endif ()
sanitizer_add_flags(${TARGET} "UndefinedBehaviorSanitizer" "UBSan")
endfunction ()

55
cmake/Sanitizers/asan-wrapper Executable file
View File

@ -0,0 +1,55 @@
#!/bin/sh
# The MIT License (MIT)
#
# Copyright (c)
# 2013 Matthew Arsenault
# 2015-2016 RWTH Aachen University, Federal Republic of Germany
#
# 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.
# This script is a wrapper for AddressSanitizer. In some special cases you need
# to preload AddressSanitizer to avoid error messages - e.g. if you're
# preloading another library to your application. At the moment this script will
# only do something, if we're running on a Linux platform. OSX might not be
# affected.
# Exit immediately, if platform is not Linux.
if [ "$(uname)" != "Linux" ]
then
exec $@
fi
# Get the used libasan of the application ($1). If a libasan was found, it will
# be prepended to LD_PRELOAD.
libasan=$(ldd $1 | grep libasan | sed "s/^[[:space:]]//" | cut -d' ' -f1)
if [ -n "$libasan" ]
then
if [ -n "$LD_PRELOAD" ]
then
export LD_PRELOAD="$libasan:$LD_PRELOAD"
else
export LD_PRELOAD="$libasan"
fi
fi
# Execute the application.
exec $@

View File

@ -0,0 +1,177 @@
# The MIT License (MIT)
#
# Copyright (c)
# 2013 Matthew Arsenault
# 2015-2016 RWTH Aachen University, Federal Republic of Germany
#
# 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.
# Helper function to get the language of a source file.
function (sanitizer_lang_of_source FILE RETURN_VAR)
get_filename_component(LONGEST_EXT "${FILE}" EXT)
# If extension is empty return. This can happen for extensionless headers
if("${LONGEST_EXT}" STREQUAL "")
set(${RETURN_VAR} "" PARENT_SCOPE)
return()
endif()
# Get shortest extension as some files can have dot in their names
string(REGEX REPLACE "^.*(\\.[^.]+)$" "\\1" FILE_EXT ${LONGEST_EXT})
string(TOLOWER "${FILE_EXT}" FILE_EXT)
string(SUBSTRING "${FILE_EXT}" 1 -1 FILE_EXT)
get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES)
foreach (LANG ${ENABLED_LANGUAGES})
list(FIND CMAKE_${LANG}_SOURCE_FILE_EXTENSIONS "${FILE_EXT}" TEMP)
if (NOT ${TEMP} EQUAL -1)
set(${RETURN_VAR} "${LANG}" PARENT_SCOPE)
return()
endif ()
endforeach()
set(${RETURN_VAR} "" PARENT_SCOPE)
endfunction ()
# Helper function to get compilers used by a target.
function (sanitizer_target_compilers TARGET RETURN_VAR)
# Check if all sources for target use the same compiler. If a target uses
# e.g. C and Fortran mixed and uses different compilers (e.g. clang and
# gfortran) this can trigger huge problems, because different compilers may
# use different implementations for sanitizers.
set(BUFFER "")
get_target_property(TSOURCES ${TARGET} SOURCES)
foreach (FILE ${TSOURCES})
# If expression was found, FILE is a generator-expression for an object
# library. Object libraries will be ignored.
string(REGEX MATCH "TARGET_OBJECTS:([^ >]+)" _file ${FILE})
if ("${_file}" STREQUAL "")
sanitizer_lang_of_source(${FILE} LANG)
if (LANG)
list(APPEND BUFFER ${CMAKE_${LANG}_COMPILER_ID})
endif ()
endif ()
endforeach ()
list(REMOVE_DUPLICATES BUFFER)
set(${RETURN_VAR} "${BUFFER}" PARENT_SCOPE)
endfunction ()
# Helper function to check compiler flags for language compiler.
function (sanitizer_check_compiler_flag FLAG LANG VARIABLE)
if (${LANG} STREQUAL "C")
include(CheckCCompilerFlag)
check_c_compiler_flag("${FLAG}" ${VARIABLE})
elseif (${LANG} STREQUAL "CXX")
include(CheckCXXCompilerFlag)
check_cxx_compiler_flag("${FLAG}" ${VARIABLE})
elseif (${LANG} STREQUAL "Fortran")
# CheckFortranCompilerFlag was introduced in CMake 3.x. To be compatible
# with older Cmake versions, we will check if this module is present
# before we use it. Otherwise we will define Fortran coverage support as
# not available.
include(CheckFortranCompilerFlag OPTIONAL RESULT_VARIABLE INCLUDED)
if (INCLUDED)
check_fortran_compiler_flag("${FLAG}" ${VARIABLE})
elseif (NOT CMAKE_REQUIRED_QUIET)
message(STATUS "Performing Test ${VARIABLE}")
message(STATUS "Performing Test ${VARIABLE}"
" - Failed (Check not supported)")
endif ()
endif()
endfunction ()
# Helper function to test compiler flags.
function (sanitizer_check_compiler_flags FLAG_CANDIDATES NAME PREFIX)
set(CMAKE_REQUIRED_QUIET ${${PREFIX}_FIND_QUIETLY})
get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES)
foreach (LANG ${ENABLED_LANGUAGES})
# Sanitizer flags are not dependent on language, but the used compiler.
# So instead of searching flags foreach language, search flags foreach
# compiler used.
set(COMPILER ${CMAKE_${LANG}_COMPILER_ID})
if (NOT DEFINED ${PREFIX}_${COMPILER}_FLAGS)
foreach (FLAG ${FLAG_CANDIDATES})
if(NOT CMAKE_REQUIRED_QUIET)
message(STATUS "Try ${COMPILER} ${NAME} flag = [${FLAG}]")
endif()
set(CMAKE_REQUIRED_FLAGS "${FLAG}")
unset(${PREFIX}_FLAG_DETECTED CACHE)
sanitizer_check_compiler_flag("${FLAG}" ${LANG}
${PREFIX}_FLAG_DETECTED)
if (${PREFIX}_FLAG_DETECTED)
# If compiler is a GNU compiler, search for static flag, if
# SANITIZE_LINK_STATIC is enabled.
if (SANITIZE_LINK_STATIC AND (${COMPILER} STREQUAL "GNU"))
string(TOLOWER ${PREFIX} PREFIX_lower)
sanitizer_check_compiler_flag(
"-static-lib${PREFIX_lower}" ${LANG}
${PREFIX}_STATIC_FLAG_DETECTED)
if (${PREFIX}_STATIC_FLAG_DETECTED)
set(FLAG "-static-lib${PREFIX_lower} ${FLAG}")
endif ()
endif ()
set(${PREFIX}_${COMPILER}_FLAGS "${FLAG}" CACHE STRING
"${NAME} flags for ${COMPILER} compiler.")
mark_as_advanced(${PREFIX}_${COMPILER}_FLAGS)
break()
endif ()
endforeach ()
if (NOT ${PREFIX}_FLAG_DETECTED)
set(${PREFIX}_${COMPILER}_FLAGS "" CACHE STRING
"${NAME} flags for ${COMPILER} compiler.")
mark_as_advanced(${PREFIX}_${COMPILER}_FLAGS)
message(WARNING "${NAME} is not available for ${COMPILER} "
"compiler. Targets using this compiler will be "
"compiled without ${NAME}.")
endif ()
endif ()
endforeach ()
endfunction ()
# Helper to assign sanitizer flags for TARGET.
function (sanitizer_add_flags TARGET NAME PREFIX)
# Get list of compilers used by target and check, if sanitizer is available
# for this target. Other compiler checks like check for conflicting
# compilers will be done in add_sanitizers function.
sanitizer_target_compilers(${TARGET} TARGET_COMPILER)
list(LENGTH TARGET_COMPILER NUM_COMPILERS)
if ("${${PREFIX}_${TARGET_COMPILER}_FLAGS}" STREQUAL "")
return()
endif()
# Set compile- and link-flags for target.
set_property(TARGET ${TARGET} APPEND_STRING
PROPERTY COMPILE_FLAGS " ${${PREFIX}_${TARGET_COMPILER}_FLAGS}")
set_property(TARGET ${TARGET} APPEND_STRING
PROPERTY COMPILE_FLAGS " ${SanBlist_${TARGET_COMPILER}_FLAGS}")
set_property(TARGET ${TARGET} APPEND_STRING
PROPERTY LINK_FLAGS " ${${PREFIX}_${TARGET_COMPILER}_FLAGS}")
endfunction ()

View File

@ -0,0 +1,4 @@
message("Replacing ${search} by ${replace} in file ${src} and writing to ${dst}...")
file(READ ${src} file_contents)
string(REPLACE "${search}" "${replace}" file_contents ${file_contents})
file(WRITE ${dst} ${file_contents})

View File

@ -0,0 +1,18 @@
if(CMAKE_C_COMPILER_ID STREQUAL "MSVC" OR CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
include(FindPythonInterp)
execute_process(
COMMAND ${Python_EXECUTABLE} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/QueryCodePage.py"
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
RESULT_VARIABLE ReturnCode
OUTPUT_VARIABLE CodePage
)
message(STATUS "CodePage is ${CodePage}")
if("${CodePage}" STREQUAL "936")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /source-charset:utf-8 /execution-charset:gbk")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /source-charset:utf-8 /execution-charset:gbk")
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /utf-8")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /utf-8")
endif()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj") # /bigobj needed for language.cpp on 64bit
endif()

View File

@ -0,0 +1,96 @@
# doxygen_version.cmake
#
# This file defines the functions and targets needed to monitor
# doxygen VERSION file.
#
# The behavior of this script can be modified by defining any of these variables:
#
# PRE_CONFIGURE_DOXYGEN_VERSION_FILE (REQUIRED)
# -- The path to the file that'll be configured.
#
# POST_CONFIGURE_DOXYGEN_VERSION_FILE (REQUIRED)
# -- The path to the configured PRE_CONFIGURE_DOXYGEN_VERSION_FILE.
#
# DOXY_STATE_FILE (OPTIONAL)
# -- The path to the file used to store the doxygen version information.
#
# This file is based on git_watcher.cmake
# Short hand for converting paths to absolute.
macro(PATH_TO_ABSOLUTE var_name)
get_filename_component(${var_name} "${${var_name}}" ABSOLUTE)
endmacro()
# Check that a required variable is set.
macro(CHECK_REQUIRED_VARIABLE var_name)
if(NOT DEFINED ${var_name})
message(FATAL_ERROR "The \"${var_name}\" variable must be defined.")
endif()
PATH_TO_ABSOLUTE(${var_name})
endmacro()
# Check that an optional variable is set, or, set it to a default value.
macro(CHECK_OPTIONAL_VARIABLE var_name default_value)
if(NOT DEFINED ${var_name})
set(${var_name} ${default_value})
endif()
PATH_TO_ABSOLUTE(${var_name})
endmacro()
CHECK_REQUIRED_VARIABLE(PRE_CONFIGURE_DOXYGEN_VERSION_FILE)
CHECK_REQUIRED_VARIABLE(POST_CONFIGURE_DOXYGEN_VERSION_FILE)
CHECK_OPTIONAL_VARIABLE(DOXY_STATE_FILE "${PROJECT_SOURCE_DIR}/VERSION")
# Function: DoxygenStateChangedAction
# Description: this function is executed when the
# doxygen version file has changed.
function(DoxygenStateChangedAction _state_as_list)
# Set variables by index, then configure the file w/ these variables defined.
LIST(GET _state_as_list 0 DOXYGEN_VERSION)
configure_file("${PRE_CONFIGURE_DOXYGEN_VERSION_FILE}" "${POST_CONFIGURE_DOXYGEN_VERSION_FILE}" @ONLY)
endfunction()
# Function: SetupDoxyMonitoring
# Description: this function sets up custom commands that make the build system
# check the doxygen version file before every build. If it has
# changed, then a file is configured.
function(SetupDoxyMonitoring)
add_custom_target(check_doxygen_version
ALL
DEPENDS ${PRE_CONFIGURE_DOXYGEN_VERSION_FILE}
BYPRODUCTS ${POST_CONFIGURE_DOXYGEN_VERSION_FILE}
COMMENT "Checking the doxygen version for changes..."
COMMAND
${CMAKE_COMMAND}
-D_BUILD_TIME_CHECK_DOXY=TRUE
-DDOXY_STATE_FILE=${DOXY_STATE_FILE}
-DPRE_CONFIGURE_DOXYGEN_VERSION_FILE=${PRE_CONFIGURE_DOXYGEN_VERSION_FILE}
-DPOST_CONFIGURE_DOXYGEN_VERSION_FILE=${POST_CONFIGURE_DOXYGEN_VERSION_FILE}
-P "${CMAKE_CURRENT_LIST_FILE}")
endfunction()
# Function: Main
# Description: primary entry-point to the script. Functions are selected based
# on whether it's configure or build time.
function(Main)
file(STRINGS "${DOXY_STATE_FILE}" DOXYGEN_VERSION)
if(_BUILD_TIME_CHECK_DOXY)
# Check if the doxygen version file has changed.
# If so, run the change action.
if(${DOXY_STATE_FILE} IS_NEWER_THAN ${POST_CONFIGURE_DOXYGEN_VERSION_FILE})
DoxygenStateChangedAction("${DOXYGEN_VERSION}")
endif()
else()
# >> Executes at configure time.
SetupDoxyMonitoring()
DoxygenStateChangedAction("${DOXYGEN_VERSION}")
endif()
endfunction()
# And off we go...
Main()

235
cmake/git_watcher.cmake Normal file
View File

@ -0,0 +1,235 @@
# git_watcher.cmake
#
# License: MIT
# Source: https://raw.githubusercontent.com/andrew-hardin/cmake-git-version-tracking/master/git_watcher.cmake
# This file defines the functions and targets needed to monitor
# the state of a git repo. If the state changes (e.g. a commit is made),
# then a file gets reconfigured.
#
# The behavior of this script can be modified by defining any of these variables:
#
# PRE_CONFIGURE_GIT_VERSION_FILE (REQUIRED)
# -- The path to the file that'll be configured.
#
# POST_CONFIGURE_GIT_VERSION_FILE (REQUIRED)
# -- The path to the configured PRE_CONFIGURE_GIT_VERSION_FILE.
#
# GIT_STATE_FILE (OPTIONAL)
# -- The path to the file used to store the previous build's git state.
# Defaults to the current binary directory.
#
# GIT_WORKING_DIR (OPTIONAL)
# -- The directory from which git commands will be run.
# Defaults to the directory with the top level CMakeLists.txt.
#
# GIT_EXECUTABLE (OPTIONAL)
# -- The path to the git executable. It'll automatically be set if the
# user doesn't supply a path.
#
# Script design:
# - This script was designed similar to a Python application
# with a Main() function. I wanted to keep it compact to
# simplify "copy + paste" usage.
#
# - This script is made to operate in two CMake contexts:
# 1. Configure time context (when build files are created).
# 2. Build time context (called via CMake -P)
# If you see something odd (e.g. the NOT DEFINED clauses),
# consider that it can run in one of two contexts.
# Short hand for converting paths to absolute.
macro(PATH_TO_ABSOLUTE var_name)
get_filename_component(${var_name} "${${var_name}}" ABSOLUTE)
endmacro()
# Check that a required variable is set.
macro(CHECK_REQUIRED_VARIABLE var_name)
if(NOT DEFINED ${var_name})
message(FATAL_ERROR "The \"${var_name}\" variable must be defined.")
endif()
PATH_TO_ABSOLUTE(${var_name})
endmacro()
# Check that an optional variable is set, or, set it to a default value.
macro(CHECK_OPTIONAL_VARIABLE var_name default_value)
if(NOT DEFINED ${var_name})
set(${var_name} ${default_value})
endif()
PATH_TO_ABSOLUTE(${var_name})
endmacro()
CHECK_REQUIRED_VARIABLE(PRE_CONFIGURE_GIT_VERSION_FILE)
CHECK_REQUIRED_VARIABLE(POST_CONFIGURE_GIT_VERSION_FILE)
CHECK_REQUIRED_VARIABLE(GIT_CONFIG_DIR)
CHECK_OPTIONAL_VARIABLE(GIT_STATE_FILE "${GENERATED_SRC}/git_state")
#CHECK_REQUIRED_VARIABLE(GIT_STATE_FILE)
CHECK_OPTIONAL_VARIABLE(GIT_WORKING_DIR "${PROJECT_SOURCE_DIR}")
# Check the optional git variable.
# If it's not set, we'll try to find it using the CMake packaging system.
if(NOT DEFINED GIT_EXECUTABLE)
find_package(Git QUIET)
endif()
CHECK_REQUIRED_VARIABLE(GIT_EXECUTABLE)
# Function: GitStateChangedAction
# Description: this function is executed when the state of the git
# repo changes (e.g. a commit is made).
function(GitStateChangedAction _state_as_list)
# Set variables by index, then configure the file w/ these variables defined.
LIST(GET _state_as_list 0 GIT_RETRIEVED_STATE)
LIST(GET _state_as_list 1 GIT_HEAD_SHA1)
LIST(GET _state_as_list 2 GIT_IS_DIRTY)
configure_file("${PRE_CONFIGURE_GIT_VERSION_FILE}" "${POST_CONFIGURE_GIT_VERSION_FILE}" @ONLY)
endfunction()
# Function: GetGitState
# Description: gets the current state of the git repo.
# Args:
# _working_dir (in) string; the directory from which git commands will be executed.
# _state (out) list; a collection of variables representing the state of the
# repository (e.g. commit SHA).
function(GetGitState _working_dir _state)
# Get the hash for HEAD.
set(_success "true")
if(EXISTS "${GIT_CONFIG_DIR}")
execute_process(COMMAND
"${GIT_EXECUTABLE}" rev-parse --verify HEAD
WORKING_DIRECTORY "${_working_dir}"
RESULT_VARIABLE res
OUTPUT_VARIABLE _hashvar
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE)
if(NOT res EQUAL 0)
set(_success "false")
set(_hashvar "GIT-NOTFOUND")
endif()
# Get whether or not the working tree is dirty.
execute_process(COMMAND
"${GIT_EXECUTABLE}" status --porcelain
WORKING_DIRECTORY "${_working_dir}"
RESULT_VARIABLE res
OUTPUT_VARIABLE out
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE)
if(NOT res EQUAL 0)
set(_success "false")
set(_dirty "false")
else()
if(NOT "${out}" STREQUAL "")
set(_dirty "true")
else()
set(_dirty "false")
endif()
endif()
else()
set(_success "false")
set(_hashvar "GIT-NOTFOUND")
set(_dirty "false")
endif()
# Return a list of our variables to the parent scope.
set(${_state} ${_success} ${_hashvar} ${_dirty} PARENT_SCOPE)
endfunction()
# Function: CheckGit
# Description: check if the git repo has changed. If so, update the state file.
# Args:
# _working_dir (in) string; the directory from which git commands will be ran.
# _state_changed (out) bool; whether or no the state of the repo has changed.
# _state (out) list; the repository state as a list (e.g. commit SHA).
function(CheckGit _working_dir _state_changed _state)
# Get the current state of the repo.
GetGitState("${_working_dir}" state)
# Set the output _state variable.
# (Passing by reference in CMake is awkward...)
set(${_state} ${state} PARENT_SCOPE)
if(EXISTS "${POST_CONFIGURE_GIT_VERSION_FILE}")
if("${PRE_CONFIGURE_GIT_VERSION_FILE}" IS_NEWER_THAN "${POST_CONFIGURE_GIT_VERSION_FILE}")
file(REMOVE "${POST_CONFIGURE_GIT_VERSION_FILE}")
file(REMOVE "${GIT_STATE_FILE}")
set(${_state_changed} "true" PARENT_SCOPE)
return()
endif()
else()
file(REMOVE "${GIT_STATE_FILE}")
set(${_state_changed} "true" PARENT_SCOPE)
return()
endif()
# Check if the state has changed compared to the backup on disk.
if(EXISTS "${GIT_STATE_FILE}")
file(READ "${GIT_STATE_FILE}" OLD_HEAD_CONTENTS)
if(OLD_HEAD_CONTENTS STREQUAL "${state}")
# State didn't change.
set(${_state_changed} "false" PARENT_SCOPE)
return()
endif()
endif()
# The state has changed.
# We need to update the state file on disk.
# Future builds will compare their state to this file.
file(WRITE "${GIT_STATE_FILE}" "${state}")
set(${_state_changed} "true" PARENT_SCOPE)
endfunction()
# Function: SetupGitMonitoring
# Description: this function sets up custom commands that make the build system
# check the state of git before every build. If the state has
# changed, then a file is configured.
function(SetupGitMonitoring)
add_custom_target(check_git_repository
ALL
DEPENDS ${PRE_CONFIGURE_GIT_VERSION_FILE}
BYPRODUCTS ${POST_CONFIGURE_GIT_VERSION_FILE}
BYPRODUCTS ${GIT_STATE_FILE}
COMMENT "Checking the git repository for changes..."
COMMAND
${CMAKE_COMMAND}
-D_BUILD_TIME_CHECK_GIT=TRUE
-DGIT_WORKING_DIR=${GIT_WORKING_DIR}
-DGIT_EXECUTABLE=${GIT_EXECUTABLE}
-DGIT_STATE_FILE=${GIT_STATE_FILE}
-DGIT_CONFIG_DIR=${GIT_CONFIG_DIR}
-DPRE_CONFIGURE_GIT_VERSION_FILE=${PRE_CONFIGURE_GIT_VERSION_FILE}
-DPOST_CONFIGURE_GIT_VERSION_FILE=${POST_CONFIGURE_GIT_VERSION_FILE}
-P "${CMAKE_CURRENT_LIST_FILE}")
endfunction()
# Function: Main
# Description: primary entry-point to the script. Functions are selected based
# on whether it's configure or build time.
function(Main)
if(_BUILD_TIME_CHECK_GIT)
# Check if the repo has changed.
# If so, run the change action.
CheckGit("${GIT_WORKING_DIR}" did_change state)
if(did_change)
GitStateChangedAction("${state}")
endif()
else()
# >> Executes at configure time.
SetupGitMonitoring()
endif()
endfunction()
# And off we go...
Main()

113
cmake/packaging.cmake Normal file
View File

@ -0,0 +1,113 @@
##### set CPack properties #####
#
# Good doc/tutorial/example:
# - https://gitlab.kitware.com/cmake/community/-/wikis/doc/cpack/PackageGenerators
# - https://www.cmake.org/cmake/help/v3.3/module/CPack.html
# - https://sourceforge.net/p/klusters/klusters/ci/master/tree/CMakeLists.txt
#
# This cmake script should generate same packages (deb,rpm) as:
# - https://mirror.debian.ikoula.com/debian/pool/main/d/doxygen
# - http://archive.ubuntu.com/ubuntu/pool/main/d/doxygen (http://old-releases.ubuntu.com/ubuntu/pool/main/d/doxygen)
# - https://rpmfind.net/linux/rpm2html/search.php?query=doxygen
set(CPACK_STRIP_FILES ON)
set(CPACK_PACKAGE_NAME ${PROJECT_NAME} )
set(CPACK_PACKAGE_VERSION ${VERSION})
set(CPACK_PACKAGE_CONTACT "Dimitri van Heesch")
set(CPACK_PACKAGE_VENDOR "Dimitri van Heesch")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Generate documentation from C, C++, Java, Python and other languages")
set(CPACK_PACKAGE_DESCRIPTION "Doxygen is the de facto standard tool for generating documentation from annotated C++ sources.
But many other popular programming languages are supported:
C, Objective-C, C#, PHP, Java, Python, Fortran, D (some extent), and IDL (Corba, Microsoft, and UNO/OpenOffice flavors).
Doxygen also supports the hardware description language VHDL.
.
Three usages:
.
1. Generate documentation from annotated source files to various format:
- On-line documentation (HTML)
- Off-line reference manual (LaTeX, RTF, PostScript, hyperlinked PDF, compressed HTML, Unix man pages)
.
2. Extract the code structure from undocumented source files.
Also generate include dependency graphs, inheritance diagrams, and collaboration diagrams.
Useful to quickly understand code organization in large source distributions.
.
3. Create normal documentation (as the doxygen user manual and web-site http://doxygen.org/)
.
Install the doxygen-latex package to build LaTeX based documents.
Install the libclang1 package to use the 'clang assisted parsing'.")
set(CPACK_RESOURCE_FILE_LICENSE ${CMAKE_CURRENT_SOURCE_DIR}/LICENSE)
set(CPACK_RESOURCE_FILE_README ${CMAKE_CURRENT_SOURCE_DIR}/README.md)
# Variables specific to CPack RPM generator
set(CPACK_RPM_PACKAGE_DESCRIPTION ${CPACK_PACKAGE_DESCRIPTION})
set(CPACK_RPM_PACKAGE_LICENSE "GPLv2")
set(CPACK_RPM_PACKAGE_GROUP "Development/Tools")
set(CPACK_RPM_PACKAGE_URL "https://doxygen.org/")
set(CPACK_RPM_PACKAGE_REQUIRES "/sbin/chkconfig, /bin/mktemp, /bin/rm, /bin/mv, libstdc++ >= 2.96")
set(CPACK_RPM_PACKAGE_SUGGESTS "doxygen-latex, doxygen-doc, doxygen-gui, graphviz, libclang1")
# Variables specific to CPack DEB generator
set(CPACK_DEBIAN_PACKAGE_DESCRIPTION ${CPACK_PACKAGE_DESCRIPTION})
set(CPACK_DEBIAN_PACKAGE_SECTION "devel")
set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://doxygen.org/")
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS YES) #set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6, libclang1-3.6, libgcc1, libstdc++6, libxapian22")
set(CPACK_DEBIAN_PACKAGE_SUGGESTS "doxygen-latex, doxygen-doc, doxygen-gui, graphviz, libclang1")
set(CPACK_DEBIAN_PACKAGE_CONFLICTS "graphviz (<< 1.12)")
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Matthias Klose <doko@debian.org>") # Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
# Variables specific to CPack NSIS generator
set(CPACK_NSIS_MUI_ICON ${CMAKE_CURRENT_SOURCE_DIR}/addon/doxywizard/doxywizard.ico)
set(CPACK_NSIS_URL_INFO_ABOUT "https://doxygen.org/")
set(CPACK_NSIS_PACKAGE_NAME ${PROJECT_NAME})
# Variables specific to CPack DragNDrop generator
set(CPACK_DMG_FORMAT "UDBZ") # UDRO=UDIF-Read-Only, UDZO=zlib, UDBZ=bzip2 -- See hdiutil
set(CPACK_DMG_VOLUME_NAME ${PROJECT_NAME})
set(CPACK_DMG_BACKGROUND_IMAGE ${CMAKE_CURRENT_SOURCE_DIR}/doc/doxygen_logo.eps)
if(WIN32)
set(CPACK_GENERATOR "ZIP;NSIS")
elseif(APPLE)
set(CPACK_GENERATOR "ZIP;DragNDrop;PackageMaker;Bundle" )
set(CPACK_SYSTEM_NAME "OSX" )
elseif(UNIX)
# Determine distribution and release
execute_process(COMMAND lsb_release -si OUTPUT_VARIABLE distribution OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(COMMAND lsb_release -sc OUTPUT_VARIABLE release OUTPUT_STRIP_TRAILING_WHITESPACE)
#xecute_process(COMMAND uname -i OUTPUT_VARIABLE CPACK_RPM_PACKAGE_ARCHITECTURE OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(COMMAND uname -m OUTPUT_VARIABLE CPACK_RPM_PACKAGE_ARCHITECTURE OUTPUT_STRIP_TRAILING_WHITESPACE)
if(distribution STREQUAL "Debian" OR distribution STREQUAL "Ubuntu")
set(CPACK_GENERATOR "DEB")
execute_process(COMMAND dpkg --print-architecture OUTPUT_VARIABLE CPACK_DEBIAN_PACKAGE_ARCHITECTURE OUTPUT_STRIP_TRAILING_WHITESPACE)
set(CPACK_PACKAGE_FILE_NAME ${CPACK_PACKAGE_NAME}_${CPACK_PACKAGE_VERSION}_${CPACK_DEBIAN_PACKAGE_ARCHITECTURE})
elseif(distribution MATCHES "RedHat.*")
# extract the major version from RedHat full version (e.g. 6.7 --> 6)
execute_process(COMMAND lsb_release -sr COMMAND sed s/[.].*// OUTPUT_VARIABLE redhat_version_major OUTPUT_STRIP_TRAILING_WHITESPACE)
set(CPACK_GENERATOR "RPM")
set(CPACK_PACKAGE_FILE_NAME ${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${CPACK_RPM_PACKAGE_RELEASE}.el${redhat_version_major}.${CPACK_RPM_PACKAGE_ARCHITECTURE})
elseif(distribution MATCHES "openSUSE.*")
set(CPACK_GENERATOR "RPM")
set(CPACK_PACKAGE_FILE_NAME ${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${release}.${CPACK_RPM_PACKAGE_ARCHITECTURE})
elseif(distribution STREQUAL "Fedora")
set(CPACK_GENERATOR "RPM")
set(CPACK_PACKAGE_FILE_NAME ${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}.fc${release}.${CPACK_RPM_PACKAGE_ARCHITECTURE})
elseif(distribution STREQUAL "Scientific")
set(CPACK_GENERATOR "RPM")
set(CPACK_PACKAGE_FILE_NAME ${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${release}.${CPACK_RPM_PACKAGE_ARCHITECTURE})
else()
set(CPACK_GENERATOR "RPM;TGZ;STGZ")
set(CPACK_PACKAGE_FILE_NAME ${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${release}.${CPACK_RPM_PACKAGE_ARCHITECTURE})
endif()
else()
# other operating system (not Windows/Apple/Unix)
endif()

3
cmake/version.cmake Normal file
View File

@ -0,0 +1,3 @@
file (STRINGS "${TOP}/VERSION" VERSION)
set(ENV{VERSION} "${VERSION}")
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${TOP}/VERSION)

9
deps/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1,9 @@
add_subdirectory(libmd5)
add_subdirectory(liblodepng)
add_subdirectory(libmscgen)
if (NOT use_sys_spdlog)
add_subdirectory(spdlog)
endif()
if (NOT use_sys_sqlite3)
add_subdirectory(sqlite3)
endif()

362
deps/TinyDeflate/README.md vendored Normal file
View File

@ -0,0 +1,362 @@
# TinyDeflate
See https://github.com/bisqwit/TinyDeflate for the original version.
A deflate/gzip decompressor, as a C++17 template function,
that requires minimal amount of memory to work.
Terms of use: Zlib
Copyright © 2018 Joel Yliluoma
## Memory usage at aggressive settings (backtrackable input)
* 408 bytes of automatic storage for three huffman trees (384 elements, 5…9 bits each, 88 % space efficiency)
* 24 bytes of temporary automatic storage while a huffman tree is being generated (15 elements, 9 bits each, 66 % space efficiency)
* An assortment of automatic variables for various purposes (may be register variables, depending on the architecture and of the compiler wits)
* ABI mandated alignment losses
Total: 408 bytes minimum, 432+N bytes maximum
Theoretical minimum at 100 % efficiency: 357.1 + 15.32 ≃ 373 bytes (not yet attained by this library).
## Memory usage at aggressive settings (non-backtrackable input)
* 144 bytes of automatic storage for length tables (288 elements, 4 bits each, 100 % space efficiency)
* 384 bytes of automatic storage for two huffman trees (350 elements, 5…9 bits each, 88 % space efficiency)
* 24 bytes of temporary automatic storage while a huffman tree is being generated (15 elements, 9 bits each, 66 % space efficiency)
* An assortment of automatic variables for various purposes (may be register variables, depending on the architecture and of the compiler wits)
* ABI mandated alignment losses
Total: 528 bytes minimum, 552+N bytes maximum
Theoretical minimum at 100 % efficiency: 144 + 338.9 + 15.32 ≃ 499 bytes (not yet attained by this library).
## Memory usage at default settings (backtrackable input)
* 687 bytes of automatic storage for three huffman trees (52 % space efficiency)
* 30 bytes of temporary automatic storage while a huffman tree is being generated (53 % space efficiency)
* An assortment of automatic variables for various purposes (may be register variables, depending on the architecture and of the compiler wits)
* ABI mandated alignment losses
Total: 687 bytes minimum, 717+N bytes maximum
## Memory usage at default settings (non-backtrackable input)
* 288 bytes of automatic storage for length tables (50 % space efficiency)
* 653 bytes of automatic storage for two huffman trees (52 % space efficiency)
* 30 bytes of temporary automatic storage while a huffman tree is being generated (53 % space efficiency)
* An assortment of automatic variables for various purposes (may be register variables, depending on the architecture and of the compiler wits)
* ABI mandated alignment losses
Total: 941 bytes minimum, 971+N bytes maximum
## Tuning
To adjust the memory usage, there are three settings in gunzip.hh you can change:
| Setting name | 'false' memory use bytes | 'true' memory use bytes | 'true' performance impact
| ------------------------------------------- | ---:| ----:|--------------
| `USE_BITARRAY_TEMPORARY_IN_HUFFMAN_CREATION` | 30 | 24 | Negligible
| `USE_BITARRAY_FOR_LENGTHS` | 288 or 0 | 144 or 0 | Noticeable
| `USE_BITARRAY_FOR_HUFFNODES` | 653 or 687 | 384 or 408 | Significant
| **Total** | 971 or 717 | 552 or 432 | _Plus alignment losses, callframes and spills_
In addition, if you neither decompress into a raw memory area nor supply your own window function,
32768 bytes of automatic storage is allocated for the look-behind window.
You can also change the memory allocation scheme:
| `#define` name | Meaning
| --- | ---
| `DEFLATE_ALLOCATION_AUTOMATIC` | Automatic allocation (usually stack)
| `DEFLATE_ALLOCATION_STATIC` | Static `thread_local` allocation (memory remains allocated throughout the program, and different threads have their own copy of the data). Note that this scheme excludes running multiple decompressions in parallel, unless you do it in different threads.
| `DEFLATE_ALLOCATION_DYNAMIC` | Storage duration is the same as with automatic allocation, but the `new` keyword is explicitly used (which usually means heap/bss allocation).
There is also a constant `MAX_WINDOW_SIZE`, which is 32768 by default,
but you can reduce it to use less memory for the automatically allocated
window in situations where one is allocated (see note 9 below).
Note that this value must not be smaller than the maximum backreference
distance used by your compressed data.
## Unrequirements
* No dynamic memory is allocated under any circumstances, unless your user-supplied functors do it, or you `#define DEFLATE_ALLOCATION_DYNAMIC`.
* Aside from assert() in assert.h and some template metaprogramming tools in type_traits, no standard library functions are used.
* No global variables.
* Compatible with -fno-exceptions -fno-rtti compilation.
* Option to compile without constant arrays.
## Rationale
* Embedded platforms (Arduino, STM32 etc).
* ROM hacking
## Caveats
* Decompressor only. Deflate and GZIP streams are supported.
* Slower than your average inflate function. The template uses densely bitpacked arrays, which require plenty of bit-shifting operations for every access.
* The code obviously performs best on 32-bit or 64-bit platforms. Platforms where 32-bit entities must be synthesized from a number of 8-bit entities are at a disadvantage.
* Decompressed data integrity is not verified. Any checksum fields are totally ignored.
* On most systems, automatic storage means stack allocation. Depending on your circumstances, you may want to change the memory allocation scheme. See the Tuning chapter for details.
## Definitions
```C++
struct DeflateTrackNoSize{};
struct DeflateTrackInSize{};
struct DeflateTrackOutSize{};
struct DeflateTrackBothSize{};
int/*exit status*/ Deflate(InputParams..., OutputParams..., DeflateTrackNoSize = {});
std::pair<int/*exit status*/, std::uint_fast64_t/*number of input bytes consumed*/>
Deflate(InputParams..., OutputParams..., DeflateTrackInSize); // (11)
std::pair<int/*exit status*/, std::uint_fast64_t/*number of output bytes generated*/>
Deflate(InputParams..., OutputParams..., DeflateTrackOutSize); // (12)
std::pair<int/*exit status*/, std::pair<std::uint_fast64_t/*in size*/, std::uint_fast64_t/*out size*/>>
Deflate(InputParams..., OutputParams..., DeflateTrackBothSize); // (13)
// A counter for sizes is only allocated if explicitly requested
// by using one of the former three tracking overloads.
```
`InputParams` may be one of the following sets of parameters:
* InputFunctor input `(5)` `(14)`
* InputIterator begin `(7)` `(14)`
* InputIterator begin, InputIterator end `(6)` `(14)`
* InputIterator begin, SizeType length `(8)` `(14)`
* BidirectionalIterator begin, SizeType length `(8)` `(15)`
* ForwardIterator begin `(7)` `(14)`
* BidirectionalIterator begin `(7)` `(15)`
* RandomAccessIterator begin `(7)` `(15)`
* ForwardIterator begin, ForwardIterator end `(6)` `(15)`
* BidirectionalIterator begin, BidirectionalIterator end `(6)` `(15)`
* RandomAccessIterator begin, RandomAccessIterator end `(6)` `(15)`
`OutputParams` may be one of the following sets of parameters:
* OutputFunctor output `(1)` `(9)`
* OutputFunctor output, WindowFunctor window `(2)`
* OutputIterator target `(9)`
* RandomAccessIterator target `(10)`
* RandomAccessIterator target, SizeType target_limit `(3)` `(10)`
* RandomAccessIterator target, RandomAccessIterator target_end `(4)` `(10)`
1) If the output functor (`output`) returns a `bool`, and the returned value is `true`, the decompression aborts with return value -3
without writing any more data.
2) If the output functor (`output`) returns a `bool`, and the returned value is `true`, the decompression aborts with return value -3
without writing any more data.
If the window function returns an integer type, and the returned value is other than 0, the decompression aborts with return value -4
without writing any more data.
If either the window function returns `void`, or the output functor does not return a `bool`, aborting on output-full will not be compiled.
3) If `target_limit` bytes have been written into `target` and the decompression is not yet complete, the decompression aborts with return value -3
without writing any more data.
4) If `target_begin == target_end`, the decompression aborts with return value -3
without writing any more data.
5) If the input functor (`input`) returns an integer type other than a `char`, `signed char`, or `unsigned char`,
and the returned value is smaller than 0 or larger than 255, the decompression aborts with return value -2
without reading any more data.
6) If `begin == end`, the decompression aborts with return value -2.
7) If the input iterator deferences into a value outside the 0 — 255 range, the decompression aborts with return value -2
without reading any more data.
8) If `length` bytes have been read from `begin` and the decompression is not yet complete, the decompression aborts with return value -2
without reading any more data.
9) A separate 32768-byte sliding window will be automatically and separately allocated for the decompression.
10) The output data buffer is assumed to persist during the call and doubles as the sliding window for the decompression.
11) The `first` field in the return value has the same meaning as the `int` type return value described earlier.
The `second` field in the return value contains the number of bytes that were consumed from the input.
12) The `first` field in the return value has the same meaning as the `int` type return value described earlier.
The `second` field in the return value contains the number of bytes that were written to the output.
13) The `first` field in the return value has the same meaning as the `int` type return value described earlier.
The `second.first` field in the return value contains the number of bytes that were consumed from the input.
The `second.second` field in the return value contains the number of bytes that were written to the output.
14) This method is non-backtrackable, and uses a bit more memory than the backtrackable ones.
15) This method is backtrackable, meaning that some bytes in the input may be read twice. It uses less memory than the non-backtrackable calls.
### Tips
Some of these definitions may be ambiguous.
If you hit a compiler error, choose a different call method.
To help distinguish between (`InputIterator`,`RandomAccessIterator`,`RandomAccessIterator`)
and (`ForwardIterator`,`ForwardIterator`,`OutputIterator`), make sure the input iterators
are _const_.
If you do multiple decompression calls in your program in different spots,
it may be wise to make sure they all use the same type of parameters,
to avoid having to instantiate multiple copies of `Deflate()`.
Lambda functors are an offender in this respect, because each lambda has a
unique type even if their contents and calling conventions are identical.
In the worst case, you can use `std::function` to wrap your calls
into a common interface. Check out this video for more about this topic: https://www.youtube.com/watch?v=rUB5Hlm9AaQ
## Requirements
```C++
// An InputFunctor has the following prototype,
// wherein type1 is convertible into unsigned char:
type1 input()
// An OutputFunctor has one of the following two prototypes,
// wherein type1 can accept unsigned int parameters in range 0-255:
void output(type1 byte_to_output)
bool output(type1 byte_to_output)
// A WindowFunctor has one of the following two prototypes,
// wherein type1 can accept unsigned int parameters in range 0-258,
// and type2 can accept unsigned int parameters:
void outputcopy(type1 length, type2 offs)
type2 outputcopy(type1 length, type2 offs)
// An InputIterator must have at least the following operations defined,
// where type1 is convertible into unsigned char:
const type1& operator*() const
InputIterator& operator++()
// A OutputIterator must have at least the following operations defined,
// where type1 is convertible into unsigned char:
type1& operator*() const
OutputIterator& operator++()
// A ForwardIterator must have at least the following operations defined,
// where type1 is convertible into unsigned char:
const type1& operator*() const
ForwardIterator& operator++()
bool operator==(const ForwardIterator&) const
// A RandomAccessIterator must have at least the following operations defined,
// where type1 is convertible into unsigned char,
// and type2 is a signed integer type (may be negative):
type1& operator*()
type1& operator[] (type2)
RandomAccessIterator operator- (type2)
RandomAccessIterator& operator++()
bool operator==(const RandomAccessIterator&) const
```
## Example use:
Decompress the standard input into the standard output (uses 32 kB automatically allocated window):
```C++
Deflate([]() { return std::getchar(); },
[](unsigned char byte) { std::putchar(byte); });
// Or more simply:
Deflate(std::getchar, std::putchar);
```
Decompress an array containing gzipped data into another array that must be large enough to hold the result.
A window buffer will not be allocated.
```C++
extern const char compressed_data[];
extern unsigned char outbuffer[131072];
Deflate(compressed_data+0, outbuffer+0);
```
Same as above, but with range checking for output, and reporting of written size:
```C++
extern const char compressed_data[];
extern unsigned char outbuffer[131072];
auto result = Deflate(compressed_data+0, outbuffer+0, sizeof(outbuffer), DeflateTrackOutSize{});
if(result.first != 0) std::fprintf(stderr, "Error %d\n", result.first);
std::fprintf(stderr, "%u bytes written\n", unsigned(result.second));
```
Same as above, but with range checking for both input and output:
```C++
extern const char compressed_data[];
extern unsigned compressed_data_length;
extern unsigned char outbuffer[131072];
int result = Deflate(compressed_data+0, compressed_data_length, outbuffer, outbuffer + sizeof(outbuffer));
if(result != 0) std::fprintf(stderr, "Error\n");
```
Decompress using a custom window function (the separate 32 kB window buffer will not be allocated):
```C++
std::vector<unsigned char> result;
Deflate(std::getchar,
[&](unsigned byte)
{
result.push_back(byte);
},
[&](unsigned length, unsigned offset)
{
if(!length)
{
// offset contains the maximum look-behind distance.
// You could use this information to allocate a buffer of a particular size.
// length=0 case is invoked exactly once before any length!=0 cases are.
}
while(length-- > 0)
{
result.push_back( result[result.size()-offset] );
}
});
```
Same as above, but stop decompressing once 4096 bytes have been written:
```C++
std::vector<unsigned char> result;
Deflate(std::getchar,
[&](unsigned byte)
{
if(result.size() >= 4096) return true;
result.push_back(byte);
return false;
},
[&](unsigned length, unsigned offset)
{
if(!length)
{
// offset contains the maximum look-behind distance.
// You could use this information to allocate a buffer of a particular size.
// length=0 case is invoked exactly once before any length!=0 cases are.
}
for(; result.size() < 4096 && length > 0; --length)
{
result.push_back( result[result.size()-offset] );
}
return length;
});
```
## Misnomer
Yes, I am aware that the project is technically named misleadingly.
This project implements the _inflate_ algorithm (decompression),
not _deflate_ (compression).
In my defense, the _compression format_ is called deflate. There is no _inflate_ format.
This library decompresses data that has been compressed with _deflate_.
Think name, not verb.

Some files were not shown because too many files have changed in this diff Show More