Commit 082e3cfabd717fdeea583c891df86e63e4203ba1

Authored by Geoffrey Hunter
2 parents 5317bfff 36fbaaa1

Merge branch 'develop'

.gitignore 0 → 100644
  1 +build/
  2 +cmake-build-debug/
  3 +.idea/workspace.xml
0 \ No newline at end of file 4 \ No newline at end of file
.idea/CppLinuxSerial.iml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<module classpath="CMake" type="CPP_MODULE" version="4" />
0 \ No newline at end of file 3 \ No newline at end of file
.idea/misc.xml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project version="4">
  3 + <component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
  4 +</project>
0 \ No newline at end of file 5 \ No newline at end of file
.idea/modules.xml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project version="4">
  3 + <component name="ProjectModuleManager">
  4 + <modules>
  5 + <module fileurl="file://$PROJECT_DIR$/.idea/CppLinuxSerial.iml" filepath="$PROJECT_DIR$/.idea/CppLinuxSerial.iml" />
  6 + </modules>
  7 + </component>
  8 +</project>
0 \ No newline at end of file 9 \ No newline at end of file
.idea/vcs.xml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project version="4">
  3 + <component name="VcsDirectoryMappings">
  4 + <mapping directory="$PROJECT_DIR$" vcs="Git" />
  5 + </component>
  6 +</project>
0 \ No newline at end of file 7 \ No newline at end of file
.travis.yml 0 → 100644
  1 +language: cpp
  2 +compiler: g++
  3 +
  4 +addons:
  5 + apt:
  6 + sources:
  7 + - llvm-toolchain-precise
  8 + - ubuntu-toolchain-r-test
  9 + packages:
  10 + - clang-3.7
  11 + - g++-5
  12 + - gcc-5
  13 +
  14 +before_install:
  15 + - pip install --user cpp-coveralls
  16 +
  17 +install:
  18 + - if [ "$CXX" = "g++" ]; then export CXX="g++-5" CC="gcc-5"; fi
  19 + - if [ "$CXX" = "clang++" ]; then export CXX="clang++-3.7" CC="clang-3.7"; fi
  20 +
  21 +script:
  22 + - ./tools/build.sh -i
0 \ No newline at end of file 23 \ No newline at end of file
.vscode/c_cpp_properties.json 0 → 100644
  1 +{
  2 + "configurations": [
  3 + {
  4 + "name": "Mac",
  5 + "includePath": [
  6 + "/usr/include",
  7 + "/usr/local/include",
  8 + "${workspaceRoot}"
  9 + ],
  10 + "defines": [],
  11 + "intelliSenseMode": "clang-x64",
  12 + "browse": {
  13 + "path": [
  14 + "/usr/include",
  15 + "/usr/local/include",
  16 + "${workspaceRoot}"
  17 + ],
  18 + "limitSymbolsToIncludedHeaders": true,
  19 + "databaseFilename": ""
  20 + },
  21 + "macFrameworkPath": [
  22 + "/System/Library/Frameworks",
  23 + "/Library/Frameworks"
  24 + ]
  25 + },
  26 + {
  27 + "name": "Linux",
  28 + "includePath": [
  29 + "/usr/include/c++/6",
  30 + "/usr/include/x86_64-linux-gnu/c++/6",
  31 + "/usr/include/c++/6/backward",
  32 + "/usr/lib/gcc/x86_64-linux-gnu/6/include",
  33 + "/usr/local/include",
  34 + "/usr/lib/gcc/x86_64-linux-gnu/6/include-fixed",
  35 + "/usr/include/x86_64-linux-gnu",
  36 + "/usr/include",
  37 + "${workspaceRoot}",
  38 + "${workspaceRoot}/include"
  39 + ],
  40 + "defines": [],
  41 + "intelliSenseMode": "clang-x64",
  42 + "browse": {
  43 + "path": [
  44 + "/usr/include/c++/6",
  45 + "/usr/include/x86_64-linux-gnu/c++/6",
  46 + "/usr/include/c++/6/backward",
  47 + "/usr/lib/gcc/x86_64-linux-gnu/6/include",
  48 + "/usr/local/include",
  49 + "/usr/lib/gcc/x86_64-linux-gnu/6/include-fixed",
  50 + "/usr/include/x86_64-linux-gnu",
  51 + "/usr/include",
  52 + "${workspaceRoot}",
  53 + "${workspaceRoot}/include"
  54 + ],
  55 + "limitSymbolsToIncludedHeaders": true,
  56 + "databaseFilename": ""
  57 + }
  58 + },
  59 + {
  60 + "name": "Win32",
  61 + "includePath": [
  62 + "C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/include",
  63 + "${workspaceRoot}"
  64 + ],
  65 + "defines": [
  66 + "_DEBUG",
  67 + "UNICODE"
  68 + ],
  69 + "intelliSenseMode": "msvc-x64",
  70 + "browse": {
  71 + "path": [
  72 + "C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/include/*",
  73 + "${workspaceRoot}"
  74 + ],
  75 + "limitSymbolsToIncludedHeaders": true,
  76 + "databaseFilename": ""
  77 + }
  78 + }
  79 + ],
  80 + "version": 3
  81 +}
0 \ No newline at end of file 82 \ No newline at end of file
CHANGELOG.md 0 → 100644
  1 +# Changelog
  2 +All notable changes to this project will be documented in this file.
  3 +
  4 +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
  5 +and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
  6 +
  7 +## [Unreleased]
  8 +
  9 +## [v2.0.0]
  10 +
  11 +### Added
  12 +- Added CMake build support.
  13 +- Added basic, config and read/write unit tests using gtest.
  14 +- Improved read() performance due to removal of buffer creation on every call.
  15 +- TravisCI configuration file.
  16 +- Build script under `tools/`.
  17 +
  18 +### Changed
  19 +- Updated serial port to use C++14.
  20 +- Changed library name from serial-port to CppLinuxSerial.
  21 +- Updated Doxygen comments.
  22 +
  23 +## [v1.0.1] - 2014-05-21
  24 +
  25 +### Changed
  26 +- Added ability to enable/disable echo with 'SerialPort::EnableEcho()'.
  27 +
  28 +## [v1.0.0] - 2014-05-15
  29 +
  30 +### Added
  31 +- Initial commit. serial-port-cpp library has basic functions up and running.
  32 +
  33 +[Unreleased]: https://github.com/mbedded-ninja/CppLinuxSerial/compare/v2.0.0...HEAD
  34 +[v2.0.0]: https://github.com/mbedded-ninja/CppLinuxSerial/compare/v2.0.0...v1.0.1
  35 +[v1.0.1]: https://github.com/mbedded-ninja/CppLinuxSerial/compare/v1.0.1...v1.0.0
0 \ No newline at end of file 36 \ No newline at end of file
CMakeLists.txt 0 → 100644
  1 +cmake_minimum_required(VERSION 3.1.0)
  2 +project(CppLinuxSerial)
  3 +
  4 +add_definitions(-std=c++14)
  5 +
  6 +option(BUILD_TESTS "If set to true, unit tests will be build as part of make all." TRUE)
  7 +if (BUILD_TESTS)
  8 + message("BUILD_TESTS=TRUE, unit tests will be built.")
  9 +else ()
  10 + message("BUILD_TESTS=FALSE, unit tests will NOT be built.")
  11 +endif ()
  12 +
  13 +#=================================================================================================#
  14 +#========================================= gtest INSTALL =========================================#
  15 +#=================================================================================================#
  16 +
  17 +# Download and unpack googletest at configure time
  18 +configure_file(CMakeLists.txt.in googletest-download/CMakeLists.txt)
  19 +execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" .
  20 + RESULT_VARIABLE result
  21 + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/googletest-download )
  22 +if(result)
  23 + message(FATAL_ERROR "CMake step for googletest failed: ${result}")
  24 +endif()
  25 +execute_process(COMMAND ${CMAKE_COMMAND} --build .
  26 + RESULT_VARIABLE result
  27 + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/googletest-download )
  28 +if(result)
  29 + message(FATAL_ERROR "Build step for googletest failed: ${result}")
  30 +endif()
  31 +
  32 +# Prevent overriding the parent project's compiler/linker
  33 +# settings on Windows
  34 +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
  35 +
  36 +# Add googletest directly to our build. This defines
  37 +# the gtest and gtest_main targets.
  38 +add_subdirectory(${CMAKE_BINARY_DIR}/googletest-src
  39 + ${CMAKE_BINARY_DIR}/googletest-build
  40 + EXCLUDE_FROM_ALL)
  41 +
  42 +# The gtest/gtest_main targets carry header search path
  43 +# dependencies automatically when using CMake 2.8.11 or
  44 +# later. Otherwise we have to add them here ourselves.
  45 +if (CMAKE_VERSION VERSION_LESS 2.8.11)
  46 + include_directories("${gtest_SOURCE_DIR}/include")
  47 +endif()
  48 +
  49 +#=================================================================================================#
  50 +#========================================= This Project ==========================================#
  51 +#=================================================================================================#
  52 +
  53 +# Now simply link your own targets against gtest, gmock,
  54 +# etc. as appropriate
  55 +include_directories(include)
  56 +
  57 +add_subdirectory(src)
  58 +if(BUILD_TESTS)
  59 + add_subdirectory(test/unit)
  60 +endif()
CMakeLists.txt.in 0 → 100644
  1 +cmake_minimum_required(VERSION 2.8.2)
  2 +
  3 +project(googletest-download NONE)
  4 +
  5 +include(ExternalProject)
  6 +ExternalProject_Add(googletest
  7 + GIT_REPOSITORY https://github.com/google/googletest.git
  8 + GIT_TAG master
  9 + SOURCE_DIR "${CMAKE_BINARY_DIR}/googletest-src"
  10 + BINARY_DIR "${CMAKE_BINARY_DIR}/googletest-build"
  11 + CONFIGURE_COMMAND ""
  12 + BUILD_COMMAND ""
  13 + INSTALL_COMMAND ""
  14 + TEST_COMMAND ""
  15 +)
0 \ No newline at end of file 16 \ No newline at end of file
README.rst
1 -==============================================================  
2 -serial-port-cpp  
3 -============================================================== 1 +==============
  2 +CppLinuxSerial
  3 +==============
