├── .github └── workflows │ └── msbuild.yml ├── .gitignore ├── LICENSE ├── MakeRelease.bat ├── README.md ├── TextTools.sln ├── inc ├── ArgParser.h ├── ClipboardText.h ├── CodeConvert.h ├── CodePageInfo.h ├── TextInput.h ├── TextOutput.h └── TextToolsCommon.h ├── lib ├── ArgParser.cpp ├── ByteOrderMark.cpp ├── ByteOrderMark.h ├── ClipboardText.cpp ├── CodeConvert.cpp ├── CodePageInfo.cpp ├── TextInput.cpp ├── TextOutput.cpp ├── TextToolsCommon.cpp ├── TextToolsLib.vcxproj ├── TextToolsLib.vcxproj.filters ├── Utility.h ├── pch.cpp └── pch.h ├── wargs ├── Program.cpp ├── TokenReader.cpp ├── TokenReader.h ├── WArgs.cpp ├── WArgs.h ├── WArgsContext.cpp ├── WArgsContext.h ├── pch.cpp ├── pch.h ├── wargs.vcxproj └── wargs.vcxproj.filters └── wconv ├── Program.cpp ├── WConv.cpp ├── WConv.h ├── pch.cpp ├── pch.h ├── wconv.vcxproj └── wconv.vcxproj.filters /.github/workflows/msbuild.yml: -------------------------------------------------------------------------------- 1 | name: MSBuild 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | # Path to the solution file relative to the root of the project. 11 | SOLUTION_FILE_PATH: . 12 | 13 | # Configuration type to build. 14 | # You can convert this to a build matrix if you need coverage of multiple configuration types. 15 | # https://docs.github.com/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix 16 | BUILD_CONFIGURATION: Release 17 | 18 | jobs: 19 | build: 20 | runs-on: windows-latest 21 | 22 | steps: 23 | - uses: actions/checkout@v2 24 | 25 | - name: Add MSBuild to PATH 26 | uses: microsoft/setup-msbuild@v1.0.2 27 | 28 | - name: Build 29 | working-directory: ${{env.GITHUB_WORKSPACE}} 30 | # Add additional options to the MSBuild command line here (like platform or verbosity level). 31 | # See https://docs.microsoft.com/visualstudio/msbuild/msbuild-command-line-reference 32 | run: msbuild /m /p:Configuration=${{env.BUILD_CONFIGURATION}} ${{env.SOLUTION_FILE_PATH}} 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.suo 2 | *.user 3 | [Oo]bjArm64/ 4 | [Oo]bjx64/ 5 | [Oo]bjWin32/ 6 | Release/ 7 | .vs/ 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Doug Cook 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MakeRelease.bat: -------------------------------------------------------------------------------- 1 | rd /s /q %~dp0objWin32 %~dp0objX64 %~dp0objArm64 %~dp0Release 2 | mkdir %~dp0Release 3 | msbuild %~dp0TextTools.sln -t:Rebuild -p:Configuration=Release;Platform=x86 && zip -j %~dp0Release\TextUtils-X86-32.zip %~dp0objWin32\Release\*.exe 4 | msbuild %~dp0TextTools.sln -t:Rebuild -p:Configuration=Release;Platform=x64 && zip -j %~dp0Release\TextUtils-X86-64.zip %~dp0objX64\Release\*.exe 5 | msbuild %~dp0TextTools.sln -t:Rebuild -p:Configuration=Release;Platform=arm64 && zip -j %~dp0Release\TextUtils-ARM-64.zip %~dp0objArm64\Release\*.exe 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TextTools 2 | Text processing tools for Windows - wargs (xargs for Windows), wconv (iconv for Windows) 3 | 4 | ## wargs - xargs for Windows 5 | 6 | wargs works like [xargs](https://www.man7.org/linux/man-pages/man1/xargs.1.html), but is 7 | coded using Win32 APIs and includes some Windows-specific options. 8 | 9 | - Option to specify the input text encoding. Text is converted to Unicode before 10 | parsing and constructing the command lines. 11 | - Option to launch commands in the background (don't wait for command to exit). 12 | - Option to read input from clipboard. 13 | - Supports up to 64 concurrent child commands. 14 | 15 | ## wconv - iconv for Windows 16 | 17 | wconv works like [iconv](https://man7.org/linux/man-pages/man1/iconv.1.html), but is 18 | coded using Win32 APIs and includes some Windows-specific options. 19 | 20 | wconv is based on TextToolsLib, which supports a streaming interface on top of the 21 | Win32 MultiByteToWideChar and WideCharToMultiByte APIs. As a result, it gains both 22 | advantages and disadvantages of the Win32 APIs. It natively supports all Windows 23 | SBCS and DBCS encodings in addition to UTF-8, UTF-16LE, UTF-16BE, UTF-32LE, and 24 | UTF-32BE. However, it does not support any of the more-complex MBCS encodings. 25 | 26 | ## TextToolsLib - streaming text encoding/decoding library 27 | 28 | - ArgParser.h - simple command-line argument parsing (getopt-style semantics). 29 | - CodeConvert.h - streaming conversion from SBCS/DBCS/UTF to UTF-16LE and from 30 | UTF-16LE to SBCS/DBCS/UTF. The SBCS, DBCS, and UTF-8 support is based on the 31 | Win32 MultiByteToWideChar and WideCharToMultiByte APIs. The UTF-16 and UTF-32 32 | support is hand-coded. Special support for correctly handling multi-byte 33 | characters that cross buffer boundaries. (Does not support more-complex MBCS 34 | encodings.) 35 | - CodePageInfo.h - simple class for getting properties for a code page. 36 | - TextInput.h - handles input from a pipe, file, console, or other source. 37 | Converts the input from a specified encoding to UTF-16LE using CodeConvert.h. 38 | - TextOutput.h - handles output to a pipe, file, console, or other destination. 39 | Converts the output from UTF-16LE to a specified encoding using 40 | CodeConvert.h. 41 | -------------------------------------------------------------------------------- /TextTools.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31903.59 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TextToolsLib", "lib\TextToolsLib.vcxproj", "{9EDD7581-DF10-4284-8418-D873D09EF4A2}" 7 | EndProject 8 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "wconv", "wconv\wconv.vcxproj", "{0B6BBA07-3611-4B20-9E12-9AC1C08C898C}" 9 | EndProject 10 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "wargs", "wargs\wargs.vcxproj", "{D21C1E8D-7B31-439D-9B50-CD943BA91DF1}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{62FE7155-E27D-477C-8CFE-E34879FEAD12}" 13 | ProjectSection(SolutionItems) = preProject 14 | README.md = README.md 15 | EndProjectSection 16 | EndProject 17 | Global 18 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 19 | Debug|ARM64 = Debug|ARM64 20 | Debug|x64 = Debug|x64 21 | Debug|x86 = Debug|x86 22 | Release|ARM64 = Release|ARM64 23 | Release|x64 = Release|x64 24 | Release|x86 = Release|x86 25 | EndGlobalSection 26 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 27 | {9EDD7581-DF10-4284-8418-D873D09EF4A2}.Debug|ARM64.ActiveCfg = Debug|ARM64 28 | {9EDD7581-DF10-4284-8418-D873D09EF4A2}.Debug|ARM64.Build.0 = Debug|ARM64 29 | {9EDD7581-DF10-4284-8418-D873D09EF4A2}.Debug|x64.ActiveCfg = Debug|x64 30 | {9EDD7581-DF10-4284-8418-D873D09EF4A2}.Debug|x64.Build.0 = Debug|x64 31 | {9EDD7581-DF10-4284-8418-D873D09EF4A2}.Debug|x86.ActiveCfg = Debug|Win32 32 | {9EDD7581-DF10-4284-8418-D873D09EF4A2}.Debug|x86.Build.0 = Debug|Win32 33 | {9EDD7581-DF10-4284-8418-D873D09EF4A2}.Release|ARM64.ActiveCfg = Release|ARM64 34 | {9EDD7581-DF10-4284-8418-D873D09EF4A2}.Release|ARM64.Build.0 = Release|ARM64 35 | {9EDD7581-DF10-4284-8418-D873D09EF4A2}.Release|x64.ActiveCfg = Release|x64 36 | {9EDD7581-DF10-4284-8418-D873D09EF4A2}.Release|x64.Build.0 = Release|x64 37 | {9EDD7581-DF10-4284-8418-D873D09EF4A2}.Release|x86.ActiveCfg = Release|Win32 38 | {9EDD7581-DF10-4284-8418-D873D09EF4A2}.Release|x86.Build.0 = Release|Win32 39 | {0B6BBA07-3611-4B20-9E12-9AC1C08C898C}.Debug|ARM64.ActiveCfg = Debug|ARM64 40 | {0B6BBA07-3611-4B20-9E12-9AC1C08C898C}.Debug|ARM64.Build.0 = Debug|ARM64 41 | {0B6BBA07-3611-4B20-9E12-9AC1C08C898C}.Debug|x64.ActiveCfg = Debug|x64 42 | {0B6BBA07-3611-4B20-9E12-9AC1C08C898C}.Debug|x64.Build.0 = Debug|x64 43 | {0B6BBA07-3611-4B20-9E12-9AC1C08C898C}.Debug|x86.ActiveCfg = Debug|Win32 44 | {0B6BBA07-3611-4B20-9E12-9AC1C08C898C}.Debug|x86.Build.0 = Debug|Win32 45 | {0B6BBA07-3611-4B20-9E12-9AC1C08C898C}.Release|ARM64.ActiveCfg = Release|ARM64 46 | {0B6BBA07-3611-4B20-9E12-9AC1C08C898C}.Release|ARM64.Build.0 = Release|ARM64 47 | {0B6BBA07-3611-4B20-9E12-9AC1C08C898C}.Release|x64.ActiveCfg = Release|x64 48 | {0B6BBA07-3611-4B20-9E12-9AC1C08C898C}.Release|x64.Build.0 = Release|x64 49 | {0B6BBA07-3611-4B20-9E12-9AC1C08C898C}.Release|x86.ActiveCfg = Release|Win32 50 | {0B6BBA07-3611-4B20-9E12-9AC1C08C898C}.Release|x86.Build.0 = Release|Win32 51 | {D21C1E8D-7B31-439D-9B50-CD943BA91DF1}.Debug|ARM64.ActiveCfg = Debug|ARM64 52 | {D21C1E8D-7B31-439D-9B50-CD943BA91DF1}.Debug|ARM64.Build.0 = Debug|ARM64 53 | {D21C1E8D-7B31-439D-9B50-CD943BA91DF1}.Debug|x64.ActiveCfg = Debug|x64 54 | {D21C1E8D-7B31-439D-9B50-CD943BA91DF1}.Debug|x64.Build.0 = Debug|x64 55 | {D21C1E8D-7B31-439D-9B50-CD943BA91DF1}.Debug|x86.ActiveCfg = Debug|Win32 56 | {D21C1E8D-7B31-439D-9B50-CD943BA91DF1}.Debug|x86.Build.0 = Debug|Win32 57 | {D21C1E8D-7B31-439D-9B50-CD943BA91DF1}.Release|ARM64.ActiveCfg = Release|ARM64 58 | {D21C1E8D-7B31-439D-9B50-CD943BA91DF1}.Release|ARM64.Build.0 = Release|ARM64 59 | {D21C1E8D-7B31-439D-9B50-CD943BA91DF1}.Release|x64.ActiveCfg = Release|x64 60 | {D21C1E8D-7B31-439D-9B50-CD943BA91DF1}.Release|x64.Build.0 = Release|x64 61 | {D21C1E8D-7B31-439D-9B50-CD943BA91DF1}.Release|x86.ActiveCfg = Release|Win32 62 | {D21C1E8D-7B31-439D-9B50-CD943BA91DF1}.Release|x86.Build.0 = Release|Win32 63 | EndGlobalSection 64 | GlobalSection(SolutionProperties) = preSolution 65 | HideSolutionNode = FALSE 66 | EndGlobalSection 67 | GlobalSection(ExtensibilityGlobals) = postSolution 68 | SolutionGuid = {96B6E6C5-2186-4624-B535-3493BD3078C8} 69 | EndGlobalSection 70 | EndGlobal 71 | -------------------------------------------------------------------------------- /inc/ArgParser.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Doug Cook. 2 | // Licensed under the MIT License. 3 | 4 | #pragma once 5 | #include 6 | 7 | /* 8 | Parser for argument lists that follow getopt-style rules. 9 | 10 | Typical usage: 11 | 12 | ArgParser ap("MyApp", argc, argv); 13 | while (ap.MoveNextArg()) 14 | { 15 | if (ap.BeginDashDashArg()) 16 | { 17 | if (ap.CurrentArgIsEmpty()) // i.e. just "--" 18 | { 19 | ... 20 | } 21 | // Need 2 characters to distinguish "sandbox" from "separate". 22 | else if (ap.CurrentArgNameMatches(2, L"sandbox")) 23 | { 24 | argSandbox = true; 25 | } 26 | // Need 2 characters to distinguish "sandbox" from "separate". 27 | else if (ap.CurrentArgNameMatches(2, "separate)) 28 | { 29 | if (!ap.GetLongArgVal(argSeparate, true)) { error... } 30 | } 31 | else 32 | { 33 | ap.PrintLongArgError(); 34 | } 35 | } 36 | else if (ap.BeginDashOrSlashArg()) 37 | { 38 | if (ap.CurrentArgIsEmpty()) // i.e. just "-" or "/" 39 | { 40 | ... 41 | } 42 | else while (ap.MoveNextArgChar()) 43 | { 44 | switch (ap.CurrentArgChar()) 45 | { 46 | case 'a': 47 | argA = true; 48 | break; 49 | case 'b': 50 | if (!ap.ReadShortArgVal(argB, true)) { error... } 51 | break; 52 | default: 53 | ap.PrintShortArgError(); 54 | break; 55 | } 56 | } 57 | } 58 | else 59 | { 60 | fileArgs.push_back(ap.CurrentArg()); 61 | } 62 | } 63 | */ 64 | class ArgParser 65 | { 66 | PCSTR const m_appName; 67 | _Field_size_(m_argCount) PWSTR const* const m_args; 68 | unsigned const m_argCount; 69 | unsigned m_currentArgIndex; 70 | PCWSTR m_currentArgPos; 71 | bool m_argError; 72 | 73 | private: 74 | 75 | bool 76 | ReadArgValImpl( 77 | std::wstring_view& val, 78 | bool emptyOk, 79 | _In_opt_z_ PCWSTR shortArgVal, 80 | WCHAR argChar, 81 | bool space) noexcept; 82 | 83 | bool 84 | ReadArgValImpl( 85 | unsigned& val, 86 | bool zeroOk, 87 | int radix, 88 | _In_opt_z_ PCWSTR shortArgVal, 89 | WCHAR argChar, 90 | bool space) noexcept; 91 | 92 | public: 93 | 94 | /* 95 | Sets AppName=appName, Args=argv, ArgCount=argc, 96 | CurrentArg=argv[0], CurrentArgPos="", ArgError=false. 97 | */ 98 | ArgParser( 99 | _In_z_ PCSTR appName, // Used in error messages. 100 | unsigned argc, 101 | _In_count_(argc) PWSTR argv[]) noexcept; 102 | 103 | // Returns AppName. 104 | _Ret_z_ PCSTR 105 | AppName() const noexcept; 106 | 107 | // Returns Args[index]. 108 | _Ret_z_ PCWSTR 109 | Arg(unsigned index) const noexcept; 110 | 111 | // Returns ArgCount. 112 | unsigned 113 | ArgCount() const noexcept; 114 | 115 | // Returns CurrentArgIndex. 116 | unsigned 117 | CurrentArgIndex() const noexcept; 118 | 119 | // Returns Args[CurrentArgIndex]. 120 | _Ret_z_ PCWSTR 121 | CurrentArg() const noexcept; 122 | 123 | // Returns CurrentArgPos. 124 | _Ret_z_ PCWSTR 125 | CurrentArgPos() const noexcept; 126 | 127 | // Returns CurrentArgPos[0]. 128 | WCHAR 129 | CurrentArgChar() const noexcept; 130 | 131 | // Returns CurrentArgPos[1] == 0. 132 | bool 133 | CurrentArgIsEmpty() const noexcept; 134 | 135 | // Returns string from CurrentArgPos[1] to first of ':', '=', '\0'. 136 | std::wstring_view 137 | CurrentArgName() const noexcept; 138 | 139 | // Returs true if CurrentArgName is at least minMatchLength characters and 140 | // is a prefix of expectedArgName. 141 | bool 142 | CurrentArgNameMatches(unsigned minMatchLength, PCWSTR expectedName) const noexcept; 143 | 144 | // Returns ArgError. 145 | bool 146 | ArgError() const noexcept; 147 | 148 | // Sets ArgError = value. 149 | void 150 | SetArgError(bool value = true) noexcept; 151 | 152 | // Sets ArgError = ArgError || !argOk. 153 | void 154 | SetArgErrorIfFalse(bool argOk) noexcept; 155 | 156 | // Sets ArgError=true and prints 157 | // "AppName: error : Unrecognized argument '-CurrentArgChar'". 158 | void 159 | PrintShortArgError() noexcept; 160 | 161 | // Sets ArgError=true and prints 162 | // "AppName: error : Unrecognized argument 'CurrentArg'". 163 | void 164 | PrintLongArgError() noexcept; 165 | 166 | // Sets CurrentArgIndex += 1, CurrentArgPos = "". 167 | // Returns true if CurrentArgIndex < ArgCount. 168 | bool 169 | MoveNextArg() noexcept; 170 | 171 | // If CurrentArg starts with "--", sets CurrentArgPos to before-begin and 172 | // returns true. 173 | bool 174 | BeginDashDashArg() noexcept; 175 | 176 | // If CurrentArg starts with "-" or "/", sets CurrentArgPos to before-begin 177 | // and returns true. 178 | bool 179 | BeginDashOrSlashArg() noexcept; 180 | 181 | // If CurrentArg starts with "-", sets CurrentArgPos to before-begin and 182 | // returns true. 183 | bool 184 | BeginDashArg() noexcept; 185 | 186 | // If CurrentArg starts with "/", sets CurrentArgPos to before-begin and 187 | // returns true. 188 | bool 189 | BeginSlashArg() noexcept; 190 | 191 | // Sets CurrentArgPos += 1. Returns true if CurrentArgPos < End. 192 | bool 193 | MoveNextArgChar() noexcept; 194 | 195 | // Consumes and returns the remainder of the current short arg. 196 | // Example: "-ab123", if current ArgChar is 'b', consumes and returns "123". 197 | _Ret_z_ PCWSTR 198 | ReadArgCharsVal() noexcept; 199 | 200 | // Consumes and returns the value of next arg, null if none. 201 | // Example: "-abc 123", if current ArgChar is 'b', consumes and returns "123". 202 | _Ret_opt_z_ PCWSTR 203 | ReadNextArgVal() noexcept; 204 | 205 | // Consumes and returns the value of the current short arg, null if none. 206 | // This selects between behavior of ReadArgCharsVal() and ReadNextArgVal(), 207 | // depending on whether we're at the end of the current short arg. 208 | // Example: "-abc 123", if current ArgChar is 'b', consumes and returns "c". 209 | // Example: "-abc 123", if current ArgChar is 'c', consumes and returns "123". 210 | _Ret_opt_z_ PCWSTR 211 | ReadShortArgVal() noexcept; 212 | 213 | // Returns the value of the current long arg, null if none. 214 | _Ret_opt_z_ PCWSTR 215 | GetLongArgVal() const noexcept; 216 | 217 | // Consumes and returns the remainder of the current short arg. Prints error 218 | // message if none. 219 | // 220 | // Example: "-ab123", if current ArgChar is 'b', consumes and returns "123". 221 | bool 222 | ReadArgCharsVal(std::wstring_view& val, bool emptyOk) noexcept; 223 | 224 | // Consumes and returns the value of next arg. Prints error 225 | // message and returns false if none. 226 | // 227 | // Example: "-abc 123", if current ArgChar is 'b', consumes and returns "123". 228 | bool 229 | ReadNextArgVal(std::wstring_view& val, bool emptyOk) noexcept; 230 | 231 | // Consumes and returns the value of the current short arg, error if invalid. 232 | // This selects between behavior of ReadArgCharsVal() and ReadNextArgVal(), 233 | // depending on whether we're at the end of the current short arg. 234 | // Example: "-abc 123", if current ArgChar is 'b', consumes and returns "c". 235 | // Example: "-abc 123", if current ArgChar is 'c', consumes and returns "123". 236 | bool 237 | ReadShortArgVal(std::wstring_view& val, bool emptyOk) noexcept; 238 | 239 | // Returns the value of the current long arg. Prints error message if none. 240 | bool 241 | GetLongArgVal(std::wstring_view& val, bool emptyOk) noexcept; 242 | 243 | // Consumes and returns the remainder of the current short arg. Prints error 244 | // message if none. 245 | // 246 | // Example: "-ab123", if current ArgChar is 'b', consumes and returns "123". 247 | bool 248 | ReadArgCharsVal(unsigned& val, bool zeroOk, int radix = 0) noexcept; 249 | 250 | // Consumes and returns the value of next arg. Prints error message 251 | // if none or invalid. 252 | // 253 | // Example: "-abc 123", if current ArgChar is 'b', consumes and returns "123". 254 | bool 255 | ReadNextArgVal(unsigned& val, bool zeroOk, int radix = 0) noexcept; 256 | 257 | // Consumes and returns the value of the current short arg, error if invalid. 258 | // This selects between behavior of ReadArgCharsVal() and ReadNextArgVal(), 259 | // depending on whether we're at the end of the current short arg. 260 | // Example: "-abc 123", if current ArgChar is 'b', consumes and returns "c". 261 | // Example: "-abc 123", if current ArgChar is 'c', consumes and returns "123". 262 | bool 263 | ReadShortArgVal(unsigned& val, bool zeroOk, int radix = 0) noexcept; 264 | 265 | // Returns the value of the current long arg. Prints error message if none 266 | // or invalid. 267 | bool 268 | GetLongArgVal(unsigned& val, bool zeroOk, int radix = 0) noexcept; 269 | }; 270 | -------------------------------------------------------------------------------- /inc/ClipboardText.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Doug Cook. 2 | // Licensed under the MIT License. 3 | 4 | #pragma once 5 | 6 | LSTATUS 7 | ClipboardTextGet(std::wstring& value); 8 | 9 | LSTATUS 10 | ClipboardTextSet(std::wstring_view value); 11 | -------------------------------------------------------------------------------- /inc/CodeConvert.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Doug Cook. 2 | // Licensed under the MIT License. 3 | 4 | #pragma once 5 | #include 6 | #include 7 | 8 | enum class CodePageCategory : UINT8; 9 | struct CodePageInfo; 10 | 11 | /* 12 | Streaming conversion between encoded character data and UTF-16. 13 | Supports UTF-8, UTF-16, UTF-32, and SBCS/DBCS Windows code pages. 14 | Best-effort support for more complex Windows code pages. 15 | */ 16 | class CodeConvert 17 | { 18 | unsigned m_codePage; 19 | 20 | // Uses GetCPInfoExW to resolve CP_MACCP or CP_THREAD_ACP into a normal 21 | // code page. 22 | static unsigned 23 | ResolveCodePage(unsigned codePage) noexcept; 24 | 25 | public: 26 | 27 | /* 28 | Returns true if the specified code page category is likely to work well 29 | with this class. This will return true for Sbcs, Dbcs, or Utf. It will 30 | return false for other categories because incorrect conversions will occur 31 | when a chunk of input ends in the middle of a character byte sequence. 32 | */ 33 | static bool 34 | SupportsCategory(CodePageCategory category) noexcept; 35 | 36 | /* 37 | Returns true if the specified code page is likely to work well with this 38 | class. This will return true for SBCS, DBCS, or UTF code pages. It will 39 | return false for other categories because incorrect conversions will occur 40 | when a chunk of input ends in the middle of a character byte sequence. 41 | */ 42 | static bool 43 | SupportsCodePage(unsigned codePage) noexcept; 44 | 45 | /* 46 | Returns true if the specified code page is likely to work well with this 47 | class. This will return true for code pages where info.Category is Sbcs, 48 | Dbcs, or Utf. It will return false for other categories because incorrect 49 | conversions will occur when a chunk of input ends in the middle of a 50 | character byte sequence. 51 | */ 52 | static bool 53 | SupportsCodePage(CodePageInfo const& info) noexcept; 54 | 55 | /* 56 | Initializes a CodeConvert that uses UTF-8. 57 | */ 58 | constexpr 59 | CodeConvert() noexcept 60 | : m_codePage(CP_UTF8) {} 61 | 62 | /* 63 | Initializes a CodeConvert that uses the encoding given by the specified Windows 64 | Code Page identifier. If codePage is a special value (CP_ACP, CP_OEMCP, CP_MACCP, 65 | or CP_THREAD_ACP), resolves the provided special value into the normal Windows 66 | Code Page identifier corresponding to the special value. Use the CodePageUtfXXX 67 | constants for UTF-8, UTF-16 and UTF-32 encodings. 68 | */ 69 | explicit constexpr 70 | CodeConvert(unsigned codePage) noexcept 71 | : m_codePage( 72 | codePage > CP_THREAD_ACP ? codePage 73 | : codePage == CP_ACP ? GetACP() 74 | : codePage == CP_OEMCP ? GetOEMCP() 75 | : ResolveCodePage(codePage)) {} 76 | 77 | /* 78 | Initializes a CodeConvert that uses the encoding given by info.CodePage. 79 | */ 80 | explicit 81 | CodeConvert(CodePageInfo const& info) noexcept; 82 | 83 | /* 84 | Returns the encoding used by this CodeConvert, specified as a Windows Code Page 85 | identifier. If this object was constructed using a special code page value (CP_ACP, 86 | CP_OEMCP, CP_MACCP, or CP_THREAD_ACP), this returns the resolved value instead of 87 | the special value. 88 | */ 89 | constexpr unsigned 90 | CodePage() const noexcept 91 | { 92 | return m_codePage; 93 | } 94 | 95 | /* 96 | If !SupportsCodePage(CodePage()), throws std::runtime_error. 97 | Otherwise, returns code page category. 98 | */ 99 | CodePageCategory 100 | ThrowIfNotSupported() const; 101 | 102 | /* 103 | Converts a chunk of encoded input to UTF-16 output and appends it to utf16Output. 104 | - Requires: encodedInputPos <= encodedInput.size(). 105 | - Requires: utf16OutputPos <= utf16Output.size(). 106 | - Conversion starts at encodedInput[encodedInputPos]. 107 | - Conversion consumes as much of encodedInput as possible. It may stop before the 108 | end if encodedInput ends in the middle of a character, e.g. on a DBCS lead byte. 109 | - encodedInputPos will be updated to reflect the position of the next unconsumed 110 | input, or will be set to encodedInput.size() if all input was consumed. 111 | - Output is written starting at utf16Output[utf16OutputPos]. 112 | - utf16Output will be resized as necessary to store the output. 113 | - utf16OutputPos will be updated to reflect the used output size. 114 | - May throw in case of out-of-memory (when utf16Output is resized). 115 | - Returns ERROR_SUCCESS or any error returned by MultiByteToWideChar. 116 | */ 117 | LSTATUS 118 | EncodedToUtf16( 119 | std::string_view encodedInput, 120 | size_t& encodedInputPos, // <= encodedInput.size() 121 | std::u16string& utf16Output, 122 | size_t& utf16OutputPos, // <= utf16Output.size() 123 | unsigned mb2wcFlags = 0) const; 124 | 125 | /* 126 | Converts a chunk of UTF-16 input to encoded output and appends it to encodedOutput. 127 | - Requires: utf16InputPos <= utf16Input.size(). 128 | - Requires: encodedOutputPos <= encodedOutput.size(). 129 | - Requires: Flags are valid for the encoding. 130 | - Requires: If encoding is UTF, pDefaultChar and pUsedDefaultChar must be null. 131 | - Conversion starts at utf16Input[utf16InputPos]. 132 | - Conversion consumes as much of utf16Input as possible. It will stop before the end 133 | if utf16Input ends with a high surrogate. 134 | - utf16InputPos will be updated to reflect the position of the next unconsumed input, 135 | or will be set to utf16Input.size() if all input was consumed. 136 | - Output is written starting at encodedOutput[encodedOutputPos]. 137 | - encodedOutput will be resized as necessary to store the output. 138 | - encodedOutputPos will be updated to reflect the used output size. 139 | - May throw in case of out-of-memory (when encodedOutput is resized). 140 | - If pUsedDefaultChar != null and default char is used, sets *pUsedDefaultChar = true. 141 | - Returns ERROR_SUCCESS or any error returned by WideCharToMultiByte. 142 | */ 143 | LSTATUS 144 | Utf16ToEncoded( 145 | std::u16string_view utf16Input, 146 | size_t& utf16InputPos, // <= utf16Input.size() 147 | std::string& encodedOutput, 148 | size_t& encodedOutputPos, // <= encodedOutput.size() 149 | unsigned wc2mbFlags = 0, 150 | _In_opt_ PCCH pDefaultChar = nullptr, 151 | _Inout_opt_ bool* pUsedDefaultChar = nullptr) const; 152 | }; 153 | -------------------------------------------------------------------------------- /inc/CodePageInfo.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Doug Cook. 2 | // Licensed under the MIT License. 3 | 4 | #pragma once 5 | #include 6 | 7 | constexpr unsigned CodePageUtf8 = CP_UTF8; 8 | constexpr unsigned CodePageUtf16LE = 1200; 9 | constexpr unsigned CodePageUtf16BE = 1201; 10 | constexpr unsigned CodePageUtf32LE = 12000; 11 | constexpr unsigned CodePageUtf32BE = 12001; 12 | 13 | enum class CodePageCategory : UINT8 14 | { 15 | None, // Invalid value. 16 | Error, // Error returned by GetCPInfoExA. 17 | Sbcs, // Single-byte character set. 18 | Dbcs, // Up-to-2-byte character set with lead-byte ranges. 19 | Complex, // Not SBCS, DBCS, or UTF. 20 | Utf, 21 | }; 22 | 23 | struct CodePageArg 24 | { 25 | unsigned CodePage; 26 | bool BomSuffix; 27 | CodePageCategory ParseResult; 28 | 29 | constexpr 30 | CodePageArg() noexcept 31 | : CodePage() 32 | , BomSuffix() 33 | , ParseResult() {} 34 | 35 | /* 36 | Parse code page. Expected format is: 37 | (NNNN|cpNNNN|utf8|utf16[be|le]|utf32[be|le])[bom] 38 | ParseResult will be Error, Utf, or None (parsed by number, may or may not be valid). 39 | */ 40 | explicit 41 | CodePageArg(std::wstring_view arg) noexcept; 42 | }; 43 | 44 | struct CodePageInfo 45 | : CPINFOEXW 46 | { 47 | unsigned UnresolvedCodePage; 48 | CodePageCategory Category; 49 | 50 | explicit 51 | CodePageInfo(unsigned codePage) noexcept; 52 | 53 | std::string 54 | Name() const; 55 | }; 56 | -------------------------------------------------------------------------------- /inc/TextInput.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Doug Cook. 2 | // Licensed under the MIT License. 3 | 4 | #pragma once 5 | #include "TextToolsCommon.h" 6 | #include "CodeConvert.h" 7 | #include 8 | #include 9 | #include 10 | 11 | enum class TextInputFlags : uint8_t 12 | { 13 | None = 0, 14 | FoldCRLF = 0x01, // Convert CRLF or CR to LF. 15 | ConsumeBom = 0x02, // If input starts with BOM, consume BOM and override codepage. 16 | InvalidMbcsError = 0x04, // Use MB_ERR_INVALID_CHARS in conversion. 17 | CheckConsole = 0x10, // If input is a console, use ReadConsoleW and override codepage. 18 | ConsoleCtrlZ = 0x20, // If using ReadConsoleW, Read() returns immediately for Ctrl-Z. 19 | Default = InvalidMbcsError | CheckConsole | ConsoleCtrlZ 20 | }; 21 | DEFINE_ENUM_FLAG_OPERATORS(TextInputFlags); 22 | 23 | enum class TextInputMode : uint8_t 24 | { 25 | None, 26 | Chars, 27 | Bytes, 28 | File, 29 | Console, 30 | }; 31 | 32 | class TextInput 33 | { 34 | std::string m_bytes; 35 | std::u16string m_chars; 36 | 37 | TextToolsUniqueHandle m_inputOwner; 38 | HANDLE m_inputHandle; 39 | CodeConvert m_codeConvert; 40 | TextInputMode m_mode; 41 | TextInputFlags m_flags; 42 | 43 | bool m_skipNextCharIfNewline; 44 | size_t m_bytesPos; 45 | size_t m_charsPos; 46 | 47 | constexpr bool 48 | IsFlagSet(TextInputFlags flag) const noexcept; 49 | 50 | void 51 | SetCodeConvert(unsigned codePage); 52 | 53 | void 54 | FoldCRLF() noexcept; 55 | 56 | void 57 | ConsumeBytes(size_t consumedBytes) noexcept; 58 | 59 | /* 60 | Clears m_chars. Fills m_chars from m_bytes. Calls FoldCRLF(). 61 | Throws for conversion failure. 62 | */ 63 | void 64 | Convert(); 65 | 66 | void 67 | ReadBytesFromFile(); 68 | 69 | void 70 | ReadBytesFromFile(DWORD cbMaxToRead); 71 | 72 | void 73 | ReadCharsFromConsole(); 74 | 75 | void 76 | OpenHandle( 77 | TextToolsUniqueHandle inputOwner, 78 | _In_ HANDLE inputHandle, 79 | unsigned codePage, 80 | TextInputFlags flags); 81 | 82 | public: 83 | 84 | TextInput() noexcept; 85 | 86 | /* 87 | Closes any existing input. 88 | */ 89 | void 90 | Close() noexcept; 91 | 92 | /* 93 | Gets the current mode of operation. 94 | */ 95 | TextInputMode 96 | Mode() const noexcept; 97 | 98 | /* 99 | Closes any existing input, then copies inputChars to the Chars() buffer. 100 | */ 101 | void 102 | OpenChars( 103 | std::u16string_view inputChars, 104 | TextInputFlags flags = TextInputFlags::Default); 105 | 106 | /* 107 | Closes any existing input, then converts inputBytes to UTF16 and stores the 108 | result in the Chars() buffer. 109 | */ 110 | void 111 | OpenBytes( 112 | std::string_view inputBytes, 113 | unsigned codePage = CP_ACP, 114 | TextInputFlags flags = TextInputFlags::Default); 115 | 116 | /* 117 | Closes any existing input. Sets up to read from the input file. Reads and 118 | converts an initial chunk of input. 119 | */ 120 | void 121 | OpenBorrowedHandle( 122 | _In_ HANDLE inputHandle, 123 | unsigned codePage = CP_ACP, 124 | TextInputFlags flags = TextInputFlags::Default); 125 | 126 | /* 127 | Opens the specified file. If successful, closes any existing input, sets up 128 | to read from the input file, and reads and converts an initial chunk of 129 | input. 130 | */ 131 | LSTATUS 132 | OpenFile( 133 | _In_ PCWSTR inputFile, 134 | unsigned codePage = CP_ACP, 135 | TextInputFlags flags = TextInputFlags::Default); 136 | 137 | /* 138 | Gets text from the clipboard. If successful, closes any existing input and 139 | copies the clipboard text to the Chars() buffer. 140 | (Implemented in ClipboardText.cpp.) 141 | */ 142 | LSTATUS 143 | OpenClipboard( 144 | TextInputFlags flags = TextInputFlags::Default); 145 | 146 | /* 147 | Gets the currently-available UTF-16LE characters. 148 | */ 149 | std::u16string_view 150 | Chars() const noexcept; 151 | 152 | /* 153 | Clears the Chars() buffer, then loads more from the input source. 154 | If no more input is available (e.g. end-of-file), returns false. 155 | */ 156 | bool 157 | ReadNextChars(); 158 | }; 159 | -------------------------------------------------------------------------------- /inc/TextOutput.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Doug Cook. 2 | // Licensed under the MIT License. 3 | 4 | #pragma once 5 | #include "TextToolsCommon.h" 6 | #include "CodeConvert.h" 7 | #include 8 | #include 9 | #include 10 | 11 | enum class TextOutputFlags : uint8_t 12 | { 13 | None = 0, 14 | ExpandCRLF = 0x01, // Convert LF to CRLF. 15 | InsertBom = 0x02, // If encoding is UTF, insert a BOM at start of output. 16 | InvalidUtf16Error = 0x04, // Use WC_ERR_INVALID_CHARS in conversion (affects UTF output only). 17 | NoBestFitChars = 0x08, // Use WC_NO_BEST_FIT_CHARS (affects non-UTF output only). 18 | CheckConsole = 0x10, // If output is a console, use WriteConsoleW and override codepage. 19 | Default = InvalidUtf16Error | NoBestFitChars | CheckConsole 20 | }; 21 | DEFINE_ENUM_FLAG_OPERATORS(TextOutputFlags); 22 | 23 | enum class TextOutputMode : uint8_t 24 | { 25 | None, 26 | Chars, 27 | Bytes, 28 | File, 29 | Console, 30 | }; 31 | 32 | class TextOutput 33 | { 34 | std::string m_bytes; 35 | std::u16string m_chars; 36 | 37 | TextToolsUniqueHandle m_outputOwner; 38 | HANDLE m_outputHandle; 39 | CodeConvert m_codeConvert; 40 | bool m_codeConvertUtf; 41 | TextOutputMode m_mode; 42 | TextOutputFlags m_flags; 43 | unsigned m_wc2mbFlags; 44 | 45 | size_t m_bytesPos; 46 | size_t m_charsPos; 47 | 48 | constexpr bool 49 | IsFlagSet(TextOutputFlags flag) const noexcept; 50 | 51 | void 52 | SetCodeConvert(unsigned codePage); 53 | 54 | // Sets m_flags, then updates m_wc2mbFlags. 55 | void 56 | SetFlags(TextOutputFlags flags) noexcept; 57 | 58 | // Inserts BOM (or not) based on m_codeConvertUtf and m_flags. 59 | void 60 | InsertBom(); 61 | 62 | void 63 | FlushFile(); 64 | 65 | void 66 | FlushConsole(std::u16string_view pendingChars); 67 | 68 | /* 69 | Converts pendingChars to bytes, appends result to m_bytes. 70 | Calls SaveRemainingChars if needed (e.g. trailing high surrogate). 71 | */ 72 | void 73 | ConvertAndAppendBytes( 74 | std::u16string_view pendingChars, 75 | _In_opt_ PCCH pDefaultChar, 76 | _Inout_opt_ bool* pUsedDefaultChar); 77 | 78 | void 79 | SaveRemainingChars(std::u16string_view pendingChars, size_t pendingCharsPos); 80 | 81 | /* 82 | Appends newChars to pending chars, then expands CRLF, 83 | then returns the result, clearing pending chars to empty. 84 | */ 85 | std::u16string_view 86 | ConsumePendingChars(std::u16string_view newChars); 87 | 88 | void 89 | AppendCharsAndExpandCRLF(std::u16string_view newChars); 90 | 91 | void 92 | AppendChars(std::u16string_view newChars); 93 | 94 | void 95 | OpenHandle( 96 | TextToolsUniqueHandle outputOwner, 97 | _In_ HANDLE outputHandle, 98 | unsigned codePage, 99 | TextOutputFlags flags); 100 | 101 | public: 102 | 103 | /* 104 | Flushes and closes any existing output. 105 | */ 106 | ~TextOutput(); 107 | 108 | TextOutput() noexcept; 109 | 110 | /* 111 | Writes any buffered bytes to file/console (as appropriate for Mode). 112 | */ 113 | void 114 | Flush(); 115 | 116 | /* 117 | Flushes and closes any existing output. 118 | */ 119 | void 120 | Close() noexcept; 121 | 122 | /* 123 | Gets the current mode of operation. 124 | */ 125 | TextOutputMode 126 | Mode() const noexcept; 127 | 128 | /* 129 | Flushes and closes any existing output, then opens with Mode = Chars 130 | (buffer chars in-memory). 131 | */ 132 | void 133 | OpenChars( 134 | TextOutputFlags flags = TextOutputFlags::Default); 135 | 136 | /* 137 | Flushes and closes any existing output, then opens with Mode = Bytes 138 | (convert and buffer bytes in-memory). 139 | */ 140 | void 141 | OpenBytes( 142 | unsigned codePage = CP_ACP, 143 | TextOutputFlags flags = TextOutputFlags::Default); 144 | 145 | /* 146 | Flushes and closes any existing output, then opens with Mode = File/Console 147 | (convert and write bytes to file/console). 148 | */ 149 | void 150 | OpenBorrowedHandle( 151 | _In_ HANDLE outputHandle, 152 | unsigned codePage = CP_ACP, 153 | TextOutputFlags flags = TextOutputFlags::Default); 154 | 155 | /* 156 | Opens the specified file. If successful, flushes and closes any existing 157 | output, then opens with Mode = File/Console (convert and write bytes to 158 | file/console). 159 | */ 160 | LSTATUS 161 | OpenFile( 162 | _In_ PCWSTR outputFile, 163 | unsigned codePage = CP_ACP, 164 | TextOutputFlags flags = TextOutputFlags::Default); 165 | 166 | /* 167 | Gets buffered in-memory chars. Valid only if Mode == Chars. 168 | */ 169 | std::u16string_view 170 | BufferedChars() const; 171 | 172 | /* 173 | Gets buffered in-memory bytes. Valid only if Mode == Bytes. 174 | */ 175 | std::string_view 176 | BufferedBytes() const; 177 | 178 | /* 179 | Appends chars to output. 180 | If the pUsedDefaultChar != null and the default char gets used, sets 181 | *pUsedDefaultChar = true. Otherwise leaves pUsedDefaultChar at the prior value. 182 | */ 183 | void 184 | WriteChars( 185 | std::u16string_view chars, 186 | _In_opt_ PCCH pDefaultChar = nullptr, 187 | _Inout_opt_ bool* pUsedDefaultChar = nullptr); 188 | }; 189 | -------------------------------------------------------------------------------- /inc/TextToolsCommon.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Doug Cook. 2 | // Licensed under the MIT License. 3 | 4 | #pragma once 5 | #include 6 | 7 | #define TEXTTOOLS_VERSION "v1.0.2" 8 | #define TEXTTOOLS_LICENSE "Distributed under the terms of the MIT License.\nWritten by Doug Cook." 9 | 10 | #define TEXTTOOLS_VERSION_STR(toolNameString) "\n" \ 11 | toolNameString " (TextUtils) " TEXTTOOLS_VERSION "\n" \ 12 | TEXTTOOLS_LICENSE "\n" \ 13 | 14 | namespace TextToolsImpl 15 | { 16 | struct CloseHandle_delete 17 | { 18 | void operator()(HANDLE h) const noexcept; 19 | }; 20 | } 21 | 22 | using TextToolsUniqueHandle = std::unique_ptr; 23 | -------------------------------------------------------------------------------- /lib/ArgParser.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Doug Cook. 2 | // Licensed under the MIT License. 3 | 4 | #include "pch.h" 5 | #include 6 | #include 7 | 8 | static bool 9 | IsLongArgNameEnd(wchar_t ch) noexcept 10 | { 11 | return 12 | ch == L'\0' || 13 | ch == L':' || 14 | ch == L'='; 15 | } 16 | 17 | static size_t 18 | GetLongArgNameLength(PCWSTR szLongArg) noexcept 19 | { 20 | size_t i; 21 | for (i = 0; !IsLongArgNameEnd(szLongArg[i]); i += 1) {} 22 | return i; 23 | } 24 | 25 | ArgParser::ArgParser( 26 | _In_z_ PCSTR appName, 27 | unsigned argc, 28 | _In_count_(argc) PWSTR argv[]) noexcept 29 | : m_appName(appName) 30 | , m_args(argv) 31 | , m_argCount(argc) 32 | , m_currentArgIndex(0) // Before-begin 33 | , m_currentArgPos(L"") 34 | , m_argError() {} 35 | 36 | _Ret_z_ PCSTR 37 | ArgParser::AppName() const noexcept 38 | { 39 | return m_appName; 40 | } 41 | 42 | _Ret_z_ PCWSTR 43 | ArgParser::Arg(unsigned index) const noexcept 44 | { 45 | assert(index < m_argCount); 46 | return m_args[index]; 47 | } 48 | 49 | unsigned 50 | ArgParser::ArgCount() const noexcept 51 | { 52 | return m_argCount; 53 | } 54 | 55 | unsigned 56 | ArgParser::CurrentArgIndex() const noexcept 57 | { 58 | return m_currentArgIndex; 59 | } 60 | 61 | _Ret_z_ PCWSTR 62 | ArgParser::CurrentArg() const noexcept 63 | { 64 | return m_args[m_currentArgIndex]; 65 | } 66 | 67 | _Ret_z_ PCWSTR 68 | ArgParser::CurrentArgPos() const noexcept 69 | { 70 | return m_currentArgPos; 71 | } 72 | 73 | bool 74 | ArgParser::CurrentArgIsEmpty() const noexcept 75 | { 76 | assert(m_currentArgPos[0] != 0); 77 | return m_currentArgPos[1] == 0; 78 | } 79 | 80 | WCHAR 81 | ArgParser::CurrentArgChar() const noexcept 82 | { 83 | return m_currentArgPos[0]; 84 | } 85 | 86 | std::wstring_view 87 | ArgParser::CurrentArgName() const noexcept 88 | { 89 | assert(m_currentArgPos[0] != 0); 90 | auto pLongArg = &m_currentArgPos[1]; 91 | return std::wstring_view(pLongArg, GetLongArgNameLength(pLongArg)); 92 | } 93 | 94 | bool 95 | ArgParser::ArgError() const noexcept 96 | { 97 | return m_argError; 98 | } 99 | 100 | void 101 | ArgParser::SetArgError(bool value) noexcept 102 | { 103 | m_argError = value; 104 | } 105 | 106 | void 107 | ArgParser::SetArgErrorIfFalse(bool argOk) noexcept 108 | { 109 | m_argError = m_argError || !argOk; 110 | } 111 | 112 | void 113 | ArgParser::PrintShortArgError() noexcept 114 | { 115 | m_argError = true; 116 | fprintf(stderr, "%hs: error : Unrecognized short argument '%lc' in '%ls'\n", 117 | m_appName, CurrentArgChar(), CurrentArg()); 118 | } 119 | 120 | void 121 | ArgParser::PrintLongArgError() noexcept 122 | { 123 | m_argError = true; 124 | fprintf(stderr, "%hs: error : Unrecognized long argument '%ls'\n", 125 | m_appName, CurrentArg()); 126 | } 127 | 128 | bool 129 | ArgParser::MoveNextArg() noexcept 130 | { 131 | assert(m_currentArgIndex < m_argCount); 132 | m_currentArgIndex += 1; 133 | m_currentArgPos = L""; 134 | return m_currentArgIndex < m_argCount; 135 | } 136 | 137 | bool 138 | ArgParser::BeginDashDashArg() noexcept 139 | { 140 | auto const current = CurrentArg(); 141 | if (current[0] == L'-' && current[1] == L'-') 142 | { 143 | m_currentArgPos = ¤t[1]; // Before-begin 144 | return true; 145 | } 146 | 147 | return false; 148 | } 149 | 150 | bool 151 | ArgParser::BeginDashOrSlashArg() noexcept 152 | { 153 | auto const current = CurrentArg(); 154 | if (current[0] == L'-' || current[0] == L'/') 155 | { 156 | m_currentArgPos = ¤t[0]; // Before-begin 157 | return true; 158 | } 159 | 160 | return false; 161 | } 162 | 163 | bool 164 | ArgParser::BeginDashArg() noexcept 165 | { 166 | auto const current = CurrentArg(); 167 | if (current[0] == L'-') 168 | { 169 | m_currentArgPos = ¤t[0]; // Before-begin 170 | return true; 171 | } 172 | 173 | return false; 174 | } 175 | 176 | bool 177 | ArgParser::BeginSlashArg() noexcept 178 | { 179 | auto const current = CurrentArg(); 180 | if (current[0] == L'/') 181 | { 182 | m_currentArgPos = ¤t[0]; // Before-begin 183 | return true; 184 | } 185 | 186 | return false; 187 | } 188 | 189 | bool 190 | ArgParser::MoveNextArgChar() noexcept 191 | { 192 | assert(m_currentArgPos[0] != 0); 193 | m_currentArgPos += 1; 194 | return m_currentArgPos[0] != 0; 195 | } 196 | 197 | _Ret_z_ PCWSTR 198 | ArgParser::ReadArgCharsVal() noexcept 199 | { 200 | assert(m_currentArgPos[0] != 0); 201 | PCWSTR const shortArgVal = m_currentArgPos + 1; 202 | 203 | // Consume rest of current arg. 204 | m_currentArgPos = L" "; 205 | 206 | return shortArgVal; 207 | } 208 | 209 | _Ret_opt_z_ PCWSTR 210 | ArgParser::ReadNextArgVal() noexcept 211 | { 212 | assert(m_currentArgPos[0] != 0); 213 | 214 | PCWSTR shortArgVal; 215 | if (m_currentArgIndex + 1 < m_argCount) 216 | { 217 | m_currentArgIndex += 1; 218 | shortArgVal = m_args[m_currentArgIndex]; 219 | } 220 | else 221 | { 222 | shortArgVal = nullptr; 223 | } 224 | 225 | return shortArgVal; 226 | } 227 | 228 | _Ret_opt_z_ PCWSTR 229 | ArgParser::ReadShortArgVal() noexcept 230 | { 231 | assert(m_currentArgPos[0] != 0); 232 | 233 | PCWSTR shortArgVal; 234 | if (m_currentArgPos[1] != 0) 235 | { 236 | shortArgVal = &m_currentArgPos[1]; 237 | } 238 | else if (m_currentArgIndex + 1 < m_argCount) 239 | { 240 | m_currentArgIndex += 1; 241 | shortArgVal = m_args[m_currentArgIndex]; 242 | } 243 | else 244 | { 245 | shortArgVal = nullptr; 246 | } 247 | 248 | // Consume rest of current arg. 249 | m_currentArgPos = L" "; 250 | 251 | return shortArgVal; 252 | } 253 | 254 | _Ret_opt_z_ PCWSTR 255 | ArgParser::GetLongArgVal() const noexcept 256 | { 257 | assert(m_currentArgPos[0] != 0); 258 | auto pLongArg = &m_currentArgPos[1]; 259 | auto pNameEnd = pLongArg + GetLongArgNameLength(pLongArg); 260 | return pNameEnd[0] ? pNameEnd + 1 : nullptr; 261 | } 262 | 263 | bool 264 | ArgParser::ReadArgValImpl( 265 | std::wstring_view& val, 266 | bool emptyOk, 267 | _In_opt_z_ PCWSTR shortArgVal, 268 | WCHAR argChar, 269 | bool space) noexcept 270 | { 271 | if (!shortArgVal || 272 | (!emptyOk && shortArgVal[0] == 0)) 273 | { 274 | m_argError = true; 275 | fprintf(stderr, "%hs: error : Expected VALUE for '-%lc%hsVALUE'\n", 276 | m_appName, argChar, space ? " " : ""); 277 | return false; 278 | } 279 | 280 | val = shortArgVal; 281 | return true; 282 | } 283 | 284 | bool 285 | ArgParser::ReadArgCharsVal(std::wstring_view& val, bool emptyOk) noexcept 286 | { 287 | auto const argChar = CurrentArgChar(); 288 | auto const shortArgVal = ReadArgCharsVal(); 289 | return ReadArgValImpl(val, emptyOk, shortArgVal, argChar, false); 290 | } 291 | 292 | bool 293 | ArgParser::ReadNextArgVal(std::wstring_view& val, bool emptyOk) noexcept 294 | { 295 | auto const argChar = CurrentArgChar(); 296 | auto const shortArgVal = ReadNextArgVal(); 297 | return ReadArgValImpl(val, emptyOk, shortArgVal, argChar, true); 298 | } 299 | 300 | bool 301 | ArgParser::ReadShortArgVal(std::wstring_view& val, bool emptyOk) noexcept 302 | { 303 | auto const argChar = CurrentArgChar(); 304 | auto const shortArgVal = ReadShortArgVal(); 305 | return ReadArgValImpl(val, emptyOk, shortArgVal, argChar, true); 306 | } 307 | 308 | bool 309 | ArgParser::GetLongArgVal(std::wstring_view& val, bool emptyOk) noexcept 310 | { 311 | auto const longArgVal = GetLongArgVal(); 312 | if (!emptyOk && (longArgVal == nullptr || longArgVal[0] == 0)) 313 | { 314 | m_argError = true; 315 | fprintf(stderr, "%hs: error : Expected VALUE for '%ls=VALUE'\n", 316 | m_appName, CurrentArg()); 317 | return false; 318 | } 319 | 320 | val = longArgVal ? std::wstring_view(longArgVal) : std::wstring_view(); 321 | return true; 322 | } 323 | 324 | bool 325 | ArgParser::ReadArgValImpl( 326 | unsigned& val, 327 | bool zeroOk, 328 | int radix, 329 | _In_opt_z_ PCWSTR shortArgVal, 330 | WCHAR argChar, 331 | bool space) noexcept 332 | { 333 | if (shortArgVal == nullptr || shortArgVal[0] == 0) 334 | { 335 | m_argError = true; 336 | fprintf(stderr, "%hs: error : Expected uint32 VALUE for '-%lc%hsVALUE'\n", 337 | m_appName, argChar, space ? " " : ""); 338 | return false; 339 | } 340 | 341 | wchar_t* end; 342 | errno = 0; 343 | unsigned parsedVal = wcstoul(shortArgVal, &end, radix); 344 | if (errno != 0) 345 | { 346 | m_argError = true; 347 | fprintf(stderr, "%hs: error : Range error parsing uint32 '-%lc%hs%ls'\n", 348 | m_appName, argChar, space ? " " : "", shortArgVal); 349 | return false; 350 | } 351 | else if (end[0]) 352 | { 353 | m_argError = true; 354 | fprintf(stderr, "%hs: error : Trailing characters following uint32 '-%lc%hs%ls'\n", 355 | m_appName, argChar, space ? " " : "", shortArgVal); 356 | return false; 357 | } 358 | else if (!zeroOk && parsedVal == 0) 359 | { 360 | m_argError = true; 361 | fprintf(stderr, "%hs: error : Expected nonzero uint32 value for '-%lc%hs%ls'\n", 362 | m_appName, argChar, space ? " " : "", shortArgVal); 363 | return false; 364 | } 365 | 366 | val = parsedVal; 367 | return true; 368 | } 369 | 370 | bool 371 | ArgParser::ReadArgCharsVal(unsigned& val, bool zeroOk, int radix) noexcept 372 | { 373 | auto const argChar = CurrentArgChar(); 374 | auto const shortArgVal = ReadArgCharsVal(); 375 | return ReadArgValImpl(val, zeroOk, radix, shortArgVal, argChar, false); 376 | } 377 | 378 | bool 379 | ArgParser::ReadNextArgVal(unsigned& val, bool zeroOk, int radix) noexcept 380 | { 381 | auto const argChar = CurrentArgChar(); 382 | auto const shortArgVal = ReadNextArgVal(); 383 | return ReadArgValImpl(val, zeroOk, radix, shortArgVal, argChar, true); 384 | } 385 | 386 | bool 387 | ArgParser::ReadShortArgVal(unsigned& val, bool zeroOk, int radix) noexcept 388 | { 389 | auto const argChar = CurrentArgChar(); 390 | auto const shortArgVal = ReadShortArgVal(); 391 | return ReadArgValImpl(val, zeroOk, radix, shortArgVal, argChar, true); 392 | } 393 | 394 | bool 395 | ArgParser::GetLongArgVal(unsigned& val, bool zeroOk, int radix) noexcept 396 | { 397 | auto const longArgVal = GetLongArgVal(); 398 | if (!longArgVal) 399 | { 400 | m_argError = true; 401 | fprintf(stderr, "%hs: error : Expected uint32 value for '%ls=value'\n", 402 | m_appName, CurrentArg()); 403 | return false; 404 | } 405 | 406 | wchar_t* end; 407 | errno = 0; 408 | unsigned parsedVal = wcstoul(longArgVal, &end, radix); 409 | if (errno != 0) 410 | { 411 | m_argError = true; 412 | fprintf(stderr, "%hs: error : Range error parsing uint32 '%ls'\n", 413 | m_appName, CurrentArg()); 414 | return false; 415 | } 416 | else if (end[0]) 417 | { 418 | m_argError = true; 419 | fprintf(stderr, "%hs: error : Trailing characters following uint32 '%ls'\n", 420 | m_appName, CurrentArg()); 421 | return false; 422 | } 423 | else if (!zeroOk && parsedVal == 0) 424 | { 425 | m_argError = true; 426 | fprintf(stderr, "%hs: error : Expected nonzero uint32 value for '%ls'\n", 427 | m_appName, CurrentArg()); 428 | return false; 429 | } 430 | 431 | val = parsedVal; 432 | return true; 433 | } 434 | 435 | bool 436 | ArgParser::CurrentArgNameMatches( 437 | unsigned minMatchLength, 438 | PCWSTR expectedName) const noexcept 439 | { 440 | assert(m_currentArgPos[0] != 0); 441 | bool matches; 442 | auto const pCurrentArg = &m_currentArgPos[1]; 443 | for (unsigned i = 0;; i += 1) 444 | { 445 | if (IsLongArgNameEnd(pCurrentArg[i])) 446 | { 447 | // Reached end of arg without any mismatch. 448 | matches = i >= minMatchLength; // Matched enough? 449 | break; 450 | } 451 | else if (expectedName[i] != pCurrentArg[i]) 452 | { 453 | matches = false; 454 | break; 455 | } 456 | } 457 | 458 | return matches; 459 | } 460 | -------------------------------------------------------------------------------- /lib/ByteOrderMark.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Doug Cook. 2 | // Licensed under the MIT License. 3 | 4 | #include "pch.h" 5 | #include "ByteOrderMark.h" 6 | #include 7 | 8 | ByteOrderMark const ByteOrderMark::Standard[5] = { 9 | { CodePageUtf8, 3, "\xEF\xBB\xBF" }, 10 | { CodePageUtf32LE, 4, "\xFF\xFE\x00\x00" }, // Must come before UTF16LE. 11 | { CodePageUtf16LE, 2, "\xFF\xFE" }, 12 | { CodePageUtf32BE, 4, "\x00\x00\xFE\xFF" }, 13 | { CodePageUtf16BE, 2, "\xFE\xFF" }, 14 | }; 15 | 16 | ByteOrderMatch 17 | ByteOrderMark::Match(std::string_view data) const 18 | { 19 | auto const dataProvided = data.data(); 20 | auto const sizeProvided = data.size(); 21 | UINT8 sizeToCheck = Size <= sizeProvided 22 | ? Size 23 | : (UINT8)sizeProvided; 24 | return 0 != memcmp(Data, dataProvided, sizeToCheck) ? ByteOrderMatch::No 25 | : sizeToCheck == Size ? ByteOrderMatch::Yes 26 | : ByteOrderMatch::NeedMoreData; 27 | } 28 | -------------------------------------------------------------------------------- /lib/ByteOrderMark.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Doug Cook. 2 | // Licensed under the MIT License. 3 | 4 | #pragma once 5 | #include 6 | 7 | enum class ByteOrderMatch 8 | { 9 | No, // Is not a match. 10 | Yes, // Is a match. 11 | NeedMoreData, // Read more data and try again. 12 | }; 13 | 14 | struct ByteOrderMark 15 | { 16 | UINT16 CodePage; 17 | UINT8 Size; 18 | char const* Data; 19 | 20 | static ByteOrderMark const Standard[5]; // UTF8, UTF32LE, UTF16LE, UTF32BE, UTF16BE. 21 | 22 | ByteOrderMatch 23 | Match(std::string_view data) const; 24 | }; 25 | -------------------------------------------------------------------------------- /lib/ClipboardText.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Doug Cook. 2 | // Licensed under the MIT License. 3 | 4 | #include "pch.h" 5 | #include 6 | #include 7 | #include 8 | #include "Utility.h" 9 | 10 | #include 11 | 12 | namespace TextToolsImpl 13 | { 14 | class ClipboardOwnership 15 | { 16 | bool const m_opened; 17 | 18 | public: 19 | 20 | ClipboardOwnership(ClipboardOwnership const&) = delete; 21 | void operator=(ClipboardOwnership const&) = delete; 22 | 23 | ~ClipboardOwnership() 24 | { 25 | if (m_opened) 26 | { 27 | verify(CloseClipboard()); 28 | } 29 | } 30 | 31 | ClipboardOwnership() 32 | : m_opened(0 != OpenClipboard(nullptr)) {} 33 | 34 | explicit constexpr 35 | operator bool() const noexcept 36 | { 37 | return m_opened; 38 | } 39 | }; 40 | 41 | struct GlobalFree_delete 42 | { 43 | void 44 | operator()(HANDLE hMem) const noexcept 45 | { 46 | verify(nullptr == GlobalFree(hMem)); 47 | } 48 | }; 49 | 50 | struct GlobalUnlock_delete 51 | { 52 | void 53 | operator()(HANDLE hMem) const noexcept 54 | { 55 | verify(GlobalUnlock(hMem)); 56 | } 57 | }; 58 | } 59 | 60 | using namespace TextToolsImpl; 61 | using unique_hglobal = std::unique_ptr; 62 | using unique_hgloballock = std::unique_ptr; 63 | 64 | LSTATUS 65 | ClipboardTextGet(std::wstring& value) 66 | { 67 | LSTATUS status; 68 | value.clear(); 69 | 70 | ClipboardOwnership clipboard; 71 | if (!clipboard) 72 | { 73 | status = GetLastError(); 74 | } 75 | else if (auto hClipMem = GetClipboardData(CF_UNICODETEXT); 76 | !hClipMem) 77 | { 78 | status = GetLastError(); 79 | } 80 | else if (auto const pchClip = (PCWSTR)GlobalLock(hClipMem); 81 | !pchClip) 82 | { 83 | status = GetLastError(); 84 | } 85 | else 86 | { 87 | unique_hgloballock hClipMemLock(hClipMem); 88 | auto const cchClip = pchClip ? wcslen(pchClip) : 0u; 89 | value.assign(pchClip, cchClip); 90 | status = ERROR_SUCCESS; 91 | } 92 | 93 | return status; 94 | } 95 | 96 | LSTATUS 97 | TextInput::OpenClipboard(TextInputFlags flags) 98 | { 99 | LSTATUS status; 100 | 101 | ClipboardOwnership clipboard; 102 | if (!clipboard) 103 | { 104 | status = GetLastError(); 105 | } 106 | else if (auto hClipMem = GetClipboardData(CF_UNICODETEXT); 107 | !hClipMem) 108 | { 109 | status = GetLastError(); 110 | } 111 | else if (auto const pchClip = (PCWSTR)GlobalLock(hClipMem); 112 | !pchClip) 113 | { 114 | status = GetLastError(); 115 | } 116 | else 117 | { 118 | unique_hgloballock hClipMemLock(hClipMem); 119 | static_assert(sizeof(char16_t) == sizeof(wchar_t)); 120 | OpenChars({ pchClip ? (char16_t const*)pchClip : u"" }, flags); 121 | status = ERROR_SUCCESS; 122 | } 123 | 124 | return status; 125 | } 126 | 127 | LSTATUS 128 | ClipboardTextSet(std::wstring_view value) 129 | { 130 | LSTATUS status; 131 | 132 | unique_hglobal hClipMem(GlobalAlloc(GMEM_MOVEABLE, (value.size() + 1) * sizeof(value[0]))); 133 | if (!hClipMem) 134 | { 135 | status = GetLastError(); 136 | } 137 | else if (auto const pchClip = (char16_t*)GlobalLock(hClipMem.get()); 138 | !pchClip) 139 | { 140 | status = GetLastError(); 141 | } 142 | else 143 | { 144 | unique_hgloballock hClipMemLock(hClipMem.get()); 145 | memcpy(pchClip, value.data(), value.size() * sizeof(value[0])); 146 | pchClip[value.size()] = 0; 147 | hClipMemLock.reset(); 148 | 149 | ClipboardOwnership clipboard; 150 | if (!clipboard) 151 | { 152 | status = GetLastError(); 153 | } 154 | else if (!EmptyClipboard()) 155 | { 156 | status = GetLastError(); 157 | } 158 | else if (!SetClipboardData(CF_UNICODETEXT, hClipMem.get())) 159 | { 160 | status = GetLastError(); 161 | } 162 | else 163 | { 164 | (void)hClipMem.release(); 165 | status = ERROR_SUCCESS; 166 | } 167 | } 168 | 169 | return status; 170 | } 171 | -------------------------------------------------------------------------------- /lib/CodePageInfo.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Doug Cook. 2 | // Licensed under the MIT License. 3 | 4 | #include "pch.h" 5 | #include 6 | #include 7 | 8 | #define WCSCPY(dest, srclit) memcpy(dest, L"" srclit, sizeof(L"" srclit)) 9 | 10 | // Get the next alphanumeric char (lowercased), or 0 if EOS. 11 | static wchar_t 12 | NextArgChar(std::wstring_view arg, size_t& iArg) noexcept 13 | { 14 | for (;;) 15 | { 16 | auto ch = iArg < arg.size() ? arg[iArg] : L'\0'; 17 | if (ch == 0) 18 | { 19 | return ch; 20 | } 21 | 22 | iArg += 1; 23 | 24 | if (L'0' <= ch && ch <= L'9') 25 | { 26 | return ch; 27 | } 28 | 29 | ch |= 32; // Make uppercase. 30 | if (L'a' <= ch && ch <= 'z') 31 | { 32 | return ch; 33 | } 34 | } 35 | } 36 | 37 | enum class BomSuffix 38 | { 39 | NoSuffix, 40 | Yes, 41 | NoMatch, 42 | }; 43 | 44 | static BomSuffix 45 | CheckBomSuffix(std::wstring_view arg, size_t iArg) noexcept 46 | { 47 | BomSuffix result; 48 | if (auto ch = NextArgChar(arg, iArg); 49 | ch == 0) 50 | { 51 | result = BomSuffix::NoSuffix; 52 | } 53 | else if (ch == L'b' && 54 | NextArgChar(arg, iArg) == L'o' && 55 | NextArgChar(arg, iArg) == L'm' && 56 | NextArgChar(arg, iArg) == 0) 57 | { 58 | result = BomSuffix::Yes; 59 | } 60 | else 61 | { 62 | result = BomSuffix::NoMatch; 63 | } 64 | 65 | return result; 66 | } 67 | 68 | CodePageArg::CodePageArg(std::wstring_view arg) noexcept 69 | : CodePage() 70 | , BomSuffix() 71 | , ParseResult(CodePageCategory::None) 72 | { 73 | size_t iArg; 74 | 75 | struct UtfEncoding { 76 | PCWSTR Name; 77 | UINT16 CodePage; 78 | }; 79 | 80 | static UtfEncoding const UtfEncodings[] = { 81 | { L"utf8", CodePageUtf8 }, 82 | { L"utf16", CodePageUtf16LE }, 83 | { L"utf16le", CodePageUtf16LE }, 84 | { L"utf16be", CodePageUtf16BE }, 85 | { L"utf32", CodePageUtf32LE }, 86 | { L"utf32le", CodePageUtf32LE }, 87 | { L"utf32be", CodePageUtf32BE }, 88 | }; 89 | 90 | for (auto& enc : UtfEncodings) 91 | { 92 | iArg = 0; 93 | for (size_t iEnc = 0;; iEnc += 1) 94 | { 95 | auto encChar = enc.Name[iEnc]; 96 | if (encChar == 0) 97 | { 98 | auto const suffix = CheckBomSuffix(arg, iArg); 99 | if (suffix == BomSuffix::NoMatch) 100 | { 101 | break; 102 | } 103 | else 104 | { 105 | CodePage = enc.CodePage; 106 | BomSuffix = suffix == BomSuffix::Yes; 107 | ParseResult = CodePageCategory::Utf; 108 | goto Done; 109 | } 110 | } 111 | 112 | auto argChar = NextArgChar(arg, iArg); 113 | if (encChar != argChar) 114 | { 115 | break; 116 | } 117 | } 118 | } 119 | 120 | iArg = 0; 121 | 122 | // Skip leading "cp" if present. 123 | if (NextArgChar(arg, iArg) != L'c' || 124 | NextArgChar(arg, iArg) != L'p') 125 | { 126 | iArg = 0; 127 | } 128 | 129 | PWSTR numEnd; 130 | errno = 0; 131 | CodePage = wcstoul(arg.data() + iArg, &numEnd, 10); 132 | if (errno != 0 || arg.data() + iArg == numEnd) 133 | { 134 | ParseResult = CodePageCategory::Error; 135 | goto Done; 136 | } 137 | 138 | for (auto& enc : UtfEncodings) 139 | { 140 | if (enc.CodePage == CodePage) 141 | { 142 | ParseResult = CodePageCategory::Utf; 143 | break; 144 | } 145 | } 146 | 147 | iArg = numEnd - arg.data(); 148 | 149 | if (auto const suffix = CheckBomSuffix(arg, iArg); 150 | suffix == BomSuffix::NoMatch) 151 | { 152 | ParseResult = CodePageCategory::Error; 153 | } 154 | else 155 | { 156 | BomSuffix = suffix == BomSuffix::Yes; 157 | } 158 | 159 | Done: 160 | 161 | return; 162 | } 163 | 164 | static _Success_(return) bool 165 | InitUtfCodePage(unsigned codePage, _Out_ CPINFOEXW* pInfo) noexcept 166 | { 167 | bool utf; 168 | 169 | switch (codePage) 170 | { 171 | default: 172 | utf = false; 173 | goto Done; 174 | case CodePageUtf8: 175 | WCSCPY(pInfo->CodePageName, L"65001 (UTF-8)"); 176 | break; 177 | case CodePageUtf16LE: 178 | WCSCPY(pInfo->CodePageName, L"1200 (UTF-16LE)"); 179 | break; 180 | case CodePageUtf16BE: 181 | WCSCPY(pInfo->CodePageName, L"1201 (UTF-16BE)"); 182 | break; 183 | case CodePageUtf32LE: 184 | WCSCPY(pInfo->CodePageName, L"12000 (UTF-32LE)"); 185 | break; 186 | case CodePageUtf32BE: 187 | WCSCPY(pInfo->CodePageName, L"12001 (UTF-32BE)"); 188 | break; 189 | } 190 | 191 | pInfo->MaxCharSize = 4; 192 | pInfo->DefaultChar[0] = '?'; 193 | pInfo->LeadByte[0] = 0; 194 | pInfo->UnicodeDefaultChar = 0xfffd; 195 | pInfo->CodePage = codePage; 196 | utf = true; 197 | 198 | Done: 199 | 200 | return utf; 201 | } 202 | 203 | CodePageInfo::CodePageInfo(unsigned codePage) noexcept 204 | : CPINFOEXW() 205 | , UnresolvedCodePage(codePage) 206 | { 207 | if (InitUtfCodePage(codePage, this)) 208 | { 209 | Category = CodePageCategory::Utf; 210 | } 211 | else if (!GetCPInfoExW(codePage, 0, this)) 212 | { 213 | CodePage = codePage; 214 | _snwprintf_s(CodePageName, _TRUNCATE, L"%u (Unknown)", codePage); 215 | Category = CodePageCategory::Error; 216 | } 217 | else if (MaxCharSize == 1) 218 | { 219 | Category = CodePageCategory::Sbcs; 220 | } 221 | else if (MaxCharSize == 2 && LeadByte[0] != 0) 222 | { 223 | Category = CodePageCategory::Dbcs; 224 | } 225 | else if (InitUtfCodePage(CodePage, this)) 226 | { 227 | Category = CodePageCategory::Utf; 228 | } 229 | else 230 | { 231 | Category = CodePageCategory::Complex; 232 | } 233 | } 234 | 235 | std::string 236 | CodePageInfo::Name() const 237 | { 238 | std::string name; 239 | 240 | size_t cch = wcsnlen(CodePageName, ARRAYSIZE(CodePageName)); 241 | name.resize(cch); 242 | for (size_t i = 0; i != cch; i++) 243 | { 244 | name[i] = (char)CodePageName[i]; 245 | } 246 | 247 | return name; 248 | } 249 | -------------------------------------------------------------------------------- /lib/TextInput.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Doug Cook. 2 | // Licensed under the MIT License. 3 | 4 | #include "pch.h" 5 | #include 6 | #include 7 | #include "ByteOrderMark.h" 8 | #include "Utility.h" 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | using namespace TextToolsImpl; 15 | 16 | static constexpr unsigned ReadMax = 0x1fffffff; // Max value to be used in ReadFile or ReadConsole. 17 | static constexpr unsigned FileBufferSize = 4096; 18 | static constexpr unsigned ConsoleBufferSize = 2048; 19 | 20 | constexpr bool 21 | TextInput::IsFlagSet(TextInputFlags flag) const noexcept 22 | { 23 | return (m_flags & flag) != TextInputFlags::None; 24 | } 25 | 26 | void 27 | TextInput::SetCodeConvert(unsigned codePage) 28 | { 29 | CodeConvert codeConvert(codePage); 30 | codeConvert.ThrowIfNotSupported(); 31 | m_codeConvert = codeConvert; 32 | } 33 | 34 | void 35 | TextInput::FoldCRLF() noexcept 36 | { 37 | assert(m_charsPos <= m_chars.size()); 38 | 39 | if (!IsFlagSet(TextInputFlags::FoldCRLF) || m_charsPos == 0) 40 | { 41 | return; 42 | } 43 | 44 | bool const skipNextCharIfNewline = m_skipNextCharIfNewline; 45 | m_skipNextCharIfNewline = false; 46 | 47 | auto const pChars = m_chars.data(); 48 | size_t iInput, iOutput; 49 | if (skipNextCharIfNewline && pChars[0] == L'\n') 50 | { 51 | // Last chunk ended on "\r", which we converted to "\n". 52 | // This chunk starts with "\n", so it was a "\r\n" sequence. 53 | // We already wrote the corresponding "\n" so skip it. 54 | iInput = 1; 55 | iOutput = 0; 56 | } 57 | else 58 | { 59 | auto const pFirstCR = (char16_t*)wmemchr((WCHAR*)pChars, L'\r', m_charsPos); 60 | if (!pFirstCR) 61 | { 62 | // If there is nothing to skip or convert, we're done. 63 | return; 64 | } 65 | 66 | iInput = pFirstCR - pChars; 67 | iOutput = iInput; 68 | } 69 | 70 | for (; iInput != m_charsPos; iInput += 1) 71 | { 72 | auto const ch = pChars[iInput]; 73 | if (ch != L'\r') 74 | { 75 | pChars[iOutput++] = ch; 76 | } 77 | else if (iInput + 1 == m_charsPos) 78 | { 79 | // "\r" at end of chunk. Assume lone "\r" and convert it to "\n". 80 | // If next chunk starts with "\n" we will need to skip it. 81 | m_skipNextCharIfNewline = true; 82 | pChars[iOutput++] = L'\n'; 83 | break; 84 | } 85 | else if (pChars[iInput + 1] != L'\n') 86 | { 87 | // Lone "\r", convert to "\n". 88 | pChars[iOutput++] = L'\n'; 89 | } 90 | else 91 | { 92 | // "\r\n" sequence, ignore the "\r". 93 | } 94 | } 95 | 96 | m_charsPos = iOutput; 97 | } 98 | 99 | void 100 | TextInput::ConsumeBytes(size_t consumedBytes) noexcept 101 | { 102 | if (consumedBytes >= m_bytesPos) 103 | { 104 | assert(consumedBytes == m_bytesPos); 105 | m_bytesPos = 0; 106 | } 107 | else if (consumedBytes != 0) 108 | { 109 | m_bytesPos -= consumedBytes; 110 | memmove(m_bytes.data(), m_bytes.data() + consumedBytes, m_bytesPos); 111 | } 112 | } 113 | 114 | void 115 | TextInput::Convert() 116 | { 117 | assert(m_mode == TextInputMode::Bytes || m_mode == TextInputMode::File); 118 | assert(m_bytesPos <= m_bytes.size()); 119 | 120 | m_charsPos = 0; 121 | 122 | size_t consumedBytes = 0; 123 | LSTATUS status = m_codeConvert.EncodedToUtf16( 124 | std::string_view(m_bytes.data(), m_bytesPos), consumedBytes, 125 | m_chars, m_charsPos, 126 | IsFlagSet(TextInputFlags::InvalidMbcsError) ? MB_ERR_INVALID_CHARS : 0); 127 | ConsumeBytes(consumedBytes); 128 | FoldCRLF(); 129 | 130 | if (status != ERROR_SUCCESS) 131 | { 132 | if (status == ERROR_NO_UNICODE_TRANSLATION) 133 | { 134 | throw std::range_error("Input is not valid for encoding " + 135 | std::to_string(m_codeConvert.CodePage()) + 136 | "."); 137 | } 138 | else 139 | { 140 | throw std::runtime_error("MBCS-to-UTF16 conversion error " + 141 | std::to_string(status) + 142 | "."); 143 | } 144 | } 145 | } 146 | 147 | void 148 | TextInput::ReadBytesFromFile() 149 | { 150 | auto const cbRemainingBuffer = m_bytes.size() - m_bytesPos; 151 | ReadBytesFromFile(cbRemainingBuffer < ReadMax 152 | ? (DWORD)cbRemainingBuffer 153 | : ReadMax); 154 | } 155 | 156 | void 157 | TextInput::ReadBytesFromFile(DWORD cbMaxToRead) 158 | { 159 | assert(m_inputHandle); 160 | assert(m_bytes.size() > m_bytesPos); 161 | assert(m_bytes.size() - m_bytesPos >= cbMaxToRead); 162 | 163 | DWORD cbRead = 0; 164 | if (!ReadFile(m_inputHandle, m_bytes.data() + m_bytesPos, cbMaxToRead, &cbRead, nullptr)) 165 | { 166 | auto lastError = GetLastError(); 167 | if (lastError != ERROR_BROKEN_PIPE) 168 | { 169 | throw std::runtime_error("ReadFile error " + std::to_string(lastError)); 170 | } 171 | } 172 | 173 | m_bytesPos += cbRead; 174 | 175 | if (cbRead == 0) 176 | { 177 | m_inputOwner.reset(); 178 | m_inputHandle = nullptr; 179 | } 180 | } 181 | 182 | void 183 | TextInput::ReadCharsFromConsole() 184 | { 185 | assert(m_inputHandle); 186 | assert(ConsoleBufferSize <= m_chars.size()); 187 | assert(m_charsPos == 0); 188 | 189 | CONSOLE_READCONSOLE_CONTROL control = {}; 190 | control.nLength = sizeof(control); 191 | control.dwCtrlWakeupMask = IsFlagSet(TextInputFlags::ConsoleCtrlZ) 192 | ? 1u << 26 193 | : 0u; 194 | 195 | DWORD const cchMaxToRead = 196 | m_chars.size() < ReadMax 197 | ? (DWORD)m_chars.size() 198 | : ReadMax; 199 | DWORD cchRead = 0; 200 | if (!ReadConsoleW(m_inputHandle, m_chars.data(), cchMaxToRead, &cchRead, &control)) 201 | { 202 | auto lastError = GetLastError(); 203 | throw std::runtime_error("ReadConsoleW error " + std::to_string(lastError)); 204 | } 205 | 206 | m_charsPos = cchRead; 207 | 208 | if (cchRead == 0) 209 | { 210 | m_inputOwner.reset(); 211 | m_inputHandle = nullptr; 212 | } 213 | } 214 | 215 | void 216 | TextInput::OpenHandle( 217 | TextToolsUniqueHandle inputOwner, 218 | _In_ HANDLE inputHandle, 219 | unsigned codePage, 220 | TextInputFlags flags) 221 | { 222 | auto const fileType = GetFileType(inputHandle); 223 | if (fileType == FILE_TYPE_UNKNOWN) 224 | { 225 | auto lastError = GetLastError(); 226 | if (lastError != ERROR_SUCCESS) 227 | { 228 | throw std::runtime_error("GetFileType error " + std::to_string(lastError)); 229 | } 230 | } 231 | 232 | Close(); 233 | 234 | // Note: Even if the text ends up having a BOM or Console, we want to validate the codePage parameter. 235 | SetCodeConvert(codePage); 236 | m_mode = TextInputMode::File; 237 | m_flags = flags; 238 | 239 | m_inputOwner = std::move(inputOwner); 240 | m_inputHandle = inputHandle; 241 | 242 | if (IsFlagSet(TextInputFlags::CheckConsole) && fileType == FILE_TYPE_CHAR) 243 | { 244 | DWORD consoleMode; 245 | if (GetConsoleMode(m_inputHandle, &consoleMode)) 246 | { 247 | EnsureSize(m_chars, ConsoleBufferSize); 248 | m_codeConvert = CodeConvert(CodePageUtf16LE); 249 | m_mode = TextInputMode::Console; 250 | goto Done; // Note: Don't consume BOM from console. 251 | } 252 | } 253 | 254 | EnsureSize(m_bytes, FileBufferSize); 255 | 256 | if (IsFlagSet(TextInputFlags::ConsumeBom)) 257 | { 258 | ReadBytesFromFile(4); 259 | for (auto& bomInfo : ByteOrderMark::Standard) 260 | { 261 | for (;;) 262 | { 263 | auto match = bomInfo.Match({ m_bytes.data(), m_bytesPos }); 264 | if (match == ByteOrderMatch::Yes) 265 | { 266 | ConsumeBytes(bomInfo.Size); 267 | m_codeConvert = CodeConvert(bomInfo.CodePage); 268 | goto Done; 269 | } 270 | else if (match == ByteOrderMatch::No) 271 | { 272 | break; 273 | } 274 | else if (!m_inputHandle) 275 | { 276 | // NeedMoreData but EOF. Don't goto Done. Consider a 2-byte file with 277 | // UTF16 BOM. UTF32 will want more data but we want UTF16 to match. 278 | break; 279 | } 280 | else 281 | { 282 | assert(bomInfo.Size > m_bytesPos); 283 | ReadBytesFromFile(bomInfo.Size - (DWORD)m_bytesPos); 284 | } 285 | } 286 | } 287 | } 288 | 289 | Done: 290 | 291 | ReadNextChars(); 292 | return; 293 | } 294 | 295 | TextInput::TextInput() noexcept 296 | : m_bytes() 297 | , m_chars() 298 | , m_inputOwner() 299 | , m_inputHandle() 300 | , m_codeConvert() 301 | , m_mode() 302 | , m_flags() 303 | , m_skipNextCharIfNewline() 304 | , m_bytesPos() 305 | , m_charsPos() 306 | { 307 | return; 308 | } 309 | 310 | void 311 | TextInput::Close() noexcept 312 | { 313 | m_inputOwner.reset(); 314 | m_inputHandle = {}; 315 | m_codeConvert = {}; 316 | m_mode = {}; 317 | m_flags = {}; 318 | m_skipNextCharIfNewline = {}; 319 | m_bytesPos = {}; 320 | m_charsPos = {}; 321 | } 322 | 323 | TextInputMode 324 | TextInput::Mode() const noexcept 325 | { 326 | return m_mode; 327 | } 328 | 329 | void 330 | TextInput::OpenChars( 331 | std::u16string_view inputChars, 332 | TextInputFlags flags) 333 | { 334 | Close(); 335 | 336 | m_codeConvert = CodeConvert(CodePageUtf16LE); 337 | m_mode = TextInputMode::Chars; 338 | m_flags = flags; 339 | 340 | if (IsFlagSet(TextInputFlags::ConsumeBom) && inputChars.starts_with(u'\xFEFF')) 341 | { 342 | inputChars.remove_prefix(1); 343 | } 344 | 345 | EnsureSize(m_chars, inputChars.size()); 346 | memcpy(m_chars.data(), inputChars.data(), inputChars.size() * sizeof(char16_t)); 347 | m_charsPos = inputChars.size(); 348 | 349 | FoldCRLF(); 350 | } 351 | 352 | void 353 | TextInput::OpenBytes( 354 | std::string_view inputBytes, 355 | unsigned codePage, 356 | TextInputFlags flags) 357 | { 358 | Close(); 359 | 360 | // Note: Even if the text ends up having a BOM or Console, we want to validate the codePage parameter. 361 | SetCodeConvert(codePage); 362 | m_mode = TextInputMode::Bytes; 363 | m_flags = flags; 364 | 365 | size_t consumedBytes = 0; 366 | 367 | if (IsFlagSet(TextInputFlags::ConsumeBom)) 368 | { 369 | for (auto& bomInfo : ByteOrderMark::Standard) 370 | { 371 | if (bomInfo.Match(inputBytes) == ByteOrderMatch::Yes) 372 | { 373 | m_codeConvert = CodeConvert(bomInfo.CodePage); 374 | consumedBytes = bomInfo.Size; 375 | break; 376 | } 377 | } 378 | } 379 | 380 | LSTATUS status = m_codeConvert.EncodedToUtf16( 381 | inputBytes, consumedBytes, 382 | m_chars, m_charsPos, 383 | IsFlagSet(TextInputFlags::InvalidMbcsError) ? MB_ERR_INVALID_CHARS : 0); 384 | FoldCRLF(); 385 | 386 | // Copy any bytes that we couldn't convert. 387 | EnsureSize(m_bytes, inputBytes.size() - consumedBytes); 388 | memcpy(m_bytes.data(), inputBytes.data() + consumedBytes, inputBytes.size() - consumedBytes); 389 | m_bytesPos = inputBytes.size() - consumedBytes; 390 | 391 | if (status != ERROR_SUCCESS) 392 | { 393 | throw std::range_error("Conversion error " + std::to_string(status)); 394 | } 395 | } 396 | 397 | void 398 | TextInput::OpenBorrowedHandle( 399 | _In_ HANDLE inputHandle, 400 | unsigned codePage, 401 | TextInputFlags flags) 402 | { 403 | OpenHandle({}, inputHandle, codePage, flags); 404 | } 405 | 406 | LSTATUS 407 | TextInput::OpenFile( 408 | _In_ PCWSTR inputFile, 409 | unsigned codePage, 410 | TextInputFlags flags) 411 | { 412 | LSTATUS status; 413 | 414 | HANDLE const inputHandle = CreateFileW( 415 | inputFile, 416 | FILE_READ_DATA, 417 | FILE_SHARE_READ | FILE_SHARE_DELETE, 418 | nullptr, 419 | OPEN_EXISTING, 420 | FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, 421 | nullptr); 422 | if (inputHandle == INVALID_HANDLE_VALUE) 423 | { 424 | status = GetLastError(); 425 | } 426 | else 427 | { 428 | OpenHandle(TextToolsUniqueHandle(inputHandle), inputHandle, codePage, flags); 429 | status = ERROR_SUCCESS; 430 | } 431 | 432 | return status; 433 | } 434 | 435 | std::u16string_view 436 | TextInput::Chars() const noexcept 437 | { 438 | return { m_chars.data(), m_charsPos }; 439 | } 440 | 441 | bool 442 | TextInput::ReadNextChars() 443 | { 444 | assert(m_mode != TextInputMode::None); 445 | m_charsPos = 0; 446 | 447 | if (m_mode == TextInputMode::Console) 448 | { 449 | while (m_inputHandle && m_charsPos == 0) 450 | { 451 | ReadCharsFromConsole(); 452 | FoldCRLF(); 453 | } 454 | } 455 | else 456 | { 457 | while (m_inputHandle && m_charsPos == 0) 458 | { 459 | ReadBytesFromFile(); 460 | Convert(); 461 | } 462 | } 463 | 464 | return m_charsPos != 0; 465 | } 466 | -------------------------------------------------------------------------------- /lib/TextOutput.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Doug Cook. 2 | // Licensed under the MIT License. 3 | 4 | #include "pch.h" 5 | #include 6 | #include 7 | #include "ByteOrderMark.h" 8 | #include "Utility.h" 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | using namespace TextToolsImpl; 15 | 16 | unsigned constexpr WriteMax = 1u << 20; 17 | char16_t constexpr BomChar = u'\xFEFF'; 18 | 19 | constexpr bool 20 | TextOutput::IsFlagSet(TextOutputFlags flag) const noexcept 21 | { 22 | return (m_flags & flag) != TextOutputFlags::None; 23 | } 24 | 25 | void 26 | TextOutput::SetCodeConvert(unsigned codePage) 27 | { 28 | CodeConvert codeConvert(codePage); 29 | auto const category = codeConvert.ThrowIfNotSupported(); 30 | m_codeConvert = codeConvert; 31 | m_codeConvertUtf = category == CodePageCategory::Utf; 32 | } 33 | 34 | void 35 | TextOutput::SetFlags(TextOutputFlags flags) noexcept 36 | { 37 | m_flags = flags; 38 | m_wc2mbFlags = m_codeConvertUtf 39 | ? (IsFlagSet(TextOutputFlags::InvalidUtf16Error) ? WC_ERR_INVALID_CHARS : 0) 40 | : (IsFlagSet(TextOutputFlags::NoBestFitChars) ? WC_NO_BEST_FIT_CHARS : 0); 41 | } 42 | 43 | void 44 | TextOutput::InsertBom() 45 | { 46 | if (m_codeConvertUtf && IsFlagSet(TextOutputFlags::InsertBom)) 47 | { 48 | WriteChars({ &BomChar, 1 }, nullptr, nullptr); 49 | } 50 | } 51 | 52 | void 53 | TextOutput::FlushFile() 54 | { 55 | assert(m_mode == TextOutputMode::File); 56 | 57 | size_t cbWritten = 0; 58 | size_t const cbToWrite = m_bytesPos; 59 | m_bytesPos = 0; 60 | 61 | while (cbWritten != cbToWrite) 62 | { 63 | DWORD cbBatch = cbToWrite - cbWritten > WriteMax 64 | ? WriteMax 65 | : static_cast(cbToWrite - cbWritten); 66 | if (!WriteFile(m_outputHandle, &m_bytes[cbWritten], cbBatch, &cbBatch, nullptr)) 67 | { 68 | auto lastError = GetLastError(); 69 | throw std::runtime_error("WriteFile error " + std::to_string(lastError)); 70 | } 71 | 72 | assert(cbBatch != 0); 73 | cbWritten += cbBatch; 74 | } 75 | } 76 | 77 | void 78 | TextOutput::FlushConsole(std::u16string_view pendingChars) 79 | { 80 | assert(m_mode == TextOutputMode::Console); 81 | 82 | size_t cchWritten = 0; 83 | size_t const cchToWrite = pendingChars.size(); 84 | 85 | while (cchWritten != cchToWrite) 86 | { 87 | DWORD cchBatch = cchToWrite - cchWritten > WriteMax 88 | ? WriteMax 89 | : static_cast(cchToWrite - cchWritten); 90 | 91 | auto const lastChar = pendingChars[cchWritten + cchBatch - 1]; 92 | if (0xD800 <= lastChar && lastChar < 0xDC00) 93 | { 94 | // Don't end batch with a high surrogate. 95 | cchBatch -= 1; 96 | if (cchBatch == 0) 97 | { 98 | SaveRemainingChars(pendingChars, cchWritten); 99 | break; 100 | } 101 | } 102 | 103 | if (!WriteConsoleW(m_outputHandle, &pendingChars[cchWritten], cchBatch, &cchBatch, nullptr)) 104 | { 105 | auto lastError = GetLastError(); 106 | throw std::runtime_error("WriteConsoleW error " + std::to_string(lastError)); 107 | } 108 | 109 | assert(cchBatch != 0); 110 | cchWritten += cchBatch; 111 | } 112 | } 113 | 114 | void 115 | TextOutput::ConvertAndAppendBytes( 116 | std::u16string_view pendingChars, 117 | _In_opt_ PCCH pDefaultChar, 118 | _Inout_opt_ bool* pUsedDefaultChar) 119 | { 120 | size_t pendingCharsPos = 0; 121 | LSTATUS status = m_codeConvert.Utf16ToEncoded( 122 | pendingChars, pendingCharsPos, 123 | m_bytes, m_bytesPos, 124 | m_wc2mbFlags, 125 | m_codeConvertUtf ? nullptr : pDefaultChar, 126 | m_codeConvertUtf ? nullptr : pUsedDefaultChar); 127 | if (pendingChars.size() != pendingCharsPos) 128 | { 129 | SaveRemainingChars(pendingChars, pendingCharsPos); 130 | } 131 | 132 | if (status != ERROR_SUCCESS) 133 | { 134 | if (status == ERROR_NO_UNICODE_TRANSLATION) 135 | { 136 | throw std::range_error("Input is not valid UTF-16LE."); 137 | } 138 | else 139 | { 140 | throw std::runtime_error("UTF16-to-MBCS conversion error " + 141 | std::to_string(status) + 142 | "."); 143 | } 144 | } 145 | } 146 | 147 | void 148 | TextOutput::SaveRemainingChars(std::u16string_view pendingChars, size_t pendingCharsPos) 149 | { 150 | assert(pendingCharsPos < pendingChars.size()); 151 | auto const cchRemaining = pendingChars.size() - pendingCharsPos; 152 | 153 | // If we're about to resize, pendingChars better not point into m_chars. 154 | assert( 155 | cchRemaining <= m_chars.size() || 156 | pendingChars.data() + pendingChars.size() < m_chars.data() || 157 | m_chars.data() + m_chars.capacity() < pendingChars.data()); 158 | 159 | EnsureSize(m_chars, cchRemaining); 160 | memmove(m_chars.data(), pendingChars.data() + pendingCharsPos, cchRemaining * sizeof(char16_t)); 161 | 162 | m_charsPos = cchRemaining; 163 | } 164 | 165 | std::u16string_view 166 | TextOutput::ConsumePendingChars(std::u16string_view newChars) 167 | { 168 | if (IsFlagSet(TextOutputFlags::ExpandCRLF)) 169 | { 170 | AppendCharsAndExpandCRLF(newChars); 171 | } 172 | else if (m_charsPos != 0) 173 | { 174 | AppendChars(newChars); 175 | } 176 | else 177 | { 178 | // Don't buffer in m_chars. 179 | return newChars; 180 | } 181 | 182 | auto const charsPos = m_charsPos; 183 | m_charsPos = 0; 184 | return { m_chars.data(), charsPos }; 185 | } 186 | 187 | void 188 | TextOutput::AppendCharsAndExpandCRLF(std::u16string_view newChars) 189 | { 190 | auto const pchSrc = newChars.data(); 191 | auto const cchSrc = newChars.size(); 192 | auto cchDest = m_charsPos; 193 | auto cchDestRequired = cchDest + cchSrc < cchDest 194 | ? ~(size_t)0 // Force exception from EnsureSize. 195 | : cchDest + cchSrc; 196 | 197 | EnsureSize(m_chars, cchDestRequired); 198 | auto pchDest = m_chars.data(); 199 | 200 | for (size_t i = 0; i != cchSrc; i++) 201 | { 202 | auto const ch = pchSrc[i]; 203 | if (ch != L'\n') 204 | { 205 | // Tight loop in the non-LF case. 206 | pchDest[cchDest++] = ch; 207 | } 208 | else 209 | { 210 | // Possible resize needed in the LF case. 211 | cchDestRequired += 1; 212 | EnsureSize(m_chars, cchDestRequired); 213 | pchDest = m_chars.data(); 214 | 215 | pchDest[cchDest++] = L'\r'; 216 | pchDest[cchDest++] = L'\n'; 217 | } 218 | } 219 | 220 | m_charsPos = cchDest; 221 | } 222 | 223 | void 224 | TextOutput::AppendChars(std::u16string_view newChars) 225 | { 226 | EnsureSize(m_chars, m_charsPos, newChars.size()); 227 | memcpy(m_chars.data() + m_charsPos, newChars.data(), newChars.size() * sizeof(char16_t)); 228 | m_charsPos += newChars.size(); 229 | } 230 | 231 | void 232 | TextOutput::OpenHandle( 233 | TextToolsUniqueHandle outputOwner, 234 | _In_ HANDLE outputHandle, 235 | unsigned codePage, 236 | TextOutputFlags flags) 237 | { 238 | auto const fileType = GetFileType(outputHandle); 239 | if (fileType == FILE_TYPE_UNKNOWN) 240 | { 241 | auto lastError = GetLastError(); 242 | if (lastError != ERROR_SUCCESS) 243 | { 244 | throw std::runtime_error("GetFileType error " + std::to_string(lastError)); 245 | } 246 | } 247 | 248 | Close(); 249 | 250 | // Note: Even if the file ends up Console, we want to validate the codePage parameter. 251 | SetCodeConvert(codePage); 252 | m_mode = TextOutputMode::File; 253 | SetFlags(flags); 254 | 255 | m_outputOwner = std::move(outputOwner); 256 | m_outputHandle = outputHandle; 257 | 258 | if (IsFlagSet(TextOutputFlags::CheckConsole) && fileType == FILE_TYPE_CHAR) 259 | { 260 | DWORD consoleMode; 261 | if (GetConsoleMode(m_outputHandle, &consoleMode)) 262 | { 263 | m_codeConvert = CodeConvert(CodePageUtf16LE); 264 | m_codeConvertUtf = true; 265 | m_mode = TextOutputMode::Console; 266 | SetFlags(flags); 267 | goto Done; // Note: Don't write BOM to console. 268 | } 269 | } 270 | 271 | InsertBom(); 272 | 273 | Done: 274 | 275 | return; 276 | } 277 | 278 | TextOutput::~TextOutput() 279 | { 280 | Flush(); 281 | } 282 | 283 | TextOutput::TextOutput() noexcept 284 | : m_bytes() 285 | , m_chars() 286 | , m_outputOwner() 287 | , m_outputHandle() 288 | , m_codeConvert() 289 | , m_codeConvertUtf() 290 | , m_mode() 291 | , m_flags() 292 | , m_wc2mbFlags() 293 | , m_bytesPos() 294 | , m_charsPos() 295 | { 296 | return; 297 | } 298 | 299 | void 300 | TextOutput::Flush() 301 | { 302 | if (m_mode == TextOutputMode::File) 303 | { 304 | FlushFile(); 305 | } 306 | } 307 | 308 | void 309 | TextOutput::Close() noexcept 310 | { 311 | Flush(); 312 | m_outputOwner.reset(); 313 | m_outputHandle = {}; 314 | m_codeConvert = {}; 315 | m_codeConvertUtf = {}; 316 | m_mode = {}; 317 | m_flags = {}; 318 | m_wc2mbFlags = {}; 319 | m_bytesPos = {}; 320 | m_charsPos = {}; 321 | } 322 | 323 | TextOutputMode 324 | TextOutput::Mode() const noexcept 325 | { 326 | return m_mode; 327 | } 328 | 329 | void 330 | TextOutput::OpenChars( 331 | TextOutputFlags flags) 332 | { 333 | Close(); 334 | 335 | m_codeConvert = CodeConvert(CodePageUtf16LE); 336 | m_codeConvertUtf = true; 337 | m_mode = TextOutputMode::Chars; 338 | SetFlags(flags); 339 | 340 | InsertBom(); 341 | } 342 | 343 | void 344 | TextOutput::OpenBytes( 345 | unsigned codePage, 346 | TextOutputFlags flags) 347 | { 348 | Close(); 349 | 350 | SetCodeConvert(codePage); 351 | m_mode = TextOutputMode::Bytes; 352 | SetFlags(flags); 353 | 354 | InsertBom(); 355 | } 356 | 357 | void 358 | TextOutput::OpenBorrowedHandle( 359 | _In_ HANDLE outputHandle, 360 | unsigned codePage, 361 | TextOutputFlags flags) 362 | { 363 | OpenHandle({}, outputHandle, codePage, flags); 364 | } 365 | 366 | LSTATUS 367 | TextOutput::OpenFile( 368 | _In_ PCWSTR outputFile, 369 | unsigned codePage, 370 | TextOutputFlags flags) 371 | { 372 | LSTATUS status; 373 | 374 | HANDLE const outputHandle = CreateFileW( 375 | outputFile, 376 | FILE_WRITE_DATA, 377 | FILE_SHARE_READ | FILE_SHARE_DELETE, 378 | nullptr, 379 | CREATE_ALWAYS, 380 | FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, 381 | nullptr); 382 | if (outputHandle == INVALID_HANDLE_VALUE) 383 | { 384 | status = GetLastError(); 385 | } 386 | else 387 | { 388 | OpenHandle(TextToolsUniqueHandle(outputHandle), outputHandle, codePage, flags); 389 | status = ERROR_SUCCESS; 390 | } 391 | 392 | return status; 393 | } 394 | 395 | std::u16string_view 396 | TextOutput::BufferedChars() const 397 | { 398 | assert(m_mode == TextOutputMode::Chars); 399 | return { m_chars.data(), m_charsPos }; 400 | } 401 | 402 | std::string_view 403 | TextOutput::BufferedBytes() const 404 | { 405 | assert(m_mode == TextOutputMode::Bytes); 406 | return { m_bytes.data(), m_bytesPos }; 407 | } 408 | 409 | void 410 | TextOutput::WriteChars( 411 | std::u16string_view chars, 412 | _In_opt_ PCCH pDefaultChar, 413 | _Inout_opt_ bool* pUsedDefaultChar) 414 | { 415 | switch (m_mode) 416 | { 417 | default: 418 | assert(false); 419 | break; 420 | 421 | case TextOutputMode::Chars: 422 | if (IsFlagSet(TextOutputFlags::ExpandCRLF)) 423 | { 424 | AppendCharsAndExpandCRLF(chars); 425 | } 426 | else 427 | { 428 | AppendChars(chars); 429 | } 430 | break; 431 | 432 | case TextOutputMode::Console: 433 | FlushConsole(ConsumePendingChars(chars)); 434 | break; 435 | 436 | case TextOutputMode::Bytes: 437 | ConvertAndAppendBytes(ConsumePendingChars(chars), pDefaultChar, pUsedDefaultChar); 438 | break; 439 | 440 | case TextOutputMode::File: 441 | ConvertAndAppendBytes(ConsumePendingChars(chars), pDefaultChar, pUsedDefaultChar); 442 | if (m_bytesPos >= 16384) 443 | { 444 | FlushFile(); 445 | } 446 | break; 447 | } 448 | 449 | return; 450 | } 451 | -------------------------------------------------------------------------------- /lib/TextToolsCommon.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Doug Cook. 2 | // Licensed under the MIT License. 3 | 4 | #include "pch.h" 5 | #include 6 | #include "Utility.h" 7 | 8 | void 9 | TextToolsImpl::CloseHandle_delete::operator()(HANDLE h) const noexcept 10 | { 11 | verify(CloseHandle(h)); 12 | } 13 | -------------------------------------------------------------------------------- /lib/TextToolsLib.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | ARM64 7 | 8 | 9 | Debug 10 | Win32 11 | 12 | 13 | Release 14 | ARM64 15 | 16 | 17 | Release 18 | Win32 19 | 20 | 21 | Debug 22 | x64 23 | 24 | 25 | Release 26 | x64 27 | 28 | 29 | 30 | 16.0 31 | Win32Proj 32 | {9edd7581-df10-4284-8418-d873d09ef4a2} 33 | TextToolsLib 34 | 10.0 35 | 36 | 37 | 38 | StaticLibrary 39 | true 40 | v143 41 | Unicode 42 | 43 | 44 | StaticLibrary 45 | false 46 | v143 47 | true 48 | Unicode 49 | 50 | 51 | StaticLibrary 52 | true 53 | v143 54 | Unicode 55 | 56 | 57 | StaticLibrary 58 | true 59 | v143 60 | Unicode 61 | 62 | 63 | StaticLibrary 64 | false 65 | v143 66 | true 67 | Unicode 68 | 69 | 70 | StaticLibrary 71 | false 72 | v143 73 | true 74 | Unicode 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | true 102 | $(SolutionDir)obj$(Platform)\$(Configuration)\ 103 | $(SolutionDir)obj$(Platform)\$(Configuration)\$(ProjectName)\ 104 | true 105 | 106 | 107 | false 108 | $(SolutionDir)obj$(Platform)\$(Configuration)\ 109 | $(SolutionDir)obj$(Platform)\$(Configuration)\$(ProjectName)\ 110 | true 111 | 112 | 113 | true 114 | $(SolutionDir)obj$(Platform)\$(Configuration)\ 115 | $(SolutionDir)obj$(Platform)\$(Configuration)\$(ProjectName)\ 116 | true 117 | 118 | 119 | true 120 | $(SolutionDir)obj$(Platform)\$(Configuration)\ 121 | $(SolutionDir)obj$(Platform)\$(Configuration)\$(ProjectName)\ 122 | true 123 | 124 | 125 | false 126 | $(SolutionDir)obj$(Platform)\$(Configuration)\ 127 | $(SolutionDir)obj$(Platform)\$(Configuration)\$(ProjectName)\ 128 | true 129 | 130 | 131 | false 132 | $(SolutionDir)obj$(Platform)\$(Configuration)\ 133 | $(SolutionDir)obj$(Platform)\$(Configuration)\$(ProjectName)\ 134 | true 135 | 136 | 137 | 138 | Level4 139 | true 140 | WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) 141 | true 142 | Use 143 | pch.h 144 | true 145 | stdcpp20 146 | ../inc 147 | true 148 | 149 | 150 | 151 | 152 | true 153 | 154 | 155 | 156 | 157 | Level4 158 | true 159 | true 160 | true 161 | WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) 162 | true 163 | Use 164 | pch.h 165 | stdcpp20 166 | ../inc 167 | true 168 | MultiThreaded 169 | 170 | 171 | 172 | 173 | true 174 | true 175 | true 176 | 177 | 178 | 179 | 180 | Level4 181 | true 182 | _DEBUG;_LIB;%(PreprocessorDefinitions) 183 | true 184 | Use 185 | pch.h 186 | true 187 | stdcpp20 188 | ../inc 189 | true 190 | 191 | 192 | 193 | 194 | true 195 | 196 | 197 | 198 | 199 | Level4 200 | true 201 | _DEBUG;_LIB;%(PreprocessorDefinitions) 202 | true 203 | Use 204 | pch.h 205 | true 206 | stdcpp20 207 | ../inc 208 | true 209 | 210 | 211 | 212 | 213 | true 214 | 215 | 216 | 217 | 218 | Level4 219 | true 220 | true 221 | true 222 | NDEBUG;_LIB;%(PreprocessorDefinitions) 223 | true 224 | Use 225 | pch.h 226 | stdcpp20 227 | ../inc 228 | true 229 | MultiThreaded 230 | 231 | 232 | 233 | 234 | true 235 | true 236 | true 237 | 238 | 239 | 240 | 241 | Level4 242 | true 243 | true 244 | true 245 | NDEBUG;_LIB;%(PreprocessorDefinitions) 246 | true 247 | Use 248 | pch.h 249 | stdcpp20 250 | ../inc 251 | true 252 | MultiThreaded 253 | 254 | 255 | 256 | 257 | true 258 | true 259 | true 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | Create 282 | Create 283 | Create 284 | Create 285 | Create 286 | Create 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | -------------------------------------------------------------------------------- /lib/TextToolsLib.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | 14 | 15 | Header Files 16 | 17 | 18 | Header Files 19 | 20 | 21 | Header Files 22 | 23 | 24 | Header Files 25 | 26 | 27 | Header Files 28 | 29 | 30 | Header Files 31 | 32 | 33 | Header Files 34 | 35 | 36 | Header Files 37 | 38 | 39 | Header Files 40 | 41 | 42 | Header Files 43 | 44 | 45 | 46 | 47 | Source Files 48 | 49 | 50 | Source Files 51 | 52 | 53 | Source Files 54 | 55 | 56 | Source Files 57 | 58 | 59 | Source Files 60 | 61 | 62 | Source Files 63 | 64 | 65 | Source Files 66 | 67 | 68 | Source Files 69 | 70 | 71 | Source Files 72 | 73 | 74 | -------------------------------------------------------------------------------- /lib/Utility.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Doug Cook. 2 | // Licensed under the MIT License. 3 | 4 | #pragma once 5 | 6 | #undef min 7 | #undef max 8 | 9 | #ifdef NDEBUG 10 | #define verify(expression) (void)(expression) 11 | #else 12 | #define verify(expression) assert(expression) 13 | #endif 14 | 15 | namespace TextToolsImpl 16 | { 17 | template 18 | void 19 | EnsureSize(Str& str, size_t minSize) 20 | { 21 | if (str.size() < minSize) 22 | { 23 | str.reserve(minSize); 24 | str.resize(str.capacity()); 25 | } 26 | } 27 | 28 | template 29 | void 30 | EnsureSize(Str& str, size_t currentPos, size_t appendSize) 31 | { 32 | size_t const minSize = currentPos + appendSize < currentPos 33 | ? ~(size_t)0 // Force exception from reserve(minSize). 34 | : currentPos + appendSize; 35 | if (str.size() < minSize) 36 | { 37 | str.reserve(minSize); 38 | str.resize(str.capacity()); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/pch.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Doug Cook. 2 | // Licensed under the MIT License. 3 | 4 | #include "pch.h" 5 | -------------------------------------------------------------------------------- /lib/pch.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Doug Cook. 2 | // Licensed under the MIT License. 3 | 4 | #pragma once 5 | #ifndef WIN32_LEAN_AND_MEAN 6 | #define WIN32_LEAN_AND_MEAN 7 | #endif 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | -------------------------------------------------------------------------------- /wargs/Program.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Doug Cook. 2 | // Licensed under the MIT License. 3 | 4 | #include "pch.h" 5 | #include "WArgs.h" 6 | 7 | #include 8 | #include 9 | 10 | static int 11 | Usage() 12 | { 13 | fputs(R"( 14 | Usage: wargs [OPTIONS...] COMMAND [PARAMS...] 15 | 16 | Repeatedly invokes "COMMAND PARAMS... ARGS..." with batches of ARGS... read 17 | from input. Similar to the Unix "xargs" tool. 18 | 19 | COMMAND is the first argument that does not start with "-" or "/". 20 | If no COMMAND is specified, the default command is echo (cmd.exe /c echo). 21 | 22 | -0, --null Same as "--delimiter=\0". 23 | -a FILE, --arg-file=FILE Read input from FILE instead of stdin. 24 | -b, --background Do not wait for command to exit. 25 | -c, --iClip Read input from clipboard instead of stdin. 26 | -d CHAR, --delimiter=CHAR Use CHAR instead of whitespace to split up input 27 | into arguments. Disables processing of "-E", 28 | quotes, and backslashes during input. CHAR is 29 | parsed as a C wchar_t literal, e.g. "-d $", 30 | "-d \t", "-d \x0A" are all accepted. 31 | -E EOFSTR, --eof=EOFSTR Stop if any input argument equals EOFSTR. 32 | -eEOFSTR Same as "--eof=EOFSTR" (deprecated). 33 | -f ENCODING, --from-code=... Encoding of input. Use NNN, cpNNN, utf8, 34 | utf8bom, utf16, utf16bom, utf16be, etc. 35 | Default: cp0bom (CP_ACP unless BOM present). 36 | -I REPLSTR, --replace=... Replace instances of REPLSTR in PARAMS... with 37 | line read from input. Splits input at newlines. 38 | -i[REPLSTR] Same as "--replace=REPLSTR" (deprecated). 39 | -L MAXLINES, --max-lines=... Limits each batch to MAXLINES lines of input. 40 | -l[MAXLINES] Same as "--max-lines=MAXLINES" (deprecated). 41 | -n MAXARGS, --max-args=... Limits each batch to MAXARGS arguments. 42 | -o, --open-tty Start COMMAND with stdin = console (CONIN$). 43 | -P MAXPROCS, --max-procs=... Start up to MAXPROCS batches in parallel. 44 | -p, --interactive Prompts for Y from console (CONIN$) before each 45 | batch. 46 | --process-slot-var=VAR Set environment variable VAR to the parallelism 47 | ID. Useful when MAXPROCS > 1. 48 | -Q, --no-quote-args When invoking COMMAND, do not add quotes around 49 | arguments that contain whitespace. 50 | -r, --no-run-if-empty Disable the standard behavior of running COMMAND 51 | once if there are no ARGS. 52 | -s MAXCHARS, --max-chars=... Limits each batch's command length to MAXCHARS. 53 | --show-limits Output the limits of this implementation before 54 | running any commands. 55 | -t, --verbose Output command line to stderr before each batch. 56 | -x, --exit Exit instead of skipping the argument if the 57 | argument would force the command line to exceed 58 | MAXCHARS. 59 | -h, -?, --help Show this usage information and then exit. 60 | --version Show the version number of wargs and then exit. 61 | 62 | ENCODING names ignore case and punctuation (e.g. 'utf-8' is the same as 63 | 'UTF8'). They may be formatted as digits (Windows code page identifier), 'CP' 64 | followed by digits, or 'UTF' followed by '8', '16', '32', '16LE', '16BE', 65 | '32LE', or '32BE'. Input encodings may have a 'BOM' suffix indicating that if 66 | the input starts with a BOM, the BOM should be consumed and the corresponding 67 | UTF encoding should override the specified encoding. 68 | )", stdout); 69 | 70 | return 1; 71 | } 72 | 73 | static int 74 | Version() 75 | { 76 | fputs(TEXTTOOLS_VERSION_STR("wargs"), stdout); 77 | return 1; 78 | } 79 | 80 | int __cdecl 81 | wmain(int argc, _In_count_(argc) PWSTR argv[]) 82 | { 83 | int exitCode; 84 | 85 | try 86 | { 87 | WArgs wargs; 88 | bool showHelp = false; 89 | bool showVersion = false; 90 | exitCode = 0; 91 | 92 | ArgParser ap(AppName, argc, argv); 93 | while (ap.MoveNextArg()) 94 | { 95 | std::wstring_view val; 96 | unsigned uval; 97 | if (ap.BeginDashDashArg()) 98 | { 99 | if (ap.CurrentArgNameMatches(1, L"arg-file")) 100 | { 101 | if (ap.GetLongArgVal(val, false)) 102 | { 103 | wargs.SetInputFilename(val, "--arg-file"); 104 | } 105 | } 106 | else if (ap.CurrentArgNameMatches(1, L"background")) 107 | { 108 | wargs.SetBackground("--background"); 109 | } 110 | else if (ap.CurrentArgNameMatches(1, L"delimiter")) 111 | { 112 | if (ap.GetLongArgVal(val, false)) 113 | { 114 | ap.SetArgErrorIfFalse(wargs.SetDelimiter(val, "--delimiter")); 115 | } 116 | } 117 | else if (ap.CurrentArgNameMatches(2, L"eof")) 118 | { 119 | if (ap.GetLongArgVal(val, true)) 120 | { 121 | wargs.SetEofStr(val, "--eof"); 122 | } 123 | } 124 | else if (ap.CurrentArgNameMatches(2, L"exit")) 125 | { 126 | wargs.SetExitIfSizeExceeded(); 127 | } 128 | else if (ap.CurrentArgNameMatches(1, L"from-code")) 129 | { 130 | if (ap.GetLongArgVal(val, false)) 131 | { 132 | ap.SetArgErrorIfFalse(wargs.SetInputEncoding(val, "--from-code")); 133 | } 134 | } 135 | else if (ap.CurrentArgNameMatches(1, L"help")) 136 | { 137 | showHelp = true; 138 | } 139 | else if (ap.CurrentArgNameMatches(2, L"iclipboard")) 140 | { 141 | wargs.SetInputClipboard("--iclip"); 142 | } 143 | else if (ap.CurrentArgNameMatches(2, L"interactive")) 144 | { 145 | wargs.SetInteractive(); 146 | } 147 | else if (ap.CurrentArgNameMatches(5, L"max-args")) 148 | { 149 | if (ap.GetLongArgVal(uval, false, 10)) 150 | { 151 | wargs.SetMaxArgs(uval, "--max-args"); 152 | } 153 | } 154 | else if (ap.CurrentArgNameMatches(5, L"max-chars")) 155 | { 156 | if (ap.GetLongArgVal(uval, false, 10)) 157 | { 158 | wargs.SetMaxChars(uval, "--max-chars"); 159 | } 160 | } 161 | else if (ap.CurrentArgNameMatches(5, L"max-lines")) 162 | { 163 | uval = 1; 164 | if (nullptr == ap.GetLongArgVal() || // If value is absent, default to 1. 165 | ap.GetLongArgVal(uval, false, 10)) // If value is present, it must not be 0 or empty. 166 | { 167 | wargs.SetMaxLines(uval, "--max-lines"); 168 | } 169 | } 170 | else if (ap.CurrentArgNameMatches(5, L"max-procs")) 171 | { 172 | if (ap.GetLongArgVal(uval, true, 10)) 173 | { 174 | wargs.SetMaxProcs(uval, "--max-procs"); 175 | } 176 | } 177 | else if (ap.CurrentArgNameMatches(4, L"no-run-if-empty")) 178 | { 179 | wargs.SetNoRunIfEmpty(); 180 | } 181 | else if (ap.CurrentArgNameMatches(4, L"no-quote-args")) 182 | { 183 | wargs.SetNoQuoteArgs(); 184 | } 185 | else if (ap.CurrentArgNameMatches(2, L"null")) 186 | { 187 | ap.SetArgErrorIfFalse(wargs.SetDelimiter(L"\\0", "--null")); 188 | } 189 | else if (ap.CurrentArgNameMatches(1, L"open-tty")) 190 | { 191 | wargs.SetOpenTty(); 192 | } 193 | else if (ap.CurrentArgNameMatches(1, L"process-slot-var")) 194 | { 195 | if (ap.GetLongArgVal(val, false)) 196 | { 197 | ap.SetArgErrorIfFalse(wargs.SetProcessSlotVar(val, "--process-slot-var")); 198 | } 199 | } 200 | else if (ap.CurrentArgNameMatches(1, L"replace")) 201 | { 202 | val = L"{}"; 203 | if (nullptr == ap.GetLongArgVal() || // If value is absent, default to "{}". 204 | ap.GetLongArgVal(val, false)) // If value is present, it must not be empty. 205 | { 206 | wargs.SetReplaceStr(val, "--replace"); 207 | } 208 | } 209 | else if (ap.CurrentArgNameMatches(1, L"show-limits")) 210 | { 211 | wargs.SetShowLimits(); 212 | } 213 | else if (ap.CurrentArgNameMatches(4, L"verbose")) 214 | { 215 | wargs.SetVerbose(); 216 | } 217 | else if (ap.CurrentArgNameMatches(4, L"version")) 218 | { 219 | showVersion = true; 220 | } 221 | else 222 | { 223 | ap.PrintLongArgError(); 224 | } 225 | } 226 | else if (ap.BeginDashOrSlashArg()) 227 | { 228 | while (ap.MoveNextArgChar()) 229 | { 230 | switch (ap.CurrentArgChar()) 231 | { 232 | case '?': 233 | case 'h': 234 | showHelp = true; 235 | break; 236 | case '0': 237 | val = L"\\0"; 238 | ap.SetArgErrorIfFalse(wargs.SetDelimiter(val, "-0")); 239 | break; 240 | case 'a': 241 | if (ap.ReadShortArgVal(val, false)) 242 | { 243 | wargs.SetInputFilename(val, "-a"); 244 | } 245 | break; 246 | case 'b': 247 | wargs.SetBackground("-b"); 248 | break; 249 | case 'c': 250 | wargs.SetInputClipboard("-c"); 251 | break; 252 | case 'd': 253 | if (ap.ReadShortArgVal(val, false)) 254 | { 255 | ap.SetArgErrorIfFalse(wargs.SetDelimiter(val, "-d")); 256 | } 257 | break; 258 | case 'E': 259 | if (ap.ReadShortArgVal(val, true)) 260 | { 261 | wargs.SetEofStr(val, "-E"); 262 | } 263 | break; 264 | case 'e': 265 | wargs.SetEofStr(ap.ReadArgCharsVal(), "-e"); 266 | break; 267 | case 'f': 268 | if (ap.ReadShortArgVal(val, false)) 269 | { 270 | ap.SetArgErrorIfFalse(wargs.SetInputEncoding(val, "-f")); 271 | } 272 | break; 273 | case 'I': 274 | if (ap.ReadShortArgVal(val, false)) 275 | { 276 | wargs.SetReplaceStr(val, "-I"); 277 | } 278 | break; 279 | case 'i': 280 | val = ap.ReadArgCharsVal(); 281 | if (val.empty()) 282 | { 283 | val = L"{}"; 284 | } 285 | wargs.SetReplaceStr(val, "-i"); 286 | break; 287 | case 'L': 288 | if (ap.ReadShortArgVal(uval, false, 10)) 289 | { 290 | wargs.SetMaxLines(uval, "-L"); 291 | } 292 | break; 293 | case 'l': 294 | if (ap.CurrentArgPos()[1] == 0) 295 | { 296 | wargs.SetMaxLines(1, "-l"); 297 | } 298 | else if (ap.ReadArgCharsVal(uval, false, 10)) 299 | { 300 | wargs.SetMaxLines(uval, "-l"); 301 | } 302 | break; 303 | case 'n': 304 | if (ap.ReadShortArgVal(uval, false, 10)) 305 | { 306 | wargs.SetMaxArgs(uval, "-n"); 307 | } 308 | break; 309 | case 'o': 310 | wargs.SetOpenTty(); 311 | break; 312 | case 'P': 313 | if (ap.ReadShortArgVal(uval, true, 10)) 314 | { 315 | wargs.SetMaxProcs(uval, "-P"); 316 | } 317 | break; 318 | case 'p': 319 | wargs.SetInteractive(); 320 | break; 321 | case 'Q': 322 | wargs.SetNoQuoteArgs(); 323 | break; 324 | case 'r': 325 | wargs.SetNoRunIfEmpty(); 326 | break; 327 | case 's': 328 | if (ap.ReadShortArgVal(uval, false, 10)) 329 | { 330 | wargs.SetMaxChars(uval, "-s"); 331 | } 332 | break; 333 | case 't': 334 | wargs.SetVerbose(); 335 | break; 336 | case 'x': 337 | wargs.SetExitIfSizeExceeded(); 338 | break; 339 | default: 340 | ap.PrintShortArgError(); 341 | break; 342 | } 343 | } 344 | } 345 | else 346 | { 347 | ap.SetArgErrorIfFalse(wargs.SetCommandAndInitialArgs( 348 | &argv[ap.CurrentArgIndex()], 349 | ap.ArgCount() - ap.CurrentArgIndex())); 350 | break; 351 | } 352 | } 353 | 354 | ap.SetArgErrorIfFalse(wargs.FinalizeParameters()); 355 | 356 | if (showHelp) 357 | { 358 | exitCode = Usage(); 359 | } 360 | else if (showVersion) 361 | { 362 | exitCode = Version(); 363 | } 364 | else if (ap.ArgError()) 365 | { 366 | fprintf(stderr, "%hs: error : Invalid command-line. Use '%hs --help' for more information.\n", 367 | AppName, AppName); 368 | exitCode = 1; 369 | } 370 | else 371 | { 372 | exitCode = wargs.Run(); 373 | } 374 | } 375 | catch (std::exception const& ex) 376 | { 377 | fprintf(stderr, "%hs: fatal error : %hs\n", 378 | AppName, ex.what()); 379 | exitCode = 1; 380 | } 381 | 382 | return exitCode; 383 | } 384 | -------------------------------------------------------------------------------- /wargs/TokenReader.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Doug Cook. 2 | // Licensed under the MIT License. 3 | 4 | #include "pch.h" 5 | #include "TokenReader.h" 6 | 7 | int 8 | TokenReader::CharPeek() 9 | { 10 | return m_charsUsed < m_chars.size() 11 | ? m_chars[m_charsUsed] 12 | : CharPeekRefill(); 13 | } 14 | 15 | int 16 | TokenReader::CharPeekRefill() 17 | { 18 | assert(m_charsUsed == m_chars.size()); 19 | 20 | int returnChar; 21 | if (m_controlZ) 22 | { 23 | m_chars = {}; 24 | m_charsUsed = 0; 25 | returnChar = -1; 26 | } 27 | else 28 | { 29 | m_input.ReadNextChars(); 30 | InitInputChars(); 31 | returnChar = m_chars.empty() 32 | ? -1 33 | : m_chars[m_charsUsed]; 34 | } 35 | 36 | return returnChar; 37 | } 38 | 39 | void 40 | TokenReader::InitInputChars() noexcept 41 | { 42 | m_chars = m_input.Chars(); 43 | m_charsUsed = 0; 44 | 45 | if (m_input.Mode() == TextInputMode::Console && 46 | m_chars.ends_with(L'\x1A')) // Control-Z 47 | { 48 | m_chars.remove_suffix(1); 49 | m_controlZ = true; 50 | } 51 | } 52 | 53 | void 54 | TokenReader::CharConsume() noexcept 55 | { 56 | assert(m_charsUsed < m_chars.size()); 57 | m_charsUsed += 1; 58 | } 59 | 60 | bool 61 | TokenReader::SkipLeadingWhitespace() 62 | { 63 | for (;;) 64 | { 65 | int peek = CharPeek(); 66 | switch (peek) 67 | { 68 | case -1: 69 | return false; 70 | 71 | case L' ': // IsBlank 72 | case L'\t': 73 | case L'\n': 74 | CharConsume(); 75 | continue; 76 | 77 | default: 78 | return true; 79 | } 80 | } 81 | } 82 | 83 | bool 84 | TokenReader::AppendQuoted(std::wstring& value, wchar_t delimiter) 85 | { 86 | bool token = false; 87 | 88 | for (;;) 89 | { 90 | int peek = CharPeek(); 91 | if (peek < 0) 92 | { 93 | break; 94 | } 95 | CharConsume(); 96 | 97 | token = true; 98 | 99 | if (peek == delimiter) 100 | { 101 | break; 102 | } 103 | 104 | value.push_back(static_cast(peek)); 105 | } 106 | 107 | return token; 108 | } 109 | 110 | bool 111 | TokenReader::AppendEscaped(std::wstring& value) 112 | { 113 | bool token; 114 | 115 | int peek = CharPeek(); 116 | if (peek < 0) 117 | { 118 | token = false; 119 | } 120 | else 121 | { 122 | CharConsume(); 123 | value.push_back(static_cast(peek)); 124 | token = true; 125 | } 126 | 127 | return token; 128 | } 129 | 130 | TokenReader::TokenReader(TextInput&& input, wchar_t delimiter) 131 | : m_input(std::move(input)) 132 | , m_chars() 133 | , m_charsUsed() 134 | , m_lineCount() 135 | , m_tokenCount() 136 | , m_controlZ() 137 | , m_delimiter(delimiter) 138 | { 139 | InitInputChars(); 140 | } 141 | 142 | void 143 | TokenReader::ResetCounts() noexcept 144 | { 145 | m_lineCount = 0; 146 | m_tokenCount = 0; 147 | } 148 | 149 | unsigned 150 | TokenReader::LineCount() const noexcept 151 | { 152 | return m_lineCount; 153 | } 154 | 155 | unsigned 156 | TokenReader::TokenCount() const noexcept 157 | { 158 | return m_tokenCount; 159 | } 160 | 161 | bool 162 | TokenReader::ReadDelimited(std::wstring& value) 163 | { 164 | bool token = false; 165 | value.clear(); 166 | 167 | for (;;) 168 | { 169 | auto const peek = CharPeek(); 170 | if (peek < 0) 171 | { 172 | break; 173 | } 174 | 175 | CharConsume(); 176 | token = true; 177 | 178 | auto const ch = static_cast(peek); 179 | if (ch == m_delimiter) 180 | { 181 | break; 182 | } 183 | 184 | value.push_back(ch); 185 | } 186 | 187 | m_lineCount += token; 188 | m_tokenCount += token; 189 | return token; 190 | } 191 | 192 | template 193 | bool 194 | TokenReader::ReadEscaped(std::wstring& value) 195 | { 196 | bool token = false; 197 | value.clear(); 198 | 199 | if (!SkipLeadingWhitespace()) 200 | { 201 | goto Done; 202 | } 203 | 204 | for (;;) 205 | { 206 | auto const peek = CharPeek(); 207 | switch (peek) 208 | { 209 | case -1: 210 | goto Done; 211 | 212 | case L' ': // IsBlank 213 | case L'\t': 214 | assert(token); 215 | if constexpr (SplitOnBlank) 216 | { 217 | // Don't consume. 218 | goto Done; 219 | } 220 | else 221 | { 222 | CharConsume(); 223 | value.push_back(static_cast(peek)); 224 | token = true; 225 | break; 226 | } 227 | 228 | case L'\n': 229 | assert(token); 230 | CharConsume(); 231 | m_lineCount += 1; 232 | goto Done; 233 | 234 | case L'\\': 235 | CharConsume(); 236 | if (!AppendEscaped(value)) 237 | { 238 | goto Done; 239 | } 240 | token = true; 241 | break; 242 | 243 | case L'"': 244 | case L'\'': 245 | CharConsume(); 246 | if (!AppendQuoted(value, static_cast(peek))) 247 | { 248 | goto Done; 249 | } 250 | token = true; 251 | break; 252 | 253 | default: 254 | CharConsume(); 255 | value.push_back(static_cast(peek)); 256 | token = true; 257 | break; 258 | } 259 | } 260 | 261 | Done: 262 | 263 | m_tokenCount += token; 264 | return token; 265 | } 266 | 267 | bool 268 | TokenReader::ReadEscapedToken(std::wstring& value) 269 | { 270 | return ReadEscaped(value); 271 | } 272 | 273 | bool 274 | TokenReader::ReadEscapedLine(std::wstring& value) 275 | { 276 | return ReadEscaped(value); 277 | } 278 | -------------------------------------------------------------------------------- /wargs/TokenReader.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Doug Cook. 2 | // Licensed under the MIT License. 3 | 4 | #pragma once 5 | #include 6 | 7 | class TokenReader 8 | { 9 | TextInput m_input; 10 | std::u16string_view m_chars; 11 | size_t m_charsUsed; 12 | unsigned m_lineCount; 13 | unsigned m_tokenCount; 14 | bool m_controlZ; 15 | wchar_t const m_delimiter; 16 | 17 | int 18 | CharPeek(); 19 | 20 | int 21 | CharPeekRefill(); 22 | 23 | void 24 | InitInputChars() noexcept; 25 | 26 | void 27 | CharConsume() noexcept; 28 | 29 | /* 30 | Returns false for EOF, true otherwise. 31 | */ 32 | bool 33 | SkipLeadingWhitespace(); 34 | 35 | /* 36 | Returns false for EOF, true otherwise. 37 | */ 38 | bool 39 | AppendQuoted(std::wstring& value, wchar_t delimiter); 40 | 41 | bool 42 | AppendEscaped(std::wstring& value); 43 | 44 | template 45 | bool 46 | ReadEscaped(std::wstring& value); 47 | 48 | public: 49 | 50 | TokenReader(TextInput&& input, wchar_t delimiter); 51 | 52 | void 53 | ResetCounts() noexcept; 54 | 55 | unsigned 56 | LineCount() const noexcept; 57 | 58 | unsigned 59 | TokenCount() const noexcept; 60 | 61 | /* 62 | Split at delimiter. No unescaping. 63 | */ 64 | bool 65 | ReadDelimited(std::wstring& value); 66 | 67 | /* 68 | Split on unquoted blank, unescaped blank, or unescaped newline. 69 | */ 70 | bool 71 | ReadEscapedToken(std::wstring& value); 72 | 73 | /* 74 | Trim leading blank. Split on unescaped newline. 75 | */ 76 | bool 77 | ReadEscapedLine(std::wstring& value); 78 | }; -------------------------------------------------------------------------------- /wargs/WArgs.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Doug Cook. 2 | // Licensed under the MIT License. 3 | 4 | #pragma once 5 | 6 | enum class CodePageCategory : UINT8; 7 | 8 | class WArgs 9 | { 10 | class Context; 11 | 12 | enum ExitCode : int 13 | { 14 | ExitCodeSuccess = 0, 15 | ExitCodeOtherError = 1, 16 | ExitCodeFatalOtherError = -1, 17 | ExitCodeCommandError = 123, 18 | ExitCodeFatalCommandError = -124, 19 | ExitCodeCommandKilled = 125, 20 | ExitCodeFatalCommandCannotRun = -126, 21 | ExitCodeFatalCommandNotFound = -127, 22 | }; 23 | 24 | struct Encoding 25 | { 26 | unsigned CodePage; 27 | bool Bom; 28 | bool Specified; 29 | }; 30 | 31 | std::wstring m_command; 32 | std::vector m_initialArgs; 33 | std::wstring m_inputFilename; // -a, --arg-file 34 | std::wstring m_eofStr; // -E, --eof 35 | std::wstring m_processSlotVar; // --process-slot-var 36 | std::wstring m_replaceStr; // -I, --replace 37 | Encoding m_inputEncoding = {}; // -f, --from-code 38 | unsigned m_maxLines = 0; // -L, --max-lines 39 | unsigned m_maxArgs = 0; // -n, --max-args 40 | unsigned m_maxChars = 0; // -s, --max-chars 41 | int m_delimiter = -1; // -d, --delimiter 42 | INT8 m_maxProcs = -1; // -P, --max-procs 43 | bool m_background = 0; // -b, --background 44 | bool m_interactive = 0; // -p, --interactive 45 | bool m_noRunIfEmpty = 0; // -r, --no-run-if-empty 46 | bool m_openTty = 0; // -o, --open-tty 47 | bool m_verbose = 0; // -t, --verbose 48 | bool m_exitIfSizeExceeded = 0; // -x, --exit 49 | bool m_showLimits = 0; // --show-limits 50 | bool m_noQuoteArgs = 0; // -Q, --no-quote-args 51 | 52 | private: 53 | 54 | static [[nodiscard]] CodePageCategory 55 | ParseEncoding(std::wstring_view value, PCSTR argName, _Inout_ Encoding* pEncoding); 56 | 57 | void 58 | EscapeArg(std::wstring& escapedArg, std::wstring_view arg) const; 59 | 60 | public: 61 | 62 | [[nodiscard]] bool 63 | SetCommandAndInitialArgs(PCWSTR const pArgs[], unsigned cArgs); 64 | 65 | [[nodiscard]] bool 66 | SetInputEncoding(std::wstring_view value, PCSTR argName); 67 | 68 | void 69 | SetInputFilename(std::wstring_view value, PCSTR argName); 70 | 71 | void 72 | SetInputClipboard(PCSTR argName); 73 | 74 | void 75 | SetEofStr(std::wstring_view value, PCSTR argName); 76 | 77 | [[nodiscard]] bool 78 | SetProcessSlotVar(std::wstring_view value, PCSTR argName); 79 | 80 | void 81 | SetReplaceStr(std::wstring_view value, PCSTR argName); 82 | 83 | void 84 | SetMaxLines(unsigned value, PCSTR argName); 85 | 86 | void 87 | SetMaxArgs(unsigned value, PCSTR argName); 88 | 89 | void 90 | SetOpenTty(); 91 | 92 | void 93 | SetMaxProcs(unsigned value, PCSTR argName); 94 | 95 | void 96 | SetBackground(PCSTR argName); 97 | 98 | void 99 | SetInteractive(); 100 | 101 | void 102 | SetNoRunIfEmpty(); 103 | 104 | void 105 | SetNoQuoteArgs(); 106 | 107 | void 108 | SetMaxChars(unsigned value, PCSTR argName); 109 | 110 | void 111 | SetVerbose(); 112 | 113 | void 114 | SetExitIfSizeExceeded(); 115 | 116 | void 117 | SetShowLimits() noexcept; 118 | 119 | [[nodiscard]] bool 120 | SetDelimiter(std::wstring_view value, PCSTR argName) noexcept; 121 | 122 | [[nodiscard]] bool 123 | FinalizeParameters(); 124 | 125 | /* 126 | Returns: 127 | 0 = Success. 128 | 123 = One or more commands exited with error status other than 255 (non-fatal). 129 | 124 = A command exited with error status 255 (fatal). 130 | 126 = A command cannot be run (fatal). 131 | 127 = A command is not found (fatal). 132 | */ 133 | [[nodiscard]] unsigned 134 | Run() const; 135 | }; 136 | -------------------------------------------------------------------------------- /wargs/WArgsContext.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Doug Cook. 2 | // Licensed under the MIT License. 3 | 4 | #include "pch.h" 5 | #include "WArgsContext.h" 6 | 7 | WArgs::Context:: ~Context() 8 | { 9 | WaitForAllProcessesToExit(); 10 | } 11 | 12 | WArgs::Context::Context(WArgs const& wargs, bool useStdIn) 13 | : m_wargs(wargs) 14 | , m_slotHandles(wargs.m_maxProcs) 15 | , m_slotCount(wargs.m_maxProcs) 16 | , m_slotsActive() 17 | , m_exitCode() 18 | , m_conin() 19 | , m_nul() 20 | , m_hTtyForPrompt() 21 | , m_hStdInputForChild() 22 | { 23 | assert(wargs.m_maxProcs <= MAXIMUM_WAIT_OBJECTS); 24 | 25 | if (m_wargs.m_interactive || m_wargs.m_openTty) 26 | { 27 | m_conin = OpenInputDevice(L"CONIN$"); 28 | } 29 | 30 | m_hTtyForPrompt = m_wargs.m_interactive 31 | ? m_conin.get() 32 | : nullptr; 33 | 34 | if (m_wargs.m_openTty) 35 | { 36 | m_hStdInputForChild = m_conin.get(); 37 | } 38 | else 39 | { 40 | auto const hStdInput = useStdIn ? GetStdHandle(STD_INPUT_HANDLE) : nullptr; 41 | if (hStdInput != nullptr && 42 | hStdInput != INVALID_HANDLE_VALUE) 43 | { 44 | m_hStdInputForChild = hStdInput; 45 | } 46 | else 47 | { 48 | m_nul = OpenInputDevice(L"NUL"); 49 | m_hStdInputForChild = m_nul.get(); 50 | } 51 | } 52 | 53 | return; 54 | } 55 | 56 | bool 57 | WArgs::Context::ExitCodeIsFatal() const noexcept 58 | { 59 | return m_exitCode < 0; 60 | } 61 | 62 | int 63 | WArgs::Context::UnsignedExitCode() const noexcept 64 | { 65 | return abs(m_exitCode); 66 | } 67 | 68 | void 69 | WArgs::Context::AccumulateExitCode(ExitCode current) noexcept 70 | { 71 | if (current != 0) 72 | { 73 | if (m_exitCode == 0) 74 | { 75 | m_exitCode = current; 76 | } 77 | else if (m_exitCode > 0 && current < 0) 78 | { 79 | m_exitCode = current; 80 | } 81 | } 82 | } 83 | 84 | void 85 | WArgs::Context::WaitForAllProcessesToExit() 86 | { 87 | while (m_slotsActive) 88 | { 89 | WaitForProcessExit(true); 90 | } 91 | } 92 | 93 | void 94 | WArgs::Context::StartProcess(_In_ PWSTR commandLine) 95 | { 96 | assert(!ExitCodeIsFatal()); 97 | assert(commandLine[0] != 0); 98 | assert(m_hStdInputForChild); 99 | 100 | UINT8 slotIndex; 101 | if (m_wargs.m_background) 102 | { 103 | slotIndex = 0; 104 | } 105 | else if (!AcquireSlotIndex(&slotIndex)) 106 | { 107 | return; 108 | } 109 | 110 | if (m_wargs.m_interactive) 111 | { 112 | assert(m_hTtyForPrompt); 113 | 114 | WCHAR consoleInput[10]; 115 | fprintf(stderr, "%ls?...", commandLine); 116 | 117 | DWORD cchRead = 0; 118 | if (!ReadConsoleW(m_hTtyForPrompt, &consoleInput, ARRAYSIZE(consoleInput), &cchRead, nullptr) || 119 | cchRead == 0 || 120 | (consoleInput[0] != L'y' && consoleInput[0] != 'Y')) 121 | { 122 | return; 123 | } 124 | } 125 | else if (m_wargs.m_verbose) 126 | { 127 | fprintf(stderr, "%ls\n", commandLine); 128 | } 129 | 130 | WCHAR slotString[3]; 131 | swprintf_s(slotString, L"%0x", slotIndex); 132 | if (!m_wargs.m_processSlotVar.empty() && 133 | !SetEnvironmentVariableW(m_wargs.m_processSlotVar.c_str(), slotString)) 134 | { 135 | fprintf(stderr, "%hs: error : SetEnvironmentVariableW(%ls) error %u.\n", 136 | AppName, m_wargs.m_processSlotVar.c_str(), GetLastError()); 137 | AccumulateExitCode(ExitCodeOtherError); 138 | } 139 | else 140 | { 141 | STARTUPINFOW si = { sizeof(si) }; 142 | si.dwFlags = STARTF_USESTDHANDLES; 143 | si.hStdInput = m_hStdInputForChild; 144 | si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); 145 | si.hStdError = GetStdHandle(STD_ERROR_HANDLE); 146 | 147 | PROCESS_INFORMATION pi = {}; 148 | if (!CreateProcessW( 149 | nullptr, 150 | commandLine, 151 | nullptr, 152 | nullptr, 153 | true, // Inherit handles 154 | 0, 155 | nullptr, // Environment 156 | nullptr, // Current directory 157 | &si, &pi)) 158 | { 159 | fprintf(stderr, "%hs: error : CreateProcessW error %u starting %ls\n", 160 | AppName, GetLastError(), m_wargs.m_command.c_str()); 161 | AccumulateExitCode(ExitCodeFatalCommandNotFound); 162 | } 163 | else 164 | { 165 | TextToolsUniqueHandle hProcess(pi.hProcess); 166 | TextToolsUniqueHandle hThread(pi.hThread); 167 | if (!m_wargs.m_background) 168 | { 169 | SetSlot(slotIndex, std::move(hProcess)); 170 | } 171 | } 172 | } 173 | } 174 | 175 | TextToolsUniqueHandle 176 | WArgs::Context::OpenInputDevice(PCWSTR name) noexcept 177 | { 178 | HANDLE hDevice = CreateFileW( 179 | name, 180 | GENERIC_READ, 181 | FILE_SHARE_READ | FILE_SHARE_WRITE, 182 | nullptr, 183 | OPEN_EXISTING, 184 | 0, 185 | nullptr); 186 | if (hDevice == INVALID_HANDLE_VALUE) 187 | { 188 | fprintf(stderr, "%hs: error : CreateFile error %u opening '%ls'.\n", 189 | AppName, GetLastError(), name); 190 | AccumulateExitCode(ExitCodeFatalOtherError); 191 | hDevice = nullptr; 192 | } 193 | else if (!DuplicateHandle( 194 | GetCurrentProcess(), hDevice, 195 | GetCurrentProcess(), &hDevice, 196 | 0, 197 | true, // Make inheritable. 198 | DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) 199 | { 200 | fprintf(stderr, "%hs: error : DuplicateHandle error %u opening '%ls'.\n", 201 | AppName, GetLastError(), name); 202 | AccumulateExitCode(ExitCodeFatalOtherError); 203 | hDevice = nullptr; 204 | } 205 | 206 | return TextToolsUniqueHandle(hDevice); 207 | } 208 | 209 | _Success_(return) bool 210 | WArgs::Context::AcquireSlotIndex(_Out_ UINT8* pSlotIndex) 211 | { 212 | UINT8 slotIndex; 213 | bool ok; 214 | if (!WaitForProcessExit(m_slotsActive == m_slotCount)) 215 | { 216 | slotIndex = 0; 217 | ok = false; 218 | } 219 | else 220 | { 221 | assert(m_slotsActive < m_slotCount); 222 | for (slotIndex = 0; slotIndex != m_slotCount; slotIndex += 1) 223 | { 224 | if (!m_slotHandles[slotIndex]) 225 | { 226 | break; 227 | } 228 | } 229 | 230 | assert(slotIndex < m_slotCount); 231 | ok = true; 232 | } 233 | 234 | *pSlotIndex = slotIndex; 235 | return ok; 236 | } 237 | 238 | void 239 | WArgs::Context::SetSlot(UINT8 slotIndex, TextToolsUniqueHandle value) noexcept 240 | { 241 | assert(slotIndex < m_slotCount); 242 | assert(value); 243 | assert(!m_slotHandles[slotIndex]); 244 | assert(m_slotsActive < m_slotCount); 245 | m_slotHandles[slotIndex] = std::move(value); 246 | m_slotsActive += 1; 247 | } 248 | 249 | TextToolsUniqueHandle 250 | WArgs::Context::ClearSlot(UINT8 slotIndex) noexcept 251 | { 252 | assert(slotIndex < m_slotCount); 253 | assert(m_slotHandles[slotIndex]); 254 | assert(m_slotsActive != 0); 255 | m_slotsActive -= 1; 256 | return std::move(m_slotHandles[slotIndex]); 257 | } 258 | 259 | bool 260 | WArgs::Context::WaitForProcessExit(bool block) 261 | { 262 | HANDLE handles[MAXIMUM_WAIT_OBJECTS]; 263 | UINT8 handleIndexToSlotIndex[MAXIMUM_WAIT_OBJECTS]; 264 | DWORD timeout = block ? INFINITE : 0; 265 | 266 | // Loop until no objects signaled. 267 | while (m_slotsActive) 268 | { 269 | UINT8 handleIndex = 0; 270 | for (UINT8 slotIndex = 0; slotIndex != m_slotCount; slotIndex++) 271 | { 272 | auto const slotHandle = m_slotHandles[slotIndex].get(); 273 | if (slotHandle) 274 | { 275 | handles[handleIndex] = slotHandle; 276 | handleIndexToSlotIndex[handleIndex] = slotIndex; 277 | handleIndex += 1; 278 | } 279 | } 280 | assert(handleIndex == m_slotsActive); 281 | 282 | auto const waitResult = WaitForMultipleObjects(handleIndex, handles, false, timeout); 283 | timeout = 0; // Block only first time through the loop. 284 | 285 | if (waitResult > WAIT_OBJECT_0 + handleIndex) 286 | { 287 | switch (waitResult) 288 | { 289 | case WAIT_TIMEOUT: 290 | break; 291 | case WAIT_FAILED: 292 | fprintf(stderr, "%hs: error : WaitForMultipleObjects failed with code %u.\n", 293 | AppName, GetLastError()); 294 | AccumulateExitCode(ExitCodeFatalOtherError); 295 | break; 296 | default: 297 | fprintf(stderr, "%hs: error : WaitForMultipleObjects returned unexpected result %u.\n", 298 | AppName, waitResult); 299 | AccumulateExitCode(ExitCodeFatalOtherError); 300 | break; 301 | } 302 | break; 303 | } 304 | 305 | auto slotIndex = handleIndexToSlotIndex[waitResult - WAIT_OBJECT_0]; 306 | auto hProcess = ClearSlot(slotIndex); 307 | 308 | DWORD processExitCode = 0; 309 | if (!GetExitCodeProcess(hProcess.get(), &processExitCode)) 310 | { 311 | fprintf(stderr, "%hs: warning : GetExitCodeProcess failed with code %u.\n", 312 | AppName, GetLastError()); 313 | } 314 | else if (processExitCode == 255) 315 | { 316 | fprintf(stderr, "%hs: error : process exit code %u (0x%x).\n", 317 | AppName, processExitCode, processExitCode); 318 | AccumulateExitCode(ExitCodeFatalCommandError); 319 | } 320 | else if (processExitCode != 0) 321 | { 322 | fprintf(stderr, "%hs: warning : process exit code %u (0x%x).\n", 323 | AppName, processExitCode, processExitCode); 324 | AccumulateExitCode(ExitCodeCommandError); 325 | } 326 | } 327 | 328 | return m_exitCode >= 0; 329 | } 330 | -------------------------------------------------------------------------------- /wargs/WArgsContext.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Doug Cook. 2 | // Licensed under the MIT License. 3 | 4 | #pragma once 5 | #include "WArgs.h" 6 | #include 7 | 8 | class WArgs::Context 9 | { 10 | private: 11 | 12 | WArgs const& m_wargs; 13 | std::vector m_slotHandles; 14 | UINT8 const m_slotCount; 15 | UINT8 m_slotsActive; 16 | ExitCode m_exitCode; 17 | TextToolsUniqueHandle m_conin; 18 | TextToolsUniqueHandle m_nul; 19 | HANDLE m_hTtyForPrompt; 20 | HANDLE m_hStdInputForChild; 21 | 22 | public: 23 | 24 | Context(Context const&) = delete; 25 | void operator=(Context const&) = delete; 26 | 27 | ~Context(); 28 | 29 | Context(WArgs const& wargs, bool useStdIn); 30 | 31 | bool 32 | ExitCodeIsFatal() const noexcept; 33 | 34 | int 35 | UnsignedExitCode() const noexcept; 36 | 37 | void 38 | AccumulateExitCode(ExitCode current) noexcept; 39 | 40 | void 41 | WaitForAllProcessesToExit(); 42 | 43 | void 44 | StartProcess(_In_ PWSTR commandLine); 45 | 46 | private: 47 | 48 | TextToolsUniqueHandle 49 | OpenInputDevice(PCWSTR name) noexcept; 50 | 51 | _Success_(return) bool 52 | AcquireSlotIndex(_Out_ UINT8* pSlotIndex); 53 | 54 | void 55 | SetSlot(UINT8 slotIndex, TextToolsUniqueHandle value) noexcept; 56 | 57 | TextToolsUniqueHandle 58 | ClearSlot(UINT8 slotIndex) noexcept; 59 | 60 | bool 61 | WaitForProcessExit(bool block); 62 | }; 63 | -------------------------------------------------------------------------------- /wargs/pch.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Doug Cook. 2 | // Licensed under the MIT License. 3 | 4 | #include "pch.h" 5 | -------------------------------------------------------------------------------- /wargs/pch.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Doug Cook. 2 | // Licensed under the MIT License. 3 | 4 | #pragma once 5 | #ifndef WIN32_LEAN_AND_MEAN 6 | #define WIN32_LEAN_AND_MEAN 7 | #endif 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | PCSTR constexpr AppName = "wargs"; 20 | -------------------------------------------------------------------------------- /wargs/wargs.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | ARM64 7 | 8 | 9 | Debug 10 | Win32 11 | 12 | 13 | Release 14 | ARM64 15 | 16 | 17 | Release 18 | Win32 19 | 20 | 21 | Debug 22 | x64 23 | 24 | 25 | Release 26 | x64 27 | 28 | 29 | 30 | 16.0 31 | Win32Proj 32 | {d21c1e8d-7b31-439d-9b50-cd943ba91df1} 33 | wargs 34 | 10.0 35 | 36 | 37 | 38 | Application 39 | true 40 | v143 41 | Unicode 42 | 43 | 44 | Application 45 | false 46 | v143 47 | true 48 | Unicode 49 | 50 | 51 | Application 52 | true 53 | v143 54 | Unicode 55 | 56 | 57 | Application 58 | true 59 | v143 60 | Unicode 61 | 62 | 63 | Application 64 | false 65 | v143 66 | true 67 | Unicode 68 | 69 | 70 | Application 71 | false 72 | v143 73 | true 74 | Unicode 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | true 102 | $(SolutionDir)obj$(Platform)\$(Configuration)\ 103 | $(SolutionDir)obj$(Platform)\$(Configuration)\$(ProjectName)\ 104 | 105 | 106 | false 107 | $(SolutionDir)obj$(Platform)\$(Configuration)\ 108 | $(SolutionDir)obj$(Platform)\$(Configuration)\$(ProjectName)\ 109 | 110 | 111 | true 112 | $(SolutionDir)obj$(Platform)\$(Configuration)\ 113 | $(SolutionDir)obj$(Platform)\$(Configuration)\$(ProjectName)\ 114 | 115 | 116 | true 117 | $(SolutionDir)obj$(Platform)\$(Configuration)\ 118 | $(SolutionDir)obj$(Platform)\$(Configuration)\$(ProjectName)\ 119 | 120 | 121 | false 122 | $(SolutionDir)obj$(Platform)\$(Configuration)\ 123 | $(SolutionDir)obj$(Platform)\$(Configuration)\$(ProjectName)\ 124 | 125 | 126 | false 127 | $(SolutionDir)obj$(Platform)\$(Configuration)\ 128 | $(SolutionDir)obj$(Platform)\$(Configuration)\$(ProjectName)\ 129 | 130 | 131 | 132 | Level4 133 | true 134 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 135 | true 136 | Use 137 | pch.h 138 | true 139 | stdcpp20 140 | ../inc 141 | true 142 | 143 | 144 | Console 145 | true 146 | kernel32.lib;user32.lib;%(AdditionalDependencies) 147 | 148 | 149 | 150 | 151 | Level4 152 | true 153 | true 154 | true 155 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 156 | true 157 | Use 158 | pch.h 159 | stdcpp20 160 | ../inc 161 | true 162 | MultiThreaded 163 | 164 | 165 | Console 166 | true 167 | true 168 | true 169 | kernel32.lib;user32.lib;%(AdditionalDependencies) 170 | 171 | 172 | 173 | 174 | Level4 175 | true 176 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 177 | true 178 | Use 179 | pch.h 180 | true 181 | stdcpp20 182 | ../inc 183 | true 184 | 185 | 186 | Console 187 | true 188 | kernel32.lib;user32.lib;%(AdditionalDependencies) 189 | 190 | 191 | 192 | 193 | Level4 194 | true 195 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 196 | true 197 | Use 198 | pch.h 199 | true 200 | stdcpp20 201 | ../inc 202 | true 203 | 204 | 205 | Console 206 | true 207 | kernel32.lib;user32.lib;%(AdditionalDependencies) 208 | 209 | 210 | 211 | 212 | Level4 213 | true 214 | true 215 | true 216 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 217 | true 218 | Use 219 | pch.h 220 | stdcpp20 221 | ../inc 222 | true 223 | MultiThreaded 224 | 225 | 226 | Console 227 | true 228 | true 229 | true 230 | kernel32.lib;user32.lib;%(AdditionalDependencies) 231 | 232 | 233 | 234 | 235 | Level4 236 | true 237 | true 238 | true 239 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 240 | true 241 | Use 242 | pch.h 243 | stdcpp20 244 | ../inc 245 | true 246 | MultiThreaded 247 | 248 | 249 | Console 250 | true 251 | true 252 | true 253 | kernel32.lib;user32.lib;%(AdditionalDependencies) 254 | 255 | 256 | 257 | 258 | Create 259 | Create 260 | Create 261 | Create 262 | Create 263 | Create 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | {9edd7581-df10-4284-8418-d873d09ef4a2} 279 | 280 | 281 | 282 | 283 | 284 | -------------------------------------------------------------------------------- /wargs/wargs.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | Source Files 23 | 24 | 25 | Source Files 26 | 27 | 28 | Source Files 29 | 30 | 31 | Source Files 32 | 33 | 34 | 35 | 36 | Header Files 37 | 38 | 39 | Header Files 40 | 41 | 42 | Header Files 43 | 44 | 45 | Header Files 46 | 47 | 48 | -------------------------------------------------------------------------------- /wconv/Program.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Doug Cook. 2 | // Licensed under the MIT License. 3 | 4 | #include "pch.h" 5 | #include "WConv.h" 6 | 7 | #include 8 | #include 9 | 10 | static int 11 | Usage() 12 | { 13 | fputs(R"( 14 | Usage: wconv [-f ENCODING] [[-i] INPUTFILE...] [-t ENCODING] [-o OUTPUTFILE] 15 | or: wconv -l 16 | 17 | Converts text from one encoding to another. Similar to the "iconv" tool. 18 | 19 | -i INPUTFILE, --input=... Input text from file. Default: stdin. 20 | --iClip Input text from clipboard. 21 | -f ENCODING, --from-code=... Encoding of input. Use NNNN, cpNNNN, utf8, 22 | utf8bom, utf16, utf16bom, utf16be, etc. 23 | Default: 1252bom (cp1252 unless UTF BOM present). 24 | 25 | -o OUTPUTFILE, --output=... Output text to file. Default: stdout. 26 | --oClip Output text to clipboard. 27 | --oNoWarn Don't warn for unconvertible output. 28 | --subst=CHAR Substitution for unconvertible output. 29 | Default: Encoding-specific, frequently '?'. 30 | -t ENCODING, --to-code=... Encoding of output. Use NNNN, cpNNNN, utf8, 31 | utf8bom, utf16, utf16bom, utf16be, etc. 32 | Default: utf8-bom (UTF-8 with BOM). 33 | 34 | -r, --replace Silently replace invalid input with U+FFFD. 35 | Default: Report an error for invalid input. 36 | --no-best-fit Disable the use of best-fit characters. 37 | -s, --silent Suppress conversion errors. Same as 38 | '--replace --oNoWarn'. 39 | -n NEWLINE, --newline=... Newline output behavior: CRLF, LF, or PRESERVE. 40 | Default: PRESERVE. 41 | 42 | If -l or --list is specified, show supported encodings and exit. 43 | If -h or --help is specified, show usage and exit. 44 | If --version is specified, show the version number of wconv and then exit. 45 | 46 | ENCODING names ignore case and punctuation (e.g. 'utf-8' is the same as 47 | 'UTF8'). They may be formatted as digits (Windows code page identifier), 'CP' 48 | followed by digits, or 'UTF' followed by '8', '16', '32', '16LE', '16BE', 49 | '32LE', or '32BE'. Input encodings may have a 'bom' suffix indicating that if 50 | the input starts with a BOM, the BOM should be consumed and the corresponding 51 | UTF encoding should override the specified encoding. Output UTF encodings may 52 | have a 'BOM' suffix indicating that the resulting output should begin with a 53 | BOM. 54 | 55 | Examples: 56 | 57 | Copy input.txt (cp1252 or UTF with BOM) to output.txt (UTF-8 with BOM): 58 | wconv input.txt -o output.txt 59 | 60 | Copy input.txt (cp437) to output.txt (UTF-16BE), normalizing CR/LF to CRLF: 61 | wconv -f cp437 input.txt -t utf16be -o output.txt -n CRLF 62 | 63 | Copy input.txt to stdout (UTF-16 if console, UTF-8 with BOM if redirected): 64 | wconv -f utf8 input.txt 65 | 66 | Copy input.txt (cp1252 or UTF with BOM) to clipboard (UTF-16): 67 | wconv -i input.txt -oclip 68 | 69 | Copy text from clipboard (UTF-16) to output.txt (UTF-8 with BOM): 70 | wconv --iclip -o output.txt 71 | )", stdout); 72 | 73 | return 1; 74 | } 75 | 76 | static int 77 | Version() 78 | { 79 | fputs(TEXTTOOLS_VERSION_STR("wconv"), stdout); 80 | return 1; 81 | } 82 | 83 | int __cdecl 84 | wmain(int argc, _In_count_(argc) PWSTR argv[]) 85 | { 86 | int returnCode; 87 | 88 | try 89 | { 90 | WConv wconv; 91 | bool showHelp = false; 92 | bool showVersion = false; 93 | bool showList = false; 94 | 95 | ArgParser ap(AppName, argc, argv); 96 | while (ap.MoveNextArg()) 97 | { 98 | std::wstring_view val; 99 | if (ap.BeginDashDashArg()) 100 | { 101 | if (ap.CurrentArgNameMatches(1, L"from-code")) 102 | { 103 | if (ap.GetLongArgVal(val, false)) 104 | { 105 | ap.SetArgErrorIfFalse(wconv.SetInputEncoding(val, "--from-code")); 106 | } 107 | } 108 | else if (ap.CurrentArgNameMatches(1, L"help")) 109 | { 110 | showHelp = true; 111 | } 112 | else if (ap.CurrentArgNameMatches(2, L"iclipboard")) 113 | { 114 | wconv.AddInputClipboard(); 115 | } 116 | else if (ap.CurrentArgNameMatches(2, L"inputfile")) 117 | { 118 | if (ap.GetLongArgVal(val, false)) 119 | { 120 | wconv.AddInputFilename(val); 121 | } 122 | } 123 | else if (ap.CurrentArgNameMatches(1, L"list")) 124 | { 125 | showList = true; 126 | } 127 | else if (ap.CurrentArgNameMatches(2, L"newline")) 128 | { 129 | if (ap.GetLongArgVal(val, false)) 130 | { 131 | ap.SetArgErrorIfFalse(wconv.SetNewline(val, "--newline")); 132 | } 133 | } 134 | else if (ap.CurrentArgNameMatches(2, L"no-best-fit")) 135 | { 136 | wconv.SetNoBestFit(); 137 | } 138 | else if (ap.CurrentArgNameMatches(2, L"oclipboard")) 139 | { 140 | wconv.SetOutputClipboard("--oclip"); 141 | } 142 | else if (ap.CurrentArgNameMatches(2, L"onowarning")) 143 | { 144 | wconv.SetOutputNoDefaultCharUsedWarning(); 145 | } 146 | else if (ap.CurrentArgNameMatches(2, L"outputfile")) 147 | { 148 | if (ap.GetLongArgVal(val, false)) 149 | { 150 | wconv.SetOutputFilename(val, "--output"); 151 | } 152 | } 153 | else if (ap.CurrentArgNameMatches(1, L"replace")) 154 | { 155 | wconv.SetReplace(); 156 | } 157 | else if (ap.CurrentArgNameMatches(2, L"silent")) 158 | { 159 | wconv.SetSilent(); 160 | } 161 | else if (ap.CurrentArgNameMatches(2, L"substitution")) 162 | { 163 | if (ap.GetLongArgVal(val, false)) 164 | { 165 | if (val.size() != 1 || val[0] > 255) 166 | { 167 | fprintf(stderr, "%hs: error : '%ls' requires one ASCII character for value.\n", 168 | AppName, ap.CurrentArg()); 169 | ap.SetArgError(); 170 | } 171 | else 172 | { 173 | wconv.SetOutputReplacementChar(static_cast(val[0]), "--subst"); 174 | } 175 | } 176 | } 177 | else if (ap.CurrentArgNameMatches(1, L"to-code")) 178 | { 179 | if (ap.GetLongArgVal(val, false)) 180 | { 181 | ap.SetArgErrorIfFalse(wconv.SetOutputEncoding(val, "--to-code")); 182 | } 183 | } 184 | else if (ap.CurrentArgNameMatches(1, L"version")) 185 | { 186 | showVersion = true; 187 | } 188 | else 189 | { 190 | ap.PrintLongArgError(); 191 | } 192 | } 193 | else if (ap.BeginDashOrSlashArg()) 194 | { 195 | while (ap.MoveNextArgChar()) 196 | { 197 | switch (ap.CurrentArgChar()) 198 | { 199 | case 'f': 200 | if (ap.ReadShortArgVal(val, false)) 201 | { 202 | ap.SetArgErrorIfFalse(wconv.SetInputEncoding(val, "-f")); 203 | } 204 | break; 205 | case 'h': 206 | case '?': 207 | showHelp = true; 208 | break; 209 | case 'i': 210 | if (ap.ReadShortArgVal(val, true)) 211 | { 212 | wconv.AddInputFilename(val); 213 | } 214 | break; 215 | case 'l': 216 | showList = true; 217 | break; 218 | case 'n': 219 | if (ap.ReadShortArgVal(val, false)) 220 | { 221 | ap.SetArgErrorIfFalse(wconv.SetNewline(val, "-n")); 222 | } 223 | break; 224 | case 'o': 225 | if (ap.ReadShortArgVal(val, false)) 226 | { 227 | wconv.SetOutputFilename(val, "-o"); 228 | } 229 | break; 230 | case 'r': 231 | wconv.SetReplace(); 232 | break; 233 | case 's': 234 | wconv.SetSilent(); 235 | break; 236 | case 't': 237 | if (ap.ReadShortArgVal(val, false)) 238 | { 239 | ap.SetArgErrorIfFalse(wconv.SetOutputEncoding(val, "-t")); 240 | } 241 | break; 242 | case 'c': 243 | default: 244 | ap.PrintShortArgError(); 245 | break; 246 | } 247 | } 248 | } 249 | else 250 | { 251 | wconv.AddInputFilename(ap.CurrentArg()); 252 | } 253 | } 254 | 255 | ap.SetArgErrorIfFalse(wconv.FinalizeParameters()); 256 | 257 | if (showHelp) 258 | { 259 | returnCode = Usage(); 260 | } 261 | else if (showVersion) 262 | { 263 | returnCode = Version(); 264 | } 265 | else if (ap.ArgError()) 266 | { 267 | fprintf(stderr, "%hs: error : Invalid command-line. Use '%hs --help' for more information.\n", 268 | AppName, AppName); 269 | returnCode = 1; 270 | } 271 | else if (showList) 272 | { 273 | returnCode = wconv.PrintSupportedEncodings(); 274 | } 275 | else 276 | { 277 | returnCode = wconv.Run(); 278 | } 279 | } 280 | catch (std::exception const& ex) 281 | { 282 | fprintf(stderr, "%hs: fatal error : %hs\n", 283 | AppName, ex.what()); 284 | returnCode = 1; 285 | } 286 | 287 | return returnCode; 288 | } 289 | -------------------------------------------------------------------------------- /wconv/WConv.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Doug Cook. 2 | // Licensed under the MIT License. 3 | 4 | #include "pch.h" 5 | #include "WConv.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | static constexpr std::wstring_view ClipboardFilename = L""; 14 | static constexpr std::wstring_view StdInFilename = L""; 15 | static constexpr std::wstring_view StdOutFilename = L""; 16 | 17 | static constexpr std::wstring_view PreserveStr = L"preserve"; 18 | static constexpr std::wstring_view CRLFStr = L"crlf"; 19 | static constexpr std::wstring_view LFStr = L"lf"; 20 | 21 | static void 22 | WarnIfNotEmpty(PCWSTR oldValue, PCSTR argName) 23 | { 24 | if (oldValue != nullptr && oldValue[0] != 0) 25 | { 26 | fprintf(stderr, "%hs: warning : '%hs' overriding old value '%ls'.\n", 27 | AppName, argName, oldValue); 28 | } 29 | } 30 | 31 | CodePageCategory 32 | WConv::ParseEncoding( 33 | std::wstring_view value, 34 | PCSTR argName, 35 | _Inout_ Encoding* pEncoding) 36 | { 37 | CodePageArg arg(value); 38 | if (arg.ParseResult == CodePageCategory::Error) 39 | { 40 | fprintf(stderr, "%hs: error : Unrecognized '%hs' encoding '%*ls'\n", 41 | AppName, argName, (unsigned)value.size(), value.data()); 42 | } 43 | else if (CodePageInfo cpi(arg.CodePage); 44 | !CodeConvert::SupportsCodePage(cpi)) 45 | { 46 | fprintf(stderr, "%hs: error : Unsupported '%hs' encoding '%*ls'." 47 | " This tool supports UTF-8, UTF-16, UTF-32, and Windows SBCS/DBCS code pages." 48 | " Use -l for a list of supported encodings.\n", 49 | AppName, argName, (unsigned)value.size(), value.data()); 50 | } 51 | else 52 | { 53 | if (pEncoding->Specified) 54 | { 55 | fprintf(stderr, "%hs: warning : '%hs' overriding old value 'cp%u%hs'.\n", 56 | AppName, argName, 57 | pEncoding->CodePage, pEncoding->Bom ? "BOM" : ""); 58 | } 59 | 60 | pEncoding->CodePage = arg.CodePage; 61 | pEncoding->Bom = arg.BomSuffix; 62 | pEncoding->Specified = true; 63 | } 64 | 65 | return arg.ParseResult; 66 | } 67 | 68 | PCSTR 69 | WConv::NewlineBehaviorToString(NewlineBehavior newlineBehavior) noexcept 70 | { 71 | switch (newlineBehavior) 72 | { 73 | case NewlineBehavior::None: return "None"; 74 | case NewlineBehavior::Preserve: return "PRESERVE"; 75 | case NewlineBehavior::LF: return "LF"; 76 | case NewlineBehavior::CRLF: return "CRLF"; 77 | default: return nullptr; 78 | } 79 | } 80 | 81 | bool 82 | WConv::SetInputEncoding(std::wstring_view value, PCSTR argName) 83 | { 84 | auto const category = ParseEncoding(value, argName, &m_inputEncoding); 85 | return category != CodePageCategory::Error; 86 | } 87 | 88 | bool 89 | WConv::SetOutputEncoding(std::wstring_view value, PCSTR argName) 90 | { 91 | auto const category = ParseEncoding(value, argName, &m_outputEncoding); 92 | if (category == CodePageCategory::None && m_outputEncoding.Bom) 93 | { 94 | fprintf(stderr, "%hs: warning : '%hs' ignoring BOM suffix for non-UTF code page '%*ls'.\n", 95 | AppName, argName, 96 | (unsigned)value.size(), value.data()); 97 | } 98 | return category != CodePageCategory::Error; 99 | } 100 | 101 | void 102 | WConv::SetOutputFilename(std::wstring_view value, PCSTR argName) 103 | { 104 | WarnIfNotEmpty(m_outputFilename.c_str(), argName); 105 | m_outputFilename = value; 106 | } 107 | 108 | void 109 | WConv::SetOutputClipboard(PCSTR argName) 110 | { 111 | WarnIfNotEmpty(m_outputFilename.c_str(), argName); 112 | m_outputFilename = ClipboardFilename; 113 | } 114 | 115 | void 116 | WConv::SetOutputReplacementChar(char value, PCSTR argName) noexcept 117 | { 118 | if (m_outputDefault) 119 | { 120 | fprintf(stderr, "%hs: warning : '%hs' overriding old value '%hc'.\n", 121 | AppName, argName, m_outputDefaultChar); 122 | } 123 | 124 | m_outputDefaultChar = value; 125 | m_outputDefault = &m_outputDefaultChar; 126 | } 127 | 128 | bool 129 | WConv::SetNewline(std::wstring_view value, PCSTR argName) 130 | { 131 | assert(!value.empty()); 132 | 133 | NewlineBehavior newlineBehavior; 134 | if (value.size() <= PreserveStr.size() && 135 | CSTR_EQUAL == CompareStringOrdinal( 136 | PreserveStr.data(), (unsigned)value.size(), 137 | value.data(), (unsigned)value.size(), TRUE)) 138 | { 139 | newlineBehavior = NewlineBehavior::Preserve; 140 | } 141 | else if (value.size() <= CRLFStr.size() && 142 | CSTR_EQUAL == CompareStringOrdinal( 143 | CRLFStr.data(), (unsigned)value.size(), 144 | value.data(), (unsigned)value.size(), TRUE)) 145 | { 146 | newlineBehavior = NewlineBehavior::CRLF; 147 | } 148 | else if (value.size() <= LFStr.size() && 149 | CSTR_EQUAL == CompareStringOrdinal( 150 | LFStr.data(), (unsigned)value.size(), 151 | value.data(), (unsigned)value.size(), TRUE)) 152 | { 153 | newlineBehavior = NewlineBehavior::LF; 154 | } 155 | else 156 | { 157 | fprintf(stderr, "%hs: error : Invalid %hs=\"%*ls\", expected CRLF, LF, or PRESERVE.\n", 158 | AppName, argName, (unsigned)value.size(), value.data()); 159 | return false; 160 | } 161 | 162 | if (m_newlineBehavior != NewlineBehavior::None) 163 | { 164 | fprintf(stderr, "%hs: warning : '%hs' overriding old value '%hs'.\n", 165 | AppName, argName, NewlineBehaviorToString(m_newlineBehavior)); 166 | } 167 | 168 | m_newlineBehavior = newlineBehavior; 169 | return true; 170 | } 171 | 172 | void 173 | WConv::AddInputFilename(std::wstring_view value) 174 | { 175 | m_inputFilenames.emplace_back(value); 176 | } 177 | 178 | void 179 | WConv::AddInputClipboard() 180 | { 181 | m_inputFilenames.emplace_back(ClipboardFilename); 182 | } 183 | 184 | void 185 | WConv::SetOutputNoDefaultCharUsedWarning() noexcept 186 | { 187 | m_outputNoDefaultCharUsedWarning = true; 188 | } 189 | 190 | void 191 | WConv::SetReplace() noexcept 192 | { 193 | m_replace = true; 194 | } 195 | 196 | void 197 | WConv::SetNoBestFit() noexcept 198 | { 199 | m_noBestFit = true; 200 | } 201 | 202 | void 203 | WConv::SetSilent() noexcept 204 | { 205 | m_replace = true; 206 | m_outputNoDefaultCharUsedWarning = true; 207 | } 208 | 209 | static BOOL CALLBACK 210 | EnumCodePagesProc(PWSTR name) 211 | { 212 | PWSTR nameEnd; 213 | errno = 0; 214 | unsigned cp = wcstoul(name, &nameEnd, 0); 215 | if (errno == 0 && nameEnd[0] == 0 && cp != 0) 216 | { 217 | CodePageInfo cpi(cp); 218 | if (cpi.Category != CodePageCategory::Utf && 219 | CodeConvert::SupportsCategory(cpi.Category)) 220 | { 221 | fprintf(stdout, "%ls\n", cpi.CodePageName); 222 | } 223 | } 224 | return true; 225 | } 226 | 227 | int 228 | WConv::PrintSupportedEncodings() 229 | { 230 | int returnCode; 231 | 232 | if (!EnumSystemCodePagesW(&EnumCodePagesProc, CP_INSTALLED)) 233 | { 234 | fprintf(stderr, "%hs: error : EnumSystemCodePagesW error %u\n", 235 | AppName, GetLastError()); 236 | returnCode = 1; 237 | } 238 | else 239 | { 240 | fprintf(stdout, "%-5u (UTF-16LE)\n", CodePageUtf16LE); 241 | fprintf(stdout, "%-5u (UTF-16BE)\n", CodePageUtf16BE); 242 | fprintf(stdout, "%-5u (UTF-32LE)\n", CodePageUtf32LE); 243 | fprintf(stdout, "%-5u (UTF-32BE)\n", CodePageUtf32BE); 244 | fprintf(stdout, "%-5u (UTF-8)\n", CodePageUtf8); 245 | returnCode = 0; 246 | } 247 | 248 | return returnCode; 249 | } 250 | 251 | bool 252 | WConv::FinalizeParameters() 253 | { 254 | if (!m_inputEncoding.Specified) 255 | { 256 | m_inputEncoding.CodePage = 1252; 257 | m_inputEncoding.Bom = true; 258 | } 259 | 260 | if (!m_outputEncoding.Specified) 261 | { 262 | m_outputEncoding.CodePage = CodePageUtf8; 263 | m_outputEncoding.Bom = true; 264 | } 265 | 266 | if (m_newlineBehavior == NewlineBehavior::None) 267 | { 268 | m_newlineBehavior = NewlineBehavior::Preserve; 269 | } 270 | 271 | if (m_outputFilename.empty()) 272 | { 273 | m_outputFilename = StdOutFilename; 274 | } 275 | 276 | if (m_inputFilenames.empty()) 277 | { 278 | m_inputFilenames.emplace_back(StdInFilename); 279 | } 280 | else 281 | { 282 | for (auto& filename : m_inputFilenames) 283 | { 284 | if (filename.empty()) 285 | { 286 | filename = StdInFilename; 287 | } 288 | } 289 | } 290 | 291 | return true; 292 | } 293 | 294 | int 295 | WConv::Run() const 296 | { 297 | #ifndef NDEBUG 298 | fprintf(stderr, "DEBUG: %hs", AppName); 299 | if (m_replace) fprintf(stderr, " -r"); 300 | if (m_noBestFit) fprintf(stderr, " --no-best-fit"); 301 | if (m_outputNoDefaultCharUsedWarning) fprintf(stderr, " --oNoWarn"); 302 | if (m_outputDefault) fprintf(stderr, " --subst=\"%hc\"", m_outputDefaultChar); 303 | fprintf(stderr, " -n %hs", NewlineBehaviorToString(m_newlineBehavior)); 304 | 305 | if (m_inputEncoding.Specified) fprintf(stderr, " -f cp%u%hs", m_inputEncoding.CodePage, m_inputEncoding.Bom ? "BOM" : ""); 306 | for (auto& inputFilename : m_inputFilenames) 307 | { 308 | fprintf(stderr, " \"%ls\"", inputFilename.c_str()); 309 | } 310 | 311 | if (m_outputEncoding.Specified) fprintf(stderr, " -t cp%u%hs", m_outputEncoding.CodePage, m_outputEncoding.Bom ? "BOM" : ""); 312 | fprintf(stderr, " -o \"%ls\"", m_outputFilename.c_str()); 313 | fprintf(stderr, "\n"); 314 | #endif // NDEBUG 315 | 316 | int returnCode = 0; 317 | bool usedDefaultChar = false; 318 | bool* const pUsedDefaultChar = m_outputNoDefaultCharUsedWarning ? nullptr : &usedDefaultChar; 319 | 320 | TextOutput output; 321 | bool const outputClipboard = ClipboardFilename == m_outputFilename; 322 | bool const outputInsertBom = m_outputEncoding.Specified 323 | ? m_outputEncoding.Bom 324 | : !outputClipboard; // Add BOM to clipboard only if explicit '-t'. 325 | TextOutputFlags const outputFlags = 326 | (NewlineBehavior::CRLF == m_newlineBehavior ? TextOutputFlags::ExpandCRLF : TextOutputFlags::None) | 327 | (outputInsertBom ? TextOutputFlags::InsertBom : TextOutputFlags::None) | 328 | (m_replace ? TextOutputFlags::None : TextOutputFlags::InvalidUtf16Error) | 329 | (m_noBestFit ? TextOutputFlags::NoBestFitChars : TextOutputFlags::None) | 330 | TextOutputFlags::CheckConsole; 331 | if (outputClipboard) 332 | { 333 | output.OpenChars(outputFlags); 334 | } 335 | else if (m_outputFilename == StdOutFilename) 336 | { 337 | output.OpenBorrowedHandle(GetStdHandle(STD_OUTPUT_HANDLE), m_outputEncoding.CodePage, outputFlags); 338 | } 339 | else 340 | { 341 | output.OpenFile(m_outputFilename.c_str(), m_outputEncoding.CodePage, outputFlags); 342 | } 343 | 344 | TextInput input; 345 | for (auto const& inputFilename : m_inputFilenames) 346 | { 347 | bool const inputClipboard = ClipboardFilename == inputFilename; 348 | bool const inputCheckBom = m_inputEncoding.Specified 349 | ? m_inputEncoding.Bom 350 | : !inputClipboard; // Eat BOM from clipboard only if explicit '-b'. 351 | TextInputFlags const inputFlags = 352 | (NewlineBehavior::Preserve != m_newlineBehavior ? TextInputFlags::FoldCRLF : TextInputFlags::None) | 353 | (inputCheckBom ? TextInputFlags::ConsumeBom : TextInputFlags::None) | 354 | (m_replace ? TextInputFlags::None : TextInputFlags::InvalidMbcsError) | 355 | TextInputFlags::CheckConsole | 356 | TextInputFlags::ConsoleCtrlZ; 357 | if (inputClipboard) 358 | { 359 | auto const status = input.OpenClipboard(inputFlags); 360 | if (status != ERROR_SUCCESS) 361 | { 362 | fprintf(stderr, "%hs: warning : clipboard error %u. Clipboard not read.\n", 363 | AppName, status); 364 | input.OpenChars({}, inputFlags); 365 | } 366 | } 367 | else if (StdInFilename == inputFilename) 368 | { 369 | input.OpenBorrowedHandle(GetStdHandle(STD_INPUT_HANDLE), m_inputEncoding.CodePage, inputFlags); 370 | } 371 | else 372 | { 373 | auto status = input.OpenFile(inputFilename.c_str(), m_inputEncoding.CodePage, inputFlags); 374 | if (status != ERROR_SUCCESS) 375 | { 376 | fprintf(stderr, "%hs: warning : CreateFile error %u opening input file '%ls'. Skipping.\n", 377 | AppName, status, inputFilename.c_str()); 378 | continue; 379 | } 380 | } 381 | 382 | try 383 | { 384 | do 385 | { 386 | auto inputChars = input.Chars(); 387 | if (input.Mode() == TextInputMode::Console && 388 | inputChars.ends_with(L'\x1A')) // Control-Z 389 | { 390 | inputChars.remove_suffix(1); 391 | output.WriteChars(inputChars, m_outputDefault, pUsedDefaultChar); 392 | break; 393 | } 394 | 395 | output.WriteChars(inputChars, m_outputDefault, pUsedDefaultChar); 396 | } while (input.ReadNextChars()); 397 | } 398 | catch (std::range_error const& ex) 399 | { 400 | fprintf(stderr, "%ls: error : %hs\n", 401 | inputFilename.c_str(), ex.what()); 402 | returnCode = 1; 403 | } 404 | } 405 | 406 | if (usedDefaultChar) 407 | { 408 | fprintf(stderr, "%hs: warning : Some input could not be converted to the output encoding.\n", 409 | AppName); 410 | } 411 | 412 | if (outputClipboard) 413 | { 414 | auto const clipChars = output.BufferedChars(); 415 | static_assert(sizeof(char16_t) == sizeof(wchar_t)); 416 | auto const status = ClipboardTextSet({ (wchar_t const*)clipChars.data(), clipChars.size() }); 417 | if (status != ERROR_SUCCESS) 418 | { 419 | fprintf(stderr, "%hs: error : clipboard error %u. Clipboard not updated.\n", 420 | AppName, status); 421 | returnCode = 1; 422 | } 423 | } 424 | 425 | return returnCode; 426 | } 427 | -------------------------------------------------------------------------------- /wconv/WConv.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Doug Cook. 2 | // Licensed under the MIT License. 3 | 4 | #pragma once 5 | 6 | enum class CodePageCategory : UINT8; 7 | 8 | class WConv 9 | { 10 | enum class NewlineBehavior : UCHAR 11 | { 12 | None, 13 | Preserve, 14 | LF, 15 | CRLF, 16 | }; 17 | 18 | struct Encoding 19 | { 20 | unsigned CodePage; 21 | bool Bom; 22 | bool Specified; 23 | }; 24 | 25 | Encoding m_inputEncoding = {}; // -f, --from-code 26 | Encoding m_outputEncoding = {}; // -t, --to-code 27 | bool m_replace = false; 28 | bool m_noBestFit = false; 29 | NewlineBehavior m_newlineBehavior = {}; 30 | bool m_outputNoDefaultCharUsedWarning = false; 31 | char m_outputDefaultChar = 0; 32 | PCCH m_outputDefault = nullptr; 33 | 34 | std::wstring m_outputFilename; 35 | std::vector m_inputFilenames; 36 | 37 | private: 38 | 39 | static PCSTR 40 | NewlineBehaviorToString(NewlineBehavior newlineBehavior) noexcept; 41 | 42 | static [[nodiscard]] CodePageCategory 43 | ParseEncoding(std::wstring_view value, PCSTR argName, _Inout_ Encoding* pEncoding); 44 | 45 | public: 46 | 47 | [[nodiscard]] bool 48 | SetInputEncoding(std::wstring_view value, PCSTR argName); 49 | 50 | [[nodiscard]] bool 51 | SetOutputEncoding(std::wstring_view value, PCSTR argName); 52 | 53 | void 54 | SetOutputFilename(std::wstring_view value, PCSTR argName); 55 | 56 | void 57 | SetOutputClipboard(PCSTR argName); 58 | 59 | void 60 | SetOutputReplacementChar(char value, PCSTR argName) noexcept; 61 | 62 | [[nodiscard]] bool 63 | SetNewline(std::wstring_view value, PCSTR argName); 64 | 65 | void 66 | AddInputFilename(std::wstring_view value); 67 | 68 | void 69 | AddInputClipboard(); 70 | 71 | void 72 | SetOutputNoDefaultCharUsedWarning() noexcept; 73 | 74 | void 75 | SetReplace() noexcept; 76 | 77 | void 78 | SetNoBestFit() noexcept; 79 | 80 | void 81 | SetSilent() noexcept; 82 | 83 | [[nodiscard]] static int 84 | PrintSupportedEncodings(); 85 | 86 | [[nodiscard]] bool 87 | FinalizeParameters(); 88 | 89 | [[nodiscard]] int 90 | Run() const; 91 | }; 92 | -------------------------------------------------------------------------------- /wconv/pch.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Doug Cook. 2 | // Licensed under the MIT License. 3 | 4 | #include "pch.h" 5 | -------------------------------------------------------------------------------- /wconv/pch.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Doug Cook. 2 | // Licensed under the MIT License. 3 | 4 | #pragma once 5 | #ifndef WIN32_LEAN_AND_MEAN 6 | #define WIN32_LEAN_AND_MEAN 7 | #endif 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | PCSTR constexpr AppName = "wconv"; 20 | -------------------------------------------------------------------------------- /wconv/wconv.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | ARM64 7 | 8 | 9 | Debug 10 | Win32 11 | 12 | 13 | Release 14 | ARM64 15 | 16 | 17 | Release 18 | Win32 19 | 20 | 21 | Debug 22 | x64 23 | 24 | 25 | Release 26 | x64 27 | 28 | 29 | 30 | 16.0 31 | Win32Proj 32 | {0b6bba07-3611-4b20-9e12-9ac1c08c898c} 33 | wconv 34 | 10.0 35 | 36 | 37 | 38 | Application 39 | true 40 | v143 41 | Unicode 42 | 43 | 44 | Application 45 | false 46 | v143 47 | true 48 | Unicode 49 | 50 | 51 | Application 52 | true 53 | v143 54 | Unicode 55 | 56 | 57 | Application 58 | true 59 | v143 60 | Unicode 61 | 62 | 63 | Application 64 | false 65 | v143 66 | true 67 | Unicode 68 | 69 | 70 | Application 71 | false 72 | v143 73 | true 74 | Unicode 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | true 102 | $(SolutionDir)obj$(Platform)\$(Configuration)\ 103 | $(SolutionDir)obj$(Platform)\$(Configuration)\$(ProjectName)\ 104 | 105 | 106 | false 107 | $(SolutionDir)obj$(Platform)\$(Configuration)\ 108 | $(SolutionDir)obj$(Platform)\$(Configuration)\$(ProjectName)\ 109 | 110 | 111 | true 112 | $(SolutionDir)obj$(Platform)\$(Configuration)\ 113 | $(SolutionDir)obj$(Platform)\$(Configuration)\$(ProjectName)\ 114 | 115 | 116 | true 117 | $(SolutionDir)obj$(Platform)\$(Configuration)\ 118 | $(SolutionDir)obj$(Platform)\$(Configuration)\$(ProjectName)\ 119 | 120 | 121 | false 122 | $(SolutionDir)obj$(Platform)\$(Configuration)\ 123 | $(SolutionDir)obj$(Platform)\$(Configuration)\$(ProjectName)\ 124 | 125 | 126 | false 127 | $(SolutionDir)obj$(Platform)\$(Configuration)\ 128 | $(SolutionDir)obj$(Platform)\$(Configuration)\$(ProjectName)\ 129 | 130 | 131 | 132 | Level4 133 | true 134 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 135 | true 136 | Use 137 | pch.h 138 | true 139 | stdcpp20 140 | ../inc 141 | true 142 | 143 | 144 | Console 145 | true 146 | kernel32.lib;user32.lib;%(AdditionalDependencies) 147 | 148 | 149 | 150 | 151 | Level4 152 | true 153 | true 154 | true 155 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 156 | true 157 | Use 158 | pch.h 159 | stdcpp20 160 | ../inc 161 | true 162 | MultiThreaded 163 | 164 | 165 | Console 166 | true 167 | true 168 | true 169 | kernel32.lib;user32.lib;%(AdditionalDependencies) 170 | 171 | 172 | 173 | 174 | Level4 175 | true 176 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 177 | true 178 | Use 179 | pch.h 180 | true 181 | stdcpp20 182 | ../inc 183 | true 184 | 185 | 186 | Console 187 | true 188 | kernel32.lib;user32.lib;%(AdditionalDependencies) 189 | 190 | 191 | 192 | 193 | Level4 194 | true 195 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 196 | true 197 | Use 198 | pch.h 199 | true 200 | stdcpp20 201 | ../inc 202 | true 203 | 204 | 205 | Console 206 | true 207 | kernel32.lib;user32.lib;%(AdditionalDependencies) 208 | 209 | 210 | 211 | 212 | Level4 213 | true 214 | true 215 | true 216 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 217 | true 218 | Use 219 | pch.h 220 | stdcpp20 221 | ../inc 222 | true 223 | MultiThreaded 224 | 225 | 226 | Console 227 | true 228 | true 229 | true 230 | kernel32.lib;user32.lib;%(AdditionalDependencies) 231 | 232 | 233 | 234 | 235 | Level4 236 | true 237 | true 238 | true 239 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 240 | true 241 | Use 242 | pch.h 243 | stdcpp20 244 | ../inc 245 | true 246 | MultiThreaded 247 | 248 | 249 | Console 250 | true 251 | true 252 | true 253 | kernel32.lib;user32.lib;%(AdditionalDependencies) 254 | 255 | 256 | 257 | 258 | Create 259 | Create 260 | Create 261 | Create 262 | Create 263 | Create 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | {9edd7581-df10-4284-8418-d873d09ef4a2} 275 | 276 | 277 | 278 | 279 | 280 | -------------------------------------------------------------------------------- /wconv/wconv.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | Source Files 23 | 24 | 25 | Source Files 26 | 27 | 28 | 29 | 30 | Header Files 31 | 32 | 33 | Header Files 34 | 35 | 36 | --------------------------------------------------------------------------------