├── docs ├── static │ ├── css │ │ ├── custom.css │ │ └── common.css │ ├── dark-light │ │ ├── moon.png │ │ ├── sun.png │ │ ├── unchecked.svg │ │ ├── checked.svg │ │ ├── moon.svg │ │ ├── sun.svg │ │ ├── light.css │ │ ├── dark.css │ │ ├── common-dark-light.css │ │ └── dark-mode-toggle.mjs │ └── images │ │ ├── logo_tm.png │ │ ├── logo_tm_full.png │ │ ├── logo.drawio │ │ └── logo.svg ├── api-reference │ ├── lwshell.rst │ ├── index.rst │ └── lwshell_opt.rst ├── changelog │ └── index.rst ├── user-manual │ ├── index.rst │ └── how-it-works.rst ├── authors │ └── index.rst ├── requirements.txt ├── Makefile ├── make.bat ├── examples │ └── index.rst ├── index.rst ├── conf.py └── get-started │ └── index.rst ├── .github ├── FUNDING.yml └── workflows │ ├── build-and-test.yml │ └── release.yml ├── lwshell ├── CMakeLists.txt ├── src │ ├── include │ │ └── lwshell │ │ │ ├── lwshell_opts_template.h │ │ │ ├── lwshell_opt.h │ │ │ └── lwshell.h │ └── lwshell │ │ └── lwshell.c └── library.cmake ├── .vscode ├── extensions.json ├── settings.json ├── c_cpp_properties.json ├── launch.json └── tasks.json ├── TODO.md ├── AUTHORS ├── cmake ├── i686-w64-mingw32-gcc.cmake └── x86_64-w64-mingw32-gcc.cmake ├── .readthedocs.yaml ├── .gitattributes ├── .clang-tidy ├── CHANGELOG.md ├── examples └── example_minimal.c ├── library.json ├── CMakeLists.txt ├── LICENSE ├── README.md ├── CMakePresets.json ├── dev ├── lwshell_opts.h └── main.c ├── tests └── test.c ├── .clang-format └── .gitignore /docs/static/css/custom.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | custom: ['paypal.me/tilz0R'] 4 | -------------------------------------------------------------------------------- /docs/api-reference/lwshell.rst: -------------------------------------------------------------------------------- 1 | .. _api_lwshell: 2 | 3 | LwSHELL 4 | ======= 5 | 6 | .. doxygengroup:: LWSHELL -------------------------------------------------------------------------------- /docs/static/dark-light/moon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaJerle/lwshell/HEAD/docs/static/dark-light/moon.png -------------------------------------------------------------------------------- /docs/static/dark-light/sun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaJerle/lwshell/HEAD/docs/static/dark-light/sun.png -------------------------------------------------------------------------------- /docs/static/images/logo_tm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaJerle/lwshell/HEAD/docs/static/images/logo_tm.png -------------------------------------------------------------------------------- /lwshell/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22) 2 | 3 | include(${CMAKE_CURRENT_LIST_DIR}/library.cmake) -------------------------------------------------------------------------------- /docs/static/images/logo_tm_full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaJerle/lwshell/HEAD/docs/static/images/logo_tm_full.png -------------------------------------------------------------------------------- /docs/changelog/index.rst: -------------------------------------------------------------------------------- 1 | .. _changelof: 2 | 3 | Changelog 4 | ========= 5 | 6 | .. literalinclude:: ../../CHANGELOG.md 7 | -------------------------------------------------------------------------------- /docs/user-manual/index.rst: -------------------------------------------------------------------------------- 1 | .. _um: 2 | 3 | User manual 4 | =========== 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | 9 | how-it-works -------------------------------------------------------------------------------- /docs/authors/index.rst: -------------------------------------------------------------------------------- 1 | .. _authors: 2 | 3 | Authors 4 | ======= 5 | 6 | List of authors and contributors to the library 7 | 8 | .. literalinclude:: ../../AUTHORS -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ms-vscode.cpptools", 4 | "ms-vscode.cmake-tools", 5 | "twxs.cmake", 6 | ] 7 | } -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | - Add option to decide for delimiter string (`\r\n`) 4 | - Improve helper functions for number parsing (do not use math.h to reduce memory footprint) -------------------------------------------------------------------------------- /docs/api-reference/index.rst: -------------------------------------------------------------------------------- 1 | .. _api_reference: 2 | 3 | API reference 4 | ============= 5 | 6 | List of all the modules: 7 | 8 | .. toctree:: 9 | :maxdepth: 2 10 | 11 | lwshell 12 | lwshell_opt -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx>=3.5.1 2 | breathe>=4.9.1 3 | urllib3==1.26.15 4 | docutils==0.16 5 | colorama 6 | sphinx_rtd_theme>=1.0.0 7 | sphinx-tabs 8 | sphinxcontrib-svg2pdfconverter 9 | sphinx-sitemap 10 | -------------------------------------------------------------------------------- /docs/static/dark-light/unchecked.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Tilen Majerle 2 | Hagai Gold 3 | Tilen Majerle 4 | Brandon 5 | ballen7 6 | Ballen7 <33672651+Ballen7@users.noreply.github.com> 7 | jnz86 -------------------------------------------------------------------------------- /cmake/i686-w64-mingw32-gcc.cmake: -------------------------------------------------------------------------------- 1 | set(CMAKE_SYSTEM_NAME Windows) 2 | 3 | # Some default GCC settings 4 | set(CMAKE_C_COMPILER i686-w64-mingw32-gcc) 5 | set(CMAKE_CXX_COMPILER i686-w64-mingw32-g++) 6 | 7 | set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) 8 | -------------------------------------------------------------------------------- /docs/static/dark-light/checked.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cmake/x86_64-w64-mingw32-gcc.cmake: -------------------------------------------------------------------------------- 1 | set(CMAKE_SYSTEM_NAME Windows) 2 | 3 | # Some default GCC settings 4 | set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc) 5 | set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++) 6 | 7 | set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) 8 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | build: 3 | os: ubuntu-22.04 4 | tools: 5 | python: "3.11" 6 | 7 | # Build documentation in the docs/ directory with Sphinx 8 | sphinx: 9 | configuration: docs/conf.py 10 | 11 | # Python configuration 12 | python: 13 | install: 14 | - requirements: docs/requirements.txt 15 | 16 | formats: 17 | - pdf 18 | - epub 19 | -------------------------------------------------------------------------------- /docs/static/dark-light/moon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | moon 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "lwevt_types.h": "c", 4 | "lwevt_type.h": "c", 5 | "lwevt.h": "c", 6 | "string.h": "c", 7 | "lwevt_opt.h": "c", 8 | "lwshell.h": "c", 9 | "lwshell_opt.h": "c", 10 | "lwshell_opts.h": "c", 11 | "typeinfo": "c" 12 | }, 13 | "esbonio.sphinx.confDir": "" 14 | } -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 4, 3 | "configurations": [ 4 | { 5 | /* 6 | * Full configuration is provided by CMake plugin for vscode, 7 | * that shall be installed by user 8 | */ 9 | "name": "Win32", 10 | "intelliSenseMode": "${default}", 11 | "configurationProvider": "ms-vscode.cmake-tools" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /docs/api-reference/lwshell_opt.rst: -------------------------------------------------------------------------------- 1 | .. _api_lwshell_opt: 2 | 3 | Configuration 4 | ============= 5 | 6 | This is the default configuration of the middleware. 7 | When any of the settings shall be modified, it shall be done in dedicated application config ``lwshell_opts.h`` file. 8 | 9 | .. note:: 10 | Check :ref:`getting_started` for guidelines on how to create and use configuration file. 11 | 12 | .. doxygengroup:: LWSHELL_OPT 13 | :inner: -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | /* GDB must in be in the PATH environment */ 6 | "name": "(Windows) Launch", 7 | "type": "cppdbg", 8 | "request": "launch", 9 | "program": "${command:cmake.launchTargetPath}", 10 | "args": [], 11 | "stopAtEntry": false, 12 | "cwd": "${fileDirname}", 13 | "environment": [] 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /docs/static/dark-light/sun.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: "*, 3 | -abseil-*, 4 | -altera-*, 5 | -android-*, 6 | -fuchsia-*, 7 | -google-*, 8 | -llvm*, 9 | -modernize-use-trailing-return-type, 10 | -zircon-*, 11 | -readability-else-after-return, 12 | -readability-static-accessed-through-instance, 13 | -readability-avoid-const-params-in-decls, 14 | -cppcoreguidelines-non-private-member-variables-in-classes, 15 | -misc-non-private-member-variables-in-classes, 16 | " 17 | WarningsAsErrors: '' 18 | HeaderFilterRegex: '' 19 | FormatStyle: none -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/static/images/logo.drawio: -------------------------------------------------------------------------------- 1 | jZJNb4MwDIZ/DcdKQGBqr+26VhrrhUPPEXFJtISgNCx0v35hOHyomrRT4scfsV8nIgfVnwxt+YdmIKM0Zn1EXqM0TeI088dAHiPJt2QEtREMg2ZQim8ImUg7weC+CrRaSyvaNax000BlV4wao9067Kbl+tWW1vAEyorKZ3oVzPKRbvN45mcQNQ8vJzF6FA3BCO6cMu0WiBwjcjBa2/Gm+gPIQbygy5j39od3asxAY/+TcNmwDHbKFRfV7bPd+01ekw1W+aKyw4ELV56PRYE920cQwuiuYTDUSiKyd1xYKFtaDV7nV+8Zt0qi+26N/pwEe/HkphuL2yWDPakRe+N5lNAXGAv9AuFoJ9AKrHn4EPSSLB9T8J+lwXbz1pItMr7YWNgkxY9ST6VnLf0F5QzmvLZf3+Lzk+MP -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Develop 4 | 5 | - Rework library CMake with removed INTERFACE type 6 | - Fix the platformio library package description 7 | 8 | ## 1.2.0 9 | 10 | - Change license year to 2022 11 | - Update code style with astyle 12 | - Add `.clang-format` draft 13 | - Add option for statically allocated commands array (improvement for small devices w/ little memory) 14 | - Add option to disable dynamic commands allocation (default value) 15 | 16 | ## v1.1.1 17 | 18 | - Split CMakeLists.txt files between library and executable 19 | - Fix wrongly interpreted backspace character 20 | 21 | ## v1.1.0 22 | 23 | - Add support for `listcmd` to print all registered commands 24 | - Optimize code and remove unnecessary brackets 25 | 26 | ## v1.0.0 27 | 28 | - First stable release 29 | - Fix wrong parsing of command names 30 | 31 | ## v0.1.0 32 | 33 | - First release 34 | -------------------------------------------------------------------------------- /examples/example_minimal.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "lwshell/lwshell.h" 3 | 4 | /* Command to get called */ 5 | int32_t 6 | mycmd_fn(int32_t argc, char** argv) { 7 | printf("mycmd_fn called. Number of argv: %d\r\n", (int)argc); 8 | for (int32_t i = 0; i < argc; ++i) { 9 | printf("ARG[%d]: %s\r\n", (int)i, argv[i]); 10 | } 11 | 12 | /* Successful execution */ 13 | return 0; 14 | } 15 | 16 | /* Example code */ 17 | void 18 | example_minimal(void) { 19 | const char* input_str = "mycmd param1 \"param 2 with space\"\r\n"; 20 | 21 | /* Init library */ 22 | lwshell_init(); 23 | 24 | /* Define shell commands */ 25 | lwshell_register_cmd("mycmd", mycmd_fn, "Adds 2 integer numbers and prints them"); 26 | 27 | /* User input to process every character */ 28 | 29 | /* Now insert input */ 30 | lwshell_input(input_str, strlen(input_str)); 31 | } 32 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /.github/workflows/build-and-test.yml: -------------------------------------------------------------------------------- 1 | name: Windows CMake Build & Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - develop 7 | pull_request: 8 | branches: 9 | - develop 10 | 11 | jobs: 12 | build: 13 | runs-on: windows-latest 14 | 15 | steps: 16 | - name: Checkout Repository 17 | uses: actions/checkout@v4 18 | 19 | - name: Install MinGW 20 | run: | 21 | choco install mingw --version=12.2.0 -y 22 | echo "C:\ProgramData\chocolatey\lib\mingw\tools\install\mingw64\bin" >> $GITHUB_PATH 23 | gcc --version 24 | 25 | - name: Build 26 | run: | 27 | mkdir __build__ 28 | cd __build__ 29 | cmake -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=gcc -S.. -G Ninja 30 | cmake --build . 31 | 32 | - name: Run Tests 33 | working-directory: __build__ 34 | run: | 35 | ctest . --output-on-failure 36 | -------------------------------------------------------------------------------- /docs/static/dark-light/light.css: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2019 Google LLC 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * https://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | :root { 18 | color-scheme: light; /* stylelint-disable-line property-no-unknown */ 19 | 20 | --background-color: rgb(240 240 240); 21 | --text-color: rgb(15 15 15); 22 | --shadow-color: rgb(15 15 15 / 50%); 23 | --accent-color: rgb(240 0 0 / 50%); 24 | } 25 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "LwSHELL", 3 | "version": "1.2.0", 4 | "description": "Lightweight shell command line manager", 5 | "keywords": "lwshell, cmd, command line, shell, manager, uart, usb, cdc, command, commands", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/MaJerle/lwshell.git" 9 | }, 10 | "authors": [ 11 | { 12 | "name": "Tilen Majerle", 13 | "email": "tilen@majerle.eu", 14 | "url": "https://majerle.eu" 15 | } 16 | ], 17 | "license": "MIT", 18 | "homepage": "https://github.com/MaJerle/lwshell", 19 | "dependencies": { 20 | 21 | }, 22 | "frameworks": "*", 23 | "platforms": "*", 24 | "export": { 25 | "exclude": [ 26 | ".github", 27 | "dev", 28 | "docs", 29 | "**/.vs", 30 | "**/Debug", 31 | "build", 32 | "**/build" 33 | ] 34 | }, 35 | "build": { 36 | "includeDir": "lwshell/src/include", 37 | "srcDir": ".", 38 | "srcFilter": "+" 39 | } 40 | } -------------------------------------------------------------------------------- /docs/static/dark-light/dark.css: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2019 Google LLC 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * https://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | :root { 18 | color-scheme: dark; /* stylelint-disable-line property-no-unknown */ 19 | 20 | --background-color: rgb(15 15 15); 21 | --text-color: rgb(240 240 240); 22 | --shadow-color: rgb(240 240 240 / 50%); 23 | --accent-color: rgb(0 0 240 / 50%); 24 | } 25 | 26 | img { 27 | filter: grayscale(50%); 28 | } 29 | 30 | .icon { 31 | filter: invert(100%); 32 | } 33 | 34 | a { 35 | color: yellow; 36 | } 37 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22) 2 | 3 | # Setup project 4 | project(LwLibPROJECT) 5 | 6 | if(NOT PROJECT_IS_TOP_LEVEL) 7 | add_subdirectory(lwshell) 8 | else() 9 | enable_testing() 10 | add_executable(${PROJECT_NAME}) 11 | target_sources(${PROJECT_NAME} PRIVATE 12 | ${CMAKE_CURRENT_LIST_DIR}/dev/main.c 13 | ${CMAKE_CURRENT_LIST_DIR}/tests/test.c 14 | ) 15 | target_include_directories(${PROJECT_NAME} PUBLIC 16 | ${CMAKE_CURRENT_LIST_DIR}/dev 17 | ) 18 | 19 | # Add subdir with lwshell and link to project 20 | set(LWSHELL_OPTS_FILE ${CMAKE_CURRENT_LIST_DIR}/dev/lwshell_opts.h) 21 | add_subdirectory(lwshell) 22 | target_link_libraries(${PROJECT_NAME} lwshell) 23 | 24 | # Add compile options to the library, which will propagate options to executable through public link 25 | target_compile_definitions(lwshell PUBLIC WIN32 _DEBUG CONSOLE LWSHELL_DEV) 26 | target_compile_options(lwshell PUBLIC -Wall -Wextra -Wpedantic) 27 | 28 | # Add test 29 | add_test(NAME Test COMMAND $) 30 | endif() 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Tilen MAJERLE 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lightweight shell 2 | 3 | LwSHELL is lightweight, platform independent, command line shell for embedded systems. 4 | It targets communication with embedded systems from remote terminal to quickly send commands and xto retrieve data from the device. 5 | 6 |