4 4
5 ---------------------------------- 5 ----------------------------------
6 Serial port library written in C++ 6 Serial port library written in C++
7 ---------------------------------- 7 ----------------------------------
8 8
9 -.. image:: https://api.travis-ci.org/gbmhunter/serial-port-cpp.png?branch=master  
10 - :target: https://travis-ci.org/gbmhunter/serial-port-cpp  
11 -  
12 -- Author: gbmhunter <gbmhunter@gmail.com> (http://www.cladlab.com)  
13 -- Created: 2014/01/07  
14 -- Last Modified: 2014/05/21  
15 -- Version: v1.0.1.0  
16 -- Company: CladLabs  
17 -- Project: Free Code Libraries  
18 -- Language: C++  
19 -- Compiler: GCC  
20 -- uC Model: n/a  
21 -- Computer Architecture: n/a  
22 -- Operating System: n/a  
23 -- Documentation Format: Doxygen  
24 -- License: GPLv3 9 +.. image:: https://api.travis-ci.org/gbmhunter/CppLinuxSerial.png?branch=master
  10 + :target: https://travis-ci.org/gbmhunter/CppLinuxSerial
25 11
26 .. role:: bash(code) 12 .. role:: bash(code)
27 :language: bash 13 :language: bash
@@ -48,11 +34,13 @@ Dependencies @@ -48,11 +34,13 @@ Dependencies
48 34
49 The following table lists all of the libraries dependencies. 35 The following table lists all of the libraries dependencies.
50 36
51 -====================== ==================== ======================================================================  
52 -Dependency Delivery Usage  
53 -====================== ==================== ======================================================================  
54 -<stdio.h> Standard C library snprintf()  
55 -====================== ==================== ====================================================================== 37 +====================== ======================================================================
  38 +Dependency Comments
  39 +====================== ======================================================================
  40 +C++14 C++14 used for strongly typed enums, std::chrono and literals.
  41 +<stdio.h> snprintf()
  42 +stty Used in unit tests to verify the serial port is configured correctly.
  43 +====================== ======================================================================
56 44
57 Issues 45 Issues
58 ====== 46 ======
@@ -62,20 +50,7 @@ See GitHub Issues. @@ -62,20 +50,7 @@ See GitHub Issues.
62 Usage 50 Usage
63 ===== 51 =====
64 52
65 -In main.c add...  
66 -  
67 -::  
68 -  
69 -  
70 -  
71 -  
72 - int main()  
73 - {  
74 -  
75 -  
76 - }  
77 -  
78 - 53 +Nothing here yet...
79 54
80 FAQ 55 FAQ
81 === 56 ===
@@ -86,9 +61,4 @@ FAQ @@ -86,9 +61,4 @@ FAQ
86 Changelog 61 Changelog
87 ========= 62 =========
88 63
89 -========= ========== ===================================================================================================  
90 -Version Date Comment  
91 -========= ========== ===================================================================================================  
92 -v1.0.1.0 2014/05/21 Added ability to enable/disable echo with 'SerialPort::EnableEcho()'.  
93 -v1.0.0.0 2014/05/15 Initial commit. serial-port-cpp library has basic functions up and running.  
94 -========= ========== ===================================================================================================  
95 \ No newline at end of file 64 \ No newline at end of file
  65 +See CHANGELOG.md.
96 \ No newline at end of file 66 \ No newline at end of file
api/SerialPortApi.hpp deleted
1 -//!  
2 -//! @file SerialPortApi.hpp  
3 -//! @author Geoffrey Hunter <gbmhunter@gmail.com> (www.cladlab.com)  
4 -//! @created 2014/05/15  
5 -//! @last-modified 2014/05/15  
6 -//! @brief File which contains all the API definitions needed to use the serial-port-cpp library.  
7 -//! @details  
8 -//! See README.rst in repo root dir for more info.  
9 -  
10 -// Header guard  
11 -#ifndef SERIAL_PORT_SERIAL_PORT_API_H  
12 -#define SERIAL_PORT_SERIAL_PORT_API_H  
13 -  
14 -// User headers  
15 -#include "../include/SerialPort.hpp"  
16 -  
17 -#endif // #ifndef SERIAL_PORT_SERIAL_PORT_API_H  
include/Config.hpp deleted
1 -//!  
2 -//! @file Config.hpp  
3 -//! @author Geoffrey Hunter <gbmhunter@gmail.com> ()  
4 -//! @created 2014/01/07  
5 -//! @last-modified 2014/01/07  
6 -//! @brief Config file for the ComPort library.  
7 -//! @details  
8 -//! See README.rst in repo root dir for more info.  
9 -  
10 -  
11 -namespace SerialPort  
12 -{  
13 -  
14 -  
15 -}  
include/CppLinuxSerial/Exception.hpp 0 → 100644
  1 +///
  2 +/// \file Exception.hpp
  3 +/// \author Geoffrey Hunter (www.mbedded.ninja) <gbmhunter@gmail.com>
  4 +/// \edited n/a
  5 +/// \created 2017-11-09
  6 +/// \last-modified 2017-11-27
  7 +/// \brief Contains the Exception class. File originally from https://github.com/mbedded-ninja/CppUtils.
  8 +/// \details
  9 +/// See README.md in root dir for more info.
  10 +
  11 +#ifndef MN_CPP_LINUX_SERIAL_EXCEPTION_H_
  12 +#define MN_CPP_LINUX_SERIAL_EXCEPTION_H_
  13 +
  14 +// System includes
  15 +#include <iostream>
  16 +#include <sstream>
  17 +#include <stdexcept>
  18 +#include <string>
  19 +
  20 +namespace mn {
  21 + namespace CppLinuxSerial {
  22 +
  23 + class Exception : public std::runtime_error {
  24 +
  25 + public:
  26 + Exception(const char *file, int line, const std::string &arg) :
  27 + std::runtime_error(arg) {
  28 + msg_ = std::string(file) + ":" + std::to_string(line) + ": " + arg;
  29 + }
  30 +
  31 + ~Exception() throw() {}
  32 +
  33 + const char *what() const throw() override {
  34 + return msg_.c_str();
  35 + }
  36 +
  37 + private:
  38 + std::string msg_;
  39 + };
  40 +
  41 + } // namespace CppLinuxSerial
  42 +} // namespace mn
  43 +
  44 +#define THROW_EXCEPT(arg) throw Exception(__FILE__, __LINE__, arg);
  45 +
  46 +
  47 +#endif // MN_CPP_LINUX_SERIAL_EXCEPTION_H_
0 \ No newline at end of file 48 \ No newline at end of file
include/CppLinuxSerial/SerialPort.hpp 0 → 100644
  1 +///
  2 +/// \file SerialPort.hpp
  3 +/// \author Geoffrey Hunter <gbmhunter@gmail.com> ()
  4 +/// \created 2014-01-07
  5 +/// \last-modified 2017-11-23
  6 +/// \brief The main serial port class.
  7 +/// \details
  8 +/// See README.rst in repo root dir for more info.
  9 +
  10 +// Header guard
  11 +#ifndef SERIAL_PORT_SERIAL_PORT_H
  12 +#define SERIAL_PORT_SERIAL_PORT_H
  13 +
  14 +// System headers
  15 +#include <string>
  16 +#include <fstream> // For file I/O (reading/writing to COM port)
  17 +#include <sstream>
  18 +#include <termios.h> // POSIX terminal control definitions (struct termios)
  19 +#include <vector>
  20 +
  21 +// User headers
  22 +
  23 +namespace mn {
  24 + namespace CppLinuxSerial {
  25 +
  26 + /// \brief Strongly-typed enumeration of baud rates for use with the SerialPort class
  27 + enum class BaudRate {
  28 + B_9600,
  29 + B_38400,
  30 + B_57600,
  31 + B_115200,
  32 + CUSTOM
  33 + };
  34 +
  35 + enum class State {
  36 + CLOSED,
  37 + OPEN
  38 + };
  39 +
  40 +/// \brief SerialPort object is used to perform rx/tx serial communication.
  41 + class SerialPort {
  42 +
  43 + public:
  44 + /// \brief Default constructor. You must specify at least the device before calling Open().
  45 + SerialPort();
  46 +
  47 + /// \brief Constructor that sets up serial port with the basic (required) parameters.
  48 + SerialPort(const std::string &device, BaudRate baudRate);
  49 +
  50 + //! @brief Destructor. Closes serial port if still open.
  51 + virtual ~SerialPort();
  52 +
  53 + /// \brief Sets the device to use for serial port communications.
  54 + /// \details Method can be called when serial port is in any state.
  55 + void SetDevice(const std::string &device);
  56 +
  57 + void SetBaudRate(BaudRate baudRate);
  58 +
  59 + /// \brief Sets the read timeout (in milliseconds)/blocking mode.
  60 + /// \details Only call when state != OPEN. This method manupulates VMIN and VTIME.
  61 + /// \param timeout_ms Set to -1 to infinite timeout, 0 to return immediately with any data (non
  62 + /// blocking, or >0 to wait for data for a specified number of milliseconds). Timeout will
  63 + /// be rounded to the nearest 100ms (a Linux API restriction). Maximum value limited to
  64 + /// 25500ms (another Linux API restriction).
  65 + void SetTimeout(int32_t timeout_ms);
  66 +
  67 + /// \brief Enables/disables echo.
  68 + /// \param value Pass in true to enable echo, false to disable echo.
  69 + void SetEcho(bool value);
  70 +
  71 + /// \brief Opens the COM port for use.
  72 + /// \throws CppLinuxSerial::Exception if device cannot be opened.
  73 + /// \note Must call this before you can configure the COM port.
  74 + void Open();
  75 +
  76 + /// \brief Closes the COM port.
  77 + void Close();
  78 +
  79 + /// \brief Sends a message over the com port.
  80 + /// \param data The data that will be written to the COM port.
  81 + /// \throws CppLinuxSerial::Exception if state != OPEN.
  82 + void Write(const std::string& data);
  83 +
  84 + /// \brief Use to read from the COM port.
  85 + /// \param data The object the read characters from the COM port will be saved to.
  86 + /// \param wait_ms The amount of time to wait for data. Set to 0 for non-blocking mode. Set to -1
  87 + /// to wait indefinitely for new data.
  88 + /// \throws CppLinuxSerial::Exception if state != OPEN.
  89 + void Read(std::string& data);
  90 +
  91 + private:
  92 +
  93 + /// \brief Returns a populated termios structure for the passed in file descriptor.
  94 + termios GetTermios();
  95 +
  96 + /// \brief Configures the tty device as a serial port.
  97 + /// \warning Device must be open (valid file descriptor) when this is called.
  98 + void ConfigureTermios();
  99 +
  100 + void SetTermios(termios myTermios);
  101 +
  102 + /// \brief Keeps track of the serial port's state.
  103 + State state_;
  104 +
  105 + /// \brief The file path to the serial port device (e.g. "/dev/ttyUSB0").
  106 + std::string device_;
  107 +
  108 + /// \brief The current baud rate.
  109 + BaudRate baudRate_;
  110 +
  111 + /// \brief The file descriptor for the open file. This gets written to when Open() is called.
  112 + int fileDesc_;
  113 +
  114 + bool echo_;
  115 +
  116 + int32_t timeout_ms_;
  117 +
  118 + std::vector<char> readBuffer_;
  119 + unsigned char readBufferSize_B_;
  120 +
  121 + static constexpr BaudRate defaultBaudRate_ = BaudRate::B_57600;
  122 + static constexpr int32_t defaultTimeout_ms_ = -1;
  123 + static constexpr unsigned char defaultReadBufferSize_B_ = 255;
  124 +
  125 +
  126 + };
  127 +
  128 + } // namespace CppLinuxSerial
  129 +} // namespace mn
  130 +
  131 +#endif // #ifndef SERIAL_PORT_SERIAL_PORT_H
include/SerialPort.hpp deleted
1 -//!  
2 -//! @file SerialPort.hpp  
3 -//! @author Geoffrey Hunter <gbmhunter@gmail.com> ()  
4 -//! @created 2014/01/07  
5 -//! @last-modified 2014/05/21  
6 -//! @brief The main serial port class.  
7 -//! @details  
8 -//! See README.rst in repo root dir for more info.  
9 -  
10 -// Header guard  
11 -#ifndef SERIAL_PORT_SERIAL_PORT_H  
12 -#define SERIAL_PORT_SERIAL_PORT_H  
13 -  
14 -// System headers  
15 -#include <fstream> // For file I/O (reading/writing to COM port)  
16 -#include <sstream>  
17 -#include <termios.h> // POSIX terminal control definitions (struct termios)  
18 -  
19 -// User headers  
20 -#include "lib/SmartPrint/include/Sp.hpp"  
21 -  
22 -namespace SerialPort  
23 -{  
24 -  
25 - //! @brief Strongly-typed enumeration of baud rates for use with the SerialPort class  
26 - enum class BaudRates  
27 - {  
28 - none,  
29 - b9600,  
30 - b57600  
31 - };  
32 -  
33 - //! @brief SerialPort object is used to perform rx/tx serial communication.  
34 - class SerialPort  
35 - {  
36 -  
37 - public:  
38 -  
39 - //! @brief Constructor  
40 - SerialPort();  
41 -  
42 - //! @brief Destructor  
43 - virtual ~SerialPort();  
44 -  
45 - //! @brief Sets the file path to use for communications. The file path must be set before Open() is called, otherwise Open() will return an error.  
46 - void SetFilePath(std::string filePath);  
47 -  
48 - void SetBaudRate(BaudRates baudRate);  
49 -  
50 - //! @brief Controls what happens when Read() is called.  
51 - //! @param numOfCharToWait Minimum number of characters to wait for before returning. Set to 0 for non-blocking mode.  
52 - void SetNumCharsToWait(uint32_t numCharsToWait);  
53 -  
54 - //! @brief Enables/disables echo.  
55 - //! param echoOn Pass in true to enable echo, false to disable echo.  
56 - void EnableEcho(bool echoOn);  
57 -  
58 - //! @brief Opens the COM port for use.  
59 - //! @throws {std::runtime_error} if filename has not been set.  
60 - //! {std::system_error} if system open() operation fails.  
61 - //! @note Must call this before you can configure the COM port.  
62 - void Open();  
63 -  
64 - //! @brief Sets all settings for the com port to common defaults.  
65 - void SetEverythingToCommonDefaults();  
66 -  
67 - //! @brief Closes the COM port.  
68 - void Close();  
69 -  
70 - //! @brief Sends a message over the com port.  
71 - //! @param str Reference to an string containing the characters to write to the COM port.  
72 - //! @throws {std::runtime_error} if filename has not been set.  
73 - //! {std::system_error} if system write() operation fails.  
74 - void Write(std::string* str);  
75 -  
76 - //! @brief Use to read from the COM port.  
77 - //! @param str Reference to a string that the read characters from the COM port will be saved to.  
78 - //! @throws {std::runtime_error} if filename has not been set.  
79 - //! {std::system_error} if system read() operation fails.  
80 - void Read(std::string* str);  
81 -  
82 - private:  
83 -  
84 - std::string filePath;  
85 -  
86 - BaudRates baudRate;  
87 -  
88 - //! @brief The file descriptor for the open file. This gets written to when Open() is called.  
89 - int fileDesc;  
90 -  
91 - //! @brief Object for printing debug and error messages with  
92 - SmartPrint::Sp* sp;  
93 -  
94 - //! @brief Returns a populated termios structure for the passed in file descriptor.  
95 - termios GetTermios();  
96 -  
97 - void SetTermios(termios myTermios);  
98 -  
99 - };  
100 -  
101 -} // namespace SerialPort  
102 -  
103 -#endif // #ifndef SERIAL_PORT_SERIAL_PORT_H  
src/CMakeLists.txt 0 → 100644
  1 +
  2 +
  3 +file(GLOB_RECURSE CppLinuxSerial_SRC
  4 + "*.cpp")
  5 +
  6 +file(GLOB_RECURSE CppLinuxSerial_HEADERS
  7 + "${CMAKE_SOURCE_DIR}/include/*.hpp")
  8 +
  9 +add_library(CppLinuxSerial ${CppLinuxSerial_SRC} ${CppLinuxSerial_HEADERS})
  10 +
  11 +target_include_directories(CppLinuxSerial PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
  12 +
  13 +# On Linux, "sudo make install" will typically copy the library
  14 +# into the folder /usr/local/bin
  15 +install(TARGETS CppLinuxSerial DESTINATION lib)
  16 +
  17 +# On Linux, "sudo make install" will typically copy the
  18 +# folder into /usr/local/include
  19 +install(DIRECTORY ${CMAKE_SOURCE_DIR}/include/CppLinuxSerial DESTINATION include)
0 \ No newline at end of file 20 \ No newline at end of file
src/SerialPort.cpp
1 //! 1 //!
2 //! @file SerialPort.cpp 2 //! @file SerialPort.cpp
3 -//! @author Geoffrey Hunter <gbmhunter@gmail.com> ()  
4 -//! @created 2014/01/07  
5 -//! @last-modified 2014/05/21 3 +//! @author Geoffrey Hunter <gbmhunter@gmail.com> (www.mbedded.ninja)
  4 +//! @created 2014-01-07
  5 +//! @last-modified 2017-11-27
6 //! @brief The main serial port class. 6 //! @brief The main serial port class.
7 //! @details 7 //! @details
8 //! See README.rst in repo root dir for more info. 8 //! See README.rst in repo root dir for more info.
9 9
  10 +// System includes
10 #include <iostream> 11 #include <iostream>
11 #include <sstream> 12 #include <sstream>
12 #include <stdio.h> // Standard input/output definitions 13 #include <stdio.h> // Standard input/output definitions
@@ -17,82 +18,57 @@ @@ -17,82 +18,57 @@
17 #include <termios.h> // POSIX terminal control definitions (struct termios) 18 #include <termios.h> // POSIX terminal control definitions (struct termios)
18 #include <system_error> // For throwing std::system_error 19 #include <system_error> // For throwing std::system_error
19 20
20 -#include "../include/Config.hpp"  
21 -#include "../include/SerialPort.hpp"  
22 -#include "lib/SmartPrint/api/SmartPrint.hpp"  
23 -  
24 -namespace SerialPort  
25 -{  
26 -  
27 - SerialPort::SerialPort() :  
28 - filePath(std::string()),  
29 - baudRate(BaudRates::none),  
30 - fileDesc(0),  
31 - sp(  
32 - new SmartPrint::Sp(  
33 - "Port",  
34 - &std::cout,  
35 - &SmartPrint::Colours::yellow,  
36 - &std::cout,  
37 - &SmartPrint::Colours::yellow,  
38 - &std::cerr,  
39 - &SmartPrint::Colours::red))  
40 - {  
41 - // Everything setup in initialiser list  
42 - } 21 +// User includes
  22 +#include "CppLinuxSerial/Exception.hpp"
  23 +#include "CppLinuxSerial/SerialPort.hpp"
43 24
44 - SerialPort::~SerialPort()  
45 - { 25 +namespace mn {
  26 +namespace CppLinuxSerial {
46 27
  28 + SerialPort::SerialPort() {
  29 + echo_ = false;
  30 + timeout_ms_ = defaultTimeout_ms_;
  31 + baudRate_ = defaultBaudRate_;
  32 + readBufferSize_B_ = defaultReadBufferSize_B_;
  33 + readBuffer_.reserve(readBufferSize_B_);
47 } 34 }
48 35
49 - void SerialPort::SetFilePath(std::string filePath)  
50 - {  
51 - // Save a pointer to the file path  
52 - this->filePath = filePath; 36 + SerialPort::SerialPort(const std::string& device, BaudRate baudRate) :
  37 + SerialPort() {
  38 + device_ = device;
  39 + baudRate_ = baudRate;
53 } 40 }
54 41
55 - void SerialPort::SetBaudRate(BaudRates baudRate)  
56 - { 42 + SerialPort::~SerialPort() {
  43 + try {
  44 + Close();
  45 + } catch(...) {
  46 + // We can't do anything about this!
  47 + // But we don't want to throw within destructor, so swallow
  48 + }
  49 + }
57 50
58 - // Get current termios struct  
59 - termios myTermios = this->GetTermios(); 51 + void SerialPort::SetDevice(const std::string& device) {
  52 + device_ = device;
  53 + if(state_ == State::OPEN)
60 54
61 - switch(baudRate)  
62 - {  
63 - case BaudRates::none:  
64 - // Error, baud rate has not been set yet  
65 - throw std::runtime_error("Baud rate for '" + this->filePath + "' cannot be set to none.");  
66 - break;  
67 - case BaudRates::b9600:  
68 - cfsetispeed(&myTermios, B9600);  
69 - cfsetospeed(&myTermios, B9600);  
70 - break;  
71 - case BaudRates::b57600:  
72 - cfsetispeed(&myTermios, B57600);  
73 - cfsetospeed(&myTermios, B57600);  
74 - break;  
75 - }  
76 55
77 - // Save back to file  
78 - this->SetTermios(myTermios); 56 + ConfigureTermios();
  57 + }
79 58
80 - // Setting the baudrate must of been successful, so we can now store this  
81 - // new value internally. This must be done last!  
82 - this->baudRate = baudRate; 59 + void SerialPort::SetBaudRate(BaudRate baudRate) {
  60 + baudRate_ = baudRate;
  61 + if(state_ == State::OPEN)
  62 + ConfigureTermios();
83 } 63 }
84 64
85 void SerialPort::Open() 65 void SerialPort::Open()
86 { 66 {
87 67
88 - this->sp->PrintDebug(SmartPrint::Ss() << "Attempting to open COM port \"" << this->filePath << "\".");  
89 -  
90 - if(this->filePath.size() == 0)  
91 - {  
92 - //this->sp->PrintError(SmartPrint::Ss() << "Attempted to open file when file path has not been assigned to.");  
93 - //return false; 68 + std::cout << "Attempting to open COM port \"" << device_ << "\"." << std::endl;
94 69
95 - throw std::runtime_error("Attempted to open file when file path has not been assigned to."); 70 + if(device_.empty()) {
  71 + THROW_EXCEPT("Attempted to open file when file path has not been assigned to.");
96 } 72 }
97 73
98 // Attempt to open file 74 // Attempt to open file
@@ -100,73 +76,31 @@ namespace SerialPort @@ -100,73 +76,31 @@ namespace SerialPort
100 76
101 // O_RDONLY for read-only, O_WRONLY for write only, O_RDWR for both read/write access 77 // O_RDONLY for read-only, O_WRONLY for write only, O_RDWR for both read/write access
102 // 3rd, optional parameter is mode_t mode 78 // 3rd, optional parameter is mode_t mode
103 - this->fileDesc = open(this->filePath.c_str(), O_RDWR); 79 + fileDesc_ = open(device_.c_str(), O_RDWR);
104 80
105 // Check status 81 // Check status
106 - if (this->fileDesc == -1)  
107 - {  
108 - // Could not open COM port  
109 - //this->sp->PrintError(SmartPrint::Ss() << "Unable to open " << this->filePath << " - " << strerror(errno));  
110 - //return false;  
111 -  
112 - throw std::system_error(EFAULT, std::system_category()); 82 + if(fileDesc_ == -1) {
  83 + THROW_EXCEPT("Could not open device " + device_ + ". Is the device name correct and do you have read/write permission?");
113 } 84 }
114 85
115 - this->sp->PrintDebug(SmartPrint::Ss() << "COM port opened successfully.");  
116 -  
117 - // If code reaches here, open and config must of been successful 86 + ConfigureTermios();
118 87
  88 + std::cout << "COM port opened successfully." << std::endl;
  89 + state_ = State::OPEN;
119 } 90 }
120 91
121 - void SerialPort::EnableEcho(bool echoOn)  
122 - {  
123 - termios settings = this->GetTermios();  
124 - settings.c_lflag = echoOn  
125 - ? (settings.c_lflag | ECHO )  
126 - : (settings.c_lflag & ~(ECHO));  
127 - //tcsetattr( STDIN_FILENO, TCSANOW, &settings );  
128 - this->SetTermios(settings); 92 + void SerialPort::SetEcho(bool value) {
  93 + echo_ = value;
  94 + ConfigureTermios();
129 } 95 }
130 96
131 - void SerialPort::SetEverythingToCommonDefaults() 97 + void SerialPort::ConfigureTermios()
132 { 98 {
133 - this->sp->PrintDebug(SmartPrint::Ss() << "Configuring COM port \"" << this->filePath << "\"."); 99 + std::cout << "Configuring COM port \"" << device_ << "\"." << std::endl;
134 100
135 //================== CONFIGURE ==================// 101 //================== CONFIGURE ==================//
136 102
137 - termios tty = this->GetTermios();  
138 - /*struct termios tty;  
139 - memset(&tty, 0, sizeof(tty));  
140 -  
141 - // Get current settings (will be stored in termios structure)  
142 - if(tcgetattr(this->fileDesc, &tty) != 0)  
143 - {  
144 - // Error occurred  
145 - this->sp->PrintError(SmartPrint::Ss() << "Could not get terminal attributes for \"" << this->filePath << "\" - " << strerror(errno));  
146 - //return false;  
147 - return;  
148 - }*/  
149 -  
150 - //========================= SET UP BAUD RATES =========================//  
151 -  
152 - this->SetBaudRate(BaudRates::b57600);  
153 -  
154 - /*  
155 - switch(this->baudRate)  
156 - {  
157 - case BaudRates::none:  
158 - // Error, baud rate has not been set yet  
159 - this->sp->PrintError(SmartPrint::Ss() << "Baud rate for \"" << this->filePath << "\" has not been set.");  
160 - return;  
161 - case BaudRates::b9600:  
162 - cfsetispeed(&tty, B9600);  
163 - cfsetospeed(&tty, B9600);  
164 - break;  
165 - case BaudRates::b57600:  
166 - cfsetispeed(&tty, B57600);  
167 - cfsetospeed(&tty, B57600);  
168 - break;  
169 - }*/ 103 + termios tty = GetTermios();
170 104
171 //================= (.c_cflag) ===============// 105 //================= (.c_cflag) ===============//
172 106
@@ -178,6 +112,32 @@ namespace SerialPort @@ -178,6 +112,32 @@ namespace SerialPort
178 tty.c_cflag |= CREAD | CLOCAL; // Turn on READ & ignore ctrl lines (CLOCAL = 1) 112 tty.c_cflag |= CREAD | CLOCAL; // Turn on READ & ignore ctrl lines (CLOCAL = 1)
179 113
180 114
  115 + //===================== BAUD RATE =================//
  116 +
  117 + switch(baudRate_) {
  118 + case BaudRate::B_9600:
  119 + cfsetispeed(&tty, B9600);
  120 + cfsetospeed(&tty, B9600);
  121 + break;
  122 + case BaudRate::B_38400:
  123 + cfsetispeed(&tty, B38400);
  124 + cfsetospeed(&tty, B38400);
  125 + break;
  126 + case BaudRate::B_57600:
  127 + cfsetispeed(&tty, B57600);
  128 + cfsetospeed(&tty, B57600);
  129 + break;
  130 + case BaudRate::B_115200:
  131 + cfsetispeed(&tty, B115200);
  132 + cfsetospeed(&tty, B115200);
  133 + break;
  134 + case BaudRate::CUSTOM:
  135 + // See https://gist.github.com/kennethryerson/f7d1abcf2633b7c03cf0
  136 + throw std::runtime_error("Custom baud rate not yet supported.");
  137 + default:
  138 + throw std::runtime_error(std::string() + "baudRate passed to " + __PRETTY_FUNCTION__ + " unrecognized.");
  139 + }
  140 +
181 //===================== (.c_oflag) =================// 141 //===================== (.c_oflag) =================//
182 142
183 tty.c_oflag = 0; // No remapping, no delays 143 tty.c_oflag = 0; // No remapping, no delays
@@ -185,14 +145,29 @@ namespace SerialPort @@ -185,14 +145,29 @@ namespace SerialPort
185 145
186 //================= CONTROL CHARACTERS (.c_cc[]) ==================// 146 //================= CONTROL CHARACTERS (.c_cc[]) ==================//
187 147
188 - // c_cc[WMIN] sets the number of characters to block (wait) for when read() is called.  
189 - // Set to 0 if you don't want read to block. Only meaningful when port set to non-canonical mode  
190 - //tty.c_cc[VMIN] = 1;  
191 - this->SetNumCharsToWait(1);  
192 -  
193 // c_cc[VTIME] sets the inter-character timer, in units of 0.1s. 148 // c_cc[VTIME] sets the inter-character timer, in units of 0.1s.
194 // Only meaningful when port is set to non-canonical mode 149 // Only meaningful when port is set to non-canonical mode
195 - tty.c_cc[VTIME] = 5; // 0.5 seconds read timeout 150 + // VMIN = 0, VTIME = 0: No blocking, return immediately with what is available
  151 + // VMIN > 0, VTIME = 0: read() waits for VMIN bytes, could block indefinitely
  152 + // VMIN = 0, VTIME > 0: Block until any amount of data is available, OR timeout occurs
  153 + // VMIN > 0, VTIME > 0: Block until either VMIN characters have been received, or VTIME
  154 + // after first character has elapsed
  155 + // c_cc[WMIN] sets the number of characters to block (wait) for when read() is called.
  156 + // Set to 0 if you don't want read to block. Only meaningful when port set to non-canonical mode
  157 +
  158 + if(timeout_ms_ == -1) {
  159 + // Always wait for at least one byte, this could
  160 + // block indefinitely
  161 + tty.c_cc[VTIME] = 0;
  162 + tty.c_cc[VMIN] = 1;
  163 + } else if(timeout_ms_ == 0) {
  164 + // Setting both to 0 will give a non-blocking read
  165 + tty.c_cc[VTIME] = 0;
  166 + tty.c_cc[VMIN] = 0;
  167 + } else if(timeout_ms_ > 0) {
  168 + tty.c_cc[VTIME] = (cc_t)(timeout_ms_/100); // 0.5 seconds read timeout
  169 + tty.c_cc[VMIN] = 0;
  170 + }
196 171
197 172
198 //======================== (.c_iflag) ====================// 173 //======================== (.c_iflag) ====================//
@@ -200,16 +175,20 @@ namespace SerialPort @@ -200,16 +175,20 @@ namespace SerialPort
200 tty.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off s/w flow ctrl 175 tty.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off s/w flow ctrl
201 tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL); 176 tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL);
202 177
  178 +
  179 +
203 //=========================== LOCAL MODES (c_lflag) =======================// 180 //=========================== LOCAL MODES (c_lflag) =======================//
204 181
205 // Canonical input is when read waits for EOL or EOF characters before returning. In non-canonical mode, the rate at which 182 // Canonical input is when read waits for EOL or EOF characters before returning. In non-canonical mode, the rate at which
206 // read() returns is instead controlled by c_cc[VMIN] and c_cc[VTIME] 183 // read() returns is instead controlled by c_cc[VMIN] and c_cc[VTIME]
207 tty.c_lflag &= ~ICANON; // Turn off canonical input, which is suitable for pass-through 184 tty.c_lflag &= ~ICANON; // Turn off canonical input, which is suitable for pass-through
208 - tty.c_lflag &= ~ECHO; // Turn off echo 185 + echo_ ? (tty.c_lflag | ECHO ) : (tty.c_lflag & ~(ECHO)); // Configure echo depending on echo_ boolean
209 tty.c_lflag &= ~ECHOE; // Turn off echo erase (echo erase only relevant if canonical input is active) 186 tty.c_lflag &= ~ECHOE; // Turn off echo erase (echo erase only relevant if canonical input is active)
210 tty.c_lflag &= ~ECHONL; // 187 tty.c_lflag &= ~ECHONL; //
211 tty.c_lflag &= ~ISIG; // Disables recognition of INTR (interrupt), QUIT and SUSP (suspend) characters 188 tty.c_lflag &= ~ISIG; // Disables recognition of INTR (interrupt), QUIT and SUSP (suspend) characters
212 189
  190 +
  191 +
213 // Try and use raw function call 192 // Try and use raw function call
214 //cfmakeraw(&tty); 193 //cfmakeraw(&tty);
215 194
@@ -227,93 +206,73 @@ namespace SerialPort @@ -227,93 +206,73 @@ namespace SerialPort
227 }*/ 206 }*/
228 } 207 }
229 208
230 - void SerialPort::SetNumCharsToWait(uint32_t numCharsToWait)  
231 - {  
232 - // Get current termios struct  
233 - termios myTermios = this->GetTermios(); 209 + void SerialPort::Write(const std::string& data) {
234 210
235 - // Save the number of characters to wait for  
236 - // to the control register  
237 - myTermios.c_cc[VMIN] = numCharsToWait; 211 + if(state_ != State::OPEN)
  212 + THROW_EXCEPT(std::string() + __PRETTY_FUNCTION__ + " called but state != OPEN. Please call Open() first.");
238 213
239 - // Save termios back  
240 - this->SetTermios(myTermios);  
241 - }  
242 -  
243 - void SerialPort::Write(std::string* str)  
244 - {  
245 - if(this->fileDesc == 0)  
246 - {  
247 - //this->sp->PrintError(SmartPrint::Ss() << );  
248 - //return false;  
249 -  
250 - throw std::runtime_error("SendMsg called but file descriptor (fileDesc) was 0, indicating file has not been opened."); 214 + if(fileDesc_ < 0) {
  215 + THROW_EXCEPT(std::string() + __PRETTY_FUNCTION__ + " called but file descriptor < 0, indicating file has not been opened.");
251 } 216 }
252 217
253 - int writeResult = write(this->fileDesc, str->c_str(), str->size()); 218 + int writeResult = write(fileDesc_, data.c_str(), data.size());
254 219
255 // Check status 220 // Check status
256 - if (writeResult == -1)  
257 - {  
258 - // Could not open COM port  
259 - //this->sp->PrintError(SmartPrint::Ss() << "Unable to write to \"" << this->filePath << "\" - " << strerror(errno));  
260 - //return false;  
261 - 221 + if (writeResult == -1) {
262 throw std::system_error(EFAULT, std::system_category()); 222 throw std::system_error(EFAULT, std::system_category());
263 } 223 }
264 -  
265 - // If code reaches here than write must of been successful  
266 } 224 }
267 225
268 - void SerialPort::Read(std::string* str) 226 + void SerialPort::Read(std::string& data)
269 { 227 {
270 - if(this->fileDesc == 0)  
271 - { 228 + data.clear();
  229 +
  230 + if(fileDesc_ == 0) {
272 //this->sp->PrintError(SmartPrint::Ss() << "Read() was called but file descriptor (fileDesc) was 0, indicating file has not been opened."); 231 //this->sp->PrintError(SmartPrint::Ss() << "Read() was called but file descriptor (fileDesc) was 0, indicating file has not been opened.");
273 //return false; 232 //return false;
274 - throw std::runtime_error("Read() was called but file descriptor (fileDesc) was 0, indicating file has not been opened."); 233 + THROW_EXCEPT("Read() was called but file descriptor (fileDesc) was 0, indicating file has not been opened.");
275 } 234 }
276 235
277 // Allocate memory for read buffer 236 // Allocate memory for read buffer
278 - char buf [256];  
279 - memset (&buf, '\0', sizeof buf); 237 +// char buf [256];
  238 +// memset (&buf, '\0', sizeof buf);
280 239
281 // Read from file 240 // Read from file
282 - int n = read(this->fileDesc, &buf, sizeof(buf)); 241 + // We provide the underlying raw array from the readBuffer_ vector to this C api.
  242 + // This will work because we do not delete/resize the vector while this method
  243 + // is called
  244 + ssize_t n = read(fileDesc_, &readBuffer_[0], readBufferSize_B_);
283 245
284 // Error Handling 246 // Error Handling
285 - if(n < 0)  
286 - {  
287 - // Could not open COM port  
288 - //this->sp->PrintError(SmartPrint::Ss() << "Unable to read from \"" << this->filePath << "\" - " << strerror(errno));  
289 - //return false;  
290 - 247 + if(n < 0) {
  248 + // Read was unsuccessful
291 throw std::system_error(EFAULT, std::system_category()); 249 throw std::system_error(EFAULT, std::system_category());
292 } 250 }
293 251
294 - if(n > 0)  
295 - {  
296 - //this->sp->PrintDebug(SmartPrint::Ss() << "\"" << n << "\" characters have been read from \"" << this->filePath << "\"");  
297 - // Characters have been read  
298 - buf[n] = '\0'; 252 + if(n > 0) {
  253 +
  254 +// buf[n] = '\0';
299 //printf("%s\r\n", buf); 255 //printf("%s\r\n", buf);
300 - str->append(buf); 256 +// data.append(buf);
  257 + data = std::string(&readBuffer_[0], n);
301 //std::cout << *str << " and size of string =" << str->size() << "\r\n"; 258 //std::cout << *str << " and size of string =" << str->size() << "\r\n";
302 } 259 }
303 260
304 // If code reaches here, read must of been successful 261 // If code reaches here, read must of been successful
305 } 262 }
306 263
307 - termios SerialPort::GetTermios()  
308 - { 264 + termios SerialPort::GetTermios() {
  265 + if(fileDesc_ == -1)
  266 + throw std::runtime_error("GetTermios() called but file descriptor was not valid.");
  267 +
309 struct termios tty; 268 struct termios tty;
310 memset(&tty, 0, sizeof(tty)); 269 memset(&tty, 0, sizeof(tty));
311 270
312 // Get current settings (will be stored in termios structure) 271 // Get current settings (will be stored in termios structure)
313 - if(tcgetattr(this->fileDesc, &tty) != 0) 272 + if(tcgetattr(fileDesc_, &tty) != 0)
314 { 273 {
315 // Error occurred 274 // Error occurred
316 - this->sp->PrintError(SmartPrint::Ss() << "Could not get terminal attributes for \"" << this->filePath << "\" - " << strerror(errno)); 275 + std::cout << "Could not get terminal attributes for \"" << device_ << "\" - " << strerror(errno) << std::endl;
317 throw std::system_error(EFAULT, std::system_category()); 276 throw std::system_error(EFAULT, std::system_category());
318 //return false; 277 //return false;
319 } 278 }
@@ -324,12 +283,12 @@ namespace SerialPort @@ -324,12 +283,12 @@ namespace SerialPort
324 void SerialPort::SetTermios(termios myTermios) 283 void SerialPort::SetTermios(termios myTermios)
325 { 284 {
326 // Flush port, then apply attributes 285 // Flush port, then apply attributes
327 - tcflush(this->fileDesc, TCIFLUSH); 286 + tcflush(fileDesc_, TCIFLUSH);
328 287
329 - if(tcsetattr(this->fileDesc, TCSANOW, &myTermios) != 0) 288 + if(tcsetattr(fileDesc_, TCSANOW, &myTermios) != 0)
330 { 289 {
331 // Error occurred 290 // Error occurred
332 - this->sp->PrintError(SmartPrint::Ss() << "Could not apply terminal attributes for \"" << this->filePath << "\" - " << strerror(errno)); 291 + std::cout << "Could not apply terminal attributes for \"" << device_ << "\" - " << strerror(errno) << std::endl;
333 throw std::system_error(EFAULT, std::system_category()); 292 throw std::system_error(EFAULT, std::system_category());
334 293
335 } 294 }
@@ -337,4 +296,27 @@ namespace SerialPort @@ -337,4 +296,27 @@ namespace SerialPort
337 // Successful! 296 // Successful!
338 } 297 }
339 298
340 -} // namespace ComPort 299 + void SerialPort::Close() {
  300 + if(fileDesc_ != -1) {
  301 + auto retVal = close(fileDesc_);
  302 + if(retVal != 0)
  303 + THROW_EXCEPT("Tried to close serial port " + device_ + ", but close() failed.");
  304 +
  305 + fileDesc_ = -1;
  306 + }
  307 +
  308 + state_ = State::CLOSED;
  309 + }
  310 +
  311 + void SerialPort::SetTimeout(int32_t timeout_ms) {
  312 + if(timeout_ms < -1)
  313 + THROW_EXCEPT(std::string() + "timeout_ms provided to " + __PRETTY_FUNCTION__ + " was < -1, which is invalid.");
  314 + if(timeout_ms > 25500)
  315 + THROW_EXCEPT(std::string() + "timeout_ms provided to " + __PRETTY_FUNCTION__ + " was > 25500, which is invalid.");
  316 + if(state_ == State::OPEN)
  317 + THROW_EXCEPT(std::string() + __PRETTY_FUNCTION__ + " called while state == OPEN.");
  318 + timeout_ms_ = timeout_ms;
  319 + }
  320 +
  321 +} // namespace CppLinuxSerial
  322 +} // namespace mn
