├── .github ├── FUNDING.yml └── workflows │ └── swift.yml ├── .gitignore ├── .swift-version ├── LICENSE ├── Package.resolved ├── Package.swift ├── README.md ├── Sources ├── TextCase │ ├── Format.swift │ ├── ListFormats.swift │ ├── TextCase.swift │ └── io │ │ ├── StandardErrorOutputStream.swift │ │ └── StandardOutputStream.swift └── TextCaseKit │ ├── Format.swift │ ├── FormatRepository.swift │ └── Formats │ ├── Base64 │ ├── DecodeBase64.swift │ └── EncodeBase64.swift │ ├── Cleaning │ ├── StripHTML.swift │ ├── StripWhitespace.swift │ └── TrimWhitespace.swift │ ├── Counts │ ├── CountCharacters.swift │ ├── CountCharactersExcWhitespace.swift │ ├── CountLines.swift │ ├── CountLinesExcBlanks.swift │ └── CountWords.swift │ ├── Fonts │ ├── BoldSansSerif.swift │ ├── BoldSerif.swift │ ├── FontFormat.swift │ ├── Gothic.swift │ ├── Italic.swift │ ├── ItalicBoldSansSerif.swift │ ├── ItalicBoldSerif.swift │ ├── LettersInCirclesFilled.swift │ ├── LettersInCirclesOutline.swift │ ├── LettersInSquaresFilled.swift │ ├── LettersInSquaresOutline.swift │ ├── Script.swift │ └── Strikethrough.swift │ ├── Fun │ ├── ClapCase.swift │ ├── Hashtags.swift │ ├── Rot13.swift │ ├── Shuffled.swift │ ├── Slug.swift │ ├── SmallCaps.swift │ ├── Spongebob.swift │ └── UpsideDown.swift │ ├── Programming │ ├── CamelCase.swift │ ├── KebabCase.swift │ ├── PascalCase.swift │ └── SnakeCase.swift │ ├── Simple │ ├── Capitalise.swift │ ├── CapitaliseWords.swift │ ├── Lowercase.swift │ ├── Reversed.swift │ ├── Sentence.swift │ └── Uppercase.swift │ └── Title │ ├── AMATitleCase.swift │ ├── APATitleCase.swift │ ├── APTitleCase.swift │ ├── BaseTitleCase.swift │ ├── BluebookTitleCase.swift │ ├── CMOSTitleCase.swift │ ├── GuardianTitleCase.swift │ ├── MLATitleCase.swift │ ├── NYTTitleCase.swift │ ├── TitleCaseConfiguration.swift │ └── WikipediaTitleCase.swift ├── Tests └── TextCaseKitTests │ ├── Base64FormatTests.swift │ ├── CleaningFormatTests.swift │ ├── CountsFormatTests.swift │ ├── FontFormatTests.swift │ ├── FormatTestBase.swift │ ├── FunFormatTests.swift │ ├── ProgrammingFormatTests.swift │ ├── SimpleFormatTests.swift │ └── TitleFormatTests.swift ├── logo.jpeg └── project.yml /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [chrishannah] 2 | -------------------------------------------------------------------------------- /.github/workflows/swift.yml: -------------------------------------------------------------------------------- 1 | name: Swift 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | pull_request: 7 | branches: ["main"] 8 | 9 | jobs: 10 | build: 11 | runs-on: macos-13 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: swift-actions/setup-swift@v1 15 | with: 16 | swift-version: "5.8" 17 | - name: Build 18 | run: swift build -v 19 | - name: Run tests 20 | run: swift test -v 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.xcodeproj 2 | .build 3 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 5.8 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "swift-argument-parser", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/apple/swift-argument-parser", 7 | "state" : { 8 | "revision" : "8f4d2753f0e4778c76d5f05ad16c74f707390531", 9 | "version" : "1.2.3" 10 | } 11 | } 12 | ], 13 | "version" : 2 14 | } 15 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.7.1 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "textcase", 8 | platforms: [.macOS(.v13)], 9 | products: [ 10 | .library(name: "TextCaseKit", targets: ["TextCaseKit"]), 11 | .executable(name: "TextCase", targets: ["TextCase"]), 12 | ], 13 | dependencies: [ 14 | .package(url: "https://github.com/apple/swift-argument-parser", from: "1.2.0"), 15 | ], 16 | targets: [ 17 | .executableTarget( 18 | name: "TextCase", 19 | dependencies: [ 20 | "TextCaseKit", 21 | .product(name: "ArgumentParser", package: "swift-argument-parser"), 22 | ] 23 | ), 24 | .target( 25 | name: "TextCaseKit"), 26 | .testTarget( 27 | name: "TextCaseKitTests", 28 | dependencies: ["TextCaseKit"] 29 | ), 30 | ] 31 | ) 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Text Case CLI](logo.jpeg) 2 | 3 | *A command line interface version of Text Case.* 4 | 5 | [Text Case][tc] is a text-transformation app for iOS, iPadOS, and macOS (by me). This project is essentially a subset of the functionality, made available as a command-line tool. 6 | 7 | ## Install 8 | 9 | You can checkout this repository and build from scratch, or to make it slightly easier, it's also available as a custom [Homebrew][hb] tap. 10 | 11 | ### Building From Source 12 | 13 | It's pretty easy: 14 | 15 | 1. Check out the code. 16 | 2. Run `swift build -c release`. 17 | 3. You'll find the executable in `.build/release/`. 18 | 19 | ### From Homebrew 20 | 21 | Also, incredibly easy: 22 | 23 | 0. Make sure you have Homebrew installed. 24 | 1. Add the custom tap to your machine: `brew tap chrishannah/textcase`. 25 | 2. Install Text Case CLI: `brew install textcase` 26 | 27 | To update Text Case CLI via Homebrew, you'll first have to run `brew update` to pull the latest changes from the tap, and if there's an update available, `brew upgrade textcase` will update it to the 28 | latest version. 29 | 30 | ## Usage 31 | 32 | You can use Text Case CLI via the `textcase` command. 33 | 34 | It is split into two separate subcommands, `format`, and `list-formats`. 35 | 36 | ### List Formats 37 | 38 | You can see a list of the available formats, with `textcase list-formats`. This will show the identifier that you'll need to provide when formatting text, and also a short description. 39 | 40 | **Help text** 41 | 42 | ``` 43 | OVERVIEW: Lists all available formats. 44 | 45 | USAGE: text-case list-formats 46 | 47 | OPTIONS: 48 | --version Show the version. 49 | -h, --help Show help information. 50 | ``` 51 | 52 | ### Format Text 53 | 54 | You can format text with the `format` subcommand. However, this is the default subcommand, so it can also be omitted, with the same result. 55 | 56 | **Input** 57 | 58 | Input text can be passed via STDIN, as a string with the input-text option, or from a file by passing the parth. 59 | 60 | **Output** 61 | 62 | The text is writted to STDOUT by default. But you can also specify a file that can be written to instead. 63 | 64 | **Help Text** 65 | 66 | ``` 67 | OVERVIEW: Format text into a specified format. 68 | 69 | USAGE: text-case format --format [--input ] [--input-file ] [--output-file ] 70 | 71 | OPTIONS: 72 | -f, --format Format to be used. 73 | -i, --input Text to be formatted. 74 | --input-file Path of file to be used as input. 75 | --output-file Path of file to be used as output. 76 | --version Show the version. 77 | -h, --help Show help information. 78 | ``` 79 | 80 | [tc]: http://textcase.app 81 | [hb]: https://brew.sh 82 | -------------------------------------------------------------------------------- /Sources/TextCase/Format.swift: -------------------------------------------------------------------------------- 1 | import ArgumentParser 2 | import Foundation 3 | import TextCaseKit 4 | 5 | extension TextCase { 6 | struct Format: ParsableCommand { 7 | static var configuration = CommandConfiguration( 8 | abstract: "Format text into a specified format.") 9 | 10 | @Option(name: [.customShort("f"), .customLong("format")], help: "Format to be used.") 11 | var formatIdentifier: String 12 | 13 | @Option(name: .shortAndLong, help: "Text to be formatted.") 14 | var input: String? 15 | 16 | @Option(name: .long, help: ArgumentHelp("Path of file to be used as input.", valueName: "file")) 17 | var inputFile: String? 18 | 19 | @Option(name: .long, help: ArgumentHelp("Path of file to be used as output.", valueName: "file")) 20 | var outputFile: String? 21 | 22 | mutating func run() { 23 | let formatRepository = FormatRepository() 24 | guard let format = formatRepository.format(for: formatIdentifier) else { 25 | print( 26 | "Format \(formatIdentifier) is not a valid format.", 27 | to: &errorOutputStream 28 | ) 29 | return 30 | } 31 | 32 | guard let input = resolveInput(input: input, inputFile: inputFile) else { 33 | print( 34 | "Input not provided, use textcase --help to see the proper syntax.", 35 | to: &errorOutputStream 36 | ) 37 | return 38 | } 39 | 40 | let formattedText = format.process(input) 41 | 42 | if let path = outputFile { 43 | do { 44 | try formattedText.write(toFile: path, atomically: true, encoding: .utf8) 45 | return 46 | } catch { 47 | print("Error writing to file", to: &errorOutputStream) 48 | print(error.localizedDescription, to: &errorOutputStream) 49 | } 50 | } 51 | 52 | print(formattedText, to: &outputStream) 53 | } 54 | 55 | func resolveInput(input: String?, inputFile: String?) -> String? { 56 | if let input { 57 | return input 58 | } 59 | if let path = inputFile { 60 | do { 61 | return try String(contentsOfFile: path, encoding: .utf8) 62 | } catch { 63 | print("Error reading file from path", to: &errorOutputStream) 64 | print(error.localizedDescription) 65 | } 66 | } 67 | if let input = readLine(strippingNewline: false) { 68 | return input 69 | } 70 | return nil 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Sources/TextCase/ListFormats.swift: -------------------------------------------------------------------------------- 1 | import ArgumentParser 2 | import Foundation 3 | import TextCaseKit 4 | 5 | extension TextCase { 6 | struct ListFormats: ParsableCommand { 7 | static var configuration 8 | = CommandConfiguration(abstract: "Lists all available formats.") 9 | 10 | mutating func run() { 11 | let formats = FormatRepository().getAllFormats() 12 | var maxFormatIdentifierLength = 0 13 | print("Formats (\(formats.count)):", to: &outputStream) 14 | for format in formats { 15 | if format.id.count > maxFormatIdentifierLength { 16 | maxFormatIdentifierLength = format.id.count 17 | } 18 | } 19 | for format in formats { 20 | let formatLine = String(repeating: " ", count: 4) 21 | .appending(format.id) 22 | .appending(String(repeating: " ", count: 2 + (maxFormatIdentifierLength - format.id.count))) 23 | .appending(format.description) 24 | print(formatLine, to: &outputStream) 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/TextCase/TextCase.swift: -------------------------------------------------------------------------------- 1 | import ArgumentParser 2 | 3 | @main 4 | struct TextCase: ParsableCommand { 5 | static var configuration = CommandConfiguration( 6 | commandName: "textcase", 7 | abstract: "A text transformation tool.", 8 | version: "1.4", 9 | subcommands: [Format.self, ListFormats.self], 10 | defaultSubcommand: Format.self 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /Sources/TextCase/io/StandardErrorOutputStream.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct StandardErrorOutputStream: TextOutputStream { 4 | public mutating func write(_ string: String) { 5 | fputs(string, stderr) 6 | } 7 | } 8 | 9 | public var errorOutputStream = StandardErrorOutputStream() 10 | -------------------------------------------------------------------------------- /Sources/TextCase/io/StandardOutputStream.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct StandardOutputStream: TextOutputStream { 4 | public mutating func write(_ string: String) { 5 | fputs(string, stdout) 6 | } 7 | } 8 | 9 | public var outputStream = StandardOutputStream() 10 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Format.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public protocol Format { 4 | var name: String { get } 5 | var id: String { get } 6 | var description: String { get } 7 | 8 | func process(_ input: String) -> String 9 | } 10 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/FormatRepository.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class FormatRepository { 4 | public init() {} 5 | 6 | public func getAllFormats() -> [Format] { 7 | [ 8 | // Cleaning 9 | StripHTML(), StripWhitespace(), TrimWhitespace(), 10 | // Fun 11 | ClapCase(), Hashtags(), Rot13(), Shuffled(), Slug(), SmallCaps(), 12 | Spongebob(), UpsideDown(), 13 | // Simple 14 | Capitalise(), CapitaliseWords(), Lowercase(), Reversed(), Uppercase(), 15 | Sentence(), 16 | // Programming 17 | CamelCase(), PascalCase(), SnakeCase(), KebabCase(), 18 | // Base64 19 | EncodeBase64(), DecodeBase64(), 20 | // Fonts 21 | BoldSerif(), BoldSansSerif(), Italic(), ItalicBoldSerif(), ItalicBoldSansSerif(), 22 | Gothic(), Strikethrough(), Script(), 23 | LettersInCirclesFilled(), LettersInCirclesOutline(), LettersInSquaresFilled(), LettersInSquaresOutline(), 24 | // Title 25 | AMATitleCase(), APTitleCase(), APATitleCase(), BluebookTitleCase(), CMOSTitleCase(), 26 | GuardianTitleCase(), MLATitleCase(), NYTTitleCase(), WikipediaTitleCase(), 27 | // Counts 28 | CountCharacters(), CountCharactersExcWhitespace(), CountWords(), 29 | CountLines(), CountLinesExcBlanks() 30 | ] 31 | } 32 | 33 | public func format(for identifier: String) -> Format? { 34 | for format in getAllFormats() { 35 | if format.id == identifier { 36 | return format 37 | } 38 | } 39 | return nil 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Base64/DecodeBase64.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class DecodeBase64: Format { 4 | public var name: String = "Decode Base64" 5 | public var description: String = "Decode Base64 encoded text." 6 | public var id: String = "base64Decode" 7 | 8 | public init() {} 9 | 10 | public func process(_ input: String) -> String { 11 | guard !input.isEmpty else { return "" } 12 | guard let base64 = Data(base64Encoded: input) else { return input } 13 | guard let string = String(data: base64, encoding: .utf8) else { return input } 14 | return string 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Base64/EncodeBase64.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class EncodeBase64: Format { 4 | public var name: String = "Encode Base64" 5 | public var description: String = "Encode text as Base64." 6 | public var id: String = "base64Encode" 7 | 8 | public init() {} 9 | 10 | public func process(_ input: String) -> String { 11 | guard !input.isEmpty else { return "" } 12 | guard let data = input.data(using: .utf8) else { return input } 13 | return data.base64EncodedString() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Cleaning/StripHTML.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class StripHTML: Format { 4 | public var name: String = "Strip HTML" 5 | public var description: String = "Strip all HTML tags." 6 | public var id: String = "stripHTML" 7 | 8 | public init() {} 9 | 10 | public func process(_ input: String) -> String { 11 | guard !input.isEmpty else { return "" } 12 | 13 | let regex = "<[^>]+>" 14 | 15 | return input 16 | .replacingOccurrences(of: regex, with: "", options: .regularExpression, range: nil) 17 | .trimmingCharacters(in: .whitespacesAndNewlines) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Cleaning/StripWhitespace.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class StripWhitespace: Format { 4 | public var name: String = "Strip Whitespace" 5 | public var description: String = "Remove all whitespace." 6 | public var id: String = "stripWhitespace" 7 | 8 | public init() {} 9 | 10 | public func process(_ input: String) -> String { 11 | guard !input.isEmpty else { return "" } 12 | return input.components(separatedBy: .whitespacesAndNewlines).joined() 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Cleaning/TrimWhitespace.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class TrimWhitespace: Format { 4 | public var name: String = "Trim Whitespace" 5 | public var description: String = "Remove any preceeding or succeeding whitespace." 6 | public var id: String = "trimWhitespace" 7 | 8 | public init() {} 9 | 10 | public func process(_ input: String) -> String { 11 | guard !input.isEmpty else { return "" } 12 | return input.trimmingCharacters(in: .whitespacesAndNewlines) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Counts/CountCharacters.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public final class CountCharacters: Format { 6 | public var name: String = "Count Characters" 7 | public var id: String = "countCharacters" 8 | public var description: String = "Count number of characters." 9 | 10 | public init() {} 11 | 12 | public func process(_ input: String) -> String { 13 | return "\(input.count)" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Counts/CountCharactersExcWhitespace.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public final class CountCharactersExcWhitespace: Format { 6 | public var name: String = "Count Characters Excl. Whitespace" 7 | public var id: String = "countCharactersExclWhitespace" 8 | public var description: String = "Count number of characters, excluding any whitespace characters." 9 | 10 | public init() {} 11 | 12 | public func process(_ input: String) -> String { 13 | guard !input.isEmpty else { return "0" } 14 | let trimmed = input.components(separatedBy: .whitespacesAndNewlines).joined() 15 | return "\(trimmed.count)" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Counts/CountLines.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public final class CountLines: Format { 6 | public var name: String = "Count Lines" 7 | public var id: String = "countLines" 8 | public var description: String = "Count number of lines." 9 | 10 | public init() {} 11 | 12 | public func process(_ input: String) -> String { 13 | guard !input.isEmpty else { return "0" } 14 | let lines = input.components(separatedBy: .newlines) 15 | return "\(lines.count)" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Counts/CountLinesExcBlanks.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public final class CountLinesExcBlanks: Format { 6 | public var name: String = "Count Lines Excl. Blanks" 7 | public var id: String = "countLinesExclBlanks" 8 | public var description: String = "Count number of lines, excluding any blank lines." 9 | 10 | public init() {} 11 | 12 | public func process(_ input: String) -> String { 13 | guard !input.isEmpty else { return "0" } 14 | let components = input.components(separatedBy: .newlines) 15 | let lines = components.filter { !$0.isEmpty } 16 | return "\(lines.count)" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Counts/CountWords.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | public final class CountWords: Format { 6 | public var name: String = "Count Words" 7 | public var id: String = "countWords" 8 | public var description: String = "Count number of words." 9 | 10 | public init() {} 11 | 12 | public func process(_ input: String) -> String { 13 | guard !input.isEmpty else { return "0" } 14 | let components = input.components(separatedBy: .whitespacesAndNewlines) 15 | let words = components.filter { !$0.isEmpty } 16 | return "\(words.count)" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Fonts/BoldSansSerif.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class BoldSansSerif: Format { 4 | public var name: String = "Bold (Sans-Serif)" 5 | public var id: String = "boldSansSerif" 6 | public var description: String = "Bold sans-serif style characters." 7 | public var source = 8 | """ 9 | abcdefghijklmnopqrstuvwxyzµßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿāăąćĉċčďđēĕėęěĝğġģĥħ\ 10 | ĩīĭįıijĵķĸĺļľŀłńņňʼnŋōŏőœŕŗřśŝşšţťŧũūŭůűųŵŷźżžABCDEFGHIJKLMNOPQRSTUVWXYZΜSSÀÁÂÃÄÅÆÇÈÉÊËÌÍ\ 11 | ÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞŸĀĂĄĆĈĊČĎĐĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIIJĴĶĸĹĻĽĿŁŃŅŇʼNŊŌŎŐŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ 12 | """ 13 | public var formatted = 14 | """ 15 | 𝗮𝗯𝗰𝗱𝗲𝗳𝗴𝗵𝗶𝗷𝗸𝗹𝗺𝗻𝗼𝗽𝗾𝗿𝘀𝘁𝘂𝘃𝘄𝘅𝘆𝘇µßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿāăąćĉċčďđēĕėęěĝğġģĥħĩīĭįıijĵķ\ 16 | ĸĺļľŀłńņňʼnŋōŏőœŕŗřśŝşšţťŧũūŭůűųŵŷźżž𝗔𝗕𝗖𝗗𝗘𝗙𝗚𝗛𝗜𝗝𝗞𝗟𝗠𝗡𝗢𝗣𝗤𝗥𝗦𝗧𝗨𝗩𝗪𝗫𝗬𝗭Μ𝗦𝗦ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕ\ 17 | ÖØÙÚÛÜÝÞŸĀĂĄĆĈĊČĎĐĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮ𝗜IJĴĶĸĹĻĽĿŁŃŅŇʼ𝗡ŊŌŎŐŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ 18 | """ 19 | 20 | public init() {} 21 | 22 | public func process(_ input: String) -> String { 23 | FontFormatter().process(input, source: source, formatted: formatted) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Fonts/BoldSerif.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class BoldSerif: Format { 4 | public var name: String = "Bold (Serif)" 5 | public var id: String = "boldSerif" 6 | public var description: String = "Bold serif style characters." 7 | 8 | let source = 9 | """ 10 | abcdefghijklmnopqrstuvwxyzµßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿāăąćĉċčďđēĕėęěĝğġģĥħĩīĭįıijĵķĸĺļľŀłńņňʼnŋō\ 11 | ŏőœŕŗřśŝşšţťŧũūŭůűųŵŷźżžABCDEFGHIJKLMNOPQRSTUVWXYZΜSSÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞŸĀĂĄĆĈĊČĎĐĒĔĖĘĚĜĞĠĢĤĦ\ 12 | ĨĪĬĮIIJĴĶĸĹĻĽĿŁŃŅŇʼNŊŌŎŐŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ 13 | """ 14 | let formatted = 15 | """ 16 | 𝐚𝐛𝐜𝐝𝐞𝐟𝐠𝐡𝐢𝐣𝐤𝐥𝐦𝐧𝐨𝐩𝐪𝐫𝐬𝐭𝐮𝐯𝐰𝐱𝐲𝐳µßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿāăąćĉċčďđēĕėęěĝğġģĥħĩīĭįıijĵķĸĺļľŀłńņňʼnŋōŏőœŕŗř\ 17 | śŝşšţťŧũūŭůűųŵŷźżž𝐀𝐁𝐂𝐃𝐄𝐅𝐆𝐇𝐈𝐉𝐊𝐋𝐌𝐍𝐎𝐏𝐐𝐑𝐒𝐓𝐔𝐕𝐖𝐗𝐘𝐙Μ𝐒𝐒ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞŸĀĂĄĆĈĊČĎĐĒĔĖĘĚĜĞĠĢĤĦĨĪĬ\ 18 | Į𝐈IJĴĶĸĹĻĽĿŁŃŅŇʼ𝐍ŊŌŎŐŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ 19 | """ 20 | 21 | public init() {} 22 | 23 | public func process(_ input: String) -> String { 24 | guard !input.isEmpty else { return "" } 25 | var output = input 26 | 27 | for i in 0 ..< source.count { 28 | let sourceIndex = source.index(source.startIndex, offsetBy: i) 29 | let formattedIndex = formatted.index(formatted.startIndex, offsetBy: i) 30 | let sourceChar = source[sourceIndex] 31 | let formattedChar = formatted[formattedIndex] 32 | output = output.replacingOccurrences(of: String(sourceChar), with: String(formattedChar)) 33 | } 34 | 35 | return output 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Fonts/FontFormat.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | class FontFormatter { 4 | func process(_ input: String, source: String, formatted: String) -> String { 5 | guard !input.isEmpty else { return "" } 6 | var output = input 7 | 8 | for i in 0 ..< source.count { 9 | let sourceIndex = source.index(source.startIndex, offsetBy: i) 10 | let formattedIndex = formatted.index(formatted.startIndex, offsetBy: i) 11 | let sourceChar = source[sourceIndex] 12 | let formattedChar = formatted[formattedIndex] 13 | output = output.replacingOccurrences(of: String(sourceChar), with: String(formattedChar)) 14 | } 15 | 16 | return output 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Fonts/Gothic.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class Gothic: Format { 4 | public var name: String = "Gothic" 5 | public var id: String = "gothic" 6 | public var description: String = "Gothic style characters." 7 | 8 | let source = 9 | """ 10 | abcdefghijklmnopqrstuvwxyzµßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿāăąćĉċčďđēĕėęěĝğġģĥħĩī\ 11 | ĭįıijĵķĸĺļľŀłńņňʼnŋōŏőœŕŗřśŝşšţťŧũūŭůűųŵŷźżžABCDEFGHIJKLMNOPQRSTUVWXYZΜSSÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐ\ 12 | ÑÒÓÔÕÖØÙÚÛÜÝÞŸĀĂĄĆĈĊČĎĐĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIIJĴĶĸĹĻĽĿŁŃŅŇʼNŊŌŎŐŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ 13 | """ 14 | let formatted = 15 | """ 16 | 𝔞𝔟𝔠𝔡𝔢𝔣𝔤𝔥𝔦𝔧𝔨𝔩𝔪𝔫𝔬𝔭𝔮𝔯𝔰𝔱𝔲𝔳𝔴𝔵𝔶𝔷µß𝔞̀𝔞́𝔞̂𝔞̃𝔞̈𝔞̊æ𝔠̧𝔢̀𝔢́𝔢̂𝔢̈ı̀𝔦́𝔦̂𝔦̈ð𝔫̃𝔬̀𝔬́𝔬̂𝔬̃𝔬̈ø𝔲̀𝔲́𝔲̂𝔲̈𝔶́þ𝔶̈𝔞̄𝔞̆𝔞̨𝔠́𝔠̂𝔠̇𝔠̌𝔡̌đ𝔢̄𝔢̆𝔢̇𝔢̨𝔢̌𝔤̂𝔤̆𝔤̇𝔤̧𝔥̂ħ𝔦̃𝔦̄𝔦̆𝔦̨ıij𝔧̂𝔨̧ĸ𝔩́𝔩̧𝔩̌𝔩ł𝔫́𝔫̧𝔫̌ʼnŋ𝔬̄𝔬̆𝔬̋œ\ 17 | 𝔯́𝔯̧𝔯̌𝔰́𝔰̂𝔰̧𝔰̌𝔱̧𝔱̌ŧ𝔲̃𝔲̄𝔲̆𝔲̊𝔲̋𝔲̨𝔴̂𝔶̂𝔷́𝔷̇𝔷̌𝔄𝔅ℭ𝔇𝔈𝔉𝔊ℌℑ𝔍𝔎𝔏𝔐𝔑𝔒𝔓𝔔ℜ𝔖𝔗𝔘𝔙𝔚𝔛𝔜ℨΜ𝔖𝔖𝔄̀𝔄́𝔄̂𝔄̃𝔄̈𝔄̊Æℭ̧𝔈̀𝔈́𝔈̂𝔈̈ℑ̀ℑ́ℑ̂ℑ̈Ð𝔑̃𝔒̀𝔒́𝔒̂𝔒̃𝔒̈Ø\ 18 | 𝔘̀𝔘́𝔘̂𝔘̈𝔜́Þ𝔜̈𝔄̄𝔄̆𝔄̨ℭ́ℭ̂ℭ̇ℭ̌𝔇̌Đ𝔈̄𝔈̆𝔈̇𝔈̨𝔈̌𝔊̂𝔊̆𝔊̇𝔊̧ℌ̂Ħℑ̃ℑ̄ℑ̆ℑ̨ℑIJ𝔍̂𝔎̧ĸ𝔏́𝔏̧𝔏̌𝔏Ł𝔑́𝔑̧𝔑̌ʼ𝔑Ŋ𝔒̄𝔒̆𝔒̋Œℜ́ℜ̧ℜ̌𝔖́𝔖̂𝔖̧𝔖̌𝔗̧𝔗̌Ŧ𝔘̃𝔘̄𝔘̆𝔘̊𝔘̋𝔘̨𝔚̂𝔜̂ℨ́ℨ̇ℨ̌ 19 | """ 20 | 21 | public init() {} 22 | 23 | public func process(_ input: String) -> String { 24 | guard !input.isEmpty else { return "" } 25 | var output = input 26 | 27 | for i in 0 ..< source.count { 28 | let sourceIndex = source.index(source.startIndex, offsetBy: i) 29 | let formattedIndex = formatted.index(formatted.startIndex, offsetBy: i) 30 | let sourceChar = source[sourceIndex] 31 | let formattedChar = formatted[formattedIndex] 32 | output = output.replacingOccurrences(of: String(sourceChar), with: String(formattedChar)) 33 | } 34 | 35 | return output 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Fonts/Italic.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class Italic: Format { 4 | public var name: String = "Italic" 5 | public var id: String = "italic" 6 | public var description: String = "Italic style characters." 7 | 8 | let source = 9 | """ 10 | abcdefghijklmnopqrstuvwxyzµßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿāăąćĉċčďđēĕėęěĝğġģĥħĩī\ 11 | ĭįıijĵķĸĺļľŀłńņňʼnŋōŏőœŕŗřśŝşšţťŧũūŭůűųŵŷźżžABCDEFGHIJKLMNOPQRSTUVWXYZΜSSÀÁÂÃÄÅÆÇÈÉÊ\ 12 | ËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞŸĀĂĄĆĈĊČĎĐĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIIJĴĶĸĹĻĽĿŁŃŅŇʼNŊŌŎŐŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ 13 | """ 14 | let formatted = 15 | """ 16 | 𝘢𝘣𝘤𝘥𝘦𝘧𝘨𝘩𝘪𝘫𝘬𝘭𝘮𝘯𝘰𝘱𝘲𝘳𝘴𝘵𝘶𝘷𝘸𝘹𝘺𝘻µßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿāăąćĉċčďđēĕėęěĝğġģĥħĩīĭįıijĵķĸĺļ\ 17 | ľŀłńņňʼnŋōŏőœŕŗřśŝşšţťŧũūŭůűųŵŷźżž𝘈𝘉𝘊𝘋𝘌𝘍𝘎𝘏𝘐𝘑𝘒𝘓𝘔𝘕𝘖𝘗𝘘𝘙𝘚𝘛𝘜𝘝𝘞𝘟𝘠𝘡Μ𝘚𝘚ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔ\ 18 | ÕÖØÙÚÛÜÝÞŸĀĂĄĆĈĊČĎĐĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮ𝘐IJĴĶĸĹĻĽĿŁŃŅŇʼ𝘕ŊŌŎŐŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ 19 | """ 20 | 21 | public init() {} 22 | 23 | public func process(_ input: String) -> String { 24 | FontFormatter().process(input, source: source, formatted: formatted) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Fonts/ItalicBoldSansSerif.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class ItalicBoldSansSerif: Format { 4 | public var name: String = "Italic Bold (Sans-Serif)" 5 | public var id: String = "italicBoldSansSerif" 6 | public var description: String = "Italic bold sans-serif style characters." 7 | 8 | let source = 9 | """ 10 | abcdefghijklmnopqrstuvwxyzµßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿāăąćĉċčďđēĕėęěĝğġģĥħĩīĭįıijĵķ\ 11 | ĸĺļľŀłńņňʼnŋōŏőœŕŗřśŝşšţťŧũūŭůűųŵŷźżžABCDEFGHIJKLMNOPQRSTUVWXYZΜSSÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ\ 12 | ØÙÚÛÜÝÞŸĀĂĄĆĈĊČĎĐĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIIJĴĶĸĹĻĽĿŁŃŅŇʼNŊŌŎŐŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ 13 | """ 14 | let formatted = 15 | """ 16 | 𝙖𝙗𝙘𝙙𝙚𝙛𝙜𝙝𝙞𝙟𝙠𝙡𝙢𝙣𝙤𝙥𝙦𝙧𝙨𝙩𝙪𝙫𝙬𝙭𝙮𝙯µßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿāăąćĉċčďđēĕėęěĝğġģĥħĩīĭįıijĵķĸĺļľŀł\ 17 | ńņňʼnŋōŏőœŕŗřśŝşšţťŧũūŭůűųŵŷźżž𝘼𝘽𝘾𝘿𝙀𝙁𝙂𝙃𝙄𝙅𝙆𝙇𝙈𝙉𝙊𝙋𝙌𝙍𝙎𝙏𝙐𝙑𝙒𝙓𝙔𝙕Μ𝙎𝙎ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚ\ 18 | ÛÜÝÞŸĀĂĄĆĈĊČĎĐĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮ𝙄IJĴĶĸĹĻĽĿŁŃŅŇʼ𝙉ŊŌŎŐŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ 19 | """ 20 | 21 | public init() {} 22 | 23 | public func process(_ input: String) -> String { 24 | FontFormatter().process(input, source: source, formatted: formatted) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Fonts/ItalicBoldSerif.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class ItalicBoldSerif: Format { 4 | public var name: String = "ItalicBoldSerif" 5 | public var id: String = "italicBoldSerif" 6 | public var description: String = "Italic bold serif style characters." 7 | 8 | let source = 9 | """ 10 | abcdefghijklmnopqrstuvwxyzµßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿāăąćĉċčďđēĕėęěĝğġģ\ 11 | ĥħĩīĭįıijĵķĸĺļľŀłńņňʼnŋōŏőœŕŗřśŝşšţťŧũūŭůűųŵŷźżžABCDEFGHIJKLMNOPQRSTUVWXYZΜSSÀÁÂÃÄÅÆÇÈÉ\ 12 | ÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞŸĀĂĄĆĈĊČĎĐĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIIJĴĶĸĹĻĽĿŁŃŅŇʼNŊŌŎŐŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ 13 | """ 14 | let formatted = 15 | """ 16 | 𝒂𝒃𝒄𝒅𝒆𝒇𝒈𝒉𝒊𝒋𝒌𝒍𝒎𝒏𝒐𝒑𝒒𝒓𝒔𝒕𝒖𝒗𝒘𝒙𝒚𝒛µßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿāăąćĉċčďđēĕėęěĝğġģĥħĩī\ 17 | ĭįıijĵķĸĺļľŀłńņňʼnŋōŏőœŕŗřśŝşšţťŧũūŭůűųŵŷźżž𝑨𝑩𝑪𝑫𝑬𝑭𝑮𝑯𝑰𝑱𝑲𝑳𝑴𝑵𝑶𝑷𝑸𝑹𝑺𝑻𝑼𝑽𝑾𝑿𝒀𝒁Μ𝑺𝑺ÀÁÂÃÄÅÆÇÈÉ\ 18 | ÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞŸĀĂĄĆĈĊČĎĐĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮ𝑰IJĴĶĸĹĻĽĿŁŃŅŇʼ𝑵ŊŌŎŐŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ 19 | """ 20 | 21 | public init() {} 22 | 23 | public func process(_ input: String) -> String { 24 | FontFormatter().process(input, source: source, formatted: formatted) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Fonts/LettersInCirclesFilled.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class LettersInCirclesFilled: Format { 4 | public var name: String = "Letters in Circles (Filled)" 5 | public var id: String = "lettersInCirclesFilled" 6 | public var description: String = "Characters inside filled circles." 7 | 8 | let source = 9 | """ 10 | abcdefghijklmnopqrstuvwxyzµßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿāăąćĉċčďđēĕėęě\ 11 | ĝğġģĥħĩīĭįıijĵķĸĺļľŀłńņňʼnŋōŏőœŕŗřśŝşšţťŧũūŭůűųŵŷźżžABCDEFGHIJKLMNOPQRSTUVWXYZΜSSÀÁÂ\ 12 | ÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞŸĀĂĄĆĈĊČĎĐĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIIJĴĶĸĹĻĽĿŁŃŅŇʼNŊŌŎŐŒŔŖŘŚŜŞŠŢŤŦ\ 13 | ŨŪŬŮŰŲŴŶŹŻŽ 14 | """ 15 | let formatted = 16 | """ 17 | 🅐🅑🅒🅓🅔🅕🅖🅗🅘🅙🅚🅛🅜🅝🅞🅟🅠🅡🅢🅣🅤🅥🅦🅧🅨🅩µß🅐̀🅐🅐̂🅐̃🅐̈🅐̊æ🅒̧🅔̀🅔🅔̂🅔̈ı̀🅘🅘̂🅘̈ð🅝̃🅞̀🅞\ 18 | 🅞̂🅞̃🅞̈ø🅤̀🅤🅤̂🅤̈🅨þ🅨̈🅐🅐̆🅐̨🅒🅒̂🅒̇🅒̌🅓̌đ🅔🅔̆🅔̇🅔̨🅔̌🅖̂🅖̆🅖̇🅖̧🅗̂ħ🅘̃🅘🅘̆🅘̨ıij🅙̂🅚̧ĸ🅛🅛̧🅛̌🅛ł🅝🅝̧🅝̌ʼnŋ🅞🅞̆🅞̋œ🅡🅡̧\ 19 | 🅡̌🅢🅢̂🅢̧🅢̌🅣̧🅣̌ŧ🅤̃🅤🅤̆🅤̊🅤̋🅤̨🅦̂🅨̂🅩🅩̇🅩̌🅐🅑🅒🅓🅔🅕🅖🅗🅘🅙🅚🅛🅜🅝🅞🅟🅠🅡🅢🅣🅤🅥🅦🅧🅨🅩Μ🅢🅢🅐̀🅐🅐̂🅐̃\ 20 | 🅐̈🅐̊Æ🅒̧🅔̀🅔🅔̂🅔̈🅘̀🅘🅘̂🅘̈Ð🅝̃🅞̀🅞🅞̂🅞̃🅞̈Ø🅤̀🅤🅤̂🅤̈🅨Þ🅨̈🅐🅐̆🅐̨🅒🅒̂🅒̇🅒̌🅓̌Đ🅔🅔̆🅔̇🅔̨🅔̌🅖̂🅖̆🅖̇🅖̧🅗̂Ħ🅘̃🅘🅘̆🅘̨🅘IJ🅙̂\ 21 | 🅚̧ĸ🅛🅛̧🅛̌🅛Ł🅝🅝̧🅝̌ʼ🅝Ŋ🅞🅞̆🅞̋Œ🅡🅡̧🅡̌🅢🅢̂🅢̧🅢̌🅣̧🅣̌Ŧ🅤̃🅤🅤̆🅤̊🅤̋🅤̨🅦̂🅨̂🅩🅩̇🅩̌ 22 | """ 23 | 24 | public init() {} 25 | 26 | public func process(_ input: String) -> String { 27 | FontFormatter().process(input, source: source, formatted: formatted) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Fonts/LettersInCirclesOutline.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class LettersInCirclesOutline: Format { 4 | public var name: String = "Letters in Circles (Outline)" 5 | public var id: String = "lettersInCirclesOutline" 6 | public var description: String = "Characters inside outlined circles." 7 | 8 | let source = 9 | """ 10 | abcdefghijklmnopqrstuvwxyzµßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿāăąćĉċčďđēĕėęěĝğġģĥħĩīĭįıijĵķĸĺ\ 11 | ļľŀłńņňʼnŋōŏőœŕŗřśŝşšţťŧũūŭůűųŵŷźżžABCDEFGHIJKLMNOPQRSTUVWXYZΜSSÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞŸĀĂĄ\ 12 | ĆĈĊČĎĐĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIIJĴĶĸĹĻĽĿŁŃŅŇʼNŊŌŎŐŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ 13 | """ 14 | let formatted = 15 | """ 16 | ⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩµßⓐ̀ⓐⓐ̂ⓐ̃ⓐ̈ⓐ̊æⓒ̧ⓔ̀ⓔⓔ̂ⓔ̈ı̀ⓘⓘ̂ⓘ̈ðⓝ̃ⓞ̀ⓞⓞ̂ⓞ̃ⓞ̈øⓤ̀ⓤⓤ̂ⓤ̈ⓨþ\ 17 | ⓨ̈ⓐⓐ̆ⓐ̨ⓒⓒ̂ⓒ̇ⓒ̌ⓓ̌đⓔⓔ̆ⓔ̇ⓔ̨ⓔ̌ⓖ̂ⓖ̆ⓖ̇ⓖ̧ⓗ̂ħⓘ̃ⓘⓘ̆ⓘ̨ıijⓙ̂ⓚ̧ĸⓛⓛ̧ⓛ̌ⓛłⓝⓝ̧ⓝ̌ʼnŋⓞⓞ̆ⓞ̋œⓡⓡ̧ⓡ̌ⓢⓢ̂ⓢ̧ⓢ̌ⓣ̧ⓣ̌ŧⓤ̃ⓤⓤ̆ⓤ̊ⓤ̋ⓤ̨ⓦ̂ⓨ̂ⓩⓩ̇\ 18 | ⓩ̌ⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀⓁⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌⓍⓎⓏΜⓈⓈⒶ̀ⒶⒶ̂Ⓐ̃Ⓐ̈Ⓐ̊ÆⒸ̧Ⓔ̀ⒺⒺ̂Ⓔ̈Ⓘ̀ⒾⒾ̂Ⓘ̈ÐⓃ̃Ⓞ̀ⓄⓄ̂Ⓞ̃Ⓞ̈ØⓊ̀ⓊⓊ̂Ⓤ̈ⓎÞⓎ̈Ⓐ\ 19 | Ⓐ̆Ⓐ̨ⒸⒸ̂Ⓒ̇Ⓒ̌Ⓓ̌ĐⒺⒺ̆Ⓔ̇Ⓔ̨Ⓔ̌Ⓖ̂Ⓖ̆Ⓖ̇Ⓖ̧Ⓗ̂ĦⒾ̃ⒾⒾ̆Ⓘ̨ⒾIJⒿ̂Ⓚ̧ĸⓁⓁ̧Ⓛ̌ⓁŁⓃⓃ̧Ⓝ̌ʼⓃŊⓄⓄ̆Ⓞ̋ŒⓇⓇ̧Ⓡ̌ⓈⓈ̂Ⓢ̧Ⓢ̌Ⓣ̧Ⓣ̌ŦⓊ̃ⓊⓊ̆Ⓤ̊Ⓤ̋Ⓤ̨Ⓦ̂Ⓨ̂ⓏⓏ̇Ⓩ̌ 20 | """ 21 | 22 | public init() {} 23 | 24 | public func process(_ input: String) -> String { 25 | FontFormatter().process(input, source: source, formatted: formatted) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Fonts/LettersInSquaresFilled.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class LettersInSquaresFilled: Format { 4 | public var name: String = "Letters in Squares (Filled)" 5 | public var id: String = "lettersInSquaresFilled" 6 | public var description: String = "Characters inside filled squares." 7 | 8 | let source = 9 | """ 10 | abcdefghijklmnopqrstuvwxyzµßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿāăąćĉċčďđēĕėęěĝğġģĥħĩīĭįıijĵķĸĺļľŀłńņňʼnŋōŏő\ 11 | œŕŗřśŝşšţťŧũūŭůűųŵŷźżžABCDEFGHIJKLMNOPQRSTUVWXYZΜSSÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞŸĀĂĄĆĈĊČĎĐĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIIJĴ\ 12 | ĶĸĹĻĽĿŁŃŅŇʼNŊŌŎŐŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ 13 | """ 14 | let formatted = 15 | """ 16 | 🅰🅱🅲🅳🅴🅵🅶🅷🅸🅹🅺🅻🅼🅽🅾🅿🆀🆁🆂🆃🆄🆅🆆🆇🆈🆉µß🅰̀🅰🅰̂🅰̃🅰̈🅰̊æ🅲̧🅴̀🅴🅴̂🅴̈ı̀🅸🅸̂🅸̈ð🅽̃🅾̀🅾🅾̂🅾̃🅾̈ø🆄̀🆄🆄̂🆄̈🆈þ🆈̈🅰🅰̆🅰̨🅲🅲̂🅲̇🅲̌\ 17 | 🅳̌đ🅴🅴̆🅴̇🅴̨🅴̌🅶̂🅶̆🅶̇🅶̧🅷̂ħ🅸̃🅸🅸̆🅸̨ıij🅹̂🅺̧ĸ🅻🅻̧🅻̌🅻ł🅽🅽̧🅽̌ʼnŋ🅾🅾̆🅾̋œ🆁🆁̧🆁̌🆂🆂̂🆂̧🆂̌🆃̧🆃̌ŧ🆄̃🆄🆄̆🆄̊🆄̋🆄̨🆆̂🆈̂🆉🆉̇🆉̌🅰🅱🅲🅳🅴🅵🅶🅷🅸🅹🅺🅻🅼🅽🅾\ 18 | 🅿🆀🆁🆂🆃🆄🆅🆆🆇🆈🆉Μ🆂🆂🅰̀🅰🅰̂🅰̃🅰̈🅰̊Æ🅲̧🅴̀🅴🅴̂🅴̈🅸̀🅸🅸̂🅸̈Ð🅽̃🅾̀🅾🅾̂🅾̃🅾̈Ø🆄̀🆄🆄̂🆄̈🆈Þ🆈̈🅰🅰̆🅰̨🅲🅲̂🅲̇🅲̌🅳̌Đ🅴🅴̆🅴̇🅴̨🅴̌🅶̂🅶̆🅶̇🅶̧🅷̂Ħ🅸̃🅸🅸̆🅸̨🅸IJ\ 19 | 🅹̂🅺̧ĸ🅻🅻̧🅻̌🅻Ł🅽🅽̧🅽̌ʼ🅽Ŋ🅾🅾̆🅾̋Œ🆁🆁̧🆁̌🆂🆂̂🆂̧🆂̌🆃̧🆃̌Ŧ🆄̃🆄🆄̆🆄̊🆄̋🆄̨🆆̂🆈̂🆉🆉̇🆉̌ 20 | """ 21 | 22 | public init() {} 23 | 24 | public func process(_ input: String) -> String { 25 | FontFormatter().process(input, source: source, formatted: formatted) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Fonts/LettersInSquaresOutline.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class LettersInSquaresOutline: Format { 4 | public var name: String = "Letters in Squares (Outline)" 5 | public var id: String = "lettersInSquaresOutline" 6 | public var description: String = "Characters inside outlined squares." 7 | 8 | let source = 9 | """ 10 | abcdefghijklmnopqrstuvwxyzµßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿāăąćĉċčďđēĕėęěĝğġģ\ 11 | ĥħĩīĭįıijĵķĸĺļľŀłńņňʼnŋōŏőœŕŗřśŝşšţťŧũūŭůűųŵŷźżžABCDEFGHIJKLMNOPQRSTUVWXYZΜSSÀÁÂÃÄÅÆÇÈÉÊËÌÍ\ 12 | ÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞŸĀĂĄĆĈĊČĎĐĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIIJĴĶĸĹĻĽĿŁŃŅŇʼNŊŌŎŐŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ 13 | """ 14 | let formatted = 15 | """ 16 | 🄰🄱🄲🄳🄴🄵🄶🄷🄸🄹🄺🄻🄼🄽🄾🄿🅀🅁🅂🅃🅄🅅🅆🅇🅈🅉µß🄰̀🄰🄰̂🄰̃🄰̈🄰̊æ🄲̧🄴̀🄴🄴̂🄴̈ı̀🄸🄸̂🄸̈ð🄽̃🄾̀🄾🄾̂🄾̃🄾̈\ 17 | ø🅄̀🅄🅄̂🅄̈🅈þ🅈̈🄰🄰̆🄰̨🄲🄲̂🄲̇🄲̌🄳̌đ🄴🄴̆🄴̇🄴̨🄴̌🄶̂🄶̆🄶̇🄶̧🄷̂ħ🄸̃🄸🄸̆🄸̨ıij🄹̂🄺̧ĸ🄻🄻̧🄻̌🄻ł🄽🄽̧🄽̌ʼnŋ🄾🄾̆🄾̋œ🅁🅁̧🅁̌🅂🅂̂🅂̧🅂̌🅃̧🅃̌\ 18 | ŧ🅄̃🅄🅄̆🅄̊🅄̋🅄̨🅆̂🅈̂🅉🅉̇🅉̌🄰🄱🄲🄳🄴🄵🄶🄷🄸🄹🄺🄻🄼🄽🄾🄿🅀🅁🅂🅃🅄🅅🅆🅇🅈🅉Μ🅂🅂🄰̀🄰🄰̂🄰̃🄰̈🄰̊Æ🄲̧🄴̀🄴🄴̂🄴̈🄸̀🄸🄸̂🄸̈\ 19 | Ð🄽̃🄾̀🄾🄾̂🄾̃🄾̈Ø🅄̀🅄🅄̂🅄̈🅈Þ🅈̈🄰🄰̆🄰̨🄲🄲̂🄲̇🄲̌🄳̌Đ🄴🄴̆🄴̇🄴̨🄴̌🄶̂🄶̆🄶̇🄶̧🄷̂Ħ🄸̃🄸🄸̆🄸̨🄸IJ🄹̂🄺̧ĸ🄻🄻̧🄻̌🄻Ł🄽🄽̧🄽̌ʼ🄽Ŋ🄾🄾̆🄾̋Œ🅁\ 20 | 🅁̧🅁̌🅂🅂̂🅂̧🅂̌🅃̧🅃̌Ŧ🅄̃🅄🅄̆🅄̊🅄̋🅄̨🅆̂🅈̂🅉🅉̇🅉̌ 21 | """ 22 | 23 | public init() {} 24 | 25 | public func process(_ input: String) -> String { 26 | FontFormatter().process(input, source: source, formatted: formatted) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Fonts/Script.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class Script: Format { 4 | public var name: String = "Script" 5 | public var id: String = "script" 6 | public var description: String = "Script style characters." 7 | 8 | let source = 9 | """ 10 | abcdefghijklmnopqrstuvwxyzµßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿāăąćĉċčďđēĕėęěĝğġģĥħĩīĭįıijĵķĸĺ\ 11 | ļľŀłńņňʼnŋōŏőœŕŗřśŝşšţťŧũūŭůűųŵŷźżžABCDEFGHIJKLMNOPQRSTUVWXYZΜSSÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚ\ 12 | ÛÜÝÞŸĀĂĄĆĈĊČĎĐĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIIJĴĶĸĹĻĽĿŁŃŅŇʼNŊŌŎŐŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ 13 | """ 14 | let formatted = 15 | """ 16 | 𝒶𝒷𝒸𝒹ℯ𝒻ℊ𝒽𝒾𝒿𝓀𝓁𝓂𝓃ℴ𝓅𝓆𝓇𝓈𝓉𝓊𝓋𝓌𝓍𝓎𝓏µß𝒶̀𝒶́𝒶̂𝒶̃𝒶̈𝒶̊æ𝒸̧ℯ̀ℯ́ℯ̂ℯ̈ı̀𝒾́𝒾̂𝒾̈ð𝓃̃ℴ̀ℴ́ℴ̂ℴ̃ℴ̈ø𝓊̀𝓊́𝓊̂𝓊̈𝓎́þ𝓎̈𝒶̄𝒶̆𝒶̨𝒸́𝒸̂𝒸̇𝒸̌𝒹̌đℯ̄ℯ̆ℯ̇ℯ̨ℯ̌ℊ̂ℊ̆ℊ̇ℊ̧𝒽̂ħ\ 17 | 𝒾̃𝒾̄𝒾̆𝒾̨ıij𝒿̂𝓀̧ĸ𝓁́𝓁̧𝓁̌𝓁ł𝓃́𝓃̧𝓃̌ʼnŋℴ̄ℴ̆ℴ̋œ𝓇́𝓇̧𝓇̌𝓈́𝓈̂𝓈̧𝓈̌𝓉̧𝓉̌ŧ𝓊̃𝓊̄𝓊̆𝓊̊𝓊̋𝓊̨𝓌̂𝓎̂𝓏́𝓏̇𝓏̌𝒜ℬ𝒞𝒟ℰℱ𝒢ℋℐ𝒥𝒦ℒℳ𝒩𝒪𝒫𝒬ℛ𝒮𝒯𝒰𝒱𝒲𝒳𝒴𝒵Μ𝒮𝒮𝒜̀𝒜́𝒜̂\ 18 | 𝒜̃𝒜̈𝒜̊Æ𝒞̧ℰ̀ℰ́ℰ̂ℰ̈ℐ̀ℐ́ℐ̂ℐ̈Ð𝒩̃𝒪̀𝒪́𝒪̂𝒪̃𝒪̈Ø𝒰̀𝒰́𝒰̂𝒰̈𝒴́Þ𝒴̈𝒜̄𝒜̆𝒜̨𝒞́𝒞̂𝒞̇𝒞̌𝒟̌Đℰ̄ℰ̆ℰ̇ℰ̨ℰ̌𝒢̂𝒢̆𝒢̇𝒢̧ℋ̂Ħℐ̃ℐ̄ℐ̆ℐ̨ℐIJ𝒥̂𝒦̧ĸℒ́ℒ̧ℒ̌ℒŁ𝒩́𝒩̧𝒩̌ʼ𝒩Ŋ𝒪̄𝒪̆\ 19 | 𝒪̋Œℛ́ℛ̧ℛ̌𝒮́𝒮̂𝒮̧𝒮̌𝒯̧𝒯̌Ŧ𝒰̃𝒰̄𝒰̆𝒰̊𝒰̋𝒰̨𝒲̂𝒴̂𝒵́𝒵̇𝒵̌ 20 | """ 21 | 22 | public init() {} 23 | 24 | public func process(_ input: String) -> String { 25 | FontFormatter().process(input, source: source, formatted: formatted) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Fonts/Strikethrough.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class Strikethrough: Format { 4 | public var name: String = "Strikethrough" 5 | public var id: String = "strikethrough" 6 | public var description: String = "Strikethrough style characters." 7 | 8 | let source = 9 | """ 10 | abcdefghijklmnopqrstuvwxyzµßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿāăąćĉċčďđēĕėęěĝğġģĥħĩīĭįıijĵķĸĺ\ 11 | ļľŀłńņňʼnŋōŏőœŕŗřśŝşšţťŧũūŭůűųŵŷźżžABCDEFGHIJKLMNOPQRSTUVWXYZΜSSÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞŸĀĂĄ\ 12 | ĆĈĊČĎĐĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIIJĴĶĸĹĻĽĿŁŃŅŇʼNŊŌŎŐŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ 13 | """ 14 | let formatted = 15 | """ 16 | a̶b̶c̶d̶e̶f̶g̶h̶i̶j̶k̶l̶m̶n̶o̶p̶q̶r̶s̶t̶u̶v̶w̶x̶y̶z̶µ̶ß̶à̶á̶â̶ã̶ä̶å̶æ̶ç̶è̶é̶ê̶ë̶ì̶í̶î̶ï̶ð̶ñ̶ò̶ó̶ô̶õ̶ö̶ø̶ù̶ú̶û̶ü̶ý̶þ̶ÿ̶ā̶ă̶ą̶ć̶ĉ̶ċ̶č̶ď̶đ̶ē̶ĕ̶ė̶ę̶ě̶ĝ̶ğ̶ġ̶ģ̶ĥ̶ħ̶ĩ̶ī̶ĭ̶į̶ı̶ij̶ĵ̶ķ̶ĸ̶ĺ̶\ 17 | ļ̶ľ̶ŀ̶ł̶ń̶ņ̶ň̶ʼn̶ŋ̶ō̶ŏ̶ő̶œ̶ŕ̶ŗ̶ř̶ś̶ŝ̶ş̶š̶ţ̶ť̶ŧ̶ũ̶ū̶ŭ̶ů̶ű̶ų̶ŵ̶ŷ̶ź̶ż̶ž̶A̶B̶C̶D̶E̶F̶G̶H̶I̶J̶K̶L̶M̶N̶O̶P̶Q̶R̶S̶T̶U̶V̶W̶X̶Y̶Z̶Μ̶S̶S̶À̶Á̶Â̶Ã̶Ä̶Å̶Æ̶Ç̶È̶É̶Ê̶Ë̶Ì̶Í̶Î̶Ï̶Ð̶Ñ̶Ò̶Ó̶Ô̶Õ̶Ö̶Ø̶Ù̶Ú̶Û̶\ 18 | Ü̶Ý̶Þ̶Ÿ̶Ā̶Ă̶Ą̶Ć̶Ĉ̶Ċ̶Č̶Ď̶Đ̶Ē̶Ĕ̶Ė̶Ę̶Ě̶Ĝ̶Ğ̶Ġ̶Ģ̶Ĥ̶Ħ̶Ĩ̶Ī̶Ĭ̶Į̶I̶IJ̶Ĵ̶Ķ̶ĸ̶Ĺ̶Ļ̶Ľ̶Ŀ̶Ł̶Ń̶Ņ̶Ň̶ʼ̶N̶Ŋ̶Ō̶Ŏ̶Ő̶Œ̶Ŕ̶Ŗ̶Ř̶Ś̶Ŝ̶Ş̶Š̶Ţ̶Ť̶Ŧ̶Ũ̶Ū̶Ŭ̶Ů̶Ű̶Ų̶Ŵ̶Ŷ̶Ź̶Ż̶Ž̶ 19 | """ 20 | 21 | public init() {} 22 | 23 | public func process(_ input: String) -> String { 24 | FontFormatter().process(input, source: source, formatted: formatted) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Fun/ClapCase.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class ClapCase: Format { 4 | public var name: String = "Clap Case" 5 | public var description: String = "Put 👏 between every word." 6 | public var id: String = "clapCase" 7 | 8 | public init() {} 9 | 10 | public func process(_ input: String) -> String { 11 | guard !input.isEmpty else { return "" } 12 | var clapped = input 13 | 14 | let separators = [" ", "_", "-"] 15 | 16 | for separator in separators { 17 | clapped = clapped.replacingOccurrences(of: separator, with: " 👏 ") 18 | } 19 | 20 | return clapped 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Fun/Hashtags.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class Hashtags: Format { 4 | public var name: String = "Hashtags" 5 | public var description: String = "Convert words into hashtags." 6 | public var id: String = "hashtags" 7 | 8 | public init() {} 9 | 10 | public func process(_ input: String) -> String { 11 | guard !input.isEmpty else { return "" } 12 | let withoutSpaces = input.replacingOccurrences(of: " ", with: "") 13 | guard !withoutSpaces.isEmpty else { return input } 14 | 15 | var hashtags = input 16 | 17 | if !input.hasPrefix(" ") { 18 | hashtags = "#\(hashtags)" 19 | } 20 | 21 | hashtags = hashtags.replacingOccurrences(of: "#", with: " ") 22 | 23 | let alternativeSeparators = [",", ".", ":", ";"] 24 | for separator in alternativeSeparators { 25 | hashtags = hashtags.replacingOccurrences(of: separator, with: "") 26 | } 27 | 28 | var cleaningMultipleSpaces = true 29 | while cleaningMultipleSpaces { 30 | hashtags = hashtags.replacingOccurrences(of: " ", with: " ") 31 | cleaningMultipleSpaces = hashtags.contains(" ") 32 | } 33 | 34 | hashtags = hashtags.replacingOccurrences(of: " ", with: " #") 35 | 36 | return TrimWhitespace().process(hashtags) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Fun/Rot13.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class Rot13: Format { 4 | public var name: String = "Rot13" 5 | public var description: String = "Reverse all characters." 6 | public var id: String = "rot13" 7 | 8 | public init() {} 9 | 10 | private let lowercase = "abcdefghijklmnopqrstuvwxyz" 11 | private let uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 12 | private let lowercaseConverted = "nopqrstuvwxyzabcdefghijklm" 13 | private let uppercaseConverted = "NOPQRSTUVWXYZABCDEFGHIJKLM" 14 | 15 | public func process(_ input: String) -> String { 16 | guard !input.isEmpty else { return "" } 17 | var output = "" 18 | 19 | for char in input { 20 | if let index = lowercase.firstIndex(of: char) { 21 | let converted = lowercaseConverted[index] 22 | output.append(converted) 23 | } else if let index = uppercase.firstIndex(of: char) { 24 | let converted = uppercaseConverted[index] 25 | output.append(converted) 26 | } else { 27 | output.append(char) 28 | } 29 | } 30 | return output 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Fun/Shuffled.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class Shuffled: Format { 4 | public var name: String = "Shuffled" 5 | public var description: String = "Shuffle all characters." 6 | public var id: String = "shuffled" 7 | 8 | public init() {} 9 | 10 | public func process(_ input: String) -> String { 11 | guard !input.isEmpty else { return "" } 12 | return String(input.shuffled()) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Fun/Slug.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class Slug: Format { 4 | public var name: String = "Slug" 5 | public var description: String = "Convert the text into a slug." 6 | public var id: String = "slug" 7 | 8 | public init() {} 9 | 10 | public func process(_ input: String) -> String { 11 | guard !input.isEmpty else { return "" } 12 | var slug = input 13 | 14 | let separators = [" ", "_", "-"] 15 | 16 | for separator in separators { 17 | slug = slug.replacingOccurrences(of: separator, with: "-") 18 | } 19 | 20 | let characterSetToStrip = CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "-")) 21 | slug = slug.components(separatedBy: characterSetToStrip.inverted).joined() 22 | .replacingOccurrences(of: "--", with: "-") 23 | .replacingOccurrences(of: "--", with: "-") 24 | .lowercased() 25 | return slug 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Fun/SmallCaps.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class SmallCaps: Format { 4 | public var name: String = "Small Caps" 5 | public var description: String = "Convert all characters into small capital characters." 6 | public var id: String = "smallCaps" 7 | 8 | public init() {} 9 | 10 | let capsMap: [String: String] = [ 11 | "a": "ᴀ", 12 | "b": "ʙ", 13 | "c": "ᴄ", 14 | "d": "ᴅ", 15 | "e": "ᴇ", 16 | "f": "ғ", 17 | "g": "ɢ", 18 | "h": "ʜ", 19 | "i": "ɪ", 20 | "j": "ᴊ", 21 | "k": "ᴋ", 22 | "l": "ʟ", 23 | "m": "ᴍ", 24 | "n": "ɴ", 25 | "o": "ᴏ", 26 | "p": "ᴘ", 27 | "q": "ǫ", 28 | "r": "ʀ", 29 | "s": "s", 30 | "t": "ᴛ", 31 | "u": "ᴜ", 32 | "v": "ᴠ", 33 | "w": "ᴡ", 34 | "x": "x", 35 | "y": "ʏ", 36 | "z": "ᴢ", 37 | "µ": "µ", 38 | "ß": "ß", 39 | "à": "ᴀ̀", 40 | "á": "ᴀ́", 41 | "â": "ᴀ̂", 42 | "ã": "ᴀ̃", 43 | "ä": "ᴀ̈", 44 | "å": "ᴀ̊", 45 | "æ": "æ", 46 | "ç": "ᴄ̧", 47 | "è": "ᴇ̀", 48 | "é": "ᴇ́", 49 | "ê": "ᴇ̂", 50 | "ë": "ᴇ̈", 51 | "ì": "ɪ̀", 52 | "í": "ɪ́", 53 | "î": "ɪ̂", 54 | "ï": "ɪ̈", 55 | "ð": "ð", 56 | "ñ": "ɴ̃", 57 | "ò": "ᴏ̀", 58 | "ó": "ᴏ́", 59 | "ô": "ᴏ̂", 60 | "õ": "ᴏ̃", 61 | "ö": "ᴏ̈", 62 | "ø": "ø", 63 | "ù": "ᴜ̀", 64 | "ú": "ᴜ́", 65 | "û": "ᴜ̂", 66 | "ü": "ᴜ̈", 67 | "ý": "ʏ́", 68 | "þ": "þ", 69 | "ÿ": "ʏ̈", 70 | "ā": "ᴀ̄", 71 | "ă": "ᴀ̆", 72 | "ą": "ᴀ̨", 73 | "ć": "ᴄ́", 74 | "ĉ": "ᴄ̂", 75 | "ċ": "ᴄ̇", 76 | "č": "ᴄ̌", 77 | "ď": "ᴅ̌", 78 | "đ": "đ", 79 | "ē": "ᴇ̄", 80 | "ĕ": "ᴇ̆", 81 | "ė": "ᴇ̇", 82 | "ę": "ᴇ̨", 83 | "ě": "ᴇ̌", 84 | "ĝ": "ɢ̂", 85 | "ğ": "ɢ̆", 86 | "ġ": "ɢ̇", 87 | "ģ": "ɢ̧", 88 | "ĥ": "ʜ̂", 89 | "ħ": "ħ", 90 | "ĩ": "ɪ̃", 91 | "ī": "ɪ̄", 92 | "ĭ": "ɪ̆", 93 | "į": "ɪ̨", 94 | "ı": "ı", 95 | "ij": "ij", 96 | "ĵ": "ᴊ̂", 97 | "ķ": "ᴋ̧", 98 | "ĸ": "ĸ", 99 | "ĺ": "ʟ́", 100 | "ļ": "ʟ̧", 101 | "ľ": "ʟ̌", 102 | "ŀ": "ŀ", 103 | "ł": "ł", 104 | "ń": "ɴ́", 105 | "ņ": "ɴ̧", 106 | "ň": "ɴ̌", 107 | "ʼn": "ʼn", 108 | "ŋ": "ŋ", 109 | "ō": "ᴏ̄", 110 | "ŏ": "ᴏ̆", 111 | "ő": "ᴏ̋", 112 | "œ": "œ", 113 | "ŕ": "ʀ́", 114 | "ŗ": "ʀ̧", 115 | "ř": "ʀ̌", 116 | "ś": "ś", 117 | "ŝ": "ŝ", 118 | "ş": "ş", 119 | "š": "š", 120 | "ţ": "ᴛ̧", 121 | "ť": "ᴛ̌", 122 | "ŧ": "ŧ", 123 | "ũ": "ᴜ̃", 124 | "ū": "ᴜ̄", 125 | "ŭ": "ᴜ̆", 126 | "ů": "ᴜ̊", 127 | "ű": "ᴜ̋", 128 | "ų": "ᴜ̨", 129 | "ŵ": "ᴡ̂", 130 | "ŷ": "ʏ̂", 131 | "ź": "ᴢ́", 132 | "ż": "ᴢ̇", 133 | "ž": "ᴢ̌", 134 | ] 135 | 136 | let uppercase = 137 | "ABCDEFGHIJKLMNOPQRSTUVWXYZΜSSÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞŸĀĂĄĆĈĊČĎĐĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIIJĴĶĸĹĻĽĿŁŃŅŇʼNŊŌŎŐŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ" 138 | 139 | public func process(_ input: String) -> String { 140 | guard !input.isEmpty else { return "" } 141 | var converted = "" 142 | for char in input { 143 | if !uppercase.contains(char), let caps = capsMap[char.lowercased()] { 144 | converted.append(caps) 145 | } else { 146 | converted.append(char) 147 | } 148 | } 149 | return converted 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Fun/Spongebob.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class Spongebob: Format { 4 | public var name: String = "Mocking Spongebob" 5 | public var description: String = 6 | "Turn your words into something sarcastic Spongebob would say" 7 | public var id: String = "mockingSpongebob" 8 | 9 | public init() {} 10 | 11 | public func process(_ input: String) -> String { 12 | guard !input.isEmpty else { return "" } 13 | let text = input.lowercased() 14 | var spOnGEbOb = "" 15 | 16 | let chanceOfCapital: UInt32 = 5 // Between 1 and 10 17 | for character in text { 18 | var charString = character.description 19 | let random = arc4random_uniform(10) 20 | if random < chanceOfCapital { 21 | charString = charString.capitalized 22 | } 23 | spOnGEbOb += charString 24 | } 25 | 26 | return spOnGEbOb 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Fun/UpsideDown.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class UpsideDown: Format { 4 | public var name: String = "Upside Down" 5 | public var description: String = "Flip all characters." 6 | public var id: String = "upsideDown" 7 | 8 | public init() {} 9 | 10 | let flippedMap: [String: String] = [ 11 | "a": "ɐ", 12 | "b": "q", 13 | "c": "ɔ", 14 | "d": "p", 15 | "e": "ǝ", 16 | "f": "ɟ", 17 | "g": "ƃ", 18 | "h": "ɥ", 19 | "i": "ᴉ", 20 | "j": "ɾ", 21 | "k": "ʞ", 22 | "l": "l", 23 | "m": "ɯ", 24 | "n": "u", 25 | "o": "o", 26 | "p": "d", 27 | "q": "b", 28 | "r": "ɹ", 29 | "s": "s", 30 | "t": "ʇ", 31 | "u": "n", 32 | "v": "ʌ", 33 | "w": "ʍ", 34 | "x": "x", 35 | "y": "ʎ", 36 | "z": "z", 37 | "µ": "µ", 38 | "ß": "ß", 39 | "à": "à", 40 | "á": "á", 41 | "â": "â", 42 | "ã": "ã", 43 | "ä": "ä", 44 | "å": "å", 45 | "æ": "æ", 46 | "ç": "ç", 47 | "è": "è", 48 | "é": "é", 49 | "ê": "ê", 50 | "ë": "ë", 51 | "ì": "ì", 52 | "í": "í", 53 | "î": "î", 54 | "ï": "ï", 55 | "ð": "ð", 56 | "ñ": "ñ", 57 | "ò": "ò", 58 | "ó": "ó", 59 | "ô": "ô", 60 | "õ": "õ", 61 | "ö": "ö", 62 | "ø": "ø", 63 | "ù": "ù", 64 | "ú": "ú", 65 | "û": "û", 66 | "ü": "ü", 67 | "ý": "ý", 68 | "þ": "þ", 69 | "ÿ": "ÿ", 70 | "ā": "ā", 71 | "ă": "ă", 72 | "ą": "ą", 73 | "ć": "ć", 74 | "ĉ": "ĉ", 75 | "ċ": "ċ", 76 | "č": "č", 77 | "ď": "ď", 78 | "đ": "đ", 79 | "ē": "ē", 80 | "ĕ": "ĕ", 81 | "ė": "ė", 82 | "ę": "ę", 83 | "ě": "ě", 84 | "ĝ": "ĝ", 85 | "ğ": "ğ", 86 | "ġ": "ġ", 87 | "ģ": "ģ", 88 | "ĥ": "ĥ", 89 | "ħ": "ħ", 90 | "ĩ": "ĩ", 91 | "ī": "ī", 92 | "ĭ": "ĭ", 93 | "į": "į", 94 | "ı": "ı", 95 | "ij": "ij", 96 | "ĵ": "ĵ", 97 | "ķ": "ķ", 98 | "ĸ": "ĸ", 99 | "ĺ": "ĺ", 100 | "ļ": "ļ", 101 | "ľ": "ľ", 102 | "ŀ": "ŀ", 103 | "ł": "ł", 104 | "ń": "ń", 105 | "ņ": "ņ", 106 | "ň": "ň", 107 | "ʼn": "ʼn", 108 | "ŋ": "ŋ", 109 | "ō": "ō", 110 | "ŏ": "ŏ", 111 | "ő": "ő", 112 | "œ": "œ", 113 | "ŕ": "ŕ", 114 | "ŗ": "ŗ", 115 | "ř": "ř", 116 | "ś": "ś", 117 | "ŝ": "ŝ", 118 | "ş": "ş", 119 | "š": "š", 120 | "ţ": "ţ", 121 | "ť": "ť", 122 | "ŧ": "ŧ", 123 | "ũ": "ũ", 124 | "ū": "ū", 125 | "ŭ": "ŭ", 126 | "ů": "ů", 127 | "ű": "ű", 128 | "ų": "ų", 129 | "ŵ": "ŵ", 130 | "ŷ": "ŷ", 131 | "ź": "ź", 132 | "ż": "ż", 133 | "ž": "ž", 134 | "1": "Ɩ", 135 | "2": "ᄅ", 136 | "3": "Ɛ", 137 | "4": "ㄣ", 138 | "5": "ϛ", 139 | "6": "9", 140 | "7": "ㄥ", 141 | "8": "8", 142 | "9": "6", 143 | "0": "0", 144 | "@": "@", 145 | "!": "¡", 146 | "£": "£", 147 | "$": "$", 148 | "%": "%", 149 | "^": "^", 150 | "&": "⅋", 151 | "*": "*", 152 | "(": ")", 153 | ")": "(", 154 | "{": "}", 155 | "}": "{", 156 | "[": "]", 157 | "]": "[", 158 | "_": "‾", 159 | "-": "-", 160 | "+": "+", 161 | "=": "=", 162 | ":": ":", 163 | ";": ";", 164 | "”": "”", 165 | "’": "’", 166 | "|": "|", 167 | "\\": "\\", 168 | "?": "¿", 169 | "/": "/", 170 | ">": "<", 171 | ".": "˙", 172 | "<": ">", 173 | ",": "'", 174 | "~": "~", 175 | "`": ",", 176 | "±": "±", 177 | "§": "§", 178 | "A": "∀", 179 | "B": "q", 180 | "C": "Ɔ", 181 | "D": "p", 182 | "E": "Ǝ", 183 | "F": "Ⅎ", 184 | "G": "פ", 185 | "H": "H", 186 | "I": "I", 187 | "J": "ſ", 188 | "K": "ʞ", 189 | "L": "˥", 190 | "M": "W", 191 | "N": "N", 192 | "O": "O", 193 | "P": "Ԁ", 194 | "Q": "Q", 195 | "R": "ɹ", 196 | "S": "S", 197 | "T": "┴", 198 | "U": "∩", 199 | "V": "Λ", 200 | "W": "M", 201 | "X": "X", 202 | "Y": "⅄", 203 | "Z": "Z", 204 | "Μ": "Μ", 205 | "À": "À", 206 | "Á": "Á", 207 | "Â": "Â", 208 | "Ã": "Ã", 209 | "Ä": "Ä", 210 | "Å": "Å", 211 | "Æ": "Æ", 212 | "Ç": "Ç", 213 | "È": "È", 214 | "É": "É", 215 | "Ê": "Ê", 216 | "Ë": "Ë", 217 | "Ì": "Ì", 218 | "Í": "Í", 219 | "Î": "Î", 220 | "Ï": "Ï", 221 | "Ð": "Ð", 222 | "Ñ": "Ñ", 223 | "Ò": "Ò", 224 | "Ó": "Ó", 225 | "Ô": "Ô", 226 | "Õ": "Õ", 227 | "Ö": "Ö", 228 | "Ø": "Ø", 229 | "Ù": "Ù", 230 | "Ú": "Ú", 231 | "Û": "Û", 232 | "Ü": "Ü", 233 | "Ý": "Ý", 234 | "Þ": "Þ", 235 | "Ÿ": "Ÿ", 236 | "Ā": "Ā", 237 | "Ă": "Ă", 238 | "Ą": "Ą", 239 | "Ć": "Ć", 240 | "Ĉ": "Ĉ", 241 | "Ċ": "Ċ", 242 | "Č": "Č", 243 | "Ď": "Ď", 244 | "Đ": "Đ", 245 | "Ē": "Ē", 246 | "Ĕ": "Ĕ", 247 | "Ė": "Ė", 248 | "Ę": "Ę", 249 | "Ě": "Ě", 250 | "Ĝ": "Ĝ", 251 | "Ğ": "Ğ", 252 | "Ġ": "Ġ", 253 | "Ģ": "Ģ", 254 | "Ĥ": "Ĥ", 255 | "Ħ": "Ħ", 256 | "Ĩ": "Ĩ", 257 | "Ī": "Ī", 258 | "Ĭ": "Ĭ", 259 | "Į": "Į", 260 | "IJ": "IJ", 261 | "Ĵ": "Ĵ", 262 | "Ķ": "Ķ", 263 | "Ĺ": "Ĺ", 264 | "Ļ": "Ļ", 265 | "Ľ": "Ľ", 266 | "Ŀ": "Ŀ", 267 | "Ł": "Ł", 268 | "Ń": "Ń", 269 | "Ņ": "Ņ", 270 | "Ň": "Ň", 271 | "ʼ": "ʼ", 272 | "Ŋ": "Ŋ", 273 | "Ō": "Ō", 274 | "Ŏ": "Ŏ", 275 | "Ő": "Ő", 276 | "Œ": "Œ", 277 | "Ŕ": "Ŕ", 278 | "Ŗ": "Ŗ", 279 | "Ř": "Ř", 280 | "Ś": "Ś", 281 | "Ŝ": "Ŝ", 282 | "Ş": "Ş", 283 | "Š": "Š", 284 | "Ţ": "Ţ", 285 | "Ť": "Ť", 286 | "Ŧ": "Ŧ", 287 | "Ũ": "Ũ", 288 | "Ū": "Ū", 289 | "Ŭ": "Ŭ", 290 | "Ů": "Ů", 291 | "Ű": "Ű", 292 | "Ų": "Ų", 293 | "Ŵ": "Ŵ", 294 | "Ŷ": "Ŷ", 295 | "Ź": "Ź", 296 | "Ż": "Ż", 297 | "Ž": "Ž", 298 | ] 299 | 300 | public func process(_ input: String) -> String { 301 | guard !input.isEmpty else { return "" } 302 | var converted = "" 303 | for char in input { 304 | if let caps = flippedMap["\(char)"] { 305 | converted.append(caps) 306 | } else { 307 | converted.append(char) 308 | } 309 | } 310 | return converted 311 | } 312 | } 313 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Programming/CamelCase.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class CamelCase: Format { 4 | public var name: String = "Camel Case" 5 | public var description: String = "Capitalise the first letter of each word, except the first, and join together." 6 | public var id: String = "camelCase" 7 | 8 | private let capitals = [ 9 | "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", 10 | "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", 11 | "W", "X", "Y", "Z", 12 | ] 13 | 14 | public init() {} 15 | 16 | public func process(_ input: String) -> String { 17 | guard !input.isEmpty else { return "" } 18 | 19 | var camel = input 20 | 21 | if !camel.contains(" ") { 22 | for capital in capitals { 23 | camel = camel.replacingOccurrences(of: capital, with: " \(capital)") 24 | } 25 | } 26 | 27 | camel = camel 28 | .replacingOccurrences(of: "_", with: " ") 29 | .replacingOccurrences(of: "-", with: " ") 30 | 31 | let capitaliseWords = CapitaliseWords() 32 | 33 | camel = camel 34 | .split(separator: " ") 35 | .compactMap { word in 36 | capitaliseWords.process(String(word)) 37 | } 38 | .joined(separator: "") 39 | 40 | guard !camel.isEmpty else { return "" } 41 | 42 | let firstChar = camel[camel.startIndex] 43 | camel = camel.replacingCharacters(in: camel.startIndex ... camel.startIndex, 44 | with: String(firstChar).lowercased()) 45 | 46 | return camel 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Programming/KebabCase.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class KebabCase: Format { 4 | public var name: String = "Kebab Case" 5 | public var description: String = "Make all letters lowercase and join with a hyphen." 6 | public var id: String = "kebabCase" 7 | 8 | private let capitals = [ 9 | "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", 10 | "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", 11 | "W", "X", "Y", "Z", 12 | ] 13 | 14 | public init() {} 15 | 16 | public func process(_ input: String) -> String { 17 | guard !input.isEmpty else { return "" } 18 | 19 | var kebab = input 20 | 21 | if !kebab.contains(" ") { 22 | for capital in capitals { 23 | kebab = kebab.replacingOccurrences(of: capital, with: " \(capital)") 24 | } 25 | } 26 | 27 | return kebab 28 | .lowercased() 29 | .replacingOccurrences(of: "_", with: " ") 30 | .split(separator: " ") 31 | .compactMap { word in 32 | word 33 | } 34 | .joined(separator: "-") 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Programming/PascalCase.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class PascalCase: Format { 4 | public var name: String = "Pascal Case" 5 | public var description: String = "Capitalise the first letter of each word, and join together." 6 | public var id: String = "pascalCase" 7 | 8 | private let capitals = [ 9 | "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", 10 | "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", 11 | "W", "X", "Y", "Z", 12 | ] 13 | 14 | public init() {} 15 | 16 | public func process(_ input: String) -> String { 17 | guard !input.isEmpty else { return "" } 18 | 19 | var pascal = input 20 | 21 | if !pascal.contains(" ") { 22 | for capital in capitals { 23 | pascal = pascal.replacingOccurrences(of: capital, with: " \(capital)") 24 | } 25 | } 26 | 27 | let capitaliseWords = CapitaliseWords() 28 | 29 | return pascal 30 | .replacingOccurrences(of: "_", with: " ") 31 | .replacingOccurrences(of: "-", with: " ") 32 | .split(separator: " ") 33 | .compactMap { word in 34 | capitaliseWords.process(String(word)) 35 | } 36 | .joined(separator: "") 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Programming/SnakeCase.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class SnakeCase: Format { 4 | public var name: String = "Snake Case" 5 | public var description: String = "Make all letters lowercase and join with an underscore." 6 | public var id: String = "snakeCase" 7 | 8 | private let capitals = [ 9 | "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", 10 | "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", 11 | "W", "X", "Y", "Z", 12 | ] 13 | 14 | public init() {} 15 | 16 | public func process(_ input: String) -> String { 17 | guard !input.isEmpty else { return "" } 18 | 19 | var snake = input 20 | 21 | if !snake.contains(" ") { 22 | for capital in capitals { 23 | snake = snake.replacingOccurrences(of: capital, with: " \(capital)") 24 | } 25 | } 26 | 27 | return snake 28 | .lowercased() 29 | .replacingOccurrences(of: "_", with: " ") 30 | .replacingOccurrences(of: "-", with: " ") 31 | .split(separator: " ") 32 | .compactMap { word in 33 | word 34 | } 35 | .joined(separator: "_") 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Simple/Capitalise.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class Capitalise: Format { 4 | public var name: String = "Capitalise" 5 | public var description: String = "Capitalise the first letter." 6 | public var id: String = "capitalise" 7 | 8 | public init() {} 9 | 10 | public func process(_ input: String) -> String { 11 | guard !input.isEmpty else { return "" } 12 | guard !input.isEmpty else { return input } 13 | var capitalise = input 14 | 15 | // first character 16 | let firstCharIndexRange = capitalise.startIndex ... capitalise.startIndex 17 | let firstChar = capitalise[firstCharIndexRange] 18 | capitalise = capitalise.replacingCharacters( 19 | in: firstCharIndexRange, with: firstChar.uppercased() 20 | ) 21 | 22 | // find periods 23 | var periods: [String.Index] = [] 24 | for i in 0 ..< capitalise.count { 25 | let index = capitalise.index(capitalise.startIndex, offsetBy: i) 26 | if capitalise[index] == "." { 27 | periods.append(index) 28 | } 29 | } 30 | 31 | // capitalise after period 32 | for periodIndex in periods { 33 | if periodIndex == capitalise.endIndex { 34 | break 35 | } 36 | 37 | for i in 0 ..< 3 { 38 | let offsetIndex = capitalise.index(periodIndex, offsetBy: i) 39 | 40 | if offsetIndex == capitalise.endIndex { 41 | break 42 | } else { 43 | if capitalise[offsetIndex] != " " { 44 | capitalise = capitalise.replacingCharacters( 45 | in: offsetIndex ... offsetIndex, 46 | with: "\(capitalise[offsetIndex])".uppercased() 47 | ) 48 | } 49 | } 50 | } 51 | } 52 | 53 | return capitalise 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Simple/CapitaliseWords.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class CapitaliseWords: Format { 4 | public var name: String = "Capitalise Words" 5 | public var description: String = "Capitalise all words." 6 | public var id: String = "capitaliseWords" 7 | 8 | public init() {} 9 | 10 | public func process(_ input: String) -> String { 11 | guard !input.isEmpty else { return "" } 12 | let lowercase = input.lowercased() 13 | 14 | let capitalised = capitaliseAfterSeparator(lowercase) 15 | 16 | return capitalised 17 | } 18 | 19 | private func capitaliseAfterSeparator(_ input: String) -> String { 20 | let inputString = input.capitalized 21 | var splitBuffer = "" 22 | var splitOutput = "" 23 | 24 | for character in inputString { 25 | if character == "-" || character == ":" { 26 | splitBuffer = splitBuffer.capitalized 27 | splitOutput += splitBuffer 28 | splitBuffer = "" 29 | splitBuffer += character.description 30 | } else { 31 | splitBuffer += character.description 32 | } 33 | } 34 | 35 | splitOutput += splitBuffer.capitalized 36 | 37 | return splitOutput 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Simple/Lowercase.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class Lowercase: Format { 4 | public var name: String = "Lowercase" 5 | public var description: String = "Make all characters lowercase." 6 | public var id: String = "lowercase" 7 | 8 | public init() {} 9 | 10 | public func process(_ input: String) -> String { 11 | guard !input.isEmpty else { return "" } 12 | return input.lowercased() 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Simple/Reversed.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class Reversed: Format { 4 | public var name: String = "Reversed" 5 | public var description: String = "Reverse all characters." 6 | public var id: String = "reversed" 7 | 8 | public init() {} 9 | 10 | public func process(_ input: String) -> String { 11 | guard !input.isEmpty else { return "" } 12 | return String(input.reversed()) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Simple/Sentence.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class Sentence: Format { 4 | public var name: String = "Sentence Case" 5 | public var description: String = "Capitalise text as a sentence." 6 | public var id: String = "sentence" 7 | 8 | public init() {} 9 | 10 | let options: NSLinguisticTagger.Options = [] 11 | let schemes = NSLinguisticTagger.availableTagSchemes( 12 | forLanguage: Locale.current.language.languageCode?.identifier ?? "en") 13 | 14 | public func process(_ input: String) -> String { 15 | guard !input.isEmpty else { return "" } 16 | guard !input.isEmpty else { return input } 17 | var capitalise = input.lowercased() 18 | 19 | // first character 20 | let firstCharIndexRange = capitalise.startIndex ... capitalise.startIndex 21 | let firstChar = capitalise[firstCharIndexRange] 22 | capitalise = capitalise.replacingCharacters( 23 | in: firstCharIndexRange, with: firstChar.uppercased() 24 | ) 25 | 26 | // find terminators 27 | let terminators: [Character] = [".", "?", "!"] 28 | 29 | for terminator in terminators { 30 | var periods: [String.Index] = [] 31 | for i in 0 ..< capitalise.count { 32 | let index = capitalise.index(capitalise.startIndex, offsetBy: i) 33 | if capitalise[index] == terminator { 34 | periods.append(index) 35 | } 36 | } 37 | 38 | // capitalise after period 39 | for periodIndex in periods { 40 | if periodIndex == capitalise.endIndex { 41 | break 42 | } 43 | 44 | for i in 0 ..< 3 { 45 | let offsetIndex = capitalise.index(periodIndex, offsetBy: i) 46 | 47 | if offsetIndex == capitalise.endIndex { 48 | break 49 | } else { 50 | if capitalise[offsetIndex] != " " { 51 | capitalise = capitalise.replacingCharacters( 52 | in: offsetIndex ... offsetIndex, 53 | with: "\(capitalise[offsetIndex])".uppercased() 54 | ) 55 | } 56 | } 57 | } 58 | } 59 | } 60 | 61 | let tagger = NSLinguisticTagger(tagSchemes: schemes, options: Int(options.rawValue)) 62 | 63 | var words: [String] = [] 64 | 65 | tagger.string = capitalise 66 | tagger.enumerateTags( 67 | in: NSRange(location: 0, length: NSString(string: capitalise).length), 68 | scheme: NSLinguisticTagScheme.nameType, options: options 69 | ) { tag, tokenRange, _, _ in 70 | let token = NSString(string: capitalise).substring(with: tokenRange) 71 | 72 | if tag?.rawValue == "PlaceName" || tag?.rawValue == "PersonalName" 73 | || tag?.rawValue == "OrganizationalName" 74 | { 75 | words.append(token.capitalized) 76 | } else { 77 | words.append(token) 78 | } 79 | } 80 | 81 | return words.joined() 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Simple/Uppercase.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class Uppercase: Format { 4 | public var name: String = "Uppercase" 5 | public var description: String = "Make all characters uppercase." 6 | public var id: String = "uppercase" 7 | 8 | public init() {} 9 | 10 | public func process(_ input: String) -> String { 11 | guard !input.isEmpty else { return "" } 12 | return input.uppercased() 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Title/AMATitleCase.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class AMATitleCase: BaseTitleCase, Format { 4 | public var name: String = "AMA Title" 5 | public var description: String = "Format text as a title using AMA style." 6 | public var id: String = "amaTitle" 7 | 8 | public init() { 9 | let configuration = TitleCaseConfiguration(shouldIgnoreLinguisticTagger: false, 10 | shouldCapitaliseFirstWord: true, 11 | shouldCapitaliseLastWord: false, 12 | shouldCapitaliseAfterEndPunctionation: true, 13 | wordsToAlwaysBeCapitalised: [], 14 | wordsToNeverBeCapitalised: [ 15 | "and", 16 | "but", 17 | "for", 18 | "nor", 19 | "or", 20 | "so", 21 | "yet", 22 | ], 23 | capitaliseAllPrepositionsLongerThanLength: 3, 24 | capitaliseAllConjunctionLongerThanLength: 3, 25 | capitaliseAllAdverbsLongerThanLength: nil, 26 | capitaliseAllAdjectivesLongerThanLength: 0, 27 | capitaliseAllVerbsLongerThanLength: nil, 28 | shouldCapitalisePronouns: true, 29 | shouldCapitaliseAllWordsAboveLength: nil) 30 | super.init(configuration: configuration) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Title/APATitleCase.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class APATitleCase: BaseTitleCase, Format { 4 | public var name: String = "APA Title" 5 | public var description: String = "Format text as a title using APA style." 6 | public var id: String = "apaTitle" 7 | 8 | public init() { 9 | let configuration = TitleCaseConfiguration(shouldIgnoreLinguisticTagger: false, 10 | shouldCapitaliseFirstWord: true, 11 | shouldCapitaliseLastWord: false, 12 | shouldCapitaliseAfterEndPunctionation: true, 13 | wordsToAlwaysBeCapitalised: [], 14 | wordsToNeverBeCapitalised: ["a", "an", "the"], 15 | capitaliseAllPrepositionsLongerThanLength: 3, 16 | capitaliseAllConjunctionLongerThanLength: 3, 17 | capitaliseAllAdverbsLongerThanLength: 0, 18 | capitaliseAllAdjectivesLongerThanLength: 0, 19 | capitaliseAllVerbsLongerThanLength: 0, 20 | shouldCapitalisePronouns: true, 21 | shouldCapitaliseAllWordsAboveLength: 3) 22 | super.init(configuration: configuration) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Title/APTitleCase.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class APTitleCase: BaseTitleCase, Format { 4 | public var name: String = "AP Title" 5 | public var description: String = "Format text as a title using AP style." 6 | public var id: String = "apTitle" 7 | 8 | public init() { 9 | let configuration = TitleCaseConfiguration(shouldIgnoreLinguisticTagger: false, 10 | shouldCapitaliseFirstWord: true, 11 | shouldCapitaliseLastWord: true, 12 | shouldCapitaliseAfterEndPunctionation: true, 13 | wordsToAlwaysBeCapitalised: [], 14 | wordsToNeverBeCapitalised: ["a", "an", "the"], 15 | capitaliseAllPrepositionsLongerThanLength: 3, 16 | capitaliseAllConjunctionLongerThanLength: 3, 17 | capitaliseAllAdverbsLongerThanLength: nil, 18 | capitaliseAllAdjectivesLongerThanLength: 0, 19 | capitaliseAllVerbsLongerThanLength: nil, 20 | shouldCapitalisePronouns: true, 21 | shouldCapitaliseAllWordsAboveLength: 3) 22 | super.init(configuration: configuration) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Title/BaseTitleCase.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public class BaseTitleCase { 4 | var configuration: TitleCaseConfiguration 5 | 6 | let options: NSLinguisticTagger.Options = [.omitWhitespace, .joinNames] 7 | let schemes = NSLinguisticTagger.availableTagSchemes(forLanguage: Locale.current.language.languageCode?.identifier ?? "en") 8 | var tagger: NSLinguisticTagger 9 | 10 | var wordsToAlwaysBeCapitalised: [String] = [] 11 | var wordsToNeverBeCapitalised: [String] = [] 12 | var wordsToBeInitiallyLowercase: [String] = [] 13 | var previousWordEndsWithEndMark = false 14 | 15 | init(configuration: TitleCaseConfiguration) { 16 | self.configuration = configuration 17 | tagger = NSLinguisticTagger(tagSchemes: schemes, options: Int(options.rawValue)) 18 | } 19 | 20 | public func process(_ input: String) -> String { 21 | wordsToAlwaysBeCapitalised = [] 22 | wordsToNeverBeCapitalised = [] 23 | wordsToBeInitiallyLowercase = [] 24 | previousWordEndsWithEndMark = false 25 | 26 | guard !input.isEmpty else { return "" } 27 | var text = input.lowercased() 28 | 29 | let scanner = Scanner(string: text) 30 | scanner.caseSensitive = true 31 | 32 | // Do some word analysis and extract any words that need to be capitalised 33 | // based on the rules set in the configuration. 34 | wordsToAlwaysBeCapitalised = configuration.wordsToAlwaysBeCapitalised 35 | wordsToNeverBeCapitalised = configuration.wordsToNeverBeCapitalised 36 | 37 | tagger.string = text 38 | tagger.enumerateTags(in: NSRange(location: 0, length: NSString(string: text).length), 39 | scheme: NSLinguisticTagScheme.lexicalClass, options: options) 40 | { tag, tokenRange, _, _ in 41 | let token = NSString(string: text).substring(with: tokenRange) 42 | 43 | switch tag?.rawValue { 44 | case "Preposition": 45 | if let length = configuration.capitaliseAllPrepositionsLongerThanLength, 46 | token.count > length 47 | { 48 | wordsToAlwaysBeCapitalised.append(token.lowercased()) 49 | } else { 50 | wordsToNeverBeCapitalised.append(token.lowercased()) 51 | } 52 | case "Conjunction": 53 | if let length = configuration.capitaliseAllConjunctionLongerThanLength, 54 | token.count > length 55 | { 56 | wordsToAlwaysBeCapitalised.append(token.lowercased()) 57 | } else { 58 | wordsToNeverBeCapitalised.append(token.lowercased()) 59 | } 60 | case "Adverb": 61 | if let length = configuration.capitaliseAllAdverbsLongerThanLength, 62 | token.count > length 63 | { 64 | wordsToAlwaysBeCapitalised.append(token.lowercased()) 65 | } else { 66 | wordsToBeInitiallyLowercase.append(token.lowercased()) 67 | } 68 | case "Adjective": 69 | if let length = configuration.capitaliseAllAdjectivesLongerThanLength, 70 | token.count > length 71 | { 72 | wordsToAlwaysBeCapitalised.append(token.lowercased()) 73 | } else { 74 | wordsToBeInitiallyLowercase.append(token.lowercased()) 75 | } 76 | case "Noun": 77 | wordsToAlwaysBeCapitalised.append(token.lowercased()) 78 | case "Verb": 79 | if let length = configuration.capitaliseAllVerbsLongerThanLength, 80 | token.count > length 81 | { 82 | wordsToAlwaysBeCapitalised.append(token.lowercased()) 83 | } else { 84 | wordsToBeInitiallyLowercase.append(token.lowercased()) 85 | } 86 | case "Pronoun": 87 | if configuration.shouldCapitalisePronouns { 88 | wordsToAlwaysBeCapitalised.append(token.lowercased()) 89 | } else { 90 | wordsToBeInitiallyLowercase.append(token.lowercased()) 91 | } 92 | case "Particle": 93 | wordsToBeInitiallyLowercase.append(token.lowercased()) 94 | default: 95 | break 96 | } 97 | } 98 | 99 | // Trim leading/trailing whitespace 100 | text = text.trimmingCharacters(in: .whitespacesAndNewlines) 101 | 102 | var words: [String] = [] 103 | while !scanner.isAtEnd { 104 | // Get word by continuously scanning until we hit whitespace or end of line. 105 | let word = scanner.scanUpToCharacters(from: .whitespaces) ?? "" 106 | let formatted = formatWord(input: word, scanner: scanner) 107 | words.append(formatted) 108 | } 109 | 110 | for i in 0 ..< words.count { 111 | let word = words[i] 112 | 113 | if i == 0 { 114 | words[i] = word.capitalized 115 | } else if i == words.count - 1, configuration.shouldCapitaliseLastWord { 116 | words[i] = word.capitalized 117 | } 118 | } 119 | 120 | return words.joined(separator: " ") 121 | } 122 | 123 | private func formatWord(input: String, scanner: Scanner) -> String { 124 | var word = input 125 | 126 | // 1. Lowercase all words that have been detected by the initial analysis, but may 127 | // be overriden later by other rules. 128 | if wordsToBeInitiallyLowercase.contains(word.lowercased()) { 129 | word = word.lowercased() 130 | } 131 | 132 | // Split all words via a hyphen, and format them seperately. 133 | // Some formats may have quirks about hyphens, but those rules are outside of the 134 | // scope for this app. 135 | let separators: [Character] = ["-", "–", "—"] 136 | for separator in separators { 137 | let split = word.split(separator: separator) 138 | if split.count > 1 { 139 | var pieces: [String] = [] 140 | for splice in split { 141 | pieces.append(formatWord(input: String(splice), scanner: scanner)) 142 | } 143 | word = pieces.joined(separator: separator.lowercased()) 144 | } 145 | } 146 | 147 | // Capitalise last word (depending on configuration) 148 | if scanner.isAtEnd, configuration.shouldCapitaliseLastWord { 149 | word = word.capitalized 150 | } 151 | 152 | // Capitalise all words that have explicitly been marked to be so 153 | if wordsToAlwaysBeCapitalised.contains(word.lowercased()) { 154 | word = word.capitalized 155 | } 156 | 157 | // Lowercase all words that have explicitly been marked to be so 158 | if wordsToNeverBeCapitalised.contains(word.lowercased()) { 159 | word = word.lowercased() 160 | } 161 | 162 | // Capitalise all words after an end mark or a few extra punctuation marks 163 | if configuration.shouldCapitaliseAfterEndPunctionation, previousWordEndsWithEndMark { 164 | word = word.capitalized 165 | } 166 | 167 | // Now check the current word for the end marks 168 | previousWordEndsWithEndMark = false 169 | if let char = word.last { 170 | let charsToLookFor = ["!", "?", ".", ":"] 171 | previousWordEndsWithEndMark = charsToLookFor.contains(String(char)) 172 | } 173 | 174 | // Capitalise all words above a specific length 175 | if let length = configuration.shouldCapitaliseAllWordsAboveLength, word.count > length { 176 | word = word.capitalized 177 | } 178 | 179 | // This word has been formatted! 180 | return word 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Title/BluebookTitleCase.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class BluebookTitleCase: BaseTitleCase, Format { 4 | public var name: String = "Bluebook Title" 5 | public var description: String = "Format text as a title using Bluebook style." 6 | public var id: String = "bluebookTitle" 7 | 8 | public init() { 9 | let configuration = TitleCaseConfiguration(shouldIgnoreLinguisticTagger: false, 10 | shouldCapitaliseFirstWord: true, 11 | shouldCapitaliseLastWord: false, 12 | shouldCapitaliseAfterEndPunctionation: true, 13 | wordsToAlwaysBeCapitalised: [], 14 | wordsToNeverBeCapitalised: [ 15 | "and", 16 | "but", 17 | "for", 18 | "nor", 19 | "or", 20 | "so", 21 | "yet", 22 | ], 23 | capitaliseAllPrepositionsLongerThanLength: 4, 24 | capitaliseAllConjunctionLongerThanLength: 4, 25 | capitaliseAllAdverbsLongerThanLength: 4, 26 | capitaliseAllAdjectivesLongerThanLength: 4, 27 | capitaliseAllVerbsLongerThanLength: 4, 28 | shouldCapitalisePronouns: true, 29 | shouldCapitaliseAllWordsAboveLength: 4) 30 | super.init(configuration: configuration) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Title/CMOSTitleCase.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class CMOSTitleCase: BaseTitleCase, Format { 4 | public var name: String = "CMOSTitle" 5 | public var description: String = "Format text as a title using Chigaco Manual of Style." 6 | public var id: String = "cmosTitle" 7 | 8 | public init() { 9 | let configuration = TitleCaseConfiguration(shouldIgnoreLinguisticTagger: false, 10 | shouldCapitaliseFirstWord: true, 11 | shouldCapitaliseLastWord: true, 12 | shouldCapitaliseAfterEndPunctionation: true, 13 | wordsToAlwaysBeCapitalised: [], 14 | wordsToNeverBeCapitalised: [ 15 | "a", 16 | "an", 17 | "the", 18 | "and", 19 | "but", 20 | "for", 21 | "or", 22 | "nor", 23 | "as", 24 | ], 25 | capitaliseAllPrepositionsLongerThanLength: 20, 26 | capitaliseAllConjunctionLongerThanLength: 20, 27 | capitaliseAllAdverbsLongerThanLength: 0, 28 | capitaliseAllAdjectivesLongerThanLength: 0, 29 | capitaliseAllVerbsLongerThanLength: 0, 30 | shouldCapitalisePronouns: true, 31 | shouldCapitaliseAllWordsAboveLength: nil) 32 | super.init(configuration: configuration) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Title/GuardianTitleCase.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class GuardianTitleCase: BaseTitleCase, Format { 4 | public var name: String = "Guardian Title" 5 | public var description: String = "Format text as a title using The Guardian style." 6 | public var id: String = "guardianTitle" 7 | 8 | public init() { 9 | let configuration = TitleCaseConfiguration(shouldIgnoreLinguisticTagger: true, 10 | shouldCapitaliseFirstWord: true, 11 | shouldCapitaliseLastWord: false, 12 | shouldCapitaliseAfterEndPunctionation: true, 13 | wordsToAlwaysBeCapitalised: [], 14 | wordsToNeverBeCapitalised: [ 15 | "a", 16 | "an", 17 | "and", 18 | "at", 19 | "for", 20 | "from", 21 | "in", 22 | "of", 23 | "on", 24 | "the", 25 | "to", 26 | ], 27 | capitaliseAllPrepositionsLongerThanLength: nil, 28 | capitaliseAllConjunctionLongerThanLength: nil, 29 | capitaliseAllAdverbsLongerThanLength: nil, 30 | capitaliseAllAdjectivesLongerThanLength: nil, 31 | capitaliseAllVerbsLongerThanLength: nil, 32 | shouldCapitalisePronouns: true, 33 | shouldCapitaliseAllWordsAboveLength: nil) 34 | super.init(configuration: configuration) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Title/MLATitleCase.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class MLATitleCase: BaseTitleCase, Format { 4 | public var name: String = "MLA Title" 5 | public var description: String = "Format text as a title using MLA style." 6 | public var id: String = "mlaTitle" 7 | 8 | public init() { 9 | let configuration = TitleCaseConfiguration(shouldIgnoreLinguisticTagger: false, 10 | shouldCapitaliseFirstWord: true, 11 | shouldCapitaliseLastWord: true, 12 | shouldCapitaliseAfterEndPunctionation: true, 13 | wordsToAlwaysBeCapitalised: [], 14 | wordsToNeverBeCapitalised: [ 15 | "a", 16 | "an", 17 | "the", 18 | "and", 19 | "but", 20 | "for", 21 | "nor", 22 | "or", 23 | "so", 24 | "yet", 25 | ], 26 | capitaliseAllPrepositionsLongerThanLength: nil, 27 | capitaliseAllConjunctionLongerThanLength: 0, 28 | capitaliseAllAdverbsLongerThanLength: 0, 29 | capitaliseAllAdjectivesLongerThanLength: 0, 30 | capitaliseAllVerbsLongerThanLength: 0, 31 | shouldCapitalisePronouns: true, 32 | shouldCapitaliseAllWordsAboveLength: nil) 33 | super.init(configuration: configuration) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Title/NYTTitleCase.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class NYTTitleCase: BaseTitleCase, Format { 4 | public var name: String = "NYT Title" 5 | public var description: String = "Format text as a title using New York Times style." 6 | public var id: String = "nytTitle" 7 | 8 | public init() { 9 | let configuration = TitleCaseConfiguration(shouldIgnoreLinguisticTagger: false, 10 | shouldCapitaliseFirstWord: true, 11 | shouldCapitaliseLastWord: false, 12 | shouldCapitaliseAfterEndPunctionation: true, 13 | wordsToAlwaysBeCapitalised: [ 14 | "no", 15 | "nor", 16 | "not", 17 | "off", 18 | "out", 19 | "so", 20 | "up", 21 | ], 22 | wordsToNeverBeCapitalised: [ 23 | "a", 24 | "and", 25 | "as", 26 | "at", 27 | "but", 28 | "by", 29 | "en", 30 | "for", 31 | "if", 32 | "in", 33 | "of", 34 | "on", 35 | "or", 36 | "the", 37 | "to", 38 | "v.", 39 | "vs.", 40 | "via", 41 | ], 42 | capitaliseAllPrepositionsLongerThanLength: 4, 43 | capitaliseAllConjunctionLongerThanLength: 0, 44 | capitaliseAllAdverbsLongerThanLength: 0, 45 | capitaliseAllAdjectivesLongerThanLength: 0, 46 | capitaliseAllVerbsLongerThanLength: 0, 47 | shouldCapitalisePronouns: true, 48 | shouldCapitaliseAllWordsAboveLength: 3) 49 | super.init(configuration: configuration) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Title/TitleCaseConfiguration.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct TitleCaseConfiguration { 4 | let shouldIgnoreLinguisticTagger: Bool 5 | let shouldCapitaliseFirstWord: Bool 6 | let shouldCapitaliseLastWord: Bool 7 | let shouldCapitaliseAfterEndPunctionation: Bool 8 | var wordsToAlwaysBeCapitalised: [String] 9 | let wordsToNeverBeCapitalised: [String] 10 | let capitaliseAllPrepositionsLongerThanLength: Int? 11 | let capitaliseAllConjunctionLongerThanLength: Int? 12 | let capitaliseAllAdverbsLongerThanLength: Int? 13 | let capitaliseAllAdjectivesLongerThanLength: Int? 14 | let capitaliseAllVerbsLongerThanLength: Int? 15 | let shouldCapitalisePronouns: Bool 16 | let shouldCapitaliseAllWordsAboveLength: Int? 17 | } 18 | -------------------------------------------------------------------------------- /Sources/TextCaseKit/Formats/Title/WikipediaTitleCase.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class WikipediaTitleCase: BaseTitleCase, Format { 4 | public var name: String = "Wikipedia Title" 5 | public var description: String = "Format text as a title using Wikipedia's style." 6 | public var id: String = "wikipediaTitle" 7 | 8 | public init() { 9 | let configuration = TitleCaseConfiguration(shouldIgnoreLinguisticTagger: false, 10 | shouldCapitaliseFirstWord: true, 11 | shouldCapitaliseLastWord: true, 12 | shouldCapitaliseAfterEndPunctionation: true, 13 | wordsToAlwaysBeCapitalised: [], 14 | wordsToNeverBeCapitalised: [ 15 | "and", 16 | "but", 17 | "for", 18 | "nor", 19 | "or", 20 | "so", 21 | "yet", 22 | "a", 23 | "an", 24 | "the", 25 | "as", 26 | "at", 27 | "by", 28 | "in", 29 | "of", 30 | "off", 31 | "on", 32 | "per", 33 | "to", 34 | "up", 35 | "via", 36 | ], 37 | capitaliseAllPrepositionsLongerThanLength: nil, 38 | capitaliseAllConjunctionLongerThanLength: nil, 39 | capitaliseAllAdverbsLongerThanLength: 0, 40 | capitaliseAllAdjectivesLongerThanLength: 0, 41 | capitaliseAllVerbsLongerThanLength: 0, 42 | shouldCapitalisePronouns: true, 43 | shouldCapitaliseAllWordsAboveLength: 3) 44 | super.init(configuration: configuration) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Tests/TextCaseKitTests/Base64FormatTests.swift: -------------------------------------------------------------------------------- 1 | import TextCaseKit 2 | import XCTest 3 | 4 | final class Base64FormatTests: FormatTestBase { 5 | func testEncodeBase64() throws { 6 | let inputs = ["Hello, World.", "3.14", "https://chrishannah.me"] 7 | let outputs: [String] = performFormatterTest(format: EncodeBase64(), inputs: inputs) 8 | XCTAssertEqual(outputs[0], "SGVsbG8sIFdvcmxkLg==") 9 | XCTAssertEqual(outputs[1], "My4xNA==") 10 | XCTAssertEqual(outputs[2], "aHR0cHM6Ly9jaHJpc2hhbm5haC5tZQ==") 11 | 12 | measure { 13 | for _ in 1 ... 10 { 14 | _ = performFormatterTest(format: EncodeBase64()) 15 | } 16 | } 17 | } 18 | 19 | func testDecodeBase64() throws { 20 | let inputs = ["SGVsbG8sIFdvcmxkLg==", "My4xNA==", "aHR0cHM6Ly9jaHJpc2hhbm5haC5tZQ=="] 21 | let outputs: [String] = performFormatterTest(format: DecodeBase64(), inputs: inputs) 22 | XCTAssertEqual(outputs[0], "Hello, World.") 23 | XCTAssertEqual(outputs[1], "3.14") 24 | XCTAssertEqual(outputs[2], "https://chrishannah.me") 25 | 26 | measure { 27 | for _ in 1 ... 10 { 28 | _ = performFormatterTest(format: DecodeBase64()) 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Tests/TextCaseKitTests/CleaningFormatTests.swift: -------------------------------------------------------------------------------- 1 | import TextCaseKit 2 | import XCTest 3 | 4 | class CleaningFormatTests: FormatTestBase { 5 | func testStripHTML() throws { 6 | let inputs = ["text", "

