Repository: adobe/hyde Branch: master Commit: 3fdcc772e07a Files: 137 Total size: 328.7 KB Directory structure: gitextract_uuaa76lr/ ├── .clang-format ├── .editorconfig ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ └── feature_request.md │ ├── PULL_REQUEST_TEMPLATE/ │ │ ├── bug_fix.md │ │ └── feature.md │ └── workflows/ │ └── tagged-release.yml ├── .gitignore ├── .gitmodules ├── .hyde-config ├── CMakeLists.txt ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── README.md ├── docs/ │ ├── Gemfile │ ├── _config.yml │ ├── _includes/ │ │ └── head.html │ ├── _posts/ │ │ └── 2018-10-30-welcome.markdown │ ├── _sass/ │ │ ├── _overrides-dark.scss │ │ ├── _overrides-light.scss │ │ └── _overrides.scss │ ├── about.md │ ├── feed.xml │ ├── index.html │ ├── libraries/ │ │ ├── classes.cpp/ │ │ │ ├── class_example/ │ │ │ │ ├── color.md │ │ │ │ ├── index.md │ │ │ │ ├── m_class_example.md │ │ │ │ ├── m_deprecated.md │ │ │ │ ├── m_deprecated_with_message.md │ │ │ │ ├── m_member_function.md │ │ │ │ ├── m_member_function_trailing_return_type.md │ │ │ │ ├── m_overloaded.md │ │ │ │ ├── m_static_method.md │ │ │ │ ├── m_template_member_function.md │ │ │ │ └── nested_class_example/ │ │ │ │ ├── index.md │ │ │ │ ├── m_nested_class_example.md │ │ │ │ └── m_~nested_class_example.md │ │ │ ├── index.md │ │ │ ├── partial_specialization_.4b7bfe45/ │ │ │ │ └── index.md │ │ │ ├── partial_specialization_.edfbc14d/ │ │ │ │ └── index.md │ │ │ ├── specialization_example3.ed9d8cc7/ │ │ │ │ ├── index.md │ │ │ │ └── m_as_tuple.md │ │ │ ├── specialization_example3CT3E/ │ │ │ │ ├── index.md │ │ │ │ └── m_as_tuple.md │ │ │ └── specialization_example3Cfloat3E/ │ │ │ ├── index.md │ │ │ └── m_as_tuple.md │ │ ├── comments.cpp/ │ │ │ ├── compiler_generated/ │ │ │ │ ├── index.md │ │ │ │ ├── m_assign.md │ │ │ │ ├── m_compiler_generated.md │ │ │ │ └── m_operator3D.md │ │ │ ├── f_template_function.md │ │ │ ├── index.md │ │ │ ├── some_other_struct/ │ │ │ │ ├── index.md │ │ │ │ ├── m_operator3D.md │ │ │ │ ├── m_some_other_struct.md │ │ │ │ ├── m_virtual_function.md │ │ │ │ └── m_~some_other_struct.md │ │ │ └── some_struct/ │ │ │ ├── index.md │ │ │ ├── m_operator3D.md │ │ │ ├── m_some_function.md │ │ │ ├── m_some_struct.md │ │ │ ├── m_virtual_function.md │ │ │ └── m_~some_struct.md │ │ ├── enums.cpp/ │ │ │ ├── color_channel.md │ │ │ ├── index.md │ │ │ └── untyped.md │ │ ├── functions.cpp/ │ │ │ ├── f_binary_function_example.md │ │ │ ├── f_nullary_function_example.md │ │ │ ├── f_overloaded.md │ │ │ ├── f_static_auto_function_example.md │ │ │ ├── f_static_function_example.md │ │ │ ├── f_static_trailing_type_function_example.md │ │ │ ├── f_template_function_example.md │ │ │ └── index.md │ │ ├── index.md │ │ ├── namespaces.cpp/ │ │ │ ├── f_function.md │ │ │ └── index.md │ │ ├── point.cpp/ │ │ │ ├── index.md │ │ │ └── point3CT3E/ │ │ │ ├── f_operator-.md │ │ │ ├── f_operator213D.md │ │ │ ├── f_operator3D3D.md │ │ │ ├── index.md │ │ │ ├── m_operator-3D.md │ │ │ ├── m_origin.md │ │ │ └── m_point3CT3E.md │ │ └── typedef_and_alias.cpp/ │ │ ├── index.md │ │ ├── template_example3CT2C20U3E/ │ │ │ └── index.md │ │ └── template_example_instantiator/ │ │ └── index.md │ └── serve.sh ├── emitters/ │ ├── yaml_base_emitter.cpp │ ├── yaml_base_emitter.hpp │ ├── yaml_base_emitter_fwd.hpp │ ├── yaml_class_emitter.cpp │ ├── yaml_class_emitter.hpp │ ├── yaml_enum_emitter.cpp │ ├── yaml_enum_emitter.hpp │ ├── yaml_function_emitter.cpp │ ├── yaml_function_emitter.hpp │ ├── yaml_library_emitter.cpp │ ├── yaml_library_emitter.hpp │ ├── yaml_sourcefile_emitter.cpp │ └── yaml_sourcefile_emitter.hpp ├── generate_test_files.sh ├── include/ │ ├── _clang_include_prefix.hpp │ ├── _clang_include_suffix.hpp │ ├── autodetect.hpp │ ├── config.hpp │ ├── json.hpp │ ├── json_fwd.hpp │ └── output_yaml.hpp ├── matchers/ │ ├── class_matcher.cpp │ ├── class_matcher.hpp │ ├── enum_matcher.cpp │ ├── enum_matcher.hpp │ ├── function_matcher.cpp │ ├── function_matcher.hpp │ ├── matcher_fwd.hpp │ ├── namespace_matcher.cpp │ ├── namespace_matcher.hpp │ ├── typealias_matcher.cpp │ ├── typealias_matcher.hpp │ ├── typedef_matcher.cpp │ ├── typedef_matcher.hpp │ ├── utilities.cpp │ └── utilities.hpp ├── sources/ │ ├── autodetect.cpp │ ├── main.cpp │ └── output_yaml.cpp └── test_files/ ├── classes.cpp ├── comments.cpp ├── enums.cpp ├── functions.cpp ├── namespaces.cpp ├── point.cpp └── typedef_and_alias.cpp ================================================ FILE CONTENTS ================================================ ================================================ FILE: .clang-format ================================================ # Format style options described here: # https://clang.llvm.org/docs/ClangFormatStyleOptions.html # Many of the alignment and single line changes were made to facilitate setting # breakpoints on specific expressions. --- AccessModifierOffset: -4 AlignAfterOpenBracket: Align AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false AlignEscapedNewlinesLeft: true AlignOperands: true AlignTrailingComments: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: All AllowShortIfStatementsOnASingleLine: true AllowShortLoopsOnASingleLine: false # AlwaysBreakAfterDefinitionReturnType: TopLevel AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: true BinPackArguments: true BinPackParameters: false # BraceWrapping: # AfterClass: true # AfterControlStatement: false # AfterEnum: false # AfterFunction: false # AfterNamespace: false # AfterObjCDeclaration: false # AfterStruct: false # AfterUnion: false # BeforeCatch: false # BeforeElse: false # IndentBraces: false BreakBeforeBinaryOperators: None BreakBeforeBraces: Attach BreakBeforeInheritanceComma: true BreakBeforeTernaryOperators: false BreakConstructorInitializersBeforeComma: false BreakAfterJavaFieldAnnotations: false BreakStringLiterals: false ColumnLimit: 100 CommentPragmas: '^ IWYU pragma:' ConstructorInitializerAllOnOneLineOrOnePerLine: false ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: true DerivePointerAlignment: false DisableFormat: false ExperimentalAutoDetectBinPacking: false FixNamespaceComments: true ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] IncludeCategories: - Regex: '^"(llvm|llvm-c|clang|clang-c)/' Priority: 2 - Regex: '^(<|"(gtest|isl|json)/)' Priority: 3 - Regex: '.*' Priority: 1 IncludeIsMainRegex: '$' IndentCaseLabels: true IndentWidth: 4 IndentWrappedFunctionNames: true JavaScriptQuotes: Leave JavaScriptWrapImports: true KeepEmptyLinesAtTheStartOfBlocks: false MacroBlockBegin: '' MacroBlockEnd: '' MaxEmptyLinesToKeep: 1 NamespaceIndentation: None ObjCBlockIndentWidth: 4 ObjCSpaceAfterProperty: true ObjCSpaceBeforeProtocolList: true PenaltyBreakBeforeFirstCallParameter: 19 PenaltyBreakComment: 300 PenaltyBreakFirstLessLess: 120 PenaltyBreakString: 1000 PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 1000 PointerAlignment: Left ReflowComments: true SortIncludes: true SpaceAfterCStyleCast: false SpaceAfterTemplateKeyword: true SpaceBeforeAssignmentOperators: true SpaceBeforeParens: ControlStatements SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: false SpacesInContainerLiterals: true SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false Standard: Cpp11 TabWidth: 4 UseTab: Never --- Language: Cpp --- Language: ObjC PointerAlignment: Right ... ================================================ FILE: .editorconfig ================================================ # According to https://stackoverflow.com/a/33831598/153535, this should improve # the way code is seen on GitHub (PRs, etc.) by making it respect tabs as being # four spaces. root = true # Matches multiple files with brace expansion notation [*.{c,cc,cpp,cxx,h,hh,hpp,hxx,mm}] indent_style = space indent_size = 4 ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - OS: [e.g. iOS] - Browser [e.g. chrome, safari] - Version [e.g. 22] **Smartphone (please complete the following information):** - Device: [e.g. iPhone6] - OS: [e.g. iOS8.1] - Browser [e.g. stock browser, safari] - Version [e.g. 22] **Additional context** Add any other context about the problem here. ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. ================================================ FILE: .github/PULL_REQUEST_TEMPLATE/bug_fix.md ================================================ --- name: Bug fix about: Submit a fix for an issue --- **Fix for issue(s)** #NNNN **Diagnosis** Please include a brief description of the root cause, and the fix(es) applied. **Additional notes** Add any other context about the problem here. _Thank you!_ ================================================ FILE: .github/PULL_REQUEST_TEMPLATE/feature.md ================================================ --- name: Feature about: Provide new functionality --- **Optional: Related issue(s)** #NNNN **What's Changed** A clear and concise description of what's included in this PR. **Rationale** A clear and concise description of why this change is valuable. **Additional context** Add any other context here. _Thank you!_ ================================================ FILE: .github/workflows/tagged-release.yml ================================================ name: Tagged Release on: push: tags: - "v*.*.*" workflow_dispatch: null permissions: contents: write jobs: release-mac: runs-on: macos-latest steps: - name: ⬇️ Checkout sources uses: actions/checkout@v3 - name: 🏗️ Setup project files run: | git submodule update --init mkdir build cd build cmake -GXcode .. - name: 🛠️ Build Hyde run: | cd build xcodebuild -quiet -target hyde -configuration Release - name: 🗜️ Create archive run: | cd build/Release # "No such xattr: com.apple.quarantine" # xattr -d com.apple.quarantine hyde strip hyde codesign --force -s - hyde tar -zcvf hyde-${{github.ref_name}}-macos.tgz hyde - name: ✍️ Post archive uses: softprops/action-gh-release@v1 with: generate_release_notes: true files: build/Release/hyde-${{github.ref_name}}-macos.tgz release-linux: runs-on: ubuntu-latest steps: - name: ⬇️ Checkout sources uses: actions/checkout@v3 - name: 🏗️ Set up Clang uses: egor-tensin/setup-clang@v1 with: version: 18 - name: 🏗️ Setup project files run: | sudo apt-get install ninja-build git submodule update --init mkdir build cd build cmake -DCMAKE_BUILD_TYPE=Release -GNinja .. - name: 🛠️ Build Hyde run: | cd build ninja - name: 🗜️ Create archive run: | cd build tar -zcvf hyde-${{github.ref_name}}-linux-${{runner.arch}}.tgz hyde - name: ✍️ Post archive uses: softprops/action-gh-release@v1 with: generate_release_notes: true files: build/hyde-${{github.ref_name}}-linux-${{runner.arch}}.tgz ================================================ FILE: .gitignore ================================================ *.sublime-project *.sublime-workspace .DS_Store .vscode/ build/ build*/ docs/_site/ docs/Gemfile.lock ================================================ FILE: .gitmodules ================================================ [submodule "submodules/yaml-cpp"] path = submodules/yaml-cpp url = https://github.com/jbeder/yaml-cpp.git [submodule "submodules/json"] path = submodules/json url = https://github.com/nlohmann/json.git ================================================ FILE: .hyde-config ================================================ { "hyde-src-root" : "./test_files", "hyde-yaml-dir" : "./docs/libraries", "clang_flags": [ "--std=c++2a", "-Wno-everything" ] } ================================================ FILE: CMakeLists.txt ================================================ # Copyright 2018 Adobe # All Rights Reserved. # NOTICE: Adobe permits you to use, modify, and distribute this file in # accordance with the terms of the Adobe license agreement accompanying # it. If you have received this file from a source other than Adobe, # then your use, modification, or distribution of it requires the prior # written permission of Adobe. cmake_minimum_required(VERSION 3.23) set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) # Avoid overriding normal variables with option() set(CMAKE_POLICY_DEFAULT_CMP0126 NEW) # Avoid overriding normal variables with set(CACHE) include(FetchContent) set(FETCHCONTENT_QUIET FALSE) set(CMAKE_OSX_ARCHITECTURES "x86_64;arm64") project(hyde) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_XCODE_GENERATE_SCHEME OFF) message(STATUS "INFO: Setting up LLVM...") FetchContent_Declare( llvm GIT_REPOSITORY https://github.com/llvm/llvm-project.git GIT_TAG 3b5b5c1ec4a3095ab096dd780e84d7ab81f3d7ff # llvmorg-18.1.8 GIT_SHALLOW TRUE GIT_PROGRESS TRUE SOURCE_SUBDIR llvm ) FetchContent_Declare( diff GIT_REPOSITORY https://github.com/fosterbrereton/diff.git GIT_TAG 2ba1687a30de266416caa141f8be408e72843be0 GIT_SHALLOW TRUE GIT_PROGRESS TRUE SOURCE_SUBDIR diff ) set(LLVM_ENABLE_PROJECTS "clang") set(LLVM_TARGETS_TO_BUILD "X86;AArch64") set(LLVM_ENABLE_ZSTD OFF) FetchContent_MakeAvailable(llvm) FetchContent_MakeAvailable(diff) message(STATUS "INFO: LLVM source dir: ${llvm_SOURCE_DIR}") message(STATUS "INFO: LLVM binary dir: ${llvm_BINARY_DIR}") add_executable(hyde) set(SRC_SOURCES ${PROJECT_SOURCE_DIR}/sources/autodetect.cpp ${PROJECT_SOURCE_DIR}/sources/main.cpp ${PROJECT_SOURCE_DIR}/sources/output_yaml.cpp ) set(SRC_EMITTERS ${PROJECT_SOURCE_DIR}/emitters/yaml_base_emitter.cpp ${PROJECT_SOURCE_DIR}/emitters/yaml_class_emitter.cpp ${PROJECT_SOURCE_DIR}/emitters/yaml_enum_emitter.cpp ${PROJECT_SOURCE_DIR}/emitters/yaml_function_emitter.cpp ${PROJECT_SOURCE_DIR}/emitters/yaml_library_emitter.cpp ${PROJECT_SOURCE_DIR}/emitters/yaml_sourcefile_emitter.cpp ) set(SRC_MATCHERS ${PROJECT_SOURCE_DIR}/matchers/class_matcher.cpp ${PROJECT_SOURCE_DIR}/matchers/enum_matcher.cpp ${PROJECT_SOURCE_DIR}/matchers/function_matcher.cpp ${PROJECT_SOURCE_DIR}/matchers/namespace_matcher.cpp ${PROJECT_SOURCE_DIR}/matchers/typealias_matcher.cpp ${PROJECT_SOURCE_DIR}/matchers/typedef_matcher.cpp ${PROJECT_SOURCE_DIR}/matchers/utilities.cpp ) set(SRC_YAMLCPP ${PROJECT_SOURCE_DIR}/submodules/yaml-cpp/src/binary.cpp ${PROJECT_SOURCE_DIR}/submodules/yaml-cpp/src/convert.cpp ${PROJECT_SOURCE_DIR}/submodules/yaml-cpp/src/depthguard.cpp ${PROJECT_SOURCE_DIR}/submodules/yaml-cpp/src/directives.cpp ${PROJECT_SOURCE_DIR}/submodules/yaml-cpp/src/emit.cpp ${PROJECT_SOURCE_DIR}/submodules/yaml-cpp/src/emitfromevents.cpp ${PROJECT_SOURCE_DIR}/submodules/yaml-cpp/src/emitter.cpp ${PROJECT_SOURCE_DIR}/submodules/yaml-cpp/src/emitterstate.cpp ${PROJECT_SOURCE_DIR}/submodules/yaml-cpp/src/emitterutils.cpp ${PROJECT_SOURCE_DIR}/submodules/yaml-cpp/src/exceptions.cpp ${PROJECT_SOURCE_DIR}/submodules/yaml-cpp/src/exp.cpp ${PROJECT_SOURCE_DIR}/submodules/yaml-cpp/src/memory.cpp ${PROJECT_SOURCE_DIR}/submodules/yaml-cpp/src/node.cpp ${PROJECT_SOURCE_DIR}/submodules/yaml-cpp/src/node_data.cpp ${PROJECT_SOURCE_DIR}/submodules/yaml-cpp/src/nodebuilder.cpp ${PROJECT_SOURCE_DIR}/submodules/yaml-cpp/src/nodeevents.cpp ${PROJECT_SOURCE_DIR}/submodules/yaml-cpp/src/null.cpp ${PROJECT_SOURCE_DIR}/submodules/yaml-cpp/src/ostream_wrapper.cpp ${PROJECT_SOURCE_DIR}/submodules/yaml-cpp/src/parse.cpp ${PROJECT_SOURCE_DIR}/submodules/yaml-cpp/src/parser.cpp ${PROJECT_SOURCE_DIR}/submodules/yaml-cpp/src/regex_yaml.cpp ${PROJECT_SOURCE_DIR}/submodules/yaml-cpp/src/scanner.cpp ${PROJECT_SOURCE_DIR}/submodules/yaml-cpp/src/scanscalar.cpp ${PROJECT_SOURCE_DIR}/submodules/yaml-cpp/src/scantag.cpp ${PROJECT_SOURCE_DIR}/submodules/yaml-cpp/src/scantoken.cpp ${PROJECT_SOURCE_DIR}/submodules/yaml-cpp/src/simplekey.cpp ${PROJECT_SOURCE_DIR}/submodules/yaml-cpp/src/singledocparser.cpp ${PROJECT_SOURCE_DIR}/submodules/yaml-cpp/src/stream.cpp ${PROJECT_SOURCE_DIR}/submodules/yaml-cpp/src/tag.cpp ) target_sources(hyde PRIVATE ${SRC_SOURCES} ${SRC_EMITTERS} ${SRC_MATCHERS} ${SRC_YAMLCPP} ) source_group(sources FILES ${SRC_SOURCES}) source_group(emitters FILES ${SRC_EMITTERS}) source_group(matchers FILES ${SRC_MATCHERS}) source_group(yaml-cpp FILES ${SRC_YAMLCPP}) target_include_directories(hyde PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/include ${PROJECT_SOURCE_DIR}/submodules/yaml-cpp/include/ ${PROJECT_SOURCE_DIR}/submodules/json/include/ ${llvm_SOURCE_DIR}/clang/include ${llvm_BINARY_DIR}/tools/clang/include ${llvm_SOURCE_DIR}/llvm/include ${llvm_BINARY_DIR}/include ${diff_SOURCE_DIR}/include ) target_compile_options(hyde PUBLIC -Wall -Wno-comment -Werror -Wno-range-loop-analysis ) if (NOT LLVM_ENABLE_RTTI) target_compile_options(hyde PRIVATE -fno-rtti) endif() target_link_libraries(hyde clang clangAST clangASTMatchers clangBasic clangFrontend clangLex clangTooling ) if (PROJECT_IS_TOP_LEVEL) set_target_properties(hyde PROPERTIES XCODE_GENERATE_SCHEME ON) endif() ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Adobe Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at Grp-opensourceoffice@adobe.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing Thanks for choosing to contribute! The following are a set of guidelines to follow when contributing to this project. ## Code Of Conduct This project adheres to the Adobe [code of conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to [Grp-opensourceoffice@adobe.com](mailto:Grp-opensourceoffice@adobe.com). ## Have A Question? Start by filing an issue. The existing committers on this project work to reach consensus around project direction and issue solutions within issue threads (when appropriate). ## Contributor License Agreement All third-party contributions to this project must be accompanied by a signed contributor license agreement. This gives Adobe permission to redistribute your contributions as part of the project. [Sign our CLA](http://opensource.adobe.com/cla.html). You only need to submit an Adobe CLA one time, so if you have submitted one previously, you are good to go! ## Code Reviews All submissions should come in the form of pull requests and need to be reviewed by project committers. Read [GitHub's pull request documentation](https://help.github.com/articles/about-pull-requests/) for more information on sending pull requests. ## From Contributor To Committer We love contributions from our community! If you'd like to go a step beyond contributor and become a committer with full write access and a say in the project, you must be invited to the project. The existing committers employ an internal nomination process that must reach lazy consensus (silence is approval) before invitations are issued. If you feel you are qualified and want to get more deeply involved, feel free to reach out to existing committers to have a conversation about that. ## Security Issues Security issues shouldn't be reported on this issue tracker. Instead, [file an issue to our security experts](https://helpx.adobe.com/security/alertus.html) ================================================ FILE: Dockerfile ================================================ FROM --platform=linux/x86_64 ubuntu:latest RUN apt-get -y update && apt-get install -y RUN apt-get -y install curl gnupg2 software-properties-common ninja-build apt-utils make RUN apt-get -y install wget RUN apt-get -y install git # Are the build-essential packages needed? This elminates a CMake error # about /usr/bin/c++ not being found but seems like overkill. RUN apt-get -y install build-essential # Install llvm/clang # This is nesessary because of an issue with the hyde resource-dir. The # version of clang installed must exactly match the version of clang used # to build hyde. This is a temporary fix until hyde installs the necessary # resource directory and encodes the path in the binary. # If you get an error message about stddef.h or size_t not being found, # the issue is here. Check where hyde is looking for it's resoruce # directory with # `hyde ./test.hpp -- -x c++ -print-resource-dir` # FROM base AS full ENV LLVM_VERSION=15 RUN apt-get -y install clang-${LLVM_VERSION} RUN apt-get -y install libc++-${LLVM_VERSION}-dev # set clang ${LLVM_VERSION} to be the version of clang we use when clang/clang++ is invoked RUN update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-${LLVM_VERSION} 100 RUN update-alternatives --install /usr/bin/clang clang /usr/bin/clang-${LLVM_VERSION} 100 ADD https://cmake.org/files/v3.24/cmake-3.24.0-linux-x86_64.sh /cmake-3.24.0-linux-x86_64.sh RUN mkdir /opt/cmake RUN sh /cmake-3.24.0-linux-x86_64.sh --prefix=/opt/cmake --skip-license RUN ln -s /opt/cmake/bin/cmake /usr/local/bin/cmake #install hyde dependencies RUN apt-get -y install libyaml-cpp-dev libboost-system-dev libboost-filesystem-dev COPY . /usr/src/hyde # build hyde and run the generate_test_files WORKDIR /usr/src/hyde RUN mkdir -p build \ && cd build \ && rm -rf * \ && cmake .. -GNinja -DCMAKE_BUILD_TYPE=Release \ && ninja # install hyde RUN cp ./build/hyde /usr/bin # RUN apt-get -y install clang-15 CMD ["./generate_test_files.sh"] # Experimenting with publishing the container and linking it to the hyde repo: LABEL org.opencontainers.image.source=https://github.com/adobe/hyde ================================================ FILE: LICENSE ================================================ MIT License © Copyright 2018 Adobe. All rights reserved. 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. ================================================ FILE: README.md ================================================ # What is `hyde` `hyde` is a utility that facilitates documenting C++. The tool is unique from existing documentation utilities in the following ways: - **Clang based**: In order to properly document your C++, `hyde` compiles it using Clang's excellent [libTooling](https://clang.llvm.org/docs/LibTooling.html) library. Therefore, as the C++ language evolves, so too will `hyde`. - **Out-of-line**: Many tools rely on documentation placed inline within source as long-form comments. While these seem appealing at first blush, they suffer from two big drawbacks. First, there is nothing keeping the comments from falling out of sync from the elements they document. Secondly (and ironically), experienced users of these libraries eventually find inline documentation to be more of a distraction than a help, cluttering code with comments they no longer read. - **Jekyll compatible**: `hyde` does not produce pretty-printed output. Rather, it produces well structured Markdown files that contain YAML front-matter. These files can then be consumed by other tools (like Jekyll) to customize the structure and layout of the final documentation. - **Schema enforcement**: Because of the highly structured nature of the output, `hyde` is able to compare pre-existing documentation files against the current state of your C++ sources. Library developers can use `hyde`'s _update_ mode to facilitate updating documentation against the state of sources. Build engineers can use `hyde`'s _validate_ mode to make sure changes to a code base are accurately reflected in the latest documentation. In the end, the documentation stays true to the code with minimal effort. - **Adaptable**: While `hyde`'s primary purpose at this point is to output and enforce documentation, the tool can also be used to output AST-based information about your code as a JSON-based IR. This makes room for additional tools to be build atop what `hyde` is able to produce, or additional emitters can be added natively to the tool. # Example Output `hyde` produces intermediate documentation files that the developer then fills in with additional details as necessary. The files are then fed through a static site generation tool (like Jekyll) to produce [output like this](https://stlab.cc/includes/stlab/copy_on_write.hpp/copy_on_write3CT3E/). # Requirements ## macOS - Homebrew - `brew install cmake` - `brew install ninja` (optional) ## Linux (Note: only tested on ubuntu bionic so far) - Apt - `sudo apt-get install libyaml-cpp-dev` # How to Build - clone this repo - `cd hyde` - `git submodule update --init` - `mkdir build` - `cd build` - `cmake .. -GNinja` (or `-GXcode`, etc.) - `ninja` (or whatever your IDE does) LLVM/Clang are declared as a dependency in the project's `CMakeLists.txt` file, and will be downloaded and made available to the project automatically. # How to run from Docker ```sh docker pull ghcr.io/adobe/hyde:latest docker run --platform linux/x86_64 --mount type=bind,source="$(pwd)",target=/mnt/host \ --tty --interactive \ ghcr.io/adobe/hyde:latest bash ``` You can then run the examples as below, except don't prefix `hyde` with `./`. # Building the Docker image You may need to increase your docker resources to build the image. (2.0.1 successfully built with 16GB RAM and 4GB swap) ```sh docker build --tag hyde . docker run --platform linux/x86_64 --mount type=bind,source="$(pwd)",target=/mnt/host \ --tty --interactive \ hyde bash ``` # Publishing the docker image (requires write access to the `adobe` GitHub organization) Instructions for publishing a GitHub package can be found [here](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry). Instructions for associating the with the `adobe/hyde` repository can be found [here](https://docs.github.com/en/packages/learn-github-packages/connecting-a-repository-to-a-package#connecting-a-repository-to-a-container-image-using-the-command-line). ```sh VERSION=2.0.1 docker tag hyde ghcr.io/adobe/hyde:$VERSION docker tag hyde ghcr.io/adobe/hyde:latest docker push ghcr.io/adobe/hyde:$VERSION docker push ghcr.io/adobe/hyde:latest ``` # Parameters and Flags There are several modes under which the tool can run: - `-hyde-json` - (default) Output an analysis dump of the input file as JSON - `-hyde-validate` - Validate existing YAML documentation - `-hyde-update` - Write updated YAML documentation - `-hyde-src-root = ` - The root path to the header file(s) being analyzed. Affects `defined_in_file` output values by taking out the root path. - `-hyde-yaml-dir = ` - Root directory for YAML validation / update. Required for either hyde-validate or hyde-update modes. - `--use-system-clang` - Autodetect and use necessary resource directories and include paths - `--fixup-hyde-subfield` - As of Hyde v0.1.5, all hyde fields are under a top-level `hyde` subfield in YAML output. This flag will update older hyde documentation that does not have this subfield by creating it, then moving all top-level fields except `title` and `layout` under it. This flag is intended to be used only once during the migration of older documentation from the non-subfield structure to the subfield structure. This tool parses the passed header using Clang. To pass arguments to the compiler (e.g., include directories), append them after the `--` token on the command line. For example: hyde input_file.hpp -hyde-json -use-system-clang -- -x c++ -I/path/to/includes Alternatively, if you have a compilation database and would like to pass that instead of command-line compiler arguments, you can pass that with `-p`. While compiling the source file, the non-function macro `ADOBE_TOOL_HYDE` is defined to the value `1`. This can be useful to explicitly omit code from the documentation. # Examples: To output JSON: ```./hyde -use-system-clang ../test_files/classes.cpp --``` To validate pre-existing YAML: ```./hyde -use-system-clang -hyde-yaml-dir=/path/to/output -hyde-validate ../test_files/classes.cpp``` To output updated YAML: ```./hyde -use-system-clang -hyde-yaml-dir=/path/to/output -hyde-update ../test_files/classes.cpp``` # Hyde 1 to Hyde 2 Format Conversion As of the Hyde 2 work, all subfields in the YAML output (except the Jekyll-required `layout` and `title` fields) must go under a top-level `hyde` subfield. This allows for other tools to include additional (possibly same-named) fields under their own top-level subfields in the YAML. Here is an example of updating from Hyde 1 to Hyde 2 formatted docs by scanning a directory for markdown-formatted files and passing them to `hyde` with the new `-hyde-fixup-subfield` mode: find . -name '*.md' | xargs -I % -L 1 /path/to/hyde -hyde-fixup-subfield % -- # Sass Updates Sometimes it may be necessary to clean up or "lint" the sass files. You can do so with: bundle exec sass-convert -i /path/to/file.scss ================================================ FILE: docs/Gemfile ================================================ source "https://rubygems.org" # Manage our dependency on the version of the github-pages gem here. gem "github-pages", "= 228" # Explicitly include this gem here. # It is not directly included in the github-pages gem list of dependencies, # even though it is included in the original GitHub Pages build infrastructure. gem "jekyll-include-cache", "= 0.2.1" gem "jekyll-octicons", "~> 14.2" ================================================ FILE: docs/_config.yml ================================================ # Welcome to Jekyll! # # This config file is meant for settings that affect your whole blog, values # which you are expected to set up once and rarely need to edit after that. # For technical reasons, this file is *NOT* reloaded automatically when you use # 'jekyll serve'. If you change this file, please restart the server process. # Site settings title: Hyde Example Docs subtitle: Website showing a possible Jekyll-formatted hyde output. email: noreply@adobe.com baseurl: /hyde url: "https://opensource.adobe.com/" # the base hostname & protocol for your site repository: adobe/hyde remote_theme: adobe/hyde-theme@v2.0.0 exclude: - "*.sh" - vendor # For Travis-CI (see https://jekyllrb.com/docs/continuous-integration/) adobe_hyde: header_image: /images/site/hyde.svg # Build settings markdown: kramdown plugins: - jekyll-redirect-from - jekyll-remote-theme ================================================ FILE: docs/_includes/head.html ================================================ {% if page.title %}{{ page.title }}{% else %}{{ site.title }}{% endif %} ================================================ FILE: docs/_posts/2018-10-30-welcome.markdown ================================================ --- layout: post title: Welcome! excerpt_separator: --- This is the landing page for the `hyde` sample files example site. Poke around in the `Libraries` section, above. ================================================ FILE: docs/_sass/_overrides-dark.scss ================================================ // Copy this file to you site and use it to override CSS variables that apply to the dark CSS // definitions. See `main-dark.scss` in the theme for a complete list of variables you can override // with this file. // @debug "Old link-color: #{$link-color}"; // $hyde-primary: $stlab-purple; // $link-color: $stlab-purple; // @debug "New link-color: #{$link-color}"; ================================================ FILE: docs/_sass/_overrides-light.scss ================================================ // Copy this file to you site and use it to override CSS variables that apply to the light CSS // definitions. See `main-light.scss` in the theme for a complete list of variables you can override // with this file. // @debug "Old hyde-primary: #{$link-color}"; // $hyde-primary: $stlab-purple; // $link-color: $stlab-purple; // @debug "New link-color: #{$link-color}"; ================================================ FILE: docs/_sass/_overrides.scss ================================================ // Copy this file to you site and use it to override CSS variables that apply to both the light and // dark CSS definitions, such as font families, content widths, etc. See `_main.scss` in the theme // for a complete list of variables you can override with this file. // @debug "Old base-font-family: #{$base-font-family}"; // $base-font-family: "Georgia"; // @debug "New base-font-family: #{$base-font-family}"; ================================================ FILE: docs/about.md ================================================ --- layout: page title: About tab: About permalink: /about/ --- This is the about page. There are many like it, but this one is mine. ================================================ FILE: docs/feed.xml ================================================ --- layout: null --- {{ site.title | xml_escape }} {{ site.description | xml_escape }} {{ site.url }}{{ site.baseurl }}/ {{ site.time | date_to_rfc822 }} {{ site.time | date_to_rfc822 }} Jekyll v{{ jekyll.version }} {% for post in site.posts limit:10 %} {{ post.title | xml_escape }} {{ post.content | xml_escape }} {{ post.date | date_to_rfc822 }} {{ post.url | prepend: site.baseurl | prepend: site.url }} {{ post.url | prepend: site.baseurl | prepend: site.url }} {% for tag in post.tags %} {{ tag | xml_escape }} {% endfor %} {% for cat in post.categories %} {{ cat | xml_escape }} {% endfor %} {% endfor %} ================================================ FILE: docs/index.html ================================================ --- layout: default ---
Libraries