test/unit/BasicTests.cpp 0 → 100644
  1 +///
  2 +/// \file BasicTests.cpp
  3 +/// \author Geoffrey Hunter <gbmhunter@gmail.com> (www.mbedded.ninja)
  4 +/// \created 2017-11-24
  5 +/// \last-modified 2017-11-24
  6 +/// \brief Basic tests for the SerialPort class.
  7 +/// \details
  8 +/// See README.rst in repo root dir for more info.
  9 +
  10 +// System includes
  11 +#include "gtest/gtest.h"
  12 +
  13 +// 3rd party includes
  14 +#include "CppLinuxSerial/SerialPort.hpp"
  15 +
  16 +// User includes
  17 +#include "TestUtil.hpp"
  18 +
  19 +using namespace mn::CppLinuxSerial;
  20 +
  21 +namespace {
  22 +
  23 + class BasicTests : public ::testing::Test {
  24 + protected:
  25 +
  26 + BasicTests() {
  27 + }
  28 +
  29 + virtual ~BasicTests() {
  30 + }
  31 +
  32 + std::string device0Name_ = TestUtil::GetInstance().GetDevice0Name();
  33 + std::string device1Name_ = TestUtil::GetInstance().GetDevice1Name();
  34 + };
  35 +
  36 + TEST_F(BasicTests, CanBeConstructed) {
  37 + SerialPort serialPort;
  38 + EXPECT_EQ(true, true);
  39 + }
  40 +
  41 + TEST_F(BasicTests, CanOpen) {
  42 + SerialPort serialPort0(device0Name_, BaudRate::B_57600);
  43 + serialPort0.Open();
  44 + }
  45 +
  46 + TEST_F(BasicTests, ReadWrite) {
  47 + SerialPort serialPort0(device0Name_, BaudRate::B_57600);
  48 + serialPort0.Open();
  49 +
  50 + SerialPort serialPort1(device1Name_, BaudRate::B_57600);
  51 + serialPort1.Open();
  52 +
  53 + serialPort0.Write("Hello");
  54 +
  55 + std::string readData;
  56 + serialPort1.Read(readData);
  57 +
  58 + ASSERT_EQ("Hello", readData);
  59 + }
  60 +
  61 +
  62 + TEST_F(BasicTests, ReadWriteDiffBaudRates) {
  63 + SerialPort serialPort0(device0Name_, BaudRate::B_9600);
  64 + serialPort0.Open();
  65 +
  66 + SerialPort serialPort1(device1Name_, BaudRate::B_57600);
  67 + serialPort1.Open();
  68 +
  69 + serialPort0.Write("Hello");
  70 +
  71 + std::string readData;
  72 + serialPort1.Read(readData);
  73 +
  74 + ASSERT_EQ("Hello", readData);
  75 + }
  76 +
  77 +} // namespace
