├── .astylerc ├── .github ├── issue_template.md └── pull_request_template.md ├── .gitignore ├── .mbedignore ├── CMakeLists.txt ├── LICENSE ├── Makefile ├── README.md ├── example ├── Mbed-OS │ ├── .mbed │ ├── .mbedignore │ ├── README.md │ ├── main.cpp │ └── mbed_app.json └── linux │ ├── Makefile │ ├── README.md │ ├── main.c │ ├── mbed-trace │ └── mbed_trace.h │ └── memtest.sh ├── mbed-client-cli └── ns_cmdline.h ├── mbed_lib.json ├── source ├── CMakeLists.txt ├── ns_cmdline.c └── ns_list_internal │ ├── ns_list.c │ ├── ns_list.h │ └── ns_types.h └── test ├── CMakeLists.txt ├── Test.cpp └── run_unit_tests.sh /.astylerc: -------------------------------------------------------------------------------- 1 | # Mbed OS code style definition file for astyle 2 | 3 | # Don't create backup files, let git handle it 4 | suffix=none 5 | 6 | # K&R style 7 | style=kr 8 | 9 | # 1 TBS addition to k&r, add braces to one liners 10 | # Use -j as it was changed in astyle from brackets to braces, this way it is compatible with older astyle versions 11 | -j 12 | 13 | # 4 spaces, convert tabs to spaces 14 | indent=spaces=4 15 | convert-tabs 16 | 17 | # Indent switches and cases 18 | indent-switches 19 | 20 | # Remove spaces in and around parentheses 21 | unpad-paren 22 | 23 | # Insert a space after if, while, for, and around operators 24 | pad-header 25 | pad-oper 26 | 27 | # Pointer/reference operators go next to the name (on the right) 28 | align-pointer=name 29 | align-reference=name 30 | 31 | # Attach { for classes and namespaces 32 | attach-namespaces 33 | attach-classes 34 | 35 | # Extend longer lines, define maximum 120 value. This results in aligned code, 36 | # otherwise the lines are broken and not consistent 37 | max-continuation-indent=120 38 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | Note: This is just a template, so feel free to use/remove the unnecessary things 2 | 3 | ### Description 4 | - Type: Bug | Enhancement | Question 5 | - Related Issue: `#abc` 6 | 7 | --------------------------------------------------------------- 8 | ## Bug 9 | 10 | **mbed-clien-cli version** 11 | (`git describe --tags`) 12 | 13 | **Expected Behavior** 14 | 15 | **Actual Behavior** 16 | 17 | **Steps to Reproduce** 18 | 19 | ---------------------------------------------------------------- 20 | ## Enhancement 21 | 22 | **Reason to enhance/problem with existing solution** 23 | 24 | **Suggested enhancement** 25 | 26 | **Pros** 27 | 28 | **Cons** 29 | 30 | ----------------------------------------------------------------- 31 | 32 | ## Question 33 | 34 | **How to?** 35 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Status 2 | **READY/IN DEVELOPMENT/HOLD** 3 | 4 | ## Migrations 5 | YES | NO 6 | 7 | ## Description 8 | A few sentences describing the overall goals of the pull request's commits. 9 | 10 | ## Related PRs 11 | List related PRs against other branches: 12 | 13 | branch | PR 14 | ------ | ------ 15 | other_pr_production | [link]() 16 | other_pr_master | [link]() 17 | 18 | 19 | ## Todos 20 | - [ ] Tests 21 | - [ ] Documentation 22 | 23 | 24 | ## Deploy Notes 25 | Notes regarding deployment the contained body of work. These should note any 26 | db migrations, etc. 27 | 28 | ## Steps to Test or Reproduce 29 | Outline the steps to test or reproduce the PR here. 30 | 31 | ```sh 32 | git pull --prune 33 | git checkout 34 | cd test 35 | ./run_unit_tests.sh 36 | ``` 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Libraries 12 | *.lib 13 | *.a 14 | *.la 15 | *.lo 16 | 17 | # Shared objects (inc. Windows DLLs) 18 | *.dll 19 | *.so 20 | *.so.* 21 | *.dylib 22 | 23 | # Executables 24 | *.exe 25 | *.out 26 | *.app 27 | *.i*86 28 | *.x86_64 29 | *.hex 30 | 31 | # Debug files 32 | *.dSYM/ 33 | build/ 34 | output/ 35 | example/linux/cli 36 | mbed-os-5/BUILD/ 37 | -------------------------------------------------------------------------------- /.mbedignore: -------------------------------------------------------------------------------- 1 | test/* 2 | source/ns_list_internal/* 3 | example/* 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ################################################################################# 2 | ## Copyright 2020-2021 Pelion. 3 | ## 4 | ## SPDX-License-Identifier: Apache-2.0 5 | ## 6 | ## Licensed under the Apache License, Version 2.0 (the "License"); 7 | ## you may not use this file except in compliance with the License. 8 | ## You may obtain a copy of the License at 9 | ## 10 | ## http://www.apache.org/licenses/LICENSE-2.0 11 | ## 12 | ## Unless required by applicable law or agreed to in writing, software 13 | ## distributed under the License is distributed on an "AS IS" BASIS, 14 | ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | ## See the License for the specific language governing permissions and 16 | ## limitations under the License. 17 | ################################################################################# 18 | 19 | target_include_directories(mbed-client-cli 20 | INTERFACE 21 | . 22 | ./mbed-client-cli 23 | ) 24 | 25 | target_sources(mbed-client-cli 26 | INTERFACE 27 | source/ns_cmdline.c 28 | ) 29 | 30 | target_link_libraries(mbed-client-cli 31 | INTERFACE 32 | mbed-nanostack-libservice 33 | ) 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 13 | owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities 16 | that control, are controlled by, or are under common control with that entity. 17 | For the purposes of this definition, "control" means (i) the power, direct or 18 | indirect, to cause the direction or management of such entity, whether by 19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 20 | outstanding shares, or (iii) beneficial ownership of such entity. 21 | 22 | "You" (or "Your") shall mean an individual or Legal Entity exercising 23 | permissions granted by this License. 24 | 25 | "Source" form shall mean the preferred form for making modifications, including 26 | but not limited to software source code, documentation source, and configuration 27 | files. 28 | 29 | "Object" form shall mean any form resulting from mechanical transformation or 30 | translation of a Source form, including but not limited to compiled object code, 31 | generated documentation, and conversions to other media types. 32 | 33 | "Work" shall mean the work of authorship, whether in Source or Object form, made 34 | available under the License, as indicated by a copyright notice that is included 35 | in or attached to the work (an example is provided in the Appendix below). 36 | 37 | "Derivative Works" shall mean any work, whether in Source or Object form, that 38 | is based on (or derived from) the Work and for which the editorial revisions, 39 | annotations, elaborations, or other modifications represent, as a whole, an 40 | original work of authorship. For the purposes of this License, Derivative Works 41 | shall not include works that remain separable from, or merely link (or bind by 42 | name) to the interfaces of, the Work and Derivative Works thereof. 43 | 44 | "Contribution" shall mean any work of authorship, including the original version 45 | of the Work and any modifications or additions to that Work or Derivative Works 46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 47 | by the copyright owner or by an individual or Legal Entity authorized to submit 48 | on behalf of the copyright owner. For the purposes of this definition, 49 | "submitted" means any form of electronic, verbal, or written communication sent 50 | to the Licensor or its representatives, including but not limited to 51 | communication on electronic mailing lists, source code control systems, and 52 | issue tracking systems that are managed by, or on behalf of, the Licensor for 53 | the purpose of discussing and improving the Work, but excluding communication 54 | that is conspicuously marked or otherwise designated in writing by the copyright 55 | owner as "Not a Contribution." 56 | 57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 58 | of whom a Contribution has been received by Licensor and subsequently 59 | incorporated within the Work. 60 | 61 | 2. Grant of Copyright License. 62 | 63 | Subject to the terms and conditions of this License, each Contributor hereby 64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 65 | irrevocable copyright license to reproduce, prepare Derivative Works of, 66 | publicly display, publicly perform, sublicense, and distribute the Work and such 67 | Derivative Works in Source or Object form. 68 | 69 | 3. Grant of Patent License. 70 | 71 | Subject to the terms and conditions of this License, each Contributor hereby 72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 73 | irrevocable (except as stated in this section) patent license to make, have 74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 75 | such license applies only to those patent claims licensable by such Contributor 76 | that are necessarily infringed by their Contribution(s) alone or by combination 77 | of their Contribution(s) with the Work to which such Contribution(s) was 78 | submitted. If You institute patent litigation against any entity (including a 79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 80 | Contribution incorporated within the Work constitutes direct or contributory 81 | patent infringement, then any patent licenses granted to You under this License 82 | for that Work shall terminate as of the date such litigation is filed. 83 | 84 | 4. Redistribution. 85 | 86 | You may reproduce and distribute copies of the Work or Derivative Works thereof 87 | in any medium, with or without modifications, and in Source or Object form, 88 | provided that You meet the following conditions: 89 | 90 | You must give any other recipients of the Work or Derivative Works a copy of 91 | this License; and 92 | You must cause any modified files to carry prominent notices stating that You 93 | changed the files; and 94 | You must retain, in the Source form of any Derivative Works that You distribute, 95 | all copyright, patent, trademark, and attribution notices from the Source form 96 | of the Work, excluding those notices that do not pertain to any part of the 97 | Derivative Works; and 98 | If the Work includes a "NOTICE" text file as part of its distribution, then any 99 | Derivative Works that You distribute must include a readable copy of the 100 | attribution notices contained within such NOTICE file, excluding those notices 101 | that do not pertain to any part of the Derivative Works, in at least one of the 102 | following places: within a NOTICE text file distributed as part of the 103 | Derivative Works; within the Source form or documentation, if provided along 104 | with the Derivative Works; or, within a display generated by the Derivative 105 | Works, if and wherever such third-party notices normally appear. The contents of 106 | the NOTICE file are for informational purposes only and do not modify the 107 | License. You may add Your own attribution notices within Derivative Works that 108 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 109 | provided that such additional attribution notices cannot be construed as 110 | modifying the License. 111 | You may add Your own copyright statement to Your modifications and may provide 112 | additional or different license terms and conditions for use, reproduction, or 113 | distribution of Your modifications, or for any such Derivative Works as a whole, 114 | provided Your use, reproduction, and distribution of the Work otherwise complies 115 | with the conditions stated in this License. 116 | 117 | 5. Submission of Contributions. 118 | 119 | Unless You explicitly state otherwise, any Contribution intentionally submitted 120 | for inclusion in the Work by You to the Licensor shall be under the terms and 121 | conditions of this License, without any additional terms or conditions. 122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 123 | any separate license agreement you may have executed with Licensor regarding 124 | such Contributions. 125 | 126 | 6. Trademarks. 127 | 128 | This License does not grant permission to use the trade names, trademarks, 129 | service marks, or product names of the Licensor, except as required for 130 | reasonable and customary use in describing the origin of the Work and 131 | reproducing the content of the NOTICE file. 132 | 133 | 7. Disclaimer of Warranty. 134 | 135 | Unless required by applicable law or agreed to in writing, Licensor provides the 136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, 137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 138 | including, without limitation, any warranties or conditions of TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 140 | solely responsible for determining the appropriateness of using or 141 | redistributing the Work and assume any risks associated with Your exercise of 142 | permissions under this License. 143 | 144 | 8. Limitation of Liability. 145 | 146 | In no event and under no legal theory, whether in tort (including negligence), 147 | contract, or otherwise, unless required by applicable law (such as deliberate 148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 149 | liable to You for damages, including any direct, indirect, special, incidental, 150 | or consequential damages of any character arising as a result of this License or 151 | out of the use or inability to use the Work (including but not limited to 152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 153 | any and all other commercial damages or losses), even if such Contributor has 154 | been advised of the possibility of such damages. 155 | 156 | 9. Accepting Warranty or Additional Liability. 157 | 158 | While redistributing the Work or Derivative Works thereof, You may choose to 159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 160 | other liability obligations and/or rights consistent with this License. However, 161 | in accepting such obligations, You may act only on Your own behalf and on Your 162 | sole responsibility, not on behalf of any other Contributor, and only if You 163 | agree to indemnify, defend, and hold each Contributor harmless for any liability 164 | incurred by, or claims asserted against, such Contributor by reason of your 165 | accepting any such warranty or additional liability. 166 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SRCS := $(wildcard source/*.c) 2 | LIB := libCmdline.a 3 | EXPORT_HEADERS := mbed-client-cli 4 | 5 | include ../exported_rules.mk 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mbed-client-cli 2 | 3 | This is the Command Line Library for a CLI application. It uses only ansi C features so it is portable and works in Mbed OS, linux and windows. 4 | 5 | ## Features 6 | 7 | Library provides features such: 8 | 9 | * Adding commands to the interpreter. 10 | * Deleting commands from the interpreter. 11 | * Executing commands. 12 | * Adding command aliases to the interpreter. 13 | * Searching command arguments. 14 | * implements several VT100/VT220 features, e.g. 15 | * move cursor left/right (or skipping word by pressing alt+left/right) 16 | * delete characters 17 | * CTRL+W to remove previous word 18 | * browse command history by pressing up/down 19 | * implements basic commands, e.g. 20 | * echo 21 | * help 22 | * (un)set 23 | * alias 24 | * history 25 | * true/false 26 | * clear 27 | 28 | ## API 29 | 30 | Command Line Library basic API's is described in the snipplet below: 31 | 32 | ```c++ 33 | // if thread safety for CLI terminal output is needed 34 | // configure output mutex wait cb before initialization so it's available immediately 35 | cmd_set_mutex_wait_func( (func)(void) ); 36 | // configure output mutex release cb before initialization so it's available immediately 37 | cmd_set_mutex_wait_func( (func)(void) ); 38 | // initialize cmdline with print function 39 | cmd_init( (func)(const char* fmt, va_list ap) ); 40 | // configure ready cb 41 | cmd_set_ready_cb( (func)(int retcode) ); 42 | // register command for library 43 | cmd_add( , (int func)(int argc, char *argv[]), , ); 44 | //execute some existing commands 45 | cmd_exe( ); 46 | ``` 47 | 48 | Full API is described [here](mbed-client-cli/ns_cmdline.h) 49 | 50 | ### Configuration 51 | 52 | Following defines can be used to configure defaults: 53 | 54 | |define|type|default value|description| 55 | |------|----|-------------|-----------| 56 | |`MBED_CONF_CMDLINE_USE_MINIMUM_SET`|bool|false|Use preconfigured minimum build. See more details from below| 57 | |`MBED_CONF_CMDLINE_ENABLE_ALIASES`|bool|true|Enable aliases| 58 | |`MBED_CONF_CMDLINE_USE_DUMMY_SET_ECHO_COMMANDS`|bool|true|Enable dummy `set` and `echo` commands| 59 | |`MBED_CONF_CMDLINE_INIT_AUTOMATION_MODE`|bool|false|Enable automation mode during initalize phase| 60 | |`MBED_CONF_CMDLINE_ENABLE_ESCAPE_HANDLING`|bool|true|Enable escape handling| 61 | |`MBED_CONF_CMDLINE_ENABLE_OPERATORS`|bool|true|Enable operators. E.g. `echo abc && echo def`| 62 | |`MBED_CONF_CMDLINE_ENABLE_INTERNAL_COMMANDS`|bool|true|Enable internal commands. E.g. `echo`| 63 | |`MBED_CONF_CMDLINE_ENABLE_INTERNAL_VARIABLES`|bool|true|Enable internal variables| 64 | |`MBED_CONF_CMDLINE_BOOT_MESSAGE`|C string|`ARM Ltd\r\n`|default boot message| 65 | |`MBED_CONF_CMDLINE_MAX_LINE_LENGTH`|int|2000|maximum command line length| 66 | |`MBED_CONF_CMDLINE_ARGS_MAX_COUNT`|int|30|maximum count of command arguments| 67 | |`MBED_CONF_CMDLINE_ENABLE_HISTORY`|bool|true|Enable command history. browsable using key up/down| 68 | |`MBED_CONF_CMDLINE_HISTORY_MAX_COUNT`|int|32|maximum history size| 69 | |`MBED_CONF_CMDLINE_INCLUDE_MAN`|bool|true|Include man pages| 70 | |`MBED_CONF_CMDLINE_ENABLE_INTERNAL_TRACES`|bool|false|Enable cli internal traces| 71 | |`MBED_CONF_CMDLINE_ENABLE_DEEP_INTERNAL_TRACES`|bool|false|Enable cli deep internal traces| 72 | 73 | 74 | #### Minimize footprint 75 | 76 | To reduce required flash and RAM usage there is pre-defined profile which can be enabled by using precompiler variable: 77 | 78 | `MBED_CONF_CMDLINE_USE_MINIMUM_SET=1` 79 | 80 | This switch off most of features and reduce buffer sizes. Below is whole configueration: 81 | 82 | |define|value| 83 | |------|----| 84 | |`MBED_CONF_CMDLINE_ENABLE_ALIASES`|false| 85 | |`MBED_CONF_CMDLINE_USE_DUMMY_SET_ECHO_COMMANDS`|false| 86 | |`MBED_CONF_CMDLINE_INIT_AUTOMATION_MODE`|false| 87 | |`MBED_CONF_CMDLINE_ENABLE_ESCAPE_HANDLING`|false| 88 | |`MBED_CONF_CMDLINE_ENABLE_OPERATORS`|false| 89 | |`MBED_CONF_CMDLINE_ENABLE_INTERNAL_COMMANDS`|false| 90 | |`MBED_CONF_CMDLINE_ENABLE_INTERNAL_VARIABLES`|false| 91 | |`MBED_CONF_CMDLINE_MAX_LINE_LENGTH`|100| 92 | |`MBED_CONF_CMDLINE_ARGS_MAX_COUNT`|10| 93 | |`MBED_CONF_CMDLINE_ENABLE_HISTORY`|false| 94 | |`MBED_CONF_CMDLINE_INCLUDE_MAN`|false| 95 | 96 | ### Pre defines return codes 97 | 98 | each command should return some of pre-defines return codes. 99 | These codes are reserved and used in test tools. 100 | 101 | |define|description| 102 | |------|-----------| 103 | |`CMDLINE_RETCODE_COMMAND_BUSY`|Command Busy| 104 | |`CMDLINE_RETCODE_EXCUTING_CONTINUE`|Execution continue in background| 105 | |`CMDLINE_RETCODE_SUCCESS`|Execution Success| 106 | |`CMDLINE_RETCODE_FAIL`|Execution Fail| 107 | |`CMDLINE_RETCODE_INVALID_PARAMETERS`|Command parameters was incorrect| 108 | |`CMDLINE_RETCODE_COMMAND_NOT_IMPLEMENTED`|Command not implemented| 109 | |`CMDLINE_RETCODE_COMMAND_CB_MISSING`|Command callback function missing| 110 | |`CMDLINE_RETCODE_COMMAND_NOT_FOUND`|Command not found| 111 | 112 | ## Tracing 113 | 114 | Command Line Library has trace messages, which are disabled by default. 115 | `MBED_CONF_CMDLINE_ENABLE_INTERNAL_TRACES` flag if defined, enables all the trace prints for debugging. 116 | 117 | ## Usage example 118 | 119 | See full examples [here](example). 120 | 121 | ### 122 | Adding new commands to the Command Line Library and executing the commands: 123 | 124 | ```c++ 125 | //example print function 126 | void myprint(const char* fmt, va_list ap){ vprintf(fmt, ap); } 127 | 128 | // ready cb, calls next command to be executed 129 | void cmd_ready_cb(int retcode) { cmd_next( retcode ); } 130 | 131 | // dummy command with some option 132 | int cmd_dummy(int argc, char *argv[]){ 133 | if( cmd_has_option(argc, argv, "o") ) { 134 | cmd_printf("This is o option"); 135 | } else { 136 | return CMDLINE_RETCODE_INVALID_PARAMETERS; 137 | } 138 | return CMDLINE_RETCODE_SUCCESS; 139 | } 140 | 141 | // timer cb (pseudo-timer-code) 142 | void timer_ready_cb(void) { 143 | cmd_ready(CMDLINE_RETCODE_SUCCESS); 144 | } 145 | 146 | // long command, that needs for example some events to complete the command execution 147 | int cmd_long(int argc, char *argv[] ) { 148 | timer_start( 5000, timer_ready_cb ); //pseudo timer code 149 | return CMDLINE_RETCODE_EXCUTING_CONTINUE; 150 | } 151 | void main(void) { 152 | cmd_init( &myprint ); // initialize cmdline with print function 153 | cmd_set_ready_cb( cmd_ready_cb ); // configure ready cb 154 | cmd_add("dummy", cmd_dummy, 0, 0); // add one dummy command 155 | cmd_add("long", cmd_long, 0, 0); // add one dummy command 156 | //execute dummy and long commands 157 | cmd_exe( "dummy;long" ); 158 | } 159 | ``` 160 | 161 | ## Thread safety 162 | The CLI library is not thread safe, but the CLI terminal output can be locked against other 163 | output streams, for example if both traces and CLI output are using serial out. 164 | 165 | Thread safety example for Mbed OS is available [here](example/Mbed-OS/main.cpp). 166 | 167 | 168 | ## Unit tests 169 | 170 | Unit tests are available in the `./test` folder. To run the unit tests in linux environment: 171 | ``` 172 | cd test 173 | ./run_unit_tests.sh 174 | ``` 175 | 176 | Unit tests xml output will be generated to folder: `test/build`. 177 | Code coverage report can be found from: `./test/build/html/coverage_index.html`. 178 | -------------------------------------------------------------------------------- /example/Mbed-OS/.mbed: -------------------------------------------------------------------------------- 1 | ROOT=. 2 | -------------------------------------------------------------------------------- /example/Mbed-OS/.mbedignore: -------------------------------------------------------------------------------- 1 | mbed-client-cli/example 2 | -------------------------------------------------------------------------------- /example/Mbed-OS/README.md: -------------------------------------------------------------------------------- 1 | # Mbed OS example application using mbed-client-cli library 2 | 3 | ## build 4 | 5 | ``` 6 | mbed deploy 7 | mbed compile -t GCC_ARM -m K64F 8 | ``` 9 | 10 | 11 | ## Usage 12 | 13 | When you flash a target with this application and open a terminal you should see the following traces: 14 | 15 | ``` 16 | [INFO][main] write 'help' and press ENTER 17 | > 18 | ... 19 | ``` 20 | 21 | 22 | ## build steps for workaround for issue #81 (PR #82) 23 | 24 | For the reason descript in [issue #81](https://github.com/ARMmbed/mbed-client-cli/issues/81), example folder moved out 25 | of repository. 26 | For cases like this, build steps are: 27 | 1. go to new example folder 28 | 2. check the revision and create `mbed-os.lib` and `mbed-client-cli.lib` lib files. 29 | 3. `mbed new .` or `mbed config root .` to make current directory import libs, then 30 | ``` 31 | mbed deploy 32 | mbed compile -t GCC_ARM -m K64F 33 | ``` 34 | -------------------------------------------------------------------------------- /example/Mbed-OS/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018-2019, Pelion and affiliates. 3 | * SPDX-License-Identifier: Apache-2.0 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 | * http://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 | #include 18 | #include 19 | 20 | #include "mbed.h" 21 | #include "mbed-trace/mbed_trace.h" 22 | #include "mbed-client-cli/ns_cmdline.h" 23 | 24 | #define TRACE_GROUP "main" 25 | 26 | static Mutex SerialOutMutex; 27 | void serial_out_mutex_wait() 28 | { 29 | SerialOutMutex.lock(); 30 | } 31 | void serial_out_mutex_release() 32 | { 33 | osStatus s = SerialOutMutex.unlock(); 34 | MBED_ASSERT(s == osOK); 35 | } 36 | 37 | // dummy command with some option 38 | static int cmd_dummy(int argc, char *argv[]) 39 | { 40 | if (cmd_has_option(argc, argv, "o")) { 41 | cmd_printf("This is o option\r\n"); 42 | } else { 43 | tr_debug("Try to write 'dummy -o' instead"); 44 | return CMDLINE_RETCODE_INVALID_PARAMETERS; 45 | } 46 | return CMDLINE_RETCODE_SUCCESS; 47 | } 48 | int main(void) 49 | { 50 | // Initialize trace library 51 | mbed_trace_init(); 52 | // Register callback used to lock serial out mutex 53 | mbed_trace_mutex_wait_function_set(serial_out_mutex_wait); 54 | // Register callback used to release serial out mutex 55 | mbed_trace_mutex_release_function_set(serial_out_mutex_release); 56 | 57 | // Initialize cmd library 58 | cmd_init(0); 59 | // Register callback used to lock serial out mutex 60 | cmd_mutex_wait_func(serial_out_mutex_wait); 61 | // Register callback used to release serial out mutex 62 | cmd_mutex_release_func(serial_out_mutex_release); 63 | // add dummy -command 64 | cmd_add("dummy", cmd_dummy, 65 | "dummy command", 66 | "This is dummy command, which does not do anything except\n" 67 | "print text when o -option is given."); // add one dummy command 68 | cmd_init_screen(); 69 | while (true) { 70 | int c = getchar(); 71 | if (c != EOF) { 72 | cmd_char_input(c); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /example/Mbed-OS/mbed_app.json: -------------------------------------------------------------------------------- 1 | { 2 | "macros": [ 3 | "MEM_ALLOC=malloc", 4 | "MEM_FREE=free" 5 | ], 6 | "target_overrides": { 7 | "*": { 8 | "platform.stdio-baud-rate": 115200, 9 | "platform.stdio-convert-newlines": true, 10 | "platform.stdio-buffered-serial": true, 11 | "mbed-trace.enable": 1 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /example/linux/Makefile: -------------------------------------------------------------------------------- 1 | override CFLAGS += -g -Wall -Wunused-function -Wundef -Wunused-parameter -Werror 2 | 3 | all: 4 | gcc main.c $(CFLAGS) -I ../../mbed-client-cli -lncurses -I ../../source/ns_list_internal ../../source/ns_cmdline.c -I. ../../source/ns_list_internal/ns_list.c -o cli 5 | -------------------------------------------------------------------------------- /example/linux/README.md: -------------------------------------------------------------------------------- 1 | # linux example application using mbed-client-cli library 2 | 3 | ## build 4 | 5 | ``` 6 | make 7 | ``` 8 | 9 | ## run 10 | 11 | ``` 12 | ./cli 13 | ``` 14 | type `help` and press enter to see available commands. To exit the application write `exit` and press enter 15 | -------------------------------------------------------------------------------- /example/linux/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018-2019, Pelion and affiliates. 3 | * SPDX-License-Identifier: Apache-2.0 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 | * http://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 | 18 | #include 19 | 20 | // to achieve more identical behaviour with mbed device you can active ncurses 21 | //#define EXAMPLE_USE_NCURSES 1 22 | 23 | #ifndef EXAMPLE_USE_NCURSES 24 | #define EXAMPLE_USE_NCURSES 0 25 | #endif 26 | #if EXAMPLE_USE_NCURSES == 1 27 | #include 28 | #endif 29 | #include "mbed-trace/mbed_trace.h" 30 | #include "ns_cmdline.h" 31 | 32 | #ifndef CTRL 33 | #define CTRL(c) ((c) & 037) 34 | #endif 35 | 36 | // dummy command with some option 37 | static int cmd_dummy(int argc, char *argv[]) 38 | { 39 | if (cmd_has_option(argc, argv, "o")) { 40 | cmd_printf("This is o option\r\n"); 41 | } else { 42 | return CMDLINE_RETCODE_INVALID_PARAMETERS; 43 | } 44 | return CMDLINE_RETCODE_SUCCESS; 45 | } 46 | volatile bool running = true; 47 | static int cmd_exit(int argc, char *argv[]) 48 | { 49 | (void)argc; 50 | (void)argv; 51 | running = false; 52 | return CMDLINE_RETCODE_SUCCESS; 53 | } 54 | 55 | 56 | int main(void) 57 | { 58 | #if EXAMPLE_USE_NCURSES == 1 59 | initscr(); // Start curses mode 60 | raw(); // Line buffering disabled 61 | noecho(); // Don't echo() while we do getch 62 | #endif 63 | // Initialize trace library 64 | mbed_trace_init(); 65 | cmd_init(0); // initialize cmdline with print function 66 | cmd_add("exit", cmd_exit, "exit shell", 0); 67 | cmd_add("dummy", cmd_dummy, 68 | "dummy command", 69 | "This is dummy command, which does not do anything except\n" 70 | "print text when o -option is given."); // add one dummy command 71 | 72 | cmd_init_screen(); 73 | while (running) { 74 | #if EXAMPLE_USE_NCURSES == 1 75 | int c = getch(); 76 | #else 77 | int c = getchar(); 78 | #endif 79 | switch (c) { 80 | case (CTRL('c')): 81 | running = false; 82 | break; 83 | case (EOF): 84 | break; 85 | default: 86 | cmd_char_input(c); 87 | } 88 | } 89 | #if EXAMPLE_USE_NCURSES == 1 90 | endwin(); 91 | #endif 92 | cmd_free(); 93 | return 0; 94 | } 95 | -------------------------------------------------------------------------------- /example/linux/mbed-trace/mbed_trace.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, Pelion and affiliates. 3 | * SPDX-License-Identifier: Apache-2.0 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 | * http://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 | #ifndef MBED_TRACE_H_ 18 | #define MBED_TRACE_H_ 19 | // this is very dummy version of trace library - just for example 20 | #define mbed_trace_init(x) 21 | #define trace(...) printf("\r" __VA_ARGS__);fputs("\r\n", stdout) 22 | #define tr_error trace 23 | #define tr_warn trace 24 | #define tr_debug trace 25 | #define tr_info trace 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /example/linux/memtest.sh: -------------------------------------------------------------------------------- 1 | printf "help && exit\n" | valgrind --leak-check=yes --error-exitcode=1 ./cli 2 | -------------------------------------------------------------------------------- /mbed-client-cli/ns_cmdline.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2021, Pelion and affiliates. 3 | * SPDX-License-Identifier: Apache-2.0 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 | * http://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 | 18 | /** 19 | * \file ns_cmdline.h 20 | * 21 | * Command line library - mbedOS shell 22 | * 23 | * Usage example: 24 | * 25 | * \code 26 | * //simple print function 27 | * void myprint(const char* fmt, va_list ap){ vprintf(fmt, ap); } 28 | * // simple ready cb, which call next command to be execute 29 | * void cmd_ready_cb(int retcode) { cmd_next( retcode ); } 30 | * 31 | * // dummy command with some option 32 | * int cmd_dummy(int argc, char *argv[]){ 33 | * if( cmd_has_option(argc, argv, "o") ) { 34 | * cmd_printf("This is o option"); 35 | * } else { 36 | * return CMDLINE_RETCODE_INVALID_PARAMETERS; 37 | * } 38 | * return CMDLINE_RETCODE_SUCCESS; 39 | *} 40 | * // timer cb ( pseudo-timer-code ) 41 | * void timer_ready_cb(void) { 42 | * cmd_ready(CMDLINE_RETCODE_SUCCESS); 43 | * } 44 | * // long command, which need e.g. some events to finalize command execution 45 | * int cmd_long(int argc, char *argv[] ) { 46 | timer_start( 5000, timer_ready_cb ); 47 | * return CMDLINE_RETCODE_EXCUTING_CONTINUE; 48 | * } 49 | * void main(void) { 50 | * cmd_init( &myprint ); // initialize cmdline with print function 51 | * cmd_set_ready_cb( cmd_ready_cb ); // configure ready cb 52 | * cmd_add("dummy", cmd_dummy, 0, 0); // add one dummy command 53 | * cmd_add("long", cmd_long, 0, 0); // add one dummy command 54 | * //execute dummy and long commands 55 | * cmd_exe( "dymmy;long" ); 56 | * } 57 | * \endcode 58 | */ 59 | #ifndef _CMDLINE_H_ 60 | #define _CMDLINE_H_ 61 | 62 | #ifdef __cplusplus 63 | extern "C" { 64 | #endif 65 | 66 | #include 67 | #include 68 | #include 69 | #include 70 | 71 | #define CMDLINE_RETCODE_COMMAND_BUSY 2 //!< Command Busy 72 | #define CMDLINE_RETCODE_EXCUTING_CONTINUE 1 //!< Execution continue in background 73 | #define CMDLINE_RETCODE_SUCCESS 0 //!< Execution Success 74 | #define CMDLINE_RETCODE_FAIL -1 //!< Execution Fail 75 | #define CMDLINE_RETCODE_INVALID_PARAMETERS -2 //!< Command parameters was incorrect 76 | #define CMDLINE_RETCODE_COMMAND_NOT_IMPLEMENTED -3 //!< Command not implemented 77 | #define CMDLINE_RETCODE_COMMAND_CB_MISSING -4 //!< Command callback function missing 78 | #define CMDLINE_RETCODE_COMMAND_NOT_FOUND -5 //!< Command not found 79 | 80 | /** 81 | * typedef for print functions 82 | */ 83 | typedef void (cmd_print_t)(const char *, va_list); 84 | 85 | /** 86 | * Initialize cmdline class. 87 | * This is command line editor without any commands. Application 88 | * needs to add commands that should be enabled. 89 | * usage e.g. 90 | * \code 91 | cmd_init( &default_cmd_response_out ); 92 | * \endcode 93 | * \param outf console printing function (like vprintf) 94 | */ 95 | void cmd_init(cmd_print_t *outf); 96 | 97 | /** Command ready function for __special__ cases. 98 | * This need to be call if command implementation return CMDLINE_RETCODE_EXECUTING_CONTINUE 99 | * because there is some background stuff ongoing before command is finally completed. 100 | * Normally there is some event, which call cmd_ready(). 101 | * \param retcode return code for command 102 | */ 103 | void cmd_ready(int retcode); 104 | 105 | /** typedef for ready cb function */ 106 | typedef void (cmd_ready_cb_f)(int); 107 | 108 | /** 109 | * Configure cb which will be called after commands are executed 110 | * or cmd_ready is called 111 | * \param cb callback function for command ready 112 | */ 113 | void cmd_set_ready_cb(cmd_ready_cb_f *cb); 114 | 115 | /** 116 | * execute next command if any 117 | * \param retcode last command return value 118 | */ 119 | void cmd_next(int retcode); 120 | 121 | /** Free cmd class */ 122 | void cmd_free(void); 123 | 124 | /** Reset cmdline to default values 125 | * detach external commands, delete all variables and aliases 126 | */ 127 | void cmd_reset(void); 128 | 129 | /** Configure command history size (default 32) 130 | * \param max maximum history size 131 | * max > 0 -> configure new value 132 | * max = 0 -> just return current value 133 | * \return current history max-size 134 | */ 135 | uint8_t cmd_history_size(uint8_t max); 136 | 137 | /** command line print function 138 | * This function should be used when user want to print something to the console 139 | * \param fmt console print function (like printf) 140 | */ 141 | #if defined(__GNUC__) 142 | void cmd_printf(const char *fmt, ...) __attribute__((__format__(__printf__, 1, 2))); 143 | #else 144 | void cmd_printf(const char *fmt, ...); 145 | #endif 146 | 147 | /** command line print function 148 | * This function should be used when user want to print something to the console with vprintf functionality 149 | * \param fmt The format string is a character string, beginning and ending in its initial shift state, if any. The format string is composed of zero or more directives. 150 | * \param ap list of parameters needed by format string. This must correspond properly with the conversion specifier. 151 | */ 152 | #if defined(__GNUC__) 153 | void cmd_vprintf(const char *fmt, va_list ap) __attribute__((__format__(__printf__, 1, 0))); 154 | #else 155 | void cmd_vprintf(const char *fmt, va_list ap); 156 | #endif 157 | 158 | /** Reconfigure default cmdline out function (cmd_printf) 159 | * \param outf select console print function 160 | */ 161 | void cmd_out_func(cmd_print_t *outf); 162 | 163 | /** Configure function, which will be called when Ctrl+A is pressed 164 | * \param sohf control function which called every time when user input control keys 165 | */ 166 | void cmd_ctrl_func(void (*sohf)(uint8_t c)); 167 | 168 | /** 169 | * Configure mutex wait function 170 | * By default, cmd_printf calls may not be thread safe, depending on the implementation of the used output. 171 | * This can be used to set a callback function that will be called before each cmd_printf call. 172 | * The specific implementation is up to the application developer, but simple mutex locking is assumed. 173 | */ 174 | void cmd_mutex_wait_func(void (*mutex_wait_f)(void)); 175 | 176 | /** 177 | * Configure mutex wait function 178 | * By default, cmd_printf calls may not be thread safe, depending on the implementation of the used output. 179 | * This can be used to set a callback function that will be called after each cmd_printf call. 180 | * The specific implementation is up to the application developer, but simple mutex locking is assumed. 181 | */ 182 | void cmd_mutex_release_func(void (*mutex_release_f)(void)); 183 | 184 | /** 185 | * Retrieve output mutex lock 186 | * This can be used to retrieve the output mutex when multiple cmd_printf/cmd_vprintf calls must be 187 | * guaranteed to be grouped together in a thread safe manner. Must be released by a following call to 188 | * cmd_mutex_unlock() 189 | * For example: 190 | * * \code 191 | * cmd_mutex_lock(); 192 | for (i = 0; i < 10; i++) { 193 | cmd_printf("%02x ", i); 194 | } 195 | // without locking a print from another thread could happen here 196 | cmd_printf("\r\n); 197 | cmd_mutex_unlock(); 198 | * \endcode 199 | * Exact behaviour depends on the implementation of the configured mutex, 200 | * but counting mutexes are required. 201 | */ 202 | void cmd_mutex_lock(void); 203 | 204 | /** 205 | * Release output mutex lock 206 | * This can be used to release the output mutex once it has been retrieved with cmd_mutex_lock() 207 | * Exact behaviour depends on the implementation of the configured mutex, 208 | * but counting mutexes are required. 209 | */ 210 | void cmd_mutex_unlock(void); 211 | 212 | /** Refresh output */ 213 | void cmd_output(void); 214 | 215 | /** default cmd response function, use stdout 216 | * \param fmt The format string is a character string, beginning and ending in its initial shift state, if any. The format string is composed of zero or more directives. 217 | * \param ap list of parameters needed by format string. This must correspond properly with the conversion specifier. 218 | */ 219 | void default_cmd_response_out(const char *fmt, va_list ap); 220 | 221 | /** Initialize screen */ 222 | void cmd_init_screen(void); 223 | 224 | /** Get echo state 225 | * \return true if echo is on otherwise false 226 | */ 227 | bool cmd_echo_state(void); 228 | 229 | /** Echo off */ 230 | void cmd_echo_off(void); 231 | 232 | /** Echo on */ 233 | void cmd_echo_on(void); 234 | 235 | /** Enter character to console. 236 | * insert key pressess to cmdline called from main loop of application 237 | * \param u_data char to be added to console 238 | */ 239 | void cmd_char_input(int16_t u_data); 240 | 241 | /* 242 | * Set the passthrough mode callback function. In passthrough mode normal command input handling is skipped and any 243 | * received characters are passed to the passthrough callback function. Setting this to null will disable passthrough mode. 244 | * \param passthrough_fnc The passthrough callback function 245 | */ 246 | typedef void (*input_passthrough_func_t)(uint8_t c); 247 | void cmd_input_passthrough_func(input_passthrough_func_t passthrough_fnc); 248 | 249 | /* Methods used for adding and handling of commands and aliases 250 | */ 251 | 252 | /** Callback called when your command is run. 253 | * \param argc argc is the count of arguments given in argv pointer list. value begins from 1 and this means that the 0 item in list argv is a string to name of command. 254 | * \param argv argv is list of arguments. List size is given in argc parameter. Value in argv[0] is string to name of command. 255 | */ 256 | typedef int (cmd_run_cb)(int argc, char *argv[]); 257 | 258 | /** Add command to intepreter 259 | * \param name command string 260 | * \param callback This function is called when command line start executing 261 | * \param info Command short description which is visible in help command, or null if not in use 262 | * \param man Help page for this command. This is shown when executing command with invalid parameters or command with --help parameter. Can be null if not in use. 263 | */ 264 | void cmd_add(const char *name, cmd_run_cb *callback, const char *info, const char *man); 265 | 266 | /** delete command from intepreter 267 | * \param name command to be delete 268 | */ 269 | void cmd_delete(const char *name); 270 | 271 | /** Command executer. 272 | * Command executer, which split&push command(s) to the buffer and 273 | * start executing commands in cmd tasklet. 274 | * if not, execute command directly. 275 | * If command implementation returns CMDLINE_RETCODE_EXCUTING_CONTINUE, 276 | * executor will wait for cmd_ready() before continue to next command. 277 | * \param str command string, e.g. "help" 278 | */ 279 | void cmd_exe(char *str); 280 | 281 | /** Add alias to interpreter. 282 | * Aliases are replaced with values before executing a command. All aliases must be started from beginning of line. 283 | * null or empty value deletes alias. 284 | * \code 285 | cmd_alias_add("print", "echo"); 286 | cmd_exe("print \"hello world!\""); // this is now same as "echo \"hello world!\"" . 287 | * \endcode 288 | * \param alias alias name 289 | * \param value value for alias. Values can be any visible ASCII -characters. 290 | */ 291 | void cmd_alias_add(const char *alias, const char *value); 292 | 293 | /** Add Variable to interpreter. 294 | * Variables are replaced with values before executing a command. 295 | * To use variables from cli, use dollar ($) -character so that interpreter knows user want to use variable in that place. 296 | * null or empty value deletes variable. 297 | * \code 298 | cmd_variable_add("world", "hello world!"); 299 | cmd_exe("echo $world"); // this is now same as echo "hello world!" . 300 | * \endcode 301 | * \param variable Variable name, which will be replaced in interpreter. 302 | * \param value Value for variable. Values can contains white spaces and '"' or '"' characters. 303 | */ 304 | void cmd_variable_add(char *variable, char *value); 305 | 306 | /** 307 | * Add integer variable to interpreter. 308 | * Variables are replaced with values before executing a command. 309 | * \code 310 | cmd_variable_add_int("world", 2); 311 | cmd_exe("echo $world"); // this is now same as 'echo 2' . 312 | * \endcode 313 | * \param variable Variable name, which will be replaced in interpreter. 314 | * \param value Value for variable 315 | 316 | */ 317 | void cmd_variable_add_int(char *variable, int value); 318 | 319 | /** 320 | * Request screen size from host 321 | * Response are stored to variables: 322 | * COLUMNS and LINES - as integer values. 323 | * Note: Require terminal that handle request codes, like screen. 324 | */ 325 | void cmd_request_screen_size(void); 326 | 327 | /** find command parameter index by key. 328 | * e.g. 329 | * \code 330 | int main(void){ 331 | //..init cmd.. 332 | //.. 333 | cmd_exe("mycmd enable") 334 | } 335 | int mycmd_command(int argc, char *argv[]) { 336 | bool found = cmd_parameter_index( argc, argv, "enable" ) > 0; 337 | } 338 | * \endcode 339 | * \param argc argc is the count of arguments given in argv pointer list. value begins from 1 and this means that the 0 item in list argv is a string to name of command. 340 | * \param argv is list of arguments. List size is given in argc parameter. Value in argv[0] is string to name of command. 341 | * \param key option key, which index you want to find out. 342 | * \return index where parameter was or -1 when not found 343 | */ 344 | int cmd_parameter_index(int argc, char *argv[], const char *key); 345 | 346 | /** check if command option is present. 347 | * e.g. cmd: "mycmd -c" 348 | * \code 349 | * bool on = cmd_has_option( argc, argv, "p" ); 350 | * \endcode 351 | * \param argc argc is the count of arguments given in argv pointer list. value begins from 1 and this means that the 0 item in list argv is a string to name of command. 352 | * \param argv is list of arguments. List size is given in argc parameter. Value in argv[0] is string to name of command. 353 | * \param key option key to be find 354 | * \return true if option found otherwise false 355 | */ 356 | bool cmd_has_option(int argc, char *argv[], const char *key); 357 | 358 | /** find command parameter by key. 359 | * if exists, return true, otherwise false. 360 | * e.g. cmd: "mycmd enable 1" 361 | * \code 362 | int mycmd_command(int argc, char *argv[]) { 363 | bool value; 364 | bool found = cmd_parameter_bool( argc, argv, "mykey", &value ); 365 | if( found ) return CMDLINE_RETCODE_SUCCESS; 366 | else return CMDLINE_RETCODE_FAIL; 367 | } 368 | * \endcode 369 | * \param argc argc is the count of arguments given in argv pointer list. value begins from 1 and this means that the 0 item in list argv is a string to name of command. 370 | * \param argv is list of arguments. List size is given in argc parameter. Value in argv[0] is string to name of command. 371 | * \param key parameter key to be find 372 | * \param value parameter value to be fetch, if key not found value are untouched. "1" and "on" and "true" and "enable" and "allow" are True -value, all others false. 373 | * \return true if parameter key and value found otherwise false 374 | */ 375 | bool cmd_parameter_bool(int argc, char *argv[], const char *key, bool *value); 376 | 377 | /** find command parameter by key and return value (next parameter). 378 | * if exists, return parameter pointer, otherwise null. 379 | * e.g. cmd: "mycmd mykey myvalue" 380 | * \code 381 | int mycmd_command(int argc, char *argv[]) { 382 | char *value; 383 | bool found = cmd_parameter_val( argc, argv, "mykey", &value ); 384 | if( found ) return CMDLINE_RETCODE_SUCCESS; 385 | else return CMDLINE_RETCODE_FAIL; 386 | } 387 | * \endcode 388 | * \param argc argc is the count of arguments given in argv pointer list. value begins from 1 and this means that the 0 item in list argv is a string to name of command. 389 | * \param argv is list of arguments. List size is given in argc parameter. Value in argv[0] is string to name of command. 390 | * \param key parameter key to be find 391 | * \param value pointer to pointer, which will point to cli input data when key and value found. if key or value not found this parameter are untouched. 392 | * \return true if parameter key and value found otherwise false 393 | */ 394 | bool cmd_parameter_val(int argc, char *argv[], const char *key, char **value); 395 | 396 | /** find command parameter by key and return value (next parameter) in integer. Only whitespaces are allowed in addition to the float to be read. 397 | * e.g. cmd: "mycmd mykey myvalue" 398 | * \code 399 | int32_t value; 400 | cmd_parameter_int( argc, argv, "key", &value ); 401 | * \endcode 402 | * \param argc argc is the count of arguments given in argv pointer list. value begins from 1 and this means that the item 0 in the list argv is a string to name of command. 403 | * \param argv is list of arguments. List size is given in argc parameter. Value in argv[0] is string to name of command. 404 | * \param key parameter key to be found 405 | * \param value A pointer to a variable where to write the converted number. If value cannot be converted, it is not touched. 406 | * \return true if parameter key and an integer is found, otherwise return false 407 | */ 408 | bool cmd_parameter_int(int argc, char *argv[], const char *key, int32_t *value); 409 | 410 | /** find command parameter by key and return value (next parameter) in float. Only whitespaces are allowed in addition to the float to be read. 411 | * e.g. cmd: "mycmd mykey myvalue" 412 | * \code 413 | float value; 414 | cmd_parameter_float( argc, argv, "key", &value ); 415 | * \endcode 416 | * \param argc argc is the count of arguments given in argv pointer list. values begin from 1 and this means that the item 0 in the list argv is a string to name of command. 417 | * \param argv is list of arguments. List size is given in argc parameter. Value in argv[0] is string to name of command. 418 | * \param key parameter key to be found 419 | * \param value A pointer to a variable where to write the converted number. If value cannot be converted, it is not touched. 420 | * \return true if parameter key and a float found, otherwise return false 421 | */ 422 | bool cmd_parameter_float(int argc, char *argv[], const char *key, float *value); 423 | 424 | /** Get last command line parameter as string. 425 | * e.g. 426 | * cmd: "mycmd hello world" 427 | * cmd_parameter_last -> "world" 428 | * cmd: "mycmd" 429 | * cmd_parameter_last() -> NULL 430 | * \code 431 | cmd_parameter_last(argc, argv) 432 | * \endcode 433 | * \param argc argc is the count of arguments given in argv pointer list. value begins from 1 and this means that the 0 item in list argv is a string to name of command. 434 | * \param argv is list of arguments. List size is given in argc parameter. Value in argv[0] is string to name of command. 435 | * \return pointer to last parameter or NULL when there is no any parameters. 436 | */ 437 | char *cmd_parameter_last(int argc, char *argv[]); 438 | 439 | /** find command parameter by key and return value (next parameter) in int64. 440 | * e.g. cmd: "mycmd mykey myvalue" 441 | * \code 442 | uint32_t i; 443 | cmd_parameter_timestamp( argc, argv, "mykey", &i ); 444 | * \endcode 445 | * 446 | * Supports following formats: 447 | * number -> direct conversion 448 | * 11:22:33:44:55:66:77:88 -> converts to number 449 | * seconds,tics -> converts thread type timestamp to int64 450 | * 451 | * \param argc argc is the count of arguments given in argv pointer list. value begins from 1 and this means that the 0 item in list argv is a string to name of command. 452 | * \param argv is list of arguments. List size is given in argc parameter. Value in argv[0] is string to name of command. 453 | * \param key parameter key to be find 454 | * \param value parameter value to be fetch, if key not found value are untouched. 455 | * \return true if parameter key and value found otherwise false 456 | */ 457 | bool cmd_parameter_timestamp(int argc, char *argv[], const char *key, int64_t *value); 458 | 459 | #ifdef __cplusplus 460 | } 461 | #endif 462 | #endif /*_CMDLINE_H_*/ 463 | -------------------------------------------------------------------------------- /mbed_lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cmdline", 3 | "config": { 4 | "use_minimum_set": { 5 | "help": "use pre configured minimum set which reduce most of features but allows to run icetea tests. Defaults to 'false' when not set", 6 | "accepted_values": [true, false, null], 7 | "value": null 8 | }, 9 | "enable_aliases": { 10 | "help": "enable alias feature. Defaults to 'true' when not set", 11 | "accepted_values": [true, false, null], 12 | "value": null 13 | }, 14 | "enable_escape_handling": { 15 | "help": "enable escape handling. Defaults to 'true' when not set", 16 | "accepted_values": [true, false, null], 17 | "value": null 18 | }, 19 | "enable_operators": { 20 | "help": "enable operators. Defaults to 'true' when not set", 21 | "accepted_values": [true, false, null], 22 | "value": null 23 | }, 24 | "enable_internal_commands": { 25 | "help": "enable internal commands, like echo, set, unset. Defaults to 'true' when not set", 26 | "accepted_values": [true, false, null], 27 | "value": null 28 | }, 29 | "use_dummy_set_and_echo": { 30 | "help": "when enable_internal_commands is disabled this can be used to activate dummy set and echo commands that is needed for icetea tests. Defaults to 'false' when not set", 31 | "accepted_values": [true, false, null], 32 | "value": null 33 | }, 34 | "enable_internal_variables": { 35 | "help": "enable internal variables. Defaults to 'true' when not set", 36 | "accepted_values": [true, false, null], 37 | "value": null 38 | }, 39 | "include_man": { 40 | "help": "include man pages. Defaults to 'true' when not set", 41 | "accepted_values": [true, false, null], 42 | "value": null 43 | }, 44 | "max_line_length": { 45 | "help": "maximum input line length. Defaults to 2000 when not set", 46 | "value": null 47 | }, 48 | "args_max_count": { 49 | "help": "maximum arguments count. Defaults to 30 when not set", 50 | "value": null 51 | }, 52 | "enable_history": { 53 | "help": "enable command history. Defaults to 'true' when not set", 54 | "accepted_values": [true, false, null], 55 | "value": null 56 | }, 57 | "history_max_count": { 58 | "help": "maximum history count. Defaults to 32 when not set", 59 | "value": null 60 | }, 61 | "boot_message": { 62 | "help": "Set custom boot message when calling cmd_init() or cmd_init_screen()", 63 | "value": null 64 | }, 65 | "enable_internal_traces": { 66 | "help": "Enable internal traces. Defaults to 'false' when not set", 67 | "accepted_values": [true, false, null], 68 | "value": null 69 | }, 70 | "enable_deep_internal_traces": { 71 | "help": "Enable more verbose internal traces. Defaults to 'false' when not set", 72 | "accepted_values": [true, false, null], 73 | "value": null 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /source/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if(DEFINED TARGET_LIKE_X86_LINUX_NATIVE) 2 | add_library( mbed-client-cli 3 | ns_cmdline.c 4 | ns_list_internal/ns_list.c 5 | ) 6 | add_definitions("-g -O0 -fprofile-arcs -ftest-coverage") 7 | target_link_libraries(mbed-client-cli gcov) 8 | elseif(DEFINED TARGET_LIKE_X86_OSX_NATIVE) 9 | add_library( mbed-client-cli 10 | ns_cmdline.c 11 | ns_list_internal/ns_list.c 12 | ) 13 | add_definitions("-g -O0") 14 | target_link_libraries(mbed-client-cli) 15 | else() 16 | add_library( mbed-client-cli 17 | ns_cmdline.c 18 | ns_list_internal/ns_list.c 19 | ) 20 | target_link_libraries(mbed-client-cli) 21 | endif() 22 | -------------------------------------------------------------------------------- /source/ns_cmdline.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2021, Pelion and affiliates. 3 | * SPDX-License-Identifier: Apache-2.0 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 | * http://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 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #if defined(_WIN32) || defined(__unix__) || defined(__unix) || defined(unix) || defined(MBED_CONF_RTOS_PRESENT) || defined(__APPLE__) 25 | #include //malloc 26 | #ifndef MEM_ALLOC 27 | #define MEM_ALLOC malloc 28 | #endif 29 | #ifndef MEM_FREE 30 | #define MEM_FREE free 31 | #endif 32 | #else 33 | #include "nsdynmemLIB.h" 34 | #ifndef MEM_ALLOC 35 | #define MEM_ALLOC ns_dyn_mem_temporary_alloc 36 | #endif 37 | #ifndef MEM_FREE 38 | #define MEM_FREE ns_dyn_mem_free 39 | #endif 40 | #endif 41 | 42 | 43 | // available configurations 44 | //#define MBED_CONF_CMDLINE_USE_MINIMUM_SET 0 45 | //#define MBED_CONF_CMDLINE_ENABLE_ALIASES 0 46 | //#define MBED_CONF_CMDLINE_USE_DUMMY_SET_ECHO_COMMANDS 1 47 | //#define MBED_CONF_CMDLINE_INIT_AUTOMATION_MODE 0 48 | //#define MBED_CONF_CMDLINE_ENABLE_HISTORY 0 49 | //#define MBED_CONF_CMDLINE_ENABLE_ESCAPE_HANDLING 0 50 | //#define MBED_CONF_CMDLINE_ENABLE_OPERATORS 0 51 | //#define MBED_CONF_CMDLINE_ENABLE_INTERNAL_COMMANDS 0 52 | //#define MBED_CONF_CMDLINE_ENABLE_INTERNAL_VARIABLES 0 53 | //#define MBED_CONF_CMDLINE_INCLUDE_MAN 0 54 | //#define MBED_CONF_CMDLINE_MAX_LINE_LENGTH 100 55 | //#define MBED_CONF_CMDLINE_ARGS_MAX_COUNT 2 56 | //#define MBED_CONF_CMDLINE_HISTORY_MAX_COUNT 1 57 | //#define MBED_CONF_CMDLINE_BOOT_MESSAGE "hello there\n" 58 | //#define MBED_CONF_CMDLINE_ENABLE_INTERNAL_TRACES 1 59 | //#define MBED_CONF_CMDLINE_ENABLE_DEEP_INTERNAL_TRACES 1 60 | 61 | 62 | 63 | // ------------------------ 64 | // backward compatible 65 | #if defined(MBED_CMDLINE_MAX_LINE_LENGTH) && !defined(MBED_CONF_CMDLINE_MAX_LINE_LENGTH) 66 | #define MBED_CONF_CMDLINE_MAX_LINE_LENGTH MBED_CMDLINE_MAX_LINE_LENGTH 67 | #endif 68 | #if defined(MBED_CMDLINE_ARGUMENTS_MAX_COUNT) && !defined(MBED_CONF_CMDLINE_ARGS_MAX_COUNT) 69 | #define MBED_CONF_CMDLINE_ARGS_MAX_COUNT MBED_CMDLINE_ARGUMENTS_MAX_COUNT 70 | #endif 71 | #if defined(MBED_CMDLINE_HISTORY_MAX_COUNT) && !defined(MBED_CONF_CMDLINE_HISTORY_MAX_COUNT) 72 | #define MBED_CONF_CMDLINE_HISTORY_MAX_COUNT MBED_CMDLINE_HISTORY_MAX_COUNT 73 | #endif 74 | #if defined(MBED_CMDLINE_INCLUDE_MAN) && !defined(MBED_CONF_CMDLINE_INCLUDE_MAN) 75 | #define MBED_CONF_CMDLINE_INCLUDE_MAN MBED_CMDLINE_INCLUDE_MAN 76 | #endif 77 | #if defined(MBED_CLIENT_CLI_TRACE_ENABLE) && !defined(MBED_CONF_CMDLINE_ENABLE_INTERNAL_TRACES) 78 | #define MBED_CONF_CMDLINE_ENABLE_INTERNAL_TRACES MBED_CLIENT_CLI_TRACE_ENABLE 79 | #endif 80 | #if defined(MBED_CMDLINE_BOOT_MESSAGE) && !defined(MBED_CONF_CMDLINE_BOOT_MESSAGE) 81 | #define MBED_CONF_CMDLINE_BOOT_MESSAGE MBED_CMDLINE_BOOT_MESSAGE 82 | #endif 83 | // ------------------------ 84 | 85 | #ifndef MBED_CONF_CMDLINE_USE_MINIMUM_SET 86 | #define MBED_CONF_CMDLINE_USE_MINIMUM_SET 0 87 | #endif 88 | 89 | #if MBED_CONF_CMDLINE_USE_MINIMUM_SET == 1 90 | // configure default minimum values, each value can be overwrite by compiling time 91 | #ifndef MBED_CONF_CMDLINE_ENABLE_ALIASES 92 | #define MBED_CONF_CMDLINE_ENABLE_ALIASES 0 93 | #endif 94 | #ifndef MBED_CONF_CMDLINE_USE_DUMMY_SET_ECHO_COMMANDS 95 | #define MBED_CONF_CMDLINE_USE_DUMMY_SET_ECHO_COMMANDS 1 96 | #endif 97 | #ifndef MBED_CONF_CMDLINE_INIT_AUTOMATION_MODE 98 | #define MBED_CONF_CMDLINE_INIT_AUTOMATION_MODE 1 99 | #endif 100 | #ifndef MBED_CONF_CMDLINE_ENABLE_HISTORY 101 | #define MBED_CONF_CMDLINE_ENABLE_HISTORY 0 102 | #endif 103 | #ifndef MBED_CONF_CMDLINE_ENABLE_ESCAPE_HANDLING 104 | #define MBED_CONF_CMDLINE_ENABLE_ESCAPE_HANDLING 0 105 | #endif 106 | #ifndef MBED_CONF_CMDLINE_ENABLE_OPERATORS 107 | #define MBED_CONF_CMDLINE_ENABLE_OPERATORS 0 108 | #endif 109 | #ifndef MBED_CONF_CMDLINE_ENABLE_INTERNAL_COMMANDS 110 | #define MBED_CONF_CMDLINE_ENABLE_INTERNAL_COMMANDS 0 111 | #endif 112 | #ifndef MBED_CONF_CMDLINE_ENABLE_INTERNAL_VARIABLES 113 | #define MBED_CONF_CMDLINE_ENABLE_INTERNAL_VARIABLES 0 114 | #endif 115 | #ifndef MBED_CONF_CMDLINE_INCLUDE_MAN 116 | #define MBED_CONF_CMDLINE_INCLUDE_MAN 0 117 | #endif 118 | #ifndef MBED_CONF_CMDLINE_MAX_LINE_LENGTH 119 | #define MBED_CONF_CMDLINE_MAX_LINE_LENGTH 100 120 | #endif 121 | #ifndef MBED_CONF_CMDLINE_ARGS_MAX_COUNT 122 | #define MBED_CONF_CMDLINE_ARGS_MAX_COUNT 10 123 | #endif 124 | #ifndef MBED_CONF_CMDLINE_HISTORY_MAX_COUNT 125 | #define MBED_CONF_CMDLINE_HISTORY_MAX_COUNT 0 126 | #endif 127 | // end of default configurations 128 | #endif 129 | 130 | #if defined(__GNUC__) || defined(__clang__) || defined(__CC_ARM) 131 | #define CMDLINE_UNUSED __attribute__((unused)) 132 | #else 133 | #define CMDLINE_UNUSED 134 | #endif 135 | 136 | #include "ns_list.h" 137 | #include "ns_cmdline.h" 138 | #include "mbed-trace/mbed_trace.h" 139 | 140 | //#define TRACE_PRINTF 141 | 142 | #ifdef TRACE_PRINTF 143 | #undef tr_debug 144 | #define tr_debug(...) printf( __VA_ARGS__);printf("\r\n") 145 | #endif 146 | 147 | // #define MBED_CONF_CMDLINE_ENABLE_INTERNAL_TRACES 1 148 | // MBED_CONF_CMDLINE_ENABLE_INTERNAL_TRACES is to enable the traces for debugging, 149 | // By default all debug traces are disabled. 150 | #if !defined(MBED_CONF_CMDLINE_ENABLE_INTERNAL_TRACES) || MBED_CONF_CMDLINE_ENABLE_INTERNAL_TRACES == 0 151 | #undef tr_error 152 | #define tr_error(...) 153 | #undef tr_warn 154 | #define tr_warn(...) 155 | #undef tr_debug 156 | #define tr_debug(...) 157 | #undef tr_info 158 | #define tr_info(...) 159 | #endif 160 | 161 | //#define MBED_CONF_CMDLINE_ENABLE_DEEP_INTERNAL_TRACES 162 | #ifdef MBED_CONF_CMDLINE_ENABLE_DEEP_INTERNAL_TRACES 163 | #define tr_deep tr_debug 164 | #else 165 | #define tr_deep(...) 166 | #endif 167 | 168 | #define TRACE_GROUP "cmdL" 169 | 170 | #ifndef MBED_CONF_CMDLINE_BOOT_MESSAGE 171 | #define MBED_CONF_CMDLINE_BOOT_MESSAGE "ARM Ltd\r\n" 172 | #endif 173 | #define ESCAPE(x) "\x1b" x 174 | #define CR_S "\r" 175 | #define LF_S "\n" 176 | #define CLEAR_ENTIRE_LINE ESCAPE("[2K") 177 | #define CLEAR_ENTIRE_SCREEN ESCAPE("[2J") 178 | #define ENABLE_AUTO_WRAP_MODE ESCAPE("[7h") 179 | #define MOVE_CURSOR_LEFT_N_CHAR ESCAPE("[%dD") 180 | 181 | #define SET_TOP_AND_BOTTOM_LINES ESCAPE("[;r") 182 | #define MOVE_CURSOR_TO_BOTTOM_RIGHT ESCAPE("[999;999H") 183 | #define STORE_CURSOR_POS ESCAPE("7") 184 | #define RESTORE_CURSOR_POS ESCAPE("8") 185 | #define GET_CURSOR_POSITION ESCAPE("[6n") 186 | 187 | #define REQUEST_SCREEN_SIZE STORE_CURSOR_POS SET_TOP_AND_BOTTOM_LINES MOVE_CURSOR_TO_BOTTOM_RIGHT GET_CURSOR_POSITION RESTORE_CURSOR_POS 188 | 189 | /*ASCII defines*/ 190 | #define ESC 0x1B 191 | #define DEL 0x7F 192 | #define BS 0x08 193 | #define ETX 0x03 194 | #define ETB 0x17 195 | #define TAB 0x09 196 | #define CAN 0x18 197 | 198 | #define DEFAULT_RETFMT "retcode: %i\r\n" 199 | #define DEFAULT_PROMPT "/>" 200 | #define VAR_PROMPT "PS1" 201 | #define VAR_RETFMT "RETFMT" 202 | #define MBED_CMDLINE_ESCAPE_BUFFER_SIZE 10 203 | 204 | // by default use 205 | #ifndef MBED_CONF_CMDLINE_ENABLE_ALIASES 206 | #define MBED_CONF_CMDLINE_ENABLE_ALIASES 1 207 | #endif 208 | #ifndef MBED_CONF_CMDLINE_USE_DUMMY_SET_ECHO_COMMANDS 209 | #define MBED_CONF_CMDLINE_USE_DUMMY_SET_ECHO_COMMANDS 0 210 | #elif defined(MBED_CONF_CMDLINE_ENABLE_INTERNAL_COMMANDS) && MBED_CONF_CMDLINE_ENABLE_INTERNAL_COMMANDS == 1 && MBED_CONF_CMDLINE_USE_DUMMY_SET_ECHO_COMMANDS == 1 211 | #warning "Cannot set MBED_CONF_CMDLINE_USE_DUMMY_SET_ECHO_COMMANDS along with MBED_CONF_CMDLINE_ENABLE_INTERNAL_COMMANDS" 212 | #endif 213 | // Maximum length of input line 214 | #ifndef MBED_CONF_CMDLINE_MAX_LINE_LENGTH 215 | #define MBED_CONF_CMDLINE_MAX_LINE_LENGTH 2000 216 | #endif 217 | // Maximum number of arguments in a single command 218 | #ifndef MBED_CONF_CMDLINE_ARGS_MAX_COUNT 219 | #define MBED_CONF_CMDLINE_ARGS_MAX_COUNT 30 220 | #endif 221 | // initialize automation mode at startup phase, no need to send set -commands 222 | #ifndef MBED_CONF_CMDLINE_INIT_AUTOMATION_MODE 223 | #define MBED_CONF_CMDLINE_INIT_AUTOMATION_MODE 0 224 | #endif 225 | // include manuals or not (save memory a little when not include) 226 | #ifndef MBED_CONF_CMDLINE_INCLUDE_MAN 227 | #define MBED_CONF_CMDLINE_INCLUDE_MAN 1 228 | #endif 229 | // allow to browse history using up/down keys (require ESCAPE_HANDLING) 230 | #ifndef MBED_CONF_CMDLINE_ENABLE_HISTORY 231 | #define MBED_CONF_CMDLINE_ENABLE_HISTORY 1 232 | #endif 233 | // Maximum number of commands saved in history 234 | #ifndef MBED_CONF_CMDLINE_HISTORY_MAX_COUNT 235 | #define MBED_CONF_CMDLINE_HISTORY_MAX_COUNT 32 236 | #endif 237 | // handle escape characters 238 | #ifndef MBED_CONF_CMDLINE_ENABLE_ESCAPE_HANDLING 239 | #define MBED_CONF_CMDLINE_ENABLE_ESCAPE_HANDLING 1 240 | #endif 241 | // enable all internal commands 242 | #ifndef MBED_CONF_CMDLINE_ENABLE_INTERNAL_COMMANDS 243 | #define MBED_CONF_CMDLINE_ENABLE_INTERNAL_COMMANDS 1 244 | #endif 245 | // enable internal variables 246 | #ifndef MBED_CONF_CMDLINE_ENABLE_INTERNAL_VARIABLES 247 | #define MBED_CONF_CMDLINE_ENABLE_INTERNAL_VARIABLES 1 248 | #endif 249 | // enable operators 250 | #ifndef MBED_CONF_CMDLINE_ENABLE_OPERATORS 251 | #define MBED_CONF_CMDLINE_ENABLE_OPERATORS 1 252 | #endif 253 | 254 | 255 | typedef struct cmd_history_s { 256 | char *command_ptr; 257 | ns_list_link_t link; 258 | } cmd_history_t; 259 | typedef NS_LIST_HEAD(cmd_history_t, link) history_list_t; 260 | 261 | typedef struct cmd_command_s { 262 | const char *name_ptr; 263 | const char *info_ptr; 264 | const char *man_ptr; 265 | cmd_run_cb *run_cb; 266 | bool busy; 267 | ns_list_link_t link; 268 | } cmd_command_t; 269 | typedef NS_LIST_HEAD(cmd_command_t, link) command_list_t; 270 | 271 | typedef struct cmd_alias_s { 272 | char *name_ptr; 273 | char *value_ptr; 274 | ns_list_link_t link; 275 | } cmd_alias_t; 276 | typedef NS_LIST_HEAD(cmd_alias_t, link) alias_list_t; 277 | 278 | union Data { 279 | char *ptr; 280 | int i; 281 | }; 282 | typedef enum value_type_s { 283 | VALUE_TYPE_STR, 284 | VALUE_TYPE_INT 285 | } value_type_t; 286 | 287 | typedef struct cmd_variable_s { 288 | char *name_ptr; 289 | union Data value; 290 | value_type_t type; 291 | ns_list_link_t link; 292 | } cmd_variable_t; 293 | typedef NS_LIST_HEAD(cmd_variable_t, link) variable_list_t; 294 | 295 | typedef enum operator_s { 296 | OPERATOR_SEMI_COLON, //default 297 | OPERATOR_AND, 298 | OPERATOR_OR, 299 | OPERATOR_BACKGROUND, 300 | OPERATOR_PIPE 301 | } operator_t; 302 | 303 | typedef struct cmd_exe_s { 304 | char *cmd_s; 305 | operator_t operator; 306 | ns_list_link_t link; 307 | } cmd_exe_t; 308 | typedef NS_LIST_HEAD(cmd_exe_t, link) cmd_list_t; 309 | 310 | 311 | typedef struct cmd_class_s { 312 | char input[MBED_CONF_CMDLINE_MAX_LINE_LENGTH]; // input data 313 | 314 | #if MBED_CONF_CMDLINE_ENABLE_HISTORY 315 | int16_t history; // history position 316 | history_list_t history_list; // input history 317 | uint8_t history_max_count; // history max size 318 | #endif 319 | int16_t cursor; // cursor position 320 | command_list_t command_list; // commands list 321 | #if MBED_CONF_CMDLINE_ENABLE_ALIASES == 1 322 | alias_list_t alias_list; // alias list 323 | #endif 324 | #if MBED_CONF_CMDLINE_ENABLE_INTERNAL_COMMANDS == 1 325 | variable_list_t variable_list; // variables list 326 | #endif 327 | #if MBED_CONF_CMDLINE_ENABLE_ESCAPE_HANDLING == 1 328 | bool vt100_on; // control characters 329 | bool escaping; // escaping input 330 | int16_t escape_index; // escape index 331 | char escape[MBED_CMDLINE_ESCAPE_BUFFER_SIZE]; // escape data 332 | #endif 333 | bool init; // true when lists are initialized already 334 | bool insert; // insert enabled 335 | int tab_lookup; // originally lookup characters count 336 | int tab_lookup_cmd_n; // index in command list 337 | int tab_lookup_n; // 338 | bool prev_cr; // indicate if cr was last char 339 | bool echo; // echo inputs 340 | cmd_ready_cb_f *ready_cb; // ready cb function 341 | cmd_list_t cmd_buffer; 342 | cmd_exe_t *cmd_buffer_ptr; 343 | cmd_command_t *cmd_ptr; 344 | int8_t tasklet_id; 345 | int8_t network_tasklet_id; 346 | bool idle; 347 | 348 | cmd_print_t *out; // print cb function 349 | void (*ctrl_fnc)(uint8_t c); // control cb function 350 | void (*mutex_wait_fnc)(void); // mutex wait cb function 351 | void (*mutex_release_fnc)(void); // mutex release cb function 352 | input_passthrough_func_t passthrough_fnc; // input passthrough cb function 353 | } cmd_class_t; 354 | 355 | cmd_class_t cmd = { 356 | .init = false, 357 | .cmd_ptr = NULL, 358 | .mutex_wait_fnc = NULL, 359 | .mutex_release_fnc = NULL, 360 | .passthrough_fnc = NULL 361 | }; 362 | 363 | /* Function prototypes 364 | */ 365 | static void cmd_init_base_commands(void); 366 | static void cmd_replace_alias(char *input) CMDLINE_UNUSED; 367 | static void cmd_replace_variables(char *input) CMDLINE_UNUSED; 368 | static int cmd_parse_argv(char *string_ptr, char **argv); 369 | static void cmd_execute(void); 370 | static void cmd_line_clear(int from); 371 | #if MBED_CONF_CMDLINE_ENABLE_HISTORY == 1 372 | static void cmd_history_item_delete(cmd_history_t *entry_ptr); 373 | static void cmd_history_save(int16_t index); 374 | static void cmd_history_get(uint16_t index); 375 | static void cmd_history_clean_overflow(void); 376 | static void cmd_history_clean(void); 377 | static cmd_history_t *cmd_history_find(int16_t index); 378 | static void cmd_goto_end_of_history(void) CMDLINE_UNUSED; 379 | static void cmd_goto_beginning_of_history(void) CMDLINE_UNUSED; 380 | #endif 381 | static void cmd_echo(bool on); 382 | static bool cmd_tab_lookup(void) CMDLINE_UNUSED; 383 | static void cmd_clear_last_word(void) CMDLINE_UNUSED; 384 | static void cmd_move_cursor_to_last_space(void) CMDLINE_UNUSED; 385 | static void cmd_move_cursor_to_next_space(void) CMDLINE_UNUSED; 386 | static void cmd_arrow_right(void) CMDLINE_UNUSED; 387 | static void cmd_arrow_left(void) CMDLINE_UNUSED; 388 | static void cmd_arrow_down(void) CMDLINE_UNUSED; 389 | static void cmd_arrow_up(void) CMDLINE_UNUSED; 390 | static const char *cmd_input_lookup(char *name, int namelength, int n); 391 | static char *cmd_input_lookup_var(char *name, int namelength, int n); 392 | static cmd_command_t *cmd_find(const char *name) CMDLINE_UNUSED; 393 | static cmd_command_t *cmd_find_n(char *name, int nameLength, int n) CMDLINE_UNUSED; 394 | static cmd_alias_t *alias_find(const char *alias) CMDLINE_UNUSED; 395 | static cmd_alias_t *alias_find_n(char *alias, int aliaslength, int n) CMDLINE_UNUSED; 396 | static cmd_variable_t *variable_find(char *variable) CMDLINE_UNUSED; 397 | static cmd_variable_t *variable_find_n(char *variable, int length, int n) CMDLINE_UNUSED; 398 | static void cmd_print_man(cmd_command_t *command_ptr); 399 | static void cmd_set_input(const char *str, int cur); 400 | static char *next_command(char *string_ptr, operator_t *mode); 401 | static void replace_variable(char *str, cmd_variable_t *variable_ptr) CMDLINE_UNUSED; 402 | static void cmd_variable_print_all(void) CMDLINE_UNUSED; 403 | /** Run single command through cmd intepreter 404 | * \param string_ptr command string with parameters 405 | * \ret command return code (CMDLINE_RETCODE_*) 406 | */ 407 | static int cmd_run(char *string_ptr); 408 | static cmd_exe_t *cmd_next_ptr(int retcode); 409 | static void cmd_split(char *string_ptr); 410 | static void cmd_push(char *cmd_str, operator_t oper); 411 | 412 | /*internal shell commands 413 | */ 414 | int help_command(int argc, char *argv[]); 415 | int echo_command(int argc, char *argv[]); 416 | int set_command(int argc, char *argv[]); 417 | int true_command(int argc, char *argv[]); 418 | int false_command(int argc, char *argv[]); 419 | int alias_command(int argc, char *argv[]); 420 | int unset_command(int argc, char *argv[]); 421 | int clear_command(int argc, char *argv[]); 422 | int history_command(int argc, char *argv[]); 423 | /** Internal helper functions 424 | */ 425 | static const char *find_last_space(const char *from, const char *to); 426 | static int replace_string( 427 | char *str, int str_len, 428 | const char *old_str, const char *new_str); 429 | 430 | void default_cmd_response_out(const char *fmt, va_list ap) 431 | { 432 | vprintf(fmt, ap); 433 | fflush(stdout); 434 | } 435 | void cmd_printf(const char *fmt, ...) 436 | { 437 | va_list ap; 438 | va_start(ap, fmt); 439 | cmd_vprintf(fmt, ap); 440 | va_end(ap); 441 | } 442 | void cmd_vprintf(const char *fmt, va_list ap) 443 | { 444 | if (cmd.mutex_wait_fnc) { 445 | cmd.mutex_wait_fnc(); 446 | } 447 | cmd.out(fmt, ap); 448 | if (cmd.mutex_release_fnc) { 449 | cmd.mutex_release_fnc(); 450 | } 451 | } 452 | /* Function definitions 453 | */ 454 | void cmd_init(cmd_print_t *outf) 455 | { 456 | if (!cmd.init) { 457 | #if MBED_CONF_CMDLINE_ENABLE_ALIASES == 1 458 | ns_list_init(&cmd.alias_list); 459 | #endif 460 | #if MBED_CONF_CMDLINE_ENABLE_HISTORY == 1 461 | ns_list_init(&cmd.history_list); 462 | #endif 463 | ns_list_init(&cmd.command_list); 464 | #if MBED_CONF_CMDLINE_ENABLE_INTERNAL_COMMANDS == 1 465 | ns_list_init(&cmd.variable_list); 466 | #endif 467 | ns_list_init(&cmd.cmd_buffer); 468 | cmd.init = true; 469 | } 470 | cmd.out = outf ? outf : default_cmd_response_out; 471 | cmd.ctrl_fnc = NULL; 472 | cmd.echo = MBED_CONF_CMDLINE_INIT_AUTOMATION_MODE == 0; 473 | cmd.insert = true; 474 | cmd.cursor = 0; 475 | cmd.prev_cr = false; 476 | #if MBED_CONF_CMDLINE_ENABLE_ESCAPE_HANDLING == 1 477 | cmd.escaping = false; 478 | cmd.vt100_on = MBED_CONF_CMDLINE_INIT_AUTOMATION_MODE == 0; 479 | #endif 480 | #if MBED_CONF_CMDLINE_ENABLE_HISTORY 481 | cmd.history_max_count = MBED_CONF_CMDLINE_HISTORY_MAX_COUNT; 482 | #endif 483 | cmd.tab_lookup = 0; 484 | cmd.tab_lookup_cmd_n = 0; 485 | cmd.tab_lookup_n = 0; 486 | cmd.cmd_buffer_ptr = 0; 487 | cmd.idle = true; 488 | cmd.ready_cb = cmd_next; 489 | cmd.passthrough_fnc = NULL; 490 | #if MBED_CONF_CMDLINE_ENABLE_INTERNAL_VARIABLES == 1 491 | cmd_variable_add(VAR_PROMPT, DEFAULT_PROMPT); 492 | cmd_variable_add_int("?", 0); 493 | //cmd_alias_add("auto-on", "set PS1=\r\nretcode=$?\r\n&&echo off"); 494 | //cmd_alias_add("auto-off", "set PS1="DEFAULT_PROMPT"&&echo on"); 495 | #endif 496 | #if MBED_CONF_CMDLINE_ENABLE_HISTORY 497 | cmd_history_save(0); // the current line is the 0 item 498 | #endif 499 | cmd_line_clear(0); // clear line 500 | cmd_init_base_commands(); 501 | cmd_init_screen(); 502 | return; 503 | } 504 | void cmd_request_screen_size(void) 505 | { 506 | cmd_printf(REQUEST_SCREEN_SIZE); 507 | } 508 | #if MBED_CONF_CMDLINE_ENABLE_INTERNAL_VARIABLES == 0 509 | #define cmdline_get_prompt() DEFAULT_PROMPT 510 | #else 511 | const char *cmdline_get_prompt(void) 512 | { 513 | cmd_variable_t *var_ptr = variable_find(VAR_PROMPT); 514 | return var_ptr && var_ptr->type == VALUE_TYPE_STR ? var_ptr->value.ptr : ""; 515 | } 516 | #endif 517 | #if MBED_CONF_CMDLINE_ENABLE_INTERNAL_VARIABLES == 0 518 | #define cmd_get_retfmt() MBED_CONF_CMDLINE_INIT_AUTOMATION_MODE ? DEFAULT_RETFMT : 0 519 | #else 520 | const char *cmd_get_retfmt(void) 521 | { 522 | cmd_variable_t *var_ptr = variable_find(VAR_RETFMT); 523 | return var_ptr && var_ptr->type == VALUE_TYPE_STR ? var_ptr->value.ptr : 0; 524 | } 525 | #endif 526 | 527 | #if MBED_CONF_CMDLINE_INCLUDE_MAN == 1 528 | #define MAN_ECHO "Displays messages, or turns command echoing on or off\r\n"\ 529 | "echo \r\n"\ 530 | "some special parameters:\r\n"\ 531 | " On/Off echo input characters\r\n" 532 | #define MAN_ALIAS "alias \r\n" 533 | #define MAN_UNSET "unset \r\n" 534 | #define MAN_SET "set \r\n"\ 535 | "some special parameters\r\n"\ 536 | "--vt100 On/Off vt100 controls\r\n"\ 537 | "--retcode On/Off retcode print after execution\r\n"\ 538 | "--retfmt Return print format. Default: \"retcode: %i\\n\"\r\n" 539 | 540 | #define MAN_CLEAR "Clears the display" 541 | #define MAN_HISTORY "Show commands history\r\n"\ 542 | "history ()\r\n"\ 543 | "clear Clear history\r\n" 544 | #else 545 | #define MAN_ECHO NULL 546 | #define MAN_ALIAS NULL 547 | #define MAN_SET NULL 548 | #define MAN_UNSET NULL 549 | #define MAN_CLEAR NULL 550 | #define MAN_HISTORY NULL 551 | #endif 552 | 553 | static void cmd_init_base_commands(void) 554 | { 555 | #if MBED_CONF_CMDLINE_ENABLE_INTERNAL_COMMANDS == 1 556 | #if MBED_CONF_CMDLINE_ENABLE_INTERNAL_VARIABLES == 1 557 | cmd_add("set", set_command, "print or set variables", MAN_SET); 558 | cmd_add("unset", unset_command, "unset variables", MAN_UNSET); 559 | #endif 560 | cmd_add("help", help_command, "This help", NULL); 561 | cmd_add("echo", echo_command, "Echo controlling", MAN_ECHO); 562 | #if MBED_CONF_CMDLINE_ENABLE_ALIASES == 1 563 | cmd_add("alias", alias_command, "Handle aliases", MAN_ALIAS); 564 | #endif 565 | cmd_add("clear", clear_command, "Clears the display", MAN_CLEAR); 566 | #if MBED_CONF_CMDLINE_ENABLE_HISTORY == 1 567 | cmd_add("history", history_command, "View your command Line History", MAN_HISTORY); 568 | #endif 569 | cmd_add("true", true_command, 0, 0); 570 | cmd_add("false", false_command, 0, 0); 571 | #elif MBED_CONF_CMDLINE_USE_DUMMY_SET_ECHO_COMMANDS == 1 572 | cmd_add("set", set_command, 0, 0); 573 | cmd_add("echo", echo_command, 0, 0); 574 | #endif 575 | } 576 | 577 | void cmd_reset(void) 578 | { 579 | cmd_free(); 580 | cmd_init_base_commands(); 581 | } 582 | 583 | void cmd_free(void) 584 | { 585 | if (!cmd.init) { 586 | tr_warn("cmd_free() called without init"); 587 | return; 588 | } 589 | ns_list_foreach_safe(cmd_command_t, cur_ptr, &cmd.command_list) { 590 | cmd_delete(cur_ptr->name_ptr); 591 | } 592 | #if MBED_CONF_CMDLINE_ENABLE_ALIASES == 1 593 | ns_list_foreach_safe(cmd_alias_t, cur_ptr, &cmd.alias_list) { 594 | cmd_alias_add(cur_ptr->name_ptr, NULL); 595 | } 596 | #endif 597 | #if MBED_CONF_CMDLINE_ENABLE_INTERNAL_COMMANDS == 1 598 | ns_list_foreach_safe(cmd_variable_t, cur_ptr, &cmd.variable_list) { 599 | if (cur_ptr->type == VALUE_TYPE_STR) { 600 | cmd_variable_add(cur_ptr->value.ptr, NULL); 601 | } 602 | } 603 | #endif 604 | #if MBED_CONF_CMDLINE_ENABLE_HISTORY 605 | ns_list_foreach_safe(cmd_history_t, cur_ptr, &cmd.history_list) { 606 | MEM_FREE(cur_ptr->command_ptr); 607 | ns_list_remove(&cmd.history_list, cur_ptr); 608 | MEM_FREE(cur_ptr); 609 | } 610 | #endif 611 | cmd.mutex_wait_fnc = NULL; 612 | cmd.mutex_release_fnc = NULL; 613 | cmd.init = false; 614 | } 615 | 616 | void cmd_input_passthrough_func(input_passthrough_func_t passthrough_fnc) 617 | { 618 | cmd.passthrough_fnc = passthrough_fnc; 619 | } 620 | 621 | void cmd_exe(char *str) 622 | { 623 | tr_deep("cmd_exe(): %s", str); 624 | if (!cmd.init) { 625 | tr_warn("cmd_exe() called without init"); 626 | return; 627 | } 628 | cmd_split(str); 629 | if (str == cmd.input) { 630 | cmd_line_clear(0); 631 | } 632 | 633 | if (cmd.cmd_buffer_ptr == 0) { 634 | //execution buffer is empty 635 | cmd.idle = false; //not really, but fake it 636 | cmd_ready(CMDLINE_RETCODE_SUCCESS); 637 | } else { 638 | tr_debug("previous cmd is still in progress"); 639 | } 640 | } 641 | 642 | void cmd_set_ready_cb(cmd_ready_cb_f *cb) 643 | { 644 | cmd.ready_cb = cb; 645 | } 646 | 647 | void cmd_ready(int retcode) 648 | { 649 | tr_deep("cmd_ready(): %d", retcode); 650 | if (!cmd.init) { 651 | tr_warn("cmd_ready() called without init"); 652 | return; 653 | } 654 | if (cmd.cmd_ptr && cmd.cmd_ptr->busy) { 655 | //execution finished 656 | cmd.cmd_ptr->busy = false; 657 | } 658 | if (!cmd.idle) { 659 | if (cmd.cmd_buffer_ptr == NULL) { 660 | tr_debug("goto next command"); 661 | } else { 662 | tr_debug("cmd '%s' executed with retcode: %i", cmd.cmd_buffer_ptr->cmd_s, retcode); 663 | } 664 | if (cmd.ready_cb == NULL) { 665 | tr_warn("Missing ready_cb! use cmd_set_ready_cb()"); 666 | } else { 667 | cmd.ready_cb(retcode); 668 | } 669 | } else { 670 | tr_warn("Someone call cmd_ready(%i) even there shouldn't be any running cmd", retcode); 671 | if (cmd.echo) { 672 | cmd_output(); //refresh if this happens 673 | } 674 | } 675 | } 676 | 677 | void cmd_next(int retcode) 678 | { 679 | if (!cmd.init) { 680 | tr_warn("cmd_next() called without init"); 681 | return; 682 | } 683 | tr_deep("cmd_next()"); 684 | cmd.idle = true; 685 | //figure out next command 686 | cmd.cmd_buffer_ptr = cmd_next_ptr(retcode); 687 | if (cmd.cmd_buffer_ptr) { 688 | cmd.idle = false; 689 | //yep there was some -> lets execute it 690 | retcode = cmd_run(cmd.cmd_buffer_ptr->cmd_s); 691 | //check if execution goes to the backend or not 692 | if (retcode == CMDLINE_RETCODE_EXCUTING_CONTINUE) { 693 | if ((NULL != cmd.cmd_buffer_ptr) && cmd.cmd_buffer_ptr->operator == OPERATOR_BACKGROUND) { 694 | //execution continue in background, but operator say that it's "ready" 695 | cmd_ready(CMDLINE_RETCODE_SUCCESS); 696 | } else { 697 | //command execution phase continuous in background 698 | tr_debug("Command execution continuous in background.."); 699 | } 700 | } else { 701 | //execution finished -> call ready function with retcode 702 | cmd_ready(retcode); 703 | } 704 | } else { 705 | const char *retfmt = cmd_get_retfmt(); 706 | if (retfmt) { 707 | cmd_printf(retfmt, retcode); 708 | } 709 | if (cmd.echo) { 710 | cmd_output(); //ready 711 | } 712 | } 713 | } 714 | 715 | static cmd_exe_t *cmd_pop(void) 716 | { 717 | cmd_exe_t *cmd_ptr = ns_list_get_first(&cmd.cmd_buffer), 718 | *next_cmd = ns_list_get_next(&cmd.cmd_buffer, cmd_ptr); 719 | 720 | if (cmd.cmd_buffer_ptr == 0) { 721 | //was first in bool 722 | next_cmd = ns_list_get_first(&cmd.cmd_buffer); 723 | } else { 724 | MEM_FREE(cmd_ptr->cmd_s); 725 | ns_list_remove(&cmd.cmd_buffer, cmd_ptr); 726 | MEM_FREE(cmd_ptr); 727 | } 728 | return next_cmd; 729 | } 730 | 731 | static cmd_exe_t *cmd_next_ptr(int retcode) 732 | { 733 | cmd_exe_t *next_cmd = cmd.cmd_buffer_ptr; 734 | if (ns_list_is_empty(&cmd.cmd_buffer)) { 735 | return NULL; 736 | } 737 | if (cmd.cmd_buffer_ptr == NULL) { 738 | return cmd_pop(); 739 | } 740 | #if MBED_CONF_CMDLINE_ENABLE_OPERATORS == 0 741 | (void)retcode; 742 | #endif 743 | switch (cmd.cmd_buffer_ptr->operator) { 744 | #if MBED_CONF_CMDLINE_ENABLE_OPERATORS 745 | case (OPERATOR_AND): 746 | if (retcode != CMDLINE_RETCODE_SUCCESS) { 747 | //if fails, go to next command, which not have AND operator 748 | while ((next_cmd->operator == OPERATOR_AND) && ((next_cmd = cmd_pop()) != 0)); 749 | } else { 750 | next_cmd = cmd_pop(); 751 | } 752 | break; 753 | case (OPERATOR_OR): 754 | if (retcode == CMDLINE_RETCODE_SUCCESS) { 755 | //if fails, go to next command, which not have OR operator 756 | while ((next_cmd->operator == OPERATOR_OR) && ((next_cmd = cmd_pop()) != 0)); 757 | } else { 758 | next_cmd = cmd_pop(); 759 | } 760 | break; 761 | case (OPERATOR_BACKGROUND): 762 | next_cmd = cmd_pop(); 763 | break; 764 | case (OPERATOR_PIPE): 765 | cmd_printf("pipe is not supported\r\n"); 766 | while ((next_cmd = cmd_pop()) != 0); 767 | break; 768 | #endif 769 | case (OPERATOR_SEMI_COLON): 770 | default: 771 | //get next command to be execute (might be null if there is no more) 772 | next_cmd = cmd_pop(); 773 | break; 774 | } 775 | 776 | //return next command if any 777 | return next_cmd; 778 | } 779 | 780 | static void cmd_split(char *string_ptr) 781 | { 782 | char *ptr = string_ptr, *next; 783 | operator_t oper = OPERATOR_SEMI_COLON; 784 | do { 785 | cmd_replace_alias(ptr); 786 | next = next_command(ptr, &oper); 787 | cmd_push(ptr, oper); 788 | ptr = next; 789 | if (next && !*next) { 790 | break; 791 | } 792 | } while (ptr != 0); 793 | } 794 | 795 | static void cmd_push(char *cmd_str, operator_t oper) 796 | { 797 | //store this command to the stack 798 | cmd_exe_t *cmd_ptr = MEM_ALLOC(sizeof(cmd_exe_t)); 799 | if (cmd_ptr == NULL) { 800 | tr_error("mem alloc failed in cmd_push"); 801 | return; 802 | } 803 | cmd_ptr->cmd_s = MEM_ALLOC(strlen(cmd_str) + 1); 804 | if (cmd_ptr->cmd_s == NULL) { 805 | MEM_FREE(cmd_ptr); 806 | tr_error("mem alloc failed in cmd_push cmd_s"); 807 | return; 808 | } 809 | strcpy(cmd_ptr->cmd_s, cmd_str); 810 | tr_deep("cmd_push: %s", cmd_ptr->cmd_s); 811 | cmd_ptr->operator = oper; 812 | ns_list_add_to_end(&cmd.cmd_buffer, cmd_ptr); 813 | } 814 | 815 | void cmd_out_func(cmd_print_t *outf) 816 | { 817 | cmd.out = outf; 818 | } 819 | 820 | void cmd_ctrl_func(void (*sohf)(uint8_t c)) 821 | { 822 | cmd.ctrl_fnc = sohf; 823 | } 824 | 825 | void cmd_mutex_wait_func(void (*mutex_wait_f)(void)) 826 | { 827 | cmd.mutex_wait_fnc = mutex_wait_f; 828 | } 829 | 830 | void cmd_mutex_release_func(void (*mutex_release_f)(void)) 831 | { 832 | cmd.mutex_release_fnc = mutex_release_f; 833 | } 834 | 835 | void cmd_mutex_lock(void) 836 | { 837 | if (cmd.mutex_wait_fnc) { 838 | cmd.mutex_wait_fnc(); 839 | } 840 | } 841 | 842 | void cmd_mutex_unlock(void) 843 | { 844 | if (cmd.mutex_release_fnc) { 845 | cmd.mutex_release_fnc(); 846 | } 847 | } 848 | 849 | void cmd_init_screen(void) 850 | { 851 | #if MBED_CONF_CMDLINE_ENABLE_ESCAPE_HANDLING == 1 852 | if (cmd.vt100_on) { 853 | cmd_printf(CR_S CLEAR_ENTIRE_SCREEN); /* Clear screen */ 854 | cmd_printf(ENABLE_AUTO_WRAP_MODE); /* enable line wrap */ 855 | } 856 | #endif 857 | cmd_printf(MBED_CONF_CMDLINE_BOOT_MESSAGE); 858 | cmd_output(); 859 | } 860 | 861 | uint8_t cmd_history_size(uint8_t max) 862 | { 863 | #if MBED_CONF_CMDLINE_ENABLE_HISTORY 864 | if (max > 0) { 865 | cmd.history_max_count = max; 866 | cmd_history_clean_overflow(); 867 | } 868 | return cmd.history_max_count; 869 | #else 870 | (void)max; 871 | return 0; 872 | #endif 873 | } 874 | 875 | static void cmd_echo(bool on) 876 | { 877 | cmd.echo = on; 878 | } 879 | 880 | bool cmd_echo_state(void) 881 | { 882 | return cmd.echo; 883 | } 884 | 885 | static cmd_command_t *cmd_find_n(char *name, int nameLength, int n) 886 | { 887 | cmd_command_t *cmd_ptr = NULL; 888 | if (name != NULL && nameLength != 0) { 889 | int i = 0; 890 | ns_list_foreach(cmd_command_t, cur_ptr, &cmd.command_list) { 891 | if (strncmp(name, cur_ptr->name_ptr, nameLength) == 0) { 892 | if (i == n) { 893 | cmd_ptr = cur_ptr; 894 | break; 895 | } 896 | i++; 897 | } 898 | } 899 | } 900 | return cmd_ptr; 901 | } 902 | 903 | static const char *cmd_input_lookup(char *name, int namelength, int n) 904 | { 905 | const char *str = NULL; 906 | cmd_command_t *cmd_ptr = cmd_find_n(name, namelength, n); 907 | if (cmd_ptr) { 908 | str = cmd_ptr->name_ptr; 909 | cmd.tab_lookup_n = n + 1; 910 | } else { 911 | n -= cmd.tab_lookup_n; 912 | #if MBED_CONF_CMDLINE_ENABLE_ALIASES == 1 913 | cmd_alias_t *alias = alias_find_n(name, namelength, n); 914 | if (alias) { 915 | str = (const char *)alias->name_ptr; 916 | } 917 | #endif 918 | } 919 | 920 | return str; 921 | } 922 | 923 | static char *cmd_input_lookup_var(char *name, int namelength, int n) 924 | { 925 | char *str = NULL; 926 | cmd_variable_t *var = variable_find_n(name, namelength, n); 927 | if (var) { 928 | str = var->name_ptr; 929 | } 930 | return str; 931 | } 932 | 933 | static cmd_command_t *cmd_find(const char *name) 934 | { 935 | cmd_command_t *cmd_ptr = NULL; 936 | if (name == NULL || strlen(name) == 0) { 937 | tr_error("cmd_find invalid parameters"); 938 | return NULL; 939 | } 940 | 941 | ns_list_foreach(cmd_command_t, cur_ptr, &cmd.command_list) { 942 | if (strcmp(name, cur_ptr->name_ptr) == 0) { 943 | cmd_ptr = cur_ptr; 944 | break; 945 | } 946 | } 947 | return cmd_ptr; 948 | } 949 | 950 | void cmd_add(const char *name, cmd_run_cb *callback, const char *info, const char *man) 951 | { 952 | cmd_command_t *cmd_ptr; 953 | 954 | if (name == NULL || callback == NULL || strlen(name) == 0) { 955 | tr_warn("cmd_add invalid parameters"); 956 | return; 957 | } 958 | cmd_ptr = (cmd_command_t *)MEM_ALLOC(sizeof(cmd_command_t)); 959 | if (cmd_ptr == NULL) { 960 | tr_error("mem alloc failed in cmd_add"); 961 | return; 962 | } 963 | cmd_ptr->name_ptr = name; 964 | cmd_ptr->info_ptr = info; 965 | #if MBED_CONF_CMDLINE_INCLUDE_MAN == 1 966 | cmd_ptr->man_ptr = man; 967 | #else 968 | (void)man; 969 | cmd_ptr->man_ptr = 0; 970 | #endif 971 | cmd_ptr->run_cb = callback; 972 | cmd_ptr->busy = false; 973 | ns_list_add_to_end(&cmd.command_list, cmd_ptr); 974 | return; 975 | } 976 | 977 | void cmd_delete(const char *name) 978 | { 979 | cmd_command_t *cmd_ptr; 980 | cmd_ptr = cmd_find(name); 981 | if (cmd_ptr == NULL) { 982 | return; 983 | } 984 | ns_list_remove(&cmd.command_list, cmd_ptr); 985 | MEM_FREE(cmd_ptr); 986 | return; 987 | } 988 | 989 | static void replace_escapes(char *string_ptr) 990 | { 991 | while ((string_ptr = strchr(string_ptr, '\\')) != NULL) { 992 | memmove(string_ptr, string_ptr + 1, strlen(string_ptr + 1) + 1); 993 | string_ptr++; 994 | } 995 | } 996 | 997 | static int cmd_parse_argv(char *string_ptr, char **argv) 998 | { 999 | tr_deep("cmd_parse_argv(%s, ..)\r\n", string_ptr); 1000 | int argc = 0; 1001 | char *str_ptr, *end_quote_ptr = NULL; 1002 | 1003 | if (string_ptr == NULL || strlen(string_ptr) == 0) { 1004 | tr_error("Invalid parameters"); 1005 | return 0; 1006 | } 1007 | str_ptr = string_ptr; 1008 | do { 1009 | argv[argc] = str_ptr; 1010 | // tr_deep("parsing.. argv[%d]: %s\r\n", argc, str_ptr); 1011 | if (*str_ptr != '\\') { 1012 | if (*str_ptr == '"') { 1013 | // check if end quote 1014 | end_quote_ptr = str_ptr; 1015 | do { 1016 | end_quote_ptr = strchr(end_quote_ptr + 1, '\"'); 1017 | if (end_quote_ptr == NULL) { 1018 | break; 1019 | } 1020 | } while (*(end_quote_ptr - 1) == '\\'); 1021 | if (end_quote_ptr != NULL) { 1022 | // remove quotes give as one parameter 1023 | argv[argc]++; 1024 | str_ptr = end_quote_ptr; 1025 | } else { 1026 | str_ptr = strchr(str_ptr, ' '); 1027 | } 1028 | } else { 1029 | str_ptr = strchr(str_ptr, ' '); 1030 | } 1031 | } else { 1032 | str_ptr = strchr(str_ptr, ' '); 1033 | } 1034 | argc++; // one argument parsed 1035 | if (str_ptr == NULL) { 1036 | break; 1037 | } 1038 | if (argc > MBED_CONF_CMDLINE_ARGS_MAX_COUNT) { 1039 | tr_warn("Maximum arguments (%d) reached", MBED_CONF_CMDLINE_ARGS_MAX_COUNT); 1040 | break; 1041 | } 1042 | *str_ptr++ = 0; 1043 | replace_escapes(argv[argc - 1]); 1044 | // tr_deep("parsed argv[%d]: %s\r\n", argc-1, argv[argc-1]); 1045 | while (*str_ptr == ' ') { 1046 | str_ptr++; // skip spaces 1047 | }; 1048 | } while (*str_ptr != 0); 1049 | return argc; 1050 | } 1051 | 1052 | static void cmd_print_man(cmd_command_t *command_ptr) 1053 | { 1054 | if (command_ptr->man_ptr) { 1055 | cmd_printf("%s\r\n", command_ptr->man_ptr); 1056 | } 1057 | } 1058 | 1059 | static void cmd_set_input(const char *str, int cur) 1060 | { 1061 | cmd_line_clear(cur); 1062 | strcpy(cmd.input + cur, str); 1063 | cmd.cursor = strlen(cmd.input); 1064 | 1065 | } 1066 | /** 1067 | * If oper is not null, function set null pointers 1068 | */ 1069 | static char *next_command(char *string_ptr, operator_t *oper) 1070 | { 1071 | char *ptr = string_ptr; 1072 | bool quote = false; 1073 | #if MBED_CONF_CMDLINE_ENABLE_OPERATORS == 0 1074 | (void)oper; 1075 | #endif 1076 | while (*ptr != 0) { 1077 | if (quote) { 1078 | if (*ptr == '"') { 1079 | quote = false; 1080 | } 1081 | } else { 1082 | //skip if previous character is '\' 1083 | if ((ptr == string_ptr) || (*(ptr - 1) != '\\')) { 1084 | switch (*ptr) { 1085 | case ('"'): { 1086 | quote = true; 1087 | break; 1088 | } 1089 | #if MBED_CONF_CMDLINE_ENABLE_OPERATORS 1090 | case (';'): //default operator 1091 | if (oper) { 1092 | *oper = OPERATOR_SEMI_COLON; 1093 | *ptr = 0; 1094 | } 1095 | return ptr + 1; 1096 | case ('&'): 1097 | if (ptr[1] == '&') { 1098 | if (oper) { 1099 | *oper = OPERATOR_AND; 1100 | *ptr = 0; 1101 | } 1102 | return ptr + 2; 1103 | } else { 1104 | if (oper) { 1105 | *oper = OPERATOR_BACKGROUND; 1106 | *ptr = 0; 1107 | } 1108 | return ptr + 1; 1109 | } 1110 | case ('|'): 1111 | if (ptr[1] == '|') { 1112 | if (oper) { 1113 | *oper = OPERATOR_OR; 1114 | *ptr = 0; 1115 | } 1116 | return ptr + 2; 1117 | } else { 1118 | tr_warn("pipe operator not supported"); 1119 | if (oper) { 1120 | *oper = OPERATOR_PIPE; 1121 | *ptr = 0; 1122 | } 1123 | return ptr + 1; 1124 | } 1125 | #endif 1126 | default: 1127 | break; 1128 | } 1129 | } 1130 | } 1131 | ptr++; 1132 | } 1133 | return 0; 1134 | } 1135 | 1136 | static int cmd_run(char *string_ptr) 1137 | { 1138 | char *argv[MBED_CONF_CMDLINE_ARGS_MAX_COUNT]; 1139 | int argc, ret; 1140 | 1141 | tr_info("Executing cmd: '%s'", string_ptr); 1142 | 1143 | // Initialize first argv to utilize the existing command-not-found -path in case of 1144 | // getting only whitespace(s) as command string. 1145 | argv[0] = ""; 1146 | 1147 | char *command_str = MEM_ALLOC(MBED_CONF_CMDLINE_MAX_LINE_LENGTH); 1148 | if (command_str == NULL) { 1149 | tr_error("mem alloc failed in cmd_run"); 1150 | return CMDLINE_RETCODE_FAIL; 1151 | } 1152 | while (isspace((unsigned char) *string_ptr) && 1153 | *string_ptr != '\n' && 1154 | *string_ptr != 0) { 1155 | string_ptr++; //skip white spaces 1156 | } 1157 | strcpy(command_str, string_ptr); 1158 | tr_deep("cmd_run('%s') ", command_str); 1159 | #if MBED_CONF_CMDLINE_ENABLE_ALIASES == 1 1160 | cmd_replace_alias(command_str); 1161 | #endif 1162 | #if MBED_CONF_CMDLINE_ENABLE_INTERNAL_COMMANDS == 1 1163 | cmd_replace_variables(command_str); 1164 | #endif 1165 | tr_debug("Parsed cmd: '%s'", command_str); 1166 | 1167 | argc = cmd_parse_argv(command_str, argv); 1168 | 1169 | cmd.cmd_ptr = cmd_find(argv[0]); 1170 | 1171 | if (cmd.cmd_ptr == NULL) { 1172 | cmd_printf("Command '%s' not found.\r\n", argv[0]); 1173 | MEM_FREE(command_str); 1174 | ret = CMDLINE_RETCODE_COMMAND_NOT_FOUND; 1175 | #if MBED_CONF_CMDLINE_ENABLE_INTERNAL_VARIABLES == 1 1176 | cmd_variable_add_int("?", ret); 1177 | cmd_alias_add("_", string_ptr); // last executed command 1178 | #endif 1179 | return ret; 1180 | } 1181 | if (cmd.cmd_ptr->run_cb == NULL) { 1182 | tr_error("Command callback missing"); 1183 | MEM_FREE(command_str); 1184 | return CMDLINE_RETCODE_COMMAND_CB_MISSING; 1185 | } 1186 | 1187 | if (argc == 2 && 1188 | (cmd_has_option(argc, argv, "h") || cmd_parameter_index(argc, argv, "--help") > 0)) { 1189 | MEM_FREE(command_str); 1190 | cmd_print_man(cmd.cmd_ptr); 1191 | return CMDLINE_RETCODE_SUCCESS; 1192 | } 1193 | 1194 | if (cmd.cmd_ptr->busy) { 1195 | MEM_FREE(command_str); 1196 | return CMDLINE_RETCODE_COMMAND_BUSY; 1197 | } 1198 | 1199 | // Run the actual callback 1200 | cmd.cmd_ptr->busy = true; 1201 | ret = cmd.cmd_ptr->run_cb(argc, argv); 1202 | #if MBED_CONF_CMDLINE_ENABLE_INTERNAL_VARIABLES == 1 1203 | cmd_variable_add_int("?", ret); 1204 | cmd_alias_add("_", string_ptr); // last executed command 1205 | #endif 1206 | MEM_FREE(command_str); 1207 | switch (ret) { 1208 | case (CMDLINE_RETCODE_COMMAND_NOT_IMPLEMENTED): 1209 | tr_warn("Command not implemented"); 1210 | break; 1211 | case (CMDLINE_RETCODE_INVALID_PARAMETERS): 1212 | tr_warn("Command parameter was incorrect"); 1213 | cmd_printf("Invalid parameters!\r\n"); 1214 | cmd_print_man(cmd.cmd_ptr); 1215 | break; 1216 | default: 1217 | break; 1218 | } 1219 | return ret; 1220 | } 1221 | 1222 | void cmd_escape_start(void) 1223 | { 1224 | #if MBED_CONF_CMDLINE_ENABLE_ESCAPE_HANDLING == 1 1225 | cmd.escaping = true; 1226 | memset(cmd.escape, 0, sizeof(cmd.escape)); 1227 | cmd.escape_index = 0; 1228 | #endif 1229 | } 1230 | 1231 | static void cmd_arrow_right(void) 1232 | { 1233 | #if MBED_CONF_CMDLINE_ENABLE_ESCAPE_HANDLING == 1 1234 | 1235 | /* @todo handle shift 1236 | if(strncmp(cmd.escape+1, "1;2", 3) == 0) { 1237 | tr_debug("Shift pressed"); 1238 | shift = true; 1239 | } 1240 | */ 1241 | if ((cmd.escape_index == 1 && cmd.escape[0] == 'O') || 1242 | (cmd.escape_index == 4 && strncmp(cmd.escape + 1, "1;5", 3) == 0)) { 1243 | cmd_move_cursor_to_next_space(); 1244 | } else { 1245 | cmd.cursor ++; 1246 | } 1247 | if ((int)cmd.cursor > (int)strlen(cmd.input)) { 1248 | cmd.cursor = strlen(cmd.input); 1249 | } 1250 | #endif 1251 | } 1252 | 1253 | static void cmd_arrow_left(void) 1254 | { 1255 | #if MBED_CONF_CMDLINE_ENABLE_ESCAPE_HANDLING == 1 1256 | /* @todo handle shift 1257 | if(strncmp(cmd.escape+1, "1;2", 3) == 0) { 1258 | tr_debug("Shift pressed"); 1259 | shift = true; 1260 | } 1261 | */ 1262 | if ((cmd.escape_index == 1 && cmd.escape[0] == 'O') || 1263 | (cmd.escape_index == 4 && strncmp(cmd.escape + 1, "1;5", 3) == 0)) { 1264 | cmd_move_cursor_to_last_space(); 1265 | } else { 1266 | cmd.cursor --; 1267 | } 1268 | if (cmd.cursor < 0) { 1269 | cmd.cursor = 0; 1270 | } 1271 | #endif 1272 | } 1273 | 1274 | static void cmd_arrow_up(void) 1275 | { 1276 | #if MBED_CONF_CMDLINE_ENABLE_HISTORY 1277 | int16_t old_entry = cmd.history++; 1278 | if (NULL == cmd_history_find(cmd.history)) { 1279 | cmd.history = old_entry; 1280 | } 1281 | if (old_entry != cmd.history) { 1282 | cmd_history_save(old_entry); 1283 | cmd_history_get(cmd.history); 1284 | } 1285 | #endif 1286 | } 1287 | 1288 | static void cmd_arrow_down(void) 1289 | { 1290 | #if MBED_CONF_CMDLINE_ENABLE_HISTORY 1291 | 1292 | int16_t old_entry = cmd.history--; 1293 | if (cmd.history < 0) { 1294 | cmd.history = 0; 1295 | } 1296 | 1297 | if (old_entry != cmd.history) { 1298 | cmd_history_save(old_entry); 1299 | cmd_history_get(cmd.history); 1300 | } 1301 | #endif 1302 | } 1303 | 1304 | #if MBED_CONF_CMDLINE_ENABLE_ESCAPE_HANDLING == 1 1305 | void cmd_escape_read(int16_t u_data) 1306 | { 1307 | tr_debug("cmd_escape_read: %02x '%c' escape_index: %d: %s", 1308 | u_data, 1309 | (isprint(u_data) ? u_data : '?'), 1310 | cmd.escape_index, 1311 | cmd.escape); 1312 | 1313 | if (u_data == 'D') { 1314 | cmd_arrow_left(); 1315 | } else if (u_data == 'C') { 1316 | cmd_arrow_right(); 1317 | #if MBED_CONF_CMDLINE_ENABLE_HISTORY 1318 | } else if (u_data == 'A') { 1319 | cmd_arrow_up(); 1320 | } else if (u_data == 'B') { 1321 | cmd_arrow_down(); 1322 | #endif 1323 | } else if (u_data == 'Z') { 1324 | // Shift+TAB 1325 | if (cmd.tab_lookup > 0) { 1326 | cmd.cursor = cmd.tab_lookup; 1327 | cmd.input[cmd.tab_lookup] = 0; 1328 | if (cmd.tab_lookup_cmd_n > 0) { 1329 | cmd.tab_lookup_cmd_n--; 1330 | } 1331 | } 1332 | cmd_tab_lookup(); 1333 | } else if (u_data == 'b') { 1334 | cmd_move_cursor_to_last_space(); 1335 | } else if (u_data == 'f') { 1336 | cmd_move_cursor_to_next_space(); 1337 | } else if (u_data == 'n') { 1338 | if (cmd.escape[1] == '5') { 1339 | // Device status report 1340 | // Response: terminal is OK 1341 | cmd_printf("%c0n", ESC); 1342 | } else if (cmd.escape[1] == '6') { 1343 | // Get cursor position 1344 | cmd_printf(ESCAPE("%d%d"), 0, cmd.cursor); 1345 | } 1346 | } else if (u_data == 'R') { 1347 | // response for Get cursor position ([6n) 1348 | // [;R 1349 | char *ptr; 1350 | int lines = strtol(cmd.escape + 1, &ptr, 10); 1351 | if (ptr == NULL) { 1352 | tr_warn("Invalid response: %s%c", cmd.escape, u_data); 1353 | } else { 1354 | int cols = strtol(ptr + 1, 0, 10); 1355 | tr_debug("Lines: %d, cols: %d", lines, cols); 1356 | cmd_variable_add_int("LINES", lines); 1357 | cmd_variable_add_int("COLUMNS", cols); 1358 | } 1359 | } else if (u_data == 'H') { 1360 | // Xterm support 1361 | cmd.cursor = 0; 1362 | } else if (u_data == 'F') { 1363 | // Xterm support 1364 | cmd.cursor = strlen(cmd.input); 1365 | } else if (isdigit((int)cmd.escape[cmd.escape_index - 1]) && u_data == '~') { 1366 | switch (cmd.escape[cmd.escape_index - 1]) { 1367 | case ('1'): //beginning-of-line # Home key 1368 | cmd.cursor = 0; 1369 | break; 1370 | case ('2'): //quoted-insert # Insert key 1371 | cmd.insert = !cmd.insert; 1372 | break; 1373 | case ('3'): //delete-char # Delete key 1374 | if ((int)strlen(cmd.input) > (int)cmd.cursor) { 1375 | memmove(&cmd.input[cmd.cursor], &cmd.input[cmd.cursor + 1], strlen(&cmd.input[cmd.cursor + 1]) + 1); 1376 | } 1377 | break; 1378 | #if MBED_CONF_CMDLINE_ENABLE_HISTORY 1379 | case ('4'): //end-of-line # End key 1380 | cmd.cursor = strlen(cmd.input); 1381 | break; 1382 | case ('5'): //beginning-of-history # PageUp key 1383 | cmd_goto_end_of_history(); 1384 | break; 1385 | case ('6'): //end-of-history # PageDown key 1386 | cmd_goto_beginning_of_history(); 1387 | break; 1388 | #endif 1389 | default: 1390 | break; 1391 | } 1392 | #if MBED_CONF_CMDLINE_ENABLE_ESCAPE_HANDLING == 1 1393 | } else if (isprint(u_data)) { //IS_NUMBER || IS_CONTROL 1394 | cmd.escape[cmd.escape_index++] = u_data; 1395 | return; 1396 | } 1397 | cmd.escaping = false; 1398 | #endif 1399 | cmd_output(); 1400 | return; 1401 | } 1402 | #endif 1403 | 1404 | #if MBED_CONF_CMDLINE_ENABLE_HISTORY 1405 | static void cmd_goto_end_of_history(void) 1406 | { 1407 | // handle new input if any and verify that 1408 | // it is not already in beginning of history or current position 1409 | bool allowStore = strlen(cmd.input) != 0; //avoid store empty lines to history 1410 | cmd_history_t *entry_ptr; 1411 | if (cmd.history > 0 && allowStore) { 1412 | entry_ptr = cmd_history_find(cmd.history); 1413 | if (entry_ptr) { 1414 | if (strcmp(entry_ptr->command_ptr, cmd.input) == 0) { 1415 | // current history contains contains same text as input 1416 | allowStore = false; 1417 | } 1418 | } 1419 | } else if (allowStore && (entry_ptr = cmd_history_find(0)) != NULL) { 1420 | if (strcmp(entry_ptr->command_ptr, cmd.input) == 0) { 1421 | //beginning of history was same text as input 1422 | allowStore = false; 1423 | } 1424 | } 1425 | if (allowStore) { 1426 | cmd_history_save(0); // new is saved to place 0 1427 | cmd_history_save(-1); // new is created to the current one 1428 | } 1429 | cmd_history_t *cmd_ptr = ns_list_get_last(&cmd.history_list); 1430 | cmd_set_input(cmd_ptr->command_ptr, 0); 1431 | cmd.history = ns_list_count(&cmd.history_list) - 1; 1432 | } 1433 | 1434 | static void cmd_goto_beginning_of_history(void) 1435 | { 1436 | cmd_history_t *cmd_ptr = ns_list_get_first(&cmd.history_list); 1437 | cmd_set_input(cmd_ptr->command_ptr, 0); 1438 | cmd.history = 0; 1439 | } 1440 | #endif 1441 | 1442 | static void cmd_reset_tab(void) 1443 | { 1444 | cmd.tab_lookup = 0; 1445 | cmd.tab_lookup_cmd_n = 0; 1446 | cmd.tab_lookup_n = 0; 1447 | } 1448 | 1449 | void cmd_char_input(int16_t u_data) 1450 | { 1451 | if (cmd.prev_cr && u_data == '\n') { 1452 | // ignore \n if previous character was \r -> 1453 | // that triggers execute so \n does not need to anymore 1454 | cmd.prev_cr = false; // unset to ensure we doesn't go here anymore 1455 | return; 1456 | } 1457 | /*Handle passthrough*/ 1458 | if (cmd.passthrough_fnc != NULL) { 1459 | cmd.passthrough_fnc(u_data); 1460 | return; 1461 | } 1462 | #if MBED_CONF_CMDLINE_ENABLE_ESCAPE_HANDLING == 1 1463 | /*handle ecape command*/ 1464 | if (cmd.escaping == true) { 1465 | cmd_escape_read(u_data); 1466 | return; 1467 | } 1468 | #endif 1469 | 1470 | tr_debug("input char: %02x '%c', cursor: %i, input: \"%s\"", u_data, (isprint(u_data) ? u_data : ' '), cmd.cursor, cmd.input); 1471 | 1472 | /*Normal character input*/ 1473 | if (u_data == '\r' || u_data == '\n') { 1474 | cmd.prev_cr = u_data == '\r'; 1475 | cmd_reset_tab(); 1476 | if (strlen(cmd.input) == 0) { 1477 | if (cmd.echo) { 1478 | cmd_printf("\r\n"); 1479 | cmd_output(); 1480 | } 1481 | } else { 1482 | if (cmd.echo) { 1483 | cmd_printf("\r\n"); 1484 | } 1485 | cmd_execute(); 1486 | } 1487 | } else if (u_data == ETX || u_data == CAN) { 1488 | //ctrl+c (End of text) or ctrl+x (cancel) 1489 | cmd_reset_tab(); 1490 | cmd_line_clear(0); 1491 | if (!cmd.idle) { 1492 | cmd_ready(CMDLINE_RETCODE_FAIL); 1493 | } 1494 | if (cmd.echo) { 1495 | cmd_output(); 1496 | } 1497 | #if MBED_CONF_CMDLINE_ENABLE_ESCAPE_HANDLING == 1 1498 | } else if (u_data == ESC) { 1499 | cmd_escape_start(); 1500 | } else if (u_data == BS || u_data == DEL) { 1501 | cmd_reset_tab(); 1502 | cmd.cursor--; 1503 | if (cmd.cursor < 0) { 1504 | cmd.cursor = 0; 1505 | return; 1506 | } 1507 | memmove(&cmd.input[cmd.cursor], &cmd.input[cmd.cursor + 1], strlen(&cmd.input[cmd.cursor + 1]) + 1); 1508 | if (cmd.echo) { 1509 | cmd_output(); 1510 | } 1511 | } else if (u_data == ETB) { 1512 | //ctrl+w (End of xmit block) 1513 | tr_debug("ctrl+w - remove last word to cursor"); 1514 | cmd_clear_last_word(); 1515 | if (cmd.echo) { 1516 | cmd_output(); 1517 | } 1518 | } else if (u_data == TAB) { 1519 | bool inc = false; 1520 | if (cmd.tab_lookup > 0) { 1521 | cmd.cursor = cmd.tab_lookup; 1522 | cmd.input[cmd.tab_lookup] = 0; 1523 | cmd.tab_lookup_cmd_n++; 1524 | inc = true; 1525 | } else { 1526 | cmd.tab_lookup = strlen(cmd.input); 1527 | } 1528 | 1529 | if (!cmd_tab_lookup()) { 1530 | if (inc) { 1531 | cmd.tab_lookup_cmd_n--; 1532 | } 1533 | memset(cmd.input + cmd.tab_lookup, 0, MBED_CONF_CMDLINE_MAX_LINE_LENGTH - cmd.tab_lookup); 1534 | } 1535 | if (cmd.echo) { 1536 | cmd_output(); 1537 | } 1538 | 1539 | } else if (iscntrl(u_data)) { 1540 | if (cmd.ctrl_fnc) { 1541 | cmd.ctrl_fnc(u_data); 1542 | } 1543 | #endif 1544 | } else { 1545 | cmd_reset_tab(); 1546 | tr_deep("cursor: %d, inputlen: %lu, u_data: %c\r\n", cmd.cursor, strlen(cmd.input), u_data); 1547 | if ((strlen(cmd.input) >= MBED_CONF_CMDLINE_MAX_LINE_LENGTH - 1) || 1548 | (cmd.cursor >= MBED_CONF_CMDLINE_MAX_LINE_LENGTH - 1)) { 1549 | tr_warn("input buffer full"); 1550 | if (cmd.echo) { 1551 | cmd_output(); 1552 | } 1553 | return; 1554 | } 1555 | if (cmd.insert) { 1556 | memmove(&cmd.input[cmd.cursor + 1], &cmd.input[cmd.cursor], strlen(&cmd.input[cmd.cursor]) + 1); 1557 | } 1558 | cmd.input[cmd.cursor++] = u_data; 1559 | if (cmd.echo) { 1560 | cmd_output(); 1561 | } 1562 | } 1563 | } 1564 | 1565 | static int check_variable_keylookup_size(char **key, int *keysize) 1566 | { 1567 | if (cmd.cursor > 0 && cmd.tab_lookup > 0) { 1568 | //printf("tab_lookup: %i\r\n", cmd.tab_lookup); 1569 | char *ptr = cmd.input + cmd.tab_lookup; 1570 | do { 1571 | //printf("varkey lookup: %c\r\n", *ptr); 1572 | if (*ptr == ' ') { 1573 | return 0; 1574 | } 1575 | if (*ptr == '$') { 1576 | int varlen = cmd.tab_lookup - (ptr - cmd.input) - 1; 1577 | *key = ptr; 1578 | *keysize = varlen; 1579 | //printf("varkey size: %i\r\n", varlen); 1580 | return (ptr - cmd.input); 1581 | } 1582 | ptr--; 1583 | } while (ptr > cmd.input); 1584 | } 1585 | return 0; 1586 | } 1587 | 1588 | bool cmd_tab_lookup(void) 1589 | { 1590 | int len = strlen(cmd.input); 1591 | if (len == 0) { 1592 | return false; 1593 | } 1594 | char *variable_keypart; 1595 | int lookupSize; 1596 | int varpos = check_variable_keylookup_size(&variable_keypart, &lookupSize); 1597 | 1598 | const char *str = NULL; 1599 | if (varpos) { 1600 | str = cmd_input_lookup_var(variable_keypart + 1, lookupSize, cmd.tab_lookup_cmd_n); 1601 | if (str) { 1602 | cmd_set_input(str, varpos + 1); 1603 | return true; 1604 | } 1605 | } else { 1606 | str = cmd_input_lookup(cmd.input, len, cmd.tab_lookup_cmd_n); 1607 | if (str != NULL) { 1608 | cmd_set_input(str, 0); 1609 | return true; 1610 | } 1611 | } 1612 | return false; 1613 | } 1614 | 1615 | static void cmd_move_cursor_to_last_space(void) 1616 | { 1617 | if (cmd.cursor) { 1618 | cmd.cursor--; 1619 | } else { 1620 | return; 1621 | } 1622 | const char *last_space = find_last_space(cmd.input + cmd.cursor, cmd.input); 1623 | if (last_space) { 1624 | cmd.cursor = last_space - cmd.input; 1625 | } else { 1626 | cmd.cursor = 0; 1627 | } 1628 | } 1629 | 1630 | static void cmd_move_cursor_to_next_space(void) 1631 | { 1632 | while (cmd.input[cmd.cursor] == ' ') { 1633 | cmd.cursor++; 1634 | } 1635 | const char *next_space = strchr(cmd.input + cmd.cursor, ' '); 1636 | if (next_space) { 1637 | cmd.cursor = next_space - cmd.input; 1638 | } else { 1639 | cmd.cursor = (int)strlen(cmd.input); 1640 | } 1641 | } 1642 | 1643 | static void cmd_clear_last_word(void) 1644 | { 1645 | if (!cmd.cursor) { 1646 | return; 1647 | } 1648 | char *ptr = cmd.input + cmd.cursor - 1; 1649 | while (*ptr == ' ' && ptr >= cmd.input) { 1650 | ptr--; 1651 | } 1652 | const char *last_space = find_last_space(ptr, cmd.input); 1653 | if (last_space) { 1654 | memmove((void *)last_space, &cmd.input[cmd.cursor], strlen(cmd.input + cmd.cursor) + 1); 1655 | cmd.cursor = last_space - cmd.input; 1656 | } else { 1657 | memmove((void *)cmd.input, &cmd.input[cmd.cursor], strlen(cmd.input + cmd.cursor) + 1); 1658 | cmd.cursor = 0; 1659 | } 1660 | } 1661 | 1662 | void cmd_output(void) 1663 | { 1664 | #if MBED_CONF_CMDLINE_ENABLE_ESCAPE_HANDLING == 1 1665 | if (cmd.vt100_on && cmd.idle) { 1666 | int curpos = (int)strlen(cmd.input) - cmd.cursor + 1; 1667 | cmd_printf(CR_S CLEAR_ENTIRE_LINE "%s%s " MOVE_CURSOR_LEFT_N_CHAR, 1668 | cmdline_get_prompt(), cmd.input, curpos); 1669 | } 1670 | #endif 1671 | } 1672 | 1673 | void cmd_echo_off(void) 1674 | { 1675 | cmd_echo(false); 1676 | } 1677 | 1678 | void cmd_echo_on(void) 1679 | { 1680 | cmd_echo(true); 1681 | } 1682 | 1683 | // alias 1684 | int replace_alias(char *str, const char *old_str, const char *new_str) 1685 | { 1686 | #if MBED_CONF_CMDLINE_ENABLE_ALIASES == 0 1687 | (void)str; 1688 | (void)old_str; 1689 | (void)new_str; 1690 | #else 1691 | int old_len = strlen(old_str), 1692 | new_len = strlen(new_str); 1693 | if ((strncmp(str, old_str, old_len) == 0) && 1694 | ((str[ old_len ] == ' ') || (str[ old_len ] == 0) || 1695 | (str[ old_len ] == ';') || (str[ old_len ] == '&'))) { 1696 | memmove(str + new_len, str + old_len, strlen(str + old_len) + 1); 1697 | memcpy(str, new_str, new_len); 1698 | return new_len - old_len; 1699 | } 1700 | #endif 1701 | return 0; 1702 | } 1703 | 1704 | static void cmd_replace_alias(char *input) 1705 | { 1706 | #if MBED_CONF_CMDLINE_ENABLE_ALIASES == 0 1707 | (void)input; 1708 | #else 1709 | ns_list_foreach(cmd_alias_t, cur_ptr, &cmd.alias_list) { 1710 | replace_alias(input, cur_ptr->name_ptr, cur_ptr->value_ptr); 1711 | } 1712 | #endif 1713 | } 1714 | 1715 | //variable 1716 | static void replace_variable(char *str, cmd_variable_t *variable_ptr) 1717 | { 1718 | const char *name = variable_ptr->name_ptr; 1719 | int name_len = strlen(variable_ptr->name_ptr); 1720 | char *value; 1721 | char valueLocal[11]; 1722 | if (variable_ptr->type == VALUE_TYPE_STR) { 1723 | value = variable_ptr->value.ptr; 1724 | } else { 1725 | value = valueLocal; 1726 | snprintf(value, 11, "%d", variable_ptr->value.i); 1727 | } 1728 | char *tmp = MEM_ALLOC(name_len + 2); 1729 | if (tmp == NULL) { 1730 | tr_error("mem alloc failed in replace_variable"); 1731 | return; 1732 | } 1733 | tmp[0] = '$'; 1734 | strcpy(tmp + 1, name); 1735 | replace_string(str, MBED_CONF_CMDLINE_MAX_LINE_LENGTH, tmp, value); 1736 | MEM_FREE(tmp); 1737 | } 1738 | 1739 | static void cmd_replace_variables(char *input) 1740 | { 1741 | #if MBED_CONF_CMDLINE_ENABLE_INTERNAL_COMMANDS == 0 1742 | (void)input; 1743 | #else 1744 | ns_list_foreach(cmd_variable_t, cur_ptr, &cmd.variable_list) { 1745 | replace_variable(input, cur_ptr); 1746 | } 1747 | #endif 1748 | } 1749 | 1750 | //history 1751 | #if MBED_CONF_CMDLINE_ENABLE_HISTORY == 1 1752 | static void cmd_history_item_delete(cmd_history_t *entry_ptr) 1753 | { 1754 | ns_list_remove(&cmd.history_list, entry_ptr); 1755 | MEM_FREE(entry_ptr->command_ptr); 1756 | MEM_FREE(entry_ptr); 1757 | } 1758 | 1759 | static cmd_history_t *cmd_history_find(int16_t index) 1760 | { 1761 | cmd_history_t *entry_ptr = NULL; 1762 | int16_t count = 0; 1763 | ns_list_foreach(cmd_history_t, cur_ptr, &cmd.history_list) { 1764 | if (count == index) { 1765 | entry_ptr = cur_ptr; 1766 | break; 1767 | } 1768 | count++; 1769 | } 1770 | return entry_ptr; 1771 | } 1772 | 1773 | static void cmd_history_clean_overflow(void) 1774 | { 1775 | while (ns_list_count(&cmd.history_list) > cmd.history_max_count) { 1776 | cmd_history_t *cmd_ptr = ns_list_get_last(&cmd.history_list); 1777 | tr_debug("removing older history (%s)", cmd_ptr->command_ptr); 1778 | cmd_history_item_delete(cmd_ptr); 1779 | } 1780 | } 1781 | 1782 | static void cmd_history_clean(void) 1783 | { 1784 | while (ns_list_count(&cmd.history_list) > 0) { 1785 | tr_debug("removing older history"); 1786 | cmd_history_item_delete(ns_list_get_last(&cmd.history_list)); 1787 | } 1788 | } 1789 | 1790 | static void cmd_history_save(int16_t index) 1791 | { 1792 | /*if entry true save it to first item which is the one currently edited*/ 1793 | cmd_history_t *entry_ptr; 1794 | int16_t len; 1795 | 1796 | len = strlen(cmd.input); 1797 | 1798 | tr_debug("saving history item %d", index); 1799 | entry_ptr = cmd_history_find(index); 1800 | 1801 | if (entry_ptr == NULL) { 1802 | /*new entry*/ 1803 | entry_ptr = (cmd_history_t *)MEM_ALLOC(sizeof(cmd_history_t)); 1804 | if (entry_ptr == NULL) { 1805 | tr_error("mem alloc failed in cmd_history_save"); 1806 | return; 1807 | } 1808 | entry_ptr->command_ptr = NULL; 1809 | ns_list_add_to_start(&cmd.history_list, entry_ptr); 1810 | } 1811 | 1812 | if (entry_ptr->command_ptr != NULL) { 1813 | MEM_FREE(entry_ptr->command_ptr); 1814 | } 1815 | entry_ptr->command_ptr = (char *)MEM_ALLOC(len + 1); 1816 | if (entry_ptr->command_ptr == NULL) { 1817 | tr_error("mem alloc failed in cmd_history_save command_ptr"); 1818 | cmd_history_item_delete(entry_ptr); 1819 | return; 1820 | } 1821 | strcpy(entry_ptr->command_ptr, cmd.input); 1822 | 1823 | cmd_history_clean_overflow(); 1824 | } 1825 | 1826 | static void cmd_history_get(uint16_t index) 1827 | { 1828 | cmd_history_t *entry_ptr; 1829 | 1830 | tr_debug("getting history item %d", index); 1831 | 1832 | entry_ptr = cmd_history_find(index); 1833 | 1834 | if (entry_ptr != NULL) { 1835 | memset(cmd.input, 0, MBED_CONF_CMDLINE_MAX_LINE_LENGTH); 1836 | cmd_set_input(entry_ptr->command_ptr, 0); 1837 | } 1838 | } 1839 | #endif 1840 | 1841 | static void cmd_line_clear(int from) 1842 | { 1843 | memset(cmd.input + from, 0, MBED_CONF_CMDLINE_MAX_LINE_LENGTH - from); 1844 | tr_deep("cmd.input cleared from %d: %s", from, cmd.input); 1845 | cmd.cursor = from; 1846 | } 1847 | 1848 | static void cmd_execute(void) 1849 | { 1850 | #if MBED_CONF_CMDLINE_ENABLE_HISTORY 1851 | if (strlen(cmd.input) != 0) { 1852 | bool noduplicates = true; 1853 | cmd_history_t *entry_ptr = cmd_history_find(0); 1854 | if (entry_ptr) { 1855 | if (strcmp(entry_ptr->command_ptr, cmd.input) == 0) { 1856 | noduplicates = false; 1857 | } 1858 | } 1859 | if (noduplicates) { 1860 | cmd_history_save(0); // new is saved to place 0 1861 | cmd_history_save(-1); // new is created to the current one 1862 | } 1863 | } 1864 | cmd.history = 0; 1865 | #endif 1866 | tr_deep("cmd_execute('%s') ", cmd.input); 1867 | cmd_exe(cmd.input); 1868 | cmd_line_clear(0); 1869 | } 1870 | 1871 | static cmd_alias_t *alias_find(const char *alias) 1872 | { 1873 | #if MBED_CONF_CMDLINE_ENABLE_ALIASES == 0 1874 | (void)alias; 1875 | return NULL; 1876 | #else 1877 | cmd_alias_t *alias_ptr = NULL; 1878 | if (alias == NULL || strlen(alias) == 0) { 1879 | tr_error("alias_find invalid parameters"); 1880 | return NULL; 1881 | } 1882 | 1883 | ns_list_foreach(cmd_alias_t, cur_ptr, &cmd.alias_list) { 1884 | if (strcmp(alias, cur_ptr->name_ptr) == 0) { 1885 | alias_ptr = cur_ptr; 1886 | break; 1887 | } 1888 | } 1889 | return alias_ptr; 1890 | #endif 1891 | } 1892 | 1893 | static cmd_alias_t *alias_find_n(char *alias, int aliaslength, int n) 1894 | { 1895 | cmd_alias_t *alias_ptr = NULL; 1896 | if (alias == NULL || strlen(alias) == 0) { 1897 | tr_error("alias_find invalid parameters"); 1898 | return NULL; 1899 | } 1900 | #if MBED_CONF_CMDLINE_ENABLE_ALIASES == 0 1901 | (void)aliaslength; 1902 | (void)n; 1903 | #else 1904 | int i = 0; 1905 | ns_list_foreach(cmd_alias_t, cur_ptr, &cmd.alias_list) { 1906 | if (strncmp(alias, cur_ptr->name_ptr, aliaslength) == 0) { 1907 | if (i == n) { 1908 | alias_ptr = cur_ptr; 1909 | break; 1910 | } 1911 | i++; 1912 | } 1913 | } 1914 | #endif 1915 | return alias_ptr; 1916 | } 1917 | 1918 | static cmd_variable_t *variable_find(char *variable) 1919 | { 1920 | cmd_variable_t *variable_ptr = NULL; 1921 | if (variable == NULL || strlen(variable) == 0) { 1922 | tr_error("variable_find invalid parameters"); 1923 | return NULL; 1924 | } 1925 | #if MBED_CONF_CMDLINE_ENABLE_INTERNAL_COMMANDS == 1 1926 | ns_list_foreach(cmd_variable_t, cur_ptr, &cmd.variable_list) { 1927 | if (strcmp(variable, cur_ptr->name_ptr) == 0) { 1928 | variable_ptr = cur_ptr; 1929 | break; 1930 | } 1931 | } 1932 | #endif 1933 | return variable_ptr; 1934 | } 1935 | 1936 | static cmd_variable_t *variable_find_n(char *variable, int length, int n) 1937 | { 1938 | cmd_variable_t *variable_ptr = NULL; 1939 | if (variable == NULL || strlen(variable) == 0) { 1940 | tr_error("variable_find invalid parameters"); 1941 | return NULL; 1942 | } 1943 | #if MBED_CONF_CMDLINE_ENABLE_INTERNAL_COMMANDS == 0 1944 | (void)length; 1945 | (void)n; 1946 | #else 1947 | int i = 0; 1948 | ns_list_foreach(cmd_variable_t, cur_ptr, &cmd.variable_list) { 1949 | if (strncmp(variable, cur_ptr->name_ptr, length) == 0) { 1950 | if (i == n) { 1951 | variable_ptr = cur_ptr; 1952 | break; 1953 | } 1954 | i++; 1955 | } 1956 | } 1957 | #endif 1958 | return variable_ptr; 1959 | } 1960 | static void cmd_alias_print_all(void) 1961 | { 1962 | #if MBED_CONF_CMDLINE_ENABLE_ALIASES == 1 1963 | ns_list_foreach(cmd_alias_t, cur_ptr, &cmd.alias_list) { 1964 | if (cur_ptr->name_ptr != NULL) { 1965 | cmd_printf("%-18s'%s'\r\n", cur_ptr->name_ptr, cur_ptr->value_ptr ? cur_ptr->value_ptr : ""); 1966 | } 1967 | } 1968 | return; 1969 | #endif 1970 | } 1971 | 1972 | static void cmd_variable_print_all(void) 1973 | { 1974 | #if MBED_CONF_CMDLINE_ENABLE_INTERNAL_COMMANDS == 1 1975 | ns_list_foreach(cmd_variable_t, cur_ptr, &cmd.variable_list) { 1976 | if (cur_ptr->type == VALUE_TYPE_STR) { 1977 | cmd_printf("%s='%s'\r\n", cur_ptr->name_ptr, cur_ptr->value.ptr ? cur_ptr->value.ptr : ""); 1978 | } else if (cur_ptr->type == VALUE_TYPE_INT) { 1979 | cmd_printf("%s=%d\r\n", cur_ptr->name_ptr, cur_ptr->value.i); 1980 | } 1981 | } 1982 | return; 1983 | #endif 1984 | } 1985 | 1986 | void cmd_alias_add(const char *alias, const char *value) 1987 | { 1988 | #if MBED_CONF_CMDLINE_ENABLE_ALIASES == 0 1989 | (void)alias; 1990 | (void)value; 1991 | #else 1992 | cmd_alias_t *alias_ptr; 1993 | if (alias == NULL || strlen(alias) == 0) { 1994 | tr_warn("cmd_alias_add invalid parameters"); 1995 | return; 1996 | } 1997 | alias_ptr = alias_find(alias); 1998 | if (alias_ptr == NULL) { 1999 | if (value == NULL) { 2000 | return; // no need to add new null one 2001 | } 2002 | if (strlen(value) == 0) { 2003 | return; // no need to add new empty one 2004 | } 2005 | alias_ptr = (cmd_alias_t *)MEM_ALLOC(sizeof(cmd_alias_t)); 2006 | if (alias_ptr == NULL) { 2007 | tr_error("Mem alloc fail in cmd_alias_add"); 2008 | return; 2009 | } 2010 | alias_ptr->name_ptr = (char *)MEM_ALLOC(strlen(alias) + 1); 2011 | if (alias_ptr->name_ptr == NULL) { 2012 | MEM_FREE(alias_ptr); 2013 | tr_error("Mem alloc fail in cmd_alias_add name_ptr"); 2014 | return; 2015 | } 2016 | ns_list_add_to_end(&cmd.alias_list, alias_ptr); 2017 | strcpy(alias_ptr->name_ptr, alias); 2018 | alias_ptr->value_ptr = NULL; 2019 | } 2020 | if (value == NULL || strlen(value) == 0) { 2021 | // delete this one 2022 | ns_list_remove(&cmd.alias_list, alias_ptr); 2023 | MEM_FREE(alias_ptr->name_ptr); 2024 | MEM_FREE(alias_ptr->value_ptr); 2025 | MEM_FREE(alias_ptr); 2026 | } else { 2027 | // add new or modify 2028 | if (alias_ptr->value_ptr != NULL) { 2029 | MEM_FREE(alias_ptr->value_ptr); 2030 | } 2031 | alias_ptr->value_ptr = (char *)MEM_ALLOC(strlen(value) + 1); 2032 | if (alias_ptr->value_ptr == NULL) { 2033 | cmd_alias_add(alias, NULL); 2034 | tr_error("Mem alloc fail in cmd_alias_add value_ptr"); 2035 | return; 2036 | } 2037 | strcpy(alias_ptr->value_ptr, value); 2038 | } 2039 | return; 2040 | #endif 2041 | } 2042 | 2043 | static cmd_variable_t *cmd_variable_add_prepare(char *variable, char *value) 2044 | { 2045 | #if MBED_CONF_CMDLINE_ENABLE_INTERNAL_COMMANDS == 0 2046 | (void)variable; 2047 | (void)value; 2048 | return NULL; 2049 | #else 2050 | if (variable == NULL || strlen(variable) == 0) { 2051 | tr_warn("cmd_variable_add invalid parameters"); 2052 | return NULL; 2053 | } 2054 | cmd_variable_t *variable_ptr = variable_find(variable); 2055 | if (variable_ptr == NULL) { 2056 | if (value == NULL) { 2057 | return NULL; // adding null variable 2058 | } 2059 | if (strlen(value) == 0) { 2060 | return NULL; // no need to add new empty one 2061 | } 2062 | variable_ptr = (cmd_variable_t *)MEM_ALLOC(sizeof(cmd_variable_t)); 2063 | if (variable_ptr == NULL) { 2064 | tr_error("Mem alloc failed cmd_variable_add"); 2065 | return NULL; 2066 | } 2067 | variable_ptr->name_ptr = (char *)MEM_ALLOC(strlen(variable) + 1); 2068 | if (variable_ptr->name_ptr == NULL) { 2069 | MEM_FREE(variable_ptr); 2070 | tr_error("Mem alloc failed cmd_variable_add name_ptr"); 2071 | return NULL; 2072 | } 2073 | 2074 | ns_list_add_to_end(&cmd.variable_list, variable_ptr); 2075 | strcpy(variable_ptr->name_ptr, variable); 2076 | variable_ptr->value.ptr = NULL; 2077 | } 2078 | if (value == NULL || strlen(value) == 0) { 2079 | // delete this one 2080 | tr_debug("Remove variable: %s", variable); 2081 | ns_list_remove(&cmd.variable_list, variable_ptr); 2082 | MEM_FREE(variable_ptr->name_ptr); 2083 | if (variable_ptr->type == VALUE_TYPE_STR) { 2084 | MEM_FREE(variable_ptr->value.ptr); 2085 | } 2086 | MEM_FREE(variable_ptr); 2087 | return NULL; 2088 | } 2089 | return variable_ptr; 2090 | #endif 2091 | } 2092 | 2093 | void cmd_variable_add_int(char *variable, int value) 2094 | { 2095 | cmd_variable_t *variable_ptr = cmd_variable_add_prepare(variable, " "); 2096 | if (variable_ptr == NULL) { 2097 | return; 2098 | } 2099 | if (variable_ptr->value.ptr != NULL && 2100 | variable_ptr->type == VALUE_TYPE_STR) { 2101 | // free memory 2102 | MEM_FREE(variable_ptr->value.ptr); 2103 | } 2104 | variable_ptr->type = VALUE_TYPE_INT; 2105 | variable_ptr->value.i = value; 2106 | } 2107 | 2108 | void cmd_variable_add(char *variable, char *value) 2109 | { 2110 | cmd_variable_t *variable_ptr = cmd_variable_add_prepare(variable, value); 2111 | if (variable_ptr == NULL || value == NULL) { 2112 | return; 2113 | } 2114 | int value_len = strlen(value); 2115 | replace_string(value, value_len, "\\n", "\n"); 2116 | replace_string(value, value_len, "\\r", "\r"); 2117 | 2118 | // add new or modify 2119 | int new_len = strlen(value) + 1; 2120 | int old_len = 0; 2121 | if (variable_ptr->value.ptr != NULL && 2122 | variable_ptr->type == VALUE_TYPE_STR) { 2123 | // free memory if required 2124 | old_len = strlen(variable_ptr->value.ptr) + 1; 2125 | if (old_len != new_len) { 2126 | MEM_FREE(variable_ptr->value.ptr); 2127 | } 2128 | } 2129 | if (old_len != new_len) { 2130 | variable_ptr->value.ptr = (char *)MEM_ALLOC(new_len); 2131 | if (variable_ptr->value.ptr == NULL) { 2132 | cmd_variable_add(variable, NULL); 2133 | tr_error("Mem alloc failed cmd_variable_add value_ptr"); 2134 | return; 2135 | } 2136 | } 2137 | variable_ptr->type = VALUE_TYPE_STR; 2138 | strcpy(variable_ptr->value.ptr, value); 2139 | return; 2140 | } 2141 | 2142 | static bool is_cmdline_commands(char *command) 2143 | { 2144 | if ((strncmp(command, "alias", 5) == 0) || 2145 | (strcmp(command, "echo") == 0) || 2146 | (strcmp(command, "set") == 0) || 2147 | (strcmp(command, "clear") == 0) || 2148 | (strcmp(command, "help") == 0)) { 2149 | return true; 2150 | } 2151 | return false; 2152 | } 2153 | 2154 | /*Basic commands for cmd line 2155 | * alias 2156 | * echo 2157 | * set 2158 | * clear 2159 | * help 2160 | */ 2161 | int alias_command(int argc, char *argv[]) 2162 | { 2163 | if (argc == 1) { 2164 | // print all alias 2165 | cmd_printf("alias:\r\n"); 2166 | cmd_alias_print_all(); 2167 | } else if (argc == 2) { 2168 | // print alias 2169 | if (is_cmdline_commands(argv[1])) { 2170 | cmd_printf("Cannot overwrite default commands with alias\r\n"); 2171 | return -1; 2172 | } 2173 | tr_debug("Deleting alias %s", argv[1]); 2174 | cmd_alias_add(argv[1], NULL); 2175 | } else { 2176 | // set alias 2177 | tr_debug("Setting alias %s = %s", argv[1], argv[2]); 2178 | cmd_alias_add(argv[1], argv[2]); 2179 | } 2180 | return 0; 2181 | } 2182 | 2183 | int unset_command(int argc, char *argv[]) 2184 | { 2185 | if (argc != 2) { 2186 | return CMDLINE_RETCODE_INVALID_PARAMETERS; 2187 | } 2188 | tr_debug("Deleting variable %s", argv[1]); 2189 | cmd_variable_add(argv[1], NULL); 2190 | return 0; 2191 | } 2192 | 2193 | #if MBED_CONF_CMDLINE_USE_DUMMY_SET_ECHO_COMMANDS == 1 2194 | int echo_command(int argc, char *argv[]) 2195 | { 2196 | (void)argc; 2197 | (void)argv; 2198 | return 0; 2199 | } 2200 | int set_command(int argc, char *argv[]) 2201 | { 2202 | (void)argc; 2203 | (void)argv; 2204 | return 0; 2205 | } 2206 | #else 2207 | int set_command(int argc, char *argv[]) 2208 | { 2209 | if (argc == 1) { 2210 | // print all alias 2211 | cmd_printf("variables:\r\n"); 2212 | cmd_variable_print_all(); 2213 | } else if (argc == 2) { 2214 | char *separator_ptr = strchr(argv[1], '='); 2215 | if (!separator_ptr) { 2216 | return CMDLINE_RETCODE_INVALID_PARAMETERS; 2217 | } 2218 | *separator_ptr = 0; 2219 | cmd_variable_add(argv[1], separator_ptr + 1); 2220 | } else { 2221 | // set alias 2222 | tr_debug("Setting variable %s = %s", argv[1], argv[2]); 2223 | //handle special cases: vt100 on|off 2224 | bool state; 2225 | #if MBED_CONF_CMDLINE_ENABLE_ESCAPE_HANDLING == 1 2226 | if (cmd_parameter_bool(argc, argv, "--vt100", &state)) { 2227 | cmd.vt100_on = state; 2228 | return 0; 2229 | } 2230 | #endif 2231 | if (cmd_parameter_bool(argc, argv, "--retcode", &state)) { 2232 | cmd_variable_add(VAR_RETFMT, state ? DEFAULT_RETFMT : NULL); 2233 | return 0; 2234 | } 2235 | char *str; 2236 | if (cmd_parameter_val(argc, argv, "--retfmt", &str)) { 2237 | cmd_variable_add(VAR_RETFMT, str); 2238 | return 0; 2239 | } 2240 | cmd_variable_add(argv[1], argv[2]); 2241 | } 2242 | return 0; 2243 | } 2244 | 2245 | int echo_command(int argc, char *argv[]) 2246 | { 2247 | bool printEcho = false; 2248 | if (argc == 1) { 2249 | printEcho = true; 2250 | } else if (argc == 2) { 2251 | if (strcmp(argv[1], "off") == 0) { 2252 | cmd_echo(false); 2253 | printEcho = true; 2254 | } else if (strcmp(argv[1], "on") == 0) { 2255 | cmd_echo(true); 2256 | printEcho = true; 2257 | } 2258 | } 2259 | if (printEcho) { 2260 | cmd_printf("ECHO is %s\r\n", cmd.echo ? "on" : "off"); 2261 | } else { 2262 | for (int n = 1; n < argc; n++) { 2263 | tr_deep("ECHO: %s\r\n", argv[n]); 2264 | cmd_printf("%s ", argv[n]); 2265 | } 2266 | cmd_printf("\r\n"); 2267 | } 2268 | return 0; 2269 | } 2270 | #endif 2271 | 2272 | int clear_command(int argc, char *argv[]) 2273 | { 2274 | (void)argc; 2275 | (void)argv; 2276 | cmd_echo(true); 2277 | cmd_init_screen(); 2278 | return 0; 2279 | } 2280 | 2281 | int help_command(int argc, char *argv[]) 2282 | { 2283 | cmd_printf("Commands:\r\n"); 2284 | if (argc == 1) { 2285 | ns_list_foreach(cmd_command_t, cur_ptr, &cmd.command_list) { 2286 | cmd_printf("%-16s%s\r\n", cur_ptr->name_ptr, (cur_ptr->info_ptr ? cur_ptr->info_ptr : "")); 2287 | } 2288 | } else if (argc == 2) { 2289 | cmd_command_t *cmd_ptr = cmd_find(argv[1]); 2290 | if (cmd_ptr) { 2291 | cmd_printf("Command: %s\r\n", cmd_ptr->name_ptr); 2292 | if (cmd_ptr->man_ptr) { 2293 | cmd_printf("%s\r\n", cmd_ptr->man_ptr); 2294 | } else if (cmd_ptr->info_ptr) { 2295 | cmd_printf("%s\r\n", cmd_ptr->info_ptr); 2296 | } 2297 | } else { 2298 | cmd_printf("Command '%s' not found", argv[1]); 2299 | } 2300 | } 2301 | return 0; 2302 | } 2303 | 2304 | int true_command(int argc, char *argv[]) 2305 | { 2306 | (void)argc; 2307 | (void)argv; 2308 | return CMDLINE_RETCODE_SUCCESS; 2309 | } 2310 | 2311 | int false_command(int argc, char *argv[]) 2312 | { 2313 | (void)argc; 2314 | (void)argv; 2315 | return CMDLINE_RETCODE_FAIL; 2316 | } 2317 | 2318 | int history_command(int argc, char *argv[]) 2319 | { 2320 | #if MBED_CONF_CMDLINE_ENABLE_HISTORY 2321 | if (argc == 1) { 2322 | int history_size = (int)ns_list_count(&cmd.history_list); 2323 | cmd_printf("History [%i/%i]:\r\n", history_size - 1, cmd.history_max_count - 1); 2324 | int i = 0; 2325 | ns_list_foreach_reverse(cmd_history_t, cur_ptr, &cmd.history_list) { 2326 | if (i != history_size - 1) { 2327 | cmd_printf("[%i]: %s\r\n", i++, cur_ptr->command_ptr); 2328 | } 2329 | } 2330 | } else if (argc == 2) { 2331 | if (strcmp(argv[1], "clear") == 0) { 2332 | cmd_history_clean(); 2333 | } else { 2334 | cmd_history_size(strtoul(argv[1], 0, 10)); 2335 | } 2336 | } 2337 | #else 2338 | (void)argc; 2339 | (void)argv; 2340 | #endif 2341 | return 0; 2342 | } 2343 | 2344 | /** Parameter helping functions 2345 | */ 2346 | int cmd_parameter_index(int argc, char *argv[], const char *key) 2347 | { 2348 | int i = 0; 2349 | for (i = 1; i < argc; i++) { 2350 | if (strcmp(argv[i], key) == 0) { 2351 | return i; 2352 | } 2353 | } 2354 | return -1; 2355 | } 2356 | 2357 | bool cmd_has_option(int argc, char *argv[], const char *key) 2358 | { 2359 | int i = 0; 2360 | for (i = 1; i < argc; i++) { 2361 | if (argv[i][0] == '-' && argv[i][1] != '-') { 2362 | if (strstr(argv[i], key) != 0) { 2363 | return true; 2364 | } 2365 | } 2366 | } 2367 | return false; 2368 | } 2369 | 2370 | bool cmd_parameter_bool(int argc, char *argv[], const char *key, bool *value) 2371 | { 2372 | int i = cmd_parameter_index(argc, argv, key); 2373 | if (i > 0) { 2374 | if (argc > (i + 1)) { 2375 | if (strcmp(argv[i + 1], "on") == 0 || 2376 | strcmp(argv[i + 1], "1") == 0 || 2377 | strcmp(argv[i + 1], "true") == 0 || 2378 | strcmp(argv[i + 1], "enable") == 0 || 2379 | strcmp(argv[i + 1], "allow") == 0) { 2380 | *value = true; 2381 | } else { 2382 | *value = false; 2383 | } 2384 | return true; 2385 | } 2386 | } 2387 | return false; 2388 | } 2389 | 2390 | bool cmd_parameter_val(int argc, char *argv[], const char *key, char **value) 2391 | { 2392 | int i = cmd_parameter_index(argc, argv, key); 2393 | if (i > 0) { 2394 | if (argc > (i + 1)) { 2395 | *value = argv[i + 1]; 2396 | return true; 2397 | } 2398 | } 2399 | return false; 2400 | } 2401 | 2402 | bool cmd_parameter_int(int argc, char *argv[], const char *key, int32_t *value) 2403 | { 2404 | int i = cmd_parameter_index(argc, argv, key); 2405 | char *tailptr; 2406 | if (i > 0) { 2407 | if (argc > (i + 1)) { 2408 | *value = strtol(argv[i + 1], &tailptr, 10); 2409 | if (0 == *tailptr) { 2410 | return true; 2411 | } 2412 | if (!isspace((unsigned char) *tailptr)) { 2413 | return false; 2414 | } else { 2415 | return true; 2416 | } 2417 | } 2418 | } 2419 | return false; 2420 | } 2421 | 2422 | bool cmd_parameter_float(int argc, char *argv[], const char *key, float *value) 2423 | { 2424 | int i = cmd_parameter_index(argc, argv, key); 2425 | char *tailptr; 2426 | if (i > 0) { 2427 | if (argc > (i + 1)) { 2428 | *value = strtof(argv[i + 1], &tailptr); 2429 | if (0 == *tailptr) { 2430 | return true; //Should be correct read always 2431 | } 2432 | if (!isspace((unsigned char) *tailptr)) { 2433 | return false; //Garbage in tailptr 2434 | } else { 2435 | return true; //Spaces are fine after float 2436 | } 2437 | } 2438 | } 2439 | return false; 2440 | } 2441 | 2442 | // convert hex string (eg. "76 ab ff") to binary array 2443 | static int string_to_bytes(const char *str, uint8_t *buf, int bytes) 2444 | { 2445 | int len = strlen(str); 2446 | if (len <= (3 * bytes - 1)) { 2447 | int i; 2448 | for (i = 0; i < bytes; i++) { 2449 | if (i * 3 < len) { 2450 | buf[i] = (uint8_t)strtoul(str + i * 3, 0, 16); 2451 | } else { 2452 | buf[i] = 0; 2453 | } 2454 | } 2455 | return 0; 2456 | } 2457 | return -1; 2458 | } 2459 | 2460 | static uint64_t read_64_bit(const uint8_t data_buf[__static 8]) 2461 | { 2462 | uint64_t temp_64; 2463 | temp_64 = (uint64_t)(*data_buf++) << 56; 2464 | temp_64 += (uint64_t)(*data_buf++) << 48; 2465 | temp_64 += (uint64_t)(*data_buf++) << 40; 2466 | temp_64 += (uint64_t)(*data_buf++) << 32; 2467 | temp_64 += (uint64_t)(*data_buf++) << 24; 2468 | temp_64 += (uint64_t)(*data_buf++) << 16; 2469 | temp_64 += (uint64_t)(*data_buf++) << 8; 2470 | temp_64 += *data_buf++; 2471 | return temp_64; 2472 | } 2473 | 2474 | bool cmd_parameter_timestamp(int argc, char *argv[], const char *key, int64_t *value) 2475 | { 2476 | int i = cmd_parameter_index(argc, argv, key); 2477 | if (i > 0) { 2478 | if (argc > (i + 1)) { 2479 | if (strchr(argv[i + 1], ',') != 0) { 2480 | // Format seconds,tics 2481 | const char splitValue[] = ", "; 2482 | char *token; 2483 | token = strtok(argv[i + 1], splitValue); 2484 | if (token) { 2485 | *value = (int64_t)strtoul(token, 0, 10) << 16; 2486 | } 2487 | token = strtok(NULL, splitValue); 2488 | if (token) { 2489 | *value |= (0xffff & strtoul(token, 0, 10)); 2490 | } 2491 | } else if (strchr(argv[i + 1], ':') != 0) { 2492 | // Format 00:00:00:00:00:00:00:00 2493 | uint8_t buf[8]; 2494 | if (strlen(argv[i + 1]) == 23 && 2495 | string_to_bytes(argv[i + 1], buf, 8) == 0) { 2496 | *value = read_64_bit(buf); 2497 | } else { 2498 | cmd_printf("timestamp should be 8 bytes long\r\n"); 2499 | return false; 2500 | } 2501 | } else { 2502 | // Format uint64 2503 | *value = strtol(argv[i + 1], 0, 10); 2504 | } 2505 | return true; 2506 | } 2507 | } 2508 | return false; 2509 | } 2510 | 2511 | char *cmd_parameter_last(int argc, char *argv[]) 2512 | { 2513 | if (argc > 1) { 2514 | return argv[ argc - 1 ]; 2515 | } 2516 | return NULL; 2517 | } 2518 | 2519 | /** 2520 | * find last space but ignore nulls and first spaces. 2521 | * used only internally to find out previous word 2522 | * e.g. 2523 | * const char str[] = "aaaab aa bbb\0\0"; 2524 | * const char* tmp = last_space(str+strlen(str), str); 2525 | * printf(tmp); // prints "bbb" 2526 | */ 2527 | static const char *find_last_space(const char *from, const char *to) 2528 | { 2529 | if (from <= to) { 2530 | return 0; 2531 | } 2532 | while ((from > to) && ((*from == 0) || (*from == ' '))) { 2533 | from--; 2534 | } 2535 | while (from > to) { 2536 | if (*from == ' ') { 2537 | return from + 1; 2538 | } 2539 | from--; 2540 | } 2541 | return 0; 2542 | } 2543 | 2544 | static int replace_string( 2545 | char *str, int str_len, 2546 | const char *old_str, const char *new_str) 2547 | { 2548 | char *ptr = str; 2549 | char *end = str + str_len; 2550 | int old_len = strlen(old_str); 2551 | int new_len = strlen(new_str); 2552 | if (old_len > 0) { 2553 | tr_deep("find: '%s'\r\n", old_str); 2554 | while ((ptr = strstr(ptr, old_str)) != 0) { 2555 | if (ptr + new_len > end) { 2556 | tr_warn("Buffer was not enough for replacing\r\n"); 2557 | return 1; 2558 | } 2559 | tr_warn("old_str found\r\n"); 2560 | if (old_len != new_len) { 2561 | tr_warn("memmove\r\n"); 2562 | memmove(ptr + new_len, ptr + old_len, strlen(ptr + old_len) + 1); 2563 | } 2564 | memcpy(ptr, new_str, new_len); 2565 | ptr += new_len; 2566 | } 2567 | } 2568 | return 0; 2569 | } 2570 | -------------------------------------------------------------------------------- /source/ns_list_internal/ns_list.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016, Pelion and affiliates. 3 | * SPDX-License-Identifier: Apache-2.0 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 | * http://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 | 18 | /* 19 | * All functions can be inlined, and definitions are in ns_list.h. 20 | * Define NS_LIST_FN before including it to generate external definitions. 21 | */ 22 | #define NS_LIST_FN extern 23 | 24 | #include "ns_list.h" 25 | -------------------------------------------------------------------------------- /source/ns_list_internal/ns_list.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016, 2018, Pelion and affiliates. 3 | * SPDX-License-Identifier: Apache-2.0 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 | * http://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 | 18 | #ifndef NS_LIST_H_ 19 | #define NS_LIST_H_ 20 | 21 | #include "ns_types.h" 22 | 23 | #ifdef __cplusplus 24 | extern "C" { 25 | #endif 26 | 27 | 28 | /** \file 29 | * \brief Linked list support library 30 | * 31 | * The ns_list.h file provides a doubly-linked list/queue, providing O(1) 32 | * performance for all insertion/removal operations, and access to either 33 | * end of the list. 34 | * 35 | * Memory footprint is two pointers for the list head, and two pointers in each 36 | * list entry. It is similar in concept to BSD's TAILQ. 37 | * 38 | * Although the API is symmetrical and O(1) in both directions, due to internal 39 | * pointer design, it is *slightly* more efficient to insert at the end when 40 | * used as a queue, and to iterate forwards rather than backwards. 41 | * 42 | * Example of an entry type that can be stored to this list. 43 | * ~~~ 44 | * typedef struct example_entry 45 | * { 46 | * uint8_t *data; 47 | * uint32_t data_count; 48 | * ns_list_link_t link; 49 | * } 50 | * example_entry_t; 51 | * 52 | * static NS_LIST_HEAD(example_entry_t, link) my_list; 53 | * ns_list_init(&my_list); 54 | * ~~~ 55 | * OR 56 | * ~~~ 57 | * NS_LIST_HEAD(example_entry_t, link) my_list = NS_LIST_INIT(my_list); 58 | * ~~~ 59 | * OR 60 | * ~~~ 61 | * static NS_LIST_DEFINE(my_list, example_entry_t, link); 62 | * ~~~ 63 | * OR 64 | * ~~~ 65 | * typedef NS_LIST_HEAD(example_entry_t, link) example_list_t; 66 | * example_list_t NS_LIST_NAME_INIT(my_list); 67 | * ~~~ 68 | * NOTE: the link field SHALL NOT be accessed by the user. 69 | * 70 | * An entry can exist on multiple lists by having multiple link fields. 71 | * 72 | * All the list operations are implemented as macros, most of which are backed 73 | * by optionally-inline functions. The macros do not evaluate any arguments more 74 | * than once, unless documented. 75 | * 76 | * In macro documentation, `list_t` refers to a list type defined using 77 | * NS_LIST_HEAD(), and `entry_t` to the entry type that was passed to it. 78 | */ 79 | 80 | /** \brief Underlying generic linked list head. 81 | * 82 | * Users should not use this type directly, but use the NS_LIST_HEAD() macro. 83 | */ 84 | typedef struct ns_list { 85 | void *first_entry; ///< Pointer to first entry, or NULL if list is empty 86 | void **last_nextptr; ///< Pointer to last entry's `next` pointer, or 87 | ///< to head's `first_entry` pointer if list is empty 88 | } ns_list_t; 89 | 90 | /** \brief Declare a list head type 91 | * 92 | * This union stores the real list head, and also encodes as compile-time type 93 | * information the offset of the link pointer, and the type of the entry. 94 | * 95 | * Note that type information is compiler-dependent; this means 96 | * ns_list_get_first() could return either `void *`, or a pointer to the actual 97 | * entry type. So `ns_list_get_first()->data` is not a portable construct - 98 | * always assign returned entry pointers to a properly typed pointer variable. 99 | * This assignment will be then type-checked where the compiler supports it, and 100 | * will dereference correctly on compilers that don't support this extension. 101 | * ~~~ 102 | * NS_LIST_HEAD(example_entry_t, link) my_list; 103 | * 104 | * example_entry_t *entry = ns_list_get_first(&my_list); 105 | * do_something(entry->data); 106 | * ~~~ 107 | * Each use of this macro generates a new anonymous union, so these two lists 108 | * have different types: 109 | * ~~~ 110 | * NS_LIST_HEAD(example_entry_t, link) my_list1; 111 | * NS_LIST_HEAD(example_entry_t, link) my_list2; 112 | * ~~~ 113 | * If you need to use a list type in multiple places, eg as a function 114 | * parameter, use typedef: 115 | * ~~~ 116 | * typedef NS_LIST_HEAD(example_entry_t, link) example_list_t; 117 | * 118 | * void example_function(example_list_t *); 119 | * ~~~ 120 | */ 121 | #define NS_LIST_HEAD(entry_type, field) \ 122 | union \ 123 | { \ 124 | ns_list_t slist; \ 125 | NS_STATIC_ASSERT(offsetof(entry_type, field) <= UINT_FAST8_MAX, "link offset too large") \ 126 | char (*offset)[offsetof(entry_type, field)]; \ 127 | entry_type *type; \ 128 | } 129 | 130 | /// \privatesection 131 | /** \brief Get offset of link field in entry. 132 | * \return `(ns_list_offset_t)` The offset of the link field for entries on the specified list 133 | */ 134 | #define NS_LIST_OFFSET_(list) ((ns_list_offset_t) sizeof *(list)->offset) 135 | 136 | /** \brief Get the entry type. 137 | * \def NS_LIST_TYPE_ 138 | * 139 | * \return The type of entry on the specified list. 140 | * 141 | * Only available if the compiler provides a "typeof" operator. 142 | */ 143 | #if defined __cplusplus && __cplusplus >= 201103L 144 | #define NS_LIST_TYPE_(list) decltype(*(list)->type) 145 | #elif defined __GNUC__ 146 | #define NS_LIST_TYPE_(list) __typeof__(*(list)->type) 147 | #endif 148 | 149 | /** \brief Check for compatible pointer types 150 | * 151 | * Although this can be done portably, the GCC custom version is provided to 152 | * produce a clearer diagnostic, and it produces an error rather than a warning. 153 | * 154 | * The portable version will produce a diagnostic about a pointer mismatch on 155 | * the == inside the sizeof operator. For example ARM/Norcroft C gives the error: 156 | * 157 | * operand types are incompatible ("entry_t *" and "other_t *") 158 | */ 159 | #ifdef CPPCHECK 160 | #define NS_PTR_MATCH_(a, b, str) ((void) 0) 161 | #elif defined __GNUC__ 162 | #define NS_PTR_MATCH_(a, b, str) __extension__ \ 163 | ({ NS_STATIC_ASSERT(__builtin_types_compatible_p(__typeof__ (*(a)), __typeof__ (*(b))), \ 164 | str) }) 165 | #else 166 | #define NS_PTR_MATCH_(a, b, str) ((void) sizeof ((a) == (b))) 167 | #endif 168 | 169 | /** \brief Internal macro to cast returned entry pointers to correct type. 170 | * 171 | * Not portable in C, alas. With GCC or C++11, the "get entry" macros return 172 | * correctly-typed pointers. Otherwise, the macros return `void *`. 173 | * 174 | * The attempt at a portable version would work if the C `?:` operator wasn't 175 | * broken - `x ? (t *) : (void *)` should really have type `(t *)` in C, but 176 | * it has type `(void *)`, which only makes sense for C++. The `?:` is left in, 177 | * in case some day it works. Some compilers may still warn if this is 178 | * assigned to a different type. 179 | */ 180 | #ifdef NS_LIST_TYPE_ 181 | #define NS_LIST_TYPECAST_(list, val) ((NS_LIST_TYPE_(list) *) (val)) 182 | #else 183 | #define NS_LIST_TYPECAST_(list, val) (0 ? (list)->type : (val)) 184 | #endif 185 | 186 | /** \brief Internal macro to check types of input entry pointer. */ 187 | #define NS_LIST_TYPECHECK_(list, entry) \ 188 | (NS_PTR_MATCH_((list)->type, (entry), "incorrect entry type for list"), (entry)) 189 | 190 | /** \brief Type used to pass link offset to underlying functions 191 | * 192 | * We could use size_t, but it would be unnecessarily large on 8-bit systems, 193 | * where we can be (pretty) confident we won't have next pointers more than 194 | * 256 bytes into a structure. 195 | */ 196 | typedef uint_fast8_t ns_list_offset_t; 197 | 198 | /// \publicsection 199 | /** \brief The type for the link member in the user's entry structure. 200 | * 201 | * Users should not access this member directly - just pass its name to the 202 | * list head macros. The funny prev pointer simplifies common operations 203 | * (eg insertion, removal), at the expense of complicating rare reverse iteration. 204 | * 205 | * NB - the list implementation relies on next being the first member. 206 | */ 207 | typedef struct ns_list_link { 208 | void *next; ///< Pointer to next entry, or NULL if none 209 | void **prev; ///< Pointer to previous entry's (or head's) next pointer 210 | } ns_list_link_t; 211 | 212 | /** \brief "Poison" value placed in unattached entries' link pointers. 213 | * \internal What are good values for this? Platform dependent, maybe just NULL 214 | */ 215 | #define NS_LIST_POISON ((void *) 0xDEADBEEF) 216 | 217 | /** \brief Initialiser for an entry's link member 218 | * 219 | * This initialiser is not required by the library, but a user may want an 220 | * initialiser to include in their own entry initialiser. See 221 | * ns_list_link_init() for more discussion. 222 | */ 223 | #define NS_LIST_LINK_INIT(name) \ 224 | NS_FUNNY_INTPTR_OK \ 225 | { NS_LIST_POISON, NS_LIST_POISON } \ 226 | NS_FUNNY_INTPTR_RESTORE 227 | 228 | /** \hideinitializer \brief Initialise an entry's list link 229 | * 230 | * This "initialises" an unattached entry's link by filling the fields with 231 | * poison. This is optional, as unattached entries field pointers are not 232 | * meaningful, and it is not valid to call ns_list_get_next or similar on 233 | * an unattached entry. 234 | * 235 | * \param entry Pointer to an entry 236 | * \param field The name of the link member to initialise 237 | */ 238 | #define ns_list_link_init(entry, field) ns_list_link_init_(&(entry)->field) 239 | 240 | /** \hideinitializer \brief Initialise a list 241 | * 242 | * Initialise a list head before use. A list head must be initialised using this 243 | * function or one of the NS_LIST_INIT()-type macros before use. A zero-initialised 244 | * list head is *not* valid. 245 | * 246 | * If used on a list containing existing entries, those entries will 247 | * become detached. (They are not modified, but their links are now effectively 248 | * undefined). 249 | * 250 | * \param list Pointer to a NS_LIST_HEAD() structure. 251 | */ 252 | #define ns_list_init(list) ns_list_init_(&(list)->slist) 253 | 254 | /** \brief Initialiser for an empty list 255 | * 256 | * Usage in an enclosing initialiser: 257 | * ~~~ 258 | * static my_type_including_list_t x = { 259 | * "Something", 260 | * 23, 261 | * NS_LIST_INIT(x), 262 | * }; 263 | * ~~~ 264 | * NS_LIST_DEFINE() or NS_LIST_NAME_INIT() may provide a shorter alternative 265 | * in simpler cases. 266 | */ 267 | #define NS_LIST_INIT(name) { { NULL, &(name).slist.first_entry } } 268 | 269 | /** \brief Name and initialiser for an empty list 270 | * 271 | * Usage: 272 | * ~~~ 273 | * list_t NS_LIST_NAME_INIT(foo); 274 | * ~~~ 275 | * acts as 276 | * ~~~ 277 | * list_t foo = { empty list }; 278 | * ~~~ 279 | * Also useful with designated initialisers: 280 | * ~~~ 281 | * .NS_LIST_NAME_INIT(foo), 282 | * ~~~ 283 | * acts as 284 | * ~~~ 285 | * .foo = { empty list }, 286 | * ~~~ 287 | */ 288 | #define NS_LIST_NAME_INIT(name) name = NS_LIST_INIT(name) 289 | 290 | /** \brief Define a list, and initialise to empty. 291 | * 292 | * Usage: 293 | * ~~~ 294 | * static NS_LIST_DEFINE(my_list, entry_t, link); 295 | * ~~~ 296 | * acts as 297 | * ~~~ 298 | * static list_type my_list = { empty list }; 299 | * ~~~ 300 | */ 301 | #define NS_LIST_DEFINE(name, type, field) \ 302 | NS_LIST_HEAD(type, field) NS_LIST_NAME_INIT(name) 303 | 304 | /** \hideinitializer \brief Add an entry to the start of the linked list. 305 | * 306 | * ns_list_add_to_end() is *slightly* more efficient than ns_list_add_to_start(). 307 | * 308 | * \param list `(list_t *)` Pointer to list. 309 | * \param entry `(entry_t * restrict)` Pointer to new entry to add. 310 | */ 311 | #define ns_list_add_to_start(list, entry) \ 312 | ns_list_add_to_start_(&(list)->slist, NS_LIST_OFFSET_(list), NS_LIST_TYPECHECK_(list, entry)) 313 | 314 | /** \hideinitializer \brief Add an entry to the end of the linked list. 315 | * 316 | * \param list `(list_t *)` Pointer to list. 317 | * \param entry `(entry_t * restrict)` Pointer to new entry to add. 318 | */ 319 | #define ns_list_add_to_end(list, entry) \ 320 | ns_list_add_to_end_(&(list)->slist, NS_LIST_OFFSET_(list), NS_LIST_TYPECHECK_(list, entry)) 321 | 322 | /** \hideinitializer \brief Add an entry before a specified entry. 323 | * 324 | * \param list `(list_t *)` Pointer to list. 325 | * \param before `(entry_t *)` Existing entry before which to place the new entry. 326 | * \param entry `(entry_t * restrict)` Pointer to new entry to add. 327 | */ 328 | #define ns_list_add_before(list, before, entry) \ 329 | ns_list_add_before_(NS_LIST_OFFSET_(list), NS_LIST_TYPECHECK_(list, before), NS_LIST_TYPECHECK_(list, entry)) 330 | 331 | /** \hideinitializer \brief Add an entry after a specified entry. 332 | * 333 | * ns_list_add_before() is *slightly* more efficient than ns_list_add_after(). 334 | * 335 | * \param list `(list_t *)` Pointer to list. 336 | * \param after `(entry_t *)` Existing entry after which to place the new entry. 337 | * \param entry `(entry_t * restrict)` Pointer to new entry to add. 338 | */ 339 | #define ns_list_add_after(list, after, entry) \ 340 | ns_list_add_after_(&(list)->slist, NS_LIST_OFFSET_(list), NS_LIST_TYPECHECK_(list, after), NS_LIST_TYPECHECK_(list, entry)) 341 | 342 | /** \brief Check if a list is empty. 343 | * 344 | * \param list `(const list_t *)` Pointer to list. 345 | * 346 | * \return `(bool)` true if the list is empty. 347 | */ 348 | #define ns_list_is_empty(list) ((bool) ((list)->slist.first_entry == NULL)) 349 | 350 | /** \brief Get the first entry. 351 | * 352 | * \param list `(const list_t *)` Pointer to list. 353 | * 354 | * \return `(entry_t *)` Pointer to first entry. 355 | * \return NULL if list is empty. 356 | */ 357 | #define ns_list_get_first(list) NS_LIST_TYPECAST_(list, (list)->slist.first_entry) 358 | 359 | /** \hideinitializer \brief Get the previous entry. 360 | * 361 | * \param list `(const list_t *)` Pointer to list. 362 | * \param current `(const entry_t *)` Pointer to current entry. 363 | * 364 | * \return `(entry_t *)` Pointer to previous entry. 365 | * \return NULL if current entry is first. 366 | */ 367 | #define ns_list_get_previous(list, current) \ 368 | NS_LIST_TYPECAST_(list, ns_list_get_previous_(&(list)->slist, NS_LIST_OFFSET_(list), NS_LIST_TYPECHECK_(list, current))) 369 | 370 | /** \hideinitializer \brief Get the next entry. 371 | * 372 | * \param list `(const list_t *)` Pointer to list. 373 | * \param current `(const entry_t *)` Pointer to current entry. 374 | * 375 | * \return `(entry_t *)` Pointer to next entry. 376 | * \return NULL if current entry is last. 377 | */ 378 | #define ns_list_get_next(list, current) \ 379 | NS_LIST_TYPECAST_(list, ns_list_get_next_(NS_LIST_OFFSET_(list), NS_LIST_TYPECHECK_(list, current))) 380 | 381 | /** \hideinitializer \brief Get the last entry. 382 | * 383 | * \param list `(const list_t *)` Pointer to list. 384 | * 385 | * \return `(entry_t *)` Pointer to last entry. 386 | * \return NULL if list is empty. 387 | */ 388 | #define ns_list_get_last(list) \ 389 | NS_LIST_TYPECAST_(list, ns_list_get_last_(&(list)->slist, NS_LIST_OFFSET_(list))) 390 | 391 | /** \hideinitializer \brief Remove an entry. 392 | * 393 | * \param list `(list_t *)` Pointer to list. 394 | * \param entry `(entry_t *)` Entry on list to be removed. 395 | */ 396 | #define ns_list_remove(list, entry) \ 397 | ns_list_remove_(&(list)->slist, NS_LIST_OFFSET_(list), NS_LIST_TYPECHECK_(list, entry)) 398 | 399 | /** \hideinitializer \brief Replace an entry. 400 | * 401 | * \param list `(list_t *)` Pointer to list. 402 | * \param current `(entry_t *)` Existing entry on list to be replaced. 403 | * \param replacement `(entry_t * restrict)` New entry to be the replacement. 404 | */ 405 | #define ns_list_replace(list, current, replacement) \ 406 | ns_list_replace_(&(list)->slist, NS_LIST_OFFSET_(list), NS_LIST_TYPECHECK_(list, current), NS_LIST_TYPECHECK_(list, replacement)) 407 | 408 | /** \hideinitializer \brief Concatenate two lists. 409 | * 410 | * Attach the entries on the source list to the end of the destination 411 | * list, leaving the source list empty. 412 | * 413 | * \param dst `(list_t *)` Pointer to destination list. 414 | * \param src `(list_t *)` Pointer to source list. 415 | * 416 | */ 417 | #define ns_list_concatenate(dst, src) \ 418 | (NS_PTR_MATCH_(dst, src, "concatenating different list types"), \ 419 | ns_list_concatenate_(&(dst)->slist, &(src)->slist, NS_LIST_OFFSET_(src))) 420 | 421 | /** \brief Iterate forwards over a list. 422 | * 423 | * Example: 424 | * ~~~ 425 | * ns_list_foreach(const my_entry_t, cur, &my_list) 426 | * { 427 | * printf("%s\n", cur->name); 428 | * } 429 | * ~~~ 430 | * Deletion of the current entry is not permitted as its next is checked after 431 | * running user code. 432 | * 433 | * The iteration pointer is declared inside the loop, using C99/C++, so it 434 | * is not accessible after the loop. This encourages good code style, and 435 | * matches the semantics of C++11's "ranged for", which only provides the 436 | * declaration form: 437 | * ~~~ 438 | * for (const my_entry_t cur : my_list) 439 | * ~~~ 440 | * If you need to see the value of the iteration pointer after a `break`, 441 | * you will need to assign it to a variable declared outside the loop before 442 | * breaking: 443 | * ~~~ 444 | * my_entry_t *match = NULL; 445 | * ns_list_foreach(my_entry_t, cur, &my_list) 446 | * { 447 | * if (cur->id == id) 448 | * { 449 | * match = cur; 450 | * break; 451 | * } 452 | * } 453 | * ~~~ 454 | * 455 | * The user has to specify the entry type for the pointer definition, as type 456 | * extraction from the list argument isn't portable. On the other hand, this 457 | * also permits const qualifiers, as in the example above, and serves as 458 | * documentation. The entry type will be checked against the list type where the 459 | * compiler supports it. 460 | * 461 | * \param type Entry type `([const] entry_t)`. 462 | * \param e Name for iteration pointer to be defined 463 | * inside the loop. 464 | * \param list `(const list_t *)` Pointer to list - evaluated multiple times. 465 | */ 466 | #define ns_list_foreach(type, e, list) \ 467 | for (type *e = ns_list_get_first(list); e; e = ns_list_get_next(list, e)) 468 | 469 | /** \brief Iterate forwards over a list, where user may delete. 470 | * 471 | * As ns_list_foreach(), but deletion of current entry is permitted as its 472 | * next pointer is recorded before running user code. 473 | * 474 | * Example: 475 | * ~~~ 476 | * ns_list_foreach_safe(my_entry_t, cur, &my_list) 477 | * { 478 | * ns_list_remove(cur); 479 | * } 480 | * ~~~ 481 | * \param type Entry type `(entry_t)`. 482 | * \param e Name for iteration pointer to be defined 483 | * inside the loop. 484 | * \param list `(list_t *)` Pointer to list - evaluated multiple times. 485 | */ 486 | #define ns_list_foreach_safe(type, e, list) \ 487 | for (type *e = ns_list_get_first(list), *_next; \ 488 | e && (_next = ns_list_get_next(list, e), true); e = _next) 489 | 490 | /** \brief Iterate backwards over a list. 491 | * 492 | * As ns_list_foreach(), but going backwards - see its documentation. 493 | * Iterating forwards is *slightly* more efficient. 494 | */ 495 | #define ns_list_foreach_reverse(type, e, list) \ 496 | for (type *e = ns_list_get_last(list); e; e = ns_list_get_previous(list, e)) 497 | 498 | /** \brief Iterate backwards over a list, where user may delete. 499 | * 500 | * As ns_list_foreach_safe(), but going backwards - see its documentation. 501 | * Iterating forwards is *slightly* more efficient. 502 | */ 503 | #define ns_list_foreach_reverse_safe(type, e, list) \ 504 | for (type *e = ns_list_get_last(list), *_next; \ 505 | e && (_next = ns_list_get_previous(list, e), true); e = _next) 506 | 507 | /** \hideinitializer \brief Count entries on a list 508 | * 509 | * Unlike other operations, this is O(n). Note: if list might contain over 510 | * 65535 entries, this function **must not** be used to get the entry count. 511 | * 512 | * \param list `(const list_t *)` Pointer to list. 513 | 514 | * \return `(uint_fast16_t)` Number of entries that are stored in list. 515 | */ 516 | #define ns_list_count(list) ns_list_count_(&(list)->slist, NS_LIST_OFFSET_(list)) 517 | 518 | /** \privatesection 519 | * Internal functions - designed to be accessed using corresponding macros above 520 | */ 521 | NS_INLINE void ns_list_init_(ns_list_t *list); 522 | NS_INLINE void ns_list_link_init_(ns_list_link_t *link); 523 | NS_INLINE void ns_list_add_to_start_(ns_list_t *list, ns_list_offset_t link_offset, void *restrict entry); 524 | NS_INLINE void ns_list_add_to_end_(ns_list_t *list, ns_list_offset_t link_offset, void *restrict entry); 525 | NS_INLINE void ns_list_add_before_(ns_list_offset_t link_offset, void *before, void *restrict entry); 526 | NS_INLINE void ns_list_add_after_(ns_list_t *list, ns_list_offset_t link_offset, void *after, void *restrict entry); 527 | NS_INLINE void *ns_list_get_next_(ns_list_offset_t link_offset, const void *current); 528 | NS_INLINE void *ns_list_get_previous_(const ns_list_t *list, ns_list_offset_t link_offset, const void *current); 529 | NS_INLINE void *ns_list_get_last_(const ns_list_t *list, ns_list_offset_t offset); 530 | NS_INLINE void ns_list_remove_(ns_list_t *list, ns_list_offset_t link_offset, void *entry); 531 | NS_INLINE void ns_list_replace_(ns_list_t *list, ns_list_offset_t link_offset, void *current, void *restrict replacement); 532 | NS_INLINE void ns_list_concatenate_(ns_list_t *dst, ns_list_t *src, ns_list_offset_t offset); 533 | NS_INLINE uint_fast16_t ns_list_count_(const ns_list_t *list, ns_list_offset_t link_offset); 534 | 535 | /* Provide definitions, either for inlining, or for ns_list.c */ 536 | #if defined NS_ALLOW_INLINING || defined NS_LIST_FN 537 | #ifndef NS_LIST_FN 538 | #define NS_LIST_FN NS_INLINE 539 | #endif 540 | 541 | /* Pointer to the link member in entry e */ 542 | #define NS_LIST_LINK_(e, offset) ((ns_list_link_t *)((char *)(e) + offset)) 543 | 544 | /* Lvalue of the next link pointer in entry e */ 545 | #define NS_LIST_NEXT_(e, offset) (NS_LIST_LINK_(e, offset)->next) 546 | 547 | /* Lvalue of the prev link pointer in entry e */ 548 | #define NS_LIST_PREV_(e, offset) (NS_LIST_LINK_(e, offset)->prev) 549 | 550 | /* Convert a pointer to a link member back to the entry; 551 | * works for linkptr either being a ns_list_link_t pointer, or its next pointer, 552 | * as the next pointer is first in the ns_list_link_t */ 553 | #define NS_LIST_ENTRY_(linkptr, offset) ((void *)((char *)(linkptr) - offset)) 554 | 555 | NS_LIST_FN void ns_list_init_(ns_list_t *list) 556 | { 557 | list->first_entry = NULL; 558 | list->last_nextptr = &list->first_entry; 559 | } 560 | 561 | NS_LIST_FN void ns_list_link_init_(ns_list_link_t *link) 562 | { 563 | NS_FUNNY_INTPTR_OK 564 | link->next = NS_LIST_POISON; 565 | link->prev = NS_LIST_POISON; 566 | NS_FUNNY_INTPTR_RESTORE 567 | } 568 | 569 | NS_LIST_FN void ns_list_add_to_start_(ns_list_t *list, ns_list_offset_t offset, void *restrict entry) 570 | { 571 | void *next; 572 | 573 | NS_LIST_PREV_(entry, offset) = &list->first_entry; 574 | NS_LIST_NEXT_(entry, offset) = next = list->first_entry; 575 | 576 | if (next) { 577 | NS_LIST_PREV_(next, offset) = &NS_LIST_NEXT_(entry, offset); 578 | } else { 579 | list->last_nextptr = &NS_LIST_NEXT_(entry, offset); 580 | } 581 | 582 | list->first_entry = entry; 583 | } 584 | 585 | NS_LIST_FN void ns_list_add_after_(ns_list_t *list, ns_list_offset_t offset, void *current, void *restrict entry) 586 | { 587 | void *next; 588 | 589 | NS_LIST_PREV_(entry, offset) = &NS_LIST_NEXT_(current, offset); 590 | NS_LIST_NEXT_(entry, offset) = next = NS_LIST_NEXT_(current, offset); 591 | 592 | if (next) { 593 | NS_LIST_PREV_(next, offset) = &NS_LIST_NEXT_(entry, offset); 594 | } else { 595 | list->last_nextptr = &NS_LIST_NEXT_(entry, offset); 596 | } 597 | 598 | NS_LIST_NEXT_(current, offset) = entry; 599 | } 600 | 601 | NS_LIST_FN void ns_list_add_before_(ns_list_offset_t offset, void *current, void *restrict entry) 602 | { 603 | void **prev_nextptr; 604 | 605 | NS_LIST_NEXT_(entry, offset) = current; 606 | NS_LIST_PREV_(entry, offset) = prev_nextptr = NS_LIST_PREV_(current, offset); 607 | *prev_nextptr = entry; 608 | NS_LIST_PREV_(current, offset) = &NS_LIST_NEXT_(entry, offset); 609 | } 610 | 611 | NS_LIST_FN void ns_list_add_to_end_(ns_list_t *list, ns_list_offset_t offset, void *restrict entry) 612 | { 613 | void **prev_nextptr; 614 | 615 | NS_LIST_NEXT_(entry, offset) = NULL; 616 | NS_LIST_PREV_(entry, offset) = prev_nextptr = list->last_nextptr; 617 | *prev_nextptr = entry; 618 | list->last_nextptr = &NS_LIST_NEXT_(entry, offset); 619 | } 620 | 621 | NS_LIST_FN void *ns_list_get_next_(ns_list_offset_t offset, const void *current) 622 | { 623 | return NS_LIST_NEXT_(current, offset); 624 | } 625 | 626 | NS_LIST_FN void *ns_list_get_previous_(const ns_list_t *list, ns_list_offset_t offset, const void *current) 627 | { 628 | if (current == list->first_entry) { 629 | return NULL; 630 | } 631 | 632 | // Tricky. We don't have a direct previous pointer, but a pointer to the 633 | // pointer that points to us - ie &head->first_entry OR &{prev}->next. 634 | // This makes life easier on insertion and removal, but this is where we 635 | // pay the price. 636 | 637 | // We have to check manually for being the first entry above, so we know it's 638 | // a real link's next pointer. Then next is the first field of 639 | // ns_list_link_t, so we can use the normal offset value. 640 | 641 | return NS_LIST_ENTRY_(NS_LIST_PREV_(current, offset), offset); 642 | } 643 | 644 | NS_LIST_FN void *ns_list_get_last_(const ns_list_t *list, ns_list_offset_t offset) 645 | { 646 | if (!list->first_entry) { 647 | return NULL; 648 | } 649 | 650 | // See comments in ns_list_get_previous_() 651 | return NS_LIST_ENTRY_(list->last_nextptr, offset); 652 | } 653 | 654 | NS_LIST_FN void ns_list_remove_(ns_list_t *list, ns_list_offset_t offset, void *removed) 655 | { 656 | void *next; 657 | void **prev_nextptr; 658 | 659 | next = NS_LIST_NEXT_(removed, offset); 660 | prev_nextptr = NS_LIST_PREV_(removed, offset); 661 | if (next) { 662 | NS_LIST_PREV_(next, offset) = prev_nextptr; 663 | } else { 664 | list->last_nextptr = prev_nextptr; 665 | } 666 | *prev_nextptr = next; 667 | 668 | ns_list_link_init_(NS_LIST_LINK_(removed, offset)); 669 | } 670 | 671 | NS_LIST_FN void ns_list_replace_(ns_list_t *list, ns_list_offset_t offset, void *current, void *restrict replacement) 672 | { 673 | void *next; 674 | void **prev_nextptr; 675 | 676 | NS_LIST_PREV_(replacement, offset) = prev_nextptr = NS_LIST_PREV_(current, offset); 677 | NS_LIST_NEXT_(replacement, offset) = next = NS_LIST_NEXT_(current, offset); 678 | 679 | if (next) { 680 | NS_LIST_PREV_(next, offset) = &NS_LIST_NEXT_(replacement, offset); 681 | } else { 682 | list->last_nextptr = &NS_LIST_NEXT_(replacement, offset); 683 | } 684 | *prev_nextptr = replacement; 685 | 686 | ns_list_link_init_(NS_LIST_LINK_(current, offset)); 687 | } 688 | 689 | NS_LIST_FN void ns_list_concatenate_(ns_list_t *dst, ns_list_t *src, ns_list_offset_t offset) 690 | { 691 | ns_list_link_t *src_first; 692 | 693 | src_first = src->first_entry; 694 | if (!src_first) { 695 | return; 696 | } 697 | 698 | *dst->last_nextptr = src_first; 699 | NS_LIST_PREV_(src_first, offset) = dst->last_nextptr; 700 | dst->last_nextptr = src->last_nextptr; 701 | 702 | ns_list_init_(src); 703 | } 704 | 705 | NS_LIST_FN uint_fast16_t ns_list_count_(const ns_list_t *list, ns_list_offset_t offset) 706 | { 707 | uint_fast16_t count = 0; 708 | 709 | for (void *p = list->first_entry; p; p = NS_LIST_NEXT_(p, offset)) { 710 | count++; 711 | } 712 | 713 | return count; 714 | } 715 | #endif /* defined NS_ALLOW_INLINING || defined NS_LIST_FN */ 716 | 717 | #ifdef __cplusplus 718 | } 719 | #endif 720 | 721 | #endif /* NS_LIST_H_ */ 722 | 723 | -------------------------------------------------------------------------------- /source/ns_list_internal/ns_types.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2016, 2018, Pelion and affiliates. 3 | * SPDX-License-Identifier: Apache-2.0 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 | * http://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 | /* 18 | * ns_types.h - Basic compiler and type setup for Nanostack libraries. 19 | */ 20 | #ifndef NS_TYPES_H_ 21 | #define NS_TYPES_H_ 22 | 23 | /** \file 24 | * \brief Basic compiler and type setup 25 | * 26 | * We currently assume C99 or later. 27 | * 28 | * C99 features being relied on: 29 | * 30 | * - and 31 | * - inline (with C99 semantics, not C++ as per default GCC); 32 | * - designated initialisers; 33 | * - compound literals; 34 | * - restrict; 35 | * - [static N] in array parameters; 36 | * - declarations in for statements; 37 | * - mixing declarations and statements 38 | * 39 | * Compilers should be set to C99 or later mode when building Nanomesh source. 40 | * For GCC this means "-std=gnu99" (C99 with usual GNU extensions). 41 | * 42 | * Also, a little extra care is required for public header files that could be 43 | * included from C++, especially as C++ lacks some C99 features. 44 | * 45 | * (TODO: as this is exposed to API users, do we need a predefine to distinguish 46 | * internal and external use, for finer control? Not yet, but maybe...) 47 | */ 48 | 49 | /* Make sure defines its macros if C++ */ 50 | #ifndef __STDC_LIMIT_MACROS 51 | #define __STDC_LIMIT_MACROS 52 | #endif 53 | #ifndef __STDC_CONSTANT_MACROS 54 | #define __STDC_CONSTANT_MACROS 55 | #endif 56 | 57 | #include 58 | #include // includes ; debugf() users need PRIu32 etc 59 | #include 60 | 61 | /* 62 | * Create the optional 24-bit types if they don't exist (worth trying 63 | * to use them, as they could exist and be more efficient than 32-bit on 8-bit 64 | * systems...) 65 | */ 66 | #ifndef UINT24_LEAST_MAX 67 | typedef uint_least32_t uint_least24_t; 68 | #define UINT_LEAST24_MAX UINT_LEAST32_MAX 69 | #define UINT24_C(x) UINT32_C(x) 70 | #define PRIoLEAST24 PRIoLEAST32 71 | #define PRIuLEAST24 PRIuLEAST32 72 | #define PRIxLEAST24 PRIxLEAST32 73 | #define PRIXLEAST24 PRIXLEAST32 74 | #endif 75 | 76 | #ifndef INT24_LEAST_MAX 77 | typedef int_least32_t int_least24_t; 78 | #define INT24_LEAST_MIN INT_LEAST32_MIN 79 | #define INT24_LEAST_MAX INT_LEAST32_MAX 80 | #define INT24_C(x) INT32_C(x) 81 | #define PRIdLEAST24 PRIdLEAST32 82 | #define PRIiLEAST24 PRIiLEAST32 83 | #endif 84 | 85 | #ifndef UINT24_FAST_MAX 86 | typedef uint_fast32_t uint_fast24_t; 87 | #define UINT_FAST24_MAX UINT_FAST32_MAX 88 | #define PRIoFAST24 PRIoFAST32 89 | #define PRIuFAST24 PRIuFAST32 90 | #define PRIxFAST24 PRIxFAST32 91 | #define PRIXFAST24 PRIXFAST32 92 | #endif 93 | 94 | #ifndef INT24_FAST_MAX 95 | typedef int_fast32_t int_fast24_t; 96 | #define INT_FAST24_MIN INT_FAST32_MIN 97 | #define INT_FAST24_MAX INT_FAST32_MAX 98 | #define PRIdFAST24 PRIdFAST32 99 | #define PRIiFAST24 PRIiFAST32 100 | #endif 101 | 102 | /* Function attribute - C11 "noreturn" or C++11 "[[noreturn]]" */ 103 | #ifndef NS_NORETURN 104 | #if defined __cplusplus && __cplusplus >= 201103L 105 | #define NS_NORETURN [[noreturn]] 106 | #elif !defined __cplusplus && __STDC_VERSION__ >= 201112L 107 | #define NS_NORETURN _Noreturn 108 | #elif defined __GNUC__ 109 | #define NS_NORETURN __attribute__((__noreturn__)) 110 | #elif defined __CC_ARM 111 | #define NS_NORETURN __declspec(noreturn) 112 | #elif defined __IAR_SYSTEMS_ICC__ 113 | #define NS_NORETURN __noreturn 114 | #else 115 | #define NS_NORETURN 116 | #endif 117 | #endif 118 | 119 | /* C11's "alignas" macro, emulated for integer expressions if necessary */ 120 | #ifndef __alignas_is_defined 121 | #if defined __CC_ARM || defined __TASKING__ 122 | #define alignas(n) __align(n) 123 | #define __alignas_is_defined 1 124 | #elif (__STDC_VERSION__ >= 201112L) || (defined __cplusplus && __cplusplus >= 201103L) 125 | #include 126 | #elif defined __GNUC__ 127 | #define alignas(n) __attribute__((__aligned__(n))) 128 | #define __alignas_is_defined 1 129 | #elif defined __IAR_SYSTEMS_ICC__ 130 | /* Does this really just apply to the next variable? */ 131 | #define alignas(n) __Alignas(data_alignment=n) 132 | #define __Alignas(x) _Pragma(#x) 133 | #define __alignas_is_defined 1 134 | #endif 135 | #endif 136 | 137 | /** 138 | * Marker for functions or objects that may be unused, suppressing warnings. 139 | * Place after the identifier: 140 | * ~~~ 141 | * static int X MAYBE_UNUSED = 3; 142 | * static int foo(void) MAYBE_UNUSED; 143 | * ~~~ 144 | */ 145 | #if defined __CC_ARM || defined __GNUC__ 146 | #define MAYBE_UNUSED __attribute__((unused)) 147 | #else 148 | #define MAYBE_UNUSED 149 | #endif 150 | 151 | /* 152 | * C++ (even C++11) doesn't provide restrict: define away or provide 153 | * alternative. 154 | */ 155 | #ifdef __cplusplus 156 | #ifdef __GNUC__ 157 | #define restrict __restrict 158 | #else 159 | #define restrict 160 | #endif 161 | #endif /* __cplusplus */ 162 | 163 | 164 | /** 165 | * C++ doesn't allow "static" in function parameter types: ie 166 | * ~~~ 167 | * entry_t *find_entry(const uint8_t address[static 16]) 168 | * ~~~ 169 | * If a header file may be included from C++, use this __static define instead. 170 | * 171 | * (Syntax introduced in C99 - `uint8_t address[16]` in a prototype was always 172 | * equivalent to `uint8_t *address`, but the C99 addition of static tells the 173 | * compiler that address is never NULL, and always points to at least 16 174 | * elements. This adds no new type-checking, but the information could aid 175 | * compiler optimisation, and it can serve as documentation). 176 | */ 177 | #ifdef __cplusplus 178 | #define __static 179 | #else 180 | #define __static static 181 | #endif 182 | 183 | #ifdef __GNUC__ 184 | #define NS_GCC_VERSION (__GNUC__ * 10000 \ 185 | + __GNUC_MINOR__ * 100 \ 186 | + __GNUC_PATCHLEVEL__) 187 | #endif 188 | 189 | /** \brief Compile-time assertion 190 | * 191 | * C11 provides _Static_assert, as does GCC even in C99 mode (and 192 | * as a freestanding implementation, we can't rely on to get 193 | * the static_assert macro). 194 | * C++11 provides static_assert as a keyword, as does G++ in C++0x mode. 195 | * 196 | * The assertion acts as a declaration that can be placed at file scope, in a 197 | * code block (except after a label), or as a member of a struct/union. It 198 | * produces a compiler error if "test" evaluates to 0. 199 | * 200 | * Note that this *includes* the required semicolon when defined, else it 201 | * is totally empty, permitting use in structs. (If the user provided the `;`, 202 | * it would leave an illegal stray `;` if unavailable). 203 | */ 204 | #ifdef __cplusplus 205 | # if __cplusplus >= 201103L || __cpp_static_assert >= 200410 206 | # define NS_STATIC_ASSERT(test, str) static_assert(test, str); 207 | # elif defined __GXX_EXPERIMENTAL_CXX0X__ && NS_GCC_VERSION >= 40300 208 | # define NS_STATIC_ASSERT(test, str) __extension__ static_assert(test, str); 209 | # else 210 | # define NS_STATIC_ASSERT(test, str) 211 | # endif 212 | #else /* C */ 213 | # if __STDC_VERSION__ >= 201112L 214 | # define NS_STATIC_ASSERT(test, str) _Static_assert(test, str); 215 | # elif defined __GNUC__ && NS_GCC_VERSION >= 40600 && !defined __CC_ARM 216 | # ifdef _Static_assert 217 | /* 218 | * Some versions of glibc cdefs.h (which comes in via above) 219 | * attempt to define their own _Static_assert (if GCC < 4.6 or 220 | * __STRICT_ANSI__) using an extern declaration, which doesn't work in a 221 | * struct/union. 222 | * 223 | * For GCC >= 4.6 and __STRICT_ANSI__, we can do better - just use 224 | * the built-in _Static_assert with __extension__. We have to do this, as 225 | * ns_list.h needs to use it in a union. No way to get at it though, without 226 | * overriding their define. 227 | */ 228 | # undef _Static_assert 229 | # define _Static_assert(x, y) __extension__ _Static_assert(x, y) 230 | # endif 231 | # define NS_STATIC_ASSERT(test, str) __extension__ _Static_assert(test, str); 232 | # else 233 | # define NS_STATIC_ASSERT(test, str) 234 | #endif 235 | #endif 236 | 237 | /** \brief Pragma to suppress warnings about unusual pointer values. 238 | * 239 | * Useful if using "poison" values. 240 | */ 241 | #ifdef __IAR_SYSTEMS_ICC__ 242 | #define NS_FUNNY_INTPTR_OK _Pragma("diag_suppress=Pe1053") 243 | #define NS_FUNNY_INTPTR_RESTORE _Pragma("diag_default=Pe1053") 244 | #else 245 | #define NS_FUNNY_INTPTR_OK 246 | #define NS_FUNNY_INTPTR_RESTORE 247 | #endif 248 | 249 | /** \brief Convert pointer to member to pointer to containing structure */ 250 | #define NS_CONTAINER_OF(ptr, type, member) \ 251 | ((type *) ((char *) (ptr) - offsetof(type, member))) 252 | 253 | /* 254 | * Inlining could cause problems when mixing with C++; provide a mechanism to 255 | * disable it. This could also be turned off for other reasons (although 256 | * this can usually be done through a compiler flag, eg -O0 on gcc). 257 | */ 258 | #ifndef __cplusplus 259 | #define NS_ALLOW_INLINING 260 | #endif 261 | 262 | /* There is inlining problem in GCC version 4.1.x and we know it works in 4.6.3 */ 263 | #if defined __GNUC__ && NS_GCC_VERSION < 40600 264 | #undef NS_ALLOW_INLINING 265 | #endif 266 | 267 | /** \brief Mark a potentially-inlineable function. 268 | * 269 | * We follow C99 semantics, which requires precisely one external definition. 270 | * To also allow inlining to be totally bypassed under control of 271 | * NS_ALLOW_INLINING, code can be structured as per the example of ns_list: 272 | * 273 | * foo.h 274 | * ----- 275 | * ~~~ 276 | * NS_INLINE int my_func(int); 277 | * 278 | * #if defined NS_ALLOW_INLINING || defined FOO_FN 279 | * #ifndef FOO_FN 280 | * #define FOO_FN NS_INLINE 281 | * #endif 282 | * FOO_FN int my_func(int a) 283 | * { 284 | * definition; 285 | * } 286 | * #endif 287 | * ~~~ 288 | * foo.c 289 | * ----- 290 | * ~~~ 291 | * #define FOO_FN extern 292 | * #include "foo.h" 293 | * ~~~ 294 | * Which generates: 295 | * ~~~ 296 | * NS_ALLOW_INLINING set NS_ALLOW_INLINING unset 297 | * ===================== ======================= 298 | * Include foo.h Include foo.h 299 | * ------------- ------------- 300 | * inline int my_func(int); int my_func(int); 301 | * 302 | * // inline definition 303 | * inline int my_func(int a) 304 | * { 305 | * definition; 306 | * } 307 | * 308 | * Compile foo.c Compile foo.c 309 | * ------------- ------------- 310 | * (from .h) inline int my_func(int); int my_func(int); 311 | * 312 | * // external definition 313 | * // because of no "inline" // normal external definition 314 | * extern int my_func(int a) extern int my_func(int a) 315 | * { { 316 | * definition; definition; 317 | * } } 318 | * ~~~ 319 | * 320 | * Note that even with inline keywords, whether the compiler inlines or not is 321 | * up to it. For example, gcc at "-O0" will not inline at all, and will always 322 | * call the real functions in foo.o, just as if NS_ALLOW_INLINING was unset. 323 | * At "-O2", gcc could potentially inline everything, meaning that foo.o is not 324 | * referenced at all. 325 | * 326 | * Alternatively, you could use "static inline", which gives every caller its 327 | * own internal definition. This is compatible with C++ inlining (which expects 328 | * the linker to eliminate duplicates), but in C it's less efficient if the code 329 | * ends up non-inlined, and it's harder to breakpoint. I don't recommend it 330 | * except for the most trivial functions (which could then probably be macros). 331 | */ 332 | #ifdef NS_ALLOW_INLINING 333 | #define NS_INLINE inline 334 | #else 335 | #define NS_INLINE 336 | #endif 337 | 338 | #if defined __SDCC_mcs51 || defined __ICC8051__ || defined __C51__ 339 | 340 | /* The 8051 environments: SDCC (historic), IAR (current), Keil (future?) */ 341 | 342 | #define NS_LARGE __xdata 343 | #define NS_LARGE_PTR __xdata 344 | #ifdef __ICC8051__ 345 | #define NS_REENTRANT 346 | #define NS_REENTRANT_PREFIX __idata_reentrant 347 | #else 348 | #define NS_REENTRANT __reentrant 349 | #define NS_REENTRANT_PREFIX 350 | #endif 351 | #define NS_NEAR_FUNC __near_func 352 | 353 | #else 354 | 355 | /* "Normal" systems. Define it all away. */ 356 | #define NS_LARGE 357 | #define NS_LARGE_PTR 358 | #define NS_REENTRANT 359 | #define NS_REENTRANT_PREFIX 360 | #define NS_NEAR_FUNC 361 | 362 | #endif 363 | 364 | /** \brief Scatter-gather descriptor 365 | * 366 | * Slightly optimised for small platforms - we assume we won't need any 367 | * element bigger than 64K. 368 | */ 369 | typedef struct ns_iovec { 370 | void *iov_base; 371 | uint_fast16_t iov_len; 372 | } ns_iovec_t; 373 | 374 | #endif /* NS_TYPES_H */ 375 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # only build tests on targets that declare they are like posix 2 | if(DEFINED LINUXIFY) 3 | 4 | project(mbed-client-cli-unittest) 5 | cmake_minimum_required(VERSION 3.11) 6 | 7 | include(FindUnixCommands) 8 | 9 | option(enable_coverage_data "Enable Coverage data" OFF) 10 | 11 | enable_testing () 12 | 13 | if (enable_coverage_data) 14 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --coverage") 15 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --coverage") 16 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage") 17 | endif () 18 | 19 | # Google Tests 20 | include(FetchContent) 21 | FetchContent_Declare( 22 | googletest 23 | URL https://github.com/google/googletest/archive/refs/tags/release-1.11.0.zip 24 | ) 25 | 26 | # For Windows: Prevent overriding the parent project's compiler/linker settings 27 | set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) 28 | FetchContent_MakeAvailable(googletest) 29 | 30 | add_definitions("-Wno-write-strings") 31 | 32 | add_library(mbed-trace INTERFACE) 33 | target_include_directories(mbed-trace INTERFACE 34 | ../example/linux 35 | ) 36 | 37 | # FULL BUILD TESTS 38 | set(FLAGS 39 | MBED_CONF_CMDLINE_USE_MINIMUM_SET=0 40 | MBED_CONF_CMDLINE_USE_MINIMUM_SET=1 41 | ) 42 | set(TESTS 43 | full 44 | min 45 | ) 46 | foreach(flag ${FLAGS}) 47 | list(FIND FLAGS ${flag} index) 48 | list(GET TESTS ${index} TEST) 49 | 50 | MESSAGE("TEST flag: ${flag}") 51 | 52 | add_library( mbed-client-cli-${TEST} 53 | ../source/ns_cmdline.c 54 | ../source/ns_list_internal/ns_list.c 55 | ) 56 | target_link_libraries(mbed-client-cli-${TEST} mbed-trace) 57 | 58 | target_include_directories(mbed-client-cli-${TEST} 59 | PRIVATE 60 | ../source/ns_list_internal 61 | ) 62 | 63 | target_include_directories(mbed-client-cli-${TEST} 64 | PUBLIC 65 | .. 66 | ../mbed-client-cli 67 | ) 68 | 69 | # GTest framework requires C++ version 11 70 | set (CMAKE_CXX_STANDARD 11) 71 | 72 | target_compile_definitions(mbed-client-cli-${TEST} PUBLIC ${flag}) 73 | 74 | # describe the test executable 75 | add_executable(mbed_client_cli_test_${TEST} Test.cpp) 76 | 77 | # describe what the test executable needs to link with 78 | target_link_libraries(mbed_client_cli_test_${TEST} 79 | "mbed-client-cli-${TEST}" 80 | "mbed-trace" 81 | gtest_main 82 | ) 83 | 84 | target_compile_definitions(mbed_client_cli_test_${TEST} PUBLIC ${flag}) 85 | add_test(NAME mbed_client_cli_test_${TEST} COMMAND mbed_client_cli_test_${TEST}) 86 | 87 | include(GoogleTest) 88 | gtest_discover_tests(mbed_client_cli_test_${TEST} EXTRA_ARGS --gtest_output=xml: XML_OUTPUT_DIR mbed_client_cli_test_${TEST}) 89 | 90 | if (enable_coverage_data AND ${CMAKE_PROJECT_NAME} STREQUAL "mbed-client-cli-unittest") 91 | file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/html") 92 | 93 | # get path mbed-client-cli top level as coverage is calculated from sources 94 | get_filename_component(CLI_TOP_LEVEL ../ ABSOLUTE) 95 | 96 | add_test(NAME mbed_client_cli_test_${TEST}_cov WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} 97 | COMMAND ${BASH} -c "gcovr --root ${CLI_TOP_LEVEL} -e 'Test.cpp' -e '.*build.*' --html --html-details -o ${CMAKE_CURRENT_BINARY_DIR}/html/coverage_index.html" 98 | ) 99 | endif () 100 | endforeach() 101 | 102 | endif() 103 | -------------------------------------------------------------------------------- /test/Test.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2019, Pelion and affiliates. 3 | * SPDX-License-Identifier: Apache-2.0 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 | * http://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 | 18 | /** 19 | * \file \test\Test.c 20 | * 21 | * \brief Unit tests for mbed-client-cli 22 | */ 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include "gtest/gtest.h" 30 | 31 | #define MBED_CONF_MBED_TRACE_ENABLE 1 32 | #define MBED_CONF_MBED_TRACE_FEA_IPV6 0 33 | 34 | /* 35 | #define MBED_CONF_CMDLINE_ENABLE_HISTORY 1 36 | #define MBED_CONF_CMDLINE_ENABLE_ESCAPE_HANDLING 1 37 | #define MBED_CONF_CMDLINE_ENABLE_OPERATORS 1 38 | #define MBED_CONF_CMDLINE_ENABLE_ALIASES 1 39 | #define MBED_CONF_CMDLINE_ENABLE_INTERNAL_COMMANDS 1 40 | #define MBED_CONF_CMDLINE_ENABLE_INTERNAL_VARIABLES 1 41 | #define MBED_CONF_CMDLINE_INCLUDE_MAN 1 42 | #define MBED_CONF_CMDLINE_MAX_LINE_LENGTH 100 43 | #define MBED_CONF_CMDLINE_ARGUMENTS_MAX_COUNT 2 44 | #define MBED_CONF_CMDLINE_HISTORY_MAX_COUNT 1 45 | */ 46 | 47 | #if MBED_CONF_CMDLINE_USE_MINIMUM_SET == 1 48 | // this is copypaste from pre-defined minimum config 49 | #define MBED_CONF_CMDLINE_INIT_AUTOMATION_MODE 1 50 | #define MBED_CONF_CMDLINE_ENABLE_HISTORY 0 51 | #define MBED_CONF_CMDLINE_ENABLE_ALIASES 0 52 | #define MBED_CONF_CMDLINE_ENABLE_ESCAPE_HANDLING 0 53 | #define MBED_CONF_CMDLINE_ENABLE_OPERATORS 0 54 | #define MBED_CONF_CMDLINE_ENABLE_INTERNAL_COMMANDS 0 55 | #define MBED_CONF_CMDLINE_ENABLE_INTERNAL_VARIABLES 0 56 | #define MBED_CONF_CMDLINE_INCLUDE_MAN 0 57 | #define MBED_CONF_CMDLINE_MAX_LINE_LENGTH 100 58 | #define MBED_CONF_CMDLINE_ARGUMENTS_MAX_COUNT 10 59 | #define MBED_CONF_CMDLINE_HISTORY_MAX_COUNT 0 60 | #else 61 | // this is copypaste from pre-defined minimum config 62 | #define MBED_CONF_CMDLINE_INIT_AUTOMATION_MODE 0 63 | #define MBED_CONF_CMDLINE_ENABLE_HISTORY 1 64 | #define MBED_CONF_CMDLINE_ENABLE_ALIASES 1 65 | #define MBED_CONF_CMDLINE_ENABLE_ESCAPE_HANDLING 1 66 | #define MBED_CONF_CMDLINE_ENABLE_OPERATORS 1 67 | #define MBED_CONF_CMDLINE_ENABLE_INTERNAL_COMMANDS 1 68 | #define MBED_CONF_CMDLINE_ENABLE_INTERNAL_VARIABLES 1 69 | #define MBED_CONF_CMDLINE_INCLUDE_MAN 1 70 | #define MBED_CONF_CMDLINE_MAX_LINE_LENGTH 2000 71 | #define MBED_CONF_CMDLINE_ARGUMENTS_MAX_COUNT 30 72 | #define MBED_CONF_CMDLINE_HISTORY_MAX_COUNT 10 73 | #endif 74 | 75 | #include "mbed-trace/mbed_trace.h" 76 | #include "mbed-client-cli/ns_cmdline.h" 77 | #define MAX(x,y) (x>y?x:y) 78 | 79 | #define BUFSIZE 1024 80 | char buf[BUFSIZE] = {0}; 81 | #define INIT_BUF() memset(buf, 0, BUFSIZE) 82 | int cmd_dummy(int argc, char *argv[]) 83 | { 84 | return 0; 85 | } 86 | 87 | int mutex_wait_count = 0; 88 | int mutex_release_count = 0; 89 | int mutex_count_expected_difference = 1; 90 | bool check_mutex_lock_state = false; 91 | void my_mutex_wait() 92 | { 93 | mutex_wait_count++; 94 | } 95 | void my_mutex_release() 96 | { 97 | mutex_release_count++; 98 | } 99 | 100 | void myprint(const char *fmt, va_list ap) 101 | { 102 | if (check_mutex_lock_state) { 103 | ASSERT_TRUE((mutex_wait_count - mutex_release_count) == mutex_count_expected_difference); 104 | } 105 | vsnprintf(buf + strlen(buf), BUFSIZE - strlen(buf), fmt, ap); 106 | //printf("\nMYPRINT: %s\n", buf); //for test test 107 | } 108 | void input(const char *str) 109 | { 110 | while (*str != 0) { 111 | cmd_char_input(*str++); 112 | } 113 | } 114 | 115 | #define ESCAPE(x) "\x1b" x 116 | #define LF '\n' 117 | #define LF_S "\n" 118 | #define CR '\r' 119 | #define CR_S "\r" 120 | #define DEFAULT_PROMPT "/>" 121 | #define FORWARD "C" 122 | #define BACKWARD "D" 123 | #define REQUEST(x) input(x);INIT_BUF();cmd_char_input(LF); 124 | #define PROMPT(input, prompt) CR_S ESCAPE("[2K") prompt input ESCAPE("[1D") 125 | #define RAW_RESPONSE_WITH_PROMPT(x, prompt) CR_S LF_S x PROMPT(" ", prompt) 126 | #define RESPONSE_WITH_PROMPT(x, prompt) RAW_RESPONSE_WITH_PROMPT(x CR_S LF_S, prompt) 127 | #define RESPONSE(x) RESPONSE_WITH_PROMPT(x, DEFAULT_PROMPT) 128 | 129 | #define CMDLINE(x) CR_S ESCAPE("[2K") DEFAULT_PROMPT x ESCAPE("[1D") 130 | #define CMDLINE_EMPTY CMDLINE(" ") 131 | #define CMDLINE_CUR(x, cursor, dir) CR_S ESCAPE("[2K") DEFAULT_PROMPT x ESCAPE("[" cursor dir) 132 | #define CLEAN() cmd_char_input(LF);INIT_BUF(); 133 | 134 | //vt100 keycodes 135 | #define HOME() input(ESCAPE("[1~")) 136 | #define INSERT() input(ESCAPE("[2~")) 137 | #define DELETE() input(ESCAPE("[3~")) 138 | #define BACKSPACE() input("\x7f") 139 | #define LEFT() input(ESCAPE("[D")) 140 | #define LEFT_N(n) for(int i=0;i 20); 349 | CHECK_RETCODE(0); 350 | 351 | INIT_BUF(); 352 | REQUEST("echo --help"); 353 | ASSERT_TRUE(strlen(buf) > 20); 354 | CHECK_RETCODE(0); 355 | #endif 356 | } 357 | 358 | TEST_F(mbedClientCli, retcodes) 359 | { 360 | #if MBED_CONF_CMDLINE_ENABLE_INTERNAL_COMMANDS 361 | TEST_RETCODE_WITH_COMMAND("true", CMDLINE_RETCODE_SUCCESS); 362 | TEST_RETCODE_WITH_COMMAND("false", CMDLINE_RETCODE_FAIL); 363 | TEST_RETCODE_WITH_COMMAND("set --abc", CMDLINE_RETCODE_INVALID_PARAMETERS); 364 | #endif 365 | TEST_RETCODE_WITH_COMMAND("abc", CMDLINE_RETCODE_COMMAND_NOT_FOUND); 366 | } 367 | TEST_F(mbedClientCli, cmd_echo) 368 | { 369 | #if MBED_CONF_CMDLINE_INIT_AUTOMATION_MODE == 1 370 | TEST_RETCODE_WITH_COMMAND("echo Hi!", CMDLINE_RETCODE_SUCCESS); 371 | #else 372 | REQUEST("echo Hi!"); 373 | EXPECT_STREQ(RESPONSE("Hi! "), buf); 374 | CHECK_RETCODE(0); 375 | #endif 376 | } 377 | #if MBED_CONF_CMDLINE_ENABLE_INTERNAL_COMMANDS 378 | TEST_F(mbedClientCli, cmd_echo_with_cr) 379 | { 380 | input("echo crlf"); 381 | INIT_BUF(); 382 | input("\r\n"); 383 | EXPECT_STREQ(RESPONSE("crlf "), buf); 384 | CHECK_RETCODE(0); 385 | } 386 | TEST_F(mbedClientCli, cmd_echo_cr_only) 387 | { 388 | input("echo cr"); 389 | INIT_BUF(); 390 | input("\r"); 391 | EXPECT_STREQ(RESPONSE("cr "), buf); 392 | CHECK_RETCODE(0); 393 | } 394 | TEST_F(mbedClientCli, cmd_echo1) 395 | { 396 | REQUEST(" echo Hi!"); 397 | EXPECT_STREQ(RESPONSE("Hi! "), buf); 398 | } 399 | TEST_F(mbedClientCli, cmd_echo2) 400 | { 401 | REQUEST("echo foo faa"); 402 | EXPECT_STREQ(RESPONSE("foo faa "), buf); 403 | } 404 | TEST_F(mbedClientCli, cmd_echo3) 405 | { 406 | REQUEST("echo foo faa"); 407 | EXPECT_STREQ(RESPONSE("foo faa "), buf); 408 | } 409 | TEST_F(mbedClientCli, cmd_echo4) 410 | { 411 | REQUEST("echo foo faa"); 412 | EXPECT_STREQ(RESPONSE("foo faa "), buf); 413 | } 414 | TEST_F(mbedClientCli, cmd_echo5) 415 | { 416 | REQUEST("echo \"foo faa\""); 417 | EXPECT_STREQ(RESPONSE("foo faa "), buf); 418 | } 419 | TEST_F(mbedClientCli, cmd_echo6) 420 | { 421 | REQUEST("echo \"foo faa"); 422 | EXPECT_STREQ(RESPONSE("\"foo faa "), buf); 423 | } 424 | TEST_F(mbedClientCli, cmd_echo7) 425 | { 426 | REQUEST("echo 'foo faa\""); 427 | EXPECT_STREQ(RESPONSE("'foo faa\" "), buf); 428 | } 429 | TEST_F(mbedClientCli, cmd_echo8) 430 | { 431 | REQUEST("echof\x7f foo faa"); 432 | EXPECT_STREQ(RESPONSE("foo faa "), buf); 433 | } 434 | TEST_F(mbedClientCli, cmd_echo9) 435 | { 436 | REQUEST("echo foo faa\x1b[D\x1b[D\x1b[D hello "); 437 | EXPECT_STREQ(RESPONSE("foo hello faa "), buf); 438 | CLEAN(); 439 | } 440 | TEST_F(mbedClientCli, cmd_echo10) 441 | { 442 | REQUEST("echo foo faa\x1b[D\x1b[C\x1b[C hello "); //echo foo hello faa 443 | EXPECT_STREQ(RESPONSE("foo faa hello "), buf); 444 | CLEAN(); 445 | } 446 | TEST_F(mbedClientCli, cmd_echo11) 447 | { 448 | REQUEST("echo off\n"); 449 | INIT_BUF(); 450 | input("echo test"); 451 | EXPECT_STREQ("", buf); 452 | input("\n"); 453 | EXPECT_STREQ("test \r\n", buf); 454 | INIT_BUF(); 455 | REQUEST("echo on\n"); 456 | INIT_BUF(); 457 | input("e"); 458 | EXPECT_STREQ(CMDLINE("e "), buf); 459 | INIT_BUF(); 460 | input("c"); 461 | EXPECT_STREQ(CMDLINE("ec "), buf); 462 | INIT_BUF(); 463 | input("h"); 464 | EXPECT_STREQ(CMDLINE("ech "), buf); 465 | INIT_BUF(); 466 | input("o"); 467 | EXPECT_STREQ(CMDLINE("echo "), buf); 468 | INIT_BUF(); 469 | input(" "); 470 | EXPECT_STREQ(CMDLINE("echo "), buf); 471 | INIT_BUF(); 472 | input("o"); 473 | EXPECT_STREQ(CMDLINE("echo o "), buf); 474 | INIT_BUF(); 475 | input("k"); 476 | EXPECT_STREQ(CMDLINE("echo ok "), buf); 477 | CLEAN(); 478 | } 479 | #endif 480 | #if MBED_CONF_CMDLINE_ENABLE_HISTORY 481 | TEST_F(mbedClientCli, cmd_arrows_up) 482 | { 483 | REQUEST("echo foo-1"); 484 | INIT_BUF(); 485 | input("\x1b[A"); 486 | EXPECT_STREQ(CMDLINE("echo foo-1 "), buf); 487 | INIT_BUF(); 488 | input("\x1b[A"); 489 | EXPECT_STREQ(CMDLINE("echo foo-1 "), buf); 490 | CLEAN(); 491 | } 492 | TEST_F(mbedClientCli, cmd_arrows_up_down) 493 | { 494 | REQUEST("echo test-1"); 495 | EXPECT_STREQ(RESPONSE("test-1 "), buf); 496 | REQUEST("echo test-2"); 497 | EXPECT_STREQ(RESPONSE("test-2 "), buf); 498 | REQUEST("echo test-3"); 499 | EXPECT_STREQ(RESPONSE("test-3 "), buf); 500 | 501 | INIT_BUF(); 502 | UP(); 503 | EXPECT_STREQ(CMDLINE("echo test-3 "), buf); 504 | INIT_BUF(); 505 | UP(); 506 | EXPECT_STREQ(CMDLINE("echo test-2 "), buf); 507 | INIT_BUF(); 508 | UP(); 509 | EXPECT_STREQ(CMDLINE("echo test-1 "), buf); 510 | INIT_BUF(); 511 | UP(); 512 | EXPECT_STREQ(CMDLINE("echo test-1 "), buf); 513 | INIT_BUF(); 514 | DOWN(); 515 | EXPECT_STREQ(CMDLINE("echo test-2 "), buf); 516 | INIT_BUF(); 517 | DOWN(); 518 | EXPECT_STREQ(CMDLINE("echo test-3 "), buf); 519 | INIT_BUF(); 520 | DOWN(); 521 | EXPECT_STREQ(CMDLINE(" "), buf); 522 | CLEAN(); 523 | } 524 | TEST_F(mbedClientCli, cmd_set) 525 | { 526 | TEST_RETCODE_WITH_COMMAND("set abc def", CMDLINE_RETCODE_SUCCESS); 527 | #if MBED_CONF_CMDLINE_INIT_AUTOMATION_MODE == 0 && MBED_CONF_CMDLINE_ENABLE_INTERNAL_COMMANDS == 1 528 | REQUEST("echo $abc"); 529 | EXPECT_STREQ(RESPONSE("def "), buf); 530 | #endif 531 | } 532 | TEST_F(mbedClientCli, cmd_history) 533 | { 534 | //history when there is some 535 | REQUEST("echo test"); 536 | INIT_BUF(); 537 | REQUEST("history"); 538 | const char *to_be = 539 | "\r\nHistory [2/31]:\r\n" \ 540 | "[0]: echo test\r\n" \ 541 | "[1]: history\r\n" \ 542 | CMDLINE_EMPTY; 543 | EXPECT_STREQ(to_be, buf); 544 | CLEAN(); 545 | } 546 | TEST_F(mbedClientCli, cmd_history_clear) 547 | { 548 | //history when there is some 549 | REQUEST("echo test"); 550 | TEST_RETCODE_WITH_COMMAND("history clear", CMDLINE_RETCODE_SUCCESS); 551 | INIT_BUF(); 552 | REQUEST("history"); 553 | const char *to_be = 554 | "\r\nHistory [1/31]:\r\n" \ 555 | "[0]: history\r\n" \ 556 | CMDLINE_EMPTY; 557 | EXPECT_STREQ(to_be, buf); 558 | CLEAN(); 559 | } 560 | TEST_F(mbedClientCli, cmd_history_skip_duplicates) 561 | { 562 | //history when there is some 563 | REQUEST("echo test"); 564 | REQUEST("echo test"); 565 | REQUEST("echo test"); 566 | INIT_BUF(); 567 | REQUEST("history"); 568 | const char *to_be = 569 | "\r\nHistory [2/31]:\r\n" \ 570 | "[0]: echo test\r\n" \ 571 | "[1]: history\r\n" \ 572 | CMDLINE_EMPTY; 573 | EXPECT_STREQ(to_be, buf); 574 | CLEAN(); 575 | } 576 | TEST_F(mbedClientCli, cmd_history_empty) 577 | { 578 | //history when its empty 579 | INIT_BUF(); 580 | REQUEST("history"); 581 | const char *to_be = 582 | "\r\nHistory [1/31]:\r\n" \ 583 | "[0]: history\r\n" \ 584 | CMDLINE_EMPTY; 585 | EXPECT_STREQ(to_be, buf); 586 | CLEAN(); 587 | } 588 | TEST_F(mbedClientCli, cmd_pageup_page_down) 589 | { 590 | //goto history beginning/end 591 | REQUEST("echo test-1"); 592 | REQUEST("echo test-2"); 593 | REQUEST("echo test-3"); 594 | REQUEST("echo test-4"); 595 | INIT_BUF(); 596 | PAGE_UP(); 597 | EXPECT_STREQ(CMDLINE("echo test-1 "), buf); 598 | INIT_BUF(); 599 | PAGE_DOWN(); 600 | EXPECT_STREQ(CMDLINE("echo test-4 "), buf); 601 | CLEAN(); 602 | } 603 | TEST_F(mbedClientCli, cmd_text_pageup) 604 | { 605 | REQUEST("echo test-1"); 606 | REQUEST("echo test-2"); 607 | REQUEST("echo test-3"); 608 | REQUEST("echo test-4"); 609 | input("hello"); 610 | INIT_BUF(); 611 | PAGE_UP(); //goto end of history 612 | EXPECT_STREQ(CMDLINE("echo test-1 "), buf); 613 | INIT_BUF(); 614 | PAGE_DOWN(); //goto beginning of history - it should be just writted "hello" 615 | EXPECT_STREQ(CMDLINE("hello "), buf); 616 | CLEAN(); 617 | } 618 | TEST_F(mbedClientCli, cmd_text_pageup_up) 619 | { 620 | REQUEST("echo test-1"); 621 | REQUEST("echo test-2"); 622 | REQUEST("echo test-3"); 623 | REQUEST("echo test-4"); 624 | input("hello"); 625 | INIT_BUF(); 626 | PAGE_UP(); //goto end of history 627 | EXPECT_STREQ(CMDLINE("echo test-1 "), buf); 628 | INIT_BUF(); 629 | DOWN(); 630 | EXPECT_STREQ(CMDLINE("echo test-2 "), buf); 631 | INIT_BUF(); 632 | PAGE_DOWN(); //goto beginning of history - it should be just writted "hello" 633 | EXPECT_STREQ(CMDLINE("hello "), buf); 634 | CLEAN(); 635 | } 636 | #endif 637 | #if MBED_CONF_CMDLINE_ENABLE_ESCAPE_HANDLING 638 | TEST_F(mbedClientCli, cmd_alt_left_right) 639 | { 640 | input("11 22 33"); 641 | INIT_BUF(); 642 | ALT_LEFT(); 643 | EXPECT_STREQ(CMDLINE_CUR("11 22 33 ", "3", BACKWARD), buf); 644 | INIT_BUF(); 645 | ALT_LEFT(); 646 | EXPECT_STREQ(CMDLINE_CUR("11 22 33 ", "6", BACKWARD), buf); 647 | INIT_BUF(); 648 | ALT_LEFT(); 649 | EXPECT_STREQ(CMDLINE_CUR("11 22 33 ", "9", BACKWARD), buf); 650 | INIT_BUF(); 651 | input("a"); 652 | EXPECT_STREQ(CMDLINE_CUR("a11 22 33 ", "9", BACKWARD), buf); 653 | INIT_BUF(); 654 | ALT_RIGHT(); 655 | EXPECT_STREQ(CMDLINE_CUR("a11 22 33 ", "7", BACKWARD), buf); 656 | INIT_BUF(); 657 | ALT_RIGHT(); 658 | EXPECT_STREQ(CMDLINE_CUR("a11 22 33 ", "4", BACKWARD), buf); 659 | INIT_BUF(); 660 | ALT_RIGHT(); 661 | EXPECT_STREQ(CMDLINE_CUR("a11 22 33 ", "1", BACKWARD), buf); 662 | INIT_BUF(); 663 | input("a"); 664 | EXPECT_STREQ(CMDLINE_CUR("a11 22 33a ", "1", BACKWARD), buf); 665 | CLEAN(); 666 | } 667 | TEST_F(mbedClientCli, cmd_text_delete) 668 | { 669 | input("hello world"); 670 | LEFT_N(2); 671 | DELETE(); 672 | INIT_BUF(); 673 | DELETE(); 674 | EXPECT_STREQ(CMDLINE("hello wor "), buf); 675 | INIT_BUF(); 676 | DELETE(); 677 | EXPECT_STREQ(CMDLINE("hello wor "), buf); 678 | INIT_BUF(); 679 | DELETE(); 680 | EXPECT_STREQ(CMDLINE("hello wor "), buf); 681 | LEFT_N(2); 682 | INIT_BUF(); 683 | DELETE(); 684 | EXPECT_STREQ(CMDLINE_CUR("hello wr ", "2", BACKWARD), buf); 685 | BACKSPACE(); 686 | BACKSPACE(); 687 | INIT_BUF(); 688 | BACKSPACE(); 689 | EXPECT_STREQ(CMDLINE_CUR("hellr ", "2", BACKWARD), buf); 690 | CLEAN(); 691 | } 692 | TEST_F(mbedClientCli, cmd_insert) 693 | { 694 | CLEAN(); 695 | input("echo hello word"); 696 | LEFT(); 697 | INIT_BUF(); 698 | input("l"); 699 | EXPECT_STREQ(CMDLINE_CUR("echo hello world ", "2", BACKWARD), buf); 700 | LEFT_N(10); 701 | INIT_BUF(); 702 | LEFT(); 703 | EXPECT_STREQ(CMDLINE_CUR("echo hello world ", "13", BACKWARD), buf); 704 | INIT_BUF(); 705 | RIGHT(); 706 | EXPECT_STREQ(CMDLINE_CUR("echo hello world ", "12", BACKWARD), buf); 707 | CLEAN(); 708 | } 709 | TEST_F(mbedClientCli, ctrl_w) 710 | { 711 | input("\r\n"); 712 | input("echo ping pong"); 713 | INIT_BUF(); 714 | input("\x17"); 715 | EXPECT_STREQ(CMDLINE_CUR("echo ping ", "1", BACKWARD), buf); 716 | 717 | INIT_BUF(); 718 | input("\x17"); 719 | EXPECT_STREQ(CMDLINE_CUR("echo ", "1", BACKWARD), buf); 720 | INIT_BUF(); 721 | input("\x17"); 722 | EXPECT_STREQ(CMDLINE_CUR(" ", "1", BACKWARD), buf); 723 | CLEAN(); 724 | } 725 | 726 | TEST_F(mbedClientCli, ctrl_w_1) 727 | { 728 | input("echo ping pong"); 729 | LEFT(); 730 | INIT_BUF(); 731 | input("\x17"); 732 | EXPECT_STREQ(CMDLINE_CUR("echo ping g ", "2", BACKWARD), buf); 733 | INIT_BUF(); 734 | input("\x17"); 735 | EXPECT_STREQ(CMDLINE_CUR("echo g ", "2", BACKWARD), buf); 736 | INIT_BUF(); 737 | input("\x17"); 738 | EXPECT_STREQ(CMDLINE_CUR("g ", "2", BACKWARD), buf); 739 | CLEAN(); 740 | } 741 | #endif 742 | 743 | #if MBED_CONF_CMDLINE_ENABLE_INTERNAL_VARIABLES 744 | TEST_F(mbedClientCli, cmd_request_screen_size) 745 | { 746 | cmd_request_screen_size(); 747 | input(ESCAPE("[6;7R")); 748 | INIT_BUF(); 749 | REQUEST("set"); 750 | EXPECT_STREQ("\r\nvariables:\r\n" 751 | "PS1='/>'\r\n" 752 | "?=0\r\n" 753 | "LINES=6\r\n" 754 | "COLUMNS=7\r\n" 755 | CMDLINE_EMPTY, buf); 756 | } 757 | #endif 758 | #if MBED_CONF_CMDLINE_ENABLE_ESCAPE_HANDLING 759 | TEST_F(mbedClientCli, cmd_tab_1) 760 | { 761 | INIT_BUF(); 762 | input("e"); 763 | INIT_BUF(); 764 | input("\t"); 765 | EXPECT_STREQ(CMDLINE_CUR("echo ", "1", BACKWARD), buf); 766 | 767 | input("\nech"); 768 | INIT_BUF(); 769 | input("\t"); 770 | EXPECT_STREQ(CMDLINE_CUR("echo ", "1", BACKWARD), buf); 771 | 772 | input("\n"); 773 | } 774 | TEST_F(mbedClientCli, cmd_tab_2) 775 | { 776 | INIT_BUF(); 777 | 778 | cmd_add("role", cmd_dummy, 0, 0); 779 | cmd_add("route", cmd_dummy, 0, 0); 780 | cmd_add("rile", cmd_dummy, 0, 0); 781 | input("r"); 782 | INIT_BUF(); 783 | 784 | input("\t"); 785 | EXPECT_STREQ(CMDLINE_CUR("role ", "1", BACKWARD), buf); 786 | 787 | INIT_BUF(); 788 | input("\t"); 789 | EXPECT_STREQ(CMDLINE_CUR("route ", "1", BACKWARD), buf); 790 | 791 | INIT_BUF(); 792 | input("\t"); 793 | EXPECT_STREQ(CMDLINE_CUR("rile ", "1", BACKWARD), buf); 794 | 795 | INIT_BUF(); 796 | input("\x1b[Z"); 797 | EXPECT_STREQ(CMDLINE_CUR("route ", "1", BACKWARD), buf); 798 | 799 | INIT_BUF(); 800 | input("\x1b[Z"); 801 | EXPECT_STREQ(CMDLINE_CUR("role ", "1", BACKWARD), buf); 802 | 803 | input("\n"); 804 | } 805 | #endif 806 | TEST_F(mbedClientCli, cmd_delete) 807 | { 808 | INIT_BUF(); 809 | cmd_add("role", cmd_dummy, 0, 0); 810 | cmd_delete("role"); 811 | REQUEST("role"); 812 | CHECK_RETCODE(CMDLINE_RETCODE_COMMAND_NOT_FOUND); 813 | } 814 | #if MBED_CONF_CMDLINE_ENABLE_INTERNAL_COMMANDS == 1 815 | TEST_F(mbedClientCli, cmd_escape) 816 | { 817 | INIT_BUF(); 818 | REQUEST("echo \\\""); 819 | EXPECT_STREQ(RESPONSE("\\\" "), buf); 820 | 821 | INIT_BUF(); 822 | REQUEST("echo \"\\\\\"\""); 823 | EXPECT_STREQ(RESPONSE("\\\" "), buf); 824 | } 825 | #endif 826 | #if MBED_CONF_CMDLINE_ENABLE_ALIASES == 1 827 | TEST_F(mbedClientCli, cmd_tab_3) 828 | { 829 | INIT_BUF(); 830 | cmd_add("role", cmd_dummy, 0, 0); 831 | cmd_alias_add("rose", "role"); 832 | cmd_alias_add("rope", "rope"); 833 | 834 | input("r"); 835 | 836 | INIT_BUF(); 837 | input("\t"); 838 | EXPECT_STREQ(CMDLINE_CUR("role ", "1", BACKWARD), buf); 839 | 840 | INIT_BUF(); 841 | input("\t"); 842 | EXPECT_STREQ(CMDLINE_CUR("rose ", "1", BACKWARD), buf); 843 | 844 | INIT_BUF(); 845 | input("\t"); 846 | EXPECT_STREQ(CMDLINE_CUR("rope ", "1", BACKWARD), buf); 847 | 848 | INIT_BUF(); 849 | input("\t"); 850 | EXPECT_STREQ(CMDLINE_CUR("r ", "1", BACKWARD), buf); 851 | 852 | INIT_BUF(); 853 | input("o"); 854 | EXPECT_STREQ(CMDLINE_CUR("ro ", "1", BACKWARD), buf); 855 | 856 | ESC(); 857 | INIT_BUF(); 858 | } 859 | #endif 860 | #if MBED_CONF_CMDLINE_ENABLE_INTERNAL_COMMANDS == 1 861 | TEST_F(mbedClientCli, cmd_tab_4) 862 | { 863 | INIT_BUF(); 864 | cmd_variable_add("dut1", "hello"); 865 | 866 | input("e"); 867 | 868 | INIT_BUF(); 869 | input("\t"); 870 | EXPECT_STREQ(CMDLINE_CUR("echo ", "1", BACKWARD), buf); 871 | 872 | input(" $d"); 873 | INIT_BUF(); 874 | input("u"); 875 | EXPECT_STREQ(CMDLINE_CUR("echo $du ", "1", BACKWARD), buf); 876 | 877 | INIT_BUF(); 878 | input("\t"); 879 | EXPECT_STREQ(CMDLINE_CUR("echo $dut1 ", "1", BACKWARD), buf); 880 | 881 | input("\ne"); 882 | INIT_BUF(); 883 | input("\t"); 884 | EXPECT_STREQ(CMDLINE_CUR("echo ", "1", BACKWARD), buf); 885 | 886 | cmd_variable_add("dut1", NULL); 887 | CLEAN(); 888 | } 889 | 890 | // alias test 891 | TEST_F(mbedClientCli, cmd_alias_2) 892 | { 893 | REQUEST("alias foo bar"); 894 | INIT_BUF(); 895 | REQUEST("alias"); 896 | EXPECT_STREQ("\r\nalias:\r\n" 897 | "foo 'bar'\r\n" 898 | "_ 'alias foo bar'\r\n" 899 | CMDLINE_EMPTY, buf); 900 | 901 | REQUEST("alias foo"); 902 | INIT_BUF(); 903 | REQUEST("alias"); 904 | EXPECT_STREQ("\r\nalias:\r\n" 905 | "_ 'alias foo'\r\n" 906 | CMDLINE_EMPTY, buf); 907 | } 908 | TEST_F(mbedClientCli, cmd_alias_3) 909 | { 910 | cmd_alias_add("p", "echo"); 911 | REQUEST("p toimii"); 912 | CHECK_RETCODE(0); 913 | EXPECT_STREQ("\r\ntoimii \r\n" CMDLINE_EMPTY, buf); 914 | 915 | cmd_alias_add("printtti", "echo"); 916 | REQUEST("printtti toimii"); 917 | EXPECT_STREQ("\r\ntoimii \r\n" CMDLINE_EMPTY, buf); 918 | } 919 | TEST_F(mbedClientCli, cmd_alias_4) 920 | { 921 | REQUEST("alias dut1 \"echo dut1\""); 922 | CHECK_RETCODE(0); 923 | REQUEST("alias dut2 \"echo dut2\""); 924 | REQUEST("alias dut3 \"echo dut3\""); 925 | REQUEST("dut1"); 926 | CHECK_RETCODE(0); 927 | EXPECT_STREQ(RESPONSE("dut1 "), buf); 928 | } 929 | TEST_F(mbedClientCli, cmd_series) 930 | { 931 | REQUEST("alias dut1 \"echo dut1\""); 932 | REQUEST("alias dut2 \"echo dut2\""); 933 | REQUEST("alias dut3 \"echo dut3\""); 934 | REQUEST("dut1;dut2;dut3"); 935 | CHECK_RETCODE(0); 936 | EXPECT_STREQ(RESPONSE("dut1 \r\ndut2 \r\ndut3 "), buf); 937 | } 938 | TEST_F(mbedClientCli, cmd_alias_series) 939 | { 940 | cmd_alias_add("multiecho", "echo dut1;echo dut2;"); 941 | REQUEST("multiecho"); 942 | CHECK_RETCODE(0); 943 | EXPECT_STREQ(RESPONSE("dut1 \r\ndut2 "), buf); 944 | } 945 | #endif 946 | #if MBED_CONF_CMDLINE_ENABLE_INTERNAL_VARIABLES 947 | TEST_F(mbedClientCli, cmd_var_1) 948 | { 949 | REQUEST("set foo \"bar test\""); 950 | INIT_BUF(); 951 | REQUEST("set"); 952 | EXPECT_STREQ("\r\nvariables:\r\n" 953 | "PS1='/>'\r\n" 954 | "?=0\r\n" 955 | "foo='bar test'\r\n" 956 | CMDLINE_EMPTY, buf); 957 | REQUEST("unset foo"); 958 | } 959 | 960 | TEST_F(mbedClientCli, cmd_unset) 961 | { 962 | REQUEST("set foo=a"); 963 | REQUEST("unset foo"); 964 | INIT_BUF(); 965 | REQUEST("set"); 966 | EXPECT_STREQ("\r\nvariables:\r\n" 967 | "PS1='/>'\r\n" 968 | "?=0\r\n" 969 | CMDLINE_EMPTY, buf); 970 | } 971 | 972 | TEST_F(mbedClientCli, cmd_var_2) 973 | { 974 | REQUEST("set foo \"hello world\""); 975 | CHECK_RETCODE(0); 976 | REQUEST("echo foo"); 977 | EXPECT_STREQ(RESPONSE("foo "), buf); 978 | 979 | REQUEST("echo $foo"); 980 | CHECK_RETCODE(0); 981 | EXPECT_STREQ(RESPONSE("hello world "), buf); 982 | 983 | REQUEST("set faa !"); 984 | REQUEST("echo $foo$faa"); 985 | EXPECT_STREQ(RESPONSE("hello world! "), buf); 986 | REQUEST("unset faa"); 987 | } 988 | #endif 989 | #if MBED_CONF_CMDLINE_ENABLE_INTERNAL_COMMANDS 990 | TEST_F(mbedClientCli, cmd__) 991 | { 992 | REQUEST("echo foo"); 993 | EXPECT_STREQ(RESPONSE("foo "), buf); 994 | REQUEST("_"); 995 | EXPECT_STREQ(RESPONSE("foo "), buf); 996 | } 997 | TEST_F(mbedClientCli, var_prev_cmd) 998 | { 999 | REQUEST("echo"); 1000 | REQUEST("set"); 1001 | EXPECT_STREQ("\r\nvariables:\r\n" 1002 | "PS1='/>'\r\n" 1003 | "?=0\r\n" 1004 | CMDLINE_EMPTY, buf); 1005 | REQUEST("invalid"); 1006 | REQUEST("set"); 1007 | EXPECT_STREQ("\r\nvariables:\r\n" 1008 | "PS1='/>'\r\n" 1009 | "?=-5\r\n" 1010 | CMDLINE_EMPTY, buf); 1011 | } 1012 | TEST_F(mbedClientCli, var_ps1) 1013 | { 1014 | REQUEST("set PS1=abc"); 1015 | EXPECT_STREQ(RAW_RESPONSE_WITH_PROMPT("", "abc"), buf); 1016 | REQUEST("set") 1017 | EXPECT_STREQ("\r\nvariables:\r\n" 1018 | "PS1='abc'\r\n" 1019 | "?=0\r\n" 1020 | "\r" ESCAPE("[2K") "abc " ESCAPE("[1D"), buf); 1021 | } 1022 | 1023 | // operators 1024 | #if MBED_CONF_CMDLINE_ENABLE_OPERATORS 1025 | TEST_F(mbedClientCli, operator_semicolon) 1026 | { 1027 | REQUEST("echo hello world") 1028 | EXPECT_STREQ(RESPONSE("hello world "), buf); 1029 | CHECK_RETCODE(CMDLINE_RETCODE_SUCCESS); 1030 | 1031 | REQUEST("setd faa \"hello world\";echo $faa"); 1032 | EXPECT_STREQ("\r\nCommand 'setd' not found.\r\n$faa \r\n" CMDLINE_EMPTY, buf); 1033 | } 1034 | TEST_F(mbedClientCli, operators_and) 1035 | { 1036 | TEST_RETCODE_WITH_COMMAND("true && true", CMDLINE_RETCODE_SUCCESS); 1037 | TEST_RETCODE_WITH_COMMAND("true && false", CMDLINE_RETCODE_FAIL); 1038 | TEST_RETCODE_WITH_COMMAND("false && true", CMDLINE_RETCODE_FAIL); 1039 | TEST_RETCODE_WITH_COMMAND("false && false", CMDLINE_RETCODE_FAIL); 1040 | } 1041 | TEST_F(mbedClientCli, operators_or) 1042 | { 1043 | TEST_RETCODE_WITH_COMMAND("true || true", CMDLINE_RETCODE_SUCCESS); 1044 | TEST_RETCODE_WITH_COMMAND("true || false", CMDLINE_RETCODE_SUCCESS); 1045 | TEST_RETCODE_WITH_COMMAND("false || true", CMDLINE_RETCODE_SUCCESS); 1046 | TEST_RETCODE_WITH_COMMAND("false || false", CMDLINE_RETCODE_FAIL); 1047 | } 1048 | 1049 | TEST_F(mbedClientCli, ampersand) 1050 | { 1051 | REQUEST("echo hello world&"); 1052 | EXPECT_STREQ(RESPONSE("hello world "), buf); 1053 | } 1054 | #endif 1055 | #endif 1056 | TEST_F(mbedClientCli, maxlength) 1057 | { 1058 | int i; 1059 | char test_data[600]; 1060 | char *ptr = test_data; 1061 | strcpy(test_data, "echo "); 1062 | for (i = 5; i < 600; i++) { 1063 | test_data[i] = 'A' + i % 26; 1064 | } 1065 | test_data[599] = 0; 1066 | REQUEST(ptr); 1067 | //EXPECT_STREQ( RESPONSE((test_data+5)), buf); 1068 | } 1069 | 1070 | #define REDIR_DATA "echo Hi!" 1071 | #define PASSTHROUGH_BUF_LENGTH 10 1072 | char passthrough_buffer[PASSTHROUGH_BUF_LENGTH]; 1073 | char *passthrough_ptr = NULL; 1074 | void passthrough_cb(uint8_t c) 1075 | { 1076 | if (passthrough_ptr != NULL) { 1077 | *passthrough_ptr++ = c; 1078 | } 1079 | } 1080 | TEST_F(mbedClientCli, passthrough_set) 1081 | { 1082 | passthrough_ptr = passthrough_buffer; 1083 | memset(&passthrough_buffer, 0, PASSTHROUGH_BUF_LENGTH); 1084 | INIT_BUF(); 1085 | 1086 | cmd_input_passthrough_func(passthrough_cb); 1087 | input(REDIR_DATA); 1088 | 1089 | ASSERT_TRUE(strlen(buf) == 0); 1090 | EXPECT_STREQ(REDIR_DATA, passthrough_buffer); 1091 | 1092 | cmd_input_passthrough_func(NULL); 1093 | INIT_BUF(); 1094 | REQUEST(REDIR_DATA); 1095 | #if MBED_CONF_CMDLINE_INIT_AUTOMATION_MODE == 1 1096 | EXPECT_STREQ("retcode: 0\r\n", buf); 1097 | #else 1098 | EXPECT_STREQ(RESPONSE("Hi! "), buf); 1099 | #endif 1100 | } 1101 | TEST_F(mbedClientCli, passthrough_lf) 1102 | { 1103 | passthrough_ptr = passthrough_buffer; 1104 | memset(&passthrough_buffer, 0, PASSTHROUGH_BUF_LENGTH); 1105 | input("\n"); 1106 | INIT_BUF(); 1107 | cmd_input_passthrough_func(passthrough_cb); 1108 | input(REDIR_DATA); 1109 | EXPECT_EQ(strlen(buf), 0); 1110 | EXPECT_STREQ(REDIR_DATA, passthrough_buffer); 1111 | } 1112 | TEST_F(mbedClientCli, passthrough_cr) 1113 | { 1114 | passthrough_ptr = passthrough_buffer; 1115 | memset(&passthrough_buffer, 0, PASSTHROUGH_BUF_LENGTH); 1116 | input("\r"); 1117 | INIT_BUF(); 1118 | cmd_input_passthrough_func(passthrough_cb); 1119 | input(REDIR_DATA); 1120 | ASSERT_TRUE(strlen(buf) == 0); 1121 | EXPECT_STREQ(REDIR_DATA, passthrough_buffer); 1122 | } 1123 | TEST_F(mbedClientCli, passthrough_crlf) 1124 | { 1125 | passthrough_ptr = passthrough_buffer; 1126 | memset(&passthrough_buffer, 0, PASSTHROUGH_BUF_LENGTH); 1127 | input("\r"); 1128 | INIT_BUF(); 1129 | cmd_input_passthrough_func(passthrough_cb); 1130 | input("\n"); 1131 | input(REDIR_DATA); 1132 | ASSERT_TRUE(strlen(buf) == 0); 1133 | EXPECT_STREQ(REDIR_DATA, passthrough_buffer); 1134 | } 1135 | int cmd_long_called = 0; 1136 | int cmd_long(int argc, char *argv[]) 1137 | { 1138 | cmd_long_called ++; 1139 | return CMDLINE_RETCODE_EXCUTING_CONTINUE; 1140 | } 1141 | TEST_F(mbedClientCli, cmd_continue) 1142 | { 1143 | cmd_add("long", cmd_long, 0, 0); 1144 | previous_retcode = 111; 1145 | TEST_RETCODE_WITH_COMMAND("long", previous_retcode); 1146 | cmd_ready(0); 1147 | CHECK_RETCODE(CMDLINE_RETCODE_SUCCESS); 1148 | EXPECT_EQ(cmd_long_called, 1); 1149 | } 1150 | TEST_F(mbedClientCli, cmd_out_func_set_null) 1151 | { 1152 | cmd_out_func(NULL); 1153 | } 1154 | 1155 | static int outf_called = 0; 1156 | void outf(const char *fmt, va_list ap) 1157 | { 1158 | outf_called++; 1159 | } 1160 | TEST_F(mbedClientCli, cmd_out_func_set) 1161 | { 1162 | outf_called = 0; 1163 | cmd_out_func(&outf); 1164 | // cppcheck-suppress formatExtraArgs 1165 | cmd_vprintf(NULL, NULL); 1166 | EXPECT_EQ(outf_called, 1); 1167 | } 1168 | 1169 | TEST_F(mbedClientCli, cmd_ctrl_func_set_null) 1170 | { 1171 | cmd_ctrl_func(NULL); 1172 | } 1173 | 1174 | int sohf_cb_called = 0; 1175 | void sohf_cb(uint8_t c) 1176 | { 1177 | sohf_cb_called++; 1178 | } 1179 | #if MBED_CONF_CMDLINE_ENABLE_ESCAPE_HANDLING == 1 1180 | TEST_F(mbedClientCli, cmd_ctrl_func_set) 1181 | { 1182 | cmd_ctrl_func(sohf_cb); 1183 | REQUEST("\x04"); 1184 | EXPECT_EQ(sohf_cb_called, 1); 1185 | cmd_ctrl_func(NULL); 1186 | } 1187 | #endif 1188 | 1189 | TEST_F(mbedClientCli, cmd_delete_null) 1190 | { 1191 | cmd_delete(NULL); 1192 | } 1193 | #if MBED_CONF_CMDLINE_ENABLE_HISTORY 1194 | TEST_F(mbedClientCli, cmd_history_size_set) 1195 | { 1196 | cmd_history_size(0); 1197 | EXPECT_EQ(cmd_history_size(1), 1); 1198 | } 1199 | #endif 1200 | TEST_F(mbedClientCli, cmd_add_invalid_params) 1201 | { 1202 | cmd_add(NULL, cmd_dummy, NULL, NULL); 1203 | cmd_add("", cmd_dummy, NULL, NULL); 1204 | cmd_add("abc", NULL, NULL, NULL); 1205 | } 1206 | /* 1207 | // @todo need more work to get in track 1208 | TEST_F(mbedClientCli, cmd_parameter_timestamp_1) 1209 | { 1210 | int argc = 3; 1211 | char *argv[] = {"cmd", "-t", "12345,6789"}; 1212 | const char *key = "-t"; 1213 | int64_t value = 0; 1214 | // for some reason this causes crash when first strtok is called.!?!? Perhaps some bug? 1215 | EXPECT_EQ(true, cmd_parameter_timestamp(argc, argv, key, &value)); 1216 | EXPECT_EQ(809048709, value); 1217 | } 1218 | */ 1219 | TEST_F(mbedClientCli, cmd_parameter_timestamp_2) 1220 | { 1221 | int argc = 3; 1222 | char *argv[] = {"cmd", "-t", "00:00:00:12:34:56:78:90"}; 1223 | const char *key = "-t"; 1224 | int64_t value = 0; 1225 | EXPECT_EQ(true, cmd_parameter_timestamp(argc, argv, key, &value)); 1226 | EXPECT_EQ(78187493520, value); 1227 | } 1228 | TEST_F(mbedClientCli, cmd_parameter_timestamp_3) 1229 | { 1230 | int argc = 3; 1231 | char *argv[] = {"cmd", "-t", "12345"}; 1232 | const char *key = "-t"; 1233 | int64_t value = 0; 1234 | EXPECT_EQ(true, cmd_parameter_timestamp(argc, argv, key, &value)); 1235 | EXPECT_EQ(12345, value); 1236 | } 1237 | TEST_F(mbedClientCli, cmd_parameter_timestamp_4) 1238 | { 1239 | int argc = 3; 1240 | char *argv[] = {"cmd", "-t", ":"}; 1241 | const char *key = "-t"; 1242 | int64_t value = 0; 1243 | EXPECT_EQ(false, cmd_parameter_timestamp(argc, argv, key, &value)); 1244 | EXPECT_EQ(0, value); 1245 | } 1246 | TEST_F(mbedClientCli, cmd_parameter_timestamp_5) 1247 | { 1248 | int argc = 3; 1249 | char *argv[] = {"cmd", "-tt", "123"}; 1250 | const char *key = "-t"; 1251 | int64_t value = 0; 1252 | EXPECT_EQ(false, cmd_parameter_timestamp(argc, argv, key, &value)); 1253 | EXPECT_EQ(0, value); 1254 | } 1255 | TEST_F(mbedClientCli, cmd_free) 1256 | { 1257 | INIT_BUF(); 1258 | cmd_free(); 1259 | // these should not do anything since 1260 | // library is not initialized anymore 1261 | cmd_exe(""); 1262 | cmd_ready(0); 1263 | cmd_next(0); 1264 | EXPECT_STREQ("", buf); 1265 | } 1266 | -------------------------------------------------------------------------------- /test/run_unit_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | ################################################################################# 4 | ## Copyright (c) 2021 Pelion. All rights reserved. 5 | ## 6 | ## SPDX-License-Identifier: Apache-2.0 7 | ## 8 | ## Licensed under the Apache License, Version 2.0 (the "License"); 9 | ## you may not use this file except in compliance with the License. 10 | ## You may obtain a copy of the License at 11 | ## 12 | ## http://www.apache.org/licenses/LICENSE-2.0 13 | ## 14 | ## Unless required by applicable law or agreed to in writing, software 15 | ## distributed under the License is distributed on an "AS IS" BASIS, 16 | ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | ## See the License for the specific language governing permissions and 18 | ## limitations under the License. 19 | ################################################################################# 20 | # 21 | # 22 | # Run unit tests and produce 'xml' output to build/html folder. 23 | # Coverage report will be available in: ./build/html/coverage_index.html 24 | # 25 | 26 | TEST_DIR="build" 27 | 28 | mkdir -p ${TEST_DIR} 29 | cd ${TEST_DIR} 30 | cmake .. -DLINUXIFY=ON -Denable_coverage_data=ON -DCMAKE_BUILD_TYPE=Debug 31 | make 32 | ctest 33 | 34 | --------------------------------------------------------------------------------