Read first: Documentation

7 | 8 | ## Features 9 | 10 | * Lightweight commands shell for embedded systems 11 | * Platform independent and very easy to port 12 | * Development of library under Win32 platform 13 | * Written in C language (C99) 14 | * No dynamic allocation, maximum number of commands assigned at compile time 15 | * Highly configurable 16 | * Simple help-text with `cmd -h` option 17 | * User friendly MIT license 18 | 19 | ## Contribute 20 | 21 | Fresh contributions are always welcome. Simple instructions to proceed: 22 | 23 | 1. Fork Github repository 24 | 2. Follow [C style & coding rules](https://github.com/MaJerle/c-code-style) already used in the project 25 | 3. Create a pull request to develop branch with new features or bug fixes 26 | 27 | Alternatively you may: 28 | 29 | 1. Report a bug 30 | 2. Ask for a feature request 31 | -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "configurePresets": [ 4 | { 5 | "name": "default", 6 | "hidden": true, 7 | "generator": "Ninja", 8 | "binaryDir": "${sourceDir}/build/${presetName}", 9 | "cacheVariables": { 10 | "CMAKE_EXPORT_COMPILE_COMMANDS": "ON" 11 | } 12 | }, 13 | { 14 | "name": "Win32-Debug", 15 | "inherits": "default", 16 | "toolchainFile": "${sourceDir}/cmake/i686-w64-mingw32-gcc.cmake", 17 | "cacheVariables": { 18 | "CMAKE_BUILD_TYPE": "Debug" 19 | } 20 | }, 21 | { 22 | "name": "Win64-Debug", 23 | "inherits": "default", 24 | "toolchainFile": "${sourceDir}/cmake/x86_64-w64-mingw32-gcc.cmake", 25 | "cacheVariables": { 26 | "CMAKE_BUILD_TYPE": "Debug" 27 | } 28 | } 29 | ], 30 | "buildPresets": [ 31 | { 32 | "name": "Win32-Debug", 33 | "configurePreset": "Win32-Debug" 34 | }, 35 | { 36 | "name": "Win64-Debug", 37 | "configurePreset": "Win64-Debug" 38 | } 39 | ] 40 | } -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release workflow 2 | 3 | on: 4 | push: 5 | # Sequence of patterns matched against refs/tags 6 | tags: 7 | - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 8 | 9 | jobs: 10 | # Create the release from the tag 11 | create-release: 12 | name: Create Release 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v2 17 | - name: Create Release 18 | id: create_release 19 | uses: actions/create-release@v1 20 | env: 21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 22 | with: 23 | tag_name: ${{ github.ref }} 24 | release_name: Release ${{ github.ref }} 25 | body: | 26 | See the [CHANGELOG](CHANGELOG.md) 27 | draft: false 28 | prerelease: false 29 | 30 | # Publish package to PlatformIO 31 | publish-platformio: 32 | runs-on: ubuntu-latest 33 | steps: 34 | - name: Checkout code 35 | uses: actions/checkout@v4 36 | 37 | - name: Set up Python 38 | uses: actions/setup-python@v5 39 | with: 40 | python-version: "3.x" 41 | 42 | - name: Install PlatformIO 43 | run: pip install platformio 44 | 45 | - name: Publish to PlatformIO 46 | env: 47 | PLATFORMIO_AUTH_TOKEN: ${{ secrets.PLATFORMIO_AUTH_TOKEN }} 48 | run: pio pkg publish --type library --non-interactive 49 | -------------------------------------------------------------------------------- /docs/static/css/common.css: -------------------------------------------------------------------------------- 1 | /* Center aligned text */ 2 | .center { 3 | text-align: center; 4 | } 5 | 6 | /* Paragraph with main links on index page */ 7 | .index-links { 8 | text-align: center; 9 | margin-top: 10px; 10 | } 11 | .index-links a { 12 | display: inline-block; 13 | border: 1px solid #0E4263; 14 | padding: 5px 20px; 15 | margin: 2px 5px; 16 | background: #2980B9; 17 | border-radius: 4px; 18 | color: #FFFFFF; 19 | } 20 | .index-links a:hover, .index-links a:active { 21 | background: #0E4263; 22 | } 23 | 24 | /* Table header p w/0 margin */ 25 | .index-links a table thead th { 26 | vertical-align: middle; 27 | } 28 | 29 | table thead th p { 30 | margin: 0; 31 | } 32 | 33 | .table-nowrap td { 34 | white-space: normal !important; 35 | } 36 | 37 | /* Breathe output changes */ 38 | .breathe-sectiondef.container { 39 | background: #f9f9f9; 40 | padding: 10px; 41 | margin-bottom: 10px; 42 | border: 1px solid #efefef; 43 | } 44 | .breathe-sectiondef.container .breathe-sectiondef-title { 45 | background: #2980b9; 46 | color: #FFFFFF; 47 | padding: 4px; 48 | margin: -10px -10px 0 -10px; 49 | } 50 | .breathe-sectiondef.container .function, 51 | .breathe-sectiondef.container .member, 52 | .breathe-sectiondef.container .class, 53 | .breathe-sectiondef.container .type { 54 | border-bottom: 1px solid #efefef; 55 | } 56 | .breathe-sectiondef.container .function:last-child, 57 | .breathe-sectiondef.container .member:last-child, 58 | .breathe-sectiondef.container .class:last-child, 59 | .breathe-sectiondef.container .type:last-child { 60 | border-bottom: none; 61 | margin-bottom: 0; 62 | } 63 | 64 | /*# sourceMappingURL=common.css.map */ 65 | -------------------------------------------------------------------------------- /docs/examples/index.rst: -------------------------------------------------------------------------------- 1 | .. _examples: 2 | 3 | Examples and demos 4 | ================== 5 | 6 | Various examples are provided for fast library evaluation on embedded systems. These are prepared and maintained for ``2`` platforms, but could be easily extended to more platforms: 7 | 8 | * WIN32 examples, prepared as `Visual Studio Community `_ projects 9 | * ARM Cortex-M examples for STM32, prepared as `STM32CubeIDE `_ GCC projects 10 | 11 | .. warning:: 12 | Library is platform independent and can be used on any platform. 13 | 14 | Example architectures 15 | ^^^^^^^^^^^^^^^^^^^^^ 16 | 17 | There are many platforms available today on a market, however supporting them all would be tough task for single person. 18 | Therefore it has been decided to support (for purpose of examples) ``2`` platforms only, `WIN32` and `STM32`. 19 | 20 | WIN32 21 | ***** 22 | 23 | Examples for *WIN32* are prepared as `Visual Studio Community `_ projects. 24 | You can directly open project in the IDE, compile & debug. 25 | 26 | STM32 27 | ***** 28 | 29 | Embedded market is supported by many vendors and STMicroelectronics is, with their `STM32 `_ series of microcontrollers, one of the most important players. 30 | There are numerous amount of examples and topics related to this architecture. 31 | 32 | Examples for *STM32* are natively supported with `STM32CubeIDE `_, an official development IDE from STMicroelectronics. 33 | 34 | You can run examples on one of official development boards, available in repository examples. 35 | 36 | .. toctree:: 37 | :maxdepth: 2 38 | -------------------------------------------------------------------------------- /lwshell/src/include/lwshell/lwshell_opts_template.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file lwshell_opts_template.h 3 | * \brief Template config file 4 | */ 5 | 6 | /* 7 | * Copyright (c) 2024 Tilen MAJERLE 8 | * 9 | * Permission is hereby granted, free of charge, to any person 10 | * obtaining a copy of this software and associated documentation 11 | * files (the "Software"), to deal in the Software without restriction, 12 | * including without limitation the rights to use, copy, modify, merge, 13 | * publish, distribute, sublicense, and/or sell copies of the Software, 14 | * and to permit persons to whom the Software is furnished to do so, 15 | * subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be 18 | * included in all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 23 | * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | * OTHER DEALINGS IN THE SOFTWARE. 28 | * 29 | * This file is part of LwSHELL - Lightweight shell library. 30 | * 31 | * Author: Tilen MAJERLE 32 | * Version: v1.2.0 33 | */ 34 | #ifndef LWSHELL_OPTS_HDR_H 35 | #define LWSHELL_OPTS_HDR_H 36 | 37 | /* Rename this file to "lwshell_opts.h" for your application */ 38 | 39 | /* 40 | * Open "include/lwshell/lwshell_opt.h" and 41 | * copy & replace here settings you want to change values 42 | */ 43 | 44 | #endif /* LWSHELL_OPTS_HDR_H */ 45 | -------------------------------------------------------------------------------- /lwshell/library.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # LIB_PREFIX: LWSHELL 3 | # 4 | # This file provides set of variables for end user 5 | # and also generates one (or more) libraries, that can be added to the project using target_link_libraries(...) 6 | # 7 | # Before this file is included to the root CMakeLists file (using include() function), user can set some variables: 8 | # 9 | # LWSHELL_OPTS_FILE: If defined, it is the path to the user options file. If not defined, one will be generated for you automatically 10 | # LWSHELL_COMPILE_OPTIONS: If defined, it provide compiler options for generated library. 11 | # LWSHELL_COMPILE_DEFINITIONS: If defined, it provides "-D" definitions to the library build 12 | # 13 | 14 | # Custom include directory 15 | set(LWSHELL_CUSTOM_INC_DIR ${CMAKE_CURRENT_BINARY_DIR}/lib_inc) 16 | 17 | # Library core sources 18 | set(lwshell_core_SRCS 19 | ${CMAKE_CURRENT_LIST_DIR}/src/lwshell/lwshell.c 20 | ) 21 | 22 | # Setup include directories 23 | set(lwshell_include_DIRS 24 | ${CMAKE_CURRENT_LIST_DIR}/src/include 25 | ${LWSHELL_CUSTOM_INC_DIR} 26 | ) 27 | 28 | # Register library to the system 29 | add_library(lwshell) 30 | target_sources(lwshell PRIVATE ${lwshell_core_SRCS}) 31 | target_include_directories(lwshell PUBLIC ${lwshell_include_DIRS}) 32 | target_compile_options(lwshell PRIVATE ${LWSHELL_COMPILE_OPTIONS}) 33 | target_compile_definitions(lwshell PRIVATE ${LWSHELL_COMPILE_DEFINITIONS}) 34 | 35 | # Create config file if user didn't provide one info himself 36 | if(NOT LWSHELL_OPTS_FILE) 37 | message(STATUS "Using default lwshell_opts.h file") 38 | set(LWSHELL_OPTS_FILE ${CMAKE_CURRENT_LIST_DIR}/src/include/lwshell/lwshell_opts_template.h) 39 | else() 40 | message(STATUS "Using custom lwshell_opts.h file from ${LWSHELL_OPTS_FILE}") 41 | endif() 42 | configure_file(${LWSHELL_OPTS_FILE} ${LWSHELL_CUSTOM_INC_DIR}/lwshell_opts.h COPYONLY) 43 | -------------------------------------------------------------------------------- /dev/lwshell_opts.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file lwshell_opts.h 3 | * \brief LwSHELL application options 4 | */ 5 | 6 | /* 7 | * Copyright (c) 2024 Tilen MAJERLE 8 | * 9 | * Permission is hereby granted, free of charge, to any person 10 | * obtaining a copy of this software and associated documentation 11 | * files (the "Software"), to deal in the Software without restriction, 12 | * including without limitation the rights to use, copy, modify, merge, 13 | * publish, distribute, sublicense, and/or sell copies of the Software, 14 | * and to permit persons to whom the Software is furnished to do so, 15 | * subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be 18 | * included in all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 23 | * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | * OTHER DEALINGS IN THE SOFTWARE. 28 | * 29 | * This file is part of Lightweight shell library. 30 | * 31 | * Author: Tilen MAJERLE 32 | * Version: v1.2.0 33 | */ 34 | #ifndef LWSHELL_HDR_OPTS_H 35 | #define LWSHELL_HDR_OPTS_H 36 | 37 | /* Rename this file to "lwshell_opts.h" for your application */ 38 | 39 | #include "windows.h" 40 | 41 | #define LWSHELL_CFG_USE_OUTPUT 1 42 | #define LWSHELL_CFG_USE_LIST_CMD 1 43 | #define LWSHELL_CFG_USE_DYNAMIC_COMMANDS 1 44 | #define LWSHELL_CFG_USE_STATIC_COMMANDS 1 45 | 46 | #endif /* LWSHELL_HDR_OPTS_H */ 47 | -------------------------------------------------------------------------------- /docs/static/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 |
LwSHELL
LwSHELL
-------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "cppbuild", 6 | "label": "Build project", 7 | "command": "cmake", 8 | "args": ["--build", "${command:cmake.buildDirectory}", "-j", "8"], 9 | "options": { 10 | "cwd": "${workspaceFolder}" 11 | }, 12 | "problemMatcher": ["$gcc"], 13 | "group": { 14 | "kind": "build", 15 | "isDefault": true 16 | } 17 | }, 18 | { 19 | "type": "shell", 20 | "label": "Re-build project", 21 | "command": "cmake", 22 | "args": ["--build", "${command:cmake.buildDirectory}", "--clean-first", "-v", "-j", "8"], 23 | "options": { 24 | "cwd": "${workspaceFolder}" 25 | }, 26 | "problemMatcher": ["$gcc"], 27 | }, 28 | { 29 | "type": "shell", 30 | "label": "Clean project", 31 | "command": "cmake", 32 | "args": ["--build", "${command:cmake.buildDirectory}", "--target", "clean"], 33 | "options": { 34 | "cwd": "${workspaceFolder}" 35 | }, 36 | "problemMatcher": [] 37 | }, 38 | { 39 | "type": "shell", 40 | "label": "Run application", 41 | "command": "${command:cmake.launchTargetPath}", 42 | "args": [], 43 | "problemMatcher": [], 44 | }, 45 | { 46 | "label": "Docs: Install python plugins from requirements.txt file", 47 | "type": "shell", 48 | "command": "python -m pip install -r requirements.txt", 49 | "options": { 50 | "cwd": "${workspaceFolder}/docs" 51 | }, 52 | "problemMatcher": [] 53 | }, 54 | { 55 | "label": "Docs: Generate html", 56 | "type": "shell", 57 | "command": ".\\make html", 58 | "options": { 59 | "cwd": "${workspaceFolder}/docs" 60 | }, 61 | "problemMatcher": [] 62 | }, 63 | { 64 | "label": "Docs: Clean build directory", 65 | "type": "shell", 66 | "command": ".\\make clean", 67 | "options": { 68 | "cwd": "${workspaceFolder}/docs" 69 | }, 70 | "problemMatcher": [] 71 | }, 72 | ] 73 | } -------------------------------------------------------------------------------- /docs/user-manual/how-it-works.rst: -------------------------------------------------------------------------------- 1 | .. _how_it_works: 2 | 3 | How it works 4 | ============ 5 | 6 | This section describes how library works from the basic perspective. 7 | 8 | LwSHELL is designed to accept *computer-command-like* input, in format of ``cmdname param1 "param 2 with space"``, 9 | parse it properly and search for function callback that is assigned for specific ``cmdname``. 10 | 11 | Library starts processing input line on *line-feed* or *carriage-return* characters. 12 | It splits tokens by space character: 13 | 14 | * Tokens must not include ``space`` character or it will be considered as multi-token input 15 | * To use *space* character as token input, encapsulate character in *double-quotes* 16 | 17 | Command structure 18 | ^^^^^^^^^^^^^^^^^ 19 | 20 | Every command has assigned dedicated name and must start with it. 21 | Application must take care to input exact command name since commands are case-sensitive, ``mycmd`` is a different command than ``Mycmd``. 22 | 23 | Command structure looks like: 24 | 25 | * It must start with command name and has at least one (``1``) parameter, eg. ``mycommand``. Command name is counted as first parameter 26 | * It may have additional parameters split with *space* character 27 | * Every input is parsed as string, even if parameter is string 28 | 29 | .. tip:: 30 | To use space as an input, encapsulate it with *double quotes*, eg. ``mycmd param1 "param 1 has spaces"`` 31 | 32 | Register command 33 | ^^^^^^^^^^^^^^^^ 34 | 35 | Application must register command(s) to be used by the system. 36 | This can be done using :cpp:func:`lwshell_register_cmd` function which accepts 37 | *command name*, *command function* and optional *command description* 38 | 39 | Command description 40 | ^^^^^^^^^^^^^^^^^^^ 41 | 42 | Every command can have assigned its very simple description text, know as *help text*. 43 | Description is later accessible with special command input that has ``2`` parameters in total and second is ``-h``, ``cmdname -h``. 44 | 45 | Data output 46 | ^^^^^^^^^^^ 47 | 48 | To properly work with the library, application must input data to process by using :cpp:func:`lwshell_input` function. 49 | Thanks to the library implementation, it is possible to get data feedback and be able to implement OS-like console. 50 | 51 | To enable data-output feature, define your output callback function and assign it with :cpp:func:`lwshell_set_output_fn` function. 52 | 53 | Data outputs works on: 54 | 55 | * Special characters for *carriage return* and *line-feed* 56 | * Special character *backspace* that returns set of characters to implement backspace-like event on your output 57 | * Actual input character printed back for user feedback 58 | * ``cmdname -h`` feature works to print simple help text 59 | 60 | .. toctree:: 61 | :maxdepth: 2 -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | LwSHELL |version| documentation 2 | =============================== 3 | 4 | Welcome to the documentation for version |version|. 5 | 6 | LwSHELL is lightweight dynamic memory manager optimized for embedded systems. 7 | 8 | .. image:: static/images/logo.svg 9 | :align: center 10 | 11 | .. rst-class:: center 12 | .. rst-class:: index_links 13 | 14 | :ref:`download_library` :ref:`getting_started` `Open Github `_ `Donate `_ 15 | 16 | Features 17 | ^^^^^^^^ 18 | 19 | * Lightweight commands shell for embedded systems 20 | * Platform independent and very easy to port 21 | 22 | * Development of library under Win32 platform 23 | * Written in C language (C99) 24 | * No dynamic allocation, maximum number of commands assigned at compile time 25 | * Highly configurable 26 | * Simple help-text with `cmd -v` option 27 | * User friendly MIT license 28 | 29 | Requirements 30 | ^^^^^^^^^^^^ 31 | 32 | * C compiler 33 | * Less than ``5kB`` of non-volatile memory 34 | 35 | Contribute 36 | ^^^^^^^^^^ 37 | 38 | Fresh contributions are always welcome. Simple instructions to proceed: 39 | 40 | #. Fork Github repository 41 | #. Respect `C style & coding rules `_ used by the library 42 | #. Create a pull request to ``develop`` branch with new features or bug fixes 43 | 44 | Alternatively you may: 45 | 46 | #. Report a bug 47 | #. Ask for a feature request 48 | 49 | License 50 | ^^^^^^^ 51 | 52 | .. literalinclude:: ../LICENSE 53 | 54 | Table of contents 55 | ^^^^^^^^^^^^^^^^^ 56 | 57 | .. toctree:: 58 | :maxdepth: 2 59 | :caption: Contents 60 | 61 | self 62 | get-started/index 63 | user-manual/index 64 | api-reference/index 65 | examples/index 66 | changelog/index 67 | authors/index 68 | 69 | .. toctree:: 70 | :maxdepth: 2 71 | :caption: Other projects 72 | :hidden: 73 | 74 | LwBTN - Button manager 75 | LwDTC - DateTimeCron 76 | LwESP - ESP-AT library 77 | LwEVT - Event manager 78 | LwGPS - GPS NMEA parser 79 | LwCELL - Cellular modem host AT library 80 | LwJSON - JSON parser 81 | LwMEM - Memory manager 82 | LwOW - OneWire with UART 83 | LwPKT - Packet protocol 84 | LwPRINTF - Printf 85 | LwRB - Ring buffer 86 | LwSHELL - Shell 87 | LwUTIL - Utility functions 88 | LwWDG - RTOS task watchdog -------------------------------------------------------------------------------- /tests/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "lwshell/lwshell.h" 3 | 4 | typedef struct { 5 | const char* command; 6 | const char* args_list[16]; 7 | } test_str_t; 8 | 9 | /* List of commands */ 10 | static const test_str_t commands[] = { 11 | { 12 | .command = "test 123 456 789\n", 13 | .args_list = 14 | { 15 | "test", 16 | "123", 17 | "456", 18 | "789", 19 | }, 20 | }, 21 | { 22 | .command = "test 123 longer text\n", 23 | .args_list = 24 | { 25 | "test", 26 | "123", 27 | "longer", 28 | "text", 29 | }, 30 | }, 31 | { 32 | .command = "test 123 \"longer text\"\n", 33 | .args_list = 34 | { 35 | "test", 36 | "123", 37 | "longer text", 38 | }, 39 | }, 40 | { 41 | .command = "test 123 \"longer text with \\\" quotes\"\n", 42 | .args_list = 43 | { 44 | "test", 45 | "123", 46 | "longer text with \\\" quotes", 47 | }, 48 | }, 49 | }; 50 | static uint32_t current_cmd_index; 51 | static int failed = 0; 52 | 53 | /** 54 | * \brief Test command function 55 | * 56 | * \param argc 57 | * \param argv 58 | * \return int32_t 59 | */ 60 | int32_t 61 | prv_test_cmd(int32_t argc, char** argv) { 62 | const test_str_t* cmd = &commands[current_cmd_index]; 63 | int32_t cmd_args_count; 64 | 65 | /* Get list of arguments from test command */ 66 | for (cmd_args_count = 0; cmd->args_list[cmd_args_count] != NULL; ++cmd_args_count) {} 67 | if (cmd_args_count != argc) { 68 | printf("Test failed: Expected argument count (%02u) does not match actual argument count (%02u)\r\n", 69 | (unsigned)cmd_args_count, (unsigned)argc); 70 | failed = 1; 71 | return -1; 72 | } 73 | 74 | /* Check if parameters match */ 75 | for (int32_t idx = 0; idx < argc; ++idx) { 76 | /* Argument failed */ 77 | if (strcmp(cmd->args_list[idx], argv[idx]) != 0) { 78 | printf("Test failed: Argument index %02u, value \"%s\" does not match actual argument value \"%s\"\r\n", 79 | (unsigned)idx, cmd->args_list[idx], argv[idx]); 80 | failed = 1; 81 | return -1; 82 | } 83 | } 84 | 85 | return 0; 86 | } 87 | 88 | /** 89 | * \brief Global test run function 90 | * 91 | */ 92 | int 93 | run_test(void) { 94 | failed = 0; 95 | 96 | printf("Running test...\r\n"); 97 | lwshell_init(); 98 | lwshell_register_cmd("test", prv_test_cmd, "Test command function\r\n"); 99 | 100 | /* Run all commands */ 101 | for (current_cmd_index = 0; current_cmd_index < LWSHELL_ARRAYSIZE(commands); ++current_cmd_index) { 102 | lwshell_input(commands[current_cmd_index].command, strlen(commands[current_cmd_index].command)); 103 | } 104 | printf("Tests completed...\r\n"); 105 | return failed ? -1 : 0; 106 | } 107 | -------------------------------------------------------------------------------- /docs/static/dark-light/common-dark-light.css: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2019 Google LLC 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * https://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | :root { 18 | --heading-color: red; 19 | --duration: 0.5s; 20 | --timing: ease; 21 | } 22 | 23 | *, 24 | ::before, 25 | ::after { 26 | box-sizing: border-box; 27 | } 28 | 29 | body { 30 | margin: 0; 31 | transition: 32 | color var(--duration) var(--timing), 33 | background-color var(--duration) var(--timing); 34 | font-family: sans-serif; 35 | font-size: 12pt; 36 | background-color: var(--background-color); 37 | color: var(--text-color); 38 | display: flex; 39 | justify-content: center; 40 | } 41 | 42 | main { 43 | margin: 1rem; 44 | max-width: 30rem; 45 | position: relative; 46 | } 47 | 48 | h1 { 49 | color: var(--heading-color); 50 | text-shadow: 0.1rem 0.1rem 0.1rem var(--shadow-color); 51 | transition: text-shadow var(--duration) var(--timing); 52 | } 53 | 54 | img { 55 | max-width: 100%; 56 | height: auto; 57 | transition: filter var(--duration) var(--timing); 58 | } 59 | 60 | p { 61 | line-height: 1.5; 62 | word-wrap: break-word; 63 | overflow-wrap: break-word; 64 | hyphens: auto; 65 | } 66 | 67 | fieldset { 68 | border: solid 0.1rem; 69 | box-shadow: 0.1rem 0.1rem 0.1rem var(--shadow-color); 70 | transition: box-shadow var(--duration) var(--timing); 71 | } 72 | 73 | div { 74 | padding: 0.5rem; 75 | } 76 | 77 | aside { 78 | position: absolute; 79 | right: 0; 80 | padding: 0.5rem; 81 | } 82 | 83 | aside:nth-of-type(1) { 84 | top: 0; 85 | } 86 | 87 | aside:nth-of-type(2) { 88 | top: 3rem; 89 | } 90 | 91 | aside:nth-of-type(3) { 92 | top: 7rem; 93 | } 94 | 95 | aside:nth-of-type(4) { 96 | top: 12rem; 97 | } 98 | 99 | #content select, 100 | #content button, 101 | #content input[type="text"], 102 | #content input[type="search"] { 103 | width: 15rem; 104 | } 105 | 106 | dark-mode-toggle { 107 | --dark-mode-toggle-remember-icon-checked: url("checked.svg"); 108 | --dark-mode-toggle-remember-icon-unchecked: url("unchecked.svg"); 109 | --dark-mode-toggle-remember-font: 0.75rem "Helvetica"; 110 | --dark-mode-toggle-legend-font: bold 0.85rem "Helvetica"; 111 | --dark-mode-toggle-label-font: 0.85rem "Helvetica"; 112 | --dark-mode-toggle-color: var(--text-color); 113 | --dark-mode-toggle-background-color: none; 114 | 115 | margin-bottom: 1.5rem; 116 | } 117 | 118 | #dark-mode-toggle-1 { 119 | --dark-mode-toggle-dark-icon: url("sun.png"); 120 | --dark-mode-toggle-light-icon: url("moon.png"); 121 | } 122 | 123 | #dark-mode-toggle-2 { 124 | --dark-mode-toggle-dark-icon: url("sun.svg"); 125 | --dark-mode-toggle-light-icon: url("moon.svg"); 126 | --dark-mode-toggle-icon-size: 2rem; 127 | --dark-mode-toggle-icon-filter: invert(100%); 128 | } 129 | 130 | #dark-mode-toggle-3, 131 | #dark-mode-toggle-4 { 132 | --dark-mode-toggle-dark-icon: url("moon.png"); 133 | --dark-mode-toggle-light-icon: url("sun.png"); 134 | } 135 | 136 | #dark-mode-toggle-3 { 137 | --dark-mode-toggle-remember-filter: invert(100%); 138 | } 139 | 140 | #dark-mode-toggle-4 { 141 | --dark-mode-toggle-active-mode-background-color: var(--accent-color); 142 | --dark-mode-toggle-remember-filter: invert(100%); 143 | } 144 | -------------------------------------------------------------------------------- /dev/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "lwshell/lwshell.h" 5 | 6 | /** 7 | * \brief Reading one character at a time 8 | * 9 | * This is useful to test the shell in a "raw" mode (non-canonical input) 10 | * Please note that conio.h is a windows only header 11 | */ 12 | #ifndef LWSHELL_TEST_READ_SINGLE_CHAR 13 | #define LWSHELL_TEST_READ_SINGLE_CHAR 0 14 | #endif 15 | 16 | #if LWSHELL_TEST_READ_SINGLE_CHAR 17 | #include 18 | #endif 19 | 20 | void example_minimal(void); 21 | extern int run_test(void); 22 | 23 | #if LWSHELL_CFG_USE_OUTPUT 24 | 25 | /** 26 | * \brief Application output function 27 | * \param[in] str: String to print, null-terminated 28 | * \param[in] lw: LwSHELL instance 29 | */ 30 | static void 31 | prv_shell_output(const char* str, lwshell_t* lw) { 32 | (void)lw; 33 | printf("%s", str); 34 | if (*str == '\r') { 35 | printf("\n"); 36 | } 37 | } 38 | 39 | #endif /* LWSHELL_CFG_USE_OUTPUT */ 40 | 41 | /* Commands ... */ 42 | 43 | int32_t 44 | addint_cmd(int32_t argc, char** argv) { 45 | long long i1, i2; 46 | if (argc < 3) { 47 | return -1; 48 | } 49 | i1 = lwshell_parse_long_long(argv[1]); 50 | i2 = lwshell_parse_long_long(argv[2]); 51 | 52 | printf("%lld\r\n", (i1 + i2)); 53 | return 0; 54 | } 55 | 56 | int32_t 57 | subint_cmd(int32_t argc, char** argv) { 58 | long long i1, i2; 59 | if (argc < 3) { 60 | return -1; 61 | } 62 | i1 = lwshell_parse_long_long(argv[1]); 63 | i2 = lwshell_parse_long_long(argv[2]); 64 | 65 | printf("%lld\r\n", (i1 - i2)); 66 | return 0; 67 | } 68 | 69 | int32_t 70 | adddbl_cmd(int32_t argc, char** argv) { 71 | double i1, i2; 72 | if (argc < 3) { 73 | return -1; 74 | } 75 | i1 = lwshell_parse_double(argv[1]); 76 | i2 = lwshell_parse_double(argv[2]); 77 | 78 | printf("%f\r\n", (i1 + i2)); 79 | return 0; 80 | } 81 | 82 | int32_t 83 | subdbl_cmd(int32_t argc, char** argv) { 84 | double i1, i2; 85 | if (argc < 3) { 86 | return -1; 87 | } 88 | i1 = lwshell_parse_double(argv[1]); 89 | i2 = lwshell_parse_double(argv[2]); 90 | 91 | printf("%f\r\n", (i1 - i2)); 92 | return 0; 93 | } 94 | 95 | #if LWSHELL_CFG_USE_STATIC_COMMANDS 96 | 97 | int32_t 98 | addintstatic_cmd(int32_t argc, char** argv) { 99 | printf("Static command...\r\n"); 100 | return addint_cmd(argc, argv); 101 | } 102 | 103 | int32_t 104 | subintstatic_cmd(int32_t argc, char** argv) { 105 | printf("Static command...\r\n"); 106 | return subint_cmd(argc, argv); 107 | } 108 | 109 | int32_t 110 | adddblstatic_cmd(int32_t argc, char** argv) { 111 | printf("Static command...\r\n"); 112 | return adddbl_cmd(argc, argv); 113 | } 114 | 115 | int32_t 116 | subdblstatic_cmd(int32_t argc, char** argv) { 117 | printf("Static command...\r\n"); 118 | return subdbl_cmd(argc, argv); 119 | } 120 | 121 | /* 122 | * Define some static commands, set as const. 123 | */ 124 | static const lwshell_cmd_t static_cmds[] = { 125 | {.name = "addintstatic", .desc = "Add 2 integers, a static implementation", .fn = addintstatic_cmd}, 126 | {.name = "subintstatic", .desc = "Add 2 integers, a static implementation", .fn = subintstatic_cmd}, 127 | {.name = "adddblstatic", .desc = "Add 2 integers, a static implementation", .fn = adddblstatic_cmd}, 128 | {.name = "subdblstatic", .desc = "Add 2 integers, a static implementation", .fn = subdblstatic_cmd}, 129 | }; 130 | 131 | #endif /* LWSHELL_CFG_USE_STATIC_COMMANDS */ 132 | 133 | /* Program entry point */ 134 | int 135 | main(void) { 136 | return run_test(); 137 | 138 | #if 0 139 | #if LWSHELL_CFG_USE_OUTPUT 140 | /* Add optional output function for the purpose of the feedback */ 141 | lwshell_set_output_fn(prv_shell_output); 142 | #endif /* LWSHELL_CFG_USE_OUTPUT */ 143 | 144 | #if LWSHELL_CFG_USE_DYNAMIC_COMMANDS 145 | /* Define shell commands */ 146 | lwshell_register_cmd("addint", addint_cmd, "Adds 2 integer numbers and prints them"); 147 | lwshell_register_cmd("subint", subint_cmd, "Substitute 2 integer numbers and prints them"); 148 | lwshell_register_cmd("adddbl", adddbl_cmd, "Adds 2 double numbers and prints them"); 149 | lwshell_register_cmd("subdbl", subdbl_cmd, "Substitute 2 double numbers and prints them"); 150 | #endif /* LWSHELL_CFG_USE_DYNAMIC_COMMANDS */ 151 | 152 | #if LWSHELL_CFG_USE_STATIC_COMMANDS 153 | /* Register static commands -> one-time call for all commands */ 154 | lwshell_register_static_cmds(static_cmds, LWSHELL_ARRAYSIZE(static_cmds)); 155 | #endif /* LWSHELL_CFG_USE_STATIC_COMMANDS */ 156 | 157 | /* User input to process every character */ 158 | printf("Start entering your command and press enter...\r\n"); 159 | while (1) { 160 | char str[255]; 161 | 162 | #if LWSHELL_TEST_READ_SINGLE_CHAR 163 | str[0] = getch(); 164 | str[1] = '\0'; 165 | #else 166 | fgets(str, sizeof(str), stdin); 167 | #endif 168 | 169 | /* Insert input to library */ 170 | lwshell_input(str, strlen(str)); 171 | } 172 | return 0; 173 | #endif 174 | } 175 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | # import os 14 | # import sys 15 | # sys.path.insert(0, os.path.abspath('.')) 16 | from sphinx.builders.html import StandaloneHTMLBuilder 17 | import subprocess, os 18 | 19 | # Run doxygen first 20 | # read_the_docs_build = os.environ.get('READTHEDOCS', None) == 'True' 21 | # if read_the_docs_build: 22 | subprocess.call('doxygen doxyfile.doxy', shell=True) 23 | # -- Project information ----------------------------------------------------- 24 | 25 | project = 'LwSHELL' 26 | copyright = '2025, Tilen MAJERLE' 27 | author = 'Tilen MAJERLE' 28 | 29 | # Try to get branch at which this is running 30 | # and try to determine which version to display in sphinx 31 | # Version is using git tag if on master/main or "latest-develop" if on develop branch 32 | version = '' 33 | git_branch = '' 34 | 35 | def cmd_exec_print(t): 36 | print("cmd > ", t, "\n", os.popen(t).read().strip(), "\n") 37 | 38 | # Print demo data here 39 | cmd_exec_print('git branch') 40 | cmd_exec_print('git describe') 41 | cmd_exec_print('git describe --tags') 42 | cmd_exec_print('git describe --tags --abbrev=0') 43 | cmd_exec_print('git describe --tags --abbrev=1') 44 | 45 | # Get current branch 46 | res = os.popen('git branch').read().strip() 47 | for line in res.split("\n"): 48 | if line[0] == '*': 49 | git_branch = line[1:].strip() 50 | 51 | # Decision for display version 52 | git_branch = git_branch.replace('(HEAD detached at ', '').replace(')', '') 53 | if git_branch.find('master') >= 0 or git_branch.find('main') >= 0: 54 | #version = os.popen('git describe --tags --abbrev=0').read().strip() 55 | version = 'latest-stable' 56 | elif git_branch.find('develop-') >= 0 or git_branch.find('develop/') >= 0: 57 | version = 'branch-' + git_branch 58 | elif git_branch == 'develop' or git_branch == 'origin/develop': 59 | version = 'latest-develop' 60 | else: 61 | version = os.popen('git describe --tags --abbrev=0').read().strip() 62 | 63 | # For debugging purpose only 64 | print("GIT BRANCH: " + git_branch) 65 | print("PROJ VERSION: " + version) 66 | 67 | # -- General configuration --------------------------------------------------- 68 | 69 | # Add any Sphinx extension module names here, as strings. They can be 70 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 71 | # ones. 72 | extensions = [ 73 | 'sphinx.ext.autodoc', 74 | 'sphinx.ext.intersphinx', 75 | 'sphinx.ext.autosectionlabel', 76 | 'sphinx.ext.todo', 77 | 'sphinx.ext.coverage', 78 | 'sphinx.ext.mathjax', 79 | 'sphinx.ext.ifconfig', 80 | 'sphinx.ext.viewcode', 81 | 'sphinx_sitemap', 82 | 83 | 'breathe', 84 | ] 85 | 86 | # Add any paths that contain templates here, relative to this directory. 87 | templates_path = ['templates'] 88 | 89 | # List of patterns, relative to source directory, that match files and 90 | # directories to ignore when looking for source files. 91 | # This pattern also affects html_static_path and html_extra_path. 92 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 93 | 94 | highlight_language = 'c' 95 | 96 | # -- Options for HTML output ------------------------------------------------- 97 | 98 | # The theme to use for HTML and HTML Help pages. See the documentation for 99 | # a list of builtin themes. 100 | # 101 | html_theme = 'sphinx_rtd_theme' 102 | html_theme_options = { 103 | 'canonical_url': '', 104 | 'analytics_id': '', # Provided by Google in your dashboard 105 | 'display_version': True, 106 | 'prev_next_buttons_location': 'bottom', 107 | 'style_external_links': False, 108 | 109 | 'logo_only': False, 110 | 111 | # Toc options 112 | 'collapse_navigation': True, 113 | 'sticky_navigation': True, 114 | 'navigation_depth': 4, 115 | 'includehidden': True, 116 | 'titles_only': False 117 | } 118 | html_logo = 'static/images/logo.svg' 119 | github_url = 'https://github.com/MaJerle/lwshell' 120 | html_baseurl = 'https://docs.majerle.eu/projects/lwshell/' 121 | 122 | # Add any paths that contain custom static files (such as style sheets) here, 123 | # relative to this directory. They are copied after the builtin static files, 124 | # so a file named "default.css" will overwrite the builtin "default.css". 125 | html_static_path = ['static'] 126 | html_css_files = [ 127 | 'css/common.css', 128 | 'css/custom.css', 129 | 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css', 130 | ] 131 | html_js_files = [ 132 | '' 133 | ] 134 | 135 | # Master index file 136 | master_doc = 'index' 137 | 138 | # --- Breathe configuration ----------------------------------------------------- 139 | breathe_projects = { 140 | "lwshell": "_build/xml/" 141 | } 142 | breathe_default_project = "lwshell" 143 | breathe_default_members = ('members', 'undoc-members') 144 | breathe_show_enumvalue_initializer = True -------------------------------------------------------------------------------- /lwshell/src/include/lwshell/lwshell_opt.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file lwshell_opt.h 3 | * \brief LwSHELL options 4 | */ 5 | 6 | /* 7 | * Copyright (c) 2024 Tilen MAJERLE 8 | * 9 | * Permission is hereby granted, free of charge, to any person 10 | * obtaining a copy of this software and associated documentation 11 | * files (the "Software"), to deal in the Software without restriction, 12 | * including without limitation the rights to use, copy, modify, merge, 13 | * publish, distribute, sublicense, and/or sell copies of the Software, 14 | * and to permit persons to whom the Software is furnished to do so, 15 | * subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be 18 | * included in all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 23 | * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | * OTHER DEALINGS IN THE SOFTWARE. 28 | * 29 | * This file is part of LwSHELL - Lightweight shell library. 30 | * 31 | * Author: Tilen MAJERLE 32 | * Version: v1.2.0 33 | */ 34 | #ifndef LWSHELL_OPT_HDR_H 35 | #define LWSHELL_OPT_HDR_H 36 | 37 | /* Uncomment to ignore user options (or set macro in compiler flags) */ 38 | /* #define LWSHELL_IGNORE_USER_OPTS */ 39 | 40 | /* Include application options */ 41 | #ifndef LWSHELL_IGNORE_USER_OPTS 42 | #include "lwshell_opts.h" 43 | #endif /* LWSHELL_IGNORE_USER_OPTS */ 44 | 45 | #ifdef __cplusplus 46 | extern "C" { 47 | #endif /* __cplusplus */ 48 | 49 | /** 50 | * \defgroup LWSHELL_OPT Configuration 51 | * \brief LwSHELL options 52 | * \{ 53 | */ 54 | 55 | /** 56 | * \brief Memory set function 57 | * 58 | * \note Function footprint is the same as \ref memset 59 | */ 60 | #ifndef LWSHELL_MEMSET 61 | #define LWSHELL_MEMSET(dst, val, len) memset((dst), (val), (len)) 62 | #endif 63 | 64 | /** 65 | * \brief Memory copy function 66 | * 67 | * \note Function footprint is the same as \ref memcpy 68 | */ 69 | #ifndef LWSHELL_MEMCPY 70 | #define LWSHELL_MEMCPY(dst, src, len) memcpy((dst), (src), (len)) 71 | #endif 72 | 73 | /** 74 | * \brief Enables `1` or disables `0` dynamic command register with \ref lwshell_register_cmd or \ref lwshell_register_cmd_ex functions 75 | * 76 | * \note Set to `1` by default for backward compatibility 77 | * \sa LWSHELL_CFG_USE_STATIC_COMMANDS 78 | */ 79 | #ifndef LWSHELL_CFG_USE_DYNAMIC_COMMANDS 80 | #define LWSHELL_CFG_USE_DYNAMIC_COMMANDS 1 81 | #endif 82 | 83 | /** 84 | * \brief Enables `1` or disables `0` static command registration. 85 | * 86 | * When enabled, a single register call is used where application 87 | * can pass constant array of the commands and respective callback functions. 88 | * 89 | * This allows RAM reduction as lookup tables can be stored in the non-volatile memory 90 | * 91 | * \note Set to `0` by default for backward compatibility 92 | * \sa LWSHELL_CFG_USE_DYNAMIC_COMMANDS 93 | */ 94 | #ifndef LWSHELL_CFG_USE_STATIC_COMMANDS 95 | #define LWSHELL_CFG_USE_STATIC_COMMANDS 0 96 | #endif 97 | 98 | /** 99 | * \brief Maximum number of different dynamic registered commands 100 | * 101 | * \warning Deprecated and replaced with \ref LWSHELL_CFG_MAX_DYNAMIC_CMDS 102 | * \deprecated 103 | */ 104 | #ifndef LWSHELL_CFG_MAX_CMDS 105 | #define LWSHELL_CFG_MAX_CMDS 8 106 | #endif 107 | 108 | /** 109 | * \brief Maximum number of different dynamic registered commands 110 | * 111 | * \note Used only when \ref LWSHELL_CFG_USE_DYNAMIC_COMMANDS is enabled 112 | */ 113 | #ifndef LWSHELL_CFG_MAX_DYNAMIC_CMDS 114 | #define LWSHELL_CFG_MAX_DYNAMIC_CMDS LWSHELL_CFG_MAX_CMDS 115 | #endif 116 | 117 | /** 118 | * \brief Maximum characters for command line input 119 | * 120 | * This includes new line character and trailing zero. 121 | * Commands longer than this are automatically discarded 122 | */ 123 | #ifndef LWSHELL_CFG_MAX_INPUT_LEN 124 | #define LWSHELL_CFG_MAX_INPUT_LEN 128 125 | #endif 126 | 127 | /** 128 | * \brief Maximum characters for command name in bytes. 129 | * 130 | * \note Used only when \ref LWSHELL_CFG_USE_DYNAMIC_COMMANDS is enabled 131 | */ 132 | #ifndef LWSHELL_CFG_MAX_CMD_NAME_LEN 133 | #define LWSHELL_CFG_MAX_CMD_NAME_LEN 16 134 | #endif 135 | 136 | /** 137 | * \brief Maximum number of parameters accepted by command. 138 | * 139 | * Number includes command name itself 140 | */ 141 | #ifndef LWSHELL_CFG_MAX_CMD_ARGS 142 | #define LWSHELL_CFG_MAX_CMD_ARGS 8 143 | #endif 144 | 145 | /** 146 | * \brief Enables `1` or disables `0` output function to 147 | * print data from library to application. 148 | * 149 | * This is useful to give library feedback to user 150 | */ 151 | #ifndef LWSHELL_CFG_USE_OUTPUT 152 | #define LWSHELL_CFG_USE_OUTPUT 1 153 | #endif 154 | 155 | /** 156 | * \brief Enables `1` or disables `0` generic ˙listcmd` command to list of registered commands 157 | * 158 | * \ref LWSHELL_CFG_USE_OUTPUT must be enabled to use this feature 159 | */ 160 | #ifndef LWSHELL_CFG_USE_LIST_CMD 161 | #define LWSHELL_CFG_USE_LIST_CMD 0 162 | #endif 163 | 164 | /** 165 | * \} 166 | */ 167 | 168 | #ifdef __cplusplus 169 | } 170 | #endif /* __cplusplus */ 171 | 172 | #endif /* LWSHELL_OPT_HDR_H */ 173 | -------------------------------------------------------------------------------- /docs/get-started/index.rst: -------------------------------------------------------------------------------- 1 | .. _getting_started: 2 | 3 | Getting started 4 | =============== 5 | 6 | Getting started may be the most challenging part of every new library. 7 | This guide is describing how to start with the library quickly and effectively 8 | 9 | .. _download_library: 10 | 11 | Download library 12 | ^^^^^^^^^^^^^^^^ 13 | 14 | Library is primarly hosted on `Github `_. 15 | 16 | You can get it by: 17 | 18 | * Downloading latest release from `releases area `_ on Github 19 | * Cloning ``main`` branch for latest stable version 20 | * Cloning ``develop`` branch for latest development 21 | 22 | Download from releases 23 | ********************** 24 | 25 | All releases are available on Github `releases area `_. 26 | 27 | Clone from Github 28 | ***************** 29 | 30 | First-time clone 31 | """""""""""""""" 32 | 33 | This is used when you do not have yet local copy on your machine. 34 | 35 | * Make sure ``git`` is installed. 36 | * Open console and navigate to path in the system to clone repository to. Use command ``cd your_path`` 37 | * Clone repository with one of available options below 38 | 39 | * Run ``git clone --recurse-submodules https://github.com/MaJerle/lwshell`` command to clone entire repository, including submodules 40 | * Run ``git clone --recurse-submodules --branch develop https://github.com/MaJerle/lwshell`` to clone `development` branch, including submodules 41 | * Run ``git clone --recurse-submodules --branch main https://github.com/MaJerle/lwshell`` to clone `latest stable` branch, including submodules 42 | 43 | * Navigate to ``examples`` directory and run favourite example 44 | 45 | Update cloned to latest version 46 | """"""""""""""""""""""""""""""" 47 | 48 | * Open console and navigate to path in the system where your repository is located. Use command ``cd your_path`` 49 | * Run ``git pull origin main`` command to get latest changes on ``main`` branch 50 | * Run ``git pull origin develop`` command to get latest changes on ``develop`` branch 51 | * Run ``git submodule update --init --remote`` to update submodules to latest version 52 | 53 | .. note:: 54 | This is preferred option to use when you want to evaluate library and run prepared examples. 55 | Repository consists of multiple submodules which can be automatically downloaded when cloning and pulling changes from root repository. 56 | 57 | Add library to project 58 | ^^^^^^^^^^^^^^^^^^^^^^ 59 | 60 | At this point it is assumed that you have successfully download library, either with ``git clone`` command or with manual download from the library releases page. 61 | Next step is to add the library to the project, by means of source files to compiler inputs and header files in search path. 62 | 63 | *CMake* is the main supported build system. Package comes with the ``CMakeLists.txt`` and ``library.cmake`` files, both located in the ``lwshell`` directory: 64 | 65 | * ``library.cmake``: It is a fully configured set of variables and with library definition. User can include this file to the project file with ``include(path/to/library.cmake)`` and then manually use the variables provided by the file, such as list of source files, include paths or necessary compiler definitions. It is up to the user to properly use the this file on its own. 66 | * ``CMakeLists.txt``: It is a wrapper-only file and includes ``library.cmake`` file. It is used for when user wants to include the library to the main project by simply calling *CMake* ``add_subdirectory`` command, followed by ``target_link_libraries`` to link external library to the final project. 67 | 68 | .. tip:: 69 | Open ``library.cmake`` and analyze the provided information. Among variables, you can also find list of all possible exposed libraries for the user. 70 | 71 | If you do not use the *CMake*, you can do the following: 72 | 73 | * Copy ``lwshell`` folder to your project, it contains library files 74 | * Add ``lwshell/src/include`` folder to `include path` of your toolchain. This is where `C/C++` compiler can find the files during compilation process. Usually using ``-I`` flag 75 | * Add source files from ``lwshell/src/`` folder to toolchain build. These files are built by `C/C++` compiler 76 | * Copy ``lwshell/src/include/lwshell/lwshell_opts_template.h`` to project folder and rename it to ``lwshell_opts.h`` 77 | * Build the project 78 | 79 | Configuration file 80 | ^^^^^^^^^^^^^^^^^^ 81 | 82 | Configuration file is used to overwrite default settings defined for the essential use case. 83 | Library comes with template config file, which can be modified according to the application needs. 84 | and it should be copied (or simply renamed in-place) and named ``lwshell_opts.h`` 85 | 86 | .. note:: 87 | Default configuration template file location: ``lwshell/src/include/lwshell/lwshell_opts_template.h``. 88 | File must be renamed to ``lwshell_opts.h`` first and then copied to the project directory where compiler 89 | include paths have access to it by using ``#include "lwshell_opts.h"``. 90 | 91 | .. tip:: 92 | If you are using *CMake* build system, define the variable ``LWSHELL_OPTS_FILE`` before adding library's directory to the *CMake* project. 93 | Variable must contain the path to the user options file. If not provided and to avoid build error, one will be generated in the build directory. 94 | 95 | Configuration options list is available available in the :ref:`api_lwshell_opt` section. 96 | If any option is about to be modified, it should be done in configuration file 97 | 98 | .. literalinclude:: ../../lwshell/src/include/lwshell/lwshell_opts_template.h 99 | :language: c 100 | :linenos: 101 | :caption: Template configuration file 102 | 103 | .. note:: 104 | If you prefer to avoid using configuration file, application must define 105 | a global symbol ``LWSHELL_IGNORE_USER_OPTS``, visible across entire application. 106 | This can be achieved with ``-D`` compiler option. 107 | 108 | Minimal example code 109 | ^^^^^^^^^^^^^^^^^^^^ 110 | 111 | To verify proper library setup, minimal example has been prepared. 112 | Run it in your main application file to verify its proper execution 113 | 114 | .. literalinclude:: ../../examples/example_minimal.c 115 | :language: c 116 | :linenos: 117 | :caption: Absolute minimum example -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | # Language part removed. With clang-format >=20.1, the C and Cpp are separately handled, 2 | # so either there is no language at all, or we need to create 2 formats for C and Cpp, separately 3 | 4 | --- 5 | # Language: Cpp 6 | # BasedOnStyle: LLVM 7 | AccessModifierOffset: -2 8 | AlignAfterOpenBracket: Align 9 | AlignArrayOfStructures: None 10 | AlignConsecutiveMacros: 11 | Enabled: true 12 | AcrossEmptyLines: true 13 | AcrossComments: true 14 | AlignConsecutiveAssignments: None 15 | AlignConsecutiveBitFields: 16 | Enabled: true 17 | AcrossEmptyLines: true 18 | AcrossComments: true 19 | AlignConsecutiveDeclarations: None 20 | AlignEscapedNewlines: Right 21 | AlignOperands: Align 22 | SortIncludes: true 23 | InsertBraces: true # Control statements must have curly brackets 24 | AlignTrailingComments: true 25 | AllowAllArgumentsOnNextLine: true 26 | AllowAllParametersOfDeclarationOnNextLine: true 27 | AllowShortEnumsOnASingleLine: true 28 | AllowShortBlocksOnASingleLine: Empty 29 | AllowShortCaseLabelsOnASingleLine: true 30 | AllowShortFunctionsOnASingleLine: All 31 | AllowShortLambdasOnASingleLine: All 32 | AllowShortIfStatementsOnASingleLine: Never 33 | AllowShortLoopsOnASingleLine: false 34 | AlwaysBreakAfterDefinitionReturnType: None 35 | AlwaysBreakAfterReturnType: AllDefinitions 36 | AlwaysBreakBeforeMultilineStrings: false 37 | AlwaysBreakTemplateDeclarations: Yes 38 | AttributeMacros: 39 | - __capability 40 | BinPackArguments: true 41 | BinPackParameters: true 42 | BraceWrapping: 43 | AfterCaseLabel: false 44 | AfterClass: false 45 | AfterControlStatement: Never 46 | AfterEnum: false 47 | AfterFunction: false 48 | AfterNamespace: false 49 | AfterObjCDeclaration: false 50 | AfterStruct: false 51 | AfterUnion: false 52 | AfterExternBlock: false 53 | BeforeCatch: false 54 | BeforeElse: false 55 | BeforeLambdaBody: false 56 | BeforeWhile: false 57 | IndentBraces: false 58 | SplitEmptyFunction: true 59 | SplitEmptyRecord: true 60 | SplitEmptyNamespace: true 61 | BreakBeforeBinaryOperators: NonAssignment 62 | BreakBeforeConceptDeclarations: true 63 | BreakBeforeBraces: Attach 64 | BreakBeforeInheritanceComma: false 65 | BreakInheritanceList: BeforeColon 66 | BreakBeforeTernaryOperators: true 67 | BreakConstructorInitializersBeforeComma: false 68 | BreakConstructorInitializers: BeforeColon 69 | BreakAfterJavaFieldAnnotations: false 70 | BreakStringLiterals: true 71 | ColumnLimit: 120 72 | CommentPragmas: "^ IWYU pragma:" 73 | QualifierAlignment: Leave 74 | CompactNamespaces: false 75 | ConstructorInitializerIndentWidth: 4 76 | ContinuationIndentWidth: 4 77 | Cpp11BracedListStyle: true 78 | DeriveLineEnding: true 79 | DerivePointerAlignment: false 80 | DisableFormat: false 81 | EmptyLineAfterAccessModifier: Never 82 | EmptyLineBeforeAccessModifier: LogicalBlock 83 | ExperimentalAutoDetectBinPacking: false 84 | PackConstructorInitializers: BinPack 85 | BasedOnStyle: "" 86 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 87 | AllowAllConstructorInitializersOnNextLine: true 88 | FixNamespaceComments: true 89 | ForEachMacros: 90 | - foreach 91 | - Q_FOREACH 92 | - BOOST_FOREACH 93 | IfMacros: 94 | - KJ_IF_MAYBE 95 | IncludeBlocks: Preserve 96 | IncludeCategories: 97 | - Regex: "^<(.*)>" 98 | Priority: 0 99 | - Regex: '^"(.*)"' 100 | Priority: 1 101 | - Regex: "(.*)" 102 | Priority: 2 103 | IncludeIsMainRegex: "(Test)?$" 104 | IncludeIsMainSourceRegex: "" 105 | IndentAccessModifiers: false 106 | IndentCaseLabels: true 107 | IndentCaseBlocks: false 108 | IndentGotoLabels: true 109 | IndentPPDirectives: None 110 | IndentExternBlock: AfterExternBlock 111 | IndentRequires: true 112 | IndentWidth: 4 113 | IndentWrappedFunctionNames: false 114 | InsertTrailingCommas: None 115 | JavaScriptQuotes: Leave 116 | JavaScriptWrapImports: true 117 | KeepEmptyLinesAtTheStartOfBlocks: true 118 | LambdaBodyIndentation: Signature 119 | MacroBlockBegin: "" 120 | MacroBlockEnd: "" 121 | MaxEmptyLinesToKeep: 1 122 | NamespaceIndentation: None 123 | ObjCBinPackProtocolList: Auto 124 | ObjCBlockIndentWidth: 2 125 | ObjCBreakBeforeNestedBlockParam: true 126 | ObjCSpaceAfterProperty: false 127 | ObjCSpaceBeforeProtocolList: true 128 | PenaltyBreakAssignment: 2 129 | PenaltyBreakBeforeFirstCallParameter: 19 130 | PenaltyBreakComment: 300 131 | PenaltyBreakFirstLessLess: 120 132 | PenaltyBreakOpenParenthesis: 0 133 | PenaltyBreakString: 1000 134 | PenaltyBreakTemplateDeclaration: 10 135 | PenaltyExcessCharacter: 1000000 136 | PenaltyReturnTypeOnItsOwnLine: 60 137 | PenaltyIndentedWhitespace: 0 138 | PointerAlignment: Left 139 | PPIndentWidth: -1 140 | ReferenceAlignment: Pointer 141 | ReflowComments: false 142 | RemoveBracesLLVM: false 143 | SeparateDefinitionBlocks: Always 144 | ShortNamespaceLines: 1 145 | SortJavaStaticImport: Before 146 | SortUsingDeclarations: true 147 | SpaceAfterCStyleCast: false 148 | SpaceAfterLogicalNot: false 149 | SpaceAfterTemplateKeyword: true 150 | SpaceBeforeAssignmentOperators: true 151 | SpaceBeforeCaseColon: false 152 | SpaceBeforeParens: ControlStatements 153 | SpaceBeforeParensOptions: 154 | AfterControlStatements: true 155 | AfterForeachMacros: true 156 | AfterFunctionDefinitionName: false 157 | AfterFunctionDeclarationName: false 158 | AfterIfMacros: true 159 | AfterOverloadedOperator: false 160 | BeforeNonEmptyParentheses: false 161 | SpaceAroundPointerQualifiers: Default 162 | SpaceBeforeRangeBasedForLoopColon: true 163 | SpaceInEmptyBlock: false 164 | SpaceInEmptyParentheses: false 165 | SpacesBeforeTrailingComments: 1 166 | SpacesInAngles: Never 167 | SpacesInConditionalStatement: false 168 | SpacesInContainerLiterals: true 169 | SpacesInCStyleCastParentheses: false 170 | SpacesInLineCommentPrefix: 171 | Minimum: 1 172 | Maximum: -1 173 | SpacesInParentheses: false 174 | SpacesInSquareBrackets: false 175 | SpaceBeforeSquareBrackets: false 176 | BitFieldColonSpacing: Both 177 | Standard: Latest 178 | StatementAttributeLikeMacros: 179 | - Q_EMIT 180 | StatementMacros: 181 | - Q_UNUSED 182 | - QT_REQUIRE_VERSION 183 | TabWidth: 8 184 | UseCRLF: false 185 | UseTab: Never 186 | WhitespaceSensitiveMacros: 187 | - STRINGIZE 188 | - PP_STRINGIZE 189 | - BOOST_PP_STRINGIZE 190 | - NS_SWIFT_NAME 191 | - CF_SWIFT_NAME 192 | SpaceBeforeCpp11BracedList: false 193 | SpaceBeforeCtorInitializerColon: true 194 | SpaceBeforeInheritanceColon: true 195 | --- 196 | 197 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #Build Keil files 2 | *.rar 3 | *.o 4 | *.d 5 | *.crf 6 | *.htm 7 | *.dep 8 | *.map 9 | *.bak 10 | *.axf 11 | *.lnp 12 | *.lst 13 | *.ini 14 | *.scvd 15 | *.iex 16 | *.sct 17 | *.MajerleT 18 | *.tjuln 19 | *.tilen 20 | *.dbgconf 21 | *.uvguix 22 | *.uvoptx 23 | *.__i 24 | *.i 25 | *.txt 26 | !docs/*.txt 27 | !CMakeLists.txt 28 | RTE/ 29 | 30 | *debug 31 | 32 | # IAR Settings 33 | **/settings/*.crun 34 | **/settings/*.dbgdt 35 | **/settings/*.cspy 36 | **/settings/*.cspy.* 37 | **/settings/*.xcl 38 | **/settings/*.dni 39 | **/settings/*.wsdt 40 | **/settings/*.wspos 41 | 42 | # IAR Debug Exe 43 | **/Exe/*.sim 44 | 45 | # IAR Debug Obj 46 | **/Obj/*.pbd 47 | **/Obj/*.pbd.* 48 | **/Obj/*.pbi 49 | **/Obj/*.pbi.* 50 | 51 | *.TMP 52 | /docs_src/x_Doxyfile.doxy 53 | 54 | .DS_Store 55 | 56 | ## Ignore Visual Studio temporary files, build results, and 57 | ## files generated by popular Visual Studio add-ons. 58 | ## 59 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 60 | 61 | # User-specific files 62 | *.suo 63 | *.user 64 | *.userosscache 65 | *.sln.docstates 66 | 67 | # User-specific files (MonoDevelop/Xamarin Studio) 68 | *.userprefs 69 | 70 | # Build results 71 | [Dd]ebug/ 72 | [Dd]ebugPublic/ 73 | [Rr]elease/ 74 | [Rr]eleases/ 75 | [Dd]ebug*/ 76 | x64/ 77 | x86/ 78 | bld/ 79 | [Bb]in/ 80 | [Oo]bj/ 81 | [Ll]og/ 82 | _build/ 83 | build/ 84 | __build__/ 85 | 86 | # Visual Studio 2015/2017 cache/options directory 87 | .vs/ 88 | # Uncomment if you have tasks that create the project's static files in wwwroot 89 | #wwwroot/ 90 | 91 | # Visual Studio 2017 auto generated files 92 | Generated\ Files/ 93 | 94 | # MSTest test Results 95 | [Tt]est[Rr]esult*/ 96 | [Bb]uild[Ll]og.* 97 | 98 | # NUNIT 99 | *.VisualState.xml 100 | TestResult.xml 101 | 102 | # Build Results of an ATL Project 103 | [Dd]ebugPS/ 104 | [Rr]eleasePS/ 105 | dlldata.c 106 | 107 | # Benchmark Results 108 | BenchmarkDotNet.Artifacts/ 109 | 110 | # .NET Core 111 | project.lock.json 112 | project.fragment.lock.json 113 | artifacts/ 114 | **/Properties/launchSettings.json 115 | 116 | # StyleCop 117 | StyleCopReport.xml 118 | 119 | # Files built by Visual Studio 120 | *_i.c 121 | *_p.c 122 | *_i.h 123 | *.ilk 124 | *.meta 125 | *.obj 126 | *.pch 127 | *.pdb 128 | *.pgc 129 | *.pgd 130 | *.rsp 131 | *.sbr 132 | *.tlb 133 | *.tli 134 | *.tlh 135 | *.tmp 136 | *.tmp_proj 137 | *.log 138 | *.vspscc 139 | *.vssscc 140 | .builds 141 | *.pidb 142 | *.svclog 143 | *.scc 144 | *.out 145 | *.sim 146 | 147 | # Chutzpah Test files 148 | _Chutzpah* 149 | 150 | # Visual C++ cache files 151 | ipch/ 152 | *.aps 153 | *.ncb 154 | *.opendb 155 | *.opensdf 156 | *.sdf 157 | *.cachefile 158 | *.VC.db 159 | *.VC.VC.opendb 160 | 161 | # Visual Studio profiler 162 | *.psess 163 | *.vsp 164 | *.vspx 165 | *.sap 166 | 167 | # Visual Studio Trace Files 168 | *.e2e 169 | 170 | # TFS 2012 Local Workspace 171 | $tf/ 172 | 173 | # Guidance Automation Toolkit 174 | *.gpState 175 | 176 | # ReSharper is a .NET coding add-in 177 | _ReSharper*/ 178 | *.[Rr]e[Ss]harper 179 | *.DotSettings.user 180 | 181 | # JustCode is a .NET coding add-in 182 | .JustCode 183 | 184 | # TeamCity is a build add-in 185 | _TeamCity* 186 | 187 | # DotCover is a Code Coverage Tool 188 | *.dotCover 189 | 190 | # AxoCover is a Code Coverage Tool 191 | .axoCover/* 192 | !.axoCover/settings.json 193 | 194 | # Visual Studio code coverage results 195 | *.coverage 196 | *.coveragexml 197 | 198 | # NCrunch 199 | _NCrunch_* 200 | .*crunch*.local.xml 201 | nCrunchTemp_* 202 | 203 | # MightyMoose 204 | *.mm.* 205 | AutoTest.Net/ 206 | 207 | # Web workbench (sass) 208 | .sass-cache/ 209 | 210 | # Installshield output folder 211 | [Ee]xpress/ 212 | 213 | # DocProject is a documentation generator add-in 214 | DocProject/buildhelp/ 215 | DocProject/Help/*.HxT 216 | DocProject/Help/*.HxC 217 | DocProject/Help/*.hhc 218 | DocProject/Help/*.hhk 219 | DocProject/Help/*.hhp 220 | DocProject/Help/Html2 221 | DocProject/Help/html 222 | 223 | # Click-Once directory 224 | publish/ 225 | 226 | # Publish Web Output 227 | *.[Pp]ublish.xml 228 | *.azurePubxml 229 | # Note: Comment the next line if you want to checkin your web deploy settings, 230 | # but database connection strings (with potential passwords) will be unencrypted 231 | *.pubxml 232 | *.publishproj 233 | 234 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 235 | # checkin your Azure Web App publish settings, but sensitive information contained 236 | # in these scripts will be unencrypted 237 | PublishScripts/ 238 | 239 | # NuGet Packages 240 | *.nupkg 241 | # The packages folder can be ignored because of Package Restore 242 | **/[Pp]ackages/* 243 | # except build/, which is used as an MSBuild target. 244 | !**/[Pp]ackages/build/ 245 | # Uncomment if necessary however generally it will be regenerated when needed 246 | #!**/[Pp]ackages/repositories.config 247 | # NuGet v3's project.json files produces more ignorable files 248 | *.nuget.props 249 | *.nuget.targets 250 | 251 | # Microsoft Azure Build Output 252 | csx/ 253 | *.build.csdef 254 | 255 | # Microsoft Azure Emulator 256 | ecf/ 257 | rcf/ 258 | 259 | # Windows Store app package directories and files 260 | AppPackages/ 261 | BundleArtifacts/ 262 | Package.StoreAssociation.xml 263 | _pkginfo.txt 264 | *.appx 265 | 266 | # Visual Studio cache files 267 | # files ending in .cache can be ignored 268 | *.[Cc]ache 269 | # but keep track of directories ending in .cache 270 | !*.[Cc]ache/ 271 | 272 | # Others 273 | ClientBin/ 274 | ~$* 275 | *~ 276 | *.dbmdl 277 | *.dbproj.schemaview 278 | *.jfm 279 | *.pfx 280 | *.publishsettings 281 | orleans.codegen.cs 282 | 283 | # Including strong name files can present a security risk 284 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 285 | #*.snk 286 | 287 | # Since there are multiple workflows, uncomment next line to ignore bower_components 288 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 289 | #bower_components/ 290 | 291 | # RIA/Silverlight projects 292 | Generated_Code/ 293 | 294 | # Backup & report files from converting an old project file 295 | # to a newer Visual Studio version. Backup files are not needed, 296 | # because we have git ;-) 297 | _UpgradeReport_Files/ 298 | Backup*/ 299 | UpgradeLog*.XML 300 | UpgradeLog*.htm 301 | 302 | # SQL Server files 303 | *.mdf 304 | *.ldf 305 | *.ndf 306 | 307 | # Business Intelligence projects 308 | *.rdl.data 309 | *.bim.layout 310 | *.bim_*.settings 311 | 312 | # Microsoft Fakes 313 | FakesAssemblies/ 314 | 315 | # GhostDoc plugin setting file 316 | *.GhostDoc.xml 317 | 318 | # Node.js Tools for Visual Studio 319 | .ntvs_analysis.dat 320 | node_modules/ 321 | 322 | # TypeScript v1 declaration files 323 | typings/ 324 | 325 | # Visual Studio 6 build log 326 | *.plg 327 | 328 | # Visual Studio 6 workspace options file 329 | *.opt 330 | 331 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 332 | *.vbw 333 | 334 | # Visual Studio LightSwitch build output 335 | **/*.HTMLClient/GeneratedArtifacts 336 | **/*.DesktopClient/GeneratedArtifacts 337 | **/*.DesktopClient/ModelManifest.xml 338 | **/*.Server/GeneratedArtifacts 339 | **/*.Server/ModelManifest.xml 340 | _Pvt_Extensions 341 | 342 | # Paket dependency manager 343 | .paket/paket.exe 344 | paket-files/ 345 | 346 | # FAKE - F# Make 347 | .fake/ 348 | 349 | # JetBrains Rider 350 | .idea/ 351 | *.sln.iml 352 | 353 | # CodeRush 354 | .cr/ 355 | 356 | # Python Tools for Visual Studio (PTVS) 357 | __pycache__/ 358 | *.pyc 359 | 360 | # Cake - Uncomment if you are using it 361 | # tools/** 362 | # !tools/packages.config 363 | 364 | # Tabs Studio 365 | *.tss 366 | 367 | # Telerik's JustMock configuration file 368 | *.jmconfig 369 | 370 | # BizTalk build output 371 | *.btp.cs 372 | *.btm.cs 373 | *.odx.cs 374 | *.xsd.cs 375 | 376 | # OpenCover UI analysis results 377 | OpenCover/ 378 | 379 | # Azure Stream Analytics local run output 380 | ASALocalRun/ 381 | 382 | # MSBuild Binary and Structured Log 383 | *.binlog 384 | 385 | log_file.txt 386 | .metadata/ 387 | .mxproject 388 | .settings/ 389 | project.ioc 390 | mx.scratch 391 | *.tilen majerle 392 | 393 | 394 | # Altium 395 | Project outputs* 396 | History/ 397 | *.SchDocPreview 398 | *.$$$Preview 399 | 400 | # VSCode projects 401 | project_vscode_compiled.exe -------------------------------------------------------------------------------- /lwshell/src/include/lwshell/lwshell.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file lwshell.h 3 | * \brief Lightweight shell 4 | */ 5 | 6 | /* 7 | * Copyright (c) 2024 Tilen MAJERLE 8 | * 9 | * Permission is hereby granted, free of charge, to any person 10 | * obtaining a copy of this software and associated documentation 11 | * files (the "Software"), to deal in the Software without restriction, 12 | * including without limitation the rights to use, copy, modify, merge, 13 | * publish, distribute, sublicense, and/or sell copies of the Software, 14 | * and to permit persons to whom the Software is furnished to do so, 15 | * subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be 18 | * included in all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 23 | * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | * OTHER DEALINGS IN THE SOFTWARE. 28 | * 29 | * This file is part of LwSHELL - Lightweight shell library. 30 | * 31 | * Author: Tilen MAJERLE 32 | * Version: v1.2.0 33 | */ 34 | #ifndef LWSHELL_HDR_H 35 | #define LWSHELL_HDR_H 36 | 37 | #include 38 | #include 39 | #include "lwshell/lwshell_opt.h" 40 | 41 | #ifdef __cplusplus 42 | extern "C" { 43 | #endif /* __cplusplus */ 44 | 45 | /** 46 | * \defgroup LWSHELL Lightweight shell 47 | * \brief Lightweight shell 48 | * \{ 49 | */ 50 | 51 | /** 52 | * \brief Get size of statically allocated array 53 | * \param[in] x: Object to get array size of 54 | * \return Number of elements in array 55 | */ 56 | #define LWSHELL_ARRAYSIZE(x) (sizeof(x) / sizeof((x)[0])) 57 | 58 | /** 59 | * \brief LwSHELL result enumeration 60 | */ 61 | typedef enum { 62 | lwshellOK = 0x00, /*!< Everything OK */ 63 | lwshellERRPAR, /*!< Parameter error */ 64 | lwshellERRMEM, /*!< Memory error */ 65 | } lwshellr_t; 66 | 67 | /* Forward declaration */ 68 | struct lwshell; 69 | 70 | /** 71 | * \brief Command function prototype 72 | * \param[in] argc: Number of arguments 73 | * \param[in] argv: Pointer to arguments 74 | * \return `0` on success, `-1` otherwise 75 | */ 76 | typedef int32_t (*lwshell_cmd_fn)(int32_t argc, char** argv); 77 | 78 | /** 79 | * \brief Callback function for character output 80 | * \param[in] str: String to output 81 | * \param[in] lwobj: LwSHELL instance 82 | */ 83 | typedef void (*lwshell_output_fn)(const char* str, struct lwshell* lwobj); 84 | 85 | /** 86 | * \brief Shell command structure 87 | */ 88 | typedef struct { 89 | lwshell_cmd_fn fn; /*!< Command function to call on match */ 90 | const char* name; /*!< Command name to search for match */ 91 | const char* desc; /*!< Command description for help */ 92 | } lwshell_cmd_t; 93 | 94 | /** 95 | * \brief LwSHELL main structure 96 | */ 97 | typedef struct lwshell { 98 | #if LWSHELL_CFG_USE_OUTPUT || __DOXYGEN__ 99 | lwshell_output_fn out_fn; /*!< Optional output function */ 100 | #endif /* LWSHELL_CFG_USE_OUTPUT || __DOXYGEN__ */ 101 | char buff[LWSHELL_CFG_MAX_INPUT_LEN + 1]; /*!< Shell command input buffer */ 102 | size_t buff_ptr; /*!< Buffer pointer for input */ 103 | int32_t argc; /*!< Number of arguments parsed in command */ 104 | char* argv[LWSHELL_CFG_MAX_CMD_ARGS]; /*!< Array of pointers to all arguments */ 105 | 106 | #if LWSHELL_CFG_USE_DYNAMIC_COMMANDS || __DOXYGEN__ 107 | lwshell_cmd_t dynamic_cmds[LWSHELL_CFG_MAX_DYNAMIC_CMDS]; /*!< Shell registered dynamic commands */ 108 | size_t dynamic_cmds_cnt; /*!< Number of registered dynamic commands */ 109 | #endif /* LWSHELL_CFG_USE_DYNAMIC_COMMANDS || __DOXYGEN__ */ 110 | 111 | #if LWSHELL_CFG_USE_STATIC_COMMANDS || __DOXYGEN__ 112 | const lwshell_cmd_t* static_cmds; /*!< Pointer to an array of static commands */ 113 | size_t static_cmds_cnt; /*!< Length of status commands array */ 114 | #endif /* LWSHELL_CFG_USE_STATIC_COMMANDS || __DOXYGEN__ */ 115 | } lwshell_t; 116 | 117 | lwshellr_t lwshell_init_ex(lwshell_t* lwobj); 118 | lwshellr_t lwshell_set_output_fn_ex(lwshell_t* lwobj, lwshell_output_fn out_fn); 119 | lwshellr_t lwshell_register_cmd_ex(lwshell_t* lwobj, const char* cmd_name, lwshell_cmd_fn cmd_fn, const char* desc); 120 | lwshellr_t lwshell_input_ex(lwshell_t* lwobj, const void* in_data, size_t len); 121 | 122 | lwshellr_t lwshell_register_static_cmds_ex(lwshell_t* lwobj, const lwshell_cmd_t* cmds, size_t cmds_len); 123 | 124 | /** 125 | * \brief Initialize shell interface 126 | * \note It applies to default shell instance 127 | * \return \ref lwshellOK on success, member of \ref lwshellr_t otherwise 128 | */ 129 | #define lwshell_init() lwshell_init_ex(NULL) 130 | 131 | /** 132 | * \brief Set output function to use to print data from library to user 133 | * \note It applies to default shell instance 134 | * \param[in] out_fn: Output function to print library data. 135 | * Set to `NULL` to disable the feature 136 | * \return \ref lwshellOK on success, member of \ref lwshellr_t otherwise 137 | */ 138 | #define lwshell_set_output_fn(out_fn) lwshell_set_output_fn_ex(NULL, (out_fn)) 139 | 140 | /** 141 | * \brief Register new command to shell 142 | * \note It applies to default shell instance 143 | * \param[in] cmd_name: Command name. This one is used when entering shell command 144 | * \param[in] cmd_fn: Function to call on command match 145 | * \param[in] desc: Custom command description 146 | * \return \ref lwshellOK on success, member of \ref lwshellr_t otherwise 147 | * \note Available only when \ref LWSHELL_CFG_USE_DYNAMIC_COMMANDS is enabled 148 | */ 149 | #define lwshell_register_cmd(cmd_name, cmd_fn, desc) lwshell_register_cmd_ex(NULL, (cmd_name), (cmd_fn), (desc)) 150 | 151 | /** 152 | * \brief Input data to shell processing 153 | * \note It applies to default shell instance 154 | * \param[in] in_data: Input data to process 155 | * \param[in] len: Length of data for input 156 | * \return \ref lwshellOK on success, member of \ref lwshellr_t otherwise 157 | */ 158 | #define lwshell_input(in_data, len) lwshell_input_ex(NULL, (in_data), (len)) 159 | 160 | /** 161 | * \brief Register new command to shell 162 | * \note It applies to default shell instance 163 | * \param[in] cmds: Array of const static commands. It can be from non-volatile memory 164 | * \param[in] cmds_len: Length of array elements 165 | * \return \ref lwshellOK on success, member of \ref lwshellr_t otherwise 166 | * \note Available only when \ref LWSHELL_CFG_USE_STATIC_COMMANDS is enabled 167 | */ 168 | #define lwshell_register_static_cmds(cmds, cmds_len) lwshell_register_static_cmds_ex(NULL, (cmds), (cmds_len)) 169 | 170 | /** 171 | * \brief Parse input string as `integer` 172 | * \param[in] str: String to parse 173 | * \return String parsed as integer 174 | */ 175 | #define lwshell_parse_int(str) atoi(str) 176 | 177 | /** 178 | * \brief Parse input string as `double` 179 | * \param[in] str: String to parse 180 | * \return String parsed as `double` 181 | */ 182 | #define lwshell_parse_double(str) atof(str) 183 | 184 | /** 185 | * \brief Parse input string as `long` 186 | * \param[in] str: String to parse 187 | * \return String parsed as `long` 188 | */ 189 | #define lwshell_parse_long(str) atol(str) 190 | 191 | /** 192 | * \brief Parse input string as `long long` 193 | * \param[in] str: String to parse 194 | * \return String parsed as `long long` 195 | */ 196 | #define lwshell_parse_long_long(str) atoll(str) 197 | 198 | /** 199 | * \} 200 | */ 201 | 202 | #ifdef __cplusplus 203 | } 204 | #endif /* __cplusplus */ 205 | 206 | #endif /* LWSHELL_HDR_H */ 207 | -------------------------------------------------------------------------------- /docs/static/dark-light/dark-mode-toggle.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // @license © 2019 Google LLC. Licensed under the Apache License, Version 2.0. 18 | const doc = document; 19 | const store = localStorage; 20 | const PREFERS_COLOR_SCHEME = 'prefers-color-scheme'; 21 | const MEDIA = 'media'; 22 | const LIGHT = 'light'; 23 | const DARK = 'dark'; 24 | const MQ_DARK = `(${PREFERS_COLOR_SCHEME}:${DARK})`; 25 | const MQ_LIGHT = `(${PREFERS_COLOR_SCHEME}:${LIGHT})`; 26 | const LINK_REL_STYLESHEET = 'link[rel=stylesheet]'; 27 | const REMEMBER = 'remember'; 28 | const LEGEND = 'legend'; 29 | const TOGGLE = 'toggle'; 30 | const SWITCH = 'switch'; 31 | const APPEARANCE = 'appearance'; 32 | const PERMANENT = 'permanent'; 33 | const MODE = 'mode'; 34 | const COLOR_SCHEME_CHANGE = 'colorschemechange'; 35 | const PERMANENT_COLOR_SCHEME = 'permanentcolorscheme'; 36 | const ALL = 'all'; 37 | const NOT_ALL = 'not all'; 38 | const NAME = 'dark-mode-toggle'; 39 | const DEFAULT_URL = 'https://googlechromelabs.github.io/dark-mode-toggle/demo/'; 40 | 41 | // See https://html.spec.whatwg.org/multipage/common-dom-interfaces.html ↵ 42 | // #reflecting-content-attributes-in-idl-attributes. 43 | const installStringReflection = (obj, attrName, propName = attrName) => { 44 | Object.defineProperty(obj, propName, { 45 | enumerable: true, 46 | get() { 47 | const value = this.getAttribute(attrName); 48 | return value === null ? '' : value; 49 | }, 50 | set(v) { 51 | this.setAttribute(attrName, v); 52 | }, 53 | }); 54 | }; 55 | 56 | const installBoolReflection = (obj, attrName, propName = attrName) => { 57 | Object.defineProperty(obj, propName, { 58 | enumerable: true, 59 | get() { 60 | return this.hasAttribute(attrName); 61 | }, 62 | set(v) { 63 | if (v) { 64 | this.setAttribute(attrName, ''); 65 | } else { 66 | this.removeAttribute(attrName); 67 | } 68 | }, 69 | }); 70 | }; 71 | 72 | const template = doc.createElement('template'); 73 | // ⚠️ Note: this is a minified version of `src/template-contents.tpl`. 74 | // Compress the CSS with https://cssminifier.com/, then paste it here. 75 | // eslint-disable-next-line max-len 76 | template.innerHTML = `
`; 77 | 78 | export class DarkModeToggle extends HTMLElement { 79 | static get observedAttributes() { 80 | return [MODE, APPEARANCE, PERMANENT, LEGEND, LIGHT, DARK, REMEMBER]; 81 | } 82 | 83 | constructor() { 84 | super(); 85 | 86 | installStringReflection(this, MODE); 87 | installStringReflection(this, APPEARANCE); 88 | installStringReflection(this, LEGEND); 89 | installStringReflection(this, LIGHT); 90 | installStringReflection(this, DARK); 91 | installStringReflection(this, REMEMBER); 92 | 93 | installBoolReflection(this, PERMANENT); 94 | 95 | this._darkCSS = null; 96 | this._lightCSS = null; 97 | 98 | doc.addEventListener(COLOR_SCHEME_CHANGE, (event) => { 99 | this.mode = event.detail.colorScheme; 100 | this._updateRadios(); 101 | this._updateCheckbox(); 102 | }); 103 | 104 | doc.addEventListener(PERMANENT_COLOR_SCHEME, (event) => { 105 | this.permanent = event.detail.permanent; 106 | this._permanentCheckbox.checked = this.permanent; 107 | }); 108 | 109 | this._initializeDOM(); 110 | } 111 | 112 | _initializeDOM() { 113 | const shadowRoot = this.attachShadow({mode: 'open'}); 114 | shadowRoot.appendChild(template.content.cloneNode(true)); 115 | 116 | // We need to support `media="(prefers-color-scheme: dark)"` (with space) 117 | // and `media="(prefers-color-scheme:dark)"` (without space) 118 | this._darkCSS = doc.querySelectorAll(`${LINK_REL_STYLESHEET}[${MEDIA}*=${PREFERS_COLOR_SCHEME}][${MEDIA}*="${DARK}"]`); 119 | this._lightCSS = doc.querySelectorAll(`${LINK_REL_STYLESHEET}[${MEDIA}*=${PREFERS_COLOR_SCHEME}][${MEDIA}*="${LIGHT}"]`); 120 | 121 | // Get DOM references. 122 | this._lightRadio = shadowRoot.querySelector('[part=lightRadio]'); 123 | this._lightLabel = shadowRoot.querySelector('[part=lightLabel]'); 124 | this._darkRadio = shadowRoot.querySelector('[part=darkRadio]'); 125 | this._darkLabel = shadowRoot.querySelector('[part=darkLabel]'); 126 | this._darkCheckbox = shadowRoot.querySelector('[part=toggleCheckbox]'); 127 | this._checkboxLabel = shadowRoot.querySelector('[part=toggleLabel]'); 128 | this._legendLabel = shadowRoot.querySelector('legend'); 129 | this._permanentAside = shadowRoot.querySelector('aside'); 130 | this._permanentCheckbox = 131 | shadowRoot.querySelector('[part=permanentCheckbox]'); 132 | this._permanentLabel = shadowRoot.querySelector('[part=permanentLabel]'); 133 | 134 | // Does the browser support native `prefers-color-scheme`? 135 | const hasNativePrefersColorScheme = 136 | matchMedia(MQ_DARK).media !== NOT_ALL; 137 | // Listen to `prefers-color-scheme` changes. 138 | if (hasNativePrefersColorScheme) { 139 | matchMedia(MQ_DARK).addListener(({matches}) => { 140 | this.mode = matches ? DARK : LIGHT; 141 | this._dispatchEvent(COLOR_SCHEME_CHANGE, {colorScheme: this.mode}); 142 | }); 143 | } 144 | // Set initial state, giving preference to a remembered value, then the 145 | // native value (if supported), and eventually defaulting to a light 146 | // experience. 147 | const rememberedValue = store.getItem(NAME); 148 | if (rememberedValue && [DARK, LIGHT].includes(rememberedValue)) { 149 | this.mode = rememberedValue; 150 | this._permanentCheckbox.checked = true; 151 | this.permanent = true; 152 | } else if (hasNativePrefersColorScheme) { 153 | this.mode = matchMedia(MQ_LIGHT).matches ? LIGHT : DARK; 154 | } 155 | if (!this.mode) { 156 | this.mode = LIGHT; 157 | } 158 | if (this.permanent && !rememberedValue) { 159 | store.setItem(NAME, this.mode); 160 | } 161 | 162 | // Default to toggle appearance. 163 | if (!this.appearance) { 164 | this.appearance = TOGGLE; 165 | } 166 | 167 | // Update the appearance to either of toggle or switch. 168 | this._updateAppearance(); 169 | 170 | // Update the radios 171 | this._updateRadios(); 172 | 173 | // Make the checkbox reflect the state of the radios 174 | this._updateCheckbox(); 175 | 176 | // Synchronize the behavior of the radio and the checkbox. 177 | [this._lightRadio, this._darkRadio].forEach((input) => { 178 | input.addEventListener('change', () => { 179 | this.mode = this._lightRadio.checked ? LIGHT : DARK; 180 | this._updateCheckbox(); 181 | this._dispatchEvent(COLOR_SCHEME_CHANGE, {colorScheme: this.mode}); 182 | }); 183 | }); 184 | this._darkCheckbox.addEventListener('change', () => { 185 | this.mode = this._darkCheckbox.checked ? DARK : LIGHT; 186 | this._updateRadios(); 187 | this._dispatchEvent(COLOR_SCHEME_CHANGE, {colorScheme: this.mode}); 188 | }); 189 | 190 | // Make remembering the last mode optional 191 | this._permanentCheckbox.addEventListener('change', () => { 192 | this.permanent = this._permanentCheckbox.checked; 193 | this._dispatchEvent(PERMANENT_COLOR_SCHEME, { 194 | permanent: this.permanent, 195 | }); 196 | }); 197 | 198 | // Finally update the mode and let the world know what's going on 199 | this._updateMode(); 200 | this._dispatchEvent(COLOR_SCHEME_CHANGE, {colorScheme: this.mode}); 201 | this._dispatchEvent(PERMANENT_COLOR_SCHEME, { 202 | permanent: this.permanent, 203 | }); 204 | } 205 | 206 | attributeChangedCallback(name, oldValue, newValue) { 207 | if (name === MODE) { 208 | if (![LIGHT, DARK].includes(newValue)) { 209 | throw new RangeError(`Allowed values: "${LIGHT}" and "${DARK}".`); 210 | } 211 | // Only show the dialog programmatically on devices not capable of hover 212 | // and only if there is a label 213 | if (matchMedia('(hover:none)').matches && this.remember) { 214 | this._showPermanentAside(); 215 | } 216 | if (this.permanent) { 217 | store.setItem(NAME, this.mode); 218 | } 219 | this._updateRadios(); 220 | this._updateCheckbox(); 221 | this._updateMode(); 222 | } else if (name === APPEARANCE) { 223 | if (![TOGGLE, SWITCH].includes(newValue)) { 224 | throw new RangeError(`Allowed values: "${TOGGLE}" and "${SWITCH}".`); 225 | } 226 | this._updateAppearance(); 227 | } else if (name === PERMANENT) { 228 | if (this.permanent) { 229 | store.setItem(NAME, this.mode); 230 | } else { 231 | store.removeItem(NAME); 232 | } 233 | this._permanentCheckbox.checked = this.permanent; 234 | } else if (name === LEGEND) { 235 | this._legendLabel.textContent = newValue; 236 | } else if (name === REMEMBER) { 237 | this._permanentLabel.textContent = newValue; 238 | } else if (name === LIGHT) { 239 | this._lightLabel.textContent = newValue; 240 | if (this.mode === LIGHT) { 241 | this._checkboxLabel.textContent = newValue; 242 | } 243 | } else if (name === DARK) { 244 | this._darkLabel.textContent = newValue; 245 | if (this.mode === DARK) { 246 | this._checkboxLabel.textContent = newValue; 247 | } 248 | } 249 | } 250 | 251 | _dispatchEvent(type, value) { 252 | this.dispatchEvent(new CustomEvent(type, { 253 | bubbles: true, 254 | composed: true, 255 | detail: value, 256 | })); 257 | } 258 | 259 | _updateAppearance() { 260 | // Hide or show the light-related affordances dependent on the appearance, 261 | // which can be "switch" or "toggle". 262 | const appearAsToggle = this.appearance === TOGGLE; 263 | this._lightRadio.hidden = appearAsToggle; 264 | this._lightLabel.hidden = appearAsToggle; 265 | this._darkRadio.hidden = appearAsToggle; 266 | this._darkLabel.hidden = appearAsToggle; 267 | this._darkCheckbox.hidden = !appearAsToggle; 268 | this._checkboxLabel.hidden = !appearAsToggle; 269 | } 270 | 271 | _updateRadios() { 272 | if (this.mode === LIGHT) { 273 | this._lightRadio.checked = true; 274 | } else { 275 | this._darkRadio.checked = true; 276 | } 277 | } 278 | 279 | _updateCheckbox() { 280 | if (this.mode === LIGHT) { 281 | this._checkboxLabel.style.setProperty(`--${NAME}-checkbox-icon`, 282 | `var(--${NAME}-light-icon,url("${DEFAULT_URL}moon.png"))`); 283 | this._checkboxLabel.textContent = this.light; 284 | if (!this.light) { 285 | this._checkboxLabel.ariaLabel = DARK; 286 | } 287 | this._darkCheckbox.checked = false; 288 | } else { 289 | this._checkboxLabel.style.setProperty(`--${NAME}-checkbox-icon`, 290 | `var(--${NAME}-dark-icon,url("${DEFAULT_URL}sun.png"))`); 291 | this._checkboxLabel.textContent = this.dark; 292 | if (!this.dark) { 293 | this._checkboxLabel.ariaLabel = LIGHT; 294 | } 295 | this._darkCheckbox.checked = true; 296 | } 297 | } 298 | 299 | _updateMode() { 300 | if (this.mode === LIGHT) { 301 | this._lightCSS.forEach((link) => { 302 | link.media = ALL; 303 | link.disabled = false; 304 | }); 305 | this._darkCSS.forEach((link) => { 306 | link.media = NOT_ALL; 307 | link.disabled = true; 308 | }); 309 | } else { 310 | this._darkCSS.forEach((link) => { 311 | link.media = ALL; 312 | link.disabled = false; 313 | }); 314 | this._lightCSS.forEach((link) => { 315 | link.media = NOT_ALL; 316 | link.disabled = true; 317 | }); 318 | } 319 | } 320 | 321 | _showPermanentAside() { 322 | this._permanentAside.style.visibility = 'visible'; 323 | setTimeout(() => { 324 | this._permanentAside.style.visibility = 'hidden'; 325 | }, 3000); 326 | } 327 | } 328 | 329 | customElements.define(NAME, DarkModeToggle); -------------------------------------------------------------------------------- /lwshell/src/lwshell/lwshell.c: -------------------------------------------------------------------------------- 1 | /** 2 | * \file lwshell.c 3 | * \brief Lightweight shell 4 | */ 5 | 6 | /* 7 | * Copyright (c) 2024 Tilen MAJERLE 8 | * 9 | * Permission is hereby granted, free of charge, to any person 10 | * obtaining a copy of this software and associated documentation 11 | * files (the "Software"), to deal in the Software without restriction, 12 | * including without limitation the rights to use, copy, modify, merge, 13 | * publish, distribute, sublicense, and/or sell copies of the Software, 14 | * and to permit persons to whom the Software is furnished to do so, 15 | * subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be 18 | * included in all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 23 | * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | * OTHER DEALINGS IN THE SOFTWARE. 28 | * 29 | * This file is part of LwSHELL - Lightweight shell library. 30 | * 31 | * Author: Tilen MAJERLE 32 | * Version: v1.2.0 33 | */ 34 | #include 35 | #include "lwshell/lwshell.h" 36 | 37 | /* Check enabled features */ 38 | #if LWSHELL_CFG_USE_LIST_CMD && !LWSHELL_CFG_USE_OUTPUT 39 | #error "To use list command feature, LWSHELL_CFG_USE_OUTPUT must be enabled" 40 | #endif /* LWSHELL_CFG_USE_LIST_CMD && !LWSHELL_CFG_USE_OUTPUT */ 41 | #if !LWSHELL_CFG_USE_DYNAMIC_COMMANDS && !LWSHELL_CFG_USE_STATIC_COMMANDS 42 | #error "At least one of LWSHELL_CFG_USE_DYNAMIC_COMMANDS or !LWSHELL_CFG_USE_STATIC_COMMANDS must be enabled" 43 | #endif /* !LWSHELL_CFG_USE_DYNAMIC_COMMANDS && !LWSHELL_CFG_USE_STATIC_COMMANDS */ 44 | 45 | /* Default characters */ 46 | #define LWSHELL_ASCII_NULL 0x00 /*!< Null character */ 47 | #define LWSHELL_ASCII_BACKSPACE 0x08 /*!< Backspace */ 48 | #define LWSHELL_ASCII_LF 0x0A /*!< Line feed */ 49 | #define LWSHELL_ASCII_CR 0x0D /*!< Carriage return */ 50 | #define LWSHELL_ASCII_DEL 0x7F /*!< Delete character */ 51 | #define LWSHELL_ASCII_SPACE 0x20 /*!< Space character */ 52 | 53 | #if LWSHELL_CFG_USE_OUTPUT 54 | #define LWSHELL_OUTPUT(lwobj, str) \ 55 | do { \ 56 | if ((lwobj)->out_fn != NULL && (str) != NULL) { \ 57 | (lwobj)->out_fn((str), (lwobj)); \ 58 | } \ 59 | } while (0) 60 | #else 61 | #define LWSHELL_OUTPUT(lwobj, str) 62 | #endif 63 | 64 | /* Array of all commands */ 65 | static lwshell_t shell; 66 | 67 | /* Get shell instance from input */ 68 | #define LWSHELL_GET_LWOBJ(lwobj) ((lwobj) != NULL ? (lwobj) : (&shell)) 69 | 70 | /* Add character to instance */ 71 | #define LWSHELL_ADD_CH(lwobj, ch) \ 72 | do { \ 73 | if ((lwobj)->buff_ptr < (LWSHELL_ARRAYSIZE((lwobj)->buff) - 1)) { \ 74 | (lwobj)->buff[(lwobj)->buff_ptr] = ch; \ 75 | (lwobj)->buff[++(lwobj)->buff_ptr] = '\0'; \ 76 | } \ 77 | } while (0) 78 | 79 | /* Reset buffers */ 80 | #define LWSHELL_RESET_BUFF(lwobj) \ 81 | do { \ 82 | LWSHELL_MEMSET((lwobj)->buff, 0x00, sizeof((lwobj)->buff)); \ 83 | LWSHELL_MEMSET((lwobj)->argv, 0x00, sizeof((lwobj)->argv)); \ 84 | (lwobj)->buff_ptr = 0; \ 85 | } while (0) 86 | 87 | /** 88 | * \brief Parse input string 89 | * \param[in] lwobj: LwSHELL instance 90 | */ 91 | static void 92 | prv_parse_input(lwshell_t* lwobj) { 93 | size_t s_len; 94 | char* str; 95 | 96 | /* 97 | * Check string length and compare with buffer pointer 98 | * Must be more than `1` character since we have to include end of line 99 | */ 100 | s_len = strlen(lwobj->buff); 101 | if (s_len != lwobj->buff_ptr || lwobj->buff_ptr == 0) { 102 | return; 103 | } 104 | 105 | /* Set default values */ 106 | lwobj->argc = 0; 107 | lwobj->argv[0] = lwobj->buff; 108 | 109 | /* Process complete input */ 110 | str = lwobj->buff; 111 | 112 | /* Process complete string */ 113 | while (*str != '\0') { 114 | while (*str == ' ' && ++str) {} /* Remove leading spaces */ 115 | if (*str == '\0') { 116 | break; 117 | } 118 | 119 | /* Check if it starts with quote to handle escapes */ 120 | if (*str == '"') { 121 | ++str; 122 | lwobj->argv[lwobj->argc++] = str; /* Set start of argument after quotes */ 123 | 124 | /* Process until end of quote */ 125 | while (*str != '\0') { 126 | if (*str == '\\') { 127 | ++str; 128 | if (*str == '"') { 129 | ++str; 130 | } 131 | } else if (*str == '"') { 132 | *str = '\0'; 133 | ++str; 134 | break; 135 | } else { 136 | ++str; 137 | } 138 | } 139 | } else { 140 | lwobj->argv[lwobj->argc++] = str; /* Set start of argument directly on character */ 141 | while (*str != ' ' && *str != '\0') { 142 | if (*str == '"') { /* Quote should not be here... */ 143 | *str = '\0'; /* ...add NULL termination to end token */ 144 | } 145 | ++str; 146 | } 147 | if (*str == '\0') { 148 | break; 149 | } 150 | *str = '\0'; 151 | ++str; 152 | } 153 | 154 | /* Check for number of arguments */ 155 | if (lwobj->argc == LWSHELL_ARRAYSIZE(lwobj->argv)) { 156 | break; 157 | } 158 | } 159 | 160 | /* Check for command */ 161 | if (lwobj->argc > 0) { 162 | const lwshell_cmd_t* ccmd = NULL; 163 | size_t arg_len = strlen(lwobj->argv[0]); 164 | 165 | #if LWSHELL_CFG_USE_DYNAMIC_COMMANDS 166 | /* Process all dynamic commands */ 167 | if (ccmd == NULL && lwobj->dynamic_cmds_cnt > 0) { 168 | for (size_t idx = 0; idx < lwobj->dynamic_cmds_cnt; ++idx) { 169 | if (arg_len == strlen(lwobj->dynamic_cmds[idx].name) 170 | && strncmp(lwobj->dynamic_cmds[idx].name, lwobj->argv[0], arg_len) == 0) { 171 | ccmd = &lwobj->dynamic_cmds[idx]; 172 | break; 173 | } 174 | } 175 | } 176 | #endif /* LWSHELL_CFG_USE_DYNAMIC_COMMANDS */ 177 | 178 | #if LWSHELL_CFG_USE_STATIC_COMMANDS 179 | /* Process all static commands */ 180 | if (ccmd == NULL && lwobj->static_cmds != NULL && lwobj->static_cmds_cnt > 0) { 181 | for (size_t idx = 0; idx < lwobj->static_cmds_cnt; ++idx) { 182 | if (arg_len == strlen(lwobj->static_cmds[idx].name) 183 | && strncmp(lwobj->static_cmds[idx].name, lwobj->argv[0], arg_len) == 0) { 184 | ccmd = &lwobj->static_cmds[idx]; 185 | break; 186 | } 187 | } 188 | } 189 | #endif /* LWSHELL_CFG_USE_STATIC_COMMANDS */ 190 | 191 | /* Valid command ready? */ 192 | if (ccmd != NULL) { 193 | if (lwobj->argc == 2U && lwobj->argv[1][0] == '-' && lwobj->argv[1][1] == 'h' 194 | && lwobj->argv[1][2] == '\0') { 195 | /* Here we can print version */ 196 | LWSHELL_OUTPUT(lwobj, ccmd->desc); 197 | LWSHELL_OUTPUT(lwobj, "\r\n"); 198 | } else { 199 | ccmd->fn(lwobj->argc, lwobj->argv); 200 | } 201 | #if LWSHELL_CFG_USE_LIST_CMD 202 | } else if (strncmp(lwobj->argv[0], "listcmd", 7U) == 0) { 203 | LWSHELL_OUTPUT(lwobj, "List of registered commands\r\n"); 204 | #if LWSHELL_CFG_USE_DYNAMIC_COMMANDS 205 | for (size_t idx = 0; idx < lwobj->dynamic_cmds_cnt; ++idx) { 206 | LWSHELL_OUTPUT(lwobj, lwobj->dynamic_cmds[idx].name); 207 | LWSHELL_OUTPUT(lwobj, "\t\t\t"); 208 | LWSHELL_OUTPUT(lwobj, lwobj->dynamic_cmds[idx].desc); 209 | LWSHELL_OUTPUT(lwobj, "\r\n"); 210 | } 211 | #endif /* LWSHELL_CFG_USE_DYNAMIC_COMMANDS */ 212 | #if LWSHELL_CFG_USE_STATIC_COMMANDS 213 | for (size_t idx = 0; idx < lwobj->static_cmds_cnt; ++idx) { 214 | LWSHELL_OUTPUT(lwobj, lwobj->static_cmds[idx].name); 215 | LWSHELL_OUTPUT(lwobj, "\t\t\t"); 216 | LWSHELL_OUTPUT(lwobj, lwobj->static_cmds[idx].desc); 217 | LWSHELL_OUTPUT(lwobj, "\r\n"); 218 | } 219 | #endif /* LWSHELL_CFG_USE_STATIC_COMMANDS */ 220 | #endif /* LWSHELL_CFG_USE_LIST_CMD */ 221 | } else { 222 | LWSHELL_OUTPUT(lwobj, LWSHELL_CFG_USE_LIST_CMD 223 | ? "Unknown command, use listcmd to list available commands\r\n" 224 | : "Unknown command\r\n"); 225 | } 226 | } 227 | } 228 | 229 | /** 230 | * \brief Initialize shell interface 231 | * \param[in] lwobj: LwSHELL object instance. Set to `NULL` to use default one 232 | * \return \ref lwshellOK on success, member of \ref lwshellr_t otherwise 233 | */ 234 | lwshellr_t 235 | lwshell_init_ex(lwshell_t* lwobj) { 236 | lwobj = LWSHELL_GET_LWOBJ(lwobj); 237 | LWSHELL_MEMSET(lwobj, 0x00, sizeof(*lwobj)); 238 | return lwshellOK; 239 | } 240 | 241 | #if LWSHELL_CFG_USE_OUTPUT || __DOXYGEN__ 242 | 243 | /** 244 | * \brief Set output function to use to print data from library to user 245 | * \param[in] lwobj: LwSHELL object instance. Set to `NULL` to use default one 246 | * \param[in] out_fn: Output function to print library data. 247 | * Set to `NULL` to disable the feature 248 | * \return \ref lwshellOK on success, member of \ref lwshellr_t otherwise 249 | */ 250 | lwshellr_t 251 | lwshell_set_output_fn_ex(lwshell_t* lwobj, lwshell_output_fn out_fn) { 252 | lwobj = LWSHELL_GET_LWOBJ(lwobj); 253 | lwobj->out_fn = out_fn; 254 | return lwshellOK; 255 | } 256 | 257 | #endif /* LWSHELL_CFG_USE_OUTPUT || __DOXYGEN__ */ 258 | 259 | #if LWSHELL_CFG_USE_DYNAMIC_COMMANDS || __DOXYGEN__ 260 | 261 | /** 262 | * \brief Register new command to shell 263 | * \param[in] lwobj: LwSHELL object instance. Set to `NULL` to use default one 264 | * \param[in] cmd_name: Command name. This one is used when entering shell command 265 | * \param[in] cmd_fn: Function to call on command match 266 | * \param[in] desc: Custom command description 267 | * \return \ref lwshellOK on success, member of \ref lwshellr_t otherwise 268 | * \note Available only when \ref LWSHELL_CFG_USE_DYNAMIC_COMMANDS is enabled 269 | */ 270 | lwshellr_t 271 | lwshell_register_cmd_ex(lwshell_t* lwobj, const char* cmd_name, lwshell_cmd_fn cmd_fn, const char* desc) { 272 | lwobj = LWSHELL_GET_LWOBJ(lwobj); 273 | 274 | if (cmd_name == NULL || cmd_fn == NULL || strlen(cmd_name) == 0) { 275 | return lwshellERRPAR; 276 | } 277 | 278 | /* Check for memory available */ 279 | if (lwobj->dynamic_cmds_cnt < LWSHELL_ARRAYSIZE(lwobj->dynamic_cmds)) { 280 | lwobj->dynamic_cmds[lwobj->dynamic_cmds_cnt].name = cmd_name; 281 | lwobj->dynamic_cmds[lwobj->dynamic_cmds_cnt].fn = cmd_fn; 282 | lwobj->dynamic_cmds[lwobj->dynamic_cmds_cnt].desc = desc; 283 | 284 | ++lwobj->dynamic_cmds_cnt; 285 | return lwshellOK; 286 | } 287 | return lwshellERRMEM; 288 | } 289 | 290 | #endif /* LWSHELL_CFG_USE_DYNAMIC_COMMANDS || __DOXYGEN__ */ 291 | 292 | #if LWSHELL_CFG_USE_STATIC_COMMANDS || __DOXYGEN__ 293 | 294 | /** 295 | * \brief Register new command to shell 296 | * \param[in] lwobj: LwSHELL object instance. Set to `NULL` to use default one 297 | * \param[in] cmds: Array of const static commands. It can be from non-volatile memory. 298 | * \param[in] cmds_len: Length of array elements 299 | * \return \ref lwshellOK on success, member of \ref lwshellr_t otherwise 300 | * \note Available only when \ref LWSHELL_CFG_USE_STATIC_COMMANDS is enabled 301 | */ 302 | lwshellr_t 303 | lwshell_register_static_cmds_ex(lwshell_t* lwobj, const lwshell_cmd_t* cmds, size_t cmds_len) { 304 | lwobj = LWSHELL_GET_LWOBJ(lwobj); 305 | lwobj->static_cmds = cmds; 306 | lwobj->static_cmds_cnt = cmds_len; 307 | return lwshellOK; 308 | } 309 | 310 | #endif /* LWSHELL_CFG_USE_STATIC_COMMANDS || __DOXYGEN__ */ 311 | 312 | /** 313 | * \brief Input data to shell processing 314 | * \param[in] lwobj: LwSHELL object instance. Set to `NULL` to use default one 315 | * \param[in] in_data: Input data to process 316 | * \param[in] len: Length of data for input 317 | * \return \ref lwshellOK on success, member of \ref lwshellr_t otherwise 318 | */ 319 | lwshellr_t 320 | lwshell_input_ex(lwshell_t* lwobj, const void* in_data, size_t len) { 321 | const char* p_data = in_data; 322 | lwobj = LWSHELL_GET_LWOBJ(lwobj); 323 | 324 | if (in_data == NULL || len == 0) { 325 | return lwshellERRPAR; 326 | } 327 | 328 | /* Process all bytes */ 329 | for (size_t idx = 0; idx < len; ++idx) { 330 | switch (p_data[idx]) { 331 | case LWSHELL_ASCII_CR: 332 | case LWSHELL_ASCII_LF: { 333 | LWSHELL_OUTPUT(lwobj, p_data[idx] == LWSHELL_ASCII_CR ? "\r" : "\n"); 334 | prv_parse_input(lwobj); 335 | LWSHELL_RESET_BUFF(lwobj); 336 | break; 337 | } 338 | case LWSHELL_ASCII_BACKSPACE: { 339 | /* Try to delete character from buffer */ 340 | if (lwobj->buff_ptr > 0) { 341 | --lwobj->buff_ptr; 342 | lwobj->buff[lwobj->buff_ptr] = '\0'; 343 | LWSHELL_OUTPUT(lwobj, "\b \b"); 344 | } 345 | break; 346 | } 347 | default: { 348 | #if LWSHELL_CFG_USE_OUTPUT 349 | char str[2] = {p_data[idx], 0}; 350 | LWSHELL_OUTPUT(lwobj, str); 351 | #endif /* LWSHELL_CFG_USE_OUTPUT */ 352 | if (p_data[idx] >= 0x20 && p_data[idx] < 0x7F) { 353 | LWSHELL_ADD_CH(lwobj, p_data[idx]); 354 | } 355 | } 356 | } 357 | } 358 | return lwshellOK; 359 | } 360 | --------------------------------------------------------------------------------