0 \ No newline at end of file 78 \ No newline at end of file
test/unit/CMakeLists.txt 0 → 100644
  1 +#
  2 +# \file CMakeLists.txt
  3 +# \author Geoffrey Hunter <gbmhunter@gmail.com> (www.mbedded.ninja)
  4 +# \edited n/a
  5 +# \created 2017-11-24
  6 +# \last-modified 2017-11-24
  7 +# \brief Contains instructions for building the unit tests.
  8 +# \details
  9 +# See README.md in root dir for more info.
  10 +
  11 +enable_testing()
  12 +find_package (Threads)
  13 +#find_package(GTest REQUIRED)
  14 +#message("gtest libraries found at ${GTEST_BOTH_LIBRARIES}")
  15 +
  16 +file(GLOB_RECURSE CppLinuxSerialUnitTests_SRC
  17 + "*.cpp"
  18 + "*.hpp")
  19 +
  20 +
  21 +
  22 +add_executable(CppLinuxSerialUnitTests ${CppLinuxSerialUnitTests_SRC})
  23 +
  24 +if(COVERAGE)
  25 + message("Coverage enabled.")
  26 + target_compile_options(CppLinuxSerialUnitTests PRIVATE --coverage)
  27 + target_link_libraries(CppLinuxSerialUnitTests PRIVATE --coverage)
  28 +endif()
  29 +
  30 +target_link_libraries(CppLinuxSerialUnitTests LINK_PUBLIC CppLinuxSerial gtest_main ${CMAKE_THREAD_LIBS_INIT})
  31 +
  32 +# The custom target and custom command below allow the unit tests
  33 +# to be run.
  34 +# If you want them to run automatically by CMake, uncomment #ALL
  35 +add_custom_target(
  36 + run_unit_tests #ALL
  37 + DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/CppLinuxSerialUnitTests.touch CppLinuxSerialUnitTests)
  38 +
  39 +add_custom_command(
  40 + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/CppLinuxSerialUnitTests.touch
  41 + COMMAND ${CMAKE_CURRENT_BINARY_DIR}/CppLinuxSerialUnitTests)
0 \ No newline at end of file 42 \ No newline at end of file
test/unit/ConfigTests.cpp 0 → 100644
  1 +///
  2 +/// \file ConfigTests.cpp
  3 +/// \author Geoffrey Hunter <gbmhunter@gmail.com> (www.mbedded.ninja)
  4 +/// \created 2017-11-24
  5 +/// \last-modified 2017-11-24
  6 +/// \brief Configuration tests for the SerialPort class.
  7 +/// \details
  8 +/// See README.rst in repo root dir for more info.
  9 +
  10 +// System includes
  11 +#include "gtest/gtest.h"
  12 +
  13 +// 3rd party includes
  14 +#include "CppLinuxSerial/SerialPort.hpp"
  15 +
  16 +// User includes
  17 +#include "TestUtil.hpp"
  18 +
  19 +using namespace mn::CppLinuxSerial;
  20 +
  21 +namespace {
  22 +
  23 + class ConfigTests : public ::testing::Test {
  24 + protected:
  25 +
  26 + ConfigTests() {
  27 + serialPort_ = SerialPort(TestUtil::GetInstance().GetDevice0Name(), BaudRate::B_57600);
  28 + serialPort_.Open();
  29 + sttyOutput_ = TestUtil::GetInstance().Exec("stty -a -F " + TestUtil::GetInstance().GetDevice0Name());
  30 + }
  31 +
  32 + virtual ~ConfigTests() {
  33 + }
  34 +
  35 + SerialPort serialPort_;
  36 + std::string sttyOutput_;
  37 + };
  38 +
  39 + TEST_F(ConfigTests, BaudRateSetCorrectly) {
  40 + EXPECT_NE(std::string::npos, sttyOutput_.find("speed 57600 baud"));
  41 + serialPort_.SetBaudRate(BaudRate::B_115200);
  42 + sttyOutput_ = TestUtil::GetInstance().Exec("stty -a -F " + TestUtil::GetInstance().GetDevice0Name());
  43 + EXPECT_NE(std::string::npos, sttyOutput_.find("speed 115200 baud"));
  44 + }
  45 +
  46 + //================================================================================================//
  47 + //======================================= LOCAL MODES (c_lflag) ==================================//
  48 + //================================================================================================//
  49 +
  50 + TEST_F(ConfigTests, CanonicalModeOff) {
  51 + EXPECT_NE(std::string::npos, sttyOutput_.find("-icanon"));
  52 + }
  53 +
  54 + TEST_F(ConfigTests, EchoModeOff) {
  55 + EXPECT_NE(std::string::npos, sttyOutput_.find("-echo"));
  56 + EXPECT_NE(std::string::npos, sttyOutput_.find("-echoe"));
  57 + EXPECT_NE(std::string::npos, sttyOutput_.find("-echonl"));
  58 + }
  59 +
  60 + TEST_F(ConfigTests, InterruptQuitSuspCharsOff) {
  61 + EXPECT_NE(std::string::npos, sttyOutput_.find("-isig"));
  62 + }
  63 +
  64 +} // namespace
