├── 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 |
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 |
--------------------------------------------------------------------------------
/.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 |
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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------