update
This commit is contained in:
commit
7c502b360e
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,9 @@
|
|||
# See https://editorconfig.org/ for more information.
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
charset = utf-8
|
||||
|
||||
[*.py]
|
||||
indent_size = 4
|
|
@ -0,0 +1,9 @@
|
|||
* text=auto eol=lf
|
||||
* text
|
||||
|
||||
### Protected
|
||||
*.pdf binary
|
||||
*.ico binary
|
||||
*.jpg binary
|
||||
*.png binary
|
||||
*.lib binary
|
|
@ -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']
|
|
@ -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.
|
|
@ -0,0 +1,6 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
|
@ -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)' }}
|
||||
|
||||
|
|
@ -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 }}
|
||||
|
|
@ -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 }}
|
|
@ -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 }}
|
|
@ -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/*
|
|
@ -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)
|
|
@ -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"]
|
|
@ -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
|
|
@ -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 ä 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.
|
||||
|
|
@ -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.
|
|
@ -0,0 +1,68 @@
|
|||
Doxygen
|
||||
===============
|
||||
[](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)
|
|
@ -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 ()
|
|
@ -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()
|
||||
|
|
@ -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.
|
|
@ -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
|
||||
|
|
@ -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
|
@ -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()
|
||||
|
|
@ -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()
|
||||
|
|
@ -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()
|
|
@ -0,0 +1,2 @@
|
|||
lxml>=4.0.0
|
||||
six>=1.0.0
|
|
@ -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
|
||||
)
|
|
@ -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)
|
|
@ -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.
|
||||
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
@ -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()
|
|
@ -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>
|
|
@ -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)
|
|
@ -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>
|
|
@ -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).
|
|
@ -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);
|
||||
}
|
|
@ -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:])
|
|
@ -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)
|
|
@ -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,">",">");
|
||||
replace_all(result,"<","<");
|
||||
replace_all(result,"'","'");
|
||||
replace_all(result,""","\"");
|
||||
replace_all(result,"&","&");
|
||||
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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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)
|
|
@ -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.
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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 << "<";
|
||||
else if (*p ==QChar::fromLatin1('>')) t << ">";
|
||||
else if (*p ==QChar::fromLatin1('&')) t << "&";
|
||||
else t << *p;
|
||||
}
|
||||
else
|
||||
{
|
||||
t << *p;
|
||||
}
|
||||
p++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
p=s.data();
|
||||
while (!p->isNull())
|
||||
{
|
||||
if (convert)
|
||||
{
|
||||
if (*p ==QChar::fromLatin1('<')) t << "<";
|
||||
else if (*p ==QChar::fromLatin1('>')) t << ">";
|
||||
else if (*p ==QChar::fromLatin1('&')) t << "&";
|
||||
else t << *p;
|
||||
}
|
||||
else
|
||||
{
|
||||
t << *p;
|
||||
}
|
||||
p++;
|
||||
}
|
||||
}
|
||||
if (needsHashEscaping || needsEscaping)
|
||||
{
|
||||
t << "\"";
|
||||
}
|
||||
}
|
||||
}
|
||||
#if USE_STATE2STRING
|
||||
#include "config_doxyw.l.h"
|
||||
#endif
|
|
@ -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();
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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>© 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();
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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>
|
File diff suppressed because it is too large
Load Diff
|
@ -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
|
|
@ -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 |
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
File diff suppressed because it is too large
Load Diff
|
@ -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
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
||||
|
|
@ -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()
|
|
@ -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()
|
|
@ -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)
|
||||
|
|
@ -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')
|
|
@ -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 ()
|
|
@ -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 ()
|
|
@ -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)
|
|
@ -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 ()
|
|
@ -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 ()
|
|
@ -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 $@
|
|
@ -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 ()
|
|
@ -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})
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -0,0 +1,3 @@
|
|||
file (STRINGS "${TOP}/VERSION" VERSION)
|
||||
set(ENV{VERSION} "${VERSION}")
|
||||
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${TOP}/VERSION)
|
|
@ -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()
|
|
@ -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
Loading…
Reference in New Issue