Posts

    {% for post in site.posts %}
  • {{ post.title }}

    {{ post.excerpt }}
  • {% endfor %}

subscribe via RSS

================================================ FILE: docs/libraries/classes.cpp/class_example/color.md ================================================ --- layout: enumeration title: color hyde: owner: __INLINED__ brief: __INLINED__ tags: - enumeration inline: brief: an example enumeration within the class. description: - Note that nested classes will not inherit their ownership from the class that contains them, so thus need their own `hyde-owner`. owner: sean-parent defined_in_file: classes.cpp values: - description: __INLINED__ inline: description: - this is an example description for the `red` value. name: red - description: __INLINED__ inline: description: - this is an example description for the `green` value. name: green - description: __INLINED__ inline: description: - this is an example description for the `blue` value. name: blue --- ================================================ FILE: docs/libraries/classes.cpp/class_example/index.md ================================================ --- layout: class title: class_example hyde: owner: __INLINED__ brief: __INLINED__ tags: - class inline: brief: an example class that demonstrates what Hyde documents. owner: fosterbrereton defined_in_file: classes.cpp declaration: "\nclass class_example;" dtor: unspecified typedefs: typedef_example: definition: std::string description: __INLINED__ inline: description: - a nested `typedef` expression. using_example: definition: std::string description: __INLINED__ inline: description: - a nested `using` expression. fields: _deprecated_member: annotation: - private - deprecated("example deprecation message") description: __INLINED__ inline: description: - a deprecated member variable that contains a message. Apparently this works?! type: int _nested: annotation: - private description: __INLINED__ inline: description: - an instance of the nested class example defined earlier. type: class_example::nested_class_example _static_member: description: __INLINED__ inline: description: - static member variable. type: const int _x: annotation: - private description: __INLINED__ inline: description: - some variable that holds an integer. type: int --- ================================================ FILE: docs/libraries/classes.cpp/class_example/m_class_example.md ================================================ --- layout: method title: class_example hyde: owner: __INLINED__ brief: __INLINED__ tags: - method inline: brief: _multiple descriptions_ owner: fosterbrereton defined_in_file: classes.cpp is_ctor: true overloads: class_example(): annotation: - defaulted description: __INLINED__ inline: description: - default constructor. signature_with_names: class_example() explicit class_example(int): arguments: - description: __OPTIONAL__ name: x type: int description: __INLINED__ inline: arguments: x: description: The one integer parameter this routine takes description: - an explicit constructor that takes a single `int`. signature_with_names: explicit class_example(int x) --- ================================================ FILE: docs/libraries/classes.cpp/class_example/m_deprecated.md ================================================ --- layout: method title: deprecated hyde: owner: __INLINED__ brief: __INLINED__ tags: - method inline: brief: - deprecated member function. owner: fosterbrereton defined_in_file: classes.cpp overloads: void deprecated(const std::string &, class_example *): annotation: - deprecated arguments: - description: __OPTIONAL__ name: first type: const std::string & - description: __OPTIONAL__ name: second type: class_example * description: __INLINED__ inline: arguments: first: description: the first parameter second: description: the second parameter description: - deprecated member function. return: __OPTIONAL__ signature_with_names: void deprecated(const std::string & first, class_example * second) --- ================================================ FILE: docs/libraries/classes.cpp/class_example/m_deprecated_with_message.md ================================================ --- layout: method title: deprecated_with_message hyde: owner: __INLINED__ brief: __INLINED__ tags: - method inline: brief: - deprecated member function that contains a compile-time deprecation message. owner: fosterbrereton defined_in_file: classes.cpp overloads: void deprecated_with_message(const std::string &, class_example *): annotation: - deprecated("example deprecation message") arguments: - description: __OPTIONAL__ name: s type: const std::string & - description: __OPTIONAL__ name: f type: class_example * description: __INLINED__ inline: arguments: f: description: the second parameter s: description: the first parameter description: - deprecated member function that contains a compile-time deprecation message. return: __OPTIONAL__ signature_with_names: void deprecated_with_message(const std::string & s, class_example * f) --- ================================================ FILE: docs/libraries/classes.cpp/class_example/m_member_function.md ================================================ --- layout: method title: member_function hyde: owner: __INLINED__ brief: __INLINED__ tags: - method inline: brief: - example member function. owner: fosterbrereton defined_in_file: classes.cpp overloads: int member_function(): description: __INLINED__ inline: description: - example member function. return: double the value of `_x`. return: __OPTIONAL__ signature_with_names: int member_function() --- ================================================ FILE: docs/libraries/classes.cpp/class_example/m_member_function_trailing_return_type.md ================================================ --- layout: method title: member_function_trailing_return_type hyde: owner: __INLINED__ brief: __INLINED__ tags: - method inline: brief: - member function with a trailing return type. owner: fosterbrereton defined_in_file: classes.cpp overloads: auto member_function_trailing_return_type() const -> int: description: __INLINED__ inline: description: - member function with a trailing return type. return: "\"`_x`\"" return: __OPTIONAL__ signature_with_names: auto member_function_trailing_return_type() const -> int --- ================================================ FILE: docs/libraries/classes.cpp/class_example/m_overloaded.md ================================================ --- layout: method title: overloaded hyde: owner: __INLINED__ brief: __INLINED__ tags: - method inline: brief: _multiple descriptions_ owner: fosterbrereton defined_in_file: classes.cpp overloads: void overloaded(): annotation: - deprecated description: __INLINED__ inline: description: - a deprecated overload that takes zero parameters. return: __OPTIONAL__ signature_with_names: void overloaded() void overloaded(const std::string &): arguments: - description: __OPTIONAL__ name: first type: const std::string & description: __INLINED__ inline: arguments: first: description: the first parameter of the first overload. brief: A series of overloaded functions. description: - an overloaded member function that takes one parameter. return: __OPTIONAL__ signature_with_names: void overloaded(const std::string & first) void overloaded(const std::string &, class_example *, int): arguments: - description: __OPTIONAL__ name: unnamed-0 type: const std::string & unnamed: true - description: __OPTIONAL__ name: unnamed-1 type: class_example * unnamed: true - description: __OPTIONAL__ name: unnamed-2 type: int unnamed: true description: __INLINED__ inline: description: - an overloaded member function that takes three unnamed parameters. Let it be known that Doxygen doesn't support documenting unnamed parameters at this time. There is a [bug open on the issue](https://github.com/doxygen/doxygen/issues/6926), but as of this writing does not appear to be progressing. return: __OPTIONAL__ signature_with_names: void overloaded(const std::string &, class_example *, int) void overloaded(const std::string &, class_example *, int, bool, std::size_t): arguments: - description: __OPTIONAL__ name: first type: const std::string & - description: __OPTIONAL__ name: second type: class_example * - description: __OPTIONAL__ name: third type: int - description: __OPTIONAL__ name: fourth type: bool - description: __OPTIONAL__ name: fifth type: std::size_t description: __INLINED__ inline: arguments: fifth: description: the fifth parameter of the fourth overload. first: description: the first parameter of the fourth overload. fourth: description: the fourth parameter of the fourth overload. second: description: the second parameter of the fourth overload. third: description: the third parameter of the fourth overload. description: - an overloaded member function that takes _five_ parameters. return: __OPTIONAL__ signature_with_names: void overloaded(const std::string & first, class_example * second, int third, bool fourth, std::size_t fifth) void overloaded(const std::string &, const std::string &) volatile: arguments: - description: __OPTIONAL__ name: first type: const std::string & - description: __OPTIONAL__ name: second type: const std::string & description: __INLINED__ inline: arguments: first: description: the first parameter of the second overload. second: description: the second parameter of the second overload. brief: Another brief describing one of the overloaded functions. description: - an overloaded member function that takes two parameters. return: __OPTIONAL__ signature_with_names: void overloaded(const std::string & first, const std::string & second) volatile void overloaded(const std::string &, std::vector) const: arguments: - description: __OPTIONAL__ name: first type: const std::string & - description: __OPTIONAL__ name: second type: std::vector description: __INLINED__ inline: arguments: first: description: the first parameter of the third overload. second: description: the second parameter of the third overload. description: - another overloaded member function that takes two parameters. return: __OPTIONAL__ signature_with_names: void overloaded(const std::string & first, std::vector second) const --- ================================================ FILE: docs/libraries/classes.cpp/class_example/m_static_method.md ================================================ --- layout: method title: static_method hyde: owner: __INLINED__ brief: __INLINED__ tags: - method inline: brief: static member function. owner: fosterbrereton defined_in_file: classes.cpp overloads: static int static_method(): description: __OPTIONAL__ inline: brief: static member function. return: Zero. By which I mean `0`. In the sources, this comment is on multiple lines. return: __OPTIONAL__ signature_with_names: static int static_method() --- ================================================ FILE: docs/libraries/classes.cpp/class_example/m_template_member_function.md ================================================ --- layout: method title: template_member_function hyde: owner: __INLINED__ brief: __INLINED__ tags: - method inline: brief: _multiple descriptions_ owner: fosterbrereton defined_in_file: classes.cpp overloads: "template \nvoid template_member_function()": description: __INLINED__ inline: description: - templatized member function. return: __OPTIONAL__ signature_with_names: "template \nvoid template_member_function()" void template_member_function(): description: __INLINED__ inline: description: - specialization of the above templatized member function. return: __OPTIONAL__ signature_with_names: void template_member_function() --- ================================================ FILE: docs/libraries/classes.cpp/class_example/nested_class_example/index.md ================================================ --- layout: class title: nested_class_example hyde: owner: __INLINED__ brief: __INLINED__ tags: - class inline: brief: a class definition contained within `example_class`. description: - Note that nested classes will not inherit their ownership from the class that contains them, so thus need their own `hyde-owner`. owner: sean-parent defined_in_file: classes.cpp declaration: "\nstruct class_example::nested_class_example;" ctor: unspecified dtor: unspecified fields: _x: description: __INLINED__ inline: description: - member field `_x` within the nested class example. type: int _y: description: __INLINED__ inline: description: - member field `_y` within the nested class example. type: int --- ================================================ FILE: docs/libraries/classes.cpp/class_example/nested_class_example/m_nested_class_example.md ================================================ --- layout: method title: nested_class_example hyde: owner: __INLINED__ brief: __OPTIONAL__ tags: - method inline: owner: sean-parent defined_in_file: classes.cpp is_ctor: true overloads: nested_class_example(): annotation: - implicit description: __OPTIONAL__ signature_with_names: nested_class_example() nested_class_example(class_example::nested_class_example &&): annotation: - implicit arguments: - description: __OPTIONAL__ name: unnamed-0 type: class_example::nested_class_example && unnamed: true description: __OPTIONAL__ signature_with_names: nested_class_example(class_example::nested_class_example &&) nested_class_example(const class_example::nested_class_example &): annotation: - implicit arguments: - description: __OPTIONAL__ name: unnamed-0 type: const class_example::nested_class_example & unnamed: true description: __OPTIONAL__ signature_with_names: nested_class_example(const class_example::nested_class_example &) --- ================================================ FILE: docs/libraries/classes.cpp/class_example/nested_class_example/m_~nested_class_example.md ================================================ --- layout: method title: ~nested_class_example hyde: owner: __INLINED__ brief: __OPTIONAL__ tags: - method inline: owner: sean-parent defined_in_file: classes.cpp is_dtor: true overloads: ~nested_class_example(): annotation: - implicit description: __OPTIONAL__ signature_with_names: ~nested_class_example() --- ================================================ FILE: docs/libraries/classes.cpp/index.md ================================================ --- layout: library title: classes.cpp hyde: owner: __MISSING__ brief: __MISSING__ tags: - sourcefile library-type: sourcefile --- ================================================ FILE: docs/libraries/classes.cpp/partial_specialization_.4b7bfe45/index.md ================================================ --- layout: class title: partial_specialization_example hyde: owner: __INLINED__ brief: __INLINED__ tags: - class inline: brief: an example `int, T` partial specialization owner: fosterbrereton defined_in_file: classes.cpp declaration: "\nclass partial_specialization_example;" ctor: unspecified dtor: unspecified fields: _first: annotation: - private description: __MISSING__ type: std::string _second: annotation: - private description: __MISSING__ type: T --- ================================================ FILE: docs/libraries/classes.cpp/partial_specialization_.edfbc14d/index.md ================================================ --- layout: class title: partial_specialization_example hyde: owner: __INLINED__ brief: __INLINED__ tags: - class inline: brief: an example template class with a partial specialization. owner: fosterbrereton defined_in_file: classes.cpp declaration: "template \nclass partial_specialization_example;" ctor: unspecified dtor: unspecified fields: _first: annotation: - private description: __MISSING__ type: T1 _second: annotation: - private description: __MISSING__ type: T2 --- ================================================ FILE: docs/libraries/classes.cpp/specialization_example3.ed9d8cc7/index.md ================================================ --- layout: class title: specialization_example hyde: owner: __INLINED__ brief: __INLINED__ tags: - class inline: brief: an example `std::int32` specialization owner: fosterbrereton defined_in_file: classes.cpp declaration: "\nstruct specialization_example;" ctor: unspecified dtor: unspecified typedefs: value_type: definition: std::int32_t description: __INLINED__ inline: description: - An example typedef fields: _first: description: __INLINED__ inline: description: - An example field used in `as_tuple` type: specialization_example::value_type --- ================================================ FILE: docs/libraries/classes.cpp/specialization_example3.ed9d8cc7/m_as_tuple.md ================================================ --- layout: method title: as_tuple hyde: owner: __INLINED__ brief: __INLINED__ tags: - method inline: brief: - An example function owner: fosterbrereton defined_in_file: classes.cpp overloads: constexpr tuple as_tuple() const: description: __INLINED__ inline: description: - An example function return: a tuple of the fields of this class return: __OPTIONAL__ signature_with_names: constexpr tuple as_tuple() const --- ================================================ FILE: docs/libraries/classes.cpp/specialization_example3CT3E/index.md ================================================ --- layout: class title: specialization_example hyde: owner: __INLINED__ brief: __INLINED__ tags: - class inline: brief: an example template class with some specializations owner: fosterbrereton defined_in_file: classes.cpp declaration: "template \nstruct specialization_example;" ctor: unspecified dtor: unspecified --- ================================================ FILE: docs/libraries/classes.cpp/specialization_example3CT3E/m_as_tuple.md ================================================ --- layout: method title: as_tuple hyde: owner: __INLINED__ brief: __MISSING__ tags: - method inline: owner: fosterbrereton defined_in_file: classes.cpp overloads: constexpr auto as_tuple() const: description: __OPTIONAL__ return: __OPTIONAL__ signature_with_names: constexpr auto as_tuple() const --- ================================================ FILE: docs/libraries/classes.cpp/specialization_example3Cfloat3E/index.md ================================================ --- layout: class title: specialization_example hyde: owner: __INLINED__ brief: __INLINED__ tags: - class inline: brief: an example `float` specialization owner: fosterbrereton defined_in_file: classes.cpp declaration: "\nstruct specialization_example;" ctor: unspecified dtor: unspecified typedefs: value_type: definition: float description: __MISSING__ fields: _first: description: __INLINED__ inline: description: - An example field used in `as_tuple` type: specialization_example::value_type --- ================================================ FILE: docs/libraries/classes.cpp/specialization_example3Cfloat3E/m_as_tuple.md ================================================ --- layout: method title: as_tuple hyde: owner: __INLINED__ brief: __MISSING__ tags: - method inline: owner: fosterbrereton defined_in_file: classes.cpp overloads: constexpr tuple as_tuple() const: description: __OPTIONAL__ return: __OPTIONAL__ signature_with_names: constexpr tuple as_tuple() const --- ================================================ FILE: docs/libraries/comments.cpp/compiler_generated/index.md ================================================ --- layout: class title: compiler_generated hyde: owner: __INLINED__ brief: __INLINED__ tags: - class inline: brief: Sample class intended to exhibit docs for compiler-generated routines owner: fosterbrereton defined_in_file: comments.cpp declaration: "\nstruct compiler_generated;" dtor: unspecified --- ================================================ FILE: docs/libraries/comments.cpp/compiler_generated/m_assign.md ================================================ --- layout: method title: assign hyde: owner: __INLINED__ brief: __INLINED__ tags: - method inline: brief: - This definition will force the compiler to auto-generate this class' assignment operator. owner: fosterbrereton defined_in_file: comments.cpp overloads: void assign(const compiler_generated &): arguments: - description: __OPTIONAL__ name: rhs type: const compiler_generated & description: __INLINED__ inline: description: - This definition will force the compiler to auto-generate this class' assignment operator. return: __OPTIONAL__ signature_with_names: void assign(const compiler_generated & rhs) --- ================================================ FILE: docs/libraries/comments.cpp/compiler_generated/m_compiler_generated.md ================================================ --- layout: method title: compiler_generated hyde: owner: __INLINED__ brief: __OPTIONAL__ tags: - method inline: owner: fosterbrereton defined_in_file: comments.cpp is_ctor: true overloads: compiler_generated(): annotation: - deleted description: __OPTIONAL__ signature_with_names: compiler_generated() compiler_generated(const compiler_generated &): annotation: - defaulted arguments: - description: __OPTIONAL__ name: unnamed-0 type: const compiler_generated & unnamed: true description: __OPTIONAL__ signature_with_names: compiler_generated(const compiler_generated &) --- ================================================ FILE: docs/libraries/comments.cpp/compiler_generated/m_operator3D.md ================================================ --- layout: method title: operator= hyde: owner: __INLINED__ brief: __OPTIONAL__ tags: - method inline: owner: fosterbrereton defined_in_file: comments.cpp overloads: constexpr compiler_generated & operator=(const compiler_generated &): annotation: - implicit arguments: - description: __OPTIONAL__ name: unnamed-0 type: const compiler_generated & unnamed: true description: __OPTIONAL__ return: __OPTIONAL__ signature_with_names: constexpr compiler_generated & operator=(const compiler_generated &) --- ================================================ FILE: docs/libraries/comments.cpp/f_template_function.md ================================================ --- layout: function title: template_function hyde: owner: __MISSING__ brief: __INLINED__ tags: - function inline: brief: some template function defined_in_file: comments.cpp overloads: "template \nT template_function()": description: __OPTIONAL__ inline: brief: some template function return: an instance of type `T` return: __OPTIONAL__ signature_with_names: "template \nT template_function()" --- ================================================ FILE: docs/libraries/comments.cpp/index.md ================================================ --- layout: library title: comments.cpp hyde: owner: __MISSING__ brief: __MISSING__ tags: - sourcefile library-type: sourcefile --- ================================================ FILE: docs/libraries/comments.cpp/some_other_struct/index.md ================================================ --- layout: class title: some_other_struct hyde: owner: __MISSING__ brief: __INLINED__ tags: - class inline: brief: This is a sample brief for `some_other_struct` description: - Notice how many of the comments for this structure are inherited from its superclass. defined_in_file: comments.cpp declaration: "\nstruct some_other_struct;" ctor: unspecified dtor: unspecified --- ================================================ FILE: docs/libraries/comments.cpp/some_other_struct/m_operator3D.md ================================================ --- layout: method title: operator= hyde: owner: __OPTIONAL__ brief: __OPTIONAL__ tags: - method defined_in_file: comments.cpp overloads: some_other_struct & operator=(const some_other_struct &): annotation: - implicit arguments: - description: __OPTIONAL__ name: unnamed-0 type: const some_other_struct & unnamed: true description: __OPTIONAL__ return: __OPTIONAL__ signature_with_names: some_other_struct & operator=(const some_other_struct &) some_other_struct & operator=(some_other_struct &&): annotation: - implicit arguments: - description: __OPTIONAL__ name: unnamed-0 type: some_other_struct && unnamed: true description: __OPTIONAL__ return: __OPTIONAL__ signature_with_names: some_other_struct & operator=(some_other_struct &&) --- ================================================ FILE: docs/libraries/comments.cpp/some_other_struct/m_some_other_struct.md ================================================ --- layout: method title: some_other_struct hyde: owner: __OPTIONAL__ brief: __OPTIONAL__ tags: - method defined_in_file: comments.cpp is_ctor: true overloads: some_other_struct(const some_other_struct &): annotation: - implicit arguments: - description: __OPTIONAL__ name: unnamed-0 type: const some_other_struct & unnamed: true description: __OPTIONAL__ signature_with_names: some_other_struct(const some_other_struct &) some_other_struct(some_other_struct &&): annotation: - implicit arguments: - description: __OPTIONAL__ name: unnamed-0 type: some_other_struct && unnamed: true description: __OPTIONAL__ signature_with_names: some_other_struct(some_other_struct &&) --- ================================================ FILE: docs/libraries/comments.cpp/some_other_struct/m_virtual_function.md ================================================ --- layout: method title: virtual_function hyde: owner: __MISSING__ brief: __INLINED__ tags: - method inline: brief: - A virtual function that intends to be overridden. defined_in_file: comments.cpp overloads: void virtual_function(): description: __INLINED__ inline: description: - A virtual function that intends to be overridden. return: __OPTIONAL__ signature_with_names: void virtual_function() --- ================================================ FILE: docs/libraries/comments.cpp/some_other_struct/m_~some_other_struct.md ================================================ --- layout: method title: ~some_other_struct hyde: owner: __OPTIONAL__ brief: __OPTIONAL__ tags: - method defined_in_file: comments.cpp is_dtor: true overloads: ~some_other_struct(): annotation: - implicit description: __OPTIONAL__ signature_with_names: ~some_other_struct() --- ================================================ FILE: docs/libraries/comments.cpp/some_struct/index.md ================================================ --- layout: class title: some_struct hyde: owner: __INLINED__ brief: __INLINED__ tags: - class inline: brief: This is a sample brief. description: - An example struct from which these commands will hang. owner: fosterbrereton par: header This is a sample paragraph. see: "[Slides](https://llvm.org/devmtg/2012-11/Gribenko_CommentParsing.pdf) from an LLVM dev meeting chat on the comment parsing feature" warning: This is a sample warning. defined_in_file: comments.cpp declaration: "\nstruct some_struct;" ctor: unspecified fields: _x: description: __INLINED__ inline: description: - A trailing comment that documents `_x`. type: int --- ================================================ FILE: docs/libraries/comments.cpp/some_struct/m_operator3D.md ================================================ --- layout: method title: operator= hyde: owner: __INLINED__ brief: __OPTIONAL__ tags: - method inline: owner: fosterbrereton defined_in_file: comments.cpp overloads: some_struct & operator=(const some_struct &): annotation: - implicit arguments: - description: __OPTIONAL__ name: unnamed-0 type: const some_struct & unnamed: true description: __OPTIONAL__ return: __OPTIONAL__ signature_with_names: some_struct & operator=(const some_struct &) --- ================================================ FILE: docs/libraries/comments.cpp/some_struct/m_some_function.md ================================================ --- layout: method title: some_function hyde: owner: __INLINED__ brief: __INLINED__ tags: - method inline: brief: A function that does a thing, and does it well. owner: fosterbrereton defined_in_file: comments.cpp overloads: int some_function(int, int &, int &): arguments: - description: __OPTIONAL__ name: input type: int - description: __OPTIONAL__ name: input_output type: int & - description: __OPTIONAL__ name: output type: int & description: __INLINED__ inline: arguments: input: description: an input parameter direction: in input_output: description: a bidirectional parameter direction: inout output: description: an output parameter direction: out brief: A function that does a thing, and does it well. description: - This is a longer description of this function that does things as well as it does. Notice how long this comment is! So impressive. 💥 post: An example postcondition. pre: An example precondition. return: Some additional value. throw: "`std::runtime_error` if the function actually _can't_ do the thing. Sorry!" todo: This really could use some cleanup. Although, its implementation doesn't exist... warning: This function may be very expensive to run. Do not call it inside a loop. return: __OPTIONAL__ signature_with_names: int some_function(int input, int & input_output, int & output) --- ================================================ FILE: docs/libraries/comments.cpp/some_struct/m_some_struct.md ================================================ --- layout: method title: some_struct hyde: owner: __INLINED__ brief: __OPTIONAL__ tags: - method inline: owner: fosterbrereton defined_in_file: comments.cpp is_ctor: true overloads: some_struct(const some_struct &): annotation: - implicit arguments: - description: __OPTIONAL__ name: unnamed-0 type: const some_struct & unnamed: true description: __OPTIONAL__ signature_with_names: some_struct(const some_struct &) --- ================================================ FILE: docs/libraries/comments.cpp/some_struct/m_virtual_function.md ================================================ --- layout: method title: virtual_function hyde: owner: __INLINED__ brief: __INLINED__ tags: - method inline: brief: - A virtual function that intends to be overridden. owner: fosterbrereton defined_in_file: comments.cpp overloads: void virtual_function(): description: __INLINED__ inline: description: - A virtual function that intends to be overridden. return: __OPTIONAL__ signature_with_names: void virtual_function() --- ================================================ FILE: docs/libraries/comments.cpp/some_struct/m_~some_struct.md ================================================ --- layout: method title: ~some_struct hyde: owner: __INLINED__ brief: __OPTIONAL__ tags: - method inline: owner: fosterbrereton defined_in_file: comments.cpp is_dtor: true overloads: ~some_struct(): annotation: - deleted description: __OPTIONAL__ signature_with_names: ~some_struct() --- ================================================ FILE: docs/libraries/enums.cpp/color_channel.md ================================================ --- layout: enumeration title: color_channel hyde: owner: __INLINED__ brief: __INLINED__ tags: - enumeration inline: brief: An example typed enumeration with three values. owner: fosterbrereton defined_in_file: enums.cpp values: - description: __INLINED__ inline: description: - Red commentary name: red - description: __INLINED__ inline: description: - Green commentary. Note this enum has a pre-set value. name: green - description: __INLINED__ inline: description: - Blue commentary name: blue --- ================================================ FILE: docs/libraries/enums.cpp/index.md ================================================ --- layout: library title: enums.cpp hyde: owner: __MISSING__ brief: __MISSING__ tags: - sourcefile library-type: sourcefile --- ================================================ FILE: docs/libraries/enums.cpp/untyped.md ================================================ --- layout: enumeration title: untyped hyde: owner: __INLINED__ brief: __INLINED__ tags: - enumeration inline: brief: An example untyped enumeration with three values. owner: fosterbrereton defined_in_file: enums.cpp values: - description: __INLINED__ inline: description: - Apple commentary name: apple - description: __INLINED__ inline: description: - Orange commentary name: orange - description: __INLINED__ inline: description: - Banana commentary name: banana --- ================================================ FILE: docs/libraries/functions.cpp/f_binary_function_example.md ================================================ --- layout: function title: binary_function_example hyde: owner: __MISSING__ brief: __INLINED__ tags: - function inline: brief: - an example binary function. defined_in_file: functions.cpp overloads: int binary_function_example(int, int): arguments: - description: __OPTIONAL__ name: first type: int - description: __OPTIONAL__ name: second type: int description: __INLINED__ inline: arguments: first: description: the first input second: description: the second input description: - an example binary function. return: The sum of the two input parameters. return: __OPTIONAL__ signature_with_names: int binary_function_example(int first, int second) --- ================================================ FILE: docs/libraries/functions.cpp/f_nullary_function_example.md ================================================ --- layout: function title: nullary_function_example hyde: owner: __MISSING__ brief: __INLINED__ tags: - function inline: brief: - an example nullary function. defined_in_file: functions.cpp overloads: int nullary_function_example(): description: __INLINED__ inline: description: - an example nullary function. return: "`0`" return: __OPTIONAL__ signature_with_names: int nullary_function_example() --- ================================================ FILE: docs/libraries/functions.cpp/f_overloaded.md ================================================ --- layout: function title: overloaded hyde: owner: __MISSING__ brief: __INLINED__ tags: - function inline: brief: _multiple descriptions_ defined_in_file: functions.cpp overloads: auto overloaded(int) -> float: arguments: - description: __OPTIONAL__ name: first type: int description: __INLINED__ inline: arguments: first: description: the first input parameter description: - an example unary overloaded function return: "`first`" return: __OPTIONAL__ signature_with_names: auto overloaded(int first) -> float auto overloaded(int, int) -> double: arguments: - description: __OPTIONAL__ name: first type: int - description: __OPTIONAL__ name: second type: int description: __INLINED__ inline: arguments: first: description: the first input parameter second: description: the second input parameter description: - an example binary overloaded function return: the product of `first` and `second` return: __OPTIONAL__ signature_with_names: auto overloaded(int first, int second) -> double auto overloaded(int, int, int) -> float: arguments: - description: __OPTIONAL__ name: first type: int - description: __OPTIONAL__ name: second type: int - description: __OPTIONAL__ name: third type: int description: __INLINED__ inline: arguments: first: description: the first input parameter second: description: the second input parameter third: description: the third input parameter description: - an example tertiary overloaded function return: the product of `first`, `second`, and `third` return: __OPTIONAL__ signature_with_names: auto overloaded(int first, int second, int third) -> float --- ================================================ FILE: docs/libraries/functions.cpp/f_static_auto_function_example.md ================================================ --- layout: function title: static_auto_function_example hyde: owner: __MISSING__ brief: __INLINED__ tags: - function inline: brief: - an example static function with `auto` return type defined_in_file: functions.cpp overloads: static int static_auto_function_example(): description: __INLINED__ inline: description: - an example static function with `auto` return type return: "`0`" return: __OPTIONAL__ signature_with_names: static int static_auto_function_example() --- ================================================ FILE: docs/libraries/functions.cpp/f_static_function_example.md ================================================ --- layout: function title: static_function_example hyde: owner: __MISSING__ brief: __INLINED__ tags: - function inline: brief: - an example static function defined_in_file: functions.cpp overloads: static int static_function_example(): description: __INLINED__ inline: description: - an example static function return: "`0`" return: __OPTIONAL__ signature_with_names: static int static_function_example() --- ================================================ FILE: docs/libraries/functions.cpp/f_static_trailing_type_function_example.md ================================================ --- layout: function title: static_trailing_type_function_example hyde: owner: __MISSING__ brief: __INLINED__ tags: - function inline: brief: - an example static function with trailing return type defined_in_file: functions.cpp overloads: static auto static_trailing_type_function_example() -> int: description: __INLINED__ inline: description: - an example static function with trailing return type return: "`0`" return: __OPTIONAL__ signature_with_names: static auto static_trailing_type_function_example() -> int --- ================================================ FILE: docs/libraries/functions.cpp/f_template_function_example.md ================================================ --- layout: function title: template_function_example hyde: owner: __MISSING__ brief: __INLINED__ tags: - function inline: brief: _multiple descriptions_ defined_in_file: functions.cpp overloads: int template_function_example(): description: __INLINED__ inline: description: - an example specialization of the template function example return: Forty-two return: __OPTIONAL__ signature_with_names: int template_function_example() "template \nT template_function_example()": description: __INLINED__ inline: description: - an example template function, deleted by default return: Not applicable, seeing that the default definition has been deleted. return: __OPTIONAL__ signature_with_names: "template \nT template_function_example()" --- ================================================ FILE: docs/libraries/functions.cpp/index.md ================================================ --- layout: library title: functions.cpp hyde: owner: __MISSING__ brief: __MISSING__ tags: - sourcefile library-type: sourcefile --- ================================================ FILE: docs/libraries/index.md ================================================ --- layout: library title: Hyde Sample Docs hyde: owner: fosterbrereton brief: Sample Hyde Documentation tags: - library library-type: library icon: book short_title: Sample Docs tab: Docs --- ================================================ FILE: docs/libraries/namespaces.cpp/f_function.md ================================================ --- layout: function title: function hyde: owner: __MISSING__ brief: __INLINED__ tags: - function inline: brief: _multiple descriptions_ defined_in_file: namespaces.cpp overloads: void function(): description: __INLINED__ inline: description: - function contained within namespace `foo`. return: __OPTIONAL__ signature_with_names: void function() namespace: - foo - bar - baz --- ================================================ FILE: docs/libraries/namespaces.cpp/index.md ================================================ --- layout: library title: namespaces.cpp hyde: owner: __MISSING__ brief: __MISSING__ tags: - sourcefile library-type: sourcefile --- ================================================ FILE: docs/libraries/point.cpp/index.md ================================================ --- layout: library title: point.cpp hyde: owner: __MISSING__ brief: __MISSING__ tags: - sourcefile library-type: sourcefile --- ================================================ FILE: docs/libraries/point.cpp/point3CT3E/f_operator-.md ================================================ --- layout: function title: operator- hyde: owner: __MISSING__ brief: __INLINED__ tags: - function inline: brief: - Subtraction operator. defined_in_file: point.cpp overloads: constexpr point operator-(const point &, const point &): arguments: - description: __OPTIONAL__ name: a type: const point & - description: __OPTIONAL__ name: b type: const point & description: __INLINED__ inline: arguments: a: description: The point to be subtracted from. b: description: The point to subtract. description: - Subtraction operator. return: A new point whose axis values are subtractions of the two inputs' axis values. return: __OPTIONAL__ signature_with_names: constexpr point operator-(const point & a, const point & b) --- ================================================ FILE: docs/libraries/point.cpp/point3CT3E/f_operator213D.md ================================================ --- layout: function title: operator!= hyde: owner: __MISSING__ brief: __INLINED__ tags: - function inline: brief: - Inequality operator. defined_in_file: point.cpp overloads: constexpr bool operator!=(const point &, const point &): arguments: - description: __OPTIONAL__ name: a type: const point & - description: __OPTIONAL__ name: b type: const point & description: __INLINED__ inline: description: - Inequality operator. return: "`true` iff the two points' `x` or `y` coordinates are memberwise inequal." return: __OPTIONAL__ signature_with_names: constexpr bool operator!=(const point & a, const point & b) --- ================================================ FILE: docs/libraries/point.cpp/point3CT3E/f_operator3D3D.md ================================================ --- layout: function title: operator== hyde: owner: __MISSING__ brief: __INLINED__ tags: - function inline: brief: - Equality operator. defined_in_file: point.cpp overloads: constexpr bool operator==(const point &, const point &): arguments: - description: __OPTIONAL__ name: a type: const point & - description: __OPTIONAL__ name: b type: const point & description: __INLINED__ inline: description: - Equality operator. return: "`true` iff the two points' `x` and `y` coordinates are memberwise equal." return: __OPTIONAL__ signature_with_names: constexpr bool operator==(const point & a, const point & b) --- ================================================ FILE: docs/libraries/point.cpp/point3CT3E/index.md ================================================ --- layout: class title: point hyde: owner: __MISSING__ brief: __INLINED__ tags: - class inline: brief: An example point class defined_in_file: point.cpp declaration: "template \nstruct point;" dtor: unspecified fields: x: description: __INLINED__ inline: description: - The `x` coordinate of the point. type: T y: description: __INLINED__ inline: description: - The `y` coordinate of the point. type: T --- ================================================ FILE: docs/libraries/point.cpp/point3CT3E/m_operator-3D.md ================================================ --- layout: method title: operator-= hyde: owner: __MISSING__ brief: __INLINED__ tags: - method inline: brief: - Subtraction-assignment operator. defined_in_file: point.cpp overloads: constexpr point & operator-=(const point &): arguments: - description: __OPTIONAL__ name: a type: const point & description: __INLINED__ inline: arguments: a: description: The point to subtract from this point description: - Subtraction-assignment operator. return: A reference to `this`. return: __OPTIONAL__ signature_with_names: constexpr point & operator-=(const point & a) --- ================================================ FILE: docs/libraries/point.cpp/point3CT3E/m_origin.md ================================================ --- layout: method title: origin hyde: owner: __MISSING__ brief: __INLINED__ tags: - method inline: brief: - A static routine that returns the origin point. defined_in_file: point.cpp overloads: constexpr static auto origin(): description: __INLINED__ inline: description: - A static routine that returns the origin point. return: The point `(0, 0)` return: __OPTIONAL__ signature_with_names: constexpr static auto origin() --- ================================================ FILE: docs/libraries/point.cpp/point3CT3E/m_point3CT3E.md ================================================ --- layout: method title: point hyde: owner: __MISSING__ brief: __INLINED__ tags: - method inline: brief: _multiple descriptions_ defined_in_file: point.cpp is_ctor: true overloads: point(): annotation: - defaulted description: __INLINED__ inline: description: - Default constructor of default definition. signature_with_names: point() point(T, T): arguments: - description: __OPTIONAL__ name: _x type: T - description: __OPTIONAL__ name: _y type: T description: __INLINED__ inline: arguments: _x: description: The `x` coordinate to sink. _y: description: The `y` coordinate to sink. description: - Value-based constructor that takes `x` and `y` values and sinks them into place. signature_with_names: point(T _x, T _y) --- ================================================ FILE: docs/libraries/typedef_and_alias.cpp/index.md ================================================ --- layout: library title: typedef_and_alias.cpp hyde: owner: __MISSING__ brief: __MISSING__ tags: - sourcefile library-type: sourcefile typedefs: typedef_example: definition: int description: __INLINED__ inline: description: - Example typedef expression whose underlying type is `int`. typedef_full_specialization_example: definition: using_partial_specialization_example description: __INLINED__ inline: description: - Using typedef to define another full specialization of the above partial specialization using_example: definition: int description: __INLINED__ inline: description: - Example using expression whose underlying type is `int`. using_full_specialization_example: definition: using_partial_specialization_example description: __INLINED__ inline: description: - Full specialization of the above partial specialization using_partial_specialization_example: definition: template_example description: __INLINED__ inline: description: - Partial specialization of the above `template_example` template --- ================================================ FILE: docs/libraries/typedef_and_alias.cpp/template_example3CT2C20U3E/index.md ================================================ --- layout: class title: template_example hyde: owner: __MISSING__ brief: __INLINED__ tags: - class inline: brief: Example class with two type definitions defined_in_file: typedef_and_alias.cpp declaration: "template \nstruct template_example;" ctor: unspecified dtor: unspecified typedefs: typedef_from_T: definition: T description: __INLINED__ inline: description: - Type derived from the first template parameter. using_from_U: definition: U description: __INLINED__ inline: description: - Type derived from the second template parameter. --- ================================================ FILE: docs/libraries/typedef_and_alias.cpp/template_example_instantiator/index.md ================================================ --- layout: class title: template_example_instantiator hyde: owner: __MISSING__ brief: __INLINED__ tags: - class inline: brief: Example struct that leverages type aliases defined above. defined_in_file: typedef_and_alias.cpp declaration: "\nstruct template_example_instantiator;" ctor: unspecified dtor: unspecified typedefs: typedef_full_specialization_example: definition: using_partial_specialization_example description: __INLINED__ inline: description: - Example partial specialization using `typedef` using_full_specialization_example: definition: using_partial_specialization_example description: __INLINED__ inline: description: - Example full specialization --- ================================================ FILE: docs/serve.sh ================================================ #!/bin/bash bundle exec jekyll serve --watch ================================================ FILE: emitters/yaml_base_emitter.cpp ================================================ /* Copyright 2018 Adobe All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. */ // identity #include "yaml_base_emitter.hpp" // stdc++ #include #include // yaml-cpp #include "yaml-cpp/yaml.h" // application #include "emitters/yaml_base_emitter_fwd.hpp" #include "json.hpp" #include "matchers/utilities.hpp" /**************************************************************************************************/ namespace { /**************************************************************************************************/ static const hyde::json no_json_k; static const hyde::json inline_json_k(hyde::tag_value_inlined_k); /**************************************************************************************************/ YAML::Node update_cleanup(const YAML::Node& node) { YAML::Node result; if (node.IsScalar() && node.Scalar() != hyde::tag_value_deprecated_k) { result = node; } else if (node.IsSequence()) { const auto count = node.size(); for (std::size_t i{0}; i < count; ++i) { YAML::Node subnode = update_cleanup(node[i]); if (!subnode.IsNull()) { result.push_back(std::move(subnode)); } } } else if (node.IsMap()) { for (const auto& pair : node) { const auto& key = pair.first.Scalar(); YAML::Node subnode = update_cleanup(node[key]); if (!subnode.IsNull()) { result[key] = std::move(subnode); } } } return result; } /**************************************************************************************************/ hyde::json yaml_to_json(const YAML::Node& yaml) { switch (yaml.Type()) { case YAML::NodeType::Null: { return hyde::json(); } break; case YAML::NodeType::Scalar: { return yaml.Scalar(); } break; case YAML::NodeType::Sequence: { hyde::json result = hyde::json::array(); for (std::size_t i{0}, count{yaml.size()}; i < count; ++i) { result.emplace_back(yaml_to_json(yaml[i])); } return result; } break; case YAML::NodeType::Map: { hyde::json result = hyde::json::object(); for (auto iter{yaml.begin()}, last{yaml.end()}; iter != last; ++iter) { if (!iter->first.IsScalar()) throw std::runtime_error("key is not scalar?"); result[iter->first.Scalar()] = yaml_to_json(iter->second); } return result; } break; case YAML::NodeType::Undefined: { throw std::runtime_error("YAML is not defined!"); } break; } return hyde::json(); } /**************************************************************************************************/ YAML::Node json_to_yaml(const hyde::json& json) { switch (json.type()) { case hyde::json::value_t::null: { return YAML::Node(); } break; case hyde::json::value_t::string: { return YAML::Node(static_cast(json)); } break; case hyde::json::value_t::array: { YAML::Node result; for (const auto& n : json) { result.push_back(json_to_yaml(n)); } return result; } break; case hyde::json::value_t::object: { YAML::Node result; for (auto it = json.begin(); it != json.end(); ++it) { result[it.key()] = json_to_yaml(it.value()); } return result; } break; case hyde::json::value_t::boolean: { return YAML::Node(json.get()); } break; case hyde::json::value_t::number_integer: { return YAML::Node(json.get()); } break; case hyde::json::value_t::number_unsigned: { return YAML::Node(json.get()); } break; case hyde::json::value_t::number_float: { return YAML::Node(json.get()); } break; case hyde::json::value_t::binary: { throw std::runtime_error("Binary JSON value unsupported"); } break; case hyde::json::value_t::discarded: { throw std::runtime_error("Discarded JSON value"); } break; } return YAML::Node(); } /**************************************************************************************************/ YAML::Node json_to_yaml_ordered(hyde::json j) { // YAML preserves the order of addition, while JSON orders lexicographically // by key value. We want the values common to all jekyll pages to appear // higher in the YAML than other keys, so we do a specific ordering here. YAML::Node result; const auto move_key = [&](const std::string& key) { if (!j.count(key)) return; result[key] = json_to_yaml(j[key]); j.erase(key); }; // These are in some ROUGH order/grouping from generic to specific fields. move_key("layout"); move_key("title"); move_key("owner"); move_key("brief"); move_key("tags"); move_key("inline"); move_key("library-type"); move_key("defined_in_file"); move_key("declaration"); move_key("annotation"); move_key("ctor"); move_key("dtor"); move_key("is_ctor"); move_key("is_dtor"); move_key("typedefs"); move_key("fields"); move_key("methods"); move_key("overloads"); if (j.count("hyde")) { result["hyde"] = json_to_yaml_ordered(j["hyde"]); j.erase("hyde"); } // copy over the remainder of the keys. for (auto it = j.begin(); it != j.end(); ++it) { result[it.key()] = json_to_yaml(it.value()); } return result; } /**************************************************************************************************/ // See Issue #75 and PR #80. Take the relevant hyde fields and move them under a top-level // `hyde` subfield. Only do this when we're asked to, in case this has already been done and those // high-level fields are used by something else. When we do this fixup, we don't know which fields // hyde actually uses, so this will move _all_ fields that are not `layout` and `title`. hyde::json fixup_hyde_subfield(hyde::json&& j) { hyde::json result; if (j.count("layout")) { result["layout"] = std::move(j.at("layout")); j.erase("layout"); } if (j.count("title")) { result["title"] = std::move(j.at("title")); j.erase("title"); } // If a fixup has already been done, or there are already fields in the `hyde` subobject, // move them before going on to the next step. if (j.count("hyde")) { result["hyde"] = std::move(j.at("hyde")); j.erase("hyde"); } // For any keys that are left over in `j`, move them under the `hyde` subobject. Note that // this will overwrite any conflicting keys that may already exist there. for (auto& entry : j.items()) { result["hyde"][entry.key()] = std::move(entry.value()); } return result; } /**************************************************************************************************/ static const std::string front_matter_begin_k("---\n"); // Using a string wrapped in newlines because some YAML data may contain "---" in them // (e.g., inline comments that have sucked up a horizontal delimiter.) static const std::string front_matter_end_k("\n---\n"); /**************************************************************************************************/ } // namespace /**************************************************************************************************/ namespace hyde { /**************************************************************************************************/ file_checker yaml_base_emitter::checker_s; // REVISIT (fbrereto) : Global. Bad programmer. No donut. /**************************************************************************************************/ json yaml_base_emitter::base_emitter_node(std::string layout, std::string title, std::string tag, bool implicit) { json node; node["layout"] = std::move(layout); node["title"] = std::move(title); const auto& default_tag_value = implicit ? tag_value_optional_k : tag_value_missing_k; node["hyde"]["owner"] = default_tag_value; node["hyde"]["tags"].emplace_back(std::move(tag)); node["hyde"]["brief"] = default_tag_value; node["hyde"]["version"] = hyde_version(); return node; } /**************************************************************************************************/ void yaml_base_emitter::insert_doxygen(const json& j, json& node) { if (!j.count("comments")) return; const auto& comments = j.at("comments"); auto& output = node["inline"]; if (comments.count("command")) { for (const auto& item : comments.at("command").items()) { const auto& command = item.value(); std::string key = command["name"].get(); if (key.starts_with("hyde-")) { key = key.substr(5); } output[key] = command["text"].get(); } } if (comments.count("paragraph")) { json::array_t paragraphs; for (const auto& paragraph : comments.at("paragraph").items()) { paragraphs.push_back(paragraph.value().at("text")); } output["description"] = std::move(paragraphs); } if (comments.count("param")) { json::object_t arguments; for (const auto& item : comments.at("param").items()) { const auto& param = item.value(); const auto& name = param["name"].get(); json::object_t argument; if (param["direction_explicit"].get()) { argument["direction"] = param["direction"]; } argument["description"] = param["text"]; arguments[name] = std::move(argument); } output["arguments"] = std::move(arguments); } } /**************************************************************************************************/ void yaml_base_emitter::insert_typedefs(const json& j, json& node, const json& inherited) { if (j.count("typedefs")) { for (const auto& type_def : j["typedefs"]) { const std::string& key = type_def["name"]; auto& type_node = node["hyde"]["typedefs"][key]; insert_inherited(inherited, type_node); insert_annotations(type_def, type_node); insert_doxygen(type_def, type_node); type_node["definition"] = static_cast(type_def["type"]); type_node["description"] = has_inline_field(type_node, "description") ? tag_value_inlined_k : tag_value_missing_k; } } if (j.count("typealiases")) { for (const auto& type_def : j["typealiases"]) { const std::string& key = type_def["name"]; auto& type_node = node["hyde"]["typedefs"][key]; insert_inherited(inherited, type_node); insert_annotations(type_def, type_node); insert_doxygen(type_def, type_node); type_node["definition"] = static_cast(type_def["type"]); type_node["description"] = has_inline_field(type_node, "description") ? tag_value_inlined_k : tag_value_missing_k; } } } /**************************************************************************************************/ void yaml_base_emitter::check_inline_comments(const json& expected, json& out_merged) { // inline comments *always* come from the sources. Therefore, they are always overwritten in the // merge. if (expected.count("inline")) { out_merged["inline"] = expected.at("inline"); } } /**************************************************************************************************/ bool yaml_base_emitter::check_typedefs(const std::string& filepath, const json& have_node, const json& expected_node, const std::string& nodepath, json& merged_node) { return check_map( filepath, have_node, expected_node, nodepath, merged_node, "typedefs", [this](const std::string& filepath, const json& have, const json& expected, const std::string& nodepath, json& out_merged) { bool failure{false}; failure |= check_scalar(filepath, have, expected, nodepath, out_merged, "name"); failure |= check_scalar(filepath, have, expected, nodepath, out_merged, "definition"); failure |= check_editable_scalar(filepath, have, expected, nodepath, out_merged, "description"); failure |= check_scalar_array(filepath, have, expected, nodepath, out_merged, "annotation"); check_inline_comments(expected, out_merged); return failure; }); } /**************************************************************************************************/ void yaml_base_emitter::check_notify(const std::string& filepath, const std::string& nodepath, const std::string& key, const std::string& validate_message, const std::string& update_message) { std::string escaped_nodepath = hyde::ReplaceAll(nodepath, "\n", "\\n"); std::string escaped_key = hyde::ReplaceAll(key, "\n", "\\n"); switch (_mode) { case yaml_mode::validate: { std::cerr << filepath << "@" << escaped_nodepath << "['" << escaped_key << "']: " << validate_message << "\n"; } break; case yaml_mode::transcribe: case yaml_mode::update: { std::cout << filepath << "@" << escaped_nodepath << "['" << escaped_key << "']: " << update_message << "\n"; } break; } } /**************************************************************************************************/ bool yaml_base_emitter::check_removed(const std::string& filepath, const json& have_node, const std::string& nodepath, const std::string& key) { if (!have_node.count(key)) { // Removed key not present in have. Do nothing, no error. return false; } else { check_notify(filepath, nodepath, key, "value present for removed key", "value removed"); return true; } } /**************************************************************************************************/ bool yaml_base_emitter::check_scalar(const std::string& filepath, const json& have_node, const json& expected_node, const std::string& nodepath, json& merged_node, const std::string& key) { const auto notify = [&](const std::string& validate_message, const std::string& update_message) { check_notify(filepath, nodepath, key, validate_message, update_message); }; if (!expected_node.count(key)) { return check_removed(filepath, have_node, nodepath, key); } const json& expected = expected_node[key]; if (!expected.is_primitive()) { throw std::runtime_error("expected type mismatch?"); } json& result = merged_node[key]; if (!have_node.count(key)) { notify("value missing", "value inserted"); result = expected; return true; } const json& have = have_node[key]; if (have != expected) { result = expected; // Since yaml <-> json type conversions aren't perfect (at least with the libraries we are // currently using), we will only report a failure if the yaml-serialized value is different auto have_yaml = json_to_yaml(have).as(); auto expected_yaml = json_to_yaml(expected).as(); if (have_yaml != expected_yaml) { notify("value mismatch; have `" + have.dump() + "`, expected `" + expected.dump() + "`", "value updated from `" + have.dump() + "` to `" + expected.dump() + "`"); return true; } } // both have and expected are both scalar and are the same value result = have; return false; } /**************************************************************************************************/ // The intent of this routine is to arrive at the ideal output given the various bits of state the // engine has available to it. The two major players are `have` (the data that has been parsed out // of documentation that already exists) and `expected` (the data derived by the engine from // compiling the source file). In addition to user-entered values in the `have` data, there are a // handful of predefined values for the key that also play a part (missing, inlined, deprecated, // etc.) Both `have` and `expected` come in as json blobs that might contain an entry under `key`. // The goal is to affect `merged_node` (which is also a json blob) under that same `key`. I // say "affect" because it doesn't always mean setting a value to the output - sometimes it's // the _removal_ of said key. Hopefully this all is made more clear by the path outlined below. // // It's worth adding a comment about "associated inline" values. To start, this is data extracted // from the source file's Doxygen-formatted inline comments (hence the term). Therefore, this data // is _always_ derived, and _never_ modified by `have`. Thus, if inline values exist, they are // always copied from `expected` into the merged output. In order for an inline value to be // considered "associated" with a given entry `expected[key]`, the value must exist under `expected // ["inline"][key]`. The associated value type is not always a string (for example, inline comments // can be an array of strings _or_ a single string. Those value type differences need to be // accounted for in the Jekyll theme that will be displaying that data - we don't care about them // here.) // // A word on the "special" values. These are placeholder string values that, depending on their // individual semantics, will affect how the final documentation is displayed: // - __MISSING__: This is the value most developers will be familiar with. In order for the // documentation to pass validation, all __MISSING__ fields must be manually filled in. // - __OPTIONAL__: This field's value is not required for validation and will be shown if // manually filled in. Among other cases, this value is used when a declaration is implicit // (e.g., compiler-implemented, v. defined in the source code.) // - __DEPRECATED__: This field will be found in `expected`, and will cause the equivalent field // in `have` to be removed. // - __INLINED__: The value _would_ be __MISSING__ except that there is an associated inline // value for the field, and thus the minimum requirement for documentation has been met, // and validation will pass for this field. Users are allowed to replace this value should // they want to add further documentation. // // These `check_` routines are used for both the validation and update phases. Their logic is the // same, but how they behave will differ (e.g., inserting a value [updating] v. reporting a value // missing [validating].) // // The logical gist of this routine is as follows: // - If `expected[key]` doesn't exist, make sure `have[key]` doesn't either. Done. // - If `expected[key]` isn't a string, we're in the wrong routine. Done. In other words, all // hyde scalars are strings. // - If `expected[key]` has an associated inline value, set `default_value` to __INLINED__ // - Otherwise, set `default_value` to __MISSING__ // - If `have[key]` does not exist, the result is `default_value`. Done. // - If `have[key]` is not a scalar, the result is `default_value`. Done. // - If both `expected[key]` and `have[key]` are __MISSING__, the result is // `default_value`. Done. // - If `expected[key]` is __DEPRECATED__, result is no output. Done. // - If `expected[key]` and `have[key]` are both __OPTIONAL__, result is __OPTIONAL__. Done. // - If `have[key]` is a special tag value, result is `default_value`. Done. // - Otherwise, result is `have[key]`. Done. bool yaml_base_emitter::check_editable_scalar(const std::string& filepath, const json& have_node, const json& expected_node, const std::string& nodepath, json& merged_node, const std::string& key) { const auto notify = [&](const std::string& validate_message, const std::string& update_message) { check_notify(filepath, nodepath, key, validate_message, update_message); }; if (!expected_node.count(key)) { return check_removed(filepath, have_node, nodepath, key); } const json& expected = expected_node[key]; const bool has_associated_inline_value = has_inline_field(expected_node, key.c_str()); if (!expected.is_string()) { throw std::runtime_error("expected type mismatch?"); } const std::string& expected_scalar(expected); const bool expected_set_to_missing = expected_scalar == tag_value_missing_k; const bool use_inline_value = expected_set_to_missing && has_associated_inline_value; const json& default_value = use_inline_value ? inline_json_k : expected; const std::string& default_value_scalar(default_value); json& result = merged_node[key]; if (!have_node.count(key)) { if (expected_scalar == tag_value_deprecated_k) { // deprecated key not present in have. Do nothing, no error. return false; } else { notify("value missing", "value inserted"); result = default_value; return true; } } const json& have = have_node[key]; if (!have.is_string()) { notify("value not scalar; expected `" + default_value_scalar + "`", "value not scalar; updated to `" + default_value_scalar + "`"); result = default_value; return true; } const std::string& have_scalar(have); if (default_value_scalar == tag_value_missing_k && have_scalar == tag_value_missing_k) { result = default_value; if (_mode == yaml_mode::validate) { notify("value not documented", ""); } return true; } if (expected_scalar == tag_value_deprecated_k) { notify("key is deprecated", "deprecated key removed"); return true; } // If the scalars are identical, they're both equal tags at this point. Done. if (default_value_scalar == have_scalar) { result = have; return false; } // Among other cases, this check will handle when tags go from __MISSING__ to __INLINED__ and // vice versa. if (hyde::is_tag(have_scalar)) { notify("found unexpected tag `" + have_scalar + "`", "tag updated from `" + have_scalar + "` to `" + default_value_scalar + "`"); // Replace unexpected tag? result = default_value; return true; } // The docs have *something*, so we're good. result = have; return false; } /**************************************************************************************************/ bool yaml_base_emitter::check_editable_scalar_array(const std::string& filepath, const json& have_node, const json& expected_node, const std::string& nodepath, json& merged_node, const std::string& key) { const auto notify = [&](const std::string& validate_message, const std::string& update_message) { check_notify(filepath, nodepath, key, validate_message, update_message); }; const auto notify_fail = [&](const std::string& message) { check_notify(filepath, nodepath, key, message, message); }; if (!expected_node.count(key)) { return check_removed(filepath, have_node, nodepath, key); } const json& expected = expected_node[key]; if (!expected.is_string()) { throw std::runtime_error("expected type mismatch?"); } const std::string& expected_scalar(expected); json& result = merged_node[key]; if (!have_node.count(key)) { if (expected_scalar == tag_value_deprecated_k) { // deprecated key not present in have. Do nothing, no error. return false; } else { notify("value missing", "value inserted"); result = expected; return true; } } const json& have = have_node[key]; if (have.is_string()) { const std::string& have_scalar(have); if (expected_scalar == tag_value_missing_k && have_scalar == tag_value_missing_k) { result = have; if (_mode == yaml_mode::validate) { notify("value not documented", ""); } return true; } if (expected_scalar == tag_value_deprecated_k) { notify("key is deprecated", "deprecated key removed"); return true; } if (expected_scalar == tag_value_optional_k && have_scalar == tag_value_optional_k) { result = have; return false; } if (hyde::is_tag(have_scalar)) { notify("value is unexpected tag `" + have_scalar + "`", "value updated from `" + have_scalar + "` to `" + expected_scalar + "`"); result = expected; // Replace unexpected tag return true; } } if (!have.is_array()) { notify_fail("value not an array; expected an array of scalar values"); result = have; return true; } result = have; // We have an array; make sure its elements are scalar std::size_t index{0}; bool failure{false}; for (const auto& have_element : have) { if (!have_element.is_string()) { failure = true; notify_fail("non-scalar array element at index " + std::to_string(index)); } else if (hyde::is_tag(have_element)) { failure = true; notify_fail("invalid value at index " + std::to_string(index)); } } return failure; } /**************************************************************************************************/ bool yaml_base_emitter::check_scalar_array(const std::string& filepath, const json& have_node, const json& expected_node, const std::string& nodepath, json& merged_node, const std::string& key) { const auto notify = [&](const std::string& validate_message, const std::string& update_message) { check_notify(filepath, nodepath, key, validate_message, update_message); }; const auto notify_fail = [&](const std::string& message) { check_notify(filepath, nodepath, key, message, message); }; if (!expected_node.count(key)) { if (!have_node.count(key)) { // Removed key not present in have. Do nothing, no error. return false; } else { notify("value present for removed key", "value removed"); return true; } } const json& expected = expected_node[key]; if (!expected.is_array()) { notify_fail("expected type mismatch"); throw std::runtime_error("Merge scalar array failure"); } json& result = merged_node[key]; if (!have_node.count(key)) { notify("value missing", "value inserted"); result = expected; return true; } if (!have_node[key].is_array()) { notify("value not an array", "non-array value replaced"); result = expected; return true; } const json& have = have_node[key]; // How does one merge an array of scalars? The solution is to use scalar value // as the "key", and treat the array like a dictionary. This does create the // possibility that multiple objects will resolve to the same key, and/or the // key value may not be a string, so we establish those requirements as function // preconditions. // // As for the actual merge, we have a `have` array, and an `expected` array. // The resulting merge will be built up in a resulting array, then moved // into `result.` // // First we build up a sorted vector of key/position pairs of the `have` // array. This will let us find elements in that array in log(N) time. // // Then we iterate the expected array from first to last. We pull out the // expected key and search for it in the have array. If it is missing, copy // the expected element into the result array, and move on. If it is found, // make sure they are in the same index in both arrays, otherwise it's a // validation error. The result array is then moved into the resulting json. // // If `have` has extraneous keys, they will be skipped during the iteration // of `expected` and subsequently dropped. // // The entire merge process should be O(N log N) time, bounded on the sort // of the have key vector. Thanks to Jared Wyles for the tip on how to solve // this one. std::vector> have_map; std::size_t count{0}; bool failure{false}; for (const auto& have_element : have) { const std::string& have_str = have_element; have_map.push_back(std::make_pair(have_str, count++)); } std::sort(have_map.begin(), have_map.end()); // Now go one by one through the expected list, and reorganize. std::size_t index{0}; json result_array; for (const auto& expected_element : expected) { const std::string& expected_str = expected_element; const auto have_found_iter = std::lower_bound(have_map.begin(), have_map.end(), expected_str, [](const auto& a, const auto& b) { return a.first < b; }); bool have_found = have_found_iter != have_map.end() && have_found_iter->first == expected_str; std::string index_str(std::to_string(index)); if (!have_found) { notify("missing required string at index " + index_str, "required string inserted at index " + index_str); result_array.push_back(expected_str); failure = true; } else { std::size_t have_index = have_found_iter->second; if (have_index != index) { std::string have_index_str(std::to_string(have_index)); notify("bad item location for item `" + expected_str + "`; have: " + have_index_str + ", expected: " + index_str, "moved item `" + expected_str + "` at index " + have_index_str + " to index " + index_str); failure = true; } result_array.push_back(expected_str); have_map.erase(have_found_iter); } ++index; } for (const auto& have_iter : have_map) { const std::string& have_str = have_iter.first; std::string have_index_str(std::to_string(have_iter.second)); auto message = "extraneous item `" + have_str + "` at index `" + have_index_str + "`"; notify(message, "removed " + message); failure = true; } result = std::move(result_array); return failure; } /**************************************************************************************************/ bool yaml_base_emitter::check_object_array(const std::string& filepath, const json& have_node, const json& expected_node, const std::string& nodepath, json& merged_node, const std::string& key, const std::string& object_key, const check_proc& proc) { const auto notify = [&](const std::string& validate_message, const std::string& update_message) { check_notify(filepath, nodepath, key, validate_message, update_message); }; const auto notify_fail = [&](const std::string& message) { check_notify(filepath, nodepath, key, message, message); }; if (!expected_node.count(key)) { return check_removed(filepath, have_node, nodepath, key); } const json& expected = expected_node[key]; if (!expected.is_array()) { notify_fail("expected type mismatch"); throw std::runtime_error("Merge object array failure"); } json& result = merged_node[key]; if (!have_node.count(key)) { notify("value missing", "value inserted"); result = expected; return true; } if (!have_node[key].is_array()) { notify("value not an array", "non-array value replaced"); result = expected; return true; } const json& have = have_node[key]; // How does one merge an array of objects? The solution is to use one of the // elements in each object as the object's "key", and treat the array like a // dictionary. This does create the possibility that multiple objects will // resolve to the same key, and/or the key value may not be a string, so we // establish those requirements as function preconditions. // // As for the actual merge, we have a `have` array, and an `expected` array. // The resulting merge will be built up in a resulting array, then moved // into `result.` // // First we build up a sorted vector of key/position pairs of the `have` // array. This will let us find elements in that array in log(N) time. // // Then we iterate the expected array from first to last. We pull out the // expected key and search for it in the have array. If it is missing, copy // the expected element into the result array, and move on. If it is found, // make sure they are in the same index in both arrays, otherwise it's a // validation error. Once that is done, merge the two objects together with // the user callback. The merged result is then pushed onto the back of the // result array. The result array is then moved into the resulting json. // // If `have` has extraneous keys, they will be skipped during the iteration // of `expected` and subsequently dropped. // // The entire merge process should be O(N log N) time, bounded on the sort // of the have key vector. Thanks to Jared Wyles for the tip on how to solve // this one. std::vector> have_map; std::size_t count{0}; bool failure{false}; for (const auto& have_elements : have) { if (have_elements.count(object_key) == 0) { std::string count_str(std::to_string(count)); std::string message("object at index " + count_str + " has no key"); notify(message, message + "; skipped"); // preserve object indices for merging below. Name is irrelevant as // long as it's unique. Prefix with '.' to prevent actual key // conflicts. have_map.push_back(std::make_pair(".unnamed-" + count_str, count++)); failure = true; } else { const std::string& key = have_elements[object_key]; have_map.push_back(std::make_pair(key, count++)); } } std::sort(have_map.begin(), have_map.end()); // Now go one by one through the expected list, and reorganize. std::size_t index{0}; json result_array; for (const auto& expected_object : expected) { const std::string& expected_key = expected_object[object_key]; const auto have_found_iter = std::lower_bound(have_map.begin(), have_map.end(), expected_key, [](const auto& a, const auto& b) { return a.first < b; }); bool have_found = have_found_iter != have_map.end() && have_found_iter->first == expected_key; std::string index_str(std::to_string(index)); if (!have_found) { notify("missing required object at index " + index_str, "required object inserted at index " + index_str); result_array.push_back(expected_object); failure = true; } else { std::size_t have_index = have_found_iter->second; if (have_index != index) { std::string have_index_str(std::to_string(have_index)); notify("bad item location for key `" + expected_key + "`; have: " + have_index_str + ", expected: " + index_str, "moved item with key `" + expected_key + "` at index " + have_index_str + " to index " + index_str); failure = true; } std::string nodepath = "['" + key + "'][" + index_str + "]"; json merged; failure |= proc(filepath, have[have_index], expected_object, nodepath, merged); result_array.push_back(std::move(merged)); have_map.erase(have_found_iter); } ++index; } for (const auto& have_iter : have_map) { const std::string& have_key = have_iter.first; std::string have_index_str(std::to_string(have_iter.second)); auto message = "extraneous item with key `" + have_key + "` at index `" + have_index_str + "`"; notify(message, "removed " + message); failure = true; } result = std::move(result_array); return failure; } /**************************************************************************************************/ std::vector object_keys(const json& j) { std::vector result; for (auto iter{j.begin()}, last{j.end()}; iter != last; ++iter) { result.push_back(static_cast(iter.key())); } return result; } template inline void move_append(T& dst, T&& src) { dst.insert(dst.end(), std::make_move_iterator(src.begin()), std::make_move_iterator(src.end())); } struct transcribe_pair { std::string src; std::string dst; }; using transcribe_pairs = std::vector; // This is O(N^2), where N is the size of both `src` and `dst`. Therefore transcription // should only be run when it is shown to be necessary. At the same time, if your code base // has enough overrides to really slow this algorithm down, hyde's performance is the least // of your concerns. transcribe_pairs derive_transcribe_pairs(const json& src, const json& dst) { std::vector src_keys = object_keys(src); std::vector dst_keys = object_keys(dst); if (src_keys.size() != dst_keys.size()) { std::cerr << "WARNING: transcription key count mismatch\n"; } transcribe_pairs result; while (!src_keys.empty()) { transcribe_pair cur_pair; // pop a key off the old name set cur_pair.src = std::move(src_keys.back()); src_keys.pop_back(); // find the best match of the dst keys to the src key std::size_t best_match = std::numeric_limits::max(); std::size_t best_index = 0; for (std::size_t i = 0; i < dst_keys.size(); ++i) { // generate the diff score of the src key and the candidate dst std::size_t cur_match = diff_score(cur_pair.src, dst_keys[i]); if (cur_match > best_match) { continue; } // if this dst candidate is better than what we've seen, remember that. best_match = cur_match; best_index = i; } // pair the best match dst and src keys and remove dst cur_pair.dst = std::move(dst_keys[best_index]); dst_keys.erase(dst_keys.begin() + best_index); // save off the pair and repeat result.emplace_back(std::move(cur_pair)); } return result; } /**************************************************************************************************/ bool yaml_base_emitter::check_map(const std::string& filepath, const json& have_node, const json& expected_node, const std::string& nodepath, json& merged_node, const std::string& key, const check_proc& proc) { const bool at_root = key == ""; const auto notify = [&](const std::string& validate_message, const std::string& update_message) { check_notify(filepath, nodepath, key, validate_message, update_message); }; if (!expected_node.count(key)) { return check_removed(filepath, have_node, nodepath, key); } const json& expected = expected_node[key]; if (!expected.is_object()) { throw std::runtime_error("expected type mismatch?"); } json& result = merged_node[key]; if (!have_node.count(key) || !have_node[key].is_object()) { notify("value not a map", "non-map value replaced"); result = expected; return true; } const json& have = have_node[key]; bool failure{false}; json result_map; if (key == "overloads" && _mode == yaml_mode::transcribe) { /* It is common during the upgrade from one version of hyde to another that the underlying clang tooling will output different symbol names for a given symbol (e.g., a namespace may get removed or added.) Although the symbol is unchanged, because its `expected` name differs from the `have` name, hyde will consider the symbols different, remove the old name and insert the new one. This wipes out any previous documentation under the old name that should have been migrated to the new name. The solution here is very specialized. For the "overloads" key only, we gather the name of each overload in both the `have` and `expected` set. We then pair them up according to how well they match to one another (using the Meyers' string diff algorithm; two strings with less "patchwork" between them are considered a better match). Ideally this results in key pairs that represent the same symbol, just with different names. Then we call the `proc` with `have[old_name]` and `expected[new_name]` which will migrate any documentation from the old name to the new. This capability assumes the overload count of both `have` and `expected` are the same. If any new functions are created or removed between upgrades in the clang driver (e.g., a new compiler-generated routine is created and documented) that will have to be managed manually. Assuming the count is the same, it also assumes there is a 1:1 mapping from the set of old names to the set of new names. This implies the transcription mode should be done as a separate step from an update. In other words, a transcription assumes the documentation is actually the same between the `have` and `expected` sets, it is _just the overload names_ that have changed, so map the old-named documentation to the new-named documentation as reasonably as possible. */ for (const auto& pair : derive_transcribe_pairs(have, expected)) { const std::string curnodepath = nodepath + "['" + pair.dst + "']"; failure |= proc(filepath, have[pair.src], expected[pair.dst], curnodepath, result_map[pair.dst]); } } else { std::vector keys; move_append(keys, object_keys(have)); move_append(keys, object_keys(expected)); std::sort(keys.begin(), keys.end()); keys.erase(std::unique(keys.begin(), keys.end()), keys.end()); for (const auto& subkey : keys) { const std::string curnodepath = nodepath + "['" + subkey + "']"; if (!expected.count(subkey)) { // Issue #75: only remove non-root keys to allow non-hyde YAML into the file. if (!at_root) { notify("extraneous map key: `" + subkey + "`", "map key removed: `" + subkey + "`"); failure = true; } } else if (!have.count(subkey)) { notify("map key missing: `" + subkey + "`", "map key inserted: `" + subkey + "`"); result_map[subkey] = expected[subkey]; failure = true; } else { failure |= proc(filepath, have[subkey], expected[subkey], curnodepath, result_map[subkey]); } } } result = std::move(result_map); return failure; } /**************************************************************************************************/ bool yaml_base_emitter::has_inline_field(const json& j, const char* field) { return j.count("inline") && j.at("inline").count(field); } /**************************************************************************************************/ std::pair yaml_base_emitter::merge(const std::string& filepath, const json& have, const json& expected) { bool failure{false}; // Create a temporary object with the json to merge as a value so we can use `check_map` // to make sure removed keys are handled static const auto root_key = ""; json have_root; have_root[root_key] = have; json expected_root; expected_root[root_key] = expected; json merged_root; failure |= check_map(filepath, have_root, expected_root, "", merged_root, root_key, [](const std::string& filepath, const json& have, const json& expected, const std::string& nodepath, json& out_merged) { return false; }); json& merged = merged_root[root_key]; // we can probably get rid of `have` in the check // routines; I don't think we can keep it from being an // out-arg, though, because we need to preserve the // values in `have` that are not managed by the // `expected` schema. failure |= check_scalar(filepath, have, expected, "", merged, "layout"); if (_editable_title) { failure |= check_editable_scalar(filepath, have, expected, "", merged, "title"); } else { failure |= check_scalar(filepath, have, expected, "", merged, "title"); } { const hyde::json& expected_hyde = expected.at("hyde"); const hyde::json& have_hyde = have.count("hyde") ? have.at("hyde") : no_json_k; hyde::json& merged_hyde = merged["hyde"]; failure |= check_editable_scalar(filepath, have_hyde, expected_hyde, "", merged_hyde, "owner"); failure |= check_editable_scalar(filepath, have_hyde, expected_hyde, "", merged_hyde, "brief"); failure |= check_scalar_array(filepath, have_hyde, expected_hyde, "", merged_hyde, "tags"); // We don't want to use `check_scalar` on the version key. If the versions mismatch its not // necessarily a validation error (as the docs may match OK), but something we want to warn // about. Then in transcription/update we want to hard-set the value to the version of this // tool. switch (_mode) { case yaml_mode::validate: { if (!have_hyde.count("version") || static_cast(have_hyde["version"]) != hyde_version()) { std::cerr << "INFO: Validation phase with a mismatched version of hyde. Consider updating then/or transcribing.\n"; } } break; case yaml_mode::update: case yaml_mode::transcribe: { merged_hyde["version"] = hyde_version(); } break; } failure |= do_merge(filepath, have_hyde, expected_hyde, merged_hyde); } return std::make_pair(failure, std::move(merged)); } /**************************************************************************************************/ inline std::uint64_t fnv_1a(const std::string& s) { constexpr std::uint64_t prime_k = 0x100000001b3; std::uint64_t result(0xcbf29ce484222325); for (const auto& c : s) { result = (result ^ static_cast(c)) * prime_k; } return result; } /**************************************************************************************************/ std::string yaml_base_emitter::filename_truncate(std::string s) { if (s.size() <= 32) return s; // We cannot use std::hash here because the hashing algorithm may give // different results per-platform. Instead we fall back on an old // favorite of mine for this kind of thing: FNV-1a. const auto hash = [&_s = s]() { auto hash = fnv_1a(_s) & 0xFFFFFFFF; std::stringstream stream; stream << std::hex << hash; return stream.str(); }(); // 23 + 1 + 8 = 32 max characters per directory name s = s.substr(0, 23) + '.' + hash; return s; } /**************************************************************************************************/ std::filesystem::path yaml_base_emitter::directory_mangle(std::filesystem::path p) { std::filesystem::path result; for (const auto& part : p) { result /= filename_truncate(filename_filter(part.string())); } return result; } /**************************************************************************************************/ bool yaml_base_emitter::create_directory_stub(std::filesystem::path p) { auto stub_name = p / index_filename_k; if (exists(stub_name)) return false; std::ofstream output(stub_name); if (!output) { std::cerr << stub_name.string() << ": could not create directory stub\n"; return true; } static const auto stub_json_k = json::object_t{ {"layout", "directory"}, {"title", p.filename().string()}, }; output << front_matter_begin_k; output << json_to_yaml_ordered(stub_json_k); output << front_matter_end_k; return false; } /**************************************************************************************************/ bool yaml_base_emitter::create_path_directories(std::filesystem::path p) { if (p.has_filename()) p = p.parent_path(); std::vector ancestors; const auto root_path = p.root_path(); while (p != root_path) { ancestors.push_back(p); if (!p.has_parent_path()) break; p = p.parent_path(); } std::reverse(ancestors.begin(), ancestors.end()); for (const auto& ancestor : ancestors) { if (checker_s.exists(ancestor)) continue; if (_mode == yaml_mode::validate) { return true; } else { std::error_code ec; create_directory(ancestor, ec); if (ec) { static std::map bad_path_map_s; const auto bad_path = ancestor.string(); if (!bad_path_map_s.count(bad_path)) std::cerr << bad_path << ": directory could not be created (" << ec << ")\n"; bad_path_map_s[bad_path] = true; return true; } if (create_directory_stub(ancestor)) { return true; } } } return false; } /**************************************************************************************************/ auto load_yaml(const std::filesystem::path& path) try { return YAML::LoadFile(path.c_str()); } catch (...) { std::cerr << "YAML File: " << path.string() << '\n'; throw; } /**************************************************************************************************/ documentation parse_documentation(const std::filesystem::path& path, bool fixup_subfield) { // we have to load the file ourselves and find the place where the // front-matter ends and any other relevant documentation begins. We // need to do this for the boilerpolate step to keep it from blasting // out any extra documentation that's already been added. std::ifstream have_file(path); std::stringstream have_contents_stream; have_contents_stream << have_file.rdbuf(); std::string have_contents = have_contents_stream.str(); documentation result; if (have_contents.find_first_of(front_matter_begin_k) != 0) { std::cerr << "./" << path.string() << ": does not begin with YAML front-matter.\n"; result._error = true; return result; } const auto contents_end = have_contents.find(front_matter_end_k); if (contents_end == std::string::npos) { std::cerr << "./" << path.string() << ": could not find end of YAML front-matter.\n"; result._error = true; return result; } const auto front_matter_end = contents_end + front_matter_end_k.size(); std::string yaml_src = have_contents.substr(0, front_matter_end); have_contents.erase(0, front_matter_end); result._remainder = std::move(have_contents); result._json = yaml_to_json(load_yaml(path)); if (fixup_subfield) { result._json = fixup_hyde_subfield(std::move(result._json)); } return result; } /**************************************************************************************************/ bool write_documentation(const documentation& docs, const std::filesystem::path& path) { std::ofstream output(path); if (!output) { std::cerr << "./" << path.string() << ": could not open file for output\n"; return true; } output << front_matter_begin_k; output << json_to_yaml_ordered(docs._json); output << front_matter_end_k; output << docs._remainder; return false; } /**************************************************************************************************/ bool yaml_base_emitter::reconcile(json expected, std::filesystem::path root_path, std::filesystem::path path, json& out_reconciled) { bool failure{false}; /* begin hack */ { // I hope to remove this soon. Paths with '...' in them make Perforce go // stir-crazy (it's a special token for the tool), so we remove them. static const std::string needle = "..."; std::string p_str = path.string(); std::size_t pos = 0; auto found = false; while (true) { pos = p_str.find(needle, pos); if (pos == std::string::npos) break; found = true; p_str.replace(pos, needle.size(), ""); } if (found) { path = std::filesystem::path(p_str); } } std::string relative_path(("." / relative(path, root_path)).string()); failure |= create_path_directories(path); if (checker_s.exists(path)) { const auto have_docs = parse_documentation(path, true); if (have_docs._error) { return true; } const auto& have = have_docs._json; const auto& remainder = have_docs._remainder; json merged; std::tie(failure, merged) = merge(relative_path, have, expected); out_reconciled = merged; out_reconciled["documentation_path"] = relative_path; switch (_mode) { case hyde::yaml_mode::validate: { // do nothing } break; case hyde::yaml_mode::transcribe: case hyde::yaml_mode::update: { failure = write_documentation({std::move(merged), std::move(remainder)}, path); } break; } } else { // file does not exist out_reconciled = expected; switch (_mode) { case hyde::yaml_mode::validate: { std::cerr << relative_path << ": required file does not exist\n"; failure = true; } break; case hyde::yaml_mode::transcribe: case hyde::yaml_mode::update: { // Add update. No remainder yet, as above. // REVISIT: Refactor all this into a call to write_documentation, // though I'm not sure what the call to `update_cleanup` is all about, // and which was missing from the prior case when the file existed. std::ofstream output(path); if (!output) { std::cerr << "./" << path.string() << ": could not open file for output\n"; failure = true; } else { output << front_matter_begin_k; output << update_cleanup(json_to_yaml_ordered(expected)); output << front_matter_end_k; } } break; } } return failure; } /**************************************************************************************************/ std::string yaml_base_emitter::defined_in_file(const std::string& src_path, const std::filesystem::path& src_root) { return relative(std::filesystem::path(src_path), src_root).string(); } /**************************************************************************************************/ std::filesystem::path yaml_base_emitter::subcomponent(const std::filesystem::path& src_path, const std::filesystem::path& src_root) { return std::filesystem::relative(src_path, src_root); } /**************************************************************************************************/ void yaml_base_emitter::insert_inherited(const json& inherited, json& node) { if (inherited.count("owner") && !is_tag(inherited.at("owner").get())) { node["owner"] = inherited.at("owner"); } if (has_inline_field(inherited, "owner")) { node["inline"]["owner"] = inherited.at("inline").at("owner"); } } /**************************************************************************************************/ void yaml_base_emitter::insert_annotations(const json& j, json& node) { std::string annotation; if (j.count("access")) { const std::string& access = j["access"]; if (access != "public") { node["annotation"].push_back(access); } } if (has_json_flag(j, "implicit")) { node["annotation"].push_back("implicit"); } if (has_json_flag(j, "defaulted")) { node["annotation"].push_back("defaulted"); } if (has_json_flag(j, "deleted")) { node["annotation"].push_back("deleted"); } if (has_json_flag(j, "deprecated")) { std::string deprecated("deprecated"); if (j.count("deprecated_message")) { const std::string& message_str = j["deprecated_message"]; if (!message_str.empty()) { deprecated = deprecated.append("(\"").append(message_str).append("\")"); } } node["annotation"].push_back(deprecated); } } /**************************************************************************************************/ std::string yaml_base_emitter::format_template_parameters(const hyde::json& json, bool with_types) { std::string result; if (json.count("template_parameters")) { std::size_t count{0}; if (with_types) result += "template "; result += "<"; for (const auto& param : json["template_parameters"]) { if (count++) result += ", "; if (with_types) { result += static_cast(param["type"]); if (param.count("parameter_pack")) result += "..."; result += " " + static_cast(param["name"]); } else { result += static_cast(param["name"]); if (param.count("parameter_pack")) result += "..."; } } result += ">"; } return result; } /**************************************************************************************************/ std::string yaml_base_emitter::filename_filter(std::string f) { constexpr const char* const uri_equivalent[] = { "00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "0A", "0B", "0C", "0D", "0E", "0F", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "1A", "1B", "1C", "1D", "1E", "1F", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "2A", "2B", "2C", "-", ".", "2F", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "3A", "3B", "3C", "3D", "3E", "3F", "40", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "5B", "5C", "5D", "5E", "_", "60", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "7B", "7C", "7D", "~", "7F", "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "8A", "8B", "8C", "8D", "8E", "8F", "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "9A", "9B", "9C", "9D", "9E", "9F", "A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "AA", "AB", "AC", "AD", "AE", "AF", "B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "BA", "BB", "BC", "BD", "BE", "BF", "C0", "C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9", "CA", "CB", "CC", "CD", "CE", "CF", "D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7", "D8", "D9", "DA", "DB", "DC", "DD", "DE", "DF", "E0", "E1", "E2", "E3", "E4", "E5", "E6", "E7", "E8", "E9", "EA", "EB", "EC", "ED", "EE", "EF", "F0", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "FA", "FB", "FC", "FD", "FE", "FF", }; std::string result; // preallocate? stringstream? std::for_each(f.begin(), f.end(), [&](const auto& c) { result += uri_equivalent[static_cast(c)]; }); return result; } /**************************************************************************************************/ } // namespace hyde /**************************************************************************************************/ ================================================ FILE: emitters/yaml_base_emitter.hpp ================================================ /* Copyright 2018 Adobe All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. */ #pragma once // application #include "emitters/yaml_base_emitter_fwd.hpp" #include "json.hpp" #include "output_yaml.hpp" /**************************************************************************************************/ namespace hyde { /**************************************************************************************************/ struct file_checker { bool exists(std::filesystem::path p) { _files.emplace_back(std::move(p)); _sorted = false; return std::filesystem::exists(_files.back()); } bool checked(const std::filesystem::path& p) { if (!_sorted) { std::sort(_files.begin(), _files.end()); _files.erase(std::unique(_files.begin(), _files.end()), _files.end()); _sorted = true; } auto found = std::lower_bound(_files.begin(), _files.end(), p); return found != _files.end() && *found == p; } private: std::vector _files; bool _sorted{false}; }; /**************************************************************************************************/ inline bool has_json_flag(const json& j, const char* k) { return j.count(k) && j.at(k).get(); } /**************************************************************************************************/ struct yaml_base_emitter { public: yaml_base_emitter(std::filesystem::path src_root, std::filesystem::path dst_root, yaml_mode mode, emit_options options, bool editable_title = false) : _src_root(std::move(src_root)), _dst_root(std::move(dst_root)), _mode(mode), _options(std::move(options)), _editable_title{editable_title} {} /// @param matched The json given to us by the matcher engine /// @param output The resulting output of this call /// @param inherited Any inherited fields from parent constructs, e.g., passing /// the owner of a class to its members /// @return `true` if an error took place during emit; `false` otherwise. virtual bool emit(const json& matched, json& output, const json& inherited) = 0; protected: json base_emitter_node(std::string layout, std::string title, std::string tag, bool implicit); bool reconcile(json node, std::filesystem::path root_path, std::filesystem::path path, json& out_reconciled); std::string defined_in_file(const std::string& src_path, const std::filesystem::path& src_root); std::filesystem::path subcomponent(const std::filesystem::path& src_path, const std::filesystem::path& src_root); // For some reason nlohmann's JSON types aren't happy with my moving them about // (they always seem to show up null (~) in the final YAML output) // so converting these to out-arg-based routines will have to wait until I can // sort that out. void insert_inherited(const json& inherited, json& node); // make out arg? void insert_annotations(const json& j, json& node); // make out arg? void insert_doxygen(const json& j, json& node); // make out arg? std::string format_template_parameters(const json& json, bool with_types); std::string filename_filter(std::string f); std::string filename_truncate(std::string s); void insert_typedefs(const json& j, json& node, const json& inherited); void check_inline_comments(const json& expected, json& out_merged); bool check_typedefs(const std::string& filepath, const json& have_node, const json& expected_node, const std::string& nodepath, json& merged_node); template std::filesystem::path dst_path(const json& j, Args&&... args); bool check_removed(const std::string& filepath, const json& have_node, const std::string& nodepath, const std::string& key); bool check_scalar(const std::string& filepath, const json& have_node, const json& expected_node, const std::string& nodepath, json& out_merged, const std::string& key); bool check_editable_scalar(const std::string& filepath, const json& have_node, const json& expected_node, const std::string& nodepath, json& out_merged, const std::string& key); bool check_editable_scalar_array(const std::string& filepath, const json& have_node, const json& expected_node, const std::string& nodepath, json& merged_node, const std::string& key); bool check_scalar_array(const std::string& filepath, const json& have_node, const json& expected_node, const std::string& nodepath, json& merged_node, const std::string& key); using check_proc = std::function; bool check_object_array(const std::string& filepath, const json& have_node, const json& expected_node, const std::string& nodepath, json& merged_node, const std::string& key, const std::string& object_key, const check_proc& proc); bool check_map(const std::string& filepath, const json& have, const json& expected, const std::string& nodepath, json& out_merged, const std::string& key, const check_proc& proc); static bool has_inline_field(const json& j, const char* field); private: template std::filesystem::path dst_path_append(std::filesystem::path p, Arg&& arg, Args&&... args); template std::filesystem::path dst_path_append(std::filesystem::path, Arg&& arg); std::filesystem::path dst_path_append(std::filesystem::path p) { return p; } bool create_directory_stub(std::filesystem::path p); bool create_path_directories(std::filesystem::path p); std::filesystem::path directory_mangle(std::filesystem::path p); void check_notify(const std::string& filepath, const std::string& nodepath, const std::string& key, const std::string& validate_message, const std::string& update_message); virtual std::pair merge(const std::string& filepath, const json& have, const json& expected); virtual bool do_merge(const std::string& filepath, const json& have, const json& expected, json& out_merged) = 0; protected: // make private? const std::filesystem::path _src_root; const std::filesystem::path _dst_root; const yaml_mode _mode; const emit_options _options; const bool _editable_title{false}; static file_checker checker_s; }; /**************************************************************************************************/ template std::filesystem::path yaml_base_emitter::dst_path(const json& j, Args&&... args) { std::filesystem::path result(_dst_root); if (j.count("defined_in_file")) { const std::string& defined_in_file = j["defined_in_file"]; result /= directory_mangle(subcomponent(defined_in_file, _src_root)); } if (j.count("parents")) { for (const auto& dir : j["parents"]) { std::string dir_str{dir}; result /= directory_mangle(std::move(dir_str)); } } return dst_path_append(std::move(result), std::forward(args)...); } /**************************************************************************************************/ template std::filesystem::path yaml_base_emitter::dst_path_append(std::filesystem::path p, Arg&& arg, Args&&... args) { return dst_path_append(dst_path_append(std::move(p), arg), std::forward(args)...); } /**************************************************************************************************/ template std::filesystem::path yaml_base_emitter::dst_path_append(std::filesystem::path p, Arg&& arg) { p /= directory_mangle(arg); return p; } /**************************************************************************************************/ } // namespace hyde /**************************************************************************************************/ ================================================ FILE: emitters/yaml_base_emitter_fwd.hpp ================================================ /* Copyright 2018 Adobe All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. */ #pragma once // stdc++ #include #include #include // application #include "json.hpp" /**************************************************************************************************/ namespace hyde { /**************************************************************************************************/ static constexpr char const* tag_value_missing_k = "__MISSING__"; static constexpr char const* tag_value_optional_k = "__OPTIONAL__"; static constexpr char const* tag_value_deprecated_k = "__DEPRECATED__"; static constexpr char const* tag_value_inlined_k = "__INLINED__"; static constexpr char const* index_filename_k = "index.md"; /**************************************************************************************************/ enum class attribute_category { disabled, required, optional, deprecated, inlined }; static constexpr char const* get_tag(attribute_category c) { switch (c) { case attribute_category::required: return tag_value_missing_k; case attribute_category::optional: return tag_value_optional_k; case attribute_category::deprecated: return tag_value_deprecated_k; case attribute_category::inlined: return tag_value_inlined_k; default: throw std::invalid_argument("unexpected attribute category"); } } static inline bool is_tag(const std::string& s) { return s.substr(0, 2) == "__"; } /**************************************************************************************************/ struct emit_options { attribute_category _tested_by{attribute_category::disabled}; bool _ignore_extraneous_files{false}; }; /**************************************************************************************************/ struct documentation { json _json; std::string _remainder; bool _error{false}; }; documentation parse_documentation(const std::filesystem::path& path, bool fixup_subfield); /// @return `true` on failure to write, `false` otherwise. bool write_documentation(const documentation& docs, const std::filesystem::path& path); /**************************************************************************************************/ } // namespace hyde /**************************************************************************************************/ ================================================ FILE: emitters/yaml_class_emitter.cpp ================================================ /* Copyright 2018 Adobe All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. */ // identity #include "yaml_class_emitter.hpp" // stdc++ #include // application #include "emitters/yaml_function_emitter.hpp" #include "matchers/utilities.hpp" /**************************************************************************************************/ namespace hyde { /**************************************************************************************************/ bool yaml_class_emitter::do_merge(const std::string& filepath, const json& have, const json& expected, json& out_merged) { bool failure{false}; failure |= check_scalar(filepath, have, expected, "", out_merged, "defined_in_file"); failure |= check_scalar_array(filepath, have, expected, "", out_merged, "annotation"); failure |= check_scalar(filepath, have, expected, "", out_merged, "declaration"); failure |= check_scalar_array(filepath, have, expected, "", out_merged, "namespace"); failure |= check_scalar(filepath, have, expected, "", out_merged, "ctor"); failure |= check_scalar(filepath, have, expected, "", out_merged, "dtor"); failure |= check_map( filepath, have, expected, "", out_merged, "fields", [this](const std::string& filepath, const json& have, const json& expected, const std::string& nodepath, json& out_merged) { bool failure{false}; failure |= check_scalar(filepath, have, expected, nodepath, out_merged, "type"); failure |= check_editable_scalar(filepath, have, expected, nodepath, out_merged, "description"); failure |= check_scalar_array(filepath, have, expected, nodepath, out_merged, "annotation"); check_inline_comments(expected, out_merged); return failure; }); failure |= check_typedefs(filepath, have, expected, "", out_merged); failure |= check_object_array( filepath, have, expected, "", out_merged, "methods", "title", [this](const std::string& filepath, const json& have, const json& expected, const std::string& nodepath, json& out_merged) { yaml_function_emitter function_emitter(_src_root, _dst_root, _mode, _options, true); return function_emitter.do_merge(filepath, have, expected, out_merged); }); check_inline_comments(expected, out_merged); return failure; } /**************************************************************************************************/ bool yaml_class_emitter::emit(const json& j, json& out_emitted, const json& inherited) { json node = base_emitter_node("class", j["name"], "class", has_json_flag(j, "implicit")); node["hyde"]["defined_in_file"] = defined_in_file(j["defined_in_file"], _src_root); insert_inherited(inherited, node["hyde"]); insert_annotations(j, node["hyde"]); insert_doxygen(j, node["hyde"]); std::string declaration = format_template_parameters(j, true) + '\n' + static_cast(j["kind"]) + " " + static_cast(j["qualified_name"]) + ";"; node["hyde"]["declaration"] = std::move(declaration); for (const auto& ns : j["namespaces"]) node["hyde"]["namespace"].push_back(static_cast(ns)); if (j.count("ctor")) node["hyde"]["ctor"] = static_cast(j["ctor"]); if (j.count("dtor")) node["hyde"]["dtor"] = static_cast(j["dtor"]); if (j.count("fields")) { for (const auto& field : j["fields"]) { const std::string& key = field["name"]; auto& field_node = node["hyde"]["fields"][key]; insert_annotations(field, field_node); insert_doxygen(field, field_node); field_node["type"] = static_cast(field["type"]); const bool inline_description_exists = field_node.count("inline") && field_node["inline"].count("description"); field_node["description"] = inline_description_exists ? tag_value_inlined_k : tag_value_missing_k; } } insert_typedefs(j, node, inherited); auto dst = dst_path(j, static_cast(j["name"])); if (_mode == yaml_mode::transcribe && !exists(dst)) { // In this case the symbol name has changed, which has caused a change to the directory name // we are now trying to load and reconcile with what we've created. In this case, we can // assume the "shape" of the documentation is the same, which means that within the parent // folder of `dst` is the actual source folder that holds the old documentation, just under // a different name. Find that folder and rename it. std::filesystem::rename(derive_transcription_src_path(dst, node["title"]), dst); } bool failure = reconcile(std::move(node), _dst_root, std::move(dst) / index_filename_k, out_emitted); yaml_function_emitter function_emitter(_src_root, _dst_root, _mode, _options, true); auto emitted_methods = hyde::json::array(); const auto& methods = j["methods"]; for (auto it = methods.begin(); it != methods.end(); ++it) { function_emitter.set_key(it.key()); auto function_emitted = hyde::json::object(); failure |= function_emitter.emit(it.value(), function_emitted, out_emitted.at("hyde")); emitted_methods.push_back(std::move(function_emitted)); } out_emitted["methods"] = std::move(emitted_methods); return failure; } /**************************************************************************************************/ } // namespace hyde /**************************************************************************************************/ ================================================ FILE: emitters/yaml_class_emitter.hpp ================================================ /* Copyright 2018 Adobe All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. */ #pragma once // application #include "emitters/yaml_base_emitter.hpp" #include "json.hpp" /**************************************************************************************************/ namespace hyde { /**************************************************************************************************/ struct yaml_class_emitter : public yaml_base_emitter { explicit yaml_class_emitter(std::filesystem::path src_root, std::filesystem::path dst_root, yaml_mode mode, emit_options options) : yaml_base_emitter(std::move(src_root), std::move(dst_root), mode, std::move(options)) {} bool emit(const json& matched, json& output, const json& inherited) override; bool do_merge(const std::string& filepath, const json& have, const json& expected, json& out_merged) override; }; /**************************************************************************************************/ } // namespace hyde /**************************************************************************************************/ ================================================ FILE: emitters/yaml_enum_emitter.cpp ================================================ /* Copyright 2018 Adobe All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. */ // identity #include "yaml_enum_emitter.hpp" // stdc++ #include /**************************************************************************************************/ namespace hyde { /**************************************************************************************************/ bool yaml_enum_emitter::do_merge(const std::string& filepath, const json& have, const json& expected, json& out_merged) { bool failure{false}; failure |= check_scalar(filepath, have, expected, "", out_merged, "defined_in_file"); failure |= check_scalar_array(filepath, have, expected, "", out_merged, "annotation"); failure |= check_scalar_array(filepath, have, expected, "", out_merged, "namespace"); failure |= check_object_array( filepath, have, expected, "", out_merged, "values", "name", [this](const std::string& filepath, const json& have, const json& expected, const std::string& nodepath, json& out_merged) { bool failure{false}; failure |= check_scalar(filepath, have, expected, nodepath, out_merged, "name"); failure |= check_editable_scalar(filepath, have, expected, nodepath, out_merged, "description"); failure |= check_scalar_array(filepath, have, expected, "", out_merged, "values"); check_inline_comments(expected, out_merged); return failure; }); check_inline_comments(expected, out_merged); return failure; } /**************************************************************************************************/ bool yaml_enum_emitter::emit(const json& j, json& out_emitted, const json& inherited) { const std::string& name = j["name"]; // Most likely an enum forward declaration. Nothing to document here. if (j["values"].empty()) return true; json base_node = base_emitter_node("enumeration", j["name"], "enumeration", has_json_flag(j, "implicit")); json& node = base_node["hyde"]; insert_inherited(inherited, node); insert_annotations(j, node); insert_doxygen(j, node); if (has_inline_field(node, "owner")) { node["owner"] = tag_value_inlined_k; } if (has_inline_field(node, "brief")) { node["brief"] = tag_value_inlined_k; } node["defined_in_file"] = defined_in_file(j["defined_in_file"], _src_root); std::string filename; for (const auto& ns : j["namespaces"]) { const std::string& namespace_str = ns; node["namespace"].push_back(namespace_str); filename += namespace_str + "::"; } filename = filename_filter(std::move(filename) + name) + ".md"; for (const auto& value : j["values"]) { json cur_value; insert_doxygen(value, cur_value); cur_value["name"] = value["name"]; cur_value["description"] = has_inline_field(cur_value, "description") ? tag_value_inlined_k : tag_value_missing_k; node["values"].push_back(std::move(cur_value)); } return reconcile(std::move(base_node), _dst_root, dst_path(j) / filename, out_emitted); } /**************************************************************************************************/ } // namespace hyde /**************************************************************************************************/ ================================================ FILE: emitters/yaml_enum_emitter.hpp ================================================ /* Copyright 2018 Adobe All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. */ #pragma once // application #include "emitters/yaml_base_emitter.hpp" #include "json.hpp" /**************************************************************************************************/ namespace hyde { /**************************************************************************************************/ struct yaml_enum_emitter : public yaml_base_emitter { explicit yaml_enum_emitter(std::filesystem::path src_root, std::filesystem::path dst_root, yaml_mode mode, emit_options options) : yaml_base_emitter(std::move(src_root), std::move(dst_root), mode, std::move(options)) {} bool emit(const json& matched, json& output, const json& inherited) override; bool do_merge(const std::string& filepath, const json& have, const json& expected, json& out_merged) override; }; /**************************************************************************************************/ } // namespace hyde /**************************************************************************************************/ ================================================ FILE: emitters/yaml_function_emitter.cpp ================================================ /* Copyright 2018 Adobe All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. */ // identity #include "yaml_function_emitter.hpp" // stdc++ #include // application #include "matchers/utilities.hpp" /**************************************************************************************************/ namespace hyde { /**************************************************************************************************/ bool yaml_function_emitter::do_merge(const std::string& filepath, const json& have, const json& expected, json& out_merged) { bool failure{false}; failure |= check_scalar(filepath, have, expected, "", out_merged, "defined_in_file"); failure |= check_scalar_array(filepath, have, expected, "", out_merged, "namespace"); failure |= check_scalar(filepath, have, expected, "", out_merged, "is_ctor"); failure |= check_scalar(filepath, have, expected, "", out_merged, "is_dtor"); failure |= check_map( filepath, have, expected, "", out_merged, "overloads", [this](const std::string& filepath, const json& have, const json& expected, const std::string& nodepath, json& out_merged) { bool failure{false}; failure |= check_editable_scalar(filepath, have, expected, nodepath, out_merged, "description"); failure |= check_scalar(filepath, have, expected, nodepath, out_merged, "signature_with_names"); if (!has_json_flag(expected, "is_ctor") && !has_json_flag(expected, "is_dtor")) { failure |= check_editable_scalar(filepath, have, expected, nodepath, out_merged, "return"); } if (_options._tested_by != hyde::attribute_category::disabled) { failure |= check_editable_scalar_array(filepath, have, expected, nodepath, out_merged, "tested_by"); } failure |= check_scalar_array(filepath, have, expected, nodepath, out_merged, "annotation"); failure |= check_object_array( filepath, have, expected, nodepath, out_merged, "arguments", "name", [this](const std::string& filepath, const json& have, const json& expected, const std::string& nodepath, json& out_merged) { bool failure{false}; failure |= check_scalar(filepath, have, expected, nodepath, out_merged, "name"); failure |= check_scalar(filepath, have, expected, nodepath, out_merged, "type"); failure |= check_editable_scalar(filepath, have, expected, nodepath, out_merged, "description"); failure |= check_scalar(filepath, have, expected, nodepath, out_merged, "unnamed"); check_inline_comments(expected, out_merged); return failure; }); check_inline_comments(expected, out_merged); return failure; }); // REVISIT (fosterbrereton) : Roll-up the owners and briefs/descriptions to see if we can derive // a set of inline values here from inline values used in the function definition(s). check_inline_comments(expected, out_merged); return failure; } /**************************************************************************************************/ bool yaml_function_emitter::emit(const json& jsn, json& out_emitted, const json& inherited) { std::filesystem::path dst; std::string name; std::string filename; std::string defined_path; json overloads = json::object(); bool first{true}; bool is_ctor{false}; bool is_dtor{false}; bool all_compiler_managed{true}; std::size_t count(jsn.size()); std::size_t inline_description_count{0}; json last_inline_description; std::size_t inline_brief_count{0}; json last_inline_brief; std::vector overload_owners; for (const auto& overload : jsn) { if (first) { dst = dst_path(overload); // always the unqualified name, as free functions may be defined // over different namespaces. name = overload["short_name"]; // prefix to keep free-function from colliding with class member (e.g., `swap`) filename = (_as_methods ? "m_" : "f_") + filename_filter(name); defined_path = defined_in_file(overload["defined_in_file"], _src_root); is_ctor = has_json_flag(overload, "is_ctor"); is_dtor = has_json_flag(overload, "is_dtor"); first = false; } const std::string& key = static_cast(overload["signature"]); auto& destination = overloads[key]; // If there are any in-source (a.k.a. Doxygen) comments, insert them into // the node *first* so we can use them to decide if subsequent Hyde fields // can be deferred. insert_doxygen(overload, destination); // The intent of these checks is to roll up brief/description details that were // entered inline to the top-level file that documents this function and all of its // overrides. Note that a given override does _not_ require a `brief` value, but // _may_ require a `description` value if there is more than one override (otherwise // the description is optional, falling back to the top-level `brief` for the functions // documentation). I foresee *a lot* of conflation between `brief` and `description` // as developers document their code, so we'll have to track both of these values as if // they are the same to make it as easy as possible to bubble up information. if (destination.count("inline") && destination["inline"].count("brief")) { ++inline_brief_count; last_inline_brief = destination["inline"]["brief"]; } if (destination.count("inline") && destination["inline"].count("description")) { ++inline_description_count; last_inline_description = destination["inline"]["description"]; } if (destination.count("inline") && destination["inline"].count("owner")) { const auto& owners = destination["inline"]["owner"]; if (owners.is_string()) { overload_owners.push_back(owners.get()); } else if (owners.is_array()) { for (const auto& owner : owners) { overload_owners.push_back(owner.get()); } } } destination["signature_with_names"] = overload["signature_with_names"]; // description is now optional when there is a singular variant, or when the overload // is compiler-managed (implicit, =default, or =delete) const bool has_associated_inline = has_inline_field(destination, "description"); const bool is_implicit = has_json_flag(overload, "implicit"); const bool is_defaulted = has_json_flag(overload, "defaulted"); const bool is_deleted = has_json_flag(overload, "deleted"); const bool is_compiler_managed = is_implicit || is_defaulted || is_deleted; const bool is_optional = count <= 1 || is_compiler_managed; destination["description"] = has_associated_inline ? tag_value_inlined_k : is_optional ? tag_value_optional_k : tag_value_missing_k; if (!is_ctor && !is_dtor) { destination["return"] = tag_value_optional_k; } if (_options._tested_by != hyde::attribute_category::disabled) { destination["tested_by"] = is_compiler_managed ? tag_value_optional_k : hyde::get_tag(_options._tested_by); } insert_annotations(overload, destination); all_compiler_managed &= is_compiler_managed; if (!overload["arguments"].empty()) { std::size_t j{0}; auto& args = destination["arguments"]; for (const auto& arg : overload["arguments"]) { auto& cur_arg = args[j]; const std::string& name = arg["name"]; const bool unnamed = name.empty(); cur_arg["name"] = unnamed ? "unnamed-" + std::to_string(j) : name; cur_arg["type"] = arg["type"]; cur_arg["description"] = tag_value_optional_k; if (unnamed) { cur_arg["unnamed"] = true; } ++j; } } } json node = base_emitter_node(_as_methods ? "method" : "function", name, _as_methods ? "method" : "function", all_compiler_managed); insert_inherited(inherited, node["hyde"]); // If the function being emitted is either the ctor or dtor of a class, // the high-level `brief` is optional, as their default implementations // should require no additional commenatary beyond that which is provided // on an overload-by-overload basis. if (is_ctor || is_dtor) { node["hyde"]["brief"] = tag_value_optional_k; } // Here we roll-up the brief(s) and description(s) from the function overloads. // The complication here is that `description` may be required for the overloads, // but `brief` is not. However at the top level, `brief` _is_ required, and // `description` is not. So if there is one brief _or_ description, use that // as the inline brief for the top-level docs. (If there is one of each, brief // wins.) Beyond that, if there are multiple briefs and multiple descriptions, // we'll put some pat statement into the brief indicating as much. Finally, if // at least one overload has an inline `brief`, then the top-level `brief` is // marked inlined. if (inline_brief_count == 1) { node["hyde"]["inline"]["brief"] = last_inline_brief; node["hyde"]["brief"] = tag_value_inlined_k; } else if (inline_description_count == 1) { node["hyde"]["inline"]["brief"] = last_inline_description; node["hyde"]["brief"] = tag_value_inlined_k; } else if (inline_brief_count > 1 || inline_description_count > 1) { node["hyde"]["inline"]["brief"] = "_multiple descriptions_"; node["hyde"]["brief"] = tag_value_inlined_k; } // Round up any overload owners into an inline top-level owner field, // and then set the hyde owner field to inlined. if (!overload_owners.empty()) { std::sort(overload_owners.begin(), overload_owners.end()); overload_owners.erase(std::unique(overload_owners.begin(), overload_owners.end()), overload_owners.end()); json::array_t owners; std::copy(overload_owners.begin(), overload_owners.end(), std::back_inserter(owners)); node["hyde"]["inline"]["owner"] = std::move(owners); node["hyde"]["owner"] = tag_value_inlined_k; } else if (node["hyde"].count("inline") && node["hyde"]["inline"].count("owner")) { node["hyde"]["owner"] = tag_value_inlined_k; } // All overloads will have the same namespace if (!_as_methods && jsn.size() > 0) { for (const auto& ns : jsn.front()["namespaces"]) node["hyde"]["namespace"].push_back(static_cast(ns)); } node["hyde"]["defined_in_file"] = defined_path; node["hyde"]["overloads"] = std::move(overloads); if (is_ctor) node["hyde"]["is_ctor"] = true; if (is_dtor) node["hyde"]["is_dtor"] = true; if (_mode == yaml_mode::transcribe && !exists(dst)) { // In this case the symbol name has changed, which has caused a change to the directory name // we are now trying to load and reconcile with what we've created. In this case, we can // assume the "shape" of the documentation is the same, which means that within the parent // folder of `dst` is the actual source folder that holds the old documentation, just under // a different name. Find that folder and rename it. std::filesystem::rename(derive_transcription_src_path(dst, node["title"]), dst); } return reconcile(std::move(node), _dst_root, dst / (filename + ".md"), out_emitted); } /**************************************************************************************************/ } // namespace hyde /**************************************************************************************************/ ================================================ FILE: emitters/yaml_function_emitter.hpp ================================================ /* Copyright 2018 Adobe All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. */ #pragma once // application #include "emitters/yaml_base_emitter.hpp" #include "json.hpp" /**************************************************************************************************/ namespace hyde { /**************************************************************************************************/ struct yaml_function_emitter : public yaml_base_emitter { explicit yaml_function_emitter(std::filesystem::path src_root, std::filesystem::path dst_root, yaml_mode mode, emit_options options, bool as_methods) : yaml_base_emitter(std::move(src_root), std::move(dst_root), mode, std::move(options)), _as_methods(as_methods) {} void set_key(std::string key) { _key = std::move(key); } bool emit(const json& matched, json& output, const json& inherited) override; bool do_merge(const std::string& filepath, const json& have, const json& expected, json& out_merged) override; private: std::string _key; bool _as_methods; }; /**************************************************************************************************/ } // namespace hyde /**************************************************************************************************/ ================================================ FILE: emitters/yaml_library_emitter.cpp ================================================ /* Copyright 2018 Adobe All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. */ // identity #include "yaml_library_emitter.hpp" // stdc++ #include /**************************************************************************************************/ namespace hyde { /**************************************************************************************************/ bool yaml_library_emitter::do_merge(const std::string& filepath, const json& have, const json& expected, json& out_merged) { bool failure{false}; failure |= check_scalar(filepath, have, expected, "", out_merged, "library-type"); failure |= check_editable_scalar(filepath, have, expected, "", out_merged, "icon"); failure |= check_editable_scalar(filepath, have, expected, "", out_merged, "tab"); failure |= check_editable_scalar(filepath, have, expected, "", out_merged, "short_title"); return failure; } /**************************************************************************************************/ bool yaml_library_emitter::emit(const json& j, json& out_emitted, const json&) { json node = base_emitter_node("library", tag_value_missing_k, "library", has_json_flag(j, "implicit")); node["hyde"]["library-type"] = "library"; node["hyde"]["icon"] = tag_value_missing_k; node["hyde"]["tab"] = tag_value_missing_k; node["hyde"]["short_title"] = tag_value_optional_k; return reconcile(std::move(node), _dst_root, _dst_root / index_filename_k, out_emitted); } /**************************************************************************************************/ } // namespace hyde /**************************************************************************************************/ ================================================ FILE: emitters/yaml_library_emitter.hpp ================================================ /* Copyright 2018 Adobe All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. */ #pragma once // application #include "emitters/yaml_base_emitter.hpp" #include "json.hpp" /**************************************************************************************************/ namespace hyde { /**************************************************************************************************/ struct yaml_library_emitter : public yaml_base_emitter { explicit yaml_library_emitter(std::filesystem::path src_root, std::filesystem::path dst_root, yaml_mode mode, emit_options options) : yaml_base_emitter( std::move(src_root), std::move(dst_root), mode, std::move(options), true) {} bool emit(const json& matched, json& output, const json& inherited) override; bool do_merge(const std::string& filepath, const json& have, const json& expected, json& out_merged) override; }; /**************************************************************************************************/ } // namespace hyde /**************************************************************************************************/ ================================================ FILE: emitters/yaml_sourcefile_emitter.cpp ================================================ /* Copyright 2018 Adobe All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. */ // identity #include "yaml_sourcefile_emitter.hpp" // stdc++ #include /**************************************************************************************************/ namespace hyde { /**************************************************************************************************/ bool yaml_sourcefile_emitter::do_merge(const std::string& filepath, const json& have, const json& expected, json& out_merged) { bool failure{false}; failure |= check_scalar(filepath, have, expected, "", out_merged, "library-type"); failure |= check_typedefs(filepath, have, expected, "", out_merged); return failure; } /**************************************************************************************************/ bool yaml_sourcefile_emitter::emit(const json& j, json& out_emitted, const json& inherited) { const auto sub_path = subcomponent(static_cast(j["paths"]["src_path"]), _src_root); json node = base_emitter_node("library", sub_path.string(), "sourcefile", has_json_flag(j, "implicit")); node["hyde"]["library-type"] = "sourcefile"; insert_typedefs(j, node, inherited); _sub_dst = dst_path(j, sub_path); return reconcile(std::move(node), _dst_root, _sub_dst / index_filename_k, out_emitted); } /**************************************************************************************************/ bool yaml_sourcefile_emitter::extraneous_file_check_internal(const std::filesystem::path& root, const std::filesystem::path& path) { bool failure{false}; std::filesystem::directory_iterator first(path); std::filesystem::directory_iterator last; while (first != last) { const auto& entry = *first; if (!checker_s.checked(entry)) { std::filesystem::path entry_path(entry); if (entry_path.filename() == ".DS_Store") { std::cerr << entry_path.string() << ": Unintended OS file (not a failure)\n"; } else if (entry_path.extension() != ".cpp") { // We need better validation here against the existence of example (cpp) files. std::cerr << entry_path.string() << ": extraneous file\n"; failure = true; } } if (is_directory(entry)) { failure |= extraneous_file_check_internal(root, entry); } ++first; } return failure; } /**************************************************************************************************/ bool yaml_sourcefile_emitter::extraneous_file_check() { return extraneous_file_check_internal(_sub_dst, _sub_dst); } /**************************************************************************************************/ } // namespace hyde /**************************************************************************************************/ ================================================ FILE: emitters/yaml_sourcefile_emitter.hpp ================================================ /* Copyright 2018 Adobe All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. */ #pragma once // application #include "emitters/yaml_base_emitter.hpp" #include "json.hpp" /**************************************************************************************************/ namespace hyde { /**************************************************************************************************/ struct yaml_sourcefile_emitter : public yaml_base_emitter { explicit yaml_sourcefile_emitter(std::filesystem::path src_root, std::filesystem::path dst_root, yaml_mode mode, emit_options options) : yaml_base_emitter(std::move(src_root), std::move(dst_root), mode, std::move(options)) {} bool emit(const json& matched, json& output, const json& inherited) override; bool do_merge(const std::string& filepath, const json& have, const json& expected, json& out_merged) override; bool extraneous_file_check(); private: bool extraneous_file_check_internal(const std::filesystem::path& root, const std::filesystem::path& path); std::filesystem::path _sub_dst; }; /**************************************************************************************************/ } // namespace hyde /**************************************************************************************************/ ================================================ FILE: generate_test_files.sh ================================================ #!/bin/bash #very simple for now, look in common locations find_hyde() { BUILD_DIR=$1 if [ -f $BUILD_DIR/hyde ] then echo $BUILD_DIR/hyde return 0 fi if [ -f $BUILD_DIR/Debug/hyde ] then echo $BUILD_DIR/Debug/hyde return 0 fi if [ -f $BUILD_DIR/Release/hyde ] then echo $BUILD_DIR/Release/hyde return 0 fi return 1 } EXE_DIR=$(dirname "$0") pushd ${EXE_DIR} > /dev/null CUR_DIR=$(pwd) HYDE_PATH=`find_hyde "${CUR_DIR}/build"` for CUR_FILE in ${CUR_DIR}/test_files/*; do # echo "Processing $CUR_FILE" CUR_COMMAND="${HYDE_PATH} -hyde-update -auto-toolchain-includes -use-system-clang "$CUR_FILE" --" echo $CUR_COMMAND eval $CUR_COMMAND done popd > /dev/null ================================================ FILE: include/_clang_include_prefix.hpp ================================================ #if __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunknown-pragmas" #pragma clang diagnostic ignored "-Wall" #pragma clang diagnostic ignored "-Weverything" #elif __GNUC__ // must follow clang (which defines both) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-enum-enum-conversion" #pragma GCC diagnostic ignored "-Wunknown-pragmas" #pragma GCC diagnostic ignored "-Wall" #pragma GCC diagnostic ignored "-Wextra" #endif ================================================ FILE: include/_clang_include_suffix.hpp ================================================ #if __clang__ #pragma clang diagnostic pop #elif __GNUC__ // must follow clang (which defines both) #pragma GCC diagnostic pop #endif ================================================ FILE: include/autodetect.hpp ================================================ /* Copyright 2018 Adobe All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. */ #pragma once // stdc++ #include #include #include // application #include "config.hpp" /**************************************************************************************************/ namespace hyde { /**************************************************************************************************/ std::vector autodetect_toolchain_paths(); std::filesystem::path autodetect_resource_directory(); #if HYDE_PLATFORM(APPLE) std::filesystem::path autodetect_sysroot_directory(); #endif // HYDE_PLATFORM(APPLE) /**************************************************************************************************/ } // namespace hyde /**************************************************************************************************/ ================================================ FILE: include/config.hpp ================================================ /* Copyright 2018 Adobe All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. */ #pragma once /**************************************************************************************************/ #define HYDE_PRIVATE_STRING_XSMASH(X, Y) X##Y #define HYDE_PRIVATE_STRING_SMASH(X, Y) HYDE_PRIVATE_STRING_XSMASH(X, Y)() #define HYDE_PLATFORM_PRIVATE_APPLE() 0 #define HYDE_PLATFORM_PRIVATE_MICROSOFT() 0 #define HYDE_PLATFORM(X) HYDE_PRIVATE_STRING_SMASH(HYDE_PLATFORM_PRIVATE_, X) #if defined(__APPLE__) #undef HYDE_PLATFORM_PRIVATE_APPLE #define HYDE_PLATFORM_PRIVATE_APPLE() 1 #elif defined(_MSC_VER) #undef HYDE_PLATFORM_PRIVATE_MICROSOFT #define HYDE_PLATFORM_PRIVATE_MICROSOFT() 1 #endif /**************************************************************************************************/ ================================================ FILE: include/json.hpp ================================================ /* Copyright 2018 Adobe All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. */ #pragma once // nlohmann/json #include "nlohmann/json.hpp" // application #include "json_fwd.hpp" /**************************************************************************************************/ ================================================ FILE: include/json_fwd.hpp ================================================ /* Copyright 2018 Adobe All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. */ #pragma once // stdc++ #include // nlohmann/json #include "nlohmann/json_fwd.hpp" /**************************************************************************************************/ namespace hyde { /**************************************************************************************************/ using json = nlohmann::json; using optional_json = std::optional; /**************************************************************************************************/ } // namespace hyde /**************************************************************************************************/ ================================================ FILE: include/output_yaml.hpp ================================================ /* Copyright 2018 Adobe All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. */ #pragma once // stdc++ #include // application #include "json_fwd.hpp" #include "emitters/yaml_base_emitter_fwd.hpp" /**************************************************************************************************/ namespace hyde { /**************************************************************************************************/ enum class yaml_mode { validate, // ensure the destination docs match the shape of the generated docs update, // update the destination docs to match the shape of the generated docs transcribe // transcribe the destination docs to match the symbols put out by upgraded tooling }; /**************************************************************************************************/ void output_yaml(json j, const std::filesystem::path& src_root, const std::filesystem::path& dst_root, json& out_emitted, yaml_mode mode, const emit_options& options); /**************************************************************************************************/ } // namespace hyde /**************************************************************************************************/ ================================================ FILE: matchers/class_matcher.cpp ================================================ /* Copyright 2018 Adobe All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. */ // identity #include "class_matcher.hpp" // stdc++ #include // clang/llvm // clang-format off #include "_clang_include_prefix.hpp" // must be first to disable warnings for clang headers #include "clang/AST/ASTConsumer.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "_clang_include_suffix.hpp" // must be last to re-enable warnings // clang-format on // application #include "json.hpp" #include "matchers/utilities.hpp" using namespace clang; using namespace clang::ast_matchers; /**************************************************************************************************/ class FindDestructor : public RecursiveASTVisitor { public: bool VisitDecl(const Decl* d) { if (isa(d)) _found = true; return !_found; } explicit operator bool() const { return _found; } private: bool _found{false}; }; /**************************************************************************************************/ class FindConstructor : public RecursiveASTVisitor { public: bool VisitDecl(const Decl* d) { if (isa(d)) _found = true; return !_found; } explicit operator bool() const { return _found; } private: bool _found{false}; }; /**************************************************************************************************/ class FindStaticMembers : public RecursiveASTVisitor { public: FindStaticMembers(const hyde::processing_options& options) : _options(options), _static_members(hyde::json::object()) {} bool VisitVarDecl(const VarDecl* d) { if (!AccessCheck(_options._access_filter, d->getAccess())) return true; auto storage = d->getStorageClass(); // TODO(Wyles): Do we want to worry about other kinds of storage? if (storage == SC_Static) { auto type_opt = hyde::StandardDeclInfo(_options, d); if (!type_opt) return true; auto type = std::move(*type_opt); auto name = type["qualified_name"].get(); type["static"] = true; type["type"] = hyde::to_string(d, d->getType()); _static_members[name] = type; } return true; } const hyde::json& get_results() const { return _static_members; } private: const hyde::processing_options& _options; hyde::json _static_members; }; namespace hyde { /**************************************************************************************************/ json fixup_short_name(json&& method) { // We have encountered cases with the latest clang drivers where the short_name field for some // methods is missing. In such case we try to cobble up a solution by finding the // first sequence of alphanumeric characters after the return type. If _that_ doesn't work, // then we fall back on the signature name. if (method.count("short_name") && method.at("short_name").is_string() && !static_cast(method.at("short_name")).empty()) { return std::move(method); } std::string short_name = method["signature"]; if (method.count("return_type") && method["return_type"].is_string() && method.count("signature") && method["signature"].is_string()) { const std::string& return_type = method["return_type"]; const std::string& signature = method["signature"]; const auto offset = signature.find(return_type); if (offset != std::string::npos) { const auto start = offset + return_type.size() + 1; const auto end = signature.find_first_of("(", start); short_name = signature.substr(start, end - start); } } method["short_name"] = std::move(short_name); return std::move(method); } /**************************************************************************************************/ void ClassInfo::run(const MatchFinder::MatchResult& Result) { auto clas = Result.Nodes.getNodeAs("class"); if (!clas->isCompleteDefinition()) return; // e.g., a forward declaration. if (clas->isLambda()) return; if (!clas->getSourceRange().isValid()) return; // e.g., compiler-injected class specialization // e.g., compiler-injected class specializations not caught by the above if (auto s = llvm::dyn_cast_or_null(clas)) { if (!s->getTypeAsWritten()) return; } auto info_opt = DetailCXXRecordDecl(_options, clas); if (!info_opt) return; auto info = std::move(*info_opt); if (NamespaceBlacklist(_options._namespace_blacklist, info)) return; info["kind"] = clas->getKindName(); info["methods"] = json::object(); FindConstructor ctor_finder; ctor_finder.TraverseDecl(const_cast(static_cast(clas))); if (!ctor_finder) info["ctor"] = "unspecified"; FindDestructor dtor_finder; dtor_finder.TraverseDecl(const_cast(static_cast(clas))); if (!dtor_finder) info["dtor"] = "unspecified"; FindStaticMembers static_finder(_options); static_finder.TraverseDecl(const_cast(static_cast(clas))); if (const auto& template_decl = clas->getDescribedClassTemplate()) { info["template_parameters"] = GetTemplateParameters(Result.Context, template_decl); } for (const auto& method : clas->methods()) { auto methodInfo_opt = DetailFunctionDecl(_options, method); if (!methodInfo_opt) continue; auto methodInfo = std::move(*methodInfo_opt); methodInfo = fixup_short_name(std::move(methodInfo)); const auto& short_name = static_cast(methodInfo["short_name"]); info["methods"][short_name].push_back(std::move(methodInfo)); } for (const auto& decl : clas->decls()) { auto* function_template_decl = dyn_cast(decl); if (!function_template_decl) continue; auto methodInfo_opt = DetailFunctionDecl(_options, function_template_decl->getTemplatedDecl()); if (!methodInfo_opt) continue; auto methodInfo = std::move(*methodInfo_opt); methodInfo = fixup_short_name(std::move(methodInfo)); const auto& short_name = static_cast(methodInfo["short_name"]); info["methods"][short_name].push_back(std::move(methodInfo)); } for (const auto& field : clas->fields()) { auto fieldInfo_opt = StandardDeclInfo(_options, field); if (!fieldInfo_opt) continue; auto fieldInfo = std::move(*fieldInfo_opt); fieldInfo["type"] = hyde::to_string(field, field->getType()); info["fields"][static_cast(fieldInfo["qualified_name"])] = fieldInfo; // can't move this into place for some reason. } hyde::json static_members = static_finder.get_results(); if (static_members.size() > 0) { if (info["fields"].size() == 0) { info["fields"] = hyde::json::object(); } info["fields"].insert(static_members.begin(), static_members.end()); } using typedef_iterator = CXXRecordDecl::specific_decl_iterator; using typedef_range = llvm::iterator_range; typedef_range typedefs(typedef_iterator(clas->decls_begin()), typedef_iterator(CXXRecordDecl::decl_iterator())); for (const auto& type_def : typedefs) { // REVISIT (fbrereto) : Refactor this block and TypedefInfo::run's. auto typedefInfo_opt = StandardDeclInfo(_options, type_def); if (!typedefInfo_opt) continue; auto typedefInfo = std::move(*typedefInfo_opt); typedefInfo["type"] = hyde::to_string(type_def, type_def->getUnderlyingType()); info["typedefs"].push_back(std::move(typedefInfo)); } using typealias_iterator = CXXRecordDecl::specific_decl_iterator; using typealias_range = llvm::iterator_range; typealias_range typealiases(typealias_iterator(clas->decls_begin()), typealias_iterator(CXXRecordDecl::decl_iterator())); for (const auto& type_alias : typealiases) { // REVISIT (fbrereto) : Refactor this block and TypeAliasInfo::run's. auto typealiasInfo_opt = StandardDeclInfo(_options, type_alias); if (!typealiasInfo_opt) continue; auto typealiasInfo = std::move(*typealiasInfo_opt); typealiasInfo["type"] = hyde::to_string(type_alias, type_alias->getUnderlyingType()); if (auto template_decl = type_alias->getDescribedAliasTemplate()) { typealiasInfo["template_parameters"] = GetTemplateParameters(Result.Context, template_decl); } info["typealiases"].push_back(std::move(typealiasInfo)); } _j["classes"].push_back(std::move(info)); } /**************************************************************************************************/ } // namespace hyde /**************************************************************************************************/ ================================================ FILE: matchers/class_matcher.hpp ================================================ /* Copyright 2018 Adobe All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. */ #pragma once // clang/llvm // clang-format off #include "_clang_include_prefix.hpp" // must be first to disable warnings for clang headers #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "_clang_include_suffix.hpp" // must be last to re-enable warnings // clang-format on // application #include "json.hpp" #include "matchers/matcher_fwd.hpp" using namespace clang; using namespace clang::ast_matchers; /**************************************************************************************************/ namespace hyde { /**************************************************************************************************/ class ClassInfo : public MatchFinder::MatchCallback { public: explicit ClassInfo(processing_options options) : _options(std::move(options)) { _j["class"] = json::array(); } void run(const MatchFinder::MatchResult& Result) override; json getJSON() { return _j; } static DeclarationMatcher GetMatcher() { return cxxRecordDecl().bind("class"); } private: processing_options _options; json _j; }; /**************************************************************************************************/ } // namespace hyde /**************************************************************************************************/ ================================================ FILE: matchers/enum_matcher.cpp ================================================ /* Copyright 2018 Adobe All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. */ // identity #include "enum_matcher.hpp" // stdc++ #include // clang/llvm // clang-format off #include "_clang_include_prefix.hpp" // must be first to disable warnings for clang headers #include "clang/AST/ASTConsumer.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "_clang_include_suffix.hpp" // must be last to re-enable warnings // clang-format on // application #include "json.hpp" #include "matchers/utilities.hpp" using namespace clang; using namespace clang::ast_matchers; /**************************************************************************************************/ namespace hyde { /**************************************************************************************************/ void EnumInfo::run(const MatchFinder::MatchResult& Result) { auto enumeration = Result.Nodes.getNodeAs("enum"); auto info_opt = StandardDeclInfo(_options, enumeration); if (!info_opt) return; auto info = std::move(*info_opt); // info["scoped"] = enumeration->isScoped(); // info["fixed"] = enumeration->isFixed(); info["type"] = hyde::to_string(enumeration, enumeration->getIntegerType()); info["values"] = json::array(); for (const auto& p : enumeration->enumerators()) { json enumerator = json::object(); enumerator["name"] = p->getNameAsString(); if (auto comments = ProcessComments(p)) { enumerator["comments"] = std::move(*comments); } info["values"].push_back(std::move(enumerator)); } _j["enums"].push_back(std::move(info)); } /**************************************************************************************************/ } // namespace hyde /**************************************************************************************************/ ================================================ FILE: matchers/enum_matcher.hpp ================================================ /* Copyright 2018 Adobe All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. */ #pragma once // clang/llvm // clang-format off #include "_clang_include_prefix.hpp" // must be first to disable warnings for clang headers #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "_clang_include_suffix.hpp" // must be last to re-enable warnings // clang-format on // application #include "json.hpp" #include "matchers/matcher_fwd.hpp" using namespace clang; using namespace clang::ast_matchers; /**************************************************************************************************/ namespace hyde { /**************************************************************************************************/ class EnumInfo : public MatchFinder::MatchCallback { public: EnumInfo(processing_options options) : _options(std::move(options)) { _j["enums"] = json::array(); } void run(const MatchFinder::MatchResult& Result) override; json getJSON() { return _j; } static DeclarationMatcher GetMatcher() { return enumDecl().bind("enum"); } private: processing_options _options; json _j; }; /**************************************************************************************************/ } // namespace hyde /**************************************************************************************************/ ================================================ FILE: matchers/function_matcher.cpp ================================================ /* Copyright 2018 Adobe All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. */ // identity #include "function_matcher.hpp" // stdc++ #include // clang/llvm // clang-format off #include "_clang_include_prefix.hpp" // must be first to disable warnings for clang headers #include "clang/AST/ASTConsumer.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Lex/Lexer.h" #include "_clang_include_suffix.hpp" // must be last to re-enable warnings // clang-format on // application #include "json.hpp" #include "matchers/utilities.hpp" using namespace clang; using namespace clang::ast_matchers; /**************************************************************************************************/ namespace hyde { /**************************************************************************************************/ void FunctionInfo::run(const MatchFinder::MatchResult& Result) { auto function = Result.Nodes.getNodeAs("func"); // Do not process class methods here. if (!_options._process_class_methods) { if (llvm::dyn_cast_or_null(function)) return; } auto info_opt = DetailFunctionDecl(_options, function); if (!info_opt) return; auto info = std::move(*info_opt); const std::string& short_name(info["short_name"]); // Omit compiler-reserved functions if (short_name.find("__") == 0) return; _j["functions"][short_name].push_back(std::move(info)); } /**************************************************************************************************/ } // namespace hyde /**************************************************************************************************/ ================================================ FILE: matchers/function_matcher.hpp ================================================ /* Copyright 2018 Adobe All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. */ #pragma once // clang/llvm // clang-format off #include "_clang_include_prefix.hpp" // must be first to disable warnings for clang headers #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "_clang_include_suffix.hpp" // must be last to re-enable warnings // clang-format on // application #include "json.hpp" #include "matchers/matcher_fwd.hpp" using namespace clang; using namespace clang::ast_matchers; /**************************************************************************************************/ namespace hyde { /**************************************************************************************************/ class FunctionInfo : public MatchFinder::MatchCallback { public: FunctionInfo(processing_options options) : _options(std::move(options)) { _j["functions"] = json::object(); } void run(const MatchFinder::MatchResult& Result) override; json getJSON() { return _j; } static DeclarationMatcher GetMatcher() { return functionDecl().bind("func"); } private: processing_options _options; json _j; }; /**************************************************************************************************/ } // namespace hyde /**************************************************************************************************/ ================================================ FILE: matchers/matcher_fwd.hpp ================================================ /* Copyright 2018 Adobe All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. */ #pragma once // stdc++ #include #include /**************************************************************************************************/ namespace hyde { /**************************************************************************************************/ enum ToolAccessFilter { ToolAccessFilterPrivate, ToolAccessFilterProtected, ToolAccessFilterPublic, }; /**************************************************************************************************/ struct processing_options { std::vector _paths; ToolAccessFilter _access_filter; std::vector _namespace_blacklist; bool _process_class_methods; }; /**************************************************************************************************/ } // namespace hyde /**************************************************************************************************/ ================================================ FILE: matchers/namespace_matcher.cpp ================================================ /* Copyright 2018 Adobe All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. */ // identity #include "namespace_matcher.hpp" // stdc++ #include // clang/llvm // clang-format off #include "_clang_include_prefix.hpp" // must be first to disable warnings for clang headers #include "clang/AST/ASTConsumer.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "_clang_include_suffix.hpp" // must be last to re-enable warnings // clang-format on // application #include "json.hpp" #include "matchers/utilities.hpp" using namespace clang; using namespace clang::ast_matchers; /**************************************************************************************************/ namespace hyde { /**************************************************************************************************/ void NamespaceInfo::run(const MatchFinder::MatchResult& Result) { auto ns = Result.Nodes.getNodeAs("ns"); auto info_opt = StandardDeclInfo(_options, ns); if (!info_opt) return; auto info = std::move(*info_opt); _j["namespaces"].push_back(std::move(info)); } /**************************************************************************************************/ } // namespace hyde /**************************************************************************************************/ ================================================ FILE: matchers/namespace_matcher.hpp ================================================ /* Copyright 2018 Adobe All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. */ #pragma once // clang/llvm // clang-format off #include "_clang_include_prefix.hpp" // must be first to disable warnings for clang headers #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "_clang_include_suffix.hpp" // must be last to re-enable warnings // clang-format on // application #include "json.hpp" #include "matchers/matcher_fwd.hpp" using namespace clang; using namespace clang::ast_matchers; /**************************************************************************************************/ namespace hyde { /**************************************************************************************************/ class NamespaceInfo : public MatchFinder::MatchCallback { public: NamespaceInfo(processing_options options) : _options(std::move(options)) { _j["namespaces"] = json::array(); } void run(const MatchFinder::MatchResult& Result) override; json getJSON() { return _j; } static DeclarationMatcher GetMatcher() { return namespaceDecl().bind("ns"); } private: processing_options _options; json _j; }; /**************************************************************************************************/ } // namespace hyde /**************************************************************************************************/ ================================================ FILE: matchers/typealias_matcher.cpp ================================================ /* Copyright 2018 Adobe All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. */ // identity #include "typealias_matcher.hpp" // stdc++ #include // clang/llvm // clang-format off #include "_clang_include_prefix.hpp" // must be first to disable warnings for clang headers #include "clang/AST/ASTConsumer.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "_clang_include_suffix.hpp" // must be last to re-enable warnings // clang-format on // application #include "json.hpp" #include "matchers/utilities.hpp" using namespace clang; using namespace clang::ast_matchers; /**************************************************************************************************/ namespace hyde { /**************************************************************************************************/ void TypeAliasInfo::run(const MatchFinder::MatchResult& Result) { auto node = Result.Nodes.getNodeAs("typealias"); auto info_opt = StandardDeclInfo(_options, node); if (!info_opt) return; auto info = std::move(*info_opt); // do not process class type aliases here. if (!info["parents"].empty()) return; info["type"] = hyde::to_string(node, node->getUnderlyingType()); if (auto template_decl = node->getDescribedAliasTemplate()) { info["template_parameters"] = GetTemplateParameters(Result.Context, template_decl); } _j["typealiases"].push_back(std::move(info)); } /**************************************************************************************************/ } // namespace hyde /**************************************************************************************************/ ================================================ FILE: matchers/typealias_matcher.hpp ================================================ /* Copyright 2018 Adobe All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. */ #pragma once // clang/llvm // clang-format off #include "_clang_include_prefix.hpp" // must be first to disable warnings for clang headers #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "_clang_include_suffix.hpp" // must be last to re-enable warnings // clang-format on // application #include "json.hpp" #include "matchers/matcher_fwd.hpp" using namespace clang; using namespace clang::ast_matchers; /**************************************************************************************************/ namespace hyde { /**************************************************************************************************/ class TypeAliasInfo : public MatchFinder::MatchCallback { public: TypeAliasInfo(processing_options options) : _options(std::move(options)) { _j["typealiases"] = json::array(); } void run(const MatchFinder::MatchResult& Result) override; json getJSON() { return _j; } static DeclarationMatcher GetMatcher() { return typeAliasDecl().bind("typealias"); } private: processing_options _options; json _j; }; /**************************************************************************************************/ } // namespace hyde /**************************************************************************************************/ ================================================ FILE: matchers/typedef_matcher.cpp ================================================ /* Copyright 2018 Adobe All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. */ // identity #include "typedef_matcher.hpp" // stdc++ #include // clang/llvm // clang-format off #include "_clang_include_prefix.hpp" // must be first to disable warnings for clang headers #include "clang/AST/ASTConsumer.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "_clang_include_suffix.hpp" // must be last to re-enable warnings // clang-format on // application #include "json.hpp" #include "matchers/utilities.hpp" using namespace clang; using namespace clang::ast_matchers; /**************************************************************************************************/ namespace hyde { /**************************************************************************************************/ void TypedefInfo::run(const MatchFinder::MatchResult& Result) { auto node = Result.Nodes.getNodeAs("typedef"); auto info_opt = StandardDeclInfo(_options, node); if (!info_opt) return; auto info = std::move(*info_opt); // do not process class type aliases here. if (!info["parents"].empty()) return; info["type"] = hyde::to_string(node, node->getUnderlyingType()); _j["typedefs"].push_back(std::move(info)); } /**************************************************************************************************/ } // namespace hyde /**************************************************************************************************/ ================================================ FILE: matchers/typedef_matcher.hpp ================================================ /* Copyright 2018 Adobe All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. */ #pragma once // clang/llvm // clang-format off #include "_clang_include_prefix.hpp" // must be first to disable warnings for clang headers #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "_clang_include_suffix.hpp" // must be last to re-enable warnings // clang-format on // application #include "json.hpp" #include "matchers/matcher_fwd.hpp" using namespace clang; using namespace clang::ast_matchers; /**************************************************************************************************/ namespace hyde { /**************************************************************************************************/ class TypedefInfo : public MatchFinder::MatchCallback { public: TypedefInfo(processing_options options) : _options(std::move(options)) { _j["typedefs"] = json::array(); } void run(const MatchFinder::MatchResult& Result) override; json getJSON() { return _j; } static DeclarationMatcher GetMatcher() { return typedefDecl().bind("typedef"); } private: processing_options _options; json _j; }; /**************************************************************************************************/ } // namespace hyde /**************************************************************************************************/ ================================================ FILE: matchers/utilities.cpp ================================================ /* Copyright 2018 Adobe All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. */ // identity #include "utilities.hpp" // stdc++ #include #include #include // clang/llvm // clang-format off #include "_clang_include_prefix.hpp" // must be first to disable warnings for clang headers #include "clang/AST/ASTConsumer.h" #include "clang/AST/ASTContext.h" #include "clang/AST/Comment.h" #include "clang/AST/Type.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Lex/Lexer.h" #include "llvm/ADT/ArrayRef.h" #include "_clang_include_suffix.hpp" // must be last to re-enable warnings // clang-format on // diff #include "diff/myers.hpp" // application #include "emitters/yaml_base_emitter_fwd.hpp" #include "json.hpp" using namespace clang; using namespace clang::ast_matchers; using namespace clang::comments; /**************************************************************************************************/ namespace { /**************************************************************************************************/ // TODO: Make this `std::string_view trim_front(std::string_view src)` void trim_front(std::string& text) { std::size_t trim_count(0); for (; trim_count < text.size(); ++trim_count) if (!(std::isspace(text[trim_count]) || text[trim_count] == '\n')) break; text.erase(0, trim_count); } /**************************************************************************************************/ // TODO: Make this `std::string_view trim_back(std::string_view src)` void trim_back(std::string& src) { std::size_t start(src.size()); while (start != 0 && (std::isspace(src[start - 1]) || src[start - 1] == '\n')) start--; src.erase(start, std::string::npos); } /**************************************************************************************************/ // TODO: Make this `std::string_view chomp(std::string_view src)` void chomp(std::string& src) { trim_front(src); trim_back(src); } /**************************************************************************************************/ // If `as_token` is true, the end of the range is assumed to be the beginning of a token, and the // range will be extended to the end of that token before it is serialized. Otherwise, the end of // the range is unchanged before serialization. std::string to_string(const ASTContext* n, SourceRange range, bool as_token) { const auto char_range = as_token ? clang::CharSourceRange::getTokenRange(range) : clang::CharSourceRange::getCharRange(range); // TODO: Make `result` a std::string_view std::string result = Lexer::getSourceText(char_range, n->getSourceManager(), n->getLangOpts()).str(); trim_back(result); return result; } /**************************************************************************************************/ std::string to_string(const ASTContext* n, const clang::TemplateDecl* template_decl) { std::size_t count{0}; std::string result = "template <"; for (const auto& parameter_decl : *template_decl->getTemplateParameters()) { if (count++) result += ", "; if (auto* template_type = dyn_cast(parameter_decl)) { result += template_type->wasDeclaredWithTypename() ? "typename" : "class"; if (template_type->isParameterPack()) result += "..."; result += " " + template_type->getNameAsString(); } else if (auto* non_template_type = dyn_cast(parameter_decl)) { result += hyde::to_string(non_template_type, non_template_type->getType()); if (non_template_type->isParameterPack()) result += "..."; result += " " + non_template_type->getNameAsString(); } } result += ">\n"; return result; } /**************************************************************************************************/ template void ForEachParent(const Decl* d, F f) { if (!d) return; auto& context = d->getASTContext(); auto parents = context.getParents(*d); while (true) { if (parents.size() == 0) break; if (parents.size() != 1) { assert(false && "What exactly is going on here?"); } auto* parent = parents.begin(); if (auto* node = parent->get()) { f(node); } parents = context.getParents(*parent); } } /**************************************************************************************************/ enum class signature_options : std::uint8_t { none = 0, fully_qualified = 1 << 0, named_args = 1 << 1, }; template bool flag_set(const T& value, const T& flag) { using type = std::underlying_type_t; return (static_cast(value) & static_cast(flag)) != 0; } /**************************************************************************************************/ // See DeclPrinter::VisitFunctionDecl in clang/lib/AST/DeclPrinter.cpp for hints // on how to make this routine better. std::string GetSignature(const ASTContext* n, const FunctionDecl* function, signature_options options = signature_options::none) { if (!function) return ""; bool fully_qualified = flag_set(options, signature_options::fully_qualified); bool named_args = flag_set(options, signature_options::named_args); bool isTrailing = false; std::stringstream signature; if (const auto* fp = function->getType()->getAs()) { isTrailing = fp->hasTrailingReturn(); } if (auto template_decl = function->getDescribedFunctionTemplate()) { signature << to_string(n, template_decl); } if (auto ctor_decl = llvm::dyn_cast_or_null(function)) { auto specifier = ctor_decl->getExplicitSpecifier(); if (specifier.isExplicit()) signature << "explicit "; } else if (auto conversion_decl = llvm::dyn_cast_or_null(function)) { auto specifier = conversion_decl->getExplicitSpecifier(); if (specifier.isExplicit()) signature << "explicit "; } if (!isa(function) && !isa(function) && !isa(function)) { if (function->isConstexpr()) { signature << "constexpr "; } switch (function->getStorageClass()) { case SC_Static: signature << "static "; break; case SC_Extern: signature << "extern "; break; default: break; } if (isTrailing) { signature << "auto "; } else { signature << hyde::to_string(function, function->getReturnType()) << " "; } } if (fully_qualified) { bool first{true}; for (const auto& ns : hyde::GetParentNamespaces(n, function)) { if (!first) signature << "::"; first = false; signature << static_cast(ns); } for (const auto& p : hyde::GetParentCXXRecords(n, function)) { if (!first) signature << "::"; first = false; signature << static_cast(p); } if (!first) signature << "::"; } if (auto conversionDecl = llvm::dyn_cast_or_null(function)) { signature << "operator " << hyde::to_string(conversionDecl, conversionDecl->getConversionType()); } else { signature << hyde::PostProcessType(function, function->getNameInfo().getAsString()); } signature << "("; for (int i = 0, paramsCount = function->getNumParams(); i < paramsCount; ++i) { if (i) signature << ", "; auto* paramDecl = function->getParamDecl(i); signature << hyde::to_string(paramDecl, paramDecl->getType()); if (named_args) { auto arg_name = function->getParamDecl(i)->getNameAsString(); if (!arg_name.empty()) { signature << " " << std::move(arg_name); } } } if (function->isVariadic()) signature << ", ..."; signature << ")"; const auto* functionT = llvm::dyn_cast_or_null(function->getType().getTypePtr()); bool canHaveCV = functionT || isa(function); if (isTrailing) { if (canHaveCV) { // bit of repetition but hey not much. if (functionT->isConst()) signature << " const"; if (functionT->isVolatile()) signature << " volatile"; if (functionT->isRestrict()) signature << " restrict"; } signature << " -> " << hyde::to_string(function, function->getReturnType()); } if (!canHaveCV) { return signature.str(); } if (!isTrailing && functionT) { if (functionT->isConst()) signature << " const"; if (functionT->isVolatile()) signature << " volatile"; if (functionT->isRestrict()) signature << " restrict"; } if (const auto* functionPT = dyn_cast_or_null(function->getType().getTypePtr())) { switch (functionPT->getRefQualifier()) { case RQ_LValue: signature << " &"; break; case RQ_RValue: signature << " &&"; break; default: break; } } return signature.str(); } /**************************************************************************************************/ std::string GetShortName(const clang::ASTContext* n, const clang::FunctionDecl* function) { // The "short name" is the unqualified-id of the function, running between // the return type and the open paren. It's used e.g., for the file names // being output. std::string result = hyde::PostProcessType(function, function->getNameInfo().getAsString()); if (!isa(function) && !isa(function) && !isa(function)) { std::string return_type = hyde::to_string(function, function->getReturnType()); auto return_pos = result.find(return_type); if (return_pos != std::string::npos) { result = result.substr(return_pos + return_type.size(), std::string::npos); } } return result; } /**************************************************************************************************/ template hyde::json GetParents(const ASTContext* n, const Decl* d) { hyde::json result = hyde::json::array(); if (!n || !d) return result; auto parent = const_cast(n)->getParents(*d); while (true) { if (parent.size() == 0) break; if (parent.size() != 1) { assert(false && "What exactly is going on here?"); } auto node = parent.begin()->get(); if (node) { std::string name = node->getNameAsString(); if (auto specialization = dyn_cast_or_null(node)) { if (auto taw = specialization->getTypeAsWritten()) { name = hyde::to_string(specialization, taw->getType()); } else { } } else if (auto cxxrecord = dyn_cast_or_null(node)) { if (auto template_decl = cxxrecord->getDescribedClassTemplate()) { name += hyde::GetArgumentList(template_decl->getTemplateParameters()->asArray()); } } result.push_back(std::move(name)); } parent = const_cast(n)->getParents(*parent.begin()); } std::reverse(result.begin(), result.end()); return result; } /**************************************************************************************************/ inline std::string_view to_string_view(StringRef string) { return std::string_view(string.data(), string.size()); } /**************************************************************************************************/ inline std::string_view to_string_view(ParamCommandPassDirection x) { // clang-format off switch (x) { case ParamCommandPassDirection::In: return "in"; case ParamCommandPassDirection::InOut: return "inout"; case ParamCommandPassDirection::Out: return "out"; } // clang-format on return "in"; // gcc on linux is asking for this. } /**************************************************************************************************/ hyde::optional_json ProcessComment(const ASTContext& n, const FullComment* full_comment, const Comment* comment) { const CommandTraits& commandTraits = n.getCommentCommandTraits(); hyde::json::object_t result; const auto process_comment_args = [](auto& comment_with_args) -> hyde::optional_json { const unsigned arg_count = comment_with_args.getNumArgs(); if (arg_count == 0) return std::nullopt; hyde::json::array_t result; for (unsigned i{0}; i < arg_count; ++i) { hyde::json::object_t args_entry; args_entry["text"] = to_string_view(comment_with_args.getArgText(i)); result.push_back(std::move(args_entry)); } return result; }; const auto process_comment_children = [&n, &full_comment](auto& comment_with_children) -> hyde::optional_json { auto first = comment_with_children.child_begin(); auto last = comment_with_children.child_end(); if (first == last) return std::nullopt; hyde::json::array_t result; while (first != last) { if (auto entry = ProcessComment(n, full_comment, *first++)) { result.emplace_back(std::move(*entry)); } } return result; }; // If the comment only has one child and it's a paragraph comment, // take the text from that child and move it into the parent, then // delete all the children (which is just the now-empty paragraph.) const auto roll_up_single_paragraph_child = [](hyde::json::object_t json) -> hyde::json::object_t { if (json.count("children") != 1) return json; auto& children = json["children"]; auto& first_child = *children.begin(); if (first_child["kind"] != "ParagraphComment") return json; json["text"] = std::move(first_child["text"]); json.erase("children"); return json; }; const auto post_process_hyde_command = [](hyde::json::object_t json) -> hyde::json::object_t { if (json["name"].get() != "hyde") return json; const std::string text = json["text"].get(); const auto first_space = text.find_first_of(" \t\n\r\v\f"); // \v and \f? Really? if (first_space == std::string::npos) return json; const std::string new_command = "hyde" + text.substr(0, first_space); const std::string new_text = text.substr(first_space + 1); json["name"] = std::move(new_command); json["text"] = std::move(new_text); return json; }; switch (comment->getCommentKind()) { case CommentKind::None: break; case CommentKind::BlockCommandComment: { const BlockCommandComment* block_command_comment = llvm::dyn_cast_or_null(comment); assert(block_command_comment); result["name"] = to_string_view(block_command_comment->getCommandName(commandTraits)); if (auto args = process_comment_args(*block_command_comment)) { result["args"] = std::move(*args); } if (auto children = process_comment_children(*block_command_comment)) { result["children"] = std::move(*children); } result = roll_up_single_paragraph_child(std::move(result)); // Do further post-processing if the comment is a hyde command. result = post_process_hyde_command(std::move(result)); } break; case CommentKind::ParamCommandComment: { const ParamCommandComment* param_command_comment = llvm::dyn_cast_or_null(comment); assert(param_command_comment); result["direction"] = to_string_view(param_command_comment->getDirection()); result["direction_explicit"] = param_command_comment->isDirectionExplicit(); result["index_valid"] = param_command_comment->isParamIndexValid(); result["vararg_param"] = param_command_comment->isVarArgParam(); if (param_command_comment->hasParamName()) { result["name"] = to_string_view(param_command_comment->getParamName(full_comment)); } if (auto children = process_comment_children(*param_command_comment)) { result["children"] = std::move(*children); } result = roll_up_single_paragraph_child(std::move(result)); } break; case CommentKind::TParamCommandComment: { const TParamCommandComment* tparam_command_comment = llvm::dyn_cast_or_null(comment); assert(tparam_command_comment); if (auto children = process_comment_children(*tparam_command_comment)) { result["children"] = std::move(*children); } result = roll_up_single_paragraph_child(std::move(result)); } break; case CommentKind::VerbatimBlockComment: { const VerbatimBlockComment* verbatim_block_comment = llvm::dyn_cast_or_null(comment); assert(verbatim_block_comment); if (auto children = process_comment_children(*verbatim_block_comment)) { result["children"] = std::move(*children); } } break; case CommentKind::VerbatimLineComment: { const VerbatimLineComment* verbatim_line_comment = llvm::dyn_cast_or_null(comment); assert(verbatim_line_comment); if (auto children = process_comment_children(*verbatim_line_comment)) { result["children"] = std::move(*children); } } break; case CommentKind::ParagraphComment: { const ParagraphComment* paragraph_comment = llvm::dyn_cast_or_null(comment); assert(paragraph_comment); // Paragraph comments have only been observed containing `TextComment`s, // one per line in the paragraph. Some formatting gets sucked into the // `TextComment`s, so processing of each is needed. auto first = paragraph_comment->child_begin(); auto last = paragraph_comment->child_end(); std::string paragraph; while (first != last) { if (const TextComment* text_comment = llvm::dyn_cast_or_null(*first++)) { std::string line(text_comment->getText()); chomp(line); if (line.empty()) continue; if (!paragraph.empty()) paragraph += ' '; // add one space between lines. paragraph += std::move(line); } } if (!paragraph.empty()) { result["text"] = std::move(paragraph); } } break; case CommentKind::FullComment: { const FullComment* full_comment_inner = llvm::dyn_cast_or_null(comment); assert(full_comment_inner); if (auto children = process_comment_children(*full_comment_inner)) { result["children"] = std::move(*children); } } break; case CommentKind::HTMLEndTagComment: break; case CommentKind::HTMLStartTagComment: break; case CommentKind::InlineCommandComment: { const InlineCommandComment* inline_command_comment = llvm::dyn_cast_or_null(comment); assert(inline_command_comment); result["name"] = to_string_view(inline_command_comment->getCommandName(commandTraits)); if (auto args = process_comment_args(*inline_command_comment)) { result["args"] = std::move(*args); } } break; case CommentKind::TextComment: { const TextComment* text_comment = llvm::dyn_cast_or_null(comment); assert(text_comment); if (auto children = process_comment_children(*text_comment)) { result["children"] = std::move(*children); } result["text"] = to_string_view(text_comment->getText()); } break; case CommentKind::VerbatimBlockLineComment: break; } if (result.empty()) { return std::nullopt; } result["kind"] = comment->getCommentKindName(); return result; } const char* remap_kind_key(std::string_view key) { // clang-format off if (key == "BlockCommandComment") return "command"; else if (key == "FullComment") return "full"; else if (key == "HTMLEndTagComment") return "html_end"; else if (key == "HTMLStartTagComment") return "html_start"; else if (key == "InlineCommandComment") return "command_inline"; else if (key == "NoComment") return "none"; else if (key == "ParagraphComment") return "paragraph"; else if (key == "ParamCommandComment") return "param"; else if (key == "TextComment") return "text"; else if (key == "TParamCommandComment") return "tparam"; else if (key == "VerbatimBlockComment") return "vblock"; else if (key == "VerbatimBlockLineComment") return "vblockline"; else if (key == "VerbatimLineComment") return "vline"; // clang-format on return key.data(); } hyde::optional_json group_comments_by_kind(hyde::json comments) { if (!comments.is_array()) return std::nullopt; hyde::json result; for (auto& comment : comments.get()) { const char* new_key = remap_kind_key(comment["kind"].get()); comment.erase("kind"); result[new_key].push_back(std::move(comment)); } return result; } /**************************************************************************************************/ } // namespace /**************************************************************************************************/ namespace hyde { /**************************************************************************************************/ json GetParentNamespaces(const ASTContext* n, const Decl* d) { return GetParents(n, d); } /**************************************************************************************************/ json GetParentCXXRecords(const ASTContext* n, const Decl* d) { return GetParents(n, d); } /**************************************************************************************************/ optional_json DetailCXXRecordDecl(const hyde::processing_options& options, const clang::CXXRecordDecl* cxx) { auto info_opt = StandardDeclInfo(options, cxx); if (!info_opt) return info_opt; auto info = std::move(*info_opt); // overrides for various fields if the record is of a specific sub-type. if (auto s = llvm::dyn_cast_or_null(cxx)) { info["name"] = hyde::to_string(s, s->getTypeAsWritten()->getType()); info["qualified_name"] = s->getQualifiedNameAsString(); } else if (auto template_decl = cxx->getDescribedClassTemplate()) { std::string arguments = GetArgumentList(template_decl->getTemplateParameters()->asArray()); info["name"] = static_cast(info["name"]) + arguments; info["qualified_name"] = template_decl->getQualifiedNameAsString(); } return info; } /**************************************************************************************************/ json GetTemplateParameters(const ASTContext* n, const clang::TemplateDecl* d) { json result = json::array(); for (const auto& parameter_decl : *d->getTemplateParameters()) { json parameter_info = json::object(); if (const auto& template_type = dyn_cast(parameter_decl)) { parameter_info["type"] = template_type->wasDeclaredWithTypename() ? "typename" : "class"; if (template_type->isParameterPack()) parameter_info["parameter_pack"] = "true"; parameter_info["name"] = template_type->getNameAsString(); } else if (const auto& non_template_type = dyn_cast(parameter_decl)) { parameter_info["type"] = hyde::to_string(non_template_type, non_template_type->getType()); if (non_template_type->isParameterPack()) parameter_info["parameter_pack"] = "true"; parameter_info["name"] = non_template_type->getNameAsString(); } else if (const auto& template_template_type = dyn_cast(parameter_decl)) { parameter_info["type"] = ::to_string(n, template_template_type->getSourceRange(), false); if (template_template_type->isParameterPack()) parameter_info["parameter_pack"] = "true"; parameter_info["name"] = template_template_type->getNameAsString(); } else { std::cerr << "What type is this thing, exactly?\n"; } result.push_back(std::move(parameter_info)); } return result; } /**************************************************************************************************/ optional_json DetailFunctionDecl(const hyde::processing_options& options, const FunctionDecl* f) { auto info_opt = StandardDeclInfo(options, f); if (!info_opt) return info_opt; auto info = std::move(*info_opt); const clang::ASTContext* n = &f->getASTContext(); info["return_type"] = hyde::to_string(f, f->getReturnType()); info["arguments"] = json::array(); info["signature"] = GetSignature(n, f); info["signature_with_names"] = GetSignature(n, f, signature_options::named_args); info["short_name"] = GetShortName(n, f); // redo the name and qualified name for this entry, now that we have a proper function info["name"] = info["signature"]; info["qualified_name"] = GetSignature(n, f, signature_options::fully_qualified); info["implicit"] = f->isImplicit(); if (f->isConstexpr()) info["constexpr"] = true; auto storage = f->getStorageClass(); switch (storage) { case SC_Static: info["static"] = true; break; case SC_Extern: info["extern"] = true; break; default: break; } auto visibility = f->getVisibility(); switch (visibility) { case Visibility::HiddenVisibility: info["visibility"] = "hidden"; break; case Visibility::DefaultVisibility: info["visibility"] = "default"; break; case Visibility::ProtectedVisibility: info["visibility"] = "protected"; break; } LinkageInfo linkage_info = f->getLinkageAndVisibility(); info["visibility_explicit"] = linkage_info.isVisibilityExplicit() ? "true" : "false"; if (const auto* method = llvm::dyn_cast_or_null(f)) { info["const"] = method->isConst(); info["volatile"] = method->isVolatile(); info["static"] = method->isStatic(); info["deleted"] = method->isDeletedAsWritten(); info["defaulted"] = method->isExplicitlyDefaulted(); const bool is_ctor = isa(method); const bool is_dtor = isa(method); if (is_ctor || is_dtor) { if (is_ctor) { info["is_ctor"] = true; if (auto ctor_decl = llvm::dyn_cast_or_null(method)) { auto specifier = ctor_decl->getExplicitSpecifier(); info["explicit"] = specifier.isExplicit(); } } else /*is_dtor*/ { info["is_dtor"] = true; } } if (auto conversion_decl = llvm::dyn_cast_or_null(method)) { auto specifier = conversion_decl->getExplicitSpecifier(); info["explicit"] = specifier.isExplicit(); } } if (auto template_decl = f->getDescribedFunctionTemplate()) { info["template_parameters"] = GetTemplateParameters(n, template_decl); } for (const auto& p : f->parameters()) { json argument = json::object(); argument["type"] = to_string(p, p->getOriginalType()); argument["name"] = p->getNameAsString(); info["arguments"].push_back(std::move(argument)); } return info; } /**************************************************************************************************/ std::string GetArgumentList(const llvm::ArrayRef args) { std::size_t count{0}; std::string result("<"); for (const auto& arg : args) { if (count++) { result += ", "; } result += arg->getNameAsString(); } result += ">"; return result; } /**************************************************************************************************/ bool PathCheck(const std::vector& paths, const Decl* d, ASTContext* n) { auto beginLoc = d->getBeginLoc(); auto location = beginLoc.printToString(n->getSourceManager()); std::string path = location.substr(0, location.find(':')); return std::find(paths.begin(), paths.end(), path) != paths.end(); } /**************************************************************************************************/ bool NamespaceBlacklist(const std::vector& blacklist, const json& j) { // iff one of the namespaces in j are found in the blacklist, return true. if (j.count("namespaces")) { for (const auto& ns : j["namespaces"]) { auto found = std::find(blacklist.begin(), blacklist.end(), ns); if (found != blacklist.end()) return true; } } return false; } /**************************************************************************************************/ bool AccessCheck(ToolAccessFilter hyde_filter, clang::AccessSpecifier clang_access) { // return true iff the element should be included in the documentation based // on its access specifier. false otherwise. if (clang_access == AS_none) return true; switch (hyde_filter) { case ToolAccessFilterPrivate: return true; case ToolAccessFilterProtected: switch (clang_access) { case AS_public: case AS_protected: return true; default: return false; } case ToolAccessFilterPublic: switch (clang_access) { case AS_public: return true; default: return false; } } return false; } /**************************************************************************************************/ std::string PostProcessTypeParameter(const clang::Decl* decl, std::string type) { // If our type contains one or more `type-parameter-N-M`s, run up the parent // tree in an attempt to resolve them. static const std::string needle("type-parameter-"); auto pos = type.find(needle); // Do this early to avoid the following rigamaroll for nearly all use cases. if (pos == std::string::npos) return type; auto& context = decl->getASTContext(); std::vector> parent_template_types; const auto iterate_template_params = [&](TemplateParameterList& tpl) { for (const auto& parameter_decl : tpl) { if (auto* template_type = dyn_cast(parameter_decl)) { auto depth = template_type->getDepth(); auto index = template_type->getIndex(); auto qualType = context.getTemplateTypeParmType( depth, index, template_type->isParameterPack(), template_type); std::string old_type = needle + std::to_string(depth) + "-" + std::to_string(index); std::string new_type = hyde::to_string(template_type, qualType); parent_template_types.emplace_back( std::make_pair(std::move(old_type), std::move(new_type))); } } }; ForEachParent(decl, [&](const Decl* parent) { if (auto* ctpsd = dyn_cast_or_null(parent)) { iterate_template_params(*ctpsd->getTemplateParameters()); } else if (auto* ctd = dyn_cast_or_null(parent)) { iterate_template_params(*ctd->getTemplateParameters()); } else if (auto* ftd = dyn_cast_or_null(parent)) { iterate_template_params(*ftd->getTemplateParameters()); } }); while (true) { auto end_pos = pos + needle.size(); // This loop is faster than std::string::find_first_not_of while (true) { auto c = type[end_pos]; if (!std::isdigit(c) && c != '-') break; ++end_pos; } auto length = end_pos - pos; std::string old_type = type.substr(pos, length); // sort and lower_bound this? parent_template_types.size() is usually // small (< 5 or so), so it might not be worth the effort. auto found = std::find_if(parent_template_types.begin(), parent_template_types.end(), [&](const auto& cur_pair) { return cur_pair.first == old_type; }); if (found != parent_template_types.end()) { const auto& new_type = found->second; type.replace(pos, length, new_type); pos += new_type.size(); } else { // Not resolved. Skip to avoid infinite loop. pos += old_type.size(); } pos = type.find(needle, pos); if (pos == std::string::npos) break; } return type; } /**************************************************************************************************/ std::string ReplaceAll(std::string str, const std::string& substr, const std::string& replacement) { std::string::size_type pos{0}; while (true) { pos = str.find(substr, pos); if (pos == std::string::npos) return str; str.replace(pos, substr.size(), replacement); pos += replacement.size(); } } std::string PostProcessSpacing(std::string type) { return ReplaceAll(type, "> >", ">>"); } /**************************************************************************************************/ std::string PostProcessType(const clang::Decl* decl, std::string type) { std::string result = PostProcessTypeParameter(decl, std::move(type)); result = PostProcessSpacing(std::move(result)); return result; } /**************************************************************************************************/ hyde::optional_json ProcessComments(const Decl* d) { const ASTContext& n = d->getASTContext(); const FullComment* full_comment = n.getCommentForDecl(d, nullptr); if (!full_comment) return std::nullopt; if (auto result = ProcessComment(n, full_comment, full_comment)) { // The top-level FullComment has only been observed to have // children and nothing else. Roll up the children as the // comments, then, and shed the needless wrapper. return group_comments_by_kind(std::move((*result)["children"])); } return std::nullopt; } /**************************************************************************************************/ constexpr auto hyde_version_major_k = 2; constexpr auto hyde_version_minor_k = 1; constexpr auto hyde_version_patch_k = 0; const std::string& hyde_version() { static const std::string result = std::to_string(hyde_version_major_k) + "." + std::to_string(hyde_version_minor_k) + "." + std::to_string(hyde_version_patch_k); return result; } /**************************************************************************************************/ std::filesystem::path derive_transcription_src_path(const std::filesystem::path& dst, const std::string& title) { // std::cout << "deriving transcription src for \"" << title << "\", dst: " << dst.string() << '\n'; const auto parent = dst.parent_path(); std::size_t best_match = std::numeric_limits::max(); std::filesystem::path result; const std::string& current_version = hyde_version(); for (const auto& entry : std::filesystem::directory_iterator(dst.parent_path())) { const auto sibling = entry.path(); if (!is_directory(sibling)) continue; const auto index_path = sibling / index_filename_k; if (!exists(index_path)) { std::cerr << "WARN: expected " << index_path.string() << " but did not find one\n"; continue; } const auto have_docs = parse_documentation(index_path, true); if (have_docs._error) { std::cerr << "WARN: expected " << index_path.string() << " to have docs\n"; continue; } const auto& have = have_docs._json; if (have.count("hyde")) { const auto& have_hyde = have.at("hyde"); if (have_hyde.count("version")) { // Transcription is when we're going from a previous version of hyde to this one. // So if the versions match, this is a directory that has already been transcribed. // (Transcribing from a newer version of hyde docs to older ones isn't supported.) if (static_cast(have_hyde.at("version")) == current_version) { // std::cout << " candidate (VSKIP) src: " << sibling.string() << '\n'; continue; } } } // REVISIT (fosterbrereton): Are these titles editable? Would // users muck with them and thus break this algorithm? const std::string& have_title = static_cast(have["title"]); // score going from what we have to what this version computed. const auto match = diff_score(have_title, title); // std::cout << " candidate (" << match << "): \"" << have_title << "\", src: " << sibling.string() << '\n'; if (match > best_match) { continue; } best_match = match; result = sibling; } // std::cout << " result is: " << result.string() << " (" << best_match << ")\n"; return result; } /**************************************************************************************************/ std::size_t diff_score(std::string_view src, std::string_view dst) { const myers::patch patch = myers::diff(src, dst); std::size_t score = 0; for (const auto& c : patch) { switch (c.operation) { case myers::operation::cpy: break; case myers::operation::del: case myers::operation::ins: { score += c.text.size(); } break; } } return score; } /**************************************************************************************************/ } // namespace hyde /**************************************************************************************************/ ================================================ FILE: matchers/utilities.hpp ================================================ /* Copyright 2018 Adobe All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. */ #pragma once // stdc++ #include // clang/llvm // clang-format off #include "_clang_include_prefix.hpp" // must be first to disable warnings for clang headers #include "clang/AST/Attr.h" #include "clang/AST/Decl.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "llvm/ADT/ArrayRef.h" #include "_clang_include_suffix.hpp" // must be last to re-enable warnings // clang-format on // application #include "json.hpp" #include "matchers/matcher_fwd.hpp" /**************************************************************************************************/ namespace hyde { /**************************************************************************************************/ json GetParentNamespaces(const clang::ASTContext* n, const clang::Decl* d); json GetParentCXXRecords(const clang::ASTContext* n, const clang::Decl* d); json GetTemplateParameters(const clang::ASTContext* n, const clang::TemplateDecl* d); optional_json DetailFunctionDecl(const hyde::processing_options& options, const clang::FunctionDecl* f); optional_json DetailCXXRecordDecl(const hyde::processing_options& options, const clang::CXXRecordDecl* cxx); bool PathCheck(const std::vector& paths, const clang::Decl* d, clang::ASTContext* n); bool AccessCheck(ToolAccessFilter hyde_filter, clang::AccessSpecifier clang_access); bool NamespaceBlacklist(const std::vector& blacklist, const json& j); std::string GetArgumentList(const llvm::ArrayRef args); std::string ReplaceAll(std::string str, const std::string& substr, const std::string& replacement); // type-parameter-N-M filtering. std::string PostProcessType(const clang::Decl* decl, std::string type); // Doxygen-style comments. optional_json ProcessComments(const clang::Decl* d); /**************************************************************************************************/ const std::string& hyde_version(); /**************************************************************************************************/ /// compute a value (based on a diff algorithm) to determine roughly how much two strings are like /// each other. The lower the value the better, with 0 meaning the two strings are identical. std::size_t diff_score(std::string_view src, std::string_view dst); // Iterate the list of `dst` subfolders, find their `index.md` files, load the `title` of each, and // find the best-fit against the given `title`. This facilitates the transcription behavior. std::filesystem::path derive_transcription_src_path(const std::filesystem::path& dst, const std::string& title); /**************************************************************************************************/ inline std::string to_string(clang::AccessSpecifier access) { switch (access) { case clang::AccessSpecifier::AS_public: return "public"; case clang::AccessSpecifier::AS_protected: return "protected"; case clang::AccessSpecifier::AS_private: return "private"; case clang::AccessSpecifier::AS_none: return "none"; } return "unknown"; } /**************************************************************************************************/ inline std::string to_string(const clang::Decl* decl, clang::QualType type) { static const clang::PrintingPolicy policy(decl->getASTContext().getLangOpts()); std::string result = PostProcessType(decl, type.getAsString(policy)); bool is_lambda = result.find("(lambda at ") == 0; return is_lambda ? "__lambda" : result; } /**************************************************************************************************/ template optional_json StandardDeclInfo(const hyde::processing_options& options, const DeclarationType* d) { clang::ASTContext* n = &d->getASTContext(); if (!PathCheck(options._paths, d, n)) return std::nullopt; json info = json::object(); info["name"] = d->getNameAsString(); info["namespaces"] = GetParentNamespaces(n, d); info["parents"] = GetParentCXXRecords(n, d); info["qualified_name"] = d->getQualifiedNameAsString(); if (NamespaceBlacklist(options._namespace_blacklist, info)) return std::nullopt; auto clang_access = d->getAccess(); if (!AccessCheck(options._access_filter, clang_access)) return std::nullopt; if (auto comments = ProcessComments(d)) { info["comments"] = std::move(*comments); } if (clang_access != clang::AccessSpecifier::AS_none) info["access"] = to_string(clang_access); info["defined_in_file"] = [&] { auto beginLoc = d->getBeginLoc(); auto location = beginLoc.printToString(n->getSourceManager()); return location.substr(0, location.find(':')); }(); info["deprecated"] = false; if (auto attr = d->template getAttr()) { info["deprecated"] = true; auto message = attr->getMessage(); info["deprecated_message"] = message.str(); } return info; } /**************************************************************************************************/ } // namespace hyde /**************************************************************************************************/ ================================================ FILE: sources/autodetect.cpp ================================================ /* Copyright 2018 Adobe All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. */ // identity #include "autodetect.hpp" // stdc++ #include #include #include #include #include #include #include #include #include #include /**************************************************************************************************/ namespace { /**************************************************************************************************/ std::vector split(const char* p, std::size_t n) { auto end = p + n; std::vector result; while (p != end) { while (p != end && *p == '\n') ++p; // eat newlines auto begin = p; while (p != end && *p != '\n') ++p; // eat non-newlines result.emplace_back(begin, p); } return result; } /**************************************************************************************************/ std::vector file_slurp(std::filesystem::path p) { std::ifstream s(p); s.seekg(0, std::ios::end); std::size_t size = s.tellg(); auto buffer{std::make_unique(size)}; s.seekg(0); s.read(&buffer[0], size); return split(buffer.get(), size); } /**************************************************************************************************/ std::string trim_front(std::string s) { std::size_t n(0); std::size_t end(s.size()); while (n != end && (std::isspace(s[n]) || s[n] == '\n')) ++n; s.erase(0, n); return s; } /**************************************************************************************************/ std::string trim_back(std::string s) { std::size_t start(s.size()); while (start != 0 && (std::isspace(s[start - 1]) || s[start - 1] == '\n')) start--; s.erase(start, std::string::npos); return s; } /**************************************************************************************************/ std::string chomp(std::string src) { return trim_back(trim_front(std::move(src))); } /**************************************************************************************************/ std::string exec(const char* cmd) { struct pclose_t { void operator()(std::FILE* p) const { (void)pclose(p); } }; std::unique_ptr pipe{popen(cmd, "r")}; if (!pipe) { throw std::runtime_error("popen() failed!"); } std::array buffer; std::string result; while (fgets(buffer.data(), buffer.size(), pipe.get())) { result += buffer.data(); } return chomp(std::move(result)); } /**************************************************************************************************/ std::vector autodetect_include_paths() { // Add a random value here so two concurrent instances of hyde don't collide. auto v = std::to_string(std::mt19937(std::random_device()())()); auto temp_dir = std::filesystem::temp_directory_path(); auto temp_out = (temp_dir / ("hyde_" + v + ".tmp")).string(); auto temp_a_out = (temp_dir / ("deleteme_" + v)).string(); auto command = "echo \"int main() { }\" | clang++ -x c++ -v -o " + temp_a_out + " - 2> " + temp_out; auto command_result = std::system(command.c_str()); (void)command_result; // TODO: handle me std::vector lines(file_slurp(temp_out)); static const std::string begin_string("#include <...> search starts here:"); static const std::string end_string("End of search list."); auto paths_begin = std::find(begin(lines), end(lines), begin_string); auto paths_end = std::find(begin(lines), end(lines), end_string); std::vector result; if (paths_begin != end(lines) && paths_end != end(lines)) { lines.erase(paths_end, end(lines)); lines.erase(begin(lines), std::next(paths_begin)); // lines.erase(std::remove_if(begin(lines), end(lines), [](auto& s){ // return s.find(".sdk/") != std::string::npos; // }), end(lines)); // Some of the paths contain cruft at the end. Filter those out, too. std::transform(begin(lines), end(lines), std::back_inserter(result), [](auto s) { static const std::string needle_k{" (framework directory)"}; auto needle_pos = s.find(needle_k); if (needle_pos != std::string::npos) { s.erase(needle_pos); } return std::filesystem::path(chomp(std::move(s))); }); } return result; } /**************************************************************************************************/ } // namespace /**************************************************************************************************/ namespace hyde { /**************************************************************************************************/ std::vector autodetect_toolchain_paths() { return autodetect_include_paths(); } /**************************************************************************************************/ std::filesystem::path autodetect_resource_directory() { return std::filesystem::path{exec("clang++ -print-resource-dir")}; } /**************************************************************************************************/ #if HYDE_PLATFORM(APPLE) std::filesystem::path autodetect_sysroot_directory() { return std::filesystem::path{exec("xcode-select -p")} / "Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk"; } #endif // HYDE_PLATFORM(APPLE) /**************************************************************************************************/ } // namespace hyde /**************************************************************************************************/ ================================================ FILE: sources/main.cpp ================================================ /* Copyright 2018 Adobe All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. */ // stdc++ #include #include #include #include #include #include #include // clang/llvm #include "_clang_include_prefix.hpp" // must be first to disable warnings for clang headers #include "clang/Frontend/FrontendActions.h" #include "clang/Tooling/CommonOptionsParser.h" #include "clang/Tooling/Tooling.h" #include "llvm/Support/CommandLine.h" #include "_clang_include_suffix.hpp" // must be last to re-enable warnings // application #include "autodetect.hpp" #include "config.hpp" #include "json.hpp" #include "output_yaml.hpp" #include "emitters/yaml_base_emitter_fwd.hpp" // instead of this, probably have a matcher manager that pushes the json object // into the file then does the collation and passes it into jsonAST to do // anything it needs to do #include "matchers/class_matcher.hpp" #include "matchers/enum_matcher.hpp" #include "matchers/function_matcher.hpp" #include "matchers/matcher_fwd.hpp" #include "matchers/namespace_matcher.hpp" #include "matchers/typealias_matcher.hpp" #include "matchers/typedef_matcher.hpp" #include "matchers/utilities.hpp" using namespace clang::tooling; using namespace llvm; /**************************************************************************************************/ namespace { /**************************************************************************************************/ std::filesystem::path make_absolute(std::filesystem::path path) { if (path.is_absolute()) return path; static const auto pwd = std::filesystem::current_path(); std::error_code ec; const auto relative = pwd / path; auto result = weakly_canonical(relative, ec); if (!ec) { return result; } throw std::runtime_error("make_absolute: \"" + relative.string() + "\": " + ec.message()); } /**************************************************************************************************/ std::string make_absolute(std::string path_string) { return make_absolute(std::filesystem::path(std::move(path_string))).string(); } /**************************************************************************************************/ std::vector make_absolute(std::vector paths) { for (auto& path : paths) path = make_absolute(std::move(path)); return paths; } /**************************************************************************************************/ } // namespace /**************************************************************************************************/ /* Command line arguments section. These are intentionally global. See: https://llvm.org/docs/CommandLine.html */ enum ToolMode { ToolModeJSON, ToolModeYAMLValidate, ToolModeYAMLUpdate, ToolModeYAMLTranscribe, ToolModeFixupSubfield }; enum ToolDiagnostic { ToolDiagnosticQuiet, ToolDiagnosticVerbose, ToolDiagnosticVeryVerbose }; static llvm::cl::OptionCategory MyToolCategory( "Hyde is a tool for the semi-automatic maintenance of API reference documentation"); static cl::opt ToolMode( cl::desc("There are several modes under which the tool can run:"), cl::values( clEnumValN(ToolModeJSON, "hyde-json", "JSON analysis (default)"), clEnumValN(ToolModeYAMLValidate, "hyde-validate", "Validate existing YAML documentation"), clEnumValN(ToolModeYAMLUpdate, "hyde-update", "Write updated YAML documentation for missing elements"), clEnumValN(ToolModeFixupSubfield, "hyde-fixup-subfield", "Fix-up preexisting documentation; move all fields except `layout` and `title` into a `hyde` subfield. Note this mode is unique in that it takes pre-existing documentation as source(s), not a C++ source file."), clEnumValN(ToolModeYAMLTranscribe, "hyde-transcribe", "Transcribe preexisting documentation given the same symbols have different names because hyde updated its clang driver. This mode assumes the generated and present documentation would otherwise be identical.") ), cl::cat(MyToolCategory)); static cl::opt ToolAccessFilter( cl::desc("Restrict documentation of class elements by their access specifier."), cl::values(clEnumValN(hyde::ToolAccessFilterPrivate, "access-filter-private", "Process all elements (default)"), clEnumValN(hyde::ToolAccessFilterProtected, "access-filter-protected", "Process only public and protected elements"), clEnumValN(hyde::ToolAccessFilterPublic, "access-filter-public", "Process only public elements")), cl::cat(MyToolCategory)); static cl::opt ToolDiagnostic( cl::desc("Several tool diagnostic modes are available:"), cl::values( clEnumValN(ToolDiagnosticQuiet, "hyde-quiet", "output less to the console (default)"), clEnumValN(ToolDiagnosticVerbose, "hyde-verbose", "output more to the console"), clEnumValN(ToolDiagnosticVeryVerbose, "hyde-very-verbose", "output much more to the console")), cl::cat(MyToolCategory)); static cl::opt YamlDstDir( "hyde-yaml-dir", cl::desc("Root directory for YAML validation / update"), cl::cat(MyToolCategory)); static cl::opt EmitJson( "hyde-emit-json", cl::desc("Output JSON emitted from operation"), cl::cat(MyToolCategory), cl::ValueDisallowed); static cl::opt TestedBy( "hyde-tested-by", cl::values( clEnumValN(hyde::attribute_category::disabled, "disabled", "Disable tested_by attribute (default)"), clEnumValN(hyde::attribute_category::required, "required", "Require tested_by attribute"), clEnumValN(hyde::attribute_category::optional, "optional", "Enable tested_by attribute with optional value")), cl::cat(MyToolCategory)); static cl::opt YamlSrcDir( "hyde-src-root", cl::desc("The root path to the header file(s) being analyzed"), cl::cat(MyToolCategory)); static cl::opt ArgumentResourceDir( "resource-dir", cl::desc("The resource dir(see clang resource dir) for hyde to use."), cl::cat(MyToolCategory)); static cl::opt AutoResourceDirectory( "auto-resource-dir", cl::desc("Autodetect and use clang's resource directory"), cl::cat(MyToolCategory), cl::ValueDisallowed); static cl::opt AutoToolchainIncludes( "auto-toolchain-includes", cl::desc("Autodetect and use the toolchain include paths"), cl::cat(MyToolCategory), cl::ValueDisallowed); #if HYDE_PLATFORM(APPLE) static cl::opt AutoSysrootDirectory( "auto-sysroot", cl::desc("Autodetect and use isysroot"), cl::cat(MyToolCategory), cl::ValueDisallowed); #endif static cl::opt UseSystemClang( "use-system-clang", #if HYDE_PLATFORM(APPLE) cl::desc("Synonym for both -auto-resource-dir and -auto-sysroot"), #else cl::desc("Synonym for both -auto-resource-dir and -auto-toolchain-includes"), #endif cl::cat(MyToolCategory), cl::ValueDisallowed); static cl::list NamespaceBlacklist( "namespace-blacklist", cl::desc("Namespace(s) whose contents should not be processed"), cl::cat(MyToolCategory), cl::CommaSeparated); static cl::opt DriverLanguage( "language", cl::desc("Override language used for compilation"), cl::cat(MyToolCategory)); static cl::alias DriverLanguageAlias( "x", cl::desc("Alias for `-language`"), cl::aliasopt(DriverLanguage)); static cl::opt ProcessClassMethods( "process-class-methods", cl::desc("Process Class Methods"), cl::cat(MyToolCategory), cl::ValueDisallowed); static cl::opt IgnoreExtraneousFiles( "ignore-extraneous-files", cl::desc("Ignore extraneous files while validating"), cl::cat(MyToolCategory), cl::ValueDisallowed); static cl::extrahelp HydeHelp( "\nThis tool parses the header source(s) using Clang. To pass arguments to the\n" "compiler (e.g., include directories), append them after the `--` token on the\n" "command line. For example:\n" "\n" " hyde -hyde-json input_file.hpp -- -x c++ -I/path/to/includes\n" "\n" "(The file to be processed must be the last argument before the `--` token.)\n" "\n" "Alternatively, if you have a compilation database and would like to pass that\n" "instead of command-line compiler arguments, you can pass that with -p.\n" "\n" "While compiling the source file, the non-function macro `ADOBE_TOOL_HYDE` is\n" "defined to the value `1`. This can be useful to explicitly omit code from\n" "the documentation.\n" "\n" "Hyde supports project configuration files. It must be named either `.hyde-config`\n" "or `_hyde-config`, and must be at or above the file being processed. The\n" "format of the file is JSON. This allows you to specify command line\n" "parameters in a common location so they do not need to be passed for every\n" "file in your project. The flags sent to Clang should be a in a top-level\n" "array under the `clang_flags` key.\n" "\n"); /**************************************************************************************************/ bool IsVerbose() { return ToolDiagnostic == ToolDiagnosticVerbose || ToolDiagnostic == ToolDiagnosticVeryVerbose; } /**************************************************************************************************/ using optional_path_t = std::optional; /**************************************************************************************************/ optional_path_t find_hyde_config(std::filesystem::path src_file) { if (!exists(src_file)) { return std::nullopt; } if (src_file.is_relative()) { const std::filesystem::path pwd_k = std::filesystem::current_path(); src_file = std::filesystem::canonical(pwd_k / src_file); } if (!is_directory(src_file)) { src_file = src_file.parent_path(); } const auto hyde_config_check = [](std::filesystem::path path) -> optional_path_t { if (std::filesystem::exists(path)) { return std::move(path); } return std::nullopt; }; while (true) { if (!exists(src_file)) return std::nullopt; if (auto path = hyde_config_check(src_file / ".hyde-config")) { return path; } if (auto path = hyde_config_check(src_file / "_hyde-config")) { return path; } auto new_parent = src_file.parent_path(); if (src_file == new_parent) return std::nullopt; src_file = std::move(new_parent); } } /**************************************************************************************************/ std::pair load_hyde_config( std::filesystem::path src_file) try { optional_path_t hyde_config_path(find_hyde_config(src_file)); if (IsVerbose()) { if (hyde_config_path) { std::cout << "INFO: hyde-config file: " << hyde_config_path->string() << '\n'; } else { std::cout << "INFO: hyde-config file: not found\n"; } } return hyde_config_path ? std::make_pair(hyde_config_path->parent_path(), hyde::json::parse(std::ifstream(*hyde_config_path))) : std::make_pair(std::filesystem::path(), hyde::json()); } catch (...) { throw std::runtime_error("failed to parse the hyde-config file"); } /**************************************************************************************************/ struct command_line_args { std::vector _hyde; std::vector _clang; }; command_line_args integrate_hyde_config(int argc, const char** argv) { auto cmdline_first = &argv[1]; auto cmdline_last = &argv[argc]; auto cmdline_mid = std::find_if(cmdline_first, cmdline_last, [](const char* arg) { return arg == std::string("--"); }); const std::vector cli_hyde_flags = [argc, cmdline_first, cmdline_mid] { std::vector result; auto hyde_first = cmdline_first; auto hyde_last = cmdline_mid; while (hyde_first != hyde_last) { result.emplace_back(*hyde_first++); } if (argc == 1) { result.push_back("-help"); } return result; }(); const std::vector cli_clang_flags = [cmdline_mid, cmdline_last] { std::vector result; auto clang_first = cmdline_mid; auto clang_last = cmdline_last; while (clang_first != clang_last) { std::string arg(*clang_first++); if (arg == "--") continue; result.emplace_back(std::move(arg)); } return result; }(); std::vector hyde_flags; std::vector clang_flags; std::filesystem::path config_dir; hyde::json config; std::tie(config_dir, config) = load_hyde_config(cli_hyde_flags.empty() ? "" : cli_hyde_flags.back()); hyde_flags.push_back(argv[0]); if (exists(config_dir)) current_path(config_dir); if (config.count("clang_flags")) { for (const auto& clang_flag : config["clang_flags"]) { clang_flags.push_back(clang_flag); } } if (config.count("hyde-src-root")) { const std::string& path_str = config["hyde-src-root"]; std::string abs_path_str = make_absolute(path_str); hyde_flags.emplace_back("-hyde-src-root=" + abs_path_str); } if (config.count("hyde-yaml-dir")) { const std::string& path_str = config["hyde-yaml-dir"]; std::string abs_path_str = make_absolute(path_str); hyde_flags.emplace_back("-hyde-yaml-dir=" + abs_path_str); } if (config.count("hyde-tested-by")) { const std::string& tested_by = config["hyde-tested-by"]; hyde_flags.emplace_back("-hyde-tested-by=" + tested_by); } hyde_flags.insert(hyde_flags.end(), cli_hyde_flags.begin(), cli_hyde_flags.end()); clang_flags.insert(clang_flags.end(), cli_clang_flags.begin(), cli_clang_flags.end()); hyde_flags.push_back("--"); command_line_args result; result._hyde = std::move(hyde_flags); result._clang = std::move(clang_flags); return result; } /**************************************************************************************************/ namespace { /**************************************************************************************************/ CommonOptionsParser MakeOptionsParser(int argc, const char** argv) { auto MaybeOptionsParser = CommonOptionsParser::create(argc, argv, MyToolCategory); if (!MaybeOptionsParser) { throw MaybeOptionsParser.takeError(); } return std::move(*MaybeOptionsParser); } /**************************************************************************************************/ // Hyde may accumulate many "fixups" throughout its lifetime. The first of these so far is to move // the hyde fields under a `hyde` subfield in the YAML, allowing for other tools' fields to coexist // under other values in the same have file. bool fixup_have_file_subfield(const std::filesystem::path& path) { // Passing `true` is what's actually causing the fixup. const auto parsed = hyde::parse_documentation(path, true); const auto failure = parsed._error || hyde::write_documentation(parsed, path); if (failure) { std::cerr << "Failed to fixup " << path << '\n'; } return failure; } /**************************************************************************************************/ } // namespace /**************************************************************************************************/ std::vector source_paths(int argc, const char** argv) { return MakeOptionsParser(argc, argv).getSourcePathList(); } /**************************************************************************************************/ int main(int argc, const char** argv) try { llvm::cl::SetVersionPrinter([](llvm::raw_ostream &OS) { OS << "hyde " << hyde::hyde_version() << "; llvm " << LLVM_VERSION_STRING << "\n"; }); command_line_args args = integrate_hyde_config(argc, argv); int new_argc = static_cast(args._hyde.size()); std::vector new_argv(args._hyde.size(), nullptr); std::transform(args._hyde.begin(), args._hyde.end(), new_argv.begin(), [](const auto& arg) { return arg.c_str(); }); CommonOptionsParser OptionsParser(MakeOptionsParser(new_argc, &new_argv[0])); if (UseSystemClang) { AutoResourceDirectory = true; #if HYDE_PLATFORM(APPLE) AutoSysrootDirectory = true; #else AutoToolchainIncludes = true; #endif } if (IsVerbose()) { std::cout << "INFO: Args:\n"; std::cout << "INFO: (hyde)\n"; for (const auto& arg : args._hyde) { std::cout << "INFO: " << arg << '\n'; } std::cout << "INFO: (clang)\n"; for (const auto& arg : args._clang) { std::cout << "INFO: " << arg << '\n'; } std::cout << "INFO: Working directory: " << std::filesystem::current_path().string() << '\n'; } auto sourcePaths = make_absolute(OptionsParser.getSourcePathList()); // Remove duplicates (CommonOptionsParser is duplicating every single entry) std::unordered_set s; for (std::string i : sourcePaths) { s.insert(i); } sourcePaths.assign(s.begin(), s.end()); if (ToolMode == ToolModeFixupSubfield) { bool failure{false}; for (const auto& path : sourcePaths) { failure |= fixup_have_file_subfield(path); } // In this mode, once the documentation file has been fixed up, we're done. return failure ? EXIT_FAILURE : EXIT_SUCCESS; } MatchFinder Finder; hyde::processing_options options{sourcePaths, ToolAccessFilter, NamespaceBlacklist, ProcessClassMethods}; hyde::FunctionInfo function_matcher(options); Finder.addMatcher(hyde::FunctionInfo::GetMatcher(), &function_matcher); hyde::EnumInfo enum_matcher(options); Finder.addMatcher(hyde::EnumInfo::GetMatcher(), &enum_matcher); hyde::ClassInfo class_matcher(options); Finder.addMatcher(hyde::ClassInfo::GetMatcher(), &class_matcher); hyde::NamespaceInfo namespace_matcher(options); Finder.addMatcher(hyde::NamespaceInfo::GetMatcher(), &namespace_matcher); hyde::TypeAliasInfo typealias_matcher(options); Finder.addMatcher(hyde::TypeAliasInfo::GetMatcher(), &typealias_matcher); hyde::TypedefInfo typedef_matcher(options); Finder.addMatcher(hyde::TypedefInfo::GetMatcher(), &typedef_matcher); clang::tooling::CommandLineArguments arguments; // start by appending the command line clang args. for (auto& arg : args._clang) { arguments.emplace_back(std::move(arg)); } if (ToolDiagnostic == ToolDiagnosticVeryVerbose) { arguments.emplace_back("-v"); } #if HYDE_PLATFORM(APPLE) // // Specify the isysroot directory to the driver // // in some versions of osx they have stopped using `/usr/include`; Apple seems to rely // on the isysroot parameter to accomplish this task in the general case, so we add it here. std::filesystem::path include_dir{"/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/"}; if (AutoSysrootDirectory) { if (IsVerbose()) { std::cout << "INFO: Sysroot autodetected\n"; } include_dir = hyde::autodetect_sysroot_directory(); } if (std::filesystem::exists(include_dir)) { if (IsVerbose()) { std::cout << "INFO: Using isysroot: " << include_dir.string() << std::endl; } arguments.emplace_back(("-isysroot" + include_dir.string()).c_str()); } #endif // // Specify toolchain includes to the driver // if (AutoToolchainIncludes) { std::vector includes = hyde::autodetect_toolchain_paths(); if (IsVerbose()) { std::cout << "INFO: Toolchain paths autodetected:\n"; } for (const auto& arg : includes) { if (IsVerbose()) { std::cout << "INFO: " << arg.string() << '\n'; } arguments.emplace_back("-I" + arg.string()); } } // // Specify the resource directory to the driver // // this may not work on windows, need to investigate using strings std::filesystem::path resource_dir; if (AutoResourceDirectory) { if (IsVerbose()) { std::cout << "INFO: Resource directory autodetected\n"; } resource_dir = hyde::autodetect_resource_directory(); } else if (!ArgumentResourceDir.empty()) { resource_dir = std::filesystem::path(ArgumentResourceDir.getValue()); } if (std::filesystem::exists(resource_dir)) { if (IsVerbose()) { std::cout << "INFO: Using resource-dir: " << resource_dir.string() << std::endl; } arguments.emplace_back("-resource-dir=" + resource_dir.string()); } // Specify the hyde preprocessor macro arguments.emplace_back("-DADOBE_TOOL_HYDE=1"); // Have the driver parse comments. See: // https://clang.llvm.org/docs/UsersManual.html#comment-parsing-options // This isn't strictly necessary, as Doxygen comments will be detected // and parsed regardless. Better to be thorough, though. arguments.emplace_back("-fparse-all-comments"); // Enables some checks built in to the clang driver to ensure comment // documentation matches whatever it is documenting. We also make it // an error because the documentation should be accurate when generated. arguments.emplace_back("-Werror=documentation"); arguments.emplace_back("-Werror=documentation-deprecated-sync"); arguments.emplace_back("-Werror=documentation-html"); arguments.emplace_back("-Werror=documentation-pedantic"); // Add hyde-specific commands to the Clang Doxygen parser. For hyde, we'll require the first // word to be the hyde field (e.g., `@hyde-owner fosterbrereton`.) Because the Doxygen parser // doesn't consider `-` or `_` as part of the command token, the first word will be // `-owner` in this case, which gives us something parseable, and it reads // reasonably in the code as well. arguments.emplace_back("-fcomment-block-commands=hyde"); // // Spin up the tool and run it. // ClangTool Tool(OptionsParser.getCompilations(), sourcePaths); // Clang usually permits the "-x" (aka "--language") flag to "treat subsequent input files as // having type ". (See https://clang.llvm.org/docs/ClangCommandLineReference.html). // As noted, the flag only works for _subsequent_ files, and since the input file is passed as // the last parameter before the flag termination token (`--`), we cannot pass the `--language` // flag thereafter and have it apply retroactively. Since the clang driver flags are all passed // after the flag termination token, we have to inject this specific flag early. if (!DriverLanguage.empty()) { std::vector prefix_arguments; prefix_arguments.emplace_back("--language=" + DriverLanguage.getValue()); Tool.appendArgumentsAdjuster( getInsertArgumentAdjuster(prefix_arguments, clang::tooling::ArgumentInsertPosition::BEGIN)); } Tool.appendArgumentsAdjuster(OptionsParser.getArgumentsAdjuster()); Tool.appendArgumentsAdjuster( getInsertArgumentAdjuster(arguments, clang::tooling::ArgumentInsertPosition::END)); if (Tool.run(newFrontendActionFactory(&Finder).get())) throw std::runtime_error("compilation failed."); // // Take the results of the tool and process them. // hyde::json paths = hyde::json::object(); paths["src_root"] = YamlSrcDir; paths["src_path"] = sourcePaths[0]; // Hmm... including multiple sources // implies we'd be analyzing multiple // subcomponents at the same time. We // should account for this at some // point. hyde::json result = hyde::json::object(); result["functions"] = function_matcher.getJSON()["functions"]; result["enums"] = enum_matcher.getJSON()["enums"]; result["classes"] = class_matcher.getJSON()["classes"]; result["namespaces"] = namespace_matcher.getJSON()["namespaces"]; result["typealiases"] = typealias_matcher.getJSON()["typealiases"]; result["typedefs"] = typedef_matcher.getJSON()["typedefs"]; result["paths"] = std::move(paths); if (ToolMode == ToolModeJSON) { // The std::setw(2) is for pretty-printing. Remove it for ugly serialization. std::cout << std::setw(2) << result << '\n'; } else { if (YamlDstDir.empty()) throw std::runtime_error("no YAML output directory specified (-hyde-yaml-dir)"); std::filesystem::path src_root(YamlSrcDir.getValue()); std::filesystem::path dst_root(YamlDstDir.getValue()); hyde::emit_options emit_options; emit_options._tested_by = TestedBy; emit_options._ignore_extraneous_files = IgnoreExtraneousFiles; const auto yaml_mode = [&]{ switch (ToolMode) { case ToolModeYAMLValidate: return hyde::yaml_mode::validate; case ToolModeYAMLUpdate: return hyde::yaml_mode::update; case ToolModeYAMLTranscribe: return hyde::yaml_mode::transcribe; default: throw std::runtime_error("Invalid YAML mode"); } }(); auto out_emitted = hyde::json::object(); output_yaml(std::move(result), std::move(src_root), std::move(dst_root), out_emitted, yaml_mode, std::move(emit_options)); if (EmitJson) { std::cout << out_emitted << '\n'; } } } catch (const std::exception& error) { std::cerr << "Fatal error: " << error.what() << '\n'; return EXIT_FAILURE; } catch (const Error& error) { std::string description; raw_string_ostream stream(description); stream << error; std::cerr << "Fatal error: " << stream.str() << '\n'; return EXIT_FAILURE; } catch (...) { std::cerr << "Fatal error: unknown\n"; return EXIT_FAILURE; } /**************************************************************************************************/ ================================================ FILE: sources/output_yaml.cpp ================================================ /* Copyright 2018 Adobe All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. */ // identity #include "output_yaml.hpp" // stdc++ #include // yaml-cpp #include "yaml-cpp/yaml.h" // application #include "emitters/yaml_class_emitter.hpp" #include "emitters/yaml_enum_emitter.hpp" #include "emitters/yaml_function_emitter.hpp" #include "emitters/yaml_library_emitter.hpp" #include "emitters/yaml_sourcefile_emitter.hpp" #include "json.hpp" /**************************************************************************************************/ namespace hyde { /**************************************************************************************************/ void output_yaml(json j, const std::filesystem::path& src_root, const std::filesystem::path& dst_root, json& out_emitted, yaml_mode mode, const emit_options& options) { bool failure{false}; auto& library_emitted = out_emitted; const json no_inheritance_k; // Process top-level library yaml_library_emitter(src_root, dst_root, mode, options).emit(j, library_emitted, no_inheritance_k); // Process sourcefile yaml_sourcefile_emitter sourcefile_emitter(src_root, dst_root, mode, options); auto sourcefile_emitted = hyde::json::object(); failure |= sourcefile_emitter.emit(j, sourcefile_emitted, no_inheritance_k); // Process classes yaml_class_emitter class_emitter(src_root, dst_root, mode, options); for (const auto& c : j["classes"]) { auto class_emitted = hyde::json::object(); failure |= class_emitter.emit(c, class_emitted, no_inheritance_k); sourcefile_emitted["classes"].push_back(std::move(class_emitted)); } // Process enums yaml_enum_emitter enum_emitter(src_root, dst_root, mode, options); for (const auto& c : j["enums"]) { auto enum_emitted = hyde::json::object(); failure |= enum_emitter.emit(c, enum_emitted, no_inheritance_k); sourcefile_emitted["enums"].push_back(std::move(enum_emitted)); } // Process functions yaml_function_emitter function_emitter(src_root, dst_root, mode, options, false); const auto& functions = j["functions"]; for (auto it = functions.begin(); it != functions.end(); ++it) { function_emitter.set_key(it.key()); auto function_emitted = hyde::json::object(); failure |= function_emitter.emit(it.value(), function_emitted, no_inheritance_k); sourcefile_emitted["functions"].push_back(std::move(function_emitted)); } library_emitted["sourcefiles"].push_back(std::move(sourcefile_emitted)); // Check for extra files. Always do this last. if (!options._ignore_extraneous_files) { failure |= sourcefile_emitter.extraneous_file_check(); } if (failure && mode == yaml_mode::validate) throw std::runtime_error("YAML documentation failed to validate."); } /**************************************************************************************************/ } // namespace hyde /**************************************************************************************************/ ================================================ FILE: test_files/classes.cpp ================================================ /* Copyright 2018 Adobe All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. */ // AST dump with // clang --std=c++1z -Xclang -ast-dump -fsyntax-only ./test_files/classes.cpp #include #include //------------------------------------------------------------------------------------------------------------------------------------------ /// @brief an example class that demonstrates what Hyde documents. /// @hyde-owner fosterbrereton class class_example { public: /// Note that nested classes will not inherit their ownership from /// the class that contains them, so thus need their own `hyde-owner`. /// @brief an example enumeration within the class. /// @hyde-owner sean-parent enum class color { /// this is an example description for the `red` value. red, /// this is an example description for the `green` value. green = 42, /// this is an example description for the `blue` value. blue }; /// Note that nested classes will not inherit their ownership from /// the class that contains them, so thus need their own `hyde-owner`. /// @brief a class definition contained within `example_class`. /// @hyde-owner sean-parent struct nested_class_example { /// member field `_x` within the nested class example. int _x{0}; /// member field `_y` within the nested class example. int _y; }; /// a nested `typedef` expression. typedef std::string typedef_example; /// a nested `using` expression. using using_example = std::string; /// default constructor. class_example() = default; /// an explicit constructor that takes a single `int`. /// @param x The one integer parameter this routine takes explicit class_example(int x) : _x(x) {} /// member function with a trailing return type. /// @return "`_x`" auto member_function_trailing_return_type() const -> int { return _x; } /// example member function. /// @return double the value of `_x`. auto member_function() { return _x *= 2; } /// an overloaded member function that takes one parameter. /// @brief A series of overloaded functions. /// @param first the first parameter of the first overload. void overloaded(const std::string& first); /// an overloaded member function that takes two parameters. /// @brief Another brief describing one of the overloaded functions. /// @param first the first parameter of the second overload. /// @param second the second parameter of the second overload. void overloaded(const std::string& first, const std::string& second) volatile; /// another overloaded member function that takes two parameters. /// @param first the first parameter of the third overload. /// @param second the second parameter of the third overload. void overloaded(const std::string& first, std::vector second) const; /// an overloaded member function that takes _five_ parameters. /// @param first the first parameter of the fourth overload. /// @param second the second parameter of the fourth overload. /// @param third the third parameter of the fourth overload. /// @param fourth the fourth parameter of the fourth overload. /// @param fifth the fifth parameter of the fourth overload. void overloaded( const std::string& first, class_example* second, int third, bool fourth, std::size_t fifth); /// an overloaded member function that takes three unnamed parameters. /// Let it be known that Doxygen doesn't support documenting unnamed parameters at this time. /// There is a [bug open on the issue](https://github.com/doxygen/doxygen/issues/6926), but as /// of this writing does not appear to be progressing. void overloaded(const std::string&, class_example*, int); // intentionally unnamed /// a deprecated overload that takes zero parameters. [[deprecated]] void overloaded(); /// deprecated member function. /// @param first the first parameter /// @param second the second parameter [[deprecated]] void deprecated(const std::string& first, class_example* second); /// deprecated member function that contains a compile-time deprecation message. /// @param s the first parameter /// @param f the second parameter [[deprecated("example deprecation message")]] void deprecated_with_message(const std::string& s, class_example* f); /// static member variable. static const int _static_member = 0; /// @brief static member function. /// @return Zero. // By which I mean `0`. // In the sources, this comment is on multiple lines. static int static_method() { return 0; }; /// templatized member function. template void template_member_function() {} /// specialization of the above templatized member function. template <> void template_member_function() {} private: /// some variable that holds an integer. int _x; /// a deprecated member variable that contains a message. Apparently this works?! [[deprecated("example deprecation message")]] int _deprecated_member = 0; /// an instance of the nested class example defined earlier. nested_class_example _nested; }; //------------------------------------------------------------------------------------------------------------------------------------------ /// @brief an example template class with some specializations /// @hyde-owner fosterbrereton template struct specialization_example { constexpr auto as_tuple() const { return std::forward_as_tuple(); } }; /// @brief an example `std::int32` specialization /// @hyde-owner fosterbrereton template <> struct specialization_example { /// An example typedef using value_type = std::int32_t; value_type _first{0}; ///< An example field used in `as_tuple` /// An example function /// @return a tuple of the fields of this class constexpr auto as_tuple() const { return std::forward_as_tuple(_first); } }; /// @brief an example `float` specialization /// @hyde-owner fosterbrereton template <> struct specialization_example { using value_type = float; ///< An example typedef value_type _first{0}; ///< An example field used in `as_tuple` constexpr auto as_tuple() const { return std::forward_as_tuple(_first); } }; //------------------------------------------------------------------------------------------------------------------------------------------ /// @brief an example template class with a partial specialization. /// @hyde-owner fosterbrereton template class partial_specialization_example { T1 _first; T2 _second; }; /// @brief an example `int, T` partial specialization /// @hyde-owner fosterbrereton template class partial_specialization_example { std::string _first; T _second; }; //------------------------------------------------------------------------------------------------------------------------------------------ ================================================ FILE: test_files/comments.cpp ================================================ /* Copyright 2018 Adobe All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. */ // AST dump with // TERM="" clang --std=c++1z -Xclang -ast-dump -fsyntax-only -fparse-all-comments -fcomment-block-commands=hyde ./comments.cpp /// @brief Sample class intended to exhibit docs for compiler-generated routines /// @hyde-owner fosterbrereton struct compiler_generated { compiler_generated() = delete; compiler_generated(const compiler_generated&) = default; /// This definition will force the compiler to auto-generate this class' assignment operator. void assign(const compiler_generated& rhs) { *this = rhs; } }; /// An example struct from which these commands will hang. /// @brief This is a sample brief. /// @warning This is a sample warning. /// @par header This is a sample paragraph. /// @hyde-owner fosterbrereton /// @see [Slides](https://llvm.org/devmtg/2012-11/Gribenko_CommentParsing.pdf) from an LLVM dev meeting chat on the comment parsing feature struct some_struct { virtual ~some_struct() = delete; /// This is a longer description of this function that does things as well as it does. /// Notice how long this comment is! So impressive. 💥 /// @brief A function that does a thing, and does it well. /// @param[in] input an input parameter /// @param[in,out] input_output a bidirectional parameter /// @param[out] output an output parameter /// @pre An example precondition. /// @post An example postcondition. /// @return Some additional value. /// @throw `std::runtime_error` if the function actually _can't_ do the thing. Sorry! /// @todo This really could use some cleanup. Although, its implementation doesn't exist... /// @warning This function may be very expensive to run. Do not call it inside a loop. int some_function(int input, int& input_output, int& output); /// A virtual function that intends to be overridden. virtual void virtual_function(); int _x{0}; ///< A trailing comment that documents `_x`. }; /// Notice how many of the comments for this structure are inherited from its superclass. /// @brief This is a sample brief for `some_other_struct` struct some_other_struct : public some_struct { void virtual_function() override; }; /// @brief some template function /// @tparam T The type of thing being returned /// @return an instance of type `T` template T template_function(); //------------------------------------------------------------------------------------------------------------------------------------------ ================================================ FILE: test_files/enums.cpp ================================================ /* Copyright 2018 Adobe All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. */ // AST dump with // clang --std=c++1z -Xclang -ast-dump -fsyntax-only ./enums.cpp //------------------------------------------------------------------------------------------------------------------------------------------ /// @brief An example typed enumeration with three values. /// @hyde-owner fosterbrereton enum class color_channel { /// Red commentary red, /// Green commentary. Note this enum has a pre-set value. green = 42, /// Blue commentary blue, }; //------------------------------------------------------------------------------------------------------------------------------------------ /// @brief An example untyped enumeration with three values. /// @hyde-owner fosterbrereton enum untyped { /// Apple commentary apple, /// Orange commentary orange, /// Banana commentary banana, }; //------------------------------------------------------------------------------------------------------------------------------------------ ================================================ FILE: test_files/functions.cpp ================================================ /* Copyright 2018 Adobe All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. */ //------------------------------------------------------------------------------------------------------------------------------------------ /// an example nullary function. /// @return `0` int nullary_function_example() { return 0; } /// an example binary function. /// @param first the first input /// @param second the second input /// @return The sum of the two input parameters. int binary_function_example(int first, int second) { return 0; } /// an example unary overloaded function /// @param first the first input parameter /// @return `first` auto overloaded(int first) -> float { return first; } /// an example binary overloaded function /// @param first the first input parameter /// @param second the second input parameter /// @return the product of `first` and `second` auto overloaded(int first, int second) -> double { return first * second; } /// an example tertiary overloaded function /// @param first the first input parameter /// @param second the second input parameter /// @param third the third input parameter /// @return the product of `first`, `second`, and `third` auto overloaded(int first, int second, int third) -> float { return first * second * third; } /// an example static function /// @return `0` static int static_function_example() { return 0; } /// an example static function with `auto` return type /// @return `0` static auto static_auto_function_example() { return 0; } /// an example static function with trailing return type /// @return `0` static auto static_trailing_type_function_example() -> int { return 0; } /// an example template function, deleted by default /// @return Not applicable, seeing that the default definition has been deleted. template T template_function_example() = delete; /// an example specialization of the template function example /// @return Forty-two template <> int template_function_example() { return 42; } //------------------------------------------------------------------------------------------------------------------------------------------ ================================================ FILE: test_files/namespaces.cpp ================================================ /* Copyright 2018 Adobe All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. */ //------------------------------------------------------------------------------------------------------------------------------------------ namespace foo { //------------------------------------------------------------------------------------------------------------------------------------------ namespace bar::baz { //------------------------------------------------------------------------------------------------------------------------------------------ /// function contained within namespaces `foo::bar::baz`. void function(); //------------------------------------------------------------------------------------------------------------------------------------------ } // namespace bar::baz //------------------------------------------------------------------------------------------------------------------------------------------ /// function contained within namespace `foo`. void function(); //------------------------------------------------------------------------------------------------------------------------------------------ } // namespace foo //------------------------------------------------------------------------------------------------------------------------------------------ ================================================ FILE: test_files/point.cpp ================================================ /* Copyright 2018 Adobe All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. */ // AST dump with // clang --std=c++1z -Xclang -ast-dump -fsyntax-only ./test_files/pod.cpp #include //------------------------------------------------------------------------------------------------------------------------------------------ /// @brief An example point class template struct point { /// The `x` coordinate of the point. T x{0}; /// The `y` coordinate of the point. T y{0}; /// A static routine that returns the origin point. /// @return The point `(0, 0)` static constexpr auto origin() { return point(); } /// Default constructor of default definition. constexpr point() noexcept = default; /// Value-based constructor that takes `x` and `y` values and sinks them into place. /// @param _x The `x` coordinate to sink. /// @param _y The `y` coordinate to sink. constexpr point(T _x, T _y) noexcept : x(std::move(_x)), y(std::move(_y)) {} /// Equality operator. /// @return `true` iff the two points' `x` and `y` coordinates are memberwise equal. friend inline constexpr bool operator==(const point& a, const point& b) { return (a.x == b.x) && (a.y == b.y); } /// Inequality operator. /// @return `true` iff the two points' `x` or `y` coordinates are memberwise inequal. friend inline constexpr bool operator!=(const point& a, const point& b) { return !(a == b); } /// Subtraction operator. /// @param a The point to be subtracted from. /// @param b The point to subtract. /// @return A new point whose axis values are subtractions of the two inputs' axis values. friend inline constexpr point operator-(const point& a, const point& b) { return point(a.x - b.x, a.y - b.y); } /// Subtraction-assignment operator. /// @param a The point to subtract from this point /// @return A reference to `this`. constexpr point& operator-=(const point& a) { x -= a.x; y -= a.y; return *this; } }; //------------------------------------------------------------------------------------------------------------------------------------------ ================================================ FILE: test_files/typedef_and_alias.cpp ================================================ /* Copyright 2018 Adobe All Rights Reserved. NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. */ // AST dump with // TERM="" clang --std=c++1z -Xclang -ast-dump -fsyntax-only -fparse-all-comments -fcomment-block-commands=hyde ./typedef_and_alias.cpp //------------------------------------------------------------------------------------------------------------------------------------------ /// Example typedef expression whose underlying type is `int`. typedef int typedef_example; /// Example using expression whose underlying type is `int`. using using_example = int; //------------------------------------------------------------------------------------------------------------------------------------------ /// @brief Example class with two type definitions template struct template_example { /// Type derived from the first template parameter. typedef T typedef_from_T; /// Type derived from the second template parameter. using using_from_U = U; }; //------------------------------------------------------------------------------------------------------------------------------------------ /// Partial specialization of the above `template_example` template template using using_partial_specialization_example = template_example; /// Full specialization of the above partial specialization using using_full_specialization_example = using_partial_specialization_example; /// Using typedef to define another full specialization of the above partial specialization typedef using_partial_specialization_example typedef_full_specialization_example; //------------------------------------------------------------------------------------------------------------------------------------------ /// @brief Example struct that leverages type aliases defined above. struct template_example_instantiator { /// Example partial specialization template using using_partial_specialization_example = template_example; /// Example full specialization using using_full_specialization_example = using_partial_specialization_example; /// Example partial specialization using `typedef` typedef using_partial_specialization_example typedef_full_specialization_example; }; //------------------------------------------------------------------------------------------------------------------------------------------