", "

hello

"] 7 | let outputs: [String] = performFormatterTest(format: StripHTML(), inputs: inputs) 8 | XCTAssertEqual(outputs[0], "text") 9 | XCTAssertEqual(outputs[1], "") 10 | XCTAssertEqual(outputs[2], "hello") 11 | 12 | measure { 13 | for _ in 1 ... 10 { 14 | _ = performFormatterTest(format: StripHTML()) 15 | } 16 | } 17 | } 18 | 19 | func testStripWhitespace() throws { 20 | let inputs = [" ab c ", "\nhello ", "\n\nHello World\n "] 21 | let outputs: [String] = performFormatterTest(format: StripWhitespace(), inputs: inputs) 22 | XCTAssertEqual(outputs[0], "abc") 23 | XCTAssertEqual(outputs[1], "hello") 24 | XCTAssertEqual(outputs[2], "HelloWorld") 25 | 26 | measure { 27 | for _ in 1 ... 10 { 28 | _ = performFormatterTest(format: StripWhitespace()) 29 | } 30 | } 31 | } 32 | 33 | func testTrimWhitespace() throws { 34 | let inputs = [" ab c ", "\nhello ", "\n\nHello World\n "] 35 | let outputs: [String] = performFormatterTest(format: TrimWhitespace(), inputs: inputs) 36 | XCTAssertEqual(outputs[0], "ab c") 37 | XCTAssertEqual(outputs[1], "hello") 38 | XCTAssertEqual(outputs[2], "Hello World") 39 | 40 | measure { 41 | for _ in 1 ... 10 { 42 | _ = performFormatterTest(format: TrimWhitespace()) 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Tests/TextCaseKitTests/CountsFormatTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import XCTest 4 | import TextCaseKit 5 | 6 | final class CountsFormatTests: FormatTestBase { 7 | 8 | func testCountCharacters() throws { 9 | let outputs: [String] = performFormatterTest(format: CountCharacters()) 10 | XCTAssertEqual(outputs[0], "12") 11 | XCTAssertEqual(outputs[1], "6") 12 | XCTAssertEqual(outputs[2], "29") 13 | XCTAssertEqual(outputs[3], "4") 14 | XCTAssertEqual(outputs[4], "3") 15 | 16 | measure { 17 | for _ in 1 ... 10 { 18 | _ = performFormatterTest(format: CountCharacters()) 19 | } 20 | } 21 | } 22 | 23 | func testCountCharactersExcWhitespace() throws { 24 | let outputs: [String] = performFormatterTest(format: CountCharactersExcWhitespace()) 25 | XCTAssertEqual(outputs[0], "11") 26 | XCTAssertEqual(outputs[1], "6") 27 | XCTAssertEqual(outputs[2], "26") 28 | XCTAssertEqual(outputs[3], "4") 29 | XCTAssertEqual(outputs[4], "3") 30 | 31 | measure { 32 | for _ in 1 ... 10 { 33 | _ = performFormatterTest(format: CountCharactersExcWhitespace()) 34 | } 35 | } 36 | } 37 | 38 | func testCountWords() throws { 39 | let outputs: [String] = performFormatterTest(format: CountWords()) 40 | XCTAssertEqual(outputs[0], "2") 41 | XCTAssertEqual(outputs[1], "1") 42 | XCTAssertEqual(outputs[2], "4") 43 | XCTAssertEqual(outputs[3], "1") 44 | XCTAssertEqual(outputs[4], "1") 45 | 46 | measure { 47 | for _ in 1 ... 10 { 48 | _ = performFormatterTest(format: CountWords()) 49 | } 50 | } 51 | } 52 | 53 | func testCountLines() throws { 54 | let inputs = ["123", "", """ 55 | 56 | """, 57 | """ 58 | Hello 59 | World 60 | 61 | . 62 | """] 63 | let outputs: [String] = performFormatterTest(format: CountLines(), inputs: inputs) 64 | XCTAssertEqual(outputs[0], "1") 65 | XCTAssertEqual(outputs[1], "0") 66 | XCTAssertEqual(outputs[2], "0") 67 | XCTAssertEqual(outputs[3], "4") 68 | 69 | measure { 70 | for _ in 1 ... 10 { 71 | _ = performFormatterTest(format: CountLines()) 72 | } 73 | } 74 | } 75 | 76 | func testCountLinesExcBlanks() throws { 77 | let inputs = ["123", "", """ 78 | 79 | """, 80 | """ 81 | Hello 82 | World 83 | 84 | . 85 | """] 86 | let outputs: [String] = performFormatterTest(format: CountLinesExcBlanks(), inputs: inputs) 87 | XCTAssertEqual(outputs[0], "1") 88 | XCTAssertEqual(outputs[1], "0") 89 | XCTAssertEqual(outputs[2], "0") 90 | XCTAssertEqual(outputs[3], "3") 91 | 92 | measure { 93 | for _ in 1 ... 10 { 94 | _ = performFormatterTest(format: CountLinesExcBlanks()) 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Tests/TextCaseKitTests/FontFormatTests.swift: -------------------------------------------------------------------------------- 1 | import TextCaseKit 2 | import XCTest 3 | 4 | final class FontFormatTests: FormatTestBase { 5 | func testBoldSansSerif() throws { 6 | let outputs: [String] = performFormatterTest(format: BoldSansSerif()) 7 | XCTAssertEqual(outputs[0], "𝗛𝗲𝗹𝗹𝗼, 𝗪𝗼𝗿𝗹𝗱") 8 | XCTAssertEqual(outputs[1], "123456") 9 | XCTAssertEqual(outputs[2], "𝗧𝗲𝘀𝘁 𝘀𝗲𝗻𝘁𝗲𝗻𝗰𝗲. 𝘁𝗲𝘀𝘁 𝘀𝗲𝗻𝘁𝗲𝗻𝗰𝗲.") 10 | XCTAssertEqual(outputs[3], "3.14") 11 | XCTAssertEqual(outputs[4], "𝗔𝗕𝗖") 12 | XCTAssertEqual(outputs[5], "𝘀𝗻𝗮𝗸𝗲_𝗰𝗮𝘀𝗲") 13 | XCTAssertEqual(outputs[6], "𝗧𝗵𝗶𝘀 𝗶𝘀 𝗮 𝘀𝗲𝗻𝘁𝗲𝗻𝗰𝗲.") 14 | 15 | measure { 16 | for _ in 1 ... 10 { 17 | _ = performFormatterTest(format: BoldSansSerif()) 18 | } 19 | } 20 | } 21 | 22 | func testBoldSerif() throws { 23 | let outputs: [String] = performFormatterTest(format: BoldSerif()) 24 | XCTAssertEqual(outputs[0], "𝐇𝐞𝐥𝐥𝐨, 𝐖𝐨𝐫𝐥𝐝") 25 | XCTAssertEqual(outputs[1], "123456") 26 | XCTAssertEqual(outputs[2], "𝐓𝐞𝐬𝐭 𝐬𝐞𝐧𝐭𝐞𝐧𝐜𝐞. 𝐭𝐞𝐬𝐭 𝐬𝐞𝐧𝐭𝐞𝐧𝐜𝐞.") 27 | XCTAssertEqual(outputs[3], "3.14") 28 | XCTAssertEqual(outputs[4], "𝐀𝐁𝐂") 29 | XCTAssertEqual(outputs[5], "𝐬𝐧𝐚𝐤𝐞_𝐜𝐚𝐬𝐞") 30 | XCTAssertEqual(outputs[6], "𝐓𝐡𝐢𝐬 𝐢𝐬 𝐚 𝐬𝐞𝐧𝐭𝐞𝐧𝐜𝐞.") 31 | 32 | measure { 33 | for _ in 1 ... 10 { 34 | _ = performFormatterTest(format: BoldSerif()) 35 | } 36 | } 37 | } 38 | 39 | func testGothic() throws { 40 | let outputs: [String] = performFormatterTest(format: Gothic()) 41 | XCTAssertEqual(outputs[0], "ℌ𝔢𝔩𝔩𝔬, 𝔚𝔬𝔯𝔩𝔡") 42 | XCTAssertEqual(outputs[1], "123456") 43 | XCTAssertEqual(outputs[2], "𝔗𝔢𝔰𝔱 𝔰𝔢𝔫𝔱𝔢𝔫𝔠𝔢. 𝔱𝔢𝔰𝔱 𝔰𝔢𝔫𝔱𝔢𝔫𝔠𝔢.") 44 | XCTAssertEqual(outputs[3], "3.14") 45 | XCTAssertEqual(outputs[4], "𝔄𝔅ℭ") 46 | XCTAssertEqual(outputs[5], "𝔰𝔫𝔞𝔨𝔢_𝔠𝔞𝔰𝔢") 47 | XCTAssertEqual(outputs[6], "𝔗𝔥𝔦𝔰 𝔦𝔰 𝔞 𝔰𝔢𝔫𝔱𝔢𝔫𝔠𝔢.") 48 | 49 | measure { 50 | for _ in 1 ... 10 { 51 | _ = performFormatterTest(format: Gothic()) 52 | } 53 | } 54 | } 55 | 56 | func testItalic() throws { 57 | let outputs: [String] = performFormatterTest(format: Italic()) 58 | XCTAssertEqual(outputs[0], "𝘏𝘦𝘭𝘭𝘰, 𝘞𝘰𝘳𝘭𝘥") 59 | XCTAssertEqual(outputs[1], "123456") 60 | XCTAssertEqual(outputs[2], "𝘛𝘦𝘴𝘵 𝘴𝘦𝘯𝘵𝘦𝘯𝘤𝘦. 𝘵𝘦𝘴𝘵 𝘴𝘦𝘯𝘵𝘦𝘯𝘤𝘦.") 61 | XCTAssertEqual(outputs[3], "3.14") 62 | XCTAssertEqual(outputs[4], "𝘈𝘉𝘊") 63 | XCTAssertEqual(outputs[5], "𝘴𝘯𝘢𝘬𝘦_𝘤𝘢𝘴𝘦") 64 | XCTAssertEqual(outputs[6], "𝘛𝘩𝘪𝘴 𝘪𝘴 𝘢 𝘴𝘦𝘯𝘵𝘦𝘯𝘤𝘦.") 65 | 66 | measure { 67 | for _ in 1 ... 10 { 68 | _ = performFormatterTest(format: Italic()) 69 | } 70 | } 71 | } 72 | 73 | func testItalicBoldSansSerif() throws { 74 | let outputs: [String] = performFormatterTest(format: ItalicBoldSansSerif()) 75 | XCTAssertEqual(outputs[0], "𝙃𝙚𝙡𝙡𝙤, 𝙒𝙤𝙧𝙡𝙙") 76 | XCTAssertEqual(outputs[1], "123456") 77 | XCTAssertEqual(outputs[2], "𝙏𝙚𝙨𝙩 𝙨𝙚𝙣𝙩𝙚𝙣𝙘𝙚. 𝙩𝙚𝙨𝙩 𝙨𝙚𝙣𝙩𝙚𝙣𝙘𝙚.") 78 | XCTAssertEqual(outputs[3], "3.14") 79 | XCTAssertEqual(outputs[4], "𝘼𝘽𝘾") 80 | XCTAssertEqual(outputs[5], "𝙨𝙣𝙖𝙠𝙚_𝙘𝙖𝙨𝙚") 81 | XCTAssertEqual(outputs[6], "𝙏𝙝𝙞𝙨 𝙞𝙨 𝙖 𝙨𝙚𝙣𝙩𝙚𝙣𝙘𝙚.") 82 | 83 | measure { 84 | for _ in 1 ... 10 { 85 | _ = performFormatterTest(format: ItalicBoldSansSerif()) 86 | } 87 | } 88 | } 89 | 90 | func testItalicBoldSerif() throws { 91 | let outputs: [String] = performFormatterTest(format: ItalicBoldSerif()) 92 | XCTAssertEqual(outputs[0], "𝑯𝒆𝒍𝒍𝒐, 𝑾𝒐𝒓𝒍𝒅") 93 | XCTAssertEqual(outputs[1], "123456") 94 | XCTAssertEqual(outputs[2], "𝑻𝒆𝒔𝒕 𝒔𝒆𝒏𝒕𝒆𝒏𝒄𝒆. 𝒕𝒆𝒔𝒕 𝒔𝒆𝒏𝒕𝒆𝒏𝒄𝒆.") 95 | XCTAssertEqual(outputs[3], "3.14") 96 | XCTAssertEqual(outputs[4], "𝑨𝑩𝑪") 97 | XCTAssertEqual(outputs[5], "𝒔𝒏𝒂𝒌𝒆_𝒄𝒂𝒔𝒆") 98 | XCTAssertEqual(outputs[6], "𝑻𝒉𝒊𝒔 𝒊𝒔 𝒂 𝒔𝒆𝒏𝒕𝒆𝒏𝒄𝒆.") 99 | 100 | measure { 101 | for _ in 1 ... 10 { 102 | _ = performFormatterTest(format: ItalicBoldSerif()) 103 | } 104 | } 105 | } 106 | 107 | func testLettersInCirclesFilled() throws { 108 | let outputs: [String] = performFormatterTest(format: LettersInCirclesFilled()) 109 | XCTAssertEqual(outputs[0], "🅗🅔🅛🅛🅞, 🅦🅞🅡🅛🅓") 110 | XCTAssertEqual(outputs[1], "123456") 111 | XCTAssertEqual(outputs[2], "🅣🅔🅢🅣 🅢🅔🅝🅣🅔🅝🅒🅔. 🅣🅔🅢🅣 🅢🅔🅝🅣🅔🅝🅒🅔.") 112 | XCTAssertEqual(outputs[3], "3.14") 113 | XCTAssertEqual(outputs[4], "🅐🅑🅒") 114 | XCTAssertEqual(outputs[5], "🅢🅝🅐🅚🅔_🅒🅐🅢🅔") 115 | XCTAssertEqual(outputs[6], "🅣🅗🅘🅢 🅘🅢 🅐 🅢🅔🅝🅣🅔🅝🅒🅔.") 116 | 117 | measure { 118 | for _ in 1 ... 10 { 119 | _ = performFormatterTest(format: LettersInCirclesFilled()) 120 | } 121 | } 122 | } 123 | 124 | func testLettersInCirclesOutline() throws { 125 | let outputs: [String] = performFormatterTest(format: LettersInCirclesOutline()) 126 | XCTAssertEqual(outputs[0], "Ⓗⓔⓛⓛⓞ, Ⓦⓞⓡⓛⓓ") 127 | XCTAssertEqual(outputs[1], "123456") 128 | XCTAssertEqual(outputs[2], "Ⓣⓔⓢⓣ ⓢⓔⓝⓣⓔⓝⓒⓔ. ⓣⓔⓢⓣ ⓢⓔⓝⓣⓔⓝⓒⓔ.") 129 | XCTAssertEqual(outputs[3], "3.14") 130 | XCTAssertEqual(outputs[4], "ⒶⒷⒸ") 131 | XCTAssertEqual(outputs[5], "ⓢⓝⓐⓚⓔ_ⓒⓐⓢⓔ") 132 | XCTAssertEqual(outputs[6], "Ⓣⓗⓘⓢ ⓘⓢ ⓐ ⓢⓔⓝⓣⓔⓝⓒⓔ.") 133 | 134 | measure { 135 | for _ in 1 ... 10 { 136 | _ = performFormatterTest(format: LettersInCirclesOutline()) 137 | } 138 | } 139 | } 140 | 141 | func testLettersInSquaresFilled() throws { 142 | let outputs: [String] = performFormatterTest(format: LettersInSquaresFilled()) 143 | XCTAssertEqual(outputs[0], "🅷🅴🅻🅻🅾, 🆆🅾🆁🅻🅳") 144 | XCTAssertEqual(outputs[1], "123456") 145 | XCTAssertEqual(outputs[2], "🆃🅴🆂🆃 🆂🅴🅽🆃🅴🅽🅲🅴. 🆃🅴🆂🆃 🆂🅴🅽🆃🅴🅽🅲🅴.") 146 | XCTAssertEqual(outputs[3], "3.14") 147 | XCTAssertEqual(outputs[4], "🅰🅱🅲") 148 | XCTAssertEqual(outputs[5], "🆂🅽🅰🅺🅴_🅲🅰🆂🅴") 149 | XCTAssertEqual(outputs[6], "🆃🅷🅸🆂 🅸🆂 🅰 🆂🅴🅽🆃🅴🅽🅲🅴.") 150 | 151 | measure { 152 | for _ in 1 ... 10 { 153 | _ = performFormatterTest(format: LettersInSquaresFilled()) 154 | } 155 | } 156 | } 157 | 158 | func testLettersInSquaresOutline() throws { 159 | let outputs: [String] = performFormatterTest(format: LettersInSquaresOutline()) 160 | XCTAssertEqual(outputs[0], "🄷🄴🄻🄻🄾, 🅆🄾🅁🄻🄳") 161 | XCTAssertEqual(outputs[1], "123456") 162 | XCTAssertEqual(outputs[2], "🅃🄴🅂🅃 🅂🄴🄽🅃🄴🄽🄲🄴. 🅃🄴🅂🅃 🅂🄴🄽🅃🄴🄽🄲🄴.") 163 | XCTAssertEqual(outputs[3], "3.14") 164 | XCTAssertEqual(outputs[4], "🄰🄱🄲") 165 | XCTAssertEqual(outputs[5], "🅂🄽🄰🄺🄴_🄲🄰🅂🄴") 166 | XCTAssertEqual(outputs[6], "🅃🄷🄸🅂 🄸🅂 🄰 🅂🄴🄽🅃🄴🄽🄲🄴.") 167 | 168 | measure { 169 | for _ in 1 ... 10 { 170 | _ = performFormatterTest(format: LettersInSquaresOutline()) 171 | } 172 | } 173 | } 174 | 175 | func testScript() throws { 176 | let outputs: [String] = performFormatterTest(format: Script()) 177 | XCTAssertEqual(outputs[0], "ℋℯ𝓁𝓁ℴ, 𝒲ℴ𝓇𝓁𝒹") 178 | XCTAssertEqual(outputs[1], "123456") 179 | XCTAssertEqual(outputs[2], "𝒯ℯ𝓈𝓉 𝓈ℯ𝓃𝓉ℯ𝓃𝒸ℯ. 𝓉ℯ𝓈𝓉 𝓈ℯ𝓃𝓉ℯ𝓃𝒸ℯ.") 180 | XCTAssertEqual(outputs[3], "3.14") 181 | XCTAssertEqual(outputs[4], "𝒜ℬ𝒞") 182 | XCTAssertEqual(outputs[5], "𝓈𝓃𝒶𝓀ℯ_𝒸𝒶𝓈ℯ") 183 | XCTAssertEqual(outputs[6], "𝒯𝒽𝒾𝓈 𝒾𝓈 𝒶 𝓈ℯ𝓃𝓉ℯ𝓃𝒸ℯ.") 184 | 185 | measure { 186 | for _ in 1 ... 10 { 187 | _ = performFormatterTest(format: Script()) 188 | } 189 | } 190 | } 191 | 192 | func testStrikethrough() throws { 193 | let outputs: [String] = performFormatterTest(format: Strikethrough()) 194 | XCTAssertEqual(outputs[0], "H̶e̶l̶l̶o̶, W̶o̶r̶l̶d̶") 195 | XCTAssertEqual(outputs[1], "123456") 196 | XCTAssertEqual(outputs[2], "T̶e̶s̶t̶ s̶e̶n̶t̶e̶n̶c̶e̶. t̶e̶s̶t̶ s̶e̶n̶t̶e̶n̶c̶e̶.") 197 | XCTAssertEqual(outputs[3], "3.14") 198 | XCTAssertEqual(outputs[4], "A̶B̶C̶") 199 | XCTAssertEqual(outputs[5], "s̶n̶a̶k̶e̶_c̶a̶s̶e̶") 200 | XCTAssertEqual(outputs[6], "T̶h̶i̶s̶ i̶s̶ a̶ s̶e̶n̶t̶e̶n̶c̶e̶.") 201 | 202 | measure { 203 | for _ in 1 ... 10 { 204 | _ = performFormatterTest(format: Strikethrough()) 205 | } 206 | } 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /Tests/TextCaseKitTests/FormatTestBase.swift: -------------------------------------------------------------------------------- 1 | import TextCaseKit 2 | import XCTest 3 | 4 | class FormatTestBase: XCTestCase { 5 | private static let defaultInputs = [ 6 | "Hello, World", "123456", "Test sentence. test sentence.", "3.14", "ABC", "snake_case", 7 | "This is a sentence.", 8 | ] 9 | 10 | // MARK: - General 11 | 12 | func performFormatterTest(format: Format, inputs: [String] = defaultInputs) -> [String] { 13 | var outputs: [String] = [] 14 | for input in inputs { 15 | outputs.append(format.process(input)) 16 | } 17 | return outputs 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Tests/TextCaseKitTests/FunFormatTests.swift: -------------------------------------------------------------------------------- 1 | import TextCaseKit 2 | import XCTest 3 | 4 | final class FunFormatTests: FormatTestBase { 5 | func testClapCase() throws { 6 | let outputs: [String] = performFormatterTest(format: ClapCase()) 7 | XCTAssertEqual(outputs[0], "Hello, 👏 World") 8 | XCTAssertEqual(outputs[1], "123456") 9 | XCTAssertEqual(outputs[2], "Test 👏 sentence. 👏 test 👏 sentence.") 10 | XCTAssertEqual(outputs[3], "3.14") 11 | XCTAssertEqual(outputs[4], "ABC") 12 | XCTAssertEqual(outputs[5], "snake 👏 case") 13 | XCTAssertEqual(outputs[6], "This 👏 is 👏 a 👏 sentence.") 14 | 15 | measure { 16 | for _ in 1 ... 10 { 17 | _ = performFormatterTest(format: ClapCase()) 18 | } 19 | } 20 | } 21 | 22 | func testHashtags() throws { 23 | let outputs: [String] = performFormatterTest(format: Hashtags()) 24 | XCTAssertEqual(outputs[0], "#Hello #World") 25 | XCTAssertEqual(outputs[1], "#123456") 26 | XCTAssertEqual(outputs[2], "#Test #sentence #test #sentence") 27 | XCTAssertEqual(outputs[3], "#314") 28 | XCTAssertEqual(outputs[4], "#ABC") 29 | XCTAssertEqual(outputs[5], "#snake_case") 30 | XCTAssertEqual(outputs[6], "#This #is #a #sentence") 31 | 32 | measure { 33 | for _ in 1 ... 10 { 34 | _ = performFormatterTest(format: Hashtags()) 35 | } 36 | } 37 | } 38 | 39 | func testRot13() throws { 40 | let outputs: [String] = performFormatterTest(format: Rot13()) 41 | XCTAssertEqual(outputs[0], "Uryyb, Jbeyq") 42 | XCTAssertEqual(outputs[1], "123456") 43 | XCTAssertEqual(outputs[2], "Grfg fragrapr. grfg fragrapr.") 44 | XCTAssertEqual(outputs[3], "3.14") 45 | XCTAssertEqual(outputs[4], "NOP") 46 | XCTAssertEqual(outputs[5], "fanxr_pnfr") 47 | XCTAssertEqual(outputs[6], "Guvf vf n fragrapr.") 48 | 49 | measure { 50 | for _ in 1 ... 10 { 51 | _ = performFormatterTest(format: Rot13()) 52 | } 53 | } 54 | } 55 | 56 | func testShuffled() throws { 57 | let inputs: [String] = ["hello", "snake", "beer bear"] 58 | for input in inputs { 59 | let output = performFormatterTest(format: Shuffled(), inputs: [input]).first ?? "" 60 | XCTAssertNotEqual(input, output) 61 | XCTAssertEqual(input.count, output.count) 62 | } 63 | 64 | measure { 65 | for _ in 1 ... 10 { 66 | _ = performFormatterTest(format: Shuffled()) 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Tests/TextCaseKitTests/ProgrammingFormatTests.swift: -------------------------------------------------------------------------------- 1 | import TextCaseKit 2 | import XCTest 3 | 4 | class ProgrammingFormatTests: FormatTestBase { 5 | func testCamelCase() throws { 6 | let inputs = ["hello world", "HelloWorld", "hello-world", "hello_world"] 7 | let outputs: [String] = performFormatterTest(format: CamelCase(), inputs: inputs) 8 | XCTAssertEqual(outputs[0], "helloWorld") 9 | XCTAssertEqual(outputs[1], "helloWorld") 10 | XCTAssertEqual(outputs[2], "helloWorld") 11 | XCTAssertEqual(outputs[3], "helloWorld") 12 | 13 | measure { 14 | for _ in 1 ... 10 { 15 | _ = performFormatterTest(format: CamelCase()) 16 | } 17 | } 18 | } 19 | 20 | func testPascalCase() throws { 21 | let inputs = ["hello world", "HelloWorld", "hello-world", "hello_world"] 22 | let outputs: [String] = performFormatterTest(format: PascalCase(), inputs: inputs) 23 | XCTAssertEqual(outputs[0], "HelloWorld") 24 | XCTAssertEqual(outputs[1], "HelloWorld") 25 | XCTAssertEqual(outputs[2], "HelloWorld") 26 | XCTAssertEqual(outputs[3], "HelloWorld") 27 | 28 | measure { 29 | for _ in 1 ... 10 { 30 | _ = performFormatterTest(format: PascalCase()) 31 | } 32 | } 33 | } 34 | 35 | func testSnakeCase() throws { 36 | let inputs = ["hello world", "HelloWorld", "hello-world", "hello_world"] 37 | let outputs: [String] = performFormatterTest(format: SnakeCase(), inputs: inputs) 38 | XCTAssertEqual(outputs[0], "hello_world") 39 | XCTAssertEqual(outputs[1], "hello_world") 40 | XCTAssertEqual(outputs[2], "hello_world") 41 | XCTAssertEqual(outputs[3], "hello_world") 42 | 43 | measure { 44 | for _ in 1 ... 10 { 45 | _ = performFormatterTest(format: SnakeCase()) 46 | } 47 | } 48 | } 49 | 50 | func testKebabCase() throws { 51 | let inputs = ["hello world", "HelloWorld", "hello-world", "hello_world"] 52 | let outputs: [String] = performFormatterTest(format: KebabCase(), inputs: inputs) 53 | XCTAssertEqual(outputs[0], "hello-world") 54 | XCTAssertEqual(outputs[1], "hello-world") 55 | XCTAssertEqual(outputs[2], "hello-world") 56 | XCTAssertEqual(outputs[3], "hello-world") 57 | 58 | measure { 59 | for _ in 1 ... 10 { 60 | _ = performFormatterTest(format: KebabCase()) 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Tests/TextCaseKitTests/SimpleFormatTests.swift: -------------------------------------------------------------------------------- 1 | import TextCaseKit 2 | import XCTest 3 | 4 | final class SimpleFormatTests: FormatTestBase { 5 | func testReversed() throws { 6 | let outputs: [String] = performFormatterTest(format: Reversed()) 7 | XCTAssertEqual(outputs[0], "dlroW ,olleH") 8 | XCTAssertEqual(outputs[1], "654321") 9 | XCTAssertEqual(outputs[2], ".ecnetnes tset .ecnetnes tseT") 10 | XCTAssertEqual(outputs[3], "41.3") 11 | XCTAssertEqual(outputs[4], "CBA") 12 | XCTAssertEqual(outputs[5], "esac_ekans") 13 | XCTAssertEqual(outputs[6], ".ecnetnes a si sihT") 14 | 15 | measure { 16 | for _ in 1 ... 10 { 17 | _ = performFormatterTest(format: Reversed()) 18 | } 19 | } 20 | } 21 | 22 | func testCapitalise() throws { 23 | let outputs = performFormatterTest(format: Capitalise()) 24 | XCTAssertEqual(outputs[0], "Hello, World") 25 | XCTAssertEqual(outputs[1], "123456") 26 | XCTAssertEqual(outputs[2], "Test sentence. Test sentence.") 27 | XCTAssertEqual(outputs[3], "3.14") 28 | XCTAssertEqual(outputs[4], "ABC") 29 | XCTAssertEqual(outputs[5], "Snake_case") 30 | XCTAssertEqual(outputs[6], "This is a sentence.") 31 | 32 | measure { 33 | for _ in 1 ... 10 { 34 | _ = performFormatterTest(format: Capitalise()) 35 | } 36 | } 37 | } 38 | 39 | func testCapitaliseWords() throws { 40 | let outputs = performFormatterTest(format: CapitaliseWords()) 41 | XCTAssertEqual(outputs[0], "Hello, World") 42 | XCTAssertEqual(outputs[1], "123456") 43 | XCTAssertEqual(outputs[2], "Test Sentence. Test Sentence.") 44 | XCTAssertEqual(outputs[3], "3.14") 45 | XCTAssertEqual(outputs[4], "Abc") 46 | XCTAssertEqual(outputs[5], "Snake_Case") 47 | XCTAssertEqual(outputs[6], "This Is A Sentence.") 48 | 49 | measure { 50 | for _ in 1 ... 10 { 51 | _ = performFormatterTest(format: CapitaliseWords()) 52 | } 53 | } 54 | } 55 | 56 | func testLowercase() throws { 57 | let outputs = performFormatterTest(format: Lowercase()) 58 | XCTAssertEqual(outputs[0], "hello, world") 59 | XCTAssertEqual(outputs[1], "123456") 60 | XCTAssertEqual(outputs[2], "test sentence. test sentence.") 61 | XCTAssertEqual(outputs[3], "3.14") 62 | XCTAssertEqual(outputs[4], "abc") 63 | XCTAssertEqual(outputs[5], "snake_case") 64 | XCTAssertEqual(outputs[6], "this is a sentence.") 65 | 66 | measure { 67 | for _ in 1 ... 10 { 68 | _ = performFormatterTest(format: Lowercase()) 69 | } 70 | } 71 | } 72 | 73 | func testUppercase() throws { 74 | let outputs = performFormatterTest(format: Uppercase()) 75 | XCTAssertEqual(outputs[0], "HELLO, WORLD") 76 | XCTAssertEqual(outputs[1], "123456") 77 | XCTAssertEqual(outputs[2], "TEST SENTENCE. TEST SENTENCE.") 78 | XCTAssertEqual(outputs[3], "3.14") 79 | XCTAssertEqual(outputs[4], "ABC") 80 | XCTAssertEqual(outputs[5], "SNAKE_CASE") 81 | XCTAssertEqual(outputs[6], "THIS IS A SENTENCE.") 82 | 83 | measure { 84 | for _ in 1 ... 10 { 85 | _ = performFormatterTest(format: Uppercase()) 86 | } 87 | } 88 | } 89 | 90 | func testSentence() throws { 91 | let outputs = performFormatterTest(format: Sentence()) 92 | XCTAssertEqual(outputs[0], "Hello, world") 93 | XCTAssertEqual(outputs[1], "123456") 94 | XCTAssertEqual(outputs[2], "Test sentence. Test sentence.") 95 | XCTAssertEqual(outputs[3], "3.14") 96 | XCTAssertEqual(outputs[4], "Abc") 97 | XCTAssertEqual(outputs[5], "Snake_case") 98 | XCTAssertEqual(outputs[6], "This is a sentence.") 99 | 100 | measure { 101 | for _ in 1 ... 10 { 102 | _ = performFormatterTest(format: Sentence()) 103 | } 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /Tests/TextCaseKitTests/TitleFormatTests.swift: -------------------------------------------------------------------------------- 1 | import TextCaseKit 2 | import XCTest 3 | 4 | final class TitleFormatTests: FormatTestBase { 5 | func testAMATitleCase() throws { 6 | let inputs = ["Hello, World.", "3.14", "this is a sentence", "THIS IS A SENTENCE. AND ANOTHER ONE.", "the fox jumped over the lazy dog"] 7 | let outputs: [String] = performFormatterTest(format: AMATitleCase(), inputs: inputs) 8 | XCTAssertEqual(outputs[0], "Hello, world.") 9 | XCTAssertEqual(outputs[1], "3.14") 10 | XCTAssertEqual(outputs[2], "This is a Sentence") 11 | XCTAssertEqual(outputs[3], "This is a Sentence. And another one.") 12 | XCTAssertEqual(outputs[4], "The Fox jumped Over the Lazy Dog") 13 | 14 | measure { 15 | for _ in 1 ... 10 { 16 | _ = performFormatterTest(format: AMATitleCase()) 17 | } 18 | } 19 | } 20 | 21 | func testAPATitleCase() throws { 22 | let inputs = ["Hello, World.", "3.14", "this is a sentence", "THIS IS A SENTENCE. AND ANOTHER ONE.", "the fox jumped over the lazy dog"] 23 | let outputs: [String] = performFormatterTest(format: APATitleCase(), inputs: inputs) 24 | XCTAssertEqual(outputs[0], "Hello, World.") 25 | XCTAssertEqual(outputs[1], "3.14") 26 | XCTAssertEqual(outputs[2], "This Is a Sentence") 27 | XCTAssertEqual(outputs[3], "This Is a Sentence. And Another One.") 28 | XCTAssertEqual(outputs[4], "The Fox Jumped Over the Lazy Dog") 29 | 30 | measure { 31 | for _ in 1 ... 10 { 32 | _ = performFormatterTest(format: APATitleCase()) 33 | } 34 | } 35 | } 36 | 37 | func testAPTitleCase() throws { 38 | let inputs = ["Hello, World.", "3.14", "this is a sentence", "THIS IS A SENTENCE. AND ANOTHER ONE.", "the fox jumped over the lazy dog"] 39 | let outputs: [String] = performFormatterTest(format: APTitleCase(), inputs: inputs) 40 | XCTAssertEqual(outputs[0], "Hello, World.") 41 | XCTAssertEqual(outputs[1], "3.14") 42 | XCTAssertEqual(outputs[2], "This is a Sentence") 43 | XCTAssertEqual(outputs[3], "This is a Sentence. And Another One.") 44 | XCTAssertEqual(outputs[4], "The Fox Jumped Over the Lazy Dog") 45 | 46 | measure { 47 | for _ in 1 ... 10 { 48 | _ = performFormatterTest(format: APTitleCase()) 49 | } 50 | } 51 | } 52 | 53 | func testBluebookTitleCase() throws { 54 | let inputs = ["Hello, World.", "3.14", "this is a sentence", "THIS IS A SENTENCE. AND ANOTHER ONE.", "the fox jumped over the lazy dog"] 55 | let outputs: [String] = performFormatterTest(format: BluebookTitleCase(), inputs: inputs) 56 | XCTAssertEqual(outputs[0], "Hello, World.") 57 | XCTAssertEqual(outputs[1], "3.14") 58 | XCTAssertEqual(outputs[2], "This is a Sentence") 59 | XCTAssertEqual(outputs[3], "This is a Sentence. And Another one.") 60 | XCTAssertEqual(outputs[4], "The Fox Jumped over the lazy Dog") 61 | 62 | measure { 63 | for _ in 1 ... 10 { 64 | _ = performFormatterTest(format: BluebookTitleCase()) 65 | } 66 | } 67 | } 68 | 69 | func testCMOSTitleCase() throws { 70 | let inputs = ["Hello, World.", "3.14", "this is a sentence", "THIS IS A SENTENCE. AND ANOTHER ONE.", "the fox jumped over the lazy dog"] 71 | let outputs: [String] = performFormatterTest(format: CMOSTitleCase(), inputs: inputs) 72 | XCTAssertEqual(outputs[0], "Hello, World.") 73 | XCTAssertEqual(outputs[1], "3.14") 74 | XCTAssertEqual(outputs[2], "This Is a Sentence") 75 | XCTAssertEqual(outputs[3], "This Is a Sentence. And another One.") 76 | XCTAssertEqual(outputs[4], "The Fox Jumped over the Lazy Dog") 77 | 78 | measure { 79 | for _ in 1 ... 10 { 80 | _ = performFormatterTest(format: CMOSTitleCase()) 81 | } 82 | } 83 | } 84 | 85 | func testGuardianTitleCase() throws { 86 | let inputs = ["Hello, World.", "3.14", "this is a sentence", "THIS IS A SENTENCE. AND ANOTHER ONE.", "the fox jumped over the lazy dog"] 87 | let outputs: [String] = performFormatterTest(format: GuardianTitleCase(), inputs: inputs) 88 | XCTAssertEqual(outputs[0], "Hello, world.") 89 | XCTAssertEqual(outputs[1], "3.14") 90 | XCTAssertEqual(outputs[2], "This is a Sentence") 91 | XCTAssertEqual(outputs[3], "This is a Sentence. And another one.") 92 | XCTAssertEqual(outputs[4], "The Fox jumped over the lazy Dog") 93 | 94 | measure { 95 | for _ in 1 ... 10 { 96 | _ = performFormatterTest(format: GuardianTitleCase()) 97 | } 98 | } 99 | } 100 | 101 | func testMLATitleCase() throws { 102 | let inputs = ["Hello, World.", "3.14", "this is a sentence", "THIS IS A SENTENCE. AND ANOTHER ONE.", "the fox jumped over the lazy dog"] 103 | let outputs: [String] = performFormatterTest(format: MLATitleCase(), inputs: inputs) 104 | XCTAssertEqual(outputs[0], "Hello, World.") 105 | XCTAssertEqual(outputs[1], "3.14") 106 | XCTAssertEqual(outputs[2], "This Is a Sentence") 107 | XCTAssertEqual(outputs[3], "This Is a Sentence. And another One.") 108 | XCTAssertEqual(outputs[4], "The Fox Jumped over the Lazy Dog") 109 | 110 | measure { 111 | for _ in 1 ... 10 { 112 | _ = performFormatterTest(format: MLATitleCase()) 113 | } 114 | } 115 | } 116 | 117 | func testNYTTitleCase() throws { 118 | let inputs = ["Hello, World.", "3.14", "this is a sentence", "THIS IS A SENTENCE. AND ANOTHER ONE.", "the fox jumped over the lazy dog"] 119 | let outputs: [String] = performFormatterTest(format: NYTTitleCase(), inputs: inputs) 120 | XCTAssertEqual(outputs[0], "Hello, World.") 121 | XCTAssertEqual(outputs[1], "3.14") 122 | XCTAssertEqual(outputs[2], "This Is a Sentence") 123 | XCTAssertEqual(outputs[3], "This Is a Sentence. And Another One.") 124 | XCTAssertEqual(outputs[4], "The Fox Jumped Over the Lazy Dog") 125 | 126 | measure { 127 | for _ in 1 ... 10 { 128 | _ = performFormatterTest(format: NYTTitleCase()) 129 | } 130 | } 131 | } 132 | 133 | func testWikipediaTitleCase() throws { 134 | let inputs = ["Hello, World.", "3.14", "this is a sentence", "THIS IS A SENTENCE. AND ANOTHER ONE.", "the fox jumped over the lazy dog"] 135 | let outputs: [String] = performFormatterTest(format: WikipediaTitleCase(), inputs: inputs) 136 | XCTAssertEqual(outputs[0], "Hello, World.") 137 | XCTAssertEqual(outputs[1], "3.14") 138 | XCTAssertEqual(outputs[2], "This Is a Sentence") 139 | XCTAssertEqual(outputs[3], "This Is a Sentence. And Another One.") 140 | XCTAssertEqual(outputs[4], "The Fox Jumped Over the Lazy Dog") 141 | 142 | measure { 143 | for _ in 1 ... 10 { 144 | _ = performFormatterTest(format: WikipediaTitleCase()) 145 | } 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /logo.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrishannah/textcase-cli/df8cdf36c881470d9f59f35d17f90ced5e51f7ae/logo.jpeg -------------------------------------------------------------------------------- /project.yml: -------------------------------------------------------------------------------- 1 | name: Text Case 2 | options: 3 | bundleIdPrefix: me.chrishannah 4 | createIntermediateGroups: true 5 | fileGroups: 6 | - project.yml 7 | settings: 8 | GENERATE_INFOPLIST_FILE: YES 9 | targets: 10 | textcase: 11 | type: tool 12 | platform: macOS 13 | deploymentTarget: "13.0" 14 | sources: Sources/TextCase 15 | dependencies: 16 | - framework: TextCaseKit 17 | TextCaseKit: 18 | type: framework 19 | platform: macOS 20 | sources: Sources/TextCaseKit 21 | scheme: 22 | testTargets: 23 | - TextCaseKitTests 24 | TextCaseKitTests: 25 | type: bundle.unit-test 26 | platform: macOS 27 | sources: 28 | - path: Tests/TextCaseKitTests 29 | dependencies: 30 | - target: TextCaseKit 31 | --------------------------------------------------------------------------------