0 \ No newline at end of file 65 \ No newline at end of file
test/unit/TestUtil.hpp 0 → 100644
  1 +///
  2 +/// \file TestUtil.hpp
  3 +/// \author Geoffrey Hunter <gbmhunter@gmail.com> (www.mbedded.ninja)
  4 +/// \created 2017-11-24
  5 +/// \last-modified 2017-11-24
  6 +/// \brief Contains utility methods to help with testing.
  7 +/// \details
  8 +/// See README.rst in repo root dir for more info.
  9 +
  10 +#ifndef MN_CPP_LINUX_SERIAL_TEST_UTIL_H_
  11 +#define MN_CPP_LINUX_SERIAL_TEST_UTIL_H_
  12 +
  13 +// System includes
  14 +#include <string>
  15 +#include <array>
  16 +#include <memory>
  17 +#include <iostream>
  18 +#include <thread>
  19 +#include <chrono>
  20 +
  21 +// 3rd party includes
  22 +
  23 +
  24 +using namespace std::literals;
  25 +
  26 +
  27 +namespace mn {
  28 + namespace CppLinuxSerial {
  29 +
  30 + class TestUtil {
  31 +
  32 + public:
  33 +
  34 + static TestUtil& GetInstance() {
  35 + static TestUtil testUtil;
  36 + return testUtil;
  37 + }
  38 +
  39 + /// \brief Executes a command on the Linux command-line.
  40 + /// \details Blocks until command is complete.
  41 + /// \throws std::runtime_error is popen() fails.
  42 + std::string Exec(const std::string &cmd) {
  43 + std::array<char, 128> buffer;
  44 + std::string result;
  45 + std::shared_ptr<FILE> pipe(popen(cmd.c_str(), "r"), pclose);
  46 + if (!pipe) throw std::runtime_error("popen() failed!");
  47 +
  48 + while (!feof(pipe.get())) {
  49 + if (fgets(buffer.data(), 128, pipe.get()) != nullptr)
  50 + result += buffer.data();
  51 + }
  52 +
  53 + return result;
  54 + }
  55 +
  56 + void CreateVirtualSerialPortPair() {
  57 + std::cout << "Creating virtual serial port pair..." << std::endl;
  58 + std::system("nohup sudo socat -d -d pty,raw,echo=0,link=/dev/ttyS10 pty,raw,echo=0,link=/dev/ttyS11 &");
  59 +
  60 + // Hacky! Since socat is detached, we have no idea at what point it has created
  61 + // ttyS10 and ttyS11. Assume 1 second is long enough...
  62 + std::this_thread::sleep_for(1s);
  63 + std::system("sudo chmod a+rw /dev/ttyS10");
  64 + std::system("sudo chmod a+rw /dev/ttyS11");
  65 + }
  66 +
  67 + void CloseSerialPorts() {
  68 + // Dangerous! Kills all socat processes running
  69 + // on computer
  70 + std::system("sudo pkill socat");
  71 + }
  72 +
  73 + std::string GetDevice0Name() {
  74 + return device0Name_;
  75 + }
  76 +
  77 + std::string GetDevice1Name() {
  78 + return device1Name_;
  79 + }
  80 +
  81 + std::string device0Name_ = "/dev/ttyS10";
  82 + std::string device1Name_ = "/dev/ttyS11";
  83 +
  84 + protected:
  85 +
  86 + TestUtil() {
  87 +
  88 + }
  89 +
  90 + };
  91 + } // namespace CppLinuxSerial
  92 +} // namespace mn
  93 +
  94 +#endif // #ifndef MN_CPP_LINUX_SERIAL_TEST_UTIL_H_
test/unit/main.cpp 0 → 100644
  1 +///
  2 +/// \file main.cpp
  3 +/// \author Geoffrey Hunter <gbmhunter@gmail.com> (www.mbedded.ninja)
  4 +/// \edited n/a
  5 +/// \created 2017-11-24
  6 +/// \last-modified 2017-11-24
  7 +/// \brief Contains the main entry point for the unit tests.
  8 +/// \details
  9 +/// See README.md in root dir for more info.
  10 +
  11 +// System includes
  12 +#include "gtest/gtest.h"
  13 +
  14 +// User includes
  15 +#include "TestUtil.hpp"
  16 +
  17 +using namespace mn::CppLinuxSerial;
  18 +
  19 +class Environment : public testing::Environment {
  20 +public:
  21 + virtual ~Environment() {}
  22 + // Override this to define how to set up the environment.
  23 + virtual void SetUp() {
  24 + std::cout << __PRETTY_FUNCTION__ << " called." << std::endl;
  25 + TestUtil::GetInstance().CreateVirtualSerialPortPair();
  26 + }
  27 + // Override this to define how to tear down the environment.
  28 + virtual void TearDown() {
  29 + TestUtil::GetInstance().CloseSerialPorts();
  30 + }
  31 +};
  32 +
  33 +int main(int argc, char **argv)
  34 +{
  35 + ::testing::InitGoogleTest(&argc, argv);
  36 +
  37 + // Create and register global test setup
  38 + // (gtest takes ownership of pointer, do not delete manaully!)
  39 + ::testing::AddGlobalTestEnvironment(new Environment);
  40 +
  41 + return RUN_ALL_TESTS();
  42 +}
0 \ No newline at end of file 43 \ No newline at end of file
tools/build.sh 0 → 100755
  1 +#!/usr/bin/env bash
  2 +
  3 +#
  4 +# \file build.sh
  5 +# \author Geoffrey Hunter (www.mbedded.ninja) <gbmhunter@gmail.com>
  6 +# \edited n/a
  7 +# \created 2017-09-27
  8 +# \last-modified 2017-11-27
  9 +# \brief Bash script for building/installing the source code.
  10 +# \details
  11 +# See README.md in root dir for more info.
  12 +
  13 +# Get script path
  14 +script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
  15 +
  16 +# 3rd party imports
  17 +. ${script_dir}/lib/shflags
  18 +
  19 +# User imports
  20 +. ${script_dir}/lib/utilities.sh
  21 +
  22 +printInfo "=========================================================================================="
  23 +printInfo "================================= CppLinuxSerial build.sh ================================"
  24 +printInfo "=========================================================================================="
  25 +
  26 +set +e
  27 +
  28 +# Define the command-line arguments
  29 +DEFINE_boolean 'install' 'false' 'Do you want to [i]nstall the CppLinuxSerial header files onto your local system after build?' 'i'
  30 +DEFINE_boolean 'coverage' 'false' 'Do you want to record test [c]overage metrics?' 'c'
  31 +
  32 +# parse the command-line
  33 +FLAGS "$@" || exit 1
  34 +eval set -- "${FLAGS_ARGV}"
  35 +
  36 +# Any subsequent commands which fail will cause the shell script to exit immediately
  37 +# WARNING: Make sure to only activate this AFTER shflags has parsed command-line arguments
  38 +set -e
  39 +
  40 +printInfo "install = ${FLAGS_install}"
  41 +printInfo "coverage = ${FLAGS_coverage}"
  42 +
  43 +BUILD_DIRECTORY_NAME="build"
  44 +
  45 +# This will only make the build directory if it doesn't already
  46 +# exist. If it does exist, there is likely to be build artifacts
  47 +# in there already.
  48 +printInfo "Making and/or changing into build directory (${script_dir}/../${BUILD_DIRECTORY_NAME}/)..."
  49 +mkdir -p ${script_dir}/../${BUILD_DIRECTORY_NAME}/
  50 +cd ${script_dir}/../${BUILD_DIRECTORY_NAME}/
  51 +
  52 +if [[ "$FLAGS_coverage" == $FLAGS_TRUE ]]; then
  53 + printInfo 'Invoking cmake with -DCOVERAGE=1...'
  54 + cmake -DCOVERAGE=1 ..
  55 +else
  56 + printInfo 'Invoking cmake without -DCOVERAGE=1...'
  57 + cmake ..
  58 +fi
  59 +
  60 +printInfo 'Invoking make...'
  61 +make -j8
  62 +
  63 +printInfo 'Running unit tests...'
  64 +make -j8 run_unit_tests
  65 +
  66 +if [[ "$FLAGS_install" == $FLAGS_TRUE ]]; then
  67 + printInfo "Installing CppLinuxSerial headers onto local system..."
  68 + sudo make install
  69 +fi
