pax_global_header00006660000000000000000000000064135275634300014522gustar00rootroot0000000000000052 comment=fb4244b244e71410dcd2298bf2b3f399c2370658 xdg-utils-cxx-1.0.1/000077500000000000000000000000001352756343000142415ustar00rootroot00000000000000xdg-utils-cxx-1.0.1/.gitignore000066400000000000000000000004761352756343000162400ustar00rootroot00000000000000# Prerequisites *.d # Compiled Object files *.slo *.lo *.o *.obj # Precompiled Headers *.gch *.pch # Compiled Dynamic libraries *.so *.dylib *.dll # Fortran module files *.mod *.smod # Compiled Static libraries *.lai *.la *.a *.lib # Executables *.exe *.out *.app # CLion .idea # Build directory cmake-build-* xdg-utils-cxx-1.0.1/.travis.yml000066400000000000000000000010021352756343000163430ustar00rootroot00000000000000os: linux language: cpp compiler: gcc sudo: required notifications: email: false addons: apt: packages: - cmake - gcovr - lcov script: - cmake . -DCMAKE_BUILD_TYPE=Debug -DXDG_UTILS_TESTS=On -DXDG_UTILS_CODE_COVERAGE=On - make ctest_coverage -j${nproc} after_success: - git clone https://github.com/azubieta/python-codacy-coverage.git --depth 1 - pushd python-codacy-coverage; sudo python setup.py install; popd; - python-codacy-coverage -vr code_coverage_report.xml -l cpp xdg-utils-cxx-1.0.1/CMakeLists.txt000066400000000000000000000013261352756343000170030ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(XdgUtils) set(PROJECT_VERSION 0.1.1) set(PROJECT_DESCRIPTION "Freedesktop standards C++ implementation") set(PROJECT_HOMEPAGE_URL https://github.com/azubieta/xdg-utils-cxx) ## Configuration options option(XDG_UTILS_SHARED "Build shared libs instead of static" OFF) option(XDG_UTILS_TESTS "Build tests" OFF) option(XDG_UTILS_CODE_COVERAGE "Build Code Coverage report" OFF) ## Global variables # Request C++-11 set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) # Set GNU Install Dirs variables include(GNUInstallDirs) # Prepare code coverage include(cmake/code_coverge_config.cmake) # Sources add_subdirectory(src) # Tests enable_testing() add_subdirectory(tests) xdg-utils-cxx-1.0.1/LICENSE000066400000000000000000000020731352756343000152500ustar00rootroot00000000000000MIT License Copyright (c) 2018-2019 Alexis López Zubieta 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. xdg-utils-cxx-1.0.1/README.md000066400000000000000000000023331352756343000155210ustar00rootroot00000000000000# Xdg Utils C++ [![Build Status](https://travis-ci.org/azubieta/xdg-utils-cxx.svg?branch=master)](https://travis-ci.org/azubieta/xdg-utils-cxx) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/9e34fda85c3d46ab83b44071d145a917)](https://www.codacy.com/app/azubieta/xdg-utils?utm_source=github.com&utm_medium=referral&utm_content=azubieta/xdg-utils&utm_campaign=Badge_Grade) [![Codacy Badge](https://api.codacy.com/project/badge/Coverage/9e34fda85c3d46ab83b44071d145a917)](https://www.codacy.com/app/azubieta/xdg-utils?utm_source=github.com&utm_medium=referral&utm_content=azubieta/xdg-utils&utm_campaign=Badge_Coverage) Implementation of the Free Desktop Standards in C++. This is a project was started to fulfill the need of a reliable implementations of such standards in the AppImage project. It is totally standalone and only depends on the standard c++ libraries (stdlib). It has been split in different modules according to the Free Desktop Standards, currently are implemented: - [Desktop Entry 1.2](https://standards.freedesktop.org/desktop-entry-spec/1.2/) (mostly complete) - [Base Dir 0.7](https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html) (draft) PR are welcome. xdg-utils-cxx-1.0.1/cmake/000077500000000000000000000000001352756343000153215ustar00rootroot00000000000000xdg-utils-cxx-1.0.1/cmake/code_coverage_utils.cmake000066400000000000000000000275011352756343000223350ustar00rootroot00000000000000# Copyright (c) 2012 - 2017, Lars Bilke # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, # are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # 2. Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # 3. Neither the name of the copyright holder nor the names of its contributors # may be used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # CHANGES: # # 2012-01-31, Lars Bilke # - Enable Code Coverage # # 2013-09-17, Joakim Söderberg # - Added support for Clang. # - Some additional usage instructions. # # 2016-02-03, Lars Bilke # - Refactored functions to use named parameters # # 2017-06-02, Lars Bilke # - Merged with modified version from github.com/ufz/ogs # # # USAGE: # # 1. Copy this file into your cmake modules path. # # 2. Add the following line to your CMakeLists.txt: # include(CodeCoverage) # # 3. Append necessary compiler flags: # APPEND_COVERAGE_COMPILER_FLAGS() # # 4. If you need to exclude additional directories from the report, specify them # using the COVERAGE_LCOV_EXCLUDES variable before calling SETUP_TARGET_FOR_COVERAGE_LCOV. # Example: # set(COVERAGE_LCOV_EXCLUDES 'dir1/*' 'dir2/*') # # 5. Use the functions described below to create a custom make target which # runs your test executable and produces a code coverage report. # # 6. Build a Debug build: # cmake -DCMAKE_BUILD_TYPE=Debug .. # make # make my_coverage_target # include(CMakeParseArguments) # Check prereqs find_program( GCOV_PATH gcov ) find_program( LCOV_PATH NAMES lcov lcov.bat lcov.exe lcov.perl) find_program( GENHTML_PATH NAMES genhtml genhtml.perl genhtml.bat ) find_program( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/scripts/test) find_program( SIMPLE_PYTHON_EXECUTABLE python ) if(NOT GCOV_PATH) message(FATAL_ERROR "gcov not found! Aborting...") endif() # NOT GCOV_PATH if("${CMAKE_CXX_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang") if("${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS 3) message(FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting...") endif() elseif(NOT CMAKE_COMPILER_IS_GNUCXX) message(FATAL_ERROR "Compiler is not GNU gcc! Aborting...") endif() set(COVERAGE_COMPILER_FLAGS "-g -O0 --coverage -fprofile-arcs -ftest-coverage" CACHE INTERNAL "") set(CMAKE_CXX_FLAGS_COVERAGE ${COVERAGE_COMPILER_FLAGS} CACHE STRING "Flags used by the C++ compiler during coverage builds." FORCE ) set(CMAKE_C_FLAGS_COVERAGE ${COVERAGE_COMPILER_FLAGS} CACHE STRING "Flags used by the C compiler during coverage builds." FORCE ) set(CMAKE_EXE_LINKER_FLAGS_COVERAGE "" CACHE STRING "Flags used for linking binaries during coverage builds." FORCE ) set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE "" CACHE STRING "Flags used by the shared libraries linker during coverage builds." FORCE ) mark_as_advanced( CMAKE_CXX_FLAGS_COVERAGE CMAKE_C_FLAGS_COVERAGE CMAKE_EXE_LINKER_FLAGS_COVERAGE CMAKE_SHARED_LINKER_FLAGS_COVERAGE ) if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") message(WARNING "Code coverage results with an optimised (non-Debug) build may be misleading") endif() # NOT CMAKE_BUILD_TYPE STREQUAL "Debug" if(CMAKE_C_COMPILER_ID STREQUAL "GNU") link_libraries(gcov) else() set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage") endif() # Defines a target for running and collection code coverage information # Builds dependencies, runs the given executable and outputs reports. # NOTE! The executable should always have a ZERO as exit code otherwise # the coverage generation will not complete. # # SETUP_TARGET_FOR_COVERAGE_LCOV( # NAME testrunner_coverage # New target name # EXECUTABLE testrunner -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR # DEPENDENCIES testrunner # Dependencies to build first # ) function(SETUP_TARGET_FOR_COVERAGE_LCOV) set(options NONE) set(oneValueArgs NAME) set(multiValueArgs EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES LCOV_ARGS GENHTML_ARGS) cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) if(NOT LCOV_PATH) message(FATAL_ERROR "lcov not found! Aborting...") endif() # NOT LCOV_PATH if(NOT GENHTML_PATH) message(FATAL_ERROR "genhtml not found! Aborting...") endif() # NOT GENHTML_PATH # Setup target add_custom_target(${Coverage_NAME} # Cleanup lcov COMMAND ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -directory . --zerocounters # Create baseline to make sure untouched files show up in the report COMMAND ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -c -i -d . -o ${Coverage_NAME}.base # Run tests COMMAND ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} # Capturing lcov counters and generating report COMMAND ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --directory . --capture --output-file ${Coverage_NAME}.info # add baseline counters COMMAND ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -a ${Coverage_NAME}.base -a ${Coverage_NAME}.info --output-file ${Coverage_NAME}.total COMMAND ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --remove ${Coverage_NAME}.total ${COVERAGE_LCOV_EXCLUDES} --output-file ${PROJECT_BINARY_DIR}/${Coverage_NAME}.info.cleaned COMMAND ${GENHTML_PATH} ${Coverage_GENHTML_ARGS} -o ${Coverage_NAME} ${PROJECT_BINARY_DIR}/${Coverage_NAME}.info.cleaned COMMAND ${CMAKE_COMMAND} -E remove ${Coverage_NAME}.base ${Coverage_NAME}.total ${PROJECT_BINARY_DIR}/${Coverage_NAME}.info.cleaned WORKING_DIRECTORY ${PROJECT_BINARY_DIR} DEPENDS ${Coverage_DEPENDENCIES} COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report." ) # Show where to find the lcov info report add_custom_command(TARGET ${Coverage_NAME} POST_BUILD COMMAND ; COMMENT "Lcov code coverage info report saved in ${Coverage_NAME}.info." ) # Show info where to find the report add_custom_command(TARGET ${Coverage_NAME} POST_BUILD COMMAND ; COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report." ) endfunction() # SETUP_TARGET_FOR_COVERAGE_LCOV # Defines a target for running and collection code coverage information # Builds dependencies, runs the given executable and outputs reports. # NOTE! The executable should always have a ZERO as exit code otherwise # the coverage generation will not complete. # # SETUP_TARGET_FOR_COVERAGE_GCOVR_XML( # NAME ctest_coverage # New target name # EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR # DEPENDENCIES executable_target # Dependencies to build first # ) function(SETUP_TARGET_FOR_COVERAGE_GCOVR_XML) set(options NONE) set(oneValueArgs NAME) set(multiValueArgs EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) if(NOT SIMPLE_PYTHON_EXECUTABLE) message(FATAL_ERROR "python not found! Aborting...") endif() # NOT SIMPLE_PYTHON_EXECUTABLE if(NOT GCOVR_PATH) message(FATAL_ERROR "gcovr not found! Aborting...") endif() # NOT GCOVR_PATH # Combine excludes to several -e arguments set(GCOVR_EXCLUDES "") foreach(EXCLUDE ${COVERAGE_GCOVR_EXCLUDES}) list(APPEND GCOVR_EXCLUDES "-e") list(APPEND GCOVR_EXCLUDES "${EXCLUDE}") endforeach() add_custom_target(${Coverage_NAME} # Run tests ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} # Running gcovr COMMAND ${GCOVR_PATH} --xml -r ${PROJECT_SOURCE_DIR} ${GCOVR_EXCLUDES} --object-directory=${PROJECT_BINARY_DIR} -o ${Coverage_NAME}.xml WORKING_DIRECTORY ${PROJECT_BINARY_DIR} DEPENDS ${Coverage_DEPENDENCIES} COMMENT "Running gcovr to produce Cobertura code coverage report." ) # Show info where to find the report add_custom_command(TARGET ${Coverage_NAME} POST_BUILD COMMAND ; COMMENT "Cobertura code coverage report saved in ${Coverage_NAME}.xml." ) endfunction() # SETUP_TARGET_FOR_COVERAGE_GCOVR_XML # Defines a target for running and collection code coverage information # Builds dependencies, runs the given executable and outputs reports. # NOTE! The executable should always have a ZERO as exit code otherwise # the coverage generation will not complete. # # SETUP_TARGET_FOR_COVERAGE_GCOVR_HTML( # NAME ctest_coverage # New target name # EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR # DEPENDENCIES executable_target # Dependencies to build first # ) function(SETUP_TARGET_FOR_COVERAGE_GCOVR_HTML) set(options NONE) set(oneValueArgs NAME) set(multiValueArgs EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) if(NOT SIMPLE_PYTHON_EXECUTABLE) message(FATAL_ERROR "python not found! Aborting...") endif() # NOT SIMPLE_PYTHON_EXECUTABLE if(NOT GCOVR_PATH) message(FATAL_ERROR "gcovr not found! Aborting...") endif() # NOT GCOVR_PATH # Combine excludes to several -e arguments set(GCOVR_EXCLUDES "") foreach(EXCLUDE ${COVERAGE_GCOVR_EXCLUDES}) list(APPEND GCOVR_EXCLUDES "-e") list(APPEND GCOVR_EXCLUDES "${EXCLUDE}") endforeach() add_custom_target(${Coverage_NAME} # Run tests ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} # Create folder COMMAND ${CMAKE_COMMAND} -E make_directory ${PROJECT_BINARY_DIR}/${Coverage_NAME} # Running gcovr COMMAND ${GCOVR_PATH} --html --html-details -r ${PROJECT_SOURCE_DIR} ${GCOVR_EXCLUDES} --object-directory=${PROJECT_BINARY_DIR} -o ${Coverage_NAME}/index.html WORKING_DIRECTORY ${PROJECT_BINARY_DIR} DEPENDS ${Coverage_DEPENDENCIES} COMMENT "Running gcovr to produce HTML code coverage report." ) # Show info where to find the report add_custom_command(TARGET ${Coverage_NAME} POST_BUILD COMMAND ; COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report." ) endfunction() # SETUP_TARGET_FOR_COVERAGE_GCOVR_HTML function(APPEND_COVERAGE_COMPILER_FLAGS) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) message(STATUS "Appending code coverage compiler flags: ${COVERAGE_COMPILER_FLAGS}") endfunction() # APPEND_COVERAGE_COMPILER_FLAGS xdg-utils-cxx-1.0.1/cmake/code_coverge_config.cmake000066400000000000000000000002121352756343000222670ustar00rootroot00000000000000if(XDG_UTILS_CODE_COVERAGE) include(${CMAKE_CURRENT_LIST_DIR}/code_coverage_utils.cmake) append_coverage_compiler_flags() endif() xdg-utils-cxx-1.0.1/cmake/generate_cmake_package.cmake000066400000000000000000000012631352756343000227320ustar00rootroot00000000000000include(CMakePackageConfigHelpers) configure_package_config_file( "${PROJECT_SOURCE_DIR}/package/cmake/XdgUtilsConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/XdgUtilsConfig.cmake" INSTALL_DESTINATION ${PROJECT_INSTALL_CMAKEDIR} PATH_VARS PROJECT_INSTALL_LIBDIR PROJECT_INSTALL_INCLUDEDIR ) write_basic_package_version_file( "${CMAKE_CURRENT_BINARY_DIR}/XdgUtilsConfigVersion.cmake" VERSION ${PROJECT_VERSION} COMPATIBILITY AnyNewerVersion ) install( FILES "${CMAKE_CURRENT_BINARY_DIR}/XdgUtilsConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/XdgUtilsConfigVersion.cmake" DESTINATION ${PROJECT_INSTALL_CMAKEDIR} COMPONENT ${PROJECT_COMPONENT_DEVEL} ) xdg-utils-cxx-1.0.1/cmake/module_utils.cmake000066400000000000000000000037401352756343000210340ustar00rootroot00000000000000function(add_xdgutils_module name srcs) if(XDG_UTILS_SHARED) message("Creating ${name} as SHARED library") add_library(${name} SHARED ${srcs}) else() message("-- Creating module target: ${name} STATIC") add_library(${name} STATIC ${srcs}) endif() message("-- Creating module target alias: ${PROJECT_NAME}::${name}") add_library(${PROJECT_NAME}::${name} ALIAS ${name}) # Configure target properties target_include_directories( ${name} PUBLIC $ PUBLIC $ PUBLIC $ ) set_target_properties( ${name} PROPERTIES PREFIX "lib${PROJECT_NAME}" PUBLIC INTERFACE_LINK_DIRECTORIES $ PUBLIC INTERFACE_LINK_DIRECTORIES $ ) endfunction() ## # Install module resources ## SET(PROJECT_INSTALL_LIBS "" CACHE INTERNAL "Libs binaries") function(install_xdgutils_module name) # Install binaries install( TARGETS ${name} EXPORT ${name} LIBRARY DESTINATION ${PROJECT_INSTALL_LIBDIR} COMPONENT ${name} ARCHIVE DESTINATION ${PROJECT_INSTALL_LIBDIR} COMPONENT ${name} ) # Install library headers install( DIRECTORY ${PROJECT_INCLUDEDIR}/${PROJECT_NAME}/${name} DESTINATION ${PROJECT_INSTALL_INCLUDEDIR} COMPONENT ${name} ) # Build cmake target config files export(EXPORT ${name} NAMESPACE ${PROJECT_NAME}::) # Install cmake target config files install( EXPORT ${name} FILE "${PROJECT_NAME}${name}.cmake" NAMESPACE ${PROJECT_NAME}:: DESTINATION ${PROJECT_INSTALL_CMAKEDIR} COMPONENT ${name} ) # Register library SET(PROJECT_INSTALL_LIBS ${PROJECT_INSTALL_LIBS} "${PROJECT_NAME}${name}" CACHE INTERNAL "Libs binaries") endfunction() xdg-utils-cxx-1.0.1/include/000077500000000000000000000000001352756343000156645ustar00rootroot00000000000000xdg-utils-cxx-1.0.1/include/XdgUtils/000077500000000000000000000000001352756343000174275ustar00rootroot00000000000000xdg-utils-cxx-1.0.1/include/XdgUtils/BaseDir/000077500000000000000000000000001352756343000207405ustar00rootroot00000000000000xdg-utils-cxx-1.0.1/include/XdgUtils/BaseDir/BaseDir.h000066400000000000000000000023631352756343000224260ustar00rootroot00000000000000#pragma once // system #include namespace XdgUtils { namespace BaseDir { /** * Single base directory relative to which user-specific data files should be written. * * See: https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html * @return XDG_DATA_HOME */ const std::string Home(); /** * Single base directory relative to which user-specific data files should be written. * * See: https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html * @return XDG_DATA_HOME */ const std::string XdgDataHome(); /** * Single base directory relative to which user-specific data files should be written. * * See: https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html * @return XDG_CONFIG_HOME */ const std::string XdgConfigHome(); /** * Single base directory relative to which user-specific data files should be written. * * See: https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html * @return XDG_CACHE_HOME */ const std::string XdgCacheHome(); } } xdg-utils-cxx-1.0.1/include/XdgUtils/DesktopEntry/000077500000000000000000000000001352756343000220625ustar00rootroot00000000000000xdg-utils-cxx-1.0.1/include/XdgUtils/DesktopEntry/DesktopEntry.h000066400000000000000000000066441352756343000247000ustar00rootroot00000000000000#pragma once // system #include #include #include #include // local #include #include #include namespace XdgUtils { namespace DesktopEntry { /** * Represents a Desktop Entry according to https://standards.freedesktop.org/desktop-entry-spec/ * * Entry contents are reference through its path. * A path is made by: /[]. Locale section is optional. */ class DesktopEntry { public: DesktopEntry(); explicit DesktopEntry(const std::string& data); explicit DesktopEntry(std::istream& data); DesktopEntry(const DesktopEntry& other); DesktopEntry& operator=(const DesktopEntry& other); DesktopEntry(DesktopEntry&& other) noexcept; DesktopEntry& operator=(DesktopEntry&& other) noexcept; virtual ~DesktopEntry(); /** * Set to the entry reference by . * If the Group or Key doesn't exist they will be created. * @param path * @param value */ void set(const std::string& path, const std::string& value); /** * Get the value of the entry pointed by . * @param path * @param fallback * @return entry value or if the entry doesn't exist */ std::string get(const std::string& path, const std::string& fallback = "") const; /** * @param path * @return true if there is an entry at , false otherwise */ bool exists(const std::string& path) const; /** * Remove entry or group referenced by * @param path */ void remove(const std::string& path); /** * @return Paths to all available entries and groups */ std::vector paths() const; /** * Attempts to read the stream as a Desktop Entry. Any existent data will be replaced. * @param input * @throw ReadError if something goes wrong */ friend std::istream& operator>>(std::istream& is, const DesktopEntry& entry); /** * Attempts to write this Desktop Entry to the stream. * @param output */ friend std::ostream& operator<<(std::ostream& os, const DesktopEntry& entry); /** * Access the key pointed by if it doesn't exist a new one will be created. * @param keyPath * @return */ DesktopEntryKeyValue operator[](const DesktopEntryKeyPath& keyPath); /** * Access the key pointed by if it doesn't exist a new one will be created. * @param keyPath * @return */ DesktopEntryKeyValue operator[](const std::string& keyPath); bool operator==(const DesktopEntry& rhs) const; bool operator!=(const DesktopEntry& rhs) const; private: struct Priv; std::unique_ptr priv; }; } } xdg-utils-cxx-1.0.1/include/XdgUtils/DesktopEntry/DesktopEntryExecValue.h000066400000000000000000000025441352756343000264750ustar00rootroot00000000000000#pragma once // system #include #include namespace XdgUtils { namespace DesktopEntry { /** * Utility class to handle values from the 'DesktopEntry/Exec' key in Desktop Entries. * More details at: https://standards.freedesktop.org/desktop-entry-spec/latest/ar01s07.html */ class DesktopEntryExecValue { public: DesktopEntryExecValue(); explicit DesktopEntryExecValue(const std::string& value); virtual ~DesktopEntryExecValue(); /** * @return total of strings contained */ unsigned long size() const; /** * Access string at * @param i * @return string */ std::string& operator[](int i); /** * @return string list raw representation to be set on the DesktopEntry */ std::string dump(); /** * Append at the end of the list * @param string */ void append(const std::string& string); /** * Remove item at * @param pos */ void remove(int pos); private: struct Priv; std::unique_ptr priv; }; } } xdg-utils-cxx-1.0.1/include/XdgUtils/DesktopEntry/DesktopEntryKeyPath.h000066400000000000000000000046611352756343000261630ustar00rootroot00000000000000#pragma once // system #include #include // local #include #include namespace XdgUtils { namespace DesktopEntry { /** * Utility class to parse, validate and extract Key Paths sections. * * A KeyPath has the following format: * /[] */ class DesktopEntryKeyPath { public: explicit DesktopEntryKeyPath(const std::string& path); DesktopEntryKeyPath(const std::string& group, const std::string& key, const std::string& locale); DesktopEntryKeyPath(const DesktopEntryKeyPath& other); DesktopEntryKeyPath& operator=(const DesktopEntryKeyPath& other); DesktopEntryKeyPath& operator=(const std::string& path); virtual ~DesktopEntryKeyPath(); bool operator==(const DesktopEntryKeyPath& rhs) const; bool operator!=(const DesktopEntryKeyPath& rhs) const; bool operator==(const std::string& path) const; bool operator!=(const std::string& path) const; friend std::ostream& operator<<(std::ostream& os, const DesktopEntryKeyPath& path); /** * @return group section of the KeyPath */ std::string group() const; /** * Set group section of the path * @param group */ void setGroup(const std::string& group); /** * @return key section of the KeyPath or empty string if none */ std::string key() const; /** * Set the key section of the path * @param key */ void setKey(const std::string& key); /** * @return locale section of the KeyPath or empty string if none */ std::string locale() const; /** * Set the locale section of the path * @param locale */ void setLocale(const std::string& locale); /** * @return key and locale sections */ std::string fullKey() const; /** * @return string representation of the KeyPath */ std::string string() const; private: struct Priv; std::unique_ptr priv; }; } } xdg-utils-cxx-1.0.1/include/XdgUtils/DesktopEntry/DesktopEntryKeyValue.h000066400000000000000000000024121352756343000263330ustar00rootroot00000000000000#pragma once // system #include #include namespace XdgUtils { namespace DesktopEntry { /** * @brief Desktop Entry values accessor. * * Provides a mutable reference to the inner desktop entry key values and cast constructors * to ease manipulation. */ class DesktopEntryKeyValue { public: DesktopEntryKeyValue(const DesktopEntryKeyValue& other); DesktopEntryKeyValue& operator=(const DesktopEntryKeyValue& other); explicit operator const char*(); explicit operator std::string(); explicit operator bool(); explicit operator int(); explicit operator double(); DesktopEntryKeyValue& operator=(double value); DesktopEntryKeyValue& operator=(int value); DesktopEntryKeyValue& operator=(const char* value); DesktopEntryKeyValue& operator=(const std::string& value); DesktopEntryKeyValue& operator=(bool value); ~DesktopEntryKeyValue(); protected: class Priv; std::unique_ptr priv; friend class DesktopEntry; explicit DesktopEntryKeyValue(Priv* priv); }; } } xdg-utils-cxx-1.0.1/include/XdgUtils/DesktopEntry/DesktopEntryStringsValue.h000066400000000000000000000027631352756343000272450ustar00rootroot00000000000000#pragma once // system #include #include namespace XdgUtils { namespace DesktopEntry { /** * Utility class to handle 'string(s)' values in Desktop Entries. * More details at: https://standards.freedesktop.org/desktop-entry-spec/latest/ar01s04.html */ class DesktopEntryStringsValue { public: DesktopEntryStringsValue(); /** * Create a instance from a DesktopEntry value * @param data */ explicit DesktopEntryStringsValue(const std::string& data); virtual ~DesktopEntryStringsValue(); /** * @return total of strings contained */ unsigned long size() const; /** * Access string at * @param i * @return string */ std::string& operator[](int i); /** * @return string list raw representation to be set on the DesktopEntry */ std::string dump(); /** * Append at the end of the list * @param string */ void append(const std::string& string); /** * Remove item at * @param pos */ void remove(int pos); private: struct Priv; std::unique_ptr priv; }; } } xdg-utils-cxx-1.0.1/include/XdgUtils/DesktopEntry/Exceptions.h000066400000000000000000000017071352756343000243610ustar00rootroot00000000000000#pragma once // system #include namespace XdgUtils { namespace DesktopEntry { class DesktopEntryError : public std::runtime_error { public: explicit DesktopEntryError(const std::string& what) : std::runtime_error(what) {} }; class ReadError : public DesktopEntryError { public: explicit ReadError(const std::string& what) : DesktopEntryError(what) {} }; class ParseError : public DesktopEntryError { public: explicit ParseError(const std::string& what) : DesktopEntryError(what) {} }; class MalformedPathError : public DesktopEntryError { public: explicit MalformedPathError(const std::string& what) : DesktopEntryError(what) {} }; class BadCast : public DesktopEntryError { public: explicit BadCast(const std::string& what) : DesktopEntryError(what) {} }; } } xdg-utils-cxx-1.0.1/package/000077500000000000000000000000001352756343000156345ustar00rootroot00000000000000xdg-utils-cxx-1.0.1/package/cmake/000077500000000000000000000000001352756343000167145ustar00rootroot00000000000000xdg-utils-cxx-1.0.1/package/cmake/XdgUtilsConfig.cmake.in000066400000000000000000000015141352756343000232150ustar00rootroot00000000000000set(@PROJECT_NAME@_VERSION @PROJECT_VERSION@) @PACKAGE_INIT@ set_and_check(XDGUTILS_LIBDIR @PACKAGE_PROJECT_INSTALL_LIBDIR@) set_and_check(XDGUTILS_INCLUDE_DIRECTORIES @PACKAGE_PROJECT_INSTALL_INCLUDEDIR@) set(XDGUTILS_LINK_LIBRARIES @PROJECT_INSTALL_LIBS@) ## Try to find the required components foreach(comp ${@PROJECT_NAME@_FIND_COMPONENTS}) set(COMPONENT_TARGETS_FILE "${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@${comp}.cmake") if(EXISTS "${COMPONENT_TARGETS_FILE}") set(@PROJECT_NAME@_${comp}_FOUND TRUE) include("${COMPONENT_TARGETS_FILE}") message(STATUS "@PROJECT_NAME@::${comp} found") else() if(@PROJECT_NAME@_FIND_REQUIRED_${comp}) message(FATAL_ERROR "@PROJECT_NAME@::${comp} not found !") endif() endif() endforeach() check_required_components(@PROJECT_NAME@) xdg-utils-cxx-1.0.1/src/000077500000000000000000000000001352756343000150305ustar00rootroot00000000000000xdg-utils-cxx-1.0.1/src/BaseDir/000077500000000000000000000000001352756343000163415ustar00rootroot00000000000000xdg-utils-cxx-1.0.1/src/BaseDir/BaseDir.cpp000066400000000000000000000041241352756343000203570ustar00rootroot00000000000000//system #include #include // library #include // local #include "XdgUtils/BaseDir/BaseDir.h" namespace XdgUtils { namespace BaseDir { const std::string Home() { std::string home; auto envHome = getenv("HOME"); if (envHome) home = envHome; return home; } const std::string XdgDataHome() { std::string xdgDataHome; // try to read it from the environment char* dataHomeVal = getenv("XDG_DATA_HOME"); if (dataHomeVal != nullptr && !std::string(dataHomeVal).empty()) xdgDataHome = dataHomeVal; else { // Fallback to $HOME/.local/share auto homeVal = Home(); if (!homeVal.empty()) xdgDataHome = homeVal + "/.local/share"; } return xdgDataHome; } const std::string XdgConfigHome() { std::string xdgConfigHome; // try to read it from the environment char* configHomeVal = getenv("XDG_CONFIG_HOME"); if (configHomeVal != nullptr && !std::string(configHomeVal).empty()) xdgConfigHome = configHomeVal; else { // Fallback to $HOME/.config auto homeVal = Home(); if (!homeVal.empty()) xdgConfigHome = homeVal + "/.config"; } return xdgConfigHome; } const std::string XdgCacheHome() { std::string xdgCacheHome; // try to read it from the environment char* cacheHomeVal = getenv("XDG_CACHE_HOME"); if (cacheHomeVal != nullptr && !std::string(cacheHomeVal).empty()) xdgCacheHome = cacheHomeVal; else { // Fallback to $HOME/.cache auto homeVal = Home(); if (!homeVal.empty()) xdgCacheHome = homeVal + "/.cache"; } return xdgCacheHome; } } } xdg-utils-cxx-1.0.1/src/BaseDir/CMakeLists.txt000066400000000000000000000001121352756343000210730ustar00rootroot00000000000000add_xdgutils_module(BaseDir BaseDir.cpp) install_xdgutils_module(BaseDir) xdg-utils-cxx-1.0.1/src/CMakeLists.txt000066400000000000000000000011001352756343000175600ustar00rootroot00000000000000# Build variables set(PROJECT_INCLUDEDIR ${PROJECT_SOURCE_DIR}/include) # Install variables set(PROJECT_INSTALL_INCLUDEDIR ${CMAKE_INSTALL_INCLUDEDIR}/XdgUtils) set(PROJECT_INSTALL_LIBDIR ${CMAKE_INSTALL_LIBDIR}/XdgUtils) set(PROJECT_INSTALL_CMAKEDIR ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}) set(PROJECT_COMPONENT ${PROJECT_NAME}) set(PROJECT_COMPONENT_DEVEL ${PROJECT_NAME}) # Modules include(${PROJECT_SOURCE_DIR}/cmake/module_utils.cmake) add_subdirectory(DesktopEntry) add_subdirectory(BaseDir) include(${PROJECT_SOURCE_DIR}/cmake/generate_cmake_package.cmake) xdg-utils-cxx-1.0.1/src/DesktopEntry/000077500000000000000000000000001352756343000174635ustar00rootroot00000000000000xdg-utils-cxx-1.0.1/src/DesktopEntry/AST/000077500000000000000000000000001352756343000201125ustar00rootroot00000000000000xdg-utils-cxx-1.0.1/src/DesktopEntry/AST/AST.cpp000066400000000000000000000044241352756343000212510ustar00rootroot00000000000000#include "AST.h" #include "Group.h" #include "Comment.h" namespace XdgUtils { namespace DesktopEntry { namespace AST { std::vector>& AST::getEntries() { return entries; } const std::vector>& AST::getEntries() const { return entries; } void AST::setEntries(const std::vector>& entries) { this->entries.clear(); for (const auto& entry: entries) this->entries.emplace_back(entry->clone()); } bool AST::operator==(const AST& rhs) const { auto aItr = entries.begin(); auto bItr = rhs.entries.begin(); while (aItr != entries.end() && bItr != rhs.entries.end()) { if (*aItr->get() != *bItr->get()) return false; ++aItr, ++bItr; } // Return true if both iterators reached the end return (aItr == entries.end() && bItr == rhs.entries.end()); } bool AST::operator!=(const AST& rhs) const { return !(rhs == *this); } std::ostream& operator<<(std::ostream& os, const AST& ast) { ast.write(os); return os; } void AST::write(std::ostream& output) const { auto last = --(entries.end()); for (auto itr = entries.begin(); itr != entries.end(); ++itr) { itr->get()->write(output); if (itr != last) output << std::endl; } } AST::AST(const AST& other) { setEntries(other.entries); } AST& AST::operator=(const AST& other) { setEntries(other.entries); return *this; } AST::AST(AST&& other) noexcept { entries = std::move(other.entries); } AST& AST::operator=(AST&& other) noexcept { entries = std::move(other.entries); return *this; } AST::AST() = default; } } } xdg-utils-cxx-1.0.1/src/DesktopEntry/AST/AST.h000066400000000000000000000026731352756343000207220ustar00rootroot00000000000000#pragma once // system #include #include #include // local #include "Node.h" namespace XdgUtils { namespace DesktopEntry { namespace AST { /** * AST stands for abstract syntax tree. This class provides a representation of a Desktop Entry in form of a * tree. * * For more details about AST see https://en.wikipedia.org/wiki/Abstract_syntax_tree */ class AST { public: AST(); // Copy constructor AST(const AST& other); // Copy assignment AST& operator=(const AST& other); // Move constructor AST(AST&& other) noexcept; // Move assignment AST& operator=(AST&& other) noexcept; std::vector>& getEntries(); const std::vector>& getEntries() const; void setEntries(const std::vector>& entries); bool operator==(const AST& rhs) const; bool operator!=(const AST& rhs) const; void write(std::ostream& output) const; friend std::ostream& operator<<(std::ostream& os, const AST& ast); private: std::vector> entries; }; } } } xdg-utils-cxx-1.0.1/src/DesktopEntry/AST/CMakeLists.txt000066400000000000000000000005171352756343000226550ustar00rootroot00000000000000set(srcs AST.cpp Group.cpp Entry.cpp Comment.cpp Node.cpp) add_library(XdgUtilsDesktopEntryAST OBJECT ${srcs}) target_include_directories(XdgUtilsDesktopEntryAST PUBLIC $) if(XDG_UTILS_SHARED) set_property(TARGET XdgUtilsDesktopEntryAST PROPERTY POSITION_INDEPENDENT_CODE ON) endif() xdg-utils-cxx-1.0.1/src/DesktopEntry/AST/Comment.cpp000066400000000000000000000027001352756343000222170ustar00rootroot00000000000000#include "Comment.h" namespace XdgUtils { namespace DesktopEntry { namespace AST { bool Comment::operator==(const Comment& rhs) const { return value == rhs.value; } bool Comment::operator!=(const Comment& rhs) const { return !(rhs == *this); } Comment::Comment(const std::string& raw, const std::string& value) : raw(raw), value(value) {} std::string Comment::getValue() const { return value; } void Comment::setValue(const std::string& newValue) { // '#' is required ahead of non blank comment lines if (raw.empty() && !newValue.empty()) raw = "#"; if (value.empty()) { raw += newValue; value = newValue; } else { auto pos = raw.find(value); raw.replace(pos, std::string::npos, newValue); value = newValue; } } void Comment::write(std::ostream& output) const { output << raw; } std::ostream& operator<<(std::ostream& os, const Comment& comment) { comment.write(os); return os; } Node* Comment::clone() const { return new Comment(*this); } } } } xdg-utils-cxx-1.0.1/src/DesktopEntry/AST/Comment.h000066400000000000000000000015531352756343000216710ustar00rootroot00000000000000#pragma once // system #include #include // local #include "Node.h" namespace XdgUtils { namespace DesktopEntry { namespace AST { class Comment : public Node { public: Comment(const std::string& raw, const std::string& value); std::string getValue() const override; void setValue(const std::string& newValue) override; void write(std::ostream& output) const override; Node* clone() const override; bool operator==(const Comment& rhs) const; bool operator!=(const Comment& rhs) const; friend std::ostream& operator<<(std::ostream& os, const Comment& comment); private: std::string raw; std::string value; }; } } } xdg-utils-cxx-1.0.1/src/DesktopEntry/AST/Entry.cpp000066400000000000000000000045751352756343000217320ustar00rootroot00000000000000// local #include "Entry.h" namespace XdgUtils { namespace DesktopEntry { namespace AST { bool Entry::operator==(const Entry& rhs) const { return keyValue == rhs.keyValue && localeValue == rhs.localeValue && valueValue == rhs.valueValue; } bool Entry::operator!=(const Entry& rhs) const { return !(rhs == *this); } Entry::Entry(const std::string& keyRaw, const std::string& keyValue, const std::string& localeRaw, const std::string& localeValue, const std::string& valueRaw, const std::string& valueValue) : keyRaw(keyRaw), keyValue(keyValue), localeRaw(localeRaw), localeValue(localeValue), valueRaw(valueRaw), valueValue(valueValue) {} Entry::Entry(const std::string& key, const std::string& locale, const std::string& value) : keyRaw(key), keyValue(key) { if (!locale.empty()) { localeRaw = ('[' + locale + ']'); localeValue = (locale); } if (!value.empty()) { valueValue = value; } valueRaw = '=' + value; } std::string Entry::getValue() const { return valueValue; } void Entry::setValue(const std::string& newValue) { if (valueValue.empty()) { valueRaw += newValue; valueValue = newValue; } else { auto pos = valueRaw.find(valueValue); valueRaw.replace(pos, std::string::npos, newValue); valueValue = newValue; } } void Entry::write(std::ostream& output) const { output << keyRaw << localeRaw << valueRaw; } std::string Entry::getKey() const { return keyValue; } std::string Entry::getLocale() const { return localeValue; } std::ostream& operator<<(std::ostream& os, const Entry& entry) { entry.write(os); return os; } Node* Entry::clone() const { return new Entry(*this); } } } } xdg-utils-cxx-1.0.1/src/DesktopEntry/AST/Entry.h000066400000000000000000000033011352756343000213610ustar00rootroot00000000000000#pragma once // system #include #include // local #include "Node.h" namespace XdgUtils { namespace DesktopEntry { namespace AST { /** * Describes a Desktop Entry entry. * See https://standards.freedesktop.org/desktop-entry-spec/latest/ */ class Entry : public Node { public: Entry(const std::string &key, const std::string &locale, const std::string &value); Entry(const std::string& keyRaw, const std::string& keyValue, const std::string& localeRaw, const std::string& localeValue, const std::string& valueRaw, const std::string& valueValue); std::string getKey() const; std::string getLocale() const; std::string getValue() const override; void setValue(const std::string& newValue) override; void write(std::ostream& output) const override; Node* clone() const override; /** * Compare two Entries according to they fields values. Raw values will be ignored * @param rhs * @return */ bool operator==(const Entry& rhs) const; bool operator!=(const Entry& rhs) const; friend std::ostream& operator<<(std::ostream& os, const Entry& entry); private: std::string keyRaw; std::string keyValue; std::string localeRaw; std::string localeValue; std::string valueRaw; std::string valueValue; }; } } } xdg-utils-cxx-1.0.1/src/DesktopEntry/AST/Group.cpp000066400000000000000000000073611352756343000217210ustar00rootroot00000000000000#include #include "Group.h" #include "Entry.h" #include "Comment.h" namespace XdgUtils { namespace DesktopEntry { namespace AST { Group::Group(const std::string& headerRawValue, const std::string& headerValue) : headerRawValue( headerRawValue), headerValue(headerValue) { if (headerValue.empty()) throw std::runtime_error("Group Header cannot be emtpy"); } std::string Group::getValue() const { return headerValue; } void Group::setValue(const std::string& newValue) { if (newValue.empty()) throw std::runtime_error("Group Header cannot be emtpy"); auto pos = headerRawValue.find(headerValue); headerRawValue = '[' + newValue + ']'; headerValue = newValue; } void Group::write(std::ostream& output) const { output << headerRawValue << std::endl; auto last = --(entries.end()); for (auto itr = entries.begin(); itr != entries.end(); ++itr) { itr->get()->write(output); if (itr != last) output << std::endl; } } std::vector>& Group::getEntries() { return entries;; } const std::vector>& Group::getEntries() const { return entries; } void Group::setEntries(const std::vector>& entries) { this->entries.clear(); for (const auto& entry: entries) this->entries.emplace_back(entry->clone()); } bool Group::operator==(const Group& rhs) const { if (headerValue != rhs.headerValue) return false; auto aItr = entries.begin(); auto bItr = rhs.entries.begin(); while (aItr != entries.end() && bItr != rhs.entries.end()) { if (*aItr->get() != *bItr->get()) return false; ++aItr, ++bItr; } // Return true if both iterators reached the end return (aItr == entries.end() && bItr == rhs.entries.end()); } bool Group::operator!=(const Group& rhs) const { return !(rhs == *this); } std::ostream& operator<<(std::ostream& os, const Group& group) { group.write(os); return os; } Node* Group::clone() const { return new Group(*this); } Group::Group(const Group& other) : headerValue(other.headerValue), headerRawValue(other.headerRawValue) { setEntries(other.entries); } Group& Group::operator=(const Group& other) { headerValue = other.headerValue; headerRawValue = other.headerRawValue; setEntries(other.entries); return *this; } Group::Group(Group&& other) noexcept { headerValue = std::move(other.headerValue); headerRawValue = std::move(other.headerRawValue); entries = std::move(other.entries); } Group& Group::operator=(Group&& other) noexcept { headerValue = std::move(other.headerValue); headerRawValue = std::move(other.headerRawValue); entries = std::move(other.entries); return *this; } Group::~Group() = default; } } } xdg-utils-cxx-1.0.1/src/DesktopEntry/AST/Group.h000066400000000000000000000040301352756343000213540ustar00rootroot00000000000000#pragma once // system #include #include #include #include // local #include "Node.h" namespace XdgUtils { namespace DesktopEntry { namespace AST { class Group : public Node { public: /** * Create a group node. and cannot be empty * @param headerRawValue * @param headerValue * @throw std::runtime_error if or are empty. */ Group(const std::string& headerRawValue, const std::string& headerValue); // Copy constructor Group(const Group& other); // Copy assigment Group& operator=(const Group& other); // Move constructor Group(Group&& other) noexcept; // Move assigment Group& operator=(Group&& other) noexcept; ~Group() override; std::string getValue() const override; /** * @param newValue (cannot be empty) * @throw std::runtime_error if is empty. */ void setValue(const std::string& newValue) override; void write(std::ostream& output) const override; Node* clone() const override; std::vector>& getEntries(); const std::vector>& getEntries() const; void setEntries(const std::vector>& entries); bool operator==(const Group& rhs) const; bool operator!=(const Group& rhs) const; friend std::ostream& operator<<(std::ostream& os, const Group& group); private: std::string headerRawValue; std::string headerValue; std::vector> entries; }; } } } xdg-utils-cxx-1.0.1/src/DesktopEntry/AST/Node.cpp000066400000000000000000000021241352756343000215020ustar00rootroot00000000000000// Local #include "Node.h" #include "Comment.h" #include "Entry.h" #include "Group.h" namespace XdgUtils { namespace DesktopEntry { namespace AST { bool Node::operator!=(const Node& rhs) const { return !operator==(rhs); } bool Node::operator==(const Node& rhs) const { try { auto a = dynamic_cast(*this); auto b = dynamic_cast(rhs); return a == b; } catch (const std::bad_cast&) {} try { auto a = dynamic_cast(*this); auto b = dynamic_cast(rhs); return a == b; } catch (const std::bad_cast&) {} try { auto a = dynamic_cast(*this); auto b = dynamic_cast(rhs); return a == b; } catch (const std::bad_cast&) {} return false; } } } } xdg-utils-cxx-1.0.1/src/DesktopEntry/AST/Node.h000066400000000000000000000014001352756343000211430ustar00rootroot00000000000000#pragma once #include namespace XdgUtils { namespace DesktopEntry { namespace AST { class Node { public: virtual std::string getValue() const = 0; virtual void setValue(const std::string& newValue) = 0; virtual void write(std::ostream& output) const = 0; virtual Node* clone() const = 0; friend std::ostream& operator<<(std::ostream& os, const Node& node) { node.write(os); return os; } bool operator==(const Node& rhs) const; bool operator!=(const Node& rhs) const; virtual ~Node() = default; }; } } } xdg-utils-cxx-1.0.1/src/DesktopEntry/CMakeLists.txt000066400000000000000000000007001352756343000222200ustar00rootroot00000000000000set(DesktopEntry_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}) add_subdirectory(AST) add_subdirectory(Reader) set( srcs DesktopEntry.cpp DesktopEntryKeyPath.cpp DesktopEntryKeyValue.cpp DesktopEntryExecValue.cpp DesktopEntryStringsValue.cpp "$" "$" ) add_xdgutils_module(DesktopEntry "${srcs}") install_xdgutils_module(DesktopEntry) xdg-utils-cxx-1.0.1/src/DesktopEntry/DesktopEntry.cpp000066400000000000000000000204701352756343000226250ustar00rootroot00000000000000#include #include #include #include // local #include #include #include "AST/AST.h" #include "Reader/Tokenizer.h" #include "Reader/Reader.h" #include "Reader/Errors.h" #include "DesktopEntryKeyValuePriv.h" namespace XdgUtils { namespace DesktopEntry { struct DesktopEntry::Priv { AST::AST ast; // Cache of the existent entries and their paths std::map> paths; typedef std::pair> path_entry_t; void read(std::istream& in) { try { Reader::Reader reader; ast = reader.read(in); updatePaths(); } catch (const Reader::MalformedEntry& err) { throw ReadError(err.what()); } } /** * Recreate the paths mappings to the nodes inside the AST */ void updatePaths() { paths.clear(); for (const auto& astEntry: ast.getEntries()) { if (auto group = std::dynamic_pointer_cast(astEntry)) { paths[group->getValue()] = astEntry; for (const auto& groupEntry: group->getEntries()) { if (auto entry = std::dynamic_pointer_cast(groupEntry)) { DesktopEntryKeyPath path(group->getValue(), entry->getKey(), entry->getLocale()); paths[path.string()] = groupEntry; } } } } } void createGroup(const std::string& name) { auto group = std::make_shared("[" + name + "]", name); // update entries auto& entries = ast.getEntries(); entries.emplace_back(group); // update path paths[name] = group; } void createEntry(const DesktopEntryKeyPath& keyPath, const std::string& value) { auto group = std::dynamic_pointer_cast(paths[keyPath.group()]); // create group if it doesn't exists if (!group) { createGroup(keyPath.group()); group = std::dynamic_pointer_cast(paths[keyPath.group()]); } // append entry to group auto entry = std::make_shared(keyPath.key(), keyPath.locale(), value); std::vector>& entries = group->getEntries(); entries.emplace_back(entry); // update paths paths[keyPath.string()] = entry; } void removeGroup(const std::string& groupName) { auto g = paths[groupName]; // remove item from the AST auto astEntries = ast.getEntries(); auto itemItr = std::find(astEntries.begin(), astEntries.end(), g); astEntries.erase(itemItr); ast.setEntries(astEntries); // remove path paths.erase(groupName); } void removeEntry(const std::string& path) { // Find path split auto splitIdx = path.rfind('/'); if (splitIdx != std::string::npos) { auto groupName = path.substr(0, splitIdx); auto keyName = path.substr(splitIdx + 1, path.size() - splitIdx); auto groupNode = paths[groupName]; auto entryNode = paths[path]; auto g = dynamic_cast(groupNode.get()); // remove item from the AST auto groupEntries = g->getEntries(); auto itemItr = std::find(groupEntries.begin(), groupEntries.end(), groupNode); groupEntries.erase(itemItr); g->setEntries(groupEntries); // remove path paths.erase(path); } } std::shared_ptr getOrCreateEntry(const DesktopEntryKeyPath& keyPath) { auto itr = paths.find(keyPath.string()); if (itr == paths.end()) { if (keyPath.key().empty()) createGroup(keyPath.group()); else createEntry(keyPath, ""); } return paths[keyPath.string()]; } }; DesktopEntry::DesktopEntry() : priv(new Priv) {} DesktopEntry::DesktopEntry(const std::string& data) : priv(new Priv) { std::stringstream ss(data); priv->read(ss); } DesktopEntry::DesktopEntry(std::istream& data) : priv(new Priv) { priv->read(data); } DesktopEntry::DesktopEntry(const DesktopEntry& other) : priv(new Priv()) { priv->ast = other.priv->ast; priv->updatePaths(); } DesktopEntry& DesktopEntry::operator=(const DesktopEntry& other) { priv->ast = other.priv->ast; priv->updatePaths(); return *this; } DesktopEntry::DesktopEntry::DesktopEntry(DesktopEntry&& other) noexcept { priv = std::move(other.priv); } DesktopEntry& DesktopEntry::operator=(DesktopEntry&& other) noexcept { priv = std::move(other.priv); return *this; } DesktopEntry::~DesktopEntry() = default; std::string DesktopEntry::get(const std::string& path, const std::string& fallback) const { auto itr = priv->paths.find(path); if (itr == priv->paths.end()) return fallback; return itr->second->getValue(); } void DesktopEntry::set(const std::string& path, const std::string& value) { auto itr = priv->paths.find(path); if (itr != priv->paths.end()) { // Update node value itr->second->setValue(value); } else { // Find path split DesktopEntryKeyPath keyPath(path); // create the group if it doesn't exists if (priv->paths.find(keyPath.group()) == priv->paths.end()) priv->createGroup(keyPath.group()); if (!keyPath.key().empty()) priv->createEntry(keyPath, value); } } bool DesktopEntry::exists(const std::string& path) const { return priv->paths.find(path) != priv->paths.end(); } void DesktopEntry::remove(const std::string& path) { if (exists(path)) { auto splitIdx = path.rfind('/'); if (splitIdx != std::string::npos) priv->removeEntry(path); else priv->removeGroup(path); } } std::vector DesktopEntry::paths() const { std::vector paths; for (const auto& itr: priv->paths) paths.emplace_back(itr.first); return paths; } std::ostream& operator<<(std::ostream& os, const DesktopEntry& entry) { entry.priv->ast.write(os); return os; } std::istream& operator>>(std::istream& is, const DesktopEntry& entry) { entry.priv->read(is); return is; } bool DesktopEntry::operator==(const DesktopEntry& rhs) const { return priv->ast == rhs.priv->ast; } bool DesktopEntry::operator!=(const DesktopEntry& rhs) const { return !(rhs == *this); } DesktopEntryKeyValue DesktopEntry::operator[](const DesktopEntryKeyPath& keyPath) { auto entry = priv->getOrCreateEntry(keyPath); return DesktopEntryKeyValue(new DesktopEntryKeyValue::Priv(keyPath, entry)); } DesktopEntryKeyValue DesktopEntry::operator[](const std::string& keyPath) { return operator[](DesktopEntryKeyPath(keyPath)); } } } xdg-utils-cxx-1.0.1/src/DesktopEntry/DesktopEntryExecValue.cpp000066400000000000000000000076671352756343000244440ustar00rootroot00000000000000// system #include #include // local #include #include namespace XdgUtils { namespace DesktopEntry { struct DesktopEntryExecValue::Priv { std::vector sections; std::string escapedCharacters = "\"`$\\"; std::string reservedCharacters = " \t\n\"\'\\<>~|&;$*?#()`"; void parse(const std::string& value) { // marks when the next char was escaped bool escapeNext = false; // marks when traversing a section between quotes '"' bool inBetweenQuotes = false; std::stringstream section; for (const auto& c: value) { if (c == '\"') { if (inBetweenQuotes) finishSection(section); inBetweenQuotes = !inBetweenQuotes; continue; } if (inBetweenQuotes) { if (escapeNext) { section << c; escapeNext = false; continue; } if (c == '\\') { escapeNext = true; continue; } section << c; } else { if (c == ' ') finishSection(section); else section << c; } } finishSection(section); } void finishSection(std::stringstream& section) { auto sectionStr = section.str(); section.str(""); if (!sectionStr.empty()) sections.emplace_back(sectionStr); } std::string dump() { bool firstSection = true; std::stringstream res; for (const auto& section: sections) { if (firstSection) firstSection = false; else res << " "; if (containsReservedCharacter(section)) { // dump escaped section res << "\""; for (const auto& c: section) { if (escapedCharacters.find(c) != std::string::npos) res << "\\"; res << c; } res << "\""; } else res << section; } return res.str(); } bool containsReservedCharacter(const std::string& string) { for (const auto& c: string) if (reservedCharacters.find(c) != std::string::npos) return true; return false; } }; DesktopEntryExecValue::DesktopEntryExecValue() : priv(new Priv()) {} DesktopEntryExecValue::DesktopEntryExecValue(const std::string& value) : priv(new Priv()) { priv->parse(value); } DesktopEntryExecValue::~DesktopEntryExecValue() = default; unsigned long DesktopEntryExecValue::size() const { return priv->sections.size(); } std::string& DesktopEntryExecValue::operator[](int i) { return priv->sections[i]; } std::string DesktopEntryExecValue::dump() { return priv->dump(); } void DesktopEntryExecValue::append(const std::string& string) { priv->sections.emplace_back(string); } void DesktopEntryExecValue::remove(int pos) { priv->sections.erase(priv->sections.begin() + pos); } } } xdg-utils-cxx-1.0.1/src/DesktopEntry/DesktopEntryKeyPath.cpp000066400000000000000000000132571352756343000241200ustar00rootroot00000000000000// system #include // local #include #include namespace XdgUtils { namespace DesktopEntry { struct DesktopEntryKeyPath::Priv { std::string group; std::string key; std::string locale; explicit Priv(const std::string& path) { parse(path); } Priv(const std::string& group, const std::string& key, const std::string& locale) : group(group), key(key), locale(locale) {} Priv& operator=(const Priv& other) = default; bool operator==(const Priv& rhs) const { return group == rhs.group && key == rhs.key && locale == rhs.locale; } bool operator!=(const Priv& rhs) const { return !(rhs == *this); } void parse(const std::string& path) { // remove old values group.clear(); key.clear(); locale.clear(); auto itr = path.begin(); // parse the group section, '[' and ']' char are not allowed std::string invalidGroupChars = "[]"; while (*itr != '\0' && *itr != '/') { if (invalidGroupChars.find(*itr) != std::string::npos) throw ParseError(std::string("Unexpected char in path group section: ") + *itr); ++itr; } group = {path.begin(), itr}; if (*itr == '\0') return; // parse the key section // ignore group-key separator ('/') ++itr; auto keyBegin = itr; while (*itr != '\0' && *itr != '[') { if (!std::isalnum(*itr) && *itr != '-' && *itr != '_') throw ParseError(std::string("Unexpected char in path key section: ") + *itr); ++itr; } key = {keyBegin, itr}; if (*itr == '\0') return; // parse locale section // ignore key-locale separator ('[') ++itr; auto localeBegin = itr; while (*itr != '\0' && *itr != ']') ++itr; if (*itr == '\0') throw ParseError(std::string("Unexpected char in path key section: ") + *itr); locale = {localeBegin, itr}; } std::string string() const { std::stringstream pathBuilder; pathBuilder << group; if (key.empty()) return pathBuilder.str(); pathBuilder << '/' << key; if (locale.empty()) return pathBuilder.str(); pathBuilder << '[' << locale << ']'; return pathBuilder.str(); } }; DesktopEntryKeyPath::DesktopEntryKeyPath(const std::string& path) : priv(new Priv(path)) {} DesktopEntryKeyPath::DesktopEntryKeyPath(const std::string& group, const std::string& key, const std::string& locale) : priv(new Priv(group, key, locale)) { } std::string DesktopEntryKeyPath::group() const { return priv->group; } void DesktopEntryKeyPath::setGroup(const std::string& group) { if (group.empty()) throw MalformedPathError("Group section cannot be empty"); priv->group = group; } std::string DesktopEntryKeyPath::key() const { return priv->key; } void DesktopEntryKeyPath::setKey(const std::string& key) { for (const auto& c: key) if (!std::isalnum(c) && c != '-' && c != '_') throw MalformedPathError("Unexpected char in key name"); priv->key = key; } std::string DesktopEntryKeyPath::locale() const { return priv->locale; } void DesktopEntryKeyPath::setLocale(const std::string& locale) { priv->locale = locale; } std::string DesktopEntryKeyPath::string() const { return priv->string(); } DesktopEntryKeyPath::DesktopEntryKeyPath(const DesktopEntryKeyPath& other) { priv.reset(new Priv(*other.priv)); } DesktopEntryKeyPath& DesktopEntryKeyPath::operator=(const DesktopEntryKeyPath& other) { *priv = *(other.priv); return *this; } DesktopEntryKeyPath& DesktopEntryKeyPath::operator=(const std::string& path) { priv->parse(path); return *this; } bool DesktopEntryKeyPath::operator==(const DesktopEntryKeyPath& rhs) const { return *priv == *(rhs.priv); } bool DesktopEntryKeyPath::operator!=(const DesktopEntryKeyPath& rhs) const { return !(rhs == *this); } bool DesktopEntryKeyPath::operator==(const std::string& path) const { return priv->string() == path; } bool DesktopEntryKeyPath::operator!=(const std::string& path) const { return !(*this == path); } std::ostream& operator<<(std::ostream& os, const DesktopEntryKeyPath& path) { os << path.string(); return os; } std::string DesktopEntryKeyPath::fullKey() const { return priv->key + '[' + priv->locale + ']'; } DesktopEntryKeyPath::~DesktopEntryKeyPath() = default; } } xdg-utils-cxx-1.0.1/src/DesktopEntry/DesktopEntryKeyValue.cpp000066400000000000000000000055251352756343000242770ustar00rootroot00000000000000// system #include // local #include #include #include "DesktopEntryKeyValuePriv.h" namespace XdgUtils { namespace DesktopEntry { DesktopEntryKeyValue::DesktopEntryKeyValue(const DesktopEntryKeyValue& other) { priv.reset(new Priv(*other.priv)); } DesktopEntryKeyValue& DesktopEntryKeyValue::operator=(const DesktopEntryKeyValue& other) { priv.reset(new Priv(*other.priv)); return *this; } DesktopEntryKeyValue::operator std::string() { return priv->node->getValue(); } DesktopEntryKeyValue& DesktopEntryKeyValue::operator=(const std::string& value) { priv->node->setValue(value); return *this; } DesktopEntryKeyValue::operator bool() { auto valueStr = priv->node->getValue(); // lower std::transform(valueStr.begin(), valueStr.end(), valueStr.begin(), ::tolower); if (valueStr.find("true") != std::string::npos) return true; if (valueStr.find("false") != std::string::npos) return false; // the value is not a bool throw XdgUtils::DesktopEntry::BadCast("DesktopEntryKeyValue " + valueStr + " can't be converted to bool"); } DesktopEntryKeyValue& DesktopEntryKeyValue::operator=(bool value) { std::string valueStr = value ? "true" : "false"; priv->node->setValue(valueStr); return *this; } DesktopEntryKeyValue::operator const char*() { return priv->node->getValue().c_str(); } DesktopEntryKeyValue& DesktopEntryKeyValue::operator=(const char* value) { if (value != nullptr) priv->node->setValue(value); else priv->node->setValue(std::string()); return *this; } DesktopEntryKeyValue::operator int() { auto valueStr = priv->node->getValue(); auto value = std::stoi(valueStr); return value; } DesktopEntryKeyValue& DesktopEntryKeyValue::operator=(int value) { priv->node->setValue(std::to_string(value)); return *this; } DesktopEntryKeyValue::operator double() { auto valueStr = priv->node->getValue(); auto value = std::stod(valueStr); return value; } DesktopEntryKeyValue& DesktopEntryKeyValue::operator=(double value) { priv->node->setValue(std::to_string(value)); return *this; } DesktopEntryKeyValue::DesktopEntryKeyValue(DesktopEntryKeyValue::Priv* priv) : priv(priv) { } DesktopEntryKeyValue::~DesktopEntryKeyValue() = default; } } xdg-utils-cxx-1.0.1/src/DesktopEntry/DesktopEntryKeyValuePriv.h000066400000000000000000000010151352756343000245730ustar00rootroot00000000000000#pragma once // local #include #include #include "DesktopEntryKeyValuePriv.h" #include namespace XdgUtils { namespace DesktopEntry { class DesktopEntryKeyValue::Priv { public: DesktopEntryKeyPath path; std::shared_ptr node; Priv(const DesktopEntryKeyPath& path, const std::shared_ptr& node) : path(path), node(node) {} }; } } xdg-utils-cxx-1.0.1/src/DesktopEntry/DesktopEntryStringsValue.cpp000066400000000000000000000050471352756343000251770ustar00rootroot00000000000000// system #include #include // local #include namespace XdgUtils { namespace DesktopEntry { struct DesktopEntryStringsValue::Priv { std::vector sections; void parse(const std::string& in) { sections.clear(); std::stringstream section; bool escapeNext = false; for (const auto& c: in) { if (escapeNext) { section << c; escapeNext = false; continue; } if (c == '\\') { escapeNext = true; continue; } if (c == ';') { sections.emplace_back(section.str()); // clear string section.str(""); continue; } section << c; } auto last = section.str(); if (!last.empty()) sections.emplace_back(last); } std::string dump() { std::stringstream res; for (const auto& str: sections) { for (const auto& c: str) { // escape semicolon if (c == ';') res << "\\"; res << c; } res << ';'; } return res.str(); }; }; DesktopEntryStringsValue::DesktopEntryStringsValue() : priv(new Priv()) { } DesktopEntryStringsValue::DesktopEntryStringsValue(const std::string& data) : priv(new Priv()) { priv->parse(data); } unsigned long DesktopEntryStringsValue::size() const { return priv->sections.size(); } std::string& DesktopEntryStringsValue::operator[](int i) { return priv->sections[i]; } std::string DesktopEntryStringsValue::dump() { return priv->dump(); } void DesktopEntryStringsValue::append(const std::string& string) { priv->sections.emplace_back(string); } void DesktopEntryStringsValue::remove(int pos) { priv->sections.erase(priv->sections.begin() + pos); } DesktopEntryStringsValue::~DesktopEntryStringsValue() = default; } } xdg-utils-cxx-1.0.1/src/DesktopEntry/README.md000066400000000000000000000017151352756343000207460ustar00rootroot00000000000000# Desktop Entry This module attempts to provide an implementation of the FreeDesktop Desktop Entry Specification as is in https://standards.freedesktop.org/desktop-entry-spec/latest/index.html It is divided in three sections: - AST: (stands for Abstract Syntax Tree) provides a data structure to hold the inner representation of the Desktop Entry - Reader: provides a parser based on formal grammar production rules which tries to resemble the Desktop Entry format. - Public Interface: which is composed by the classes available to the module users and abstracts them from all the parsing and formatting rules. Example usage: ``` using namespace XdgUtils::DesktopEntry; ... std::ifstream ifs ("miapp.desktop", std::ifstream::in); DesktopEntry entry(ifs) entry.set("Desktop Entry/Name[es]","Mi Gran App"); std::ofstream ofs ("miapp.desktop", std::ofstream::out); entry.write(ofs) ``` For more usage examples consult the DesktopEntry unit tests. xdg-utils-cxx-1.0.1/src/DesktopEntry/Reader/000077500000000000000000000000001352756343000206655ustar00rootroot00000000000000xdg-utils-cxx-1.0.1/src/DesktopEntry/Reader/CMakeLists.txt000066400000000000000000000006271352756343000234320ustar00rootroot00000000000000set(srcs Lexer.cpp Token.cpp Tokenizer.cpp Reader.cpp) add_library(XdgUtilsDesktopEntryReader OBJECT ${srcs}) target_include_directories(XdgUtilsDesktopEntryReader PUBLIC $ PUBLIC $ ) if(XDG_UTILS_SHARED) set_property(TARGET XdgUtilsDesktopEntryReader PROPERTY POSITION_INDEPENDENT_CODE ON) endif() xdg-utils-cxx-1.0.1/src/DesktopEntry/Reader/Errors.h000066400000000000000000000007761352756343000223240ustar00rootroot00000000000000#pragma once // system #include namespace XdgUtils { namespace DesktopEntry { namespace Reader { class MalformedEntry : public std::runtime_error { public: explicit MalformedEntry(const std::string& msg) : runtime_error(msg) {} }; class NoTokensLeft : public std::runtime_error { public: explicit NoTokensLeft(const std::string& msg) : runtime_error(msg) {} }; } } } xdg-utils-cxx-1.0.1/src/DesktopEntry/Reader/Lexer.cpp000066400000000000000000000027301352756343000224520ustar00rootroot00000000000000#include #include "Lexer.h" namespace XdgUtils { namespace DesktopEntry { namespace Reader { Lexer::Lexer(std::istream& input) : input(input), c('\0') {} Lexer::~Lexer() = default; bool Lexer::consume() { if (input.get(c)) { if (eol) { lineCont++; eol = false; } // Increment line count in the next iteration if (isEOL()) eol = true; return true; } else { c = L'\0'; eof = true; return false; } } char Lexer::top() { return c; } bool Lexer::isAlfaNumeric() const { return isalnum(c); } bool Lexer::isOpenSquareBracket() const { return c == L'['; } bool Lexer::isCloseSquareBracket() const { return c == L']'; } bool Lexer::isHash() const { return c == L'#'; } bool Lexer::isSpace() const { return isblank(c); } bool Lexer::isEOL() const { return c == L'\n'; } bool Lexer::isEOF() const { return eof; } bool Lexer::isAssignment() const { return c == L'='; } unsigned long Lexer::line() { return lineCont; } bool Lexer::isDash() { return c == L'-'; } } } } xdg-utils-cxx-1.0.1/src/DesktopEntry/Reader/Lexer.h000066400000000000000000000023351352756343000221200ustar00rootroot00000000000000#pragma once #include #include namespace XdgUtils { namespace DesktopEntry { namespace Reader { class Lexer { std::istream &input; char c; unsigned long lineCont = 0; bool eol = false; // Line increment flag bool eof = false; // EOF flag public: explicit Lexer(std::istream& input); virtual ~Lexer(); /** * Read next char from the input * @return true if the character was properly read, false otherwise */ bool consume(); /** * @return Last read character */ char top(); bool isAlfaNumeric() const; bool isOpenSquareBracket() const; bool isCloseSquareBracket() const; bool isAssignment() const; bool isHash() const; bool isSpace() const; bool isEOL() const; bool isEOF() const; unsigned long line(); bool isDash(); }; } } } xdg-utils-cxx-1.0.1/src/DesktopEntry/Reader/Reader.cpp000066400000000000000000000074361352756343000226050ustar00rootroot00000000000000// system #include #include #include #include // local #include "Reader.h" #include "Tokenizer.h" #include "Errors.h" using namespace XdgUtils::DesktopEntry::AST; namespace XdgUtils { namespace DesktopEntry { namespace Reader { AST::AST Reader::read(std::istream& input) { Tokenizer tokenizer(input); XdgUtils::DesktopEntry::AST::AST ast; std::vector> entries; tokenizer.consume(); while (!tokenizer.isCompleted() && tokenizer.get().type != TokenType::UNKNOWN) { if (tokenizer.get().type == TokenType::COMMENT) { entries.emplace_back(new Comment(tokenizer.get().raw, tokenizer.get().value)); tokenizer.consume(); continue; } if (tokenizer.get().type == TokenType::GROUP_HEADER) { entries.emplace_back(readGroup(tokenizer)); continue; } // only comments or group headers tokens can be found other things are an error std::stringstream err; err << "Unexpected token: " << tokenizer.get().type << " at line: " << tokenizer.get().line; throw MalformedEntry(err.str()); } if (!tokenizer.isCompleted()) throw MalformedEntry(tokenizer.get().value); ast.setEntries(entries); return ast; } std::shared_ptr Reader::readGroup(Tokenizer& tokenizer) { std::shared_ptr g(new Group(tokenizer.get().raw, tokenizer.get().value)); std::vector> entries; while (!tokenizer.isCompleted() && tokenizer.consume() && (tokenizer.get().type == TokenType::COMMENT || tokenizer.get().type == TokenType::ENTRY_KEY)) { if (tokenizer.get().type == TokenType::ENTRY_KEY) entries.emplace_back(readEntry(tokenizer)); if (tokenizer.get().type == TokenType::COMMENT) entries.emplace_back(new Comment(tokenizer.get().raw, tokenizer.get().value)); } if (!tokenizer.isCompleted() && tokenizer.get().type == TokenType::UNKNOWN) throw MalformedEntry(tokenizer.get().value); g->setEntries(entries); return g; } AST::Entry* Reader::readEntry(Tokenizer& tokenizer) { Token key = tokenizer.get(); // next token could be a Local or a Value tokenizer.consume(); Token next = tokenizer.get(); if (next.type == TokenType::UNKNOWN) throw MalformedEntry(tokenizer.get().value); if (next.type == TokenType::ENTRY_LOCALE) { tokenizer.consume(); if (tokenizer.get().type == TokenType::ENTRY_VALUE) return new Entry(key.raw, key.value, next.raw, next.value, tokenizer.get().raw, tokenizer.get().value); } if (tokenizer.get().type == TokenType::ENTRY_VALUE) return new Entry(key.raw, key.value, "", "", tokenizer.get().raw, tokenizer.get().value); // if this position is reached something went wrong std::stringstream errorMsg; errorMsg << "Unexpected token " << tokenizer.get().type << " at line " << tokenizer.get().line; throw MalformedEntry(errorMsg.str()); } } } } xdg-utils-cxx-1.0.1/src/DesktopEntry/Reader/Reader.h000066400000000000000000000042741352756343000222470ustar00rootroot00000000000000#pragma once // system #include #include #include // local #include "AST/AST.h" #include "AST/Group.h" #include "AST/Entry.h" #include "Tokenizer.h" namespace XdgUtils { namespace DesktopEntry { namespace Reader { /** * Reads a Desktop Entry according to the following formal grammar. * F -> C* G+ EOF * C -> # (any ASCII char)+ EOL * C -> (space)* EOL * G -> GH E* * GH -> [ (A-Za-z0-9)+ ] EOL * E -> EK EL? EV EOL * EK -> (A-Za-z0-9)+ * EL -> [ (any ASCII char)+ ] (space)* * EV -> '=' (any ASCII char)+ EOL * * Where: * F: stands for File * C: stands for Comment * G: stands for Group * GH: stands for Group Header * E: stands for Entry * EK: stands for Entry Key * EL: stands for Entry Locale * EV: stands for Entry Value * * More about formal grammars at: https://en.wikipedia.org/wiki/Formal_grammar * More about Desktop Entries at: https://standards.freedesktop.org/desktop-entry-spec/latest/index.html */ class Reader { public: /** * Read a Desktop Entry into a AST * @param input * @return AST if all goes ok * @throw MalformedEntry in case of errors */ XdgUtils::DesktopEntry::AST::AST read(std::istream& input); /** * Read Group tokens * @param tokenizer * @return Group Node * @throw MalformedEntry in case of errors */ std::shared_ptr readGroup(Tokenizer& tokenizer); /** * Read entry tokens * @param tokenizer * @return Entry node * @throw MalformedEntry in case of errors */ AST::Entry* readEntry(Tokenizer& tokenizer); }; } } } xdg-utils-cxx-1.0.1/src/DesktopEntry/Reader/Token.cpp000066400000000000000000000032451352756343000224550ustar00rootroot00000000000000#include #include "Token.h" namespace XdgUtils { namespace DesktopEntry { namespace Reader { Token::Token(const std::string& raw, unsigned long line, const std::string& value, XdgUtils::DesktopEntry::Reader::TokenType type) : raw(raw), line(line), value(value), type(type) {} bool Token::operator==(const Token& rhs) const { return raw == rhs.raw && line == rhs.line && value == rhs.value && type == rhs.type; } bool Token::operator!=(const Token& rhs) const { return !(rhs == *this); } std::ostream& operator<<(std::ostream& os, const Token& token) { os << "{ raw: \"" << token.raw << "\", "; os << "line: " << token.line << ", "; os << "value: \"" << token.value << "\", "; os << "type: " << token.type << " }"; return os; } std::ostream& operator<<(std::ostream& os, const TokenType& tokenType) { const char* s = nullptr; #define PROCESS_VAL(p) case(p): s = #p; break; switch (tokenType) { PROCESS_VAL(COMMENT); PROCESS_VAL(GROUP_HEADER); PROCESS_VAL(ENTRY_KEY); PROCESS_VAL(ENTRY_LOCALE); PROCESS_VAL(ENTRY_VALUE); PROCESS_VAL(UNKNOWN); } #undef PROCESS_VAL return os << s; } } } } xdg-utils-cxx-1.0.1/src/DesktopEntry/Reader/Token.h000066400000000000000000000017551352756343000221260ustar00rootroot00000000000000#pragma once #include #include namespace XdgUtils { namespace DesktopEntry { namespace Reader { enum TokenType { COMMENT, GROUP_HEADER, ENTRY_KEY, ENTRY_LOCALE, ENTRY_VALUE, UNKNOWN }; struct Token { std::string raw; unsigned long line; std::string value; TokenType type; Token(const std::string& raw, unsigned long line, const std::string& value, TokenType type); bool operator==(const Token& rhs) const; bool operator!=(const Token& rhs) const; friend std::ostream& operator<<(std::ostream& os, const Token& token); }; /** * Print TokenTypes names */ std::ostream& operator<<(std::ostream& os, const TokenType& tokenType); } } } xdg-utils-cxx-1.0.1/src/DesktopEntry/Reader/Tokenizer.cpp000066400000000000000000000174101352756343000233460ustar00rootroot00000000000000#include #include "Tokenizer.h" #include "Token.h" #include "Errors.h" namespace XdgUtils { namespace DesktopEntry { namespace Reader { Tokenizer::Tokenizer(std::istream& input) : lexer(input) {} Tokenizer::~Tokenizer() = default; Token Tokenizer::get() const { if (buffer.begin() != buffer.end()) return *buffer.begin(); else throw NoTokensLeft("There are no tokens left or Tokenizer::consume wasn't called."); } bool Tokenizer::consume() { if (completed) return false; // remove buffer top if (!buffer.empty()) buffer.erase(buffer.begin()); if (buffer.empty()) { if (lexer.isEOF()) completed = true; else buffer = tokenizeLine(); } return !completed; } bool Tokenizer::isCompleted() const { return completed; } std::vector Tokenizer::tokenizeLine() { std::vector lineTokens; std::stringstream raw; // consume spaces while (lexer.consume() && lexer.isSpace() && !lexer.isEOL()) raw << lexer.top(); // Comment Line if (lexer.isHash()) { lineTokens.push_back(tokenizeCommentLine(raw)); return lineTokens; } // pure blank line also a Comment Line if (lexer.isEOL() || lexer.isEOF()) { lineTokens.emplace_back(Token(raw.str(), lexer.line(), raw.str(), COMMENT)); return lineTokens; } // Group Header if (lexer.isOpenSquareBracket()) return tokenizeGroupHeaderLine(raw); // Entry Line if (lexer.isAlfaNumeric()) { lineTokens.push_back(tokenizeEntryKey(raw)); // after an entry key is expected a '[', '=' or a set of whitespaces and a ' if (lexer.isOpenSquareBracket()) { auto locale = tokenizeEntryLocale(raw); lineTokens.push_back(locale); if (locale.type == UNKNOWN) return lineTokens; } if (lexer.isAssignment()) { auto value = tokenizeEntryValue(raw); lineTokens.push_back(value); } return lineTokens; } // if this section is reached the input doesn't conform with any known line type therefore we will // consume the whole line and assume it's an UNKNOWN token lineTokens.emplace_back(tokenizeUnknownLine(raw)); return lineTokens; } Token Tokenizer::tokenizeEntryKey(std::stringstream& raw) { std::stringstream value; do value << lexer.top(); while (lexer.consume() && (lexer.isAlfaNumeric() || lexer.isDash()) && !lexer.isEOL()); raw << value.str(); while (lexer.isSpace()) { raw << lexer.top(); lexer.consume(); } return Token(raw.str(), lexer.line(), value.str(), ENTRY_KEY); } Token Tokenizer::tokenizeEntryLocale(std::stringstream& raw) { std::stringstream rawSection; std::stringstream value; rawSection << lexer.top(); while (lexer.consume() && !lexer.isEOL() && !lexer.isSpace() && !lexer.isCloseSquareBracket() && !lexer.isOpenSquareBracket()) { rawSection << lexer.top(); value << lexer.top(); } if (lexer.isCloseSquareBracket()) { // consume ']' with trailing spaces do rawSection << lexer.top(); while (lexer.consume() && lexer.isSpace()); raw << rawSection.str(); return Token(rawSection.str(), lexer.line(), value.str(), ENTRY_LOCALE); } raw << rawSection.str(); return tokenizeUnknownLine(raw); } Token Tokenizer::tokenizeEntryValue(std::stringstream& raw) { // consume entry value std::stringstream value; std::stringstream sectionRaw; // save '=' raw sectionRaw << lexer.top(); while (lexer.consume() && !lexer.isEOL()) value << lexer.top(); sectionRaw << value.str(); if (lexer.isEOL() || lexer.isEOF()) return Token(sectionRaw.str(), lexer.line(), value.str(), ENTRY_VALUE); raw << sectionRaw.str(); return tokenizeUnknownLine(raw); } Token Tokenizer::tokenizeCommentLine(std::stringstream& raw) { raw << lexer.top(); std::stringstream value; // consume the rest of the line while (lexer.consume() && !lexer.isEOL()) { raw << lexer.top(); value << lexer.top(); } return Token(raw.str(), lexer.line(), value.str(), COMMENT); } std::vector Tokenizer::tokenizeGroupHeaderLine(std::stringstream& raw) { std::vector tokens; // save char '[' raw << lexer.top(); std::stringstream value; // consume until a ']' is found while (lexer.consume() && !lexer.isEOL() && !lexer.isCloseSquareBracket() && !lexer.isOpenSquareBracket()) { raw << lexer.top(); value << lexer.top(); } if (lexer.isCloseSquareBracket()) { raw << lexer.top(); // consume trailing spaces while (lexer.consume() && lexer.isSpace() && !lexer.isEOL()) raw << lexer.top(); if (lexer.isEOL() || lexer.isEOF()) { tokens.emplace_back(Token(raw.str(), lexer.line(), value.str(), GROUP_HEADER)); return tokens; } } // if this section is reached the input doesn't conform with the group header spec therefore the whole // line must be consumed and it must be treated as an error tokens.emplace_back(tokenizeUnknownLine(raw)); return tokens; } Token Tokenizer::tokenizeUnknownLine(std::stringstream& raw) { std::stringstream errorMsg; errorMsg << "Unexpected char \'" << lexer.top() << "\' at " << std::to_string(raw.str().size()); consumeLine(raw); return Token(raw.str(), lexer.line(), errorMsg.str(), UNKNOWN); } void Tokenizer::consumeLine(std::stringstream& data) { data << lexer.top(); while (lexer.consume() && !lexer.isEOL()) data << lexer.top(); } std::vector Tokenizer::consumeAll() { std::vector tokens; while (consume()) tokens.emplace_back(get()); return tokens; } } } } xdg-utils-cxx-1.0.1/src/DesktopEntry/Reader/Tokenizer.h000066400000000000000000000077641352756343000230260ustar00rootroot00000000000000#pragma once #include #include "Lexer.h" #include "Token.h" namespace XdgUtils { namespace DesktopEntry { namespace Reader { class Tokenizer { Lexer lexer; public: explicit Tokenizer(std::istream& input); virtual ~Tokenizer(); /** * Returns the current token * @return */ Token get() const; /** * Move to the next token * @return true if a new token is available, otherwise false; */ bool consume(); bool isCompleted() const; std::vector consumeAll(); private: std::vector buffer; bool completed = false; /** * Map the file contents to tokens in order to ease manipulation * @return list of tokens identified at in the file */ std::vector tokenize(); /** * Attempts to create tokens from the current line following the rules: * L -> C (comment line) * L -> DH (Desktop Header) * L -> E (key-locale-value entry) * * @return tokens found at the given line */ std::vector tokenizeLine(); /** * Attempts to read the current line as a COMMENT token described as: * C -> '#' (all ASCII char)* EOL * * @param raw * @return COMMENT token if all goes ok or UNKNOWN token if something unexpected happens */ Token tokenizeCommentLine(std::stringstream& raw); /** * Attempts to read the current line as a GROUP_HEADER token described as: * GH -> [ (all ASCII chars but '[')+ ](spaces)*EOL * * @param raw input line * @return GROUP_HEADER token if all goes ok UNKNOWN token in case of error */ std::vector tokenizeGroupHeaderLine(std::stringstream& raw); /** * Attempt to read a ENTRY_KEY token described as * (any alphanumeric char)+ * @param raw input line * @return ENTRY_KEY token if all goes ok UNKNOWN token in case of error */ Token tokenizeEntryKey(std::stringstream& raw); /** * Attempt to read a ENTRY_LOCALE token described as: * [ (any ASCII char but ']' and '[')+ ] (space)+ * @param raw input line * @return ENTRY_LOCALE token if all goes ok UNKNOWN token in case of error */ Token tokenizeEntryLocale(std::stringstream& raw); /** * Attempt to read a ENTRY_VALUE token described as: * = (any UTF-8 char) EOL * @param raw input line * @return ENTRY_VALUE token if all goes ok UNKNOWN token in case of error */ Token tokenizeEntryValue(std::stringstream& raw); /** * Assume the current char as unexpected, read what remains from the line and create an UNKNOWN * token. The token value will contain an error message string. * * @param raw input line * @return UNKNOWN token */ Token tokenizeUnknownLine(std::stringstream& raw); /** * Consume all characters until the end of the line * @param data stream where the consumed characters will be placed */ void consumeLine(std::stringstream& data); }; } } } xdg-utils-cxx-1.0.1/tests/000077500000000000000000000000001352756343000154035ustar00rootroot00000000000000xdg-utils-cxx-1.0.1/tests/BaseDir/000077500000000000000000000000001352756343000167145ustar00rootroot00000000000000xdg-utils-cxx-1.0.1/tests/BaseDir/CMakeLists.txt000066400000000000000000000001601352756343000214510ustar00rootroot00000000000000add_gtest(TestXdgUtilsBaseDir TestBaseDir.cpp) target_link_libraries(TestXdgUtilsBaseDir XdgUtils::BaseDir) xdg-utils-cxx-1.0.1/tests/BaseDir/TestBaseDir.cpp000066400000000000000000000010561352756343000215730ustar00rootroot00000000000000// system #include // library #include // local #include TEST(TestBaseDir, dataHomeFromEnv) { setenv("XDG_DATA_HOME", "/tmp/dataHome", 1); const std::string& dataHome = XdgUtils::BaseDir::XdgDataHome(); ASSERT_EQ(dataHome, "/tmp/dataHome"); } TEST(TestBaseDir, dataHomeFromFallback) { setenv("XDG_DATA_HOME", "", 1); std::string dataHome = XdgUtils::BaseDir::XdgDataHome(); std::string home = XdgUtils::BaseDir::Home(); ASSERT_EQ(dataHome, home + "/.local/share"); } xdg-utils-cxx-1.0.1/tests/CMakeLists.txt000066400000000000000000000003221352756343000201400ustar00rootroot00000000000000if(XDG_UTILS_TESTS) include(fetch_and_build_gtest.cmake) include(utils.cmake) add_subdirectory(DesktopEntry) add_subdirectory(BaseDir) include(setup_coverage_target.cmake) endif() xdg-utils-cxx-1.0.1/tests/DesktopEntry/000077500000000000000000000000001352756343000200365ustar00rootroot00000000000000xdg-utils-cxx-1.0.1/tests/DesktopEntry/AST/000077500000000000000000000000001352756343000204655ustar00rootroot00000000000000xdg-utils-cxx-1.0.1/tests/DesktopEntry/AST/CMakeLists.txt000066400000000000000000000004341352756343000232260ustar00rootroot00000000000000set( srcs TestAST.cpp TestEntry.cpp TestComment.cpp TestGroup.cpp "$" ) add_gtest(TestXdgUtilsDesktopAST "${srcs}") target_include_directories(TestXdgUtilsDesktopAST PRIVATE ${PROJECT_SOURCE_DIR}/src/DesktopEntry/AST) xdg-utils-cxx-1.0.1/tests/DesktopEntry/AST/TestAST.cpp000066400000000000000000000104351352756343000224630ustar00rootroot00000000000000#include #include #include #include #include using namespace XdgUtils::DesktopEntry::AST; TEST(TestAST, create) { AST ast1; auto g1 = new Group("[Desktop Entry]", "Desktop Entry");; std::vector> g1Entries = { std::shared_ptr(new Entry(" Name", "Name", "", "", "=My App", "My App")), std::shared_ptr(new Comment("# Test", " Test")) }; g1->setEntries(g1Entries); std::vector> ast1Entries = { std::shared_ptr(new Comment("# Test", " Test")), std::shared_ptr(g1) }; ast1.setEntries(ast1Entries);; } TEST(TestAST, compare) { AST ast1; auto g1 = new Group("[Desktop Entry]", "Desktop Entry");; std::vector> g1Entries = { std::shared_ptr(new Entry(" Name", "Name", "", "", "=My App", "My App")), std::shared_ptr(new Comment("# Test", " Test")) }; g1->setEntries(g1Entries); std::vector> ast1Entries = { std::shared_ptr(new Comment("# Test", " Test")), std::shared_ptr(g1) }; ast1.setEntries(ast1Entries);; AST ast2; auto g2 = new Group("[Desktop Entry]", "Desktop Entry");; std::vector> g2Entries = { std::shared_ptr(new Entry(" Name", "Name", "", "", "=My App", "My App")), std::shared_ptr(new Comment("# Test", " Test")) }; g2->setEntries(g2Entries); std::vector> ast2Entries = { std::shared_ptr(new Comment("# Test", " Test")), std::shared_ptr(g2) }; ast2.setEntries(ast2Entries);; ASSERT_EQ(ast1, ast2); AST ast3; auto g3 = new Group("[Desktop Entry]", "Desktop Entry");; std::vector> g3Entries = { std::shared_ptr(new Entry(" Name", "Name", "", "", "=My App", "My App")), std::shared_ptr(new Comment("# Test", " Test")) }; g3->setEntries(g3Entries); std::vector> ast3Entries = {std::shared_ptr(g3)}; ast3.setEntries(ast3Entries);; ASSERT_NE(ast1, ast3); } TEST(TestAST, write) { AST ast1; auto g1 = new Group("[Desktop Entry]", "Desktop Entry");; std::vector> g1Entries = { std::shared_ptr(new Entry(" Name", "Name", "", "", "=My App", "My App")), std::shared_ptr(new Comment("# Test", " Test")) }; g1->setEntries(g1Entries); std::vector> ast1Entries = { std::shared_ptr(new Comment("# Test", " Test")), std::shared_ptr(g1) }; ast1.setEntries(ast1Entries);; std::stringstream res; ast1.write(res); ASSERT_EQ(res.str(), "# Test\n" "[Desktop Entry]\n" " Name=My App\n" "# Test"); } TEST(TestAST, copy) { AST ast1; auto g1 = new Group("[Desktop Entry]", "Desktop Entry");; std::vector> g1Entries = { std::shared_ptr(new Entry(" Name", "Name", "", "", "=My App", "My App")), std::shared_ptr(new Comment("# Test", " Test")) }; g1->setEntries(g1Entries); std::vector> ast1Entries = { std::shared_ptr(new Comment("# Test", " Test")), std::shared_ptr(g1) }; ast1.setEntries(ast1Entries);; AST ast2 = ast1; ASSERT_EQ(ast1, ast2); ast1.getEntries().front()->setValue("Desktop Action One"); ASSERT_NE(ast1, ast2); } TEST(TestAST, move) { AST ast1; auto g1 = new Group("[Desktop Entry]", "Desktop Entry");; std::vector> g1Entries = { std::shared_ptr(new Entry(" Name", "Name", "", "", "=My App", "My App")), std::shared_ptr(new Comment("# Test", " Test")) }; g1->setEntries(g1Entries); std::vector> ast1Entries = { std::shared_ptr(new Comment("# Test", " Test")), std::shared_ptr(g1) }; ast1.setEntries(ast1Entries);; std::stringstream stream1; stream1 << ast1; AST ast2 = std::move(ast1); std::stringstream stream2; stream2 << ast2; ASSERT_EQ(stream1.str(), stream2.str()); } xdg-utils-cxx-1.0.1/tests/DesktopEntry/AST/TestComment.cpp000066400000000000000000000021371352756343000234360ustar00rootroot00000000000000#include #include using namespace XdgUtils::DesktopEntry::AST; TEST(TestComment, create) { Comment comment1("# Test", " Test"); ASSERT_EQ(" Test", comment1.getValue()); } TEST(TestComment, setValue) { Comment comment1("# Test", " Test"); comment1.setValue(" More Tests!"); ASSERT_EQ(" More Tests!", comment1.getValue()); std::stringstream out; comment1.write(out); ASSERT_EQ("# More Tests!", out.str()); } TEST(TestComment, setValueToEmpty) { Comment comment1("", ""); comment1.setValue(" More Tests!"); ASSERT_EQ(" More Tests!", comment1.getValue()); std::stringstream out; comment1.write(out); ASSERT_EQ("# More Tests!", out.str()); } TEST(TestComment, compare) { ASSERT_EQ(Comment("", ""), Comment("", "")); ASSERT_EQ(Comment("# ", " Random Rocker"), Comment("# ", " Random Rocker")); ASSERT_EQ(Comment("# ", " Random Rocker"), Comment("#", " Random Rocker")); ASSERT_NE(Comment("# ", " Random Blocker"), Comment("#", " Random Rocker")); ASSERT_NE(Comment("", ""), Comment("#", " Random Rocker")); } xdg-utils-cxx-1.0.1/tests/DesktopEntry/AST/TestEntry.cpp000066400000000000000000000023231352756343000231320ustar00rootroot00000000000000#include #include using namespace XdgUtils::DesktopEntry::AST; TEST(TestEntry, create) { Entry e1(" Name", "Name", "[es] ", "es", "= Mi App", "Mi App"); ASSERT_EQ("Name", e1.getKey()); ASSERT_EQ("es", e1.getLocale()); ASSERT_EQ("Mi App", e1.getValue()); } TEST(TestEntry, write) { Entry e1("Name", "Name", "[es] ", "es", "= Mi App", "Mi App"); std::stringstream s; e1.write(s); ASSERT_EQ("Name[es] = Mi App", s.str()); } TEST(TestEntry, setRegularEntryValue) { Entry e1(" Name", "Name", "[es] ", "es", "= Mi App", "Mi App"); e1.setValue("Mi Grand App"); ASSERT_EQ("Mi Grand App", e1.getValue()); std::stringstream s; e1.write(s); ASSERT_EQ(" Name[es] = Mi Grand App", s.str()); } TEST(TestEntry, setEmptyEntryValue) { Entry e1(" Name", "Name", "[es] ", "es", "= ", ""); e1.setValue("Mi Grand App"); ASSERT_EQ("Mi Grand App", e1.getValue()); std::stringstream s; e1.write(s); ASSERT_EQ(" Name[es] = Mi Grand App", s.str()); } TEST(TestEntry, compare) { Entry e1(" Name", "Name", "[es] ", "es", "= Mi App", "Mi App"); Entry e2(" Name", "Name", "[es]", "es", "=Mi App", "Mi App"); ASSERT_EQ(e1, e2); } xdg-utils-cxx-1.0.1/tests/DesktopEntry/AST/TestGroup.cpp000066400000000000000000000050321352756343000231250ustar00rootroot00000000000000#include #include #include #include using namespace XdgUtils::DesktopEntry::AST; TEST(TestGroup, create) { Group g1("[Desktop Entry]", "Desktop Entry");; ASSERT_EQ(g1.getValue(), "Desktop Entry"); std::vector> entries1; entries1.emplace_back(new Entry(" Name", "Name", "", "", "=My App", "My App")); entries1.emplace_back(new Comment("# Test", " Test")); g1.setEntries(entries1); auto res = g1.getEntries(); for (int i = 0; i < entries1.size(); ++i) ASSERT_EQ(*res[i], *entries1[i]); } TEST(TestGroup, compare) { Group g1("[Desktop Entry]", "Desktop Entry");; std::vector> entries1; entries1.emplace_back(new Entry(" Name", "Name", "", "", "=My App", "My App")); entries1.emplace_back(new Comment("# Test", " Test")); g1.setEntries(entries1); Group g2("[Desktop Entry]", "Desktop Entry");; std::vector> entries2; entries2.emplace_back(new Entry(" Name", "Name", "", "", "=My App", "My App")); entries2.emplace_back(new Comment("# Test", " Test")); g2.setEntries(entries2); ASSERT_EQ(g1, g2); entries1.emplace_back(new Comment("# Test", " Test")); g1.setEntries(entries1); ASSERT_NE(g1, g2); } TEST(TestGroup, write) { Group g1("[Desktop Entry]", "Desktop Entry");; std::vector> entries1; entries1.emplace_back(new Entry(" Name", "Name", "", "", "=My App", "My App")); entries1.emplace_back(new Comment("# Test", " Test")); g1.setEntries(entries1); std::stringstream res; g1.write(res); ASSERT_EQ(res.str(), "[Desktop Entry]\n Name=My App\n# Test"); } TEST(TestGroup, copy) { Group g1("[Desktop Entry]", "Desktop Entry");; std::vector> entries1; entries1.emplace_back(new Entry(" Name", "Name", "", "", "=My App", "My App")); entries1.emplace_back(new Comment("# Test", " Test")); g1.setEntries(entries1); Group g2 = g1; ASSERT_EQ(g1, g2); // Assert each group has a different copy of the entries g1.getEntries().front()->setValue("Your App"); ASSERT_NE(g1, g2); } TEST(TestGroup, move) { Group g1("[Desktop Entry]", "Desktop Entry");; std::vector> entries1; entries1.emplace_back(new Entry(" Name", "Name", "", "", "=My App", "My App")); entries1.emplace_back(new Comment("# Test", " Test")); g1.setEntries(entries1); Group g2 = std::move(g1); ASSERT_NE(g2.getEntries(), entries1); } xdg-utils-cxx-1.0.1/tests/DesktopEntry/CMakeLists.txt000066400000000000000000000005011352756343000225720ustar00rootroot00000000000000add_subdirectory(AST) add_subdirectory(Reader) add_gtest( TestXdgUtilsDesktopEntry TestDesktopEntry.cpp TestDesktopEntryKeyPath.cpp TestDesktopEntryValue.cpp TestDesktopEntryExecValue.cpp TestDesktopEntryStringsValue.cpp ) target_link_libraries(TestXdgUtilsDesktopEntry XdgUtils::DesktopEntry) xdg-utils-cxx-1.0.1/tests/DesktopEntry/Reader/000077500000000000000000000000001352756343000212405ustar00rootroot00000000000000xdg-utils-cxx-1.0.1/tests/DesktopEntry/Reader/CMakeLists.txt000066400000000000000000000005761352756343000240100ustar00rootroot00000000000000set( srcs TestDesktopEntryReaderLexer.cpp TestDesktopEntryReaderTokenizer.cpp TestDesktopEntryReader.cpp "$" "$" ) add_gtest(TestXdgUtilsDesktopEntryReader "${srcs}") target_include_directories(TestXdgUtilsDesktopEntryReader PRIVATE ${PROJECT_SOURCE_DIR}/src/DesktopEntry) xdg-utils-cxx-1.0.1/tests/DesktopEntry/Reader/TestDesktopEntryReader.cpp000066400000000000000000000014361352756343000263660ustar00rootroot00000000000000#include #include #include using namespace XdgUtils::DesktopEntry::Reader; using namespace XdgUtils::DesktopEntry::AST; TEST(TestDesktopEntryReader, readWrite) { std::string data = { "# Test Destkop file\n" "[Desktop Entry]\n" "Name=My App\n" "Name[es]=Mi App\n" }; Reader r; std::stringstream in(data); AST a = r.read(in); std::stringstream res; a.write(res); ASSERT_EQ(res.str(), data); } TEST(TestDesktopEntryReader, readBroken) { std::string data = { "# Test Destkop file\n" "[Desktop Entry]\n" "--Name=My App\n" "Name[es]=Mi App\n" }; Reader r; std::stringstream in(data); ASSERT_THROW(r.read(in), std::runtime_error); } xdg-utils-cxx-1.0.1/tests/DesktopEntry/Reader/TestDesktopEntryReaderLexer.cpp000066400000000000000000000027061352756343000273670ustar00rootroot00000000000000#include #include using namespace XdgUtils::DesktopEntry::Reader; TEST(TestDesktopEntryReaderLexer, consume) { const auto str = "[Desktop Entry]\n"; std::stringstream in(str); Lexer l(in); int i = 0; while (l.consume()) { ASSERT_EQ(l.top(), str[i]); i++; } } TEST(TestDesktopEntryReaderLexer, classify) { const auto str = "#" // Comment begin char " " // white space "\t" // white space "\n" // EOL "[" // Group Open "]" // Group Close "=" // Assignment "R"; // Regular char std::stringstream strStream(str); Lexer l(strStream); l.consume(); ASSERT_TRUE(l.isHash()); ASSERT_FALSE(l.isAlfaNumeric()); l.consume(); ASSERT_TRUE(l.isSpace()); l.consume(); ASSERT_TRUE(l.isSpace()); l.consume(); ASSERT_TRUE(l.isEOL()); l.consume(); ASSERT_TRUE(l.isOpenSquareBracket()); l.consume(); ASSERT_TRUE(l.isCloseSquareBracket()); l.consume(); ASSERT_TRUE(l.isAssignment()); l.consume(); ASSERT_TRUE(l.isAlfaNumeric()); } TEST(TestDesktopEntryReaderLexer, lineCount) { const auto str = "\n\na"; std::stringstream strStream(str); Lexer l(strStream); int i = 0; while (l.consume()) { ASSERT_EQ(l.line(), i); i++; } } xdg-utils-cxx-1.0.1/tests/DesktopEntry/Reader/TestDesktopEntryReaderTokenizer.cpp000066400000000000000000000126211352756343000302570ustar00rootroot00000000000000// system #include // libraries #include // local #include #include using namespace XdgUtils::DesktopEntry::Reader; TEST(TestDesktopEntryReaderTokenizer, tokenizeComments) { const auto input = "# Simple comment"; std::stringstream inputStream(input); Tokenizer t(inputStream); auto tokens = t.consumeAll(); std::vector expectedTokens = {Token("# Simple comment", 0, " Simple comment", COMMENT)}; ASSERT_EQ(tokens, expectedTokens); } TEST(TestDesktopEntryReaderTokenizer, tokenizeBlankLine) { const auto input = " "; std::stringstream inputStream(input); Tokenizer t(inputStream); auto tokens = t.consumeAll(); std::vector expectedTokens = {Token(" ", 0, " ", COMMENT)}; ASSERT_EQ(tokens, expectedTokens); } TEST(TestDesktopEntryReaderTokenizer, tokenizeGroupHeader) { const auto input = " [Desktop Entry] "; std::stringstream inputStream(input); Tokenizer t(inputStream); auto tokens = t.consumeAll(); std::vector expectedTokens = {Token(" [Desktop Entry] ", 0, "Desktop Entry", GROUP_HEADER)}; ASSERT_EQ(tokens, expectedTokens); } TEST(TestDesktopEntryReaderTokenizer, tokenizeBrokenGroupHeader) { const auto input = "[Desktop[ Entry]"; std::stringstream inputStream(input); Tokenizer t(inputStream); auto tokens = t.consumeAll(); std::vector expectedTokens = {Token("[Desktop[ Entry]", 0, "Unexpected char '[' at 8", UNKNOWN)}; ASSERT_EQ(tokens, expectedTokens); } TEST(TestDesktopEntryReaderTokenizer, tokenizeEntry) { const auto input = "Name=Super App"; std::stringstream inputStream(input); Tokenizer t(inputStream); auto tokens = t.consumeAll(); std::vector expectedTokens = {Token("Name", 0, "Name", ENTRY_KEY), Token("=Super App", 0, "Super App", ENTRY_VALUE)}; ASSERT_EQ(tokens, expectedTokens); } TEST(TestDesktopEntryReaderTokenizer, tokenizeLocalizedEntry) { const auto input = "Name[es_ES] = Super App"; std::stringstream inputStream(input); Tokenizer t(inputStream); auto tokens = t.consumeAll(); std::vector expectedTokens = {Token("Name", 0, "Name", ENTRY_KEY), Token("[es_ES] ", 0, "es_ES", ENTRY_LOCALE), Token("= Super App", 0, " Super App", ENTRY_VALUE)}; ASSERT_EQ(tokens, expectedTokens); } TEST(TestDesktopEntryReaderTokenizer, tokenizeCompleteDesktopEntry) { const auto input = "[Desktop Entry]\n" "Version=1.0\n" "Type=Application\n" "Name=Foo Viewer\n" "Comment=The best viewer for Foo objects available!\n" "TryExec=fooview\n" "Exec=fooview %F\n" "Icon=fooview\n" "MimeType=image/x-foo;\n" "Actions=Gallery;Create;\n" "\n" "[Desktop Action Gallery]\n" "Exec=fooview --gallery\n" "Name=Browse Gallery\n" "\n" "[Desktop Action Create]\n" "Exec=fooview --create-new\n" "Name=Create a new Foo!\n" "Icon=fooview-new"; std::stringstream inputStream(input); Tokenizer t(inputStream); auto tokens = t.consumeAll(); std::vector expectedTokens = { Token("[Desktop Entry]", 0, "Desktop Entry", GROUP_HEADER), Token("Version", 1, "Version", ENTRY_KEY), Token("=1.0", 1, "1.0", ENTRY_VALUE), Token("Type", 2, "Type", ENTRY_KEY), Token("=Application", 2, "Application", ENTRY_VALUE), Token("Name", 3, "Name", ENTRY_KEY), Token("=Foo Viewer", 3, "Foo Viewer", ENTRY_VALUE), Token("Comment", 4, "Comment", ENTRY_KEY), Token("=The best viewer for Foo objects available!", 4, "The best viewer for Foo objects available!", ENTRY_VALUE), Token("TryExec", 5, "TryExec", ENTRY_KEY), Token("=fooview", 5, "fooview", ENTRY_VALUE), Token("Exec", 6, "Exec", ENTRY_KEY), Token("=fooview %F", 6, "fooview %F", ENTRY_VALUE), Token("Icon", 7, "Icon", ENTRY_KEY), Token("=fooview", 7, "fooview", ENTRY_VALUE), Token("MimeType", 8, "MimeType", ENTRY_KEY), Token("=image/x-foo;", 8, "image/x-foo;", ENTRY_VALUE), Token("Actions", 9, "Actions", ENTRY_KEY), Token("=Gallery;Create;", 9, "Gallery;Create;", ENTRY_VALUE), Token("", 10, "", COMMENT), Token("[Desktop Action Gallery]", 11, "Desktop Action Gallery", GROUP_HEADER), Token("Exec", 12, "Exec", ENTRY_KEY), Token("=fooview --gallery", 12, "fooview --gallery", ENTRY_VALUE), Token("Name", 13, "Name", ENTRY_KEY), Token("=Browse Gallery", 13, "Browse Gallery", ENTRY_VALUE), Token("", 14, "", COMMENT), Token("[Desktop Action Create]", 15, "Desktop Action Create", GROUP_HEADER), Token("Exec", 16, "Exec", ENTRY_KEY), Token("=fooview --create-new", 16, "fooview --create-new", ENTRY_VALUE), Token("Name", 17, "Name", ENTRY_KEY), Token("=Create a new Foo!", 17, "Create a new Foo!", ENTRY_VALUE), Token("Icon", 18, "Icon", ENTRY_KEY), Token("=fooview-new", 18, "fooview-new", ENTRY_VALUE) }; ASSERT_EQ(tokens, expectedTokens); } xdg-utils-cxx-1.0.1/tests/DesktopEntry/TestDesktopEntry.cpp000066400000000000000000000162421352756343000240420ustar00rootroot00000000000000#include #include using namespace XdgUtils::DesktopEntry; class TestDesktopEntry : public ::testing::Test { protected: const std::string exampleDesktopEntry = "[Desktop Entry]\n" "Version=1.0\n" "Type=Application\n" "Name=Foo Viewer\n" "Comment=The best viewer for Foo objects available!\n" "TryExec=fooview\n" "Exec=fooview %F\n" "Icon=fooview\n" "MimeType=image/x-foo;\n" "Actions=Gallery;Create;\n" "\n" "[Desktop Action Gallery]\n" "Exec=fooview --gallery\n" "Name=Browse Gallery\n" "\n" "[Desktop Action Create]\n" "Exec=fooview --create-new\n" "Name=Create a new Foo!\n" "Icon=fooview-new\n"; }; TEST_F(TestDesktopEntry, create) { DesktopEntry a; auto entryPath = "Desktop Entry/Name"; auto entryValue = "A"; a.set(entryPath, entryValue); // Copy constructor DesktopEntry b = a; ASSERT_EQ(a.get(entryPath), b.get(entryPath)); ASSERT_EQ(a, b); b.set(entryPath, "b"); ASSERT_NE(a, b); } TEST_F(TestDesktopEntry, readWrite) { DesktopEntry entry; std::stringstream in(exampleDesktopEntry); in >> entry; std::stringstream out; out << entry; ASSERT_EQ(in.str(), out.str()); } TEST_F(TestDesktopEntry, paths) { DesktopEntry entry{ "[Desktop Entry]\n" "Version=1.0\n" "Type=Application\n" "Name=Foo Viewer\n" "Comment=The best viewer for Foo objects available!\n" "TryExec=fooview\n" "Exec=fooview %F\n" "Icon=fooview\n" }; std::vector paths = entry.paths(); std::vector expectedPaths = { "Desktop Entry", "Desktop Entry/Comment", "Desktop Entry/Exec", "Desktop Entry/Icon", "Desktop Entry/Name", "Desktop Entry/TryExec", "Desktop Entry/Type", "Desktop Entry/Version", }; ASSERT_EQ(paths, expectedPaths); } TEST_F(TestDesktopEntry, get) { const std::string s = "[Desktop Entry]\n" "Version=1.0\n" "Type=Application\n" "Name=Foo Viewer\n" "Comment=The best viewer for Foo objects available!\n" "TryExec=fooview\n" "Exec=fooview %F\n" "Icon=fooview\n" "MimeType=image/x-foo;\n" "Actions=Gallery;Create;\n" "\n" "[Desktop Action Gallery]\n" "Exec=fooview --gallery\n" "Name=Browse Gallery\n" "\n" "[Desktop Action Create]\n" "Exec=fooview --create-new\n" "Name=Create a new Foo!\n" "Icon=fooview-new\n"; DesktopEntry entry; std::stringstream in(s); in >> entry; ASSERT_EQ(entry.get("Desktop Entry/Name"), "Foo Viewer"); ASSERT_EQ(entry.get("Desktop Action Gallery/Name"), "Browse Gallery"); ASSERT_EQ(entry.get("Desktop Action Create/Name"), "Create a new Foo!"); } TEST_F(TestDesktopEntry, set) { DesktopEntry entry; entry.set("A/B", "C"); std::stringstream out; out << entry; std::string expected = "[A]\nB=C"; ASSERT_EQ(out.str(), expected); } TEST_F(TestDesktopEntry, edit) { DesktopEntry entry; std::stringstream in(exampleDesktopEntry); in >> entry; entry.set("Spam", ""); ASSERT_EQ(entry.get("Spam"), "Spam"); entry.set("Spam/Spam", "Food"); ASSERT_EQ(entry.get("Spam/Spam"), "Food"); entry.set("Spam2/Spam", "Food"); ASSERT_EQ(entry.get("Spam2/Spam"), "Food"); entry.set("Spam2/Spam", ""); ASSERT_EQ(entry.get("Spam2/Spam"), ""); entry.set("Desktop Entry/Name", "New Foo Viewer"); ASSERT_EQ(entry.get("Desktop Entry/Name"), "New Foo Viewer"); entry.set("Desktop Action Gallery/Name", "Browse The Gallery"); ASSERT_EQ(entry.get("Desktop Action Gallery/Name"), "Browse The Gallery"); entry.set("Desktop Action Create/Name", "Create Foo!"); ASSERT_EQ(entry.get("Desktop Action Create/Name"), "Create Foo!"); entry.set("Desktop Action Create/Name[es_ES]", "Crear Foo"); ASSERT_EQ(entry.get("Desktop Action Create/Name[es_ES]"), "Crear Foo"); } TEST_F(TestDesktopEntry, exists) { DesktopEntry entry; std::stringstream in(exampleDesktopEntry); in >> entry; ASSERT_TRUE(entry.exists("Desktop Action Gallery/Name")); ASSERT_FALSE(entry.exists("Desktop Action Gallery/Nam")); } TEST_F(TestDesktopEntry, removeGroup) { DesktopEntry entry; std::string entryStr = {"[G1]\nName=1\n[g2]\nName=2\n"}; std::stringstream in(entryStr); in >> entry; ASSERT_TRUE(entry.exists("G1")); entry.remove("G1"); ASSERT_FALSE(entry.exists("G1")); std::stringstream out; out << entry; ASSERT_EQ(out.str(), "[g2]\nName=2\n"); } TEST_F(TestDesktopEntry, removeEntry) { DesktopEntry entry; std::string entryStr = {"[G1]\nName=1\nIcon=1\n[g2]\nName=2\n"}; std::stringstream in(entryStr); in >> entry; ASSERT_TRUE(entry.exists("G1/Icon")); entry.remove("G1/Icon"); ASSERT_FALSE(entry.exists("G1/Icon")); std::stringstream out; out << entry; ASSERT_EQ(out.str(), "[G1]\nName=1\n[g2]\nName=2\n"); } TEST_F(TestDesktopEntry, arraySubscriptOperatorAccessGroup) { DesktopEntry entry; entry["Group"] = "Group"; std::stringstream out; out << entry; ASSERT_EQ(out.str(), "[Group]\n"); } TEST_F(TestDesktopEntry, arraySubscriptOperatorAccessEntry) { DesktopEntry entry; entry["Group/Key"] = "My App"; ASSERT_EQ(static_cast(entry["Group/Key"]), "My App"); std::stringstream out; out << entry; ASSERT_EQ(out.str(), "[Group]\n" "Key=My App"); } TEST_F(TestDesktopEntry, copy) { DesktopEntry a; a["g/k"] = "v"; // Assert that values are copied DesktopEntry b = a; ASSERT_EQ(static_cast(b["g/k"]), "v"); // Assert that b inner data pointers are different from the a ones b["g/k"] = "v1"; ASSERT_EQ(static_cast(a["g/k"]), "v"); ASSERT_EQ(static_cast(b["g/k"]), "v1"); } TEST_F(TestDesktopEntry, move) { DesktopEntry a; a["g/k"] = "v"; // Assert that values are copied DesktopEntry b = std::move(a); ASSERT_EQ(static_cast(b["g/k"]), "v"); } xdg-utils-cxx-1.0.1/tests/DesktopEntry/TestDesktopEntryExecValue.cpp000066400000000000000000000027601352756343000256440ustar00rootroot00000000000000// libraries #include // local #include using namespace XdgUtils::DesktopEntry; TEST(TestDesktopDesktopEntryExecValue, create) { DesktopEntryExecValue value("fooview %F"); } TEST(TestDesktopDesktopEntryExecValue, size) { ASSERT_EQ(DesktopEntryExecValue("fooview %F").size(), 2); ASSERT_EQ(DesktopEntryExecValue("\"spaced dir/run\" %F").size(), 2); } TEST(TestDesktopDesktopEntryExecValue, access) { DesktopEntryExecValue value("\"spaced dir/run\" %F"); std::vector expected = {"spaced dir/run", "%F"}; ASSERT_EQ(value.size(), expected.size()); for (int i = 0; i < value.size(); ++i) ASSERT_EQ(value[i], expected[i]); } TEST(TestDesktopDesktopEntryExecValue, dump) { const auto str = "\"spaced dir/run\" %F"; DesktopEntryExecValue value(str); ASSERT_EQ(value.dump(), str); } TEST(TestDesktopDesktopEntryExecValue, set) { const auto str = "\"spaced dir/run\" %F"; DesktopEntryExecValue value(str); value[0] = "random/$PWD/bin"; ASSERT_EQ(value.dump(), "\"random/\\$PWD/bin\" %F"); } TEST(TestDesktopDesktopEntryExecValue, append) { DesktopEntryExecValue stringList; stringList.append("1"); ASSERT_EQ(stringList.size(), 1); ASSERT_EQ(stringList[0], "1"); } TEST(TestDesktopDesktopEntryExecValue, remove) { DesktopEntryExecValue stringList("1 2"); stringList.remove(0); ASSERT_EQ(stringList.size(), 1); ASSERT_EQ(stringList[0], "2"); } xdg-utils-cxx-1.0.1/tests/DesktopEntry/TestDesktopEntryKeyPath.cpp000066400000000000000000000033221352756343000253230ustar00rootroot00000000000000#include #include using namespace XdgUtils::DesktopEntry; TEST(TestDesktopEntryKeyPath, create) { DesktopEntryKeyPath path("Desktop Entry"); path = "Desktop Entry/Name"; } TEST(TestDesktopEntryKeyPath, string) { DesktopEntryKeyPath path("a"); path.setGroup("g"); ASSERT_EQ(path, "g"); ASSERT_THROW(path.setGroup(""), MalformedPathError); path.setKey("k"); ASSERT_EQ(path, "g/k"); path.setKey("k_k"); ASSERT_EQ(path, "g/k_k"); path.setKey("k-k"); ASSERT_EQ(path, "g/k-k"); ASSERT_THROW(path.setKey("@!@#$"), MalformedPathError); path.setLocale("l"); ASSERT_EQ(path, "g/k-k[l]"); } TEST(TestDesktopEntryKeyPath, parseGroup) { // Test group parsing ASSERT_EQ(DesktopEntryKeyPath("Desktop Entry"), "Desktop Entry"); ASSERT_THROW(DesktopEntryKeyPath("Invalid Group["), ParseError); ASSERT_THROW(DesktopEntryKeyPath("Invalid Group]"), ParseError); } TEST(TestDesktopEntryKeyPath, parseKey) { ASSERT_EQ(DesktopEntryKeyPath("G/K"), "G/K"); ASSERT_EQ(DesktopEntryKeyPath("G/K-K"), "G/K-K"); ASSERT_EQ(DesktopEntryKeyPath("G/K_K"), "G/K_K"); ASSERT_THROW(DesktopEntryKeyPath("Invalid/Key]"), ParseError); ASSERT_THROW(DesktopEntryKeyPath("Invalid/Key#"), ParseError); ASSERT_THROW(DesktopEntryKeyPath("Invalid/#"), ParseError); } TEST(TestDesktopEntryKeyPath, parseLocale) { ASSERT_EQ(DesktopEntryKeyPath("G/K[l]"), "G/K[l]"); ASSERT_EQ(DesktopEntryKeyPath("G/K[l_C]"), "G/K[l_C]"); ASSERT_EQ(DesktopEntryKeyPath("G/K[l@M]"), "G/K[l@M]"); ASSERT_EQ(DesktopEntryKeyPath("G/K[l_C@M]"), "G/K[l_C@M]"); ASSERT_THROW(DesktopEntryKeyPath("G/K[l_C@M"), ParseError); } xdg-utils-cxx-1.0.1/tests/DesktopEntry/TestDesktopEntryStringsValue.cpp000066400000000000000000000031701352756343000264050ustar00rootroot00000000000000// libraries #include // local #include using namespace XdgUtils::DesktopEntry; TEST(TestDesktopEntryStringList, create) { DesktopEntryStringsValue stringList("one;two;three"); } TEST(TestDesktopEntryStringList, size) { DesktopEntryStringsValue stringList("part one;part two;three\\;;four;"); ASSERT_EQ(stringList.size(), 4); } TEST(TestDesktopEntryStringList, access) { DesktopEntryStringsValue stringList("part one;part two;three\\;;four;;"); ASSERT_EQ(stringList.size(), 5); std::vector expected = {"part one", "part two", "three;", "four", ""}; ASSERT_EQ(stringList.size(), expected.size()); for (int i = 0; i < stringList.size(); ++i) ASSERT_EQ(stringList[i], expected[i]); } TEST(TestDesktopEntryStringList, dump) { auto str = "part one;part two;three\\;;four;"; DesktopEntryStringsValue stringList(str); ASSERT_EQ(stringList.dump(), str); } TEST(TestDesktopEntryStringList, set) { auto str = "part one;part two;three\\;;four;"; DesktopEntryStringsValue stringList(str); stringList[0] = "1"; stringList[1] = "2"; stringList[2] = "3"; stringList[3] = "4"; ASSERT_EQ(stringList.dump(), "1;2;3;4;"); } TEST(TestDesktopEntryStringList, append) { DesktopEntryStringsValue stringList; stringList.append("1"); ASSERT_EQ(stringList.size(), 1); ASSERT_EQ(stringList[0], "1"); } TEST(TestDesktopEntryStringList, remove) { DesktopEntryStringsValue stringList("1;2"); stringList.remove(0); ASSERT_EQ(stringList.size(), 1); ASSERT_EQ(stringList[0], "2"); } xdg-utils-cxx-1.0.1/tests/DesktopEntry/TestDesktopEntryValue.cpp000066400000000000000000000074621352756343000250430ustar00rootroot00000000000000// libraries #include // local #include #include "DesktopEntryKeyValuePriv.h" #include using namespace XdgUtils::DesktopEntry; class FakeDesktopEntryKeyValue : public DesktopEntryKeyValue { public: FakeDesktopEntryKeyValue(const DesktopEntryKeyPath& path, const std::shared_ptr& node) : DesktopEntryKeyValue(new DesktopEntryKeyValue::Priv(path, node)) { } }; TEST(TestDesktopEntryKeyValue, cstringCasting) { DesktopEntryKeyPath path("Group/Entry"); auto astNode = std::make_shared("Entry", "", "value"); FakeDesktopEntryKeyValue valueImpl(path, astNode); DesktopEntryKeyValue& value = valueImpl; ASSERT_STREQ(static_cast(value), "value"); value = "rock"; ASSERT_STREQ(static_cast(value), "rock"); } TEST(TestDesktopEntryKeyValue, stringCasting) { DesktopEntryKeyPath path("Group/Entry"); auto astNode = std::make_shared("Entry", "", "value"); FakeDesktopEntryKeyValue valueImpl(path, astNode); DesktopEntryKeyValue& value = valueImpl; ASSERT_EQ(static_cast(value), "value"); value = std::string("rock"); ASSERT_EQ(static_cast(value), "rock"); } TEST(TestDesktopEntryKeyValue, boolCasting) { DesktopEntryKeyPath path("Group/Entry"); auto astNode = std::make_shared("Entry", "", "true"); FakeDesktopEntryKeyValue valueImpl(path, astNode); DesktopEntryKeyValue& value = valueImpl; ASSERT_TRUE(static_cast(value)); value = false; ASSERT_FALSE(static_cast(value)); value = true; ASSERT_TRUE(static_cast(value)); value = std::string(" True "); ASSERT_TRUE(static_cast(value)); value = std::string(" TRUE"); ASSERT_TRUE(static_cast(value)); value = std::string("false"); ASSERT_FALSE(static_cast(value)); value = std::string("FaLsE "); ASSERT_FALSE(static_cast(value)); value = std::string(" fAlse"); ASSERT_FALSE(static_cast(value)); } TEST(TestDesktopEntryKeyValue, badBoolCasting) { DesktopEntryKeyPath path("Group/Entry"); auto astNode = std::make_shared("Entry", "", "not a bool"); FakeDesktopEntryKeyValue valueImpl(path, astNode); DesktopEntryKeyValue& value = valueImpl; ASSERT_THROW(static_cast(value), XdgUtils::DesktopEntry::BadCast); } TEST(TestDesktopEntryKeyValue, intCasting) { FakeDesktopEntryKeyValue valueImpl(DesktopEntryKeyPath("Group/Entry"), std::make_shared("Entry", "", "123")); DesktopEntryKeyValue& value = valueImpl; ASSERT_EQ(static_cast(value), 123); value = 44; ASSERT_EQ(static_cast(value), 44); } TEST(TestDesktopEntryKeyValue, doubleCasting) { FakeDesktopEntryKeyValue valueImpl(DesktopEntryKeyPath("Group/Entry"), std::make_shared("Entry", "", "1.2")); DesktopEntryKeyValue& value = valueImpl; ASSERT_EQ(static_cast(value), 1.2); value = 3.2; ASSERT_EQ(static_cast(value), 3.2); } TEST(TestDesktopEntryKeyValue, copy) { FakeDesktopEntryKeyValue valueImpl1(DesktopEntryKeyPath("Group/Entry"), std::make_shared("Entry", "", "1.2")); DesktopEntryKeyValue& value1 = valueImpl1; FakeDesktopEntryKeyValue valueImpl2(DesktopEntryKeyPath("Group/Entry"), std::make_shared("Entry", "", "1.4")); DesktopEntryKeyValue& value2 = valueImpl2; ASSERT_EQ(static_cast(value1), "1.2"); ASSERT_EQ(static_cast(value2), "1.4"); value1 = value2; ASSERT_EQ(static_cast(value1), "1.4"); } xdg-utils-cxx-1.0.1/tests/fetch_and_build_gtest.cmake000066400000000000000000000025721352756343000227130ustar00rootroot00000000000000find_package(GTest) # Fetch and build GoogleTest if not present in the system if(NOT TARGET GTest::GTest) include(ExternalProject) externalproject_add( googletest GIT_REPOSITORY https://github.com/google/googletest.git GIT_TAG master GIT_SHALLOW On SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/googletest-src" BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/googletest-build" INSTALL_COMMAND "" TEST_COMMAND "" ) externalproject_get_property(googletest SOURCE_DIR) externalproject_get_property(googletest BINARY_DIR) # Workaround fro https://cmake.org/Bug/view.php?id=15052 file(MAKE_DIRECTORY "${SOURCE_DIR}/googletest/include/") add_library(GTest::GTest UNKNOWN IMPORTED) add_library(GTest::Main UNKNOWN IMPORTED) set_target_properties( GTest::GTest PROPERTIES IMPORTED_LOCATION ${BINARY_DIR}/lib/libgtest.a INTERFACE_INCLUDE_DIRECTORIES "${SOURCE_DIR}/googletest/include/" INTERFACE_LINK_LIBRARIES pthread ) set_target_properties( GTest::Main PROPERTIES IMPORTED_LOCATION ${BINARY_DIR}/lib/libgtest_main.a INTERFACE_INCLUDE_DIRECTORIES "${SOURCE_DIR}/googletest/include/" INTERFACE_LINK_LIBRARIES pthread ) add_dependencies(GTest::GTest googletest) add_dependencies(GTest::Main googletest) endif() include(GoogleTest) xdg-utils-cxx-1.0.1/tests/setup_coverage_target.cmake000066400000000000000000000004671352756343000227750ustar00rootroot00000000000000if(XDG_UTILS_CODE_COVERAGE) set(COVERAGE_LCOV_EXCLUDES '${PROJECT_SOURCE_DIR}/tests/*' '${PROJECT_SOURCE_DIR}/*build*' '/usr/*') SETUP_TARGET_FOR_COVERAGE_GCOVR_XML( NAME ctest_coverage EXECUTABLE ctest -j ${PROCESSOR_COUNT} DEPENDENCIES "${PROJECT_TESTS_TARGETS}" ) endif() xdg-utils-cxx-1.0.1/tests/utils.cmake000066400000000000000000000007171352756343000175520ustar00rootroot00000000000000SET(PROJECT_TESTS_TARGETS "" CACHE INTERNAL "Available tests targets") function(add_gtest name srcs) add_executable(${name} ${srcs}) target_link_libraries(${name} GTest::GTest GTest::Main) if(${CMAKE_VERSION} VERSION_LESS "3.10.0") add_test(${name} ${name}) else() gtest_discover_tests(${name}) endif() SET(PROJECT_TESTS_TARGETS ${PROJECT_TESTS_TARGETS} "${name}" CACHE INTERNAL "Available tests targets") endfunction()