tools/lib/shflags 0 → 100755
  1 +# vim:et:ft=sh:sts=2:sw=2
  2 +#
  3 +# Copyright 2008-2016 Kate Ward. All Rights Reserved.
  4 +# Released under the Apache License 2.0.
  5 +#
  6 +# shFlags -- Advanced command-line flag library for Unix shell scripts.
  7 +# http://code.google.com/p/shflags/
  8 +#
  9 +# Author: kate.ward@forestent.com (Kate Ward)
  10 +#
  11 +# This module implements something like the google-gflags library available
  12 +# from http://code.google.com/p/google-gflags/.
  13 +#
  14 +# FLAG TYPES: This is a list of the DEFINE_*'s that you can do. All flags take
  15 +# a name, default value, help-string, and optional 'short' name (one-letter
  16 +# name). Some flags have other arguments, which are described with the flag.
  17 +#
  18 +# DEFINE_string: takes any input, and intreprets it as a string.
  19 +#
  20 +# DEFINE_boolean: does not take any arguments. Say --myflag to set
  21 +# FLAGS_myflag to true, or --nomyflag to set FLAGS_myflag to false. For short
  22 +# flags, passing the flag on the command-line negates the default value, i.e.
  23 +# if the default is true, passing the flag sets the value to false.
  24 +#
  25 +# DEFINE_float: takes an input and intreprets it as a floating point number. As
  26 +# shell does not support floats per-se, the input is merely validated as
  27 +# being a valid floating point value.
  28 +#
  29 +# DEFINE_integer: takes an input and intreprets it as an integer.
  30 +#
  31 +# SPECIAL FLAGS: There are a few flags that have special meaning:
  32 +# --help (or -?) prints a list of all the flags in a human-readable fashion
  33 +# --flagfile=foo read flags from foo. (not implemented yet)
  34 +# -- as in getopt(), terminates flag-processing
  35 +#
  36 +# EXAMPLE USAGE:
  37 +#
  38 +# -- begin hello.sh --
  39 +# #! /bin/sh
  40 +# . ./shflags
  41 +# DEFINE_string name 'world' "somebody's name" n
  42 +# FLAGS "$@" || exit $?
  43 +# eval set -- "${FLAGS_ARGV}"
  44 +# echo "Hello, ${FLAGS_name}."
  45 +# -- end hello.sh --
  46 +#
  47 +# $ ./hello.sh -n Kate
  48 +# Hello, Kate.
  49 +#
  50 +# CUSTOMIZABLE BEHAVIOR:
  51 +#
  52 +# A script can override the default 'getopt' command by providing the path to
  53 +# an alternate implementation by defining the FLAGS_GETOPT_CMD variable.
  54 +#
  55 +# NOTES:
  56 +#
  57 +# * Not all systems include a getopt version that supports long flags. On these
  58 +# systems, only short flags are recognized.
  59 +
  60 +#==============================================================================
  61 +# shFlags
  62 +#
  63 +# Shared attributes:
  64 +# flags_error: last error message
  65 +# flags_output: last function output (rarely valid)
  66 +# flags_return: last return value
  67 +#
  68 +# __flags_longNames: list of long names for all flags
  69 +# __flags_shortNames: list of short names for all flags
  70 +# __flags_boolNames: list of boolean flag names
  71 +#
  72 +# __flags_opts: options parsed by getopt
  73 +#
  74 +# Per-flag attributes:
  75 +# FLAGS_<flag_name>: contains value of flag named 'flag_name'
  76 +# __flags_<flag_name>_default: the default flag value
  77 +# __flags_<flag_name>_help: the flag help string
  78 +# __flags_<flag_name>_short: the flag short name
  79 +# __flags_<flag_name>_type: the flag type
  80 +#
  81 +# Notes:
  82 +# - lists of strings are space separated, and a null value is the '~' char.
  83 +
  84 +# return if FLAGS already loaded
  85 +[ -n "${FLAGS_VERSION:-}" ] && return 0
  86 +FLAGS_VERSION='1.2.0'
  87 +
  88 +# return values that scripts can use
  89 +FLAGS_TRUE=0
  90 +FLAGS_FALSE=1
  91 +FLAGS_ERROR=2
  92 +
  93 +# determine some reasonable command defaults
  94 +__FLAGS_UNAME_S=`uname -s`
  95 +case "${__FLAGS_UNAME_S}" in
  96 + BSD) __FLAGS_EXPR_CMD='gexpr' ;;
  97 + *) __FLAGS_EXPR_CMD='expr' ;;
  98 +esac
  99 +
  100 +# commands a user can override if needed
  101 +FLAGS_EXPR_CMD=${FLAGS_EXPR_CMD:-${__FLAGS_EXPR_CMD}}
  102 +FLAGS_GETOPT_CMD=${FLAGS_GETOPT_CMD:-getopt}
  103 +
  104 +# specific shell checks
  105 +if [ -n "${ZSH_VERSION:-}" ]; then
  106 + setopt |grep "^shwordsplit$" >/dev/null
  107 + if [ $? -ne ${FLAGS_TRUE} ]; then
  108 + _flags_fatal 'zsh shwordsplit option is required for proper zsh operation'
  109 + fi
  110 + if [ -z "${FLAGS_PARENT:-}" ]; then
  111 + _flags_fatal "zsh does not pass \$0 through properly. please declare' \
  112 +\"FLAGS_PARENT=\$0\" before calling shFlags"
  113 + fi
  114 +fi
  115 +
  116 +# can we use built-ins?
  117 +( echo "${FLAGS_TRUE#0}"; ) >/dev/null 2>&1
  118 +if [ $? -eq ${FLAGS_TRUE} ]; then
  119 + __FLAGS_USE_BUILTIN=${FLAGS_TRUE}
  120 +else
  121 + __FLAGS_USE_BUILTIN=${FLAGS_FALSE}
  122 +fi
  123 +
  124 +#
  125 +# constants
  126 +#
  127 +
  128 +# reserved flag names
  129 +__FLAGS_RESERVED_LIST=' ARGC ARGV ERROR FALSE GETOPT_CMD HELP PARENT TRUE '
  130 +__FLAGS_RESERVED_LIST="${__FLAGS_RESERVED_LIST} VERSION "
  131 +
  132 +# getopt version
  133 +__FLAGS_GETOPT_VERS_STD=0
  134 +__FLAGS_GETOPT_VERS_ENH=1
  135 +__FLAGS_GETOPT_VERS_BSD=2
  136 +
  137 +${FLAGS_GETOPT_CMD} >/dev/null 2>&1
  138 +case $? in
  139 + 0) __FLAGS_GETOPT_VERS=${__FLAGS_GETOPT_VERS_STD} ;; # bsd getopt
  140 + 2)
  141 + # TODO(kward): look into '-T' option to test the internal getopt() version
  142 + if [ "`${FLAGS_GETOPT_CMD} --version`" = '-- ' ]; then
  143 + __FLAGS_GETOPT_VERS=${__FLAGS_GETOPT_VERS_STD}
  144 + else
  145 + __FLAGS_GETOPT_VERS=${__FLAGS_GETOPT_VERS_ENH}
  146 + fi
  147 + ;;
  148 + *) _flags_fatal 'unable to determine getopt version' ;;
  149 +esac
  150 +
  151 +# getopt optstring lengths
  152 +__FLAGS_OPTSTR_SHORT=0
  153 +__FLAGS_OPTSTR_LONG=1
  154 +
  155 +__FLAGS_NULL='~'
  156 +
  157 +# flag info strings
  158 +__FLAGS_INFO_DEFAULT='default'
  159 +__FLAGS_INFO_HELP='help'
  160 +__FLAGS_INFO_SHORT='short'
  161 +__FLAGS_INFO_TYPE='type'
  162 +
  163 +# flag lengths
  164 +__FLAGS_LEN_SHORT=0
  165 +__FLAGS_LEN_LONG=1
  166 +
  167 +# flag types
  168 +__FLAGS_TYPE_NONE=0
  169 +__FLAGS_TYPE_BOOLEAN=1
  170 +__FLAGS_TYPE_FLOAT=2
  171 +__FLAGS_TYPE_INTEGER=3
  172 +__FLAGS_TYPE_STRING=4
  173 +
  174 +# set the constants readonly
  175 +__flags_constants=`set |awk -F= '/^FLAGS_/ || /^__FLAGS_/ {print $1}'`
  176 +for __flags_const in ${__flags_constants}; do
  177 + # skip certain flags
  178 + case ${__flags_const} in
  179 + FLAGS_HELP) continue ;;
  180 + FLAGS_PARENT) continue ;;
  181 + esac
  182 + # set flag readonly
  183 + if [ -z "${ZSH_VERSION:-}" ]; then
  184 + readonly ${__flags_const}
  185 + else # handle zsh
  186 + case ${ZSH_VERSION} in
  187 + [123].*) readonly ${__flags_const} ;;
  188 + *) readonly -g ${__flags_const} ;; # declare readonly constants globally
  189 + esac
  190 + fi
  191 +done
  192 +unset __flags_const __flags_constants
  193 +
  194 +#
  195 +# internal variables
  196 +#
  197 +
  198 +# space separated lists
  199 +__flags_boolNames=' ' # boolean flag names
  200 +__flags_longNames=' ' # long flag names
  201 +__flags_shortNames=' ' # short flag names
  202 +__flags_definedNames=' ' # defined flag names (used for validation)
  203 +
  204 +__flags_columns='' # screen width in columns
  205 +__flags_opts='' # temporary storage for parsed getopt flags
  206 +
  207 +#------------------------------------------------------------------------------
  208 +# private functions
  209 +#
  210 +
  211 +# logging functions
  212 +_flags_debug() { echo "flags:DEBUG $@" >&2; }
  213 +_flags_warn() { echo "flags:WARN $@" >&2; }
  214 +_flags_error() { echo "flags:ERROR $@" >&2; }
  215 +_flags_fatal() { echo "flags:FATAL $@" >&2; exit ${FLAGS_ERROR}; }
  216 +
  217 +# Define a flag.
  218 +#
  219 +# Calling this function will define the following info variables for the
  220 +# specified flag:
  221 +# FLAGS_flagname - the name for this flag (based upon the long flag name)
  222 +# __flags_<flag_name>_default - the default value
  223 +# __flags_flagname_help - the help string
  224 +# __flags_flagname_short - the single letter alias
  225 +# __flags_flagname_type - the type of flag (one of __FLAGS_TYPE_*)
  226 +#
  227 +# Args:
  228 +# _flags__type: integer: internal type of flag (__FLAGS_TYPE_*)
  229 +# _flags__name: string: long flag name
  230 +# _flags__default: default flag value
  231 +# _flags__help: string: help string
  232 +# _flags__short: string: (optional) short flag name
  233 +# Returns:
  234 +# integer: success of operation, or error
  235 +_flags_define()
  236 +{
  237 + if [ $# -lt 4 ]; then
  238 + flags_error='DEFINE error: too few arguments'
  239 + flags_return=${FLAGS_ERROR}
  240 + _flags_error "${flags_error}"
  241 + return ${flags_return}
  242 + fi
  243 +
  244 + _flags_type_=$1
  245 + _flags_name_=$2
  246 + _flags_default_=$3
  247 + _flags_help_=$4
  248 + _flags_short_=${5:-${__FLAGS_NULL}}
  249 +
  250 + _flags_return_=${FLAGS_TRUE}
  251 + _flags_usName_=`_flags_underscoreName ${_flags_name_}`
  252 +
  253 + # check whether the flag name is reserved
  254 + _flags_itemInList ${_flags_usName_} "${__FLAGS_RESERVED_LIST}"
  255 + if [ $? -eq ${FLAGS_TRUE} ]; then
  256 + flags_error="flag name (${_flags_name_}) is reserved"
  257 + _flags_return_=${FLAGS_ERROR}
  258 + fi
  259 +
  260 + # require short option for getopt that don't support long options
  261 + if [ ${_flags_return_} -eq ${FLAGS_TRUE} \
  262 + -a ${__FLAGS_GETOPT_VERS} -ne ${__FLAGS_GETOPT_VERS_ENH} \
  263 + -a "${_flags_short_}" = "${__FLAGS_NULL}" ]
  264 + then
  265 + flags_error="short flag required for (${_flags_name_}) on this platform"
  266 + _flags_return_=${FLAGS_ERROR}
  267 + fi
  268 +
  269 + # check for existing long name definition
  270 + if [ ${_flags_return_} -eq ${FLAGS_TRUE} ]; then
  271 + if _flags_itemInList ${_flags_usName_} ${__flags_definedNames}; then
  272 + flags_error="definition for ([no]${_flags_name_}) already exists"
  273 + _flags_warn "${flags_error}"
  274 + _flags_return_=${FLAGS_FALSE}
  275 + fi
  276 + fi
  277 +
  278 + # check for existing short name definition
  279 + if [ ${_flags_return_} -eq ${FLAGS_TRUE} \
  280 + -a "${_flags_short_}" != "${__FLAGS_NULL}" ]
  281 + then
  282 + if _flags_itemInList "${_flags_short_}" ${__flags_shortNames}; then
  283 + flags_error="flag short name (${_flags_short_}) already defined"
  284 + _flags_warn "${flags_error}"
  285 + _flags_return_=${FLAGS_FALSE}
  286 + fi
  287 + fi
  288 +
  289 + # handle default value. note, on several occasions the 'if' portion of an
  290 + # if/then/else contains just a ':' which does nothing. a binary reversal via
  291 + # '!' is not done because it does not work on all shells.
  292 + if [ ${_flags_return_} -eq ${FLAGS_TRUE} ]; then
  293 + case ${_flags_type_} in
  294 + ${__FLAGS_TYPE_BOOLEAN})
  295 + if _flags_validBool "${_flags_default_}"; then
  296 + case ${_flags_default_} in
  297 + true|t|0) _flags_default_=${FLAGS_TRUE} ;;
  298 + false|f|1) _flags_default_=${FLAGS_FALSE} ;;
  299 + esac
  300 + else
  301 + flags_error="invalid default flag value '${_flags_default_}'"
  302 + _flags_return_=${FLAGS_ERROR}
  303 + fi
  304 + ;;
  305 +
  306 + ${__FLAGS_TYPE_FLOAT})
  307 + if _flags_validFloat "${_flags_default_}"; then
  308 + :
  309 + else
  310 + flags_error="invalid default flag value '${_flags_default_}'"
  311 + _flags_return_=${FLAGS_ERROR}
  312 + fi
  313 + ;;
  314 +
  315 + ${__FLAGS_TYPE_INTEGER})
  316 + if _flags_validInt "${_flags_default_}"; then
  317 + :
  318 + else
  319 + flags_error="invalid default flag value '${_flags_default_}'"
  320 + _flags_return_=${FLAGS_ERROR}
  321 + fi
  322 + ;;
  323 +
  324 + ${__FLAGS_TYPE_STRING}) ;; # everything in shell is a valid string
  325 +
  326 + *)
  327 + flags_error="unrecognized flag type '${_flags_type_}'"
  328 + _flags_return_=${FLAGS_ERROR}
  329 + ;;
  330 + esac
  331 + fi
  332 +
  333 + if [ ${_flags_return_} -eq ${FLAGS_TRUE} ]; then
  334 + # store flag information
  335 + eval "FLAGS_${_flags_usName_}='${_flags_default_}'"
  336 + eval "__flags_${_flags_usName_}_${__FLAGS_INFO_TYPE}=${_flags_type_}"
  337 + eval "__flags_${_flags_usName_}_${__FLAGS_INFO_DEFAULT}=\
  338 +\"${_flags_default_}\""
  339 + eval "__flags_${_flags_usName_}_${__FLAGS_INFO_HELP}=\"${_flags_help_}\""
  340 + eval "__flags_${_flags_usName_}_${__FLAGS_INFO_SHORT}='${_flags_short_}'"
  341 +
  342 + # append flag names to name lists
  343 + __flags_shortNames="${__flags_shortNames}${_flags_short_} "
  344 + __flags_longNames="${__flags_longNames}${_flags_name_} "
  345 + [ ${_flags_type_} -eq ${__FLAGS_TYPE_BOOLEAN} ] && \
  346 + __flags_boolNames="${__flags_boolNames}no${_flags_name_} "
  347 +
  348 + # append flag names to defined names for later validation checks
  349 + __flags_definedNames="${__flags_definedNames}${_flags_usName_} "
  350 + [ ${_flags_type_} -eq ${__FLAGS_TYPE_BOOLEAN} ] && \
  351 + __flags_definedNames="${__flags_definedNames}no${_flags_usName_} "
  352 + fi
  353 +
  354 + flags_return=${_flags_return_}
  355 + unset _flags_default_ _flags_help_ _flags_name_ _flags_return_ \
  356 + _flags_short_ _flags_type_ _flags_usName_
  357 + [ ${flags_return} -eq ${FLAGS_ERROR} ] && _flags_error "${flags_error}"
  358 + return ${flags_return}
  359 +}
  360 +
  361 +# Underscore a flag name by replacing dashes with underscores.
  362 +#
  363 +# Args:
  364 +# unnamed: string: log flag name
  365 +# Output:
  366 +# string: underscored name
  367 +_flags_underscoreName()
  368 +{
  369 + echo $1 |tr '-' '_'
  370 +}
  371 +
  372 +# Return valid getopt options using currently defined list of long options.
  373 +#
  374 +# This function builds a proper getopt option string for short (and long)
  375 +# options, using the current list of long options for reference.
  376 +#
  377 +# Args:
  378 +# _flags_optStr: integer: option string type (__FLAGS_OPTSTR_*)
  379 +# Output:
  380 +# string: generated option string for getopt
  381 +# Returns:
  382 +# boolean: success of operation (always returns True)
  383 +_flags_genOptStr()
  384 +{
  385 + _flags_optStrType_=$1
  386 +
  387 + _flags_opts_=''
  388 +
  389 + for _flags_name_ in ${__flags_longNames}; do
  390 + _flags_usName_=`_flags_underscoreName ${_flags_name_}`
  391 + _flags_type_=`_flags_getFlagInfo ${_flags_usName_} ${__FLAGS_INFO_TYPE}`
  392 + [ $? -eq ${FLAGS_TRUE} ] || _flags_fatal 'call to _flags_type_ failed'
  393 + case ${_flags_optStrType_} in
  394 + ${__FLAGS_OPTSTR_SHORT})
  395 + _flags_shortName_=`_flags_getFlagInfo \
  396 + ${_flags_usName_} ${__FLAGS_INFO_SHORT}`
  397 + if [ "${_flags_shortName_}" != "${__FLAGS_NULL}" ]; then
  398 + _flags_opts_="${_flags_opts_}${_flags_shortName_}"
  399 + # getopt needs a trailing ':' to indicate a required argument
  400 + [ ${_flags_type_} -ne ${__FLAGS_TYPE_BOOLEAN} ] && \
  401 + _flags_opts_="${_flags_opts_}:"
  402 + fi
  403 + ;;
  404 +
  405 + ${__FLAGS_OPTSTR_LONG})
  406 + _flags_opts_="${_flags_opts_:+${_flags_opts_},}${_flags_name_}"
  407 + # getopt needs a trailing ':' to indicate a required argument
  408 + [ ${_flags_type_} -ne ${__FLAGS_TYPE_BOOLEAN} ] && \
  409 + _flags_opts_="${_flags_opts_}:"
  410 + ;;
  411 + esac
  412 + done
  413 +
  414 + echo "${_flags_opts_}"
  415 + unset _flags_name_ _flags_opts_ _flags_optStrType_ _flags_shortName_ \
  416 + _flags_type_ _flags_usName_
  417 + return ${FLAGS_TRUE}
  418 +}
  419 +
  420 +# Returns flag details based on a flag name and flag info.
  421 +#
  422 +# Args:
  423 +# string: underscored flag name
  424 +# string: flag info (see the _flags_define function for valid info types)
  425 +# Output:
  426 +# string: value of dereferenced flag variable
  427 +# Returns:
  428 +# integer: one of FLAGS_{TRUE|FALSE|ERROR}
  429 +_flags_getFlagInfo()
  430 +{
  431 + # note: adding gFI to variable names to prevent naming conflicts with calling
  432 + # functions
  433 + _flags_gFI_usName_=$1
  434 + _flags_gFI_info_=$2
  435 +
  436 + _flags_infoVar_="__flags_${_flags_gFI_usName_}_${_flags_gFI_info_}"
  437 + _flags_strToEval_="_flags_infoValue_=\"\${${_flags_infoVar_}:-}\""
  438 + eval "${_flags_strToEval_}"
  439 + if [ -n "${_flags_infoValue_}" ]; then
  440 + flags_return=${FLAGS_TRUE}
  441 + else
  442 + # see if the _flags_gFI_usName_ variable is a string as strings can be
  443 + # empty...
  444 + # note: the DRY principle would say to have this function call itself for
  445 + # the next three lines, but doing so results in an infinite loop as an
  446 + # invalid _flags_name_ will also not have the associated _type variable.
  447 + # Because it doesn't (it will evaluate to an empty string) the logic will
  448 + # try to find the _type variable of the _type variable, and so on. Not so
  449 + # good ;-)
  450 + _flags_typeVar_="__flags_${_flags_gFI_usName_}_${__FLAGS_INFO_TYPE}"
  451 + _flags_strToEval_="_flags_typeValue_=\"\${${_flags_typeVar_}:-}\""
  452 + eval "${_flags_strToEval_}"
  453 + if [ "${_flags_typeValue_}" = "${__FLAGS_TYPE_STRING}" ]; then
  454 + flags_return=${FLAGS_TRUE}
  455 + else
  456 + flags_return=${FLAGS_ERROR}
  457 + flags_error="missing flag info variable (${_flags_infoVar_})"
  458 + fi
  459 + fi
  460 +
  461 + echo "${_flags_infoValue_}"
  462 + unset _flags_gFI_usName_ _flags_gfI_info_ _flags_infoValue_ _flags_infoVar_ \
  463 + _flags_strToEval_ _flags_typeValue_ _flags_typeVar_
  464 + [ ${flags_return} -eq ${FLAGS_ERROR} ] && _flags_error "${flags_error}"
  465 + return ${flags_return}
  466 +}
  467 +
  468 +# Check for presense of item in a list.
  469 +#
  470 +# Passed a string (e.g. 'abc'), this function will determine if the string is
  471 +# present in the list of strings (e.g. ' foo bar abc ').
  472 +#
  473 +# Args:
  474 +# _flags_str_: string: string to search for in a list of strings
  475 +# unnamed: list: list of strings
  476 +# Returns:
  477 +# boolean: true if item is in the list
  478 +_flags_itemInList() {
  479 + _flags_str_=$1
  480 + shift
  481 +
  482 + echo " ${*:-} " |grep " ${_flags_str_} " >/dev/null
  483 + if [ $? -eq 0 ]; then
  484 + flags_return=${FLAGS_TRUE}
  485 + else
  486 + flags_return=${FLAGS_FALSE}
  487 + fi
  488 +
  489 + unset _flags_str_
  490 + return ${flags_return}
  491 +}
  492 +
  493 +# Returns the width of the current screen.
  494 +#
  495 +# Output:
  496 +# integer: width in columns of the current screen.
  497 +_flags_columns()
  498 +{
  499 + if [ -z "${__flags_columns}" ]; then
  500 + # determine the value and store it
  501 + if eval stty size >/dev/null 2>&1; then
  502 + # stty size worked :-)
  503 + set -- `stty size`
  504 + __flags_columns=$2
  505 + elif eval tput cols >/dev/null 2>&1; then
  506 + set -- `tput cols`
  507 + __flags_columns=$1
  508 + else
  509 + __flags_columns=80 # default terminal width
  510 + fi
  511 + fi
  512 + echo ${__flags_columns}
  513 +}
  514 +
  515 +# Validate a boolean.
  516 +#
  517 +# Args:
  518 +# _flags__bool: boolean: value to validate
  519 +# Returns:
  520 +# bool: true if the value is a valid boolean
  521 +_flags_validBool()
  522 +{
  523 + _flags_bool_=$1
  524 +
  525 + flags_return=${FLAGS_TRUE}
  526 + case "${_flags_bool_}" in
  527 + true|t|0) ;;
  528 + false|f|1) ;;
  529 + *) flags_return=${FLAGS_FALSE} ;;
  530 + esac
  531 +
  532 + unset _flags_bool_
  533 + return ${flags_return}
  534 +}
  535 +
  536 +# Validate a float.
  537 +#
  538 +# Args:
  539 +# _flags_float_: float: value to validate
  540 +# Returns:
  541 +# bool: true if the value is a valid integer
  542 +_flags_validFloat()
  543 +{
  544 + flags_return=${FLAGS_FALSE}
  545 + [ -n "$1" ] || return ${flags_return}
  546 + _flags_float_=$1
  547 +
  548 + if _flags_validInt ${_flags_float_}; then
  549 + flags_return=${FLAGS_TRUE}
  550 + elif _flags_useBuiltin; then
  551 + _flags_float_whole_=${_flags_float_%.*}
  552 + _flags_float_fraction_=${_flags_float_#*.}
  553 + if _flags_validInt ${_flags_float_whole_:-0} -a \
  554 + _flags_validInt ${_flags_float_fraction_}; then
  555 + flags_return=${FLAGS_TRUE}
  556 + fi
  557 + unset _flags_float_whole_ _flags_float_fraction_
  558 + else
  559 + flags_return=${FLAGS_TRUE}
  560 + case ${_flags_float_} in
  561 + -*) # negative floats
  562 + _flags_test_=`${FLAGS_EXPR_CMD} -- "${_flags_float_}" :\
  563 + '\(-[0-9]*\.[0-9]*\)'`
  564 + ;;
  565 + *) # positive floats
  566 + _flags_test_=`${FLAGS_EXPR_CMD} -- "${_flags_float_}" :\
  567 + '\([0-9]*\.[0-9]*\)'`
  568 + ;;
  569 + esac
  570 + [ "${_flags_test_}" != "${_flags_float_}" ] && flags_return=${FLAGS_FALSE}
  571 + unset _flags_test_
  572 + fi
  573 +
  574 + unset _flags_float_ _flags_float_whole_ _flags_float_fraction_
  575 + return ${flags_return}
  576 +}
  577 +
  578 +# Validate an integer.
  579 +#
  580 +# Args:
  581 +# _flags_int_: integer: value to validate
  582 +# Returns:
  583 +# bool: true if the value is a valid integer
  584 +_flags_validInt()
  585 +{
  586 + flags_return=${FLAGS_FALSE}
  587 + [ -n "$1" ] || return ${flags_return}
  588 + _flags_int_=$1
  589 +
  590 + case ${_flags_int_} in
  591 + -*.*) ;; # ignore negative floats (we'll invalidate them later)
  592 + -*) # strip possible leading negative sign
  593 + if _flags_useBuiltin; then
  594 + _flags_int_=${_flags_int_#-}
  595 + else
  596 + _flags_int_=`${FLAGS_EXPR_CMD} -- "${_flags_int_}" : '-\([0-9][0-9]*\)'`
  597 + fi
  598 + ;;
  599 + esac
  600 +
  601 + case ${_flags_int_} in
  602 + *[!0-9]*) flags_return=${FLAGS_FALSE} ;;
  603 + *) flags_return=${FLAGS_TRUE} ;;
  604 + esac
  605 +
  606 + unset _flags_int_
  607 + return ${flags_return}
  608 +}
  609 +
  610 +# Parse command-line options using the standard getopt.
  611 +#
  612 +# Note: the flag options are passed around in the global __flags_opts so that
  613 +# the formatting is not lost due to shell parsing and such.
  614 +#
  615 +# Args:
  616 +# @: varies: command-line options to parse
  617 +# Returns:
  618 +# integer: a FLAGS success condition
  619 +_flags_getoptStandard()
  620 +{
  621 + flags_return=${FLAGS_TRUE}
  622 + _flags_shortOpts_=`_flags_genOptStr ${__FLAGS_OPTSTR_SHORT}`
  623 +
  624 + # check for spaces in passed options
  625 + for _flags_opt_ in "$@"; do
  626 + # note: the silliness with the x's is purely for ksh93 on Ubuntu 6.06
  627 + _flags_match_=`echo "x${_flags_opt_}x" |sed 's/ //g'`
  628 + if [ "${_flags_match_}" != "x${_flags_opt_}x" ]; then
  629 + flags_error='the available getopt does not support spaces in options'
  630 + flags_return=${FLAGS_ERROR}
  631 + break
  632 + fi
  633 + done
  634 +
  635 + if [ ${flags_return} -eq ${FLAGS_TRUE} ]; then
  636 + __flags_opts=`getopt ${_flags_shortOpts_} $@ 2>&1`
  637 + _flags_rtrn_=$?
  638 + if [ ${_flags_rtrn_} -ne ${FLAGS_TRUE} ]; then
  639 + _flags_warn "${__flags_opts}"
  640 + flags_error='unable to parse provided options with getopt.'
  641 + flags_return=${FLAGS_ERROR}
  642 + fi
  643 + fi
  644 +
  645 + unset _flags_match_ _flags_opt_ _flags_rtrn_ _flags_shortOpts_
  646 + return ${flags_return}
  647 +}
  648 +
  649 +# Parse command-line options using the enhanced getopt.
  650 +#
  651 +# Note: the flag options are passed around in the global __flags_opts so that
  652 +# the formatting is not lost due to shell parsing and such.
  653 +#
  654 +# Args:
  655 +# @: varies: command-line options to parse
  656 +# Returns:
  657 +# integer: a FLAGS success condition
  658 +_flags_getoptEnhanced()
  659 +{
  660 + flags_return=${FLAGS_TRUE}
  661 + _flags_shortOpts_=`_flags_genOptStr ${__FLAGS_OPTSTR_SHORT}`
  662 + _flags_boolOpts_=`echo "${__flags_boolNames}" \
  663 + |sed 's/^ *//;s/ *$//;s/ /,/g'`
  664 + _flags_longOpts_=`_flags_genOptStr ${__FLAGS_OPTSTR_LONG}`
  665 +
  666 + __flags_opts=`${FLAGS_GETOPT_CMD} \
  667 + -o ${_flags_shortOpts_} \
  668 + -l "${_flags_longOpts_},${_flags_boolOpts_}" \
  669 + -- "$@" 2>&1`
  670 + _flags_rtrn_=$?
  671 + if [ ${_flags_rtrn_} -ne ${FLAGS_TRUE} ]; then
  672 + _flags_warn "${__flags_opts}"
  673 + flags_error='unable to parse provided options with getopt.'
  674 + flags_return=${FLAGS_ERROR}
  675 + fi
  676 +
  677 + unset _flags_boolOpts_ _flags_longOpts_ _flags_rtrn_ _flags_shortOpts_
  678 + return ${flags_return}
  679 +}
  680 +
  681 +# Dynamically parse a getopt result and set appropriate variables.
  682 +#
  683 +# This function does the actual conversion of getopt output and runs it through
  684 +# the standard case structure for parsing. The case structure is actually quite
  685 +# dynamic to support any number of flags.
  686 +#
  687 +# Args:
  688 +# argc: int: original command-line argument count
  689 +# @: varies: output from getopt parsing
  690 +# Returns:
  691 +# integer: a FLAGS success condition
  692 +_flags_parseGetopt()
  693 +{
  694 + _flags_argc_=$1
  695 + shift
  696 +
  697 + flags_return=${FLAGS_TRUE}
  698 +
  699 + if [ ${__FLAGS_GETOPT_VERS} -ne ${__FLAGS_GETOPT_VERS_ENH} ]; then
  700 + set -- $@
  701 + else
  702 + # note the quotes around the `$@' -- they are essential!
  703 + eval set -- "$@"
  704 + fi
  705 +
  706 + # Provide user with the number of arguments to shift by later.
  707 + # NOTE: the FLAGS_ARGC variable is obsolete as of 1.0.3 because it does not
  708 + # properly give user access to non-flag arguments mixed in between flag
  709 + # arguments. Its usage was replaced by FLAGS_ARGV, and it is being kept only
  710 + # for backwards compatibility reasons.
  711 + FLAGS_ARGC=`_flags_math "$# - 1 - ${_flags_argc_}"`
  712 +
  713 + # handle options. note options with values must do an additional shift
  714 + while true; do
  715 + _flags_opt_=$1
  716 + _flags_arg_=${2:-}
  717 + _flags_type_=${__FLAGS_TYPE_NONE}
  718 + _flags_name_=''
  719 +
  720 + # determine long flag name
  721 + case "${_flags_opt_}" in
  722 + --) shift; break ;; # discontinue option parsing
  723 +
  724 + --*) # long option
  725 + if _flags_useBuiltin; then
  726 + _flags_opt_=${_flags_opt_#*--}
  727 + else
  728 + _flags_opt_=`${FLAGS_EXPR_CMD} -- "${_flags_opt_}" : '--\(.*\)'`
  729 + fi
  730 + _flags_len_=${__FLAGS_LEN_LONG}
  731 + if _flags_itemInList "${_flags_opt_}" ${__flags_longNames}; then
  732 + _flags_name_=${_flags_opt_}
  733 + else
  734 + # check for negated long boolean version
  735 + if _flags_itemInList "${_flags_opt_}" ${__flags_boolNames}; then
  736 + if _flags_useBuiltin; then
  737 + _flags_name_=${_flags_opt_#*no}
  738 + else
  739 + _flags_name_=`${FLAGS_EXPR_CMD} -- "${_flags_opt_}" : 'no\(.*\)'`
  740 + fi
  741 + _flags_type_=${__FLAGS_TYPE_BOOLEAN}
  742 + _flags_arg_=${__FLAGS_NULL}
  743 + fi
  744 + fi
  745 + ;;
  746 +
  747 + -*) # short option
  748 + if _flags_useBuiltin; then
  749 + _flags_opt_=${_flags_opt_#*-}
  750 + else
  751 + _flags_opt_=`${FLAGS_EXPR_CMD} -- "${_flags_opt_}" : '-\(.*\)'`
  752 + fi
  753 + _flags_len_=${__FLAGS_LEN_SHORT}
  754 + if _flags_itemInList "${_flags_opt_}" ${__flags_shortNames}; then
  755 + # yes. match short name to long name. note purposeful off-by-one
  756 + # (too high) with awk calculations.
  757 + _flags_pos_=`echo "${__flags_shortNames}" \
  758 + |awk 'BEGIN{RS=" ";rn=0}$0==e{rn=NR}END{print rn}' \
  759 + e=${_flags_opt_}`
  760 + _flags_name_=`echo "${__flags_longNames}" \
  761 + |awk 'BEGIN{RS=" "}rn==NR{print $0}' rn="${_flags_pos_}"`
  762 + fi
  763 + ;;
  764 + esac
  765 +
  766 + # die if the flag was unrecognized
  767 + if [ -z "${_flags_name_}" ]; then
  768 + flags_error="unrecognized option (${_flags_opt_})"
  769 + flags_return=${FLAGS_ERROR}
  770 + break
  771 + fi
  772 +
  773 + # set new flag value
  774 + _flags_usName_=`_flags_underscoreName ${_flags_name_}`
  775 + [ ${_flags_type_} -eq ${__FLAGS_TYPE_NONE} ] && \
  776 + _flags_type_=`_flags_getFlagInfo \
  777 + "${_flags_usName_}" ${__FLAGS_INFO_TYPE}`
  778 + case ${_flags_type_} in
  779 + ${__FLAGS_TYPE_BOOLEAN})
  780 + if [ ${_flags_len_} -eq ${__FLAGS_LEN_LONG} ]; then
  781 + if [ "${_flags_arg_}" != "${__FLAGS_NULL}" ]; then
  782 + eval "FLAGS_${_flags_usName_}=${FLAGS_TRUE}"
  783 + else
  784 + eval "FLAGS_${_flags_usName_}=${FLAGS_FALSE}"
  785 + fi
  786 + else
  787 + _flags_strToEval_="_flags_val_=\
  788 +\${__flags_${_flags_usName_}_${__FLAGS_INFO_DEFAULT}}"
  789 + eval "${_flags_strToEval_}"
  790 + if [ ${_flags_val_} -eq ${FLAGS_FALSE} ]; then
  791 + eval "FLAGS_${_flags_usName_}=${FLAGS_TRUE}"
  792 + else
  793 + eval "FLAGS_${_flags_usName_}=${FLAGS_FALSE}"
  794 + fi
  795 + fi
  796 + ;;
  797 +
  798 + ${__FLAGS_TYPE_FLOAT})
  799 + if _flags_validFloat "${_flags_arg_}"; then
  800 + eval "FLAGS_${_flags_usName_}='${_flags_arg_}'"
  801 + else
  802 + flags_error="invalid float value (${_flags_arg_})"
  803 + flags_return=${FLAGS_ERROR}
  804 + break
  805 + fi
  806 + ;;
  807 +
  808 + ${__FLAGS_TYPE_INTEGER})
  809 + if _flags_validInt "${_flags_arg_}"; then
  810 + eval "FLAGS_${_flags_usName_}='${_flags_arg_}'"
  811 + else
  812 + flags_error="invalid integer value (${_flags_arg_})"
  813 + flags_return=${FLAGS_ERROR}
  814 + break
  815 + fi
  816 + ;;
  817 +
  818 + ${__FLAGS_TYPE_STRING})
  819 + eval "FLAGS_${_flags_usName_}='${_flags_arg_}'"
  820 + ;;
  821 + esac
  822 +
  823 + # handle special case help flag
  824 + if [ "${_flags_usName_}" = 'help' ]; then
  825 + if [ ${FLAGS_help} -eq ${FLAGS_TRUE} ]; then
  826 + flags_help
  827 + flags_error='help requested'
  828 + flags_return=${FLAGS_FALSE}
  829 + break
  830 + fi
  831 + fi
  832 +
  833 + # shift the option and non-boolean arguements out.
  834 + shift
  835 + [ ${_flags_type_} != ${__FLAGS_TYPE_BOOLEAN} ] && shift
  836 + done
  837 +
  838 + # give user back non-flag arguments
  839 + FLAGS_ARGV=''
  840 + while [ $# -gt 0 ]; do
  841 + FLAGS_ARGV="${FLAGS_ARGV:+${FLAGS_ARGV} }'$1'"
  842 + shift
  843 + done
  844 +
  845 + unset _flags_arg_ _flags_len_ _flags_name_ _flags_opt_ _flags_pos_ \
  846 + _flags_strToEval_ _flags_type_ _flags_usName_ _flags_val_
  847 + return ${flags_return}
  848 +}
  849 +
  850 +# Perform some path using built-ins.
  851 +#
  852 +# Args:
  853 +# $@: string: math expression to evaluate
  854 +# Output:
  855 +# integer: the result
  856 +# Returns:
  857 +# bool: success of math evaluation
  858 +_flags_math()
  859 +{
  860 + if [ $# -eq 0 ]; then
  861 + flags_return=${FLAGS_FALSE}
  862 + elif _flags_useBuiltin; then
  863 + # Variable assignment is needed as workaround for Solaris Bourne shell,
  864 + # which cannot parse a bare $((expression)).
  865 + _flags_expr_='$(($@))'
  866 + eval echo ${_flags_expr_}
  867 + flags_return=$?
  868 + unset _flags_expr_
  869 + else
  870 + eval expr $@
  871 + flags_return=$?
  872 + fi
  873 +
  874 + return ${flags_return}
  875 +}
  876 +
  877 +# Cross-platform strlen() implementation.
  878 +#
  879 +# Args:
  880 +# _flags_str: string: to determine length of
  881 +# Output:
  882 +# integer: length of string
  883 +# Returns:
  884 +# bool: success of strlen evaluation
  885 +_flags_strlen()
  886 +{
  887 + _flags_str_=${1:-}
  888 +
  889 + if [ -z "${_flags_str_}" ]; then
  890 + flags_output=0
  891 + elif _flags_useBuiltin; then
  892 + flags_output=${#_flags_str_}
  893 + else
  894 + flags_output=`${FLAGS_EXPR_CMD} -- "${_flags_str_}" : '.*'`
  895 + fi
  896 + flags_return=$?
  897 +
  898 + unset _flags_str_
  899 + echo ${flags_output}
  900 + return ${flags_return}
  901 +}
  902 +
  903 +# Use built-in helper function to enable unit testing.
  904 +#
  905 +# Args:
  906 +# None
  907 +# Returns:
  908 +# bool: true if built-ins should be used
  909 +_flags_useBuiltin()
  910 +{
  911 + return ${__FLAGS_USE_BUILTIN}
  912 +}
  913 +
  914 +#------------------------------------------------------------------------------
  915 +# public functions
  916 +#
  917 +# A basic boolean flag. Boolean flags do not take any arguments, and their
  918 +# value is either 1 (false) or 0 (true). For long flags, the false value is
  919 +# specified on the command line by prepending the word 'no'. With short flags,
  920 +# the presense of the flag toggles the current value between true and false.
  921 +# Specifying a short boolean flag twice on the command results in returning the
  922 +# value back to the default value.
  923 +#
  924 +# A default value is required for boolean flags.
  925 +#
  926 +# For example, lets say a Boolean flag was created whose long name was 'update'
  927 +# and whose short name was 'x', and the default value was 'false'. This flag
  928 +# could be explicitly set to 'true' with '--update' or by '-x', and it could be
  929 +# explicitly set to 'false' with '--noupdate'.
  930 +DEFINE_boolean() { _flags_define ${__FLAGS_TYPE_BOOLEAN} "$@"; }
  931 +
  932 +# Other basic flags.
  933 +DEFINE_float() { _flags_define ${__FLAGS_TYPE_FLOAT} "$@"; }
  934 +DEFINE_integer() { _flags_define ${__FLAGS_TYPE_INTEGER} "$@"; }
  935 +DEFINE_string() { _flags_define ${__FLAGS_TYPE_STRING} "$@"; }
  936 +
  937 +# Parse the flags.
  938 +#
  939 +# Args:
  940 +# unnamed: list: command-line flags to parse
  941 +# Returns:
  942 +# integer: success of operation, or error
  943 +FLAGS()
  944 +{
  945 + # define a standard 'help' flag if one isn't already defined
  946 + [ -z "${__flags_help_type:-}" ] && \
  947 + DEFINE_boolean 'help' false 'show this help' 'h'
  948 +
  949 + # parse options
  950 + if [ $# -gt 0 ]; then
  951 + if [ ${__FLAGS_GETOPT_VERS} -ne ${__FLAGS_GETOPT_VERS_ENH} ]; then
  952 + _flags_getoptStandard "$@"
  953 + else
  954 + _flags_getoptEnhanced "$@"
  955 + fi
  956 + flags_return=$?
  957 + else
  958 + # nothing passed; won't bother running getopt
  959 + __flags_opts='--'
  960 + flags_return=${FLAGS_TRUE}
  961 + fi
  962 +
  963 + if [ ${flags_return} -eq ${FLAGS_TRUE} ]; then
  964 + _flags_parseGetopt $# "${__flags_opts}"
  965 + flags_return=$?
  966 + fi
  967 +
  968 + [ ${flags_return} -eq ${FLAGS_ERROR} ] && _flags_fatal "${flags_error}"
  969 + return ${flags_return}
  970 +}
  971 +
  972 +# This is a helper function for determining the 'getopt' version for platforms
  973 +# where the detection isn't working. It simply outputs debug information that
  974 +# can be included in a bug report.
  975 +#
  976 +# Args:
  977 +# none
  978 +# Output:
  979 +# debug info that can be included in a bug report
  980 +# Returns:
  981 +# nothing
  982 +flags_getoptInfo()
  983 +{
  984 + # platform info
  985 + _flags_debug "uname -a: `uname -a`"
  986 + _flags_debug "PATH: ${PATH}"
  987 +
  988 + # shell info
  989 + if [ -n "${BASH_VERSION:-}" ]; then
  990 + _flags_debug 'shell: bash'
  991 + _flags_debug "BASH_VERSION: ${BASH_VERSION}"
  992 + elif [ -n "${ZSH_VERSION:-}" ]; then
  993 + _flags_debug 'shell: zsh'
  994 + _flags_debug "ZSH_VERSION: ${ZSH_VERSION}"
  995 + fi
  996 +
  997 + # getopt info
  998 + ${FLAGS_GETOPT_CMD} >/dev/null
  999 + _flags_getoptReturn=$?
  1000 + _flags_debug "getopt return: ${_flags_getoptReturn}"
  1001 + _flags_debug "getopt --version: `${FLAGS_GETOPT_CMD} --version 2>&1`"
  1002 +
  1003 + unset _flags_getoptReturn
  1004 +}
  1005 +
  1006 +# Returns whether the detected getopt version is the enhanced version.
  1007 +#
  1008 +# Args:
  1009 +# none
  1010 +# Output:
  1011 +# none
  1012 +# Returns:
  1013 +# bool: true if getopt is the enhanced version
  1014 +flags_getoptIsEnh()
  1015 +{
  1016 + test ${__FLAGS_GETOPT_VERS} -eq ${__FLAGS_GETOPT_VERS_ENH}
  1017 +}
  1018 +
  1019 +# Returns whether the detected getopt version is the standard version.
  1020 +#
  1021 +# Args:
  1022 +# none
  1023 +# Returns:
  1024 +# bool: true if getopt is the standard version
  1025 +flags_getoptIsStd()
  1026 +{
  1027 + test ${__FLAGS_GETOPT_VERS} -eq ${__FLAGS_GETOPT_VERS_STD}
  1028 +}
  1029 +
  1030 +# This is effectively a 'usage()' function. It prints usage information and
  1031 +# exits the program with ${FLAGS_FALSE} if it is ever found in the command line
  1032 +# arguments. Note this function can be overridden so other apps can define
  1033 +# their own --help flag, replacing this one, if they want.
  1034 +#
  1035 +# Args:
  1036 +# none
  1037 +# Returns:
  1038 +# integer: success of operation (always returns true)
  1039 +flags_help()
  1040 +{
  1041 + if [ -n "${FLAGS_HELP:-}" ]; then
  1042 + echo "${FLAGS_HELP}" >&2
  1043 + else
  1044 + echo "USAGE: ${FLAGS_PARENT:-$0} [flags] args" >&2
  1045 + fi
  1046 + if [ -n "${__flags_longNames}" ]; then
  1047 + echo 'flags:' >&2
  1048 + for flags_name_ in ${__flags_longNames}; do
  1049 + flags_flagStr_=''
  1050 + flags_boolStr_=''
  1051 + flags_usName_=`_flags_underscoreName ${flags_name_}`
  1052 +
  1053 + flags_default_=`_flags_getFlagInfo \
  1054 + "${flags_usName_}" ${__FLAGS_INFO_DEFAULT}`
  1055 + flags_help_=`_flags_getFlagInfo \
  1056 + "${flags_usName_}" ${__FLAGS_INFO_HELP}`
  1057 + flags_short_=`_flags_getFlagInfo \
  1058 + "${flags_usName_}" ${__FLAGS_INFO_SHORT}`
  1059 + flags_type_=`_flags_getFlagInfo \
  1060 + "${flags_usName_}" ${__FLAGS_INFO_TYPE}`
  1061 +
  1062 + [ "${flags_short_}" != "${__FLAGS_NULL}" ] && \
  1063 + flags_flagStr_="-${flags_short_}"
  1064 +
  1065 + if [ ${__FLAGS_GETOPT_VERS} -eq ${__FLAGS_GETOPT_VERS_ENH} ]; then
  1066 + [ "${flags_short_}" != "${__FLAGS_NULL}" ] && \
  1067 + flags_flagStr_="${flags_flagStr_},"
  1068 + # add [no] to long boolean flag names, except the 'help' flag
  1069 + [ ${flags_type_} -eq ${__FLAGS_TYPE_BOOLEAN} \
  1070 + -a "${flags_usName_}" != 'help' ] && \
  1071 + flags_boolStr_='[no]'
  1072 + flags_flagStr_="${flags_flagStr_}--${flags_boolStr_}${flags_name_}:"
  1073 + fi
  1074 +
  1075 + case ${flags_type_} in
  1076 + ${__FLAGS_TYPE_BOOLEAN})
  1077 + if [ ${flags_default_} -eq ${FLAGS_TRUE} ]; then
  1078 + flags_defaultStr_='true'
  1079 + else
  1080 + flags_defaultStr_='false'
  1081 + fi
  1082 + ;;
  1083 + ${__FLAGS_TYPE_FLOAT}|${__FLAGS_TYPE_INTEGER})
  1084 + flags_defaultStr_=${flags_default_} ;;
  1085 + ${__FLAGS_TYPE_STRING}) flags_defaultStr_="'${flags_default_}'" ;;
  1086 + esac
  1087 + flags_defaultStr_="(default: ${flags_defaultStr_})"
  1088 +
  1089 + flags_helpStr_=" ${flags_flagStr_} ${flags_help_} ${flags_defaultStr_}"
  1090 + _flags_strlen "${flags_helpStr_}" >/dev/null
  1091 + flags_helpStrLen_=${flags_output}
  1092 + flags_columns_=`_flags_columns`
  1093 +
  1094 + if [ ${flags_helpStrLen_} -lt ${flags_columns_} ]; then
  1095 + echo "${flags_helpStr_}" >&2
  1096 + else
  1097 + echo " ${flags_flagStr_} ${flags_help_}" >&2
  1098 + # note: the silliness with the x's is purely for ksh93 on Ubuntu 6.06
  1099 + # because it doesn't like empty strings when used in this manner.
  1100 + flags_emptyStr_="`echo \"x${flags_flagStr_}x\" \
  1101 + |awk '{printf "%"length($0)-2"s", ""}'`"
  1102 + flags_helpStr_=" ${flags_emptyStr_} ${flags_defaultStr_}"
  1103 + _flags_strlen "${flags_helpStr_}" >/dev/null
  1104 + flags_helpStrLen_=${flags_output}
  1105 +
  1106 + if [ ${__FLAGS_GETOPT_VERS} -eq ${__FLAGS_GETOPT_VERS_STD} \
  1107 + -o ${flags_helpStrLen_} -lt ${flags_columns_} ]; then
  1108 + # indented to match help string
  1109 + echo "${flags_helpStr_}" >&2
  1110 + else
  1111 + # indented four from left to allow for longer defaults as long flag
  1112 + # names might be used too, making things too long
  1113 + echo " ${flags_defaultStr_}" >&2
  1114 + fi
  1115 + fi
  1116 + done
  1117 + fi
  1118 +
  1119 + unset flags_boolStr_ flags_default_ flags_defaultStr_ flags_emptyStr_ \
  1120 + flags_flagStr_ flags_help_ flags_helpStr flags_helpStrLen flags_name_ \
  1121 + flags_columns_ flags_short_ flags_type_ flags_usName_
  1122 + return ${FLAGS_TRUE}
  1123 +}
  1124 +
  1125 +# Reset shflags back to an uninitialized state.
  1126 +#
  1127 +# Args:
  1128 +# none
  1129 +# Returns:
  1130 +# nothing
  1131 +flags_reset()
  1132 +{
  1133 + for flags_name_ in ${__flags_longNames}; do
  1134 + flags_usName_=`_flags_underscoreName ${flags_name_}`
  1135 + flags_strToEval_="unset FLAGS_${flags_usName_}"
  1136 + for flags_type_ in \
  1137 + ${__FLAGS_INFO_DEFAULT} \
  1138 + ${__FLAGS_INFO_HELP} \
  1139 + ${__FLAGS_INFO_SHORT} \
  1140 + ${__FLAGS_INFO_TYPE}
  1141 + do
  1142 + flags_strToEval_=\
  1143 +"${flags_strToEval_} __flags_${flags_usName_}_${flags_type_}"
  1144 + done
  1145 + eval ${flags_strToEval_}
  1146 + done
  1147 +
  1148 + # reset internal variables
  1149 + __flags_boolNames=' '
  1150 + __flags_longNames=' '
  1151 + __flags_shortNames=' '
  1152 + __flags_definedNames=' '
  1153 +
  1154 + unset flags_name_ flags_type_ flags_strToEval_ flags_usName_
  1155 +}
tools/lib/utilities.sh 0 → 100755
  1 +#!/usr/bin/env bash
  2 +
  3 +# Any subsequent commands which fail will cause the shell script to exit immediately
  4 +# set -e
  5 +
  6 +# ANSI escape codes for message colouring
  7 +RED='\033[0;31m'
  8 +GREEN='\033[0;32m'
  9 +LIGHT_GREEN='\033[1;32m'
  10 +NC='\033[0m' # No Color
  11 +
  12 +printInfo () {
  13 + echo -e "${LIGHT_GREEN}${1}${NC}"
  14 +}
  15 +export -f printInfo
  16 +
  17 +printError () {
  18 + echo -e "${RED}${1}${NC}"
  19 +}
  20 +export -f printError
0 \ No newline at end of file 21 \ No newline at end of file