├── .clang_complete ├── .github └── workflows │ └── makefile.yml ├── .gitignore ├── CHANGELOG ├── LICENSE ├── Makefile ├── README.md ├── android-build.sh ├── build.config ├── docs └── FrameIntroduction.odp ├── frame-browser ├── FrameBrowser.ico ├── FrameBrowser.lpi ├── FrameBrowser.lpr ├── FrameBrowser.lps ├── FrameBrowser.res ├── framewrapper.pas ├── mainwin.lfm └── mainwin.pas ├── frame.bash.autocomplete ├── src ├── ds_array.c ├── ds_array.h ├── ds_str.c ├── ds_str.h ├── frame.c ├── frm.c └── frm.h ├── swig-input.swig ├── test.ps1 ├── test.results.saved └── test.sh /.clang_complete: -------------------------------------------------------------------------------- 1 | -I. 2 | -I./include 3 | -std=gnu99 4 | -Dframe_version='"0.0.1"' 5 | -DBUILD_TIMESTAMP='"201909162104"' 6 | 7 | -------------------------------------------------------------------------------- /.github/workflows/makefile.yml: -------------------------------------------------------------------------------- 1 | name: Makefile CI 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v3 16 | 17 | - name: build 18 | run: make debug 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | v0.1.4 - Tue 18 Jul 2023 09:49:03 SAST 2 | 1, Adds Windows support, including powershell test script. 3 | 2. Lots of improvements to GUI, including proper Windows support and moving to 4 | a monospace font for the notes. 5 | 6 | v0.1.3 - Mon 12 Jun 2023 16:35:42 SAST 7 | 1. Adds some documentation. 8 | 2. Adds '--frame=' option, to execute the command in the context of a different 9 | frame. 10 | 3. Adds a gui program, FrameBrowser, for easy navigation and editing of 11 | frames. 12 | 4. Adds 'rename' command. 13 | 5. Adds lockfile to prevent different applications using the frm_*() functions 14 | at the same time. 15 | 16 | v0.1.2 - Fri 05 May 2023 22:02:07 SAST 17 | 18 | Features 19 | 1. Adds --force for pop command, to let user pop a non-empty frame. 20 | 2. With no command, a short message is printed and the command defaults to status. 21 | 3. Adds tree command and tree functions, to print an indented tree of frames] to 22 | strip the dates'. 23 | 24 | v0.1.1 - Wed 19 Apr 2023 18:00:27 SAST 25 | 26 | Features 27 | 1. Adds edit command. 28 | 2. Listing defaults to current node descendents only. 29 | 3. Adds --quiet mode for calling scripts. 30 | 4. Adds 'back' command for navigating through history. 31 | 5. Updates history to take count argument. 32 | 6. Prints out payload after every node change. 33 | 7. Default message in EDITOR contains the pathname being created. 34 | 8. Adds optional target for "list" command. 35 | 9. Switching works smartly, by "remembering" the most recent subnode in the node 36 | being switched to. 37 | 10. New "top" command to quickly go to the root of the tree. 38 | 11. Frame pop command does not allow popping if subframe exists. 39 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # ###################################################################### 2 | # We include the user-specified variables first 3 | include build.config 4 | ifndef PROJNAME 5 | $(error $$PROJNAME not defined. Is the 'build.config' file missing?) 6 | endif 7 | 8 | export DESCRIPTION 9 | 10 | VERSION?=0.0.0 11 | MAINTAINER?="No maintainer" 12 | HOMEPAGE?="No homepage" 13 | DESCRIPTION?="No description" 14 | SECTION?="custom" 15 | DEPENDS= 16 | 17 | export DESCRIPTION 18 | 19 | # ###################################################################### 20 | # Set some colours for $(ECHO) to use 21 | NONE:=\e[0m 22 | INV:=\e[7m 23 | RED:=\e[31m 24 | GREEN:=\e[32m 25 | BLUE:=\e[34m 26 | CYAN:=\e[36m 27 | YELLOW:=\e[33m 28 | 29 | 30 | # ###################################################################### 31 | # We record the start time, for determining how long the build took 32 | START_TIME:=$(shell date +"%s") 33 | 34 | 35 | # ###################################################################### 36 | # Some housekeeping to determine if we are running on a POSIX 37 | # platform or on Windows 38 | 39 | MAKEPROGRAM_EXE=$(findstring exe,$(MAKE)) 40 | MAKEPROGRAM_MINGW=$(findstring mingw,$(MAKE)) 41 | GITSHELL=$(findstring Git,$(SHELL)) 42 | GITSHELL+=$(findstring git,$(SHELL)) 43 | MINGW_DETECTED=$(findstring mingw,$(GCC)) 44 | BUILD_HOST=$(findstring Linux,$(shell uname -s)) 45 | 46 | 47 | # TODO: Remember that freebsd might use not gmake/gnu-make; must add in 48 | # some diagnostics so that user gets a message to install gnu make. 49 | 50 | ifneq ($(MINGW_DETECTED),) 51 | ifeq ($(strip $(BUILD_HOST)),Linux) 52 | HOME=$(subst \,/,$(HOMEDRIVE)$(HOMEPATH)) 53 | PLATFORM:=Windows 54 | EXE_EXT:=.exe 55 | LIB_EXT:=.dll 56 | PLATFORM_LDFLAGS:=-L$(HOME)/lib -lmingw32 -lmsvcrt -lgcc -liphlpapi -lws2_32 57 | PLATFORM_CFLAGS:= -D__USE_MINGW_ANSI_STDIO -DWINVER=0x0600 -D_WIN32_WINNT=0x0600 58 | ECHO:=echo 59 | endif 60 | endif 61 | 62 | 63 | ifneq ($(MAKEPROGRAM_EXE),) 64 | ifeq ($(strip $(GITSHELL)),) 65 | $(error On windows this must be executed from the Git bash shell) 66 | endif 67 | HOME=$(subst \,/,$(HOMEDRIVE)$(HOMEPATH)) 68 | PLATFORM:=Windows 69 | EXE_EXT:=.exe 70 | LIB_EXT:=.dll 71 | PLATFORM_LDFLAGS:=-L$(HOME)/lib -lws2_32 -lmsvcrt -lgcc 72 | PLATFORM_CFLAGS:= -D__USE_MINGW_ANSI_STDIO 73 | ECHO:=echo -e 74 | endif 75 | 76 | ifneq ($(MAKEPROGRAM_MINGW),) 77 | ifeq ($(strip $(GITSHELL)),) 78 | $(error On windows this must be executed from the Git bash shell) 79 | endif 80 | HOME=$(subst \,/,$(HOMEDRIVE)$(HOMEPATH)) 81 | PLATFORM:=Windows 82 | EXE_EXT:=.exe 83 | LIB_EXT:=.dll 84 | PLATFORM_LDFLAGS:=-L$(HOME)/lib -lmingw32 -lws2_32 -lmsvcrt -lgcc 85 | PLATFORM_CFLAGS:= -D__USE_MINGW_ANSI_STDIO 86 | ECHO:=echo -e 87 | endif 88 | 89 | # If neither of the above are true then we assume a working POSIX 90 | # platform 91 | ifeq ($(PLATFORM),) 92 | PLATFORM:=POSIX 93 | EXE_EXT:=.elf 94 | LIB_EXT:=.so 95 | PLATFORM_LDFLAGS:= -ldl 96 | ECHO:=echo 97 | REAL_SHOW:=real-show 98 | endif 99 | 100 | 101 | 102 | # ###################################################################### 103 | # Set the output directories, output filenames 104 | 105 | OUTDIR=debug 106 | ifeq ($(INSTALL_PREFIX),) 107 | $(warning *********************************************************************) 108 | $(warning * $$INSTALL_PREFIX not defined! *) 109 | $(warning * If you get include path or link errors, set the $$INSTALL_PREFIX *) 110 | $(warning * and try again. *) 111 | $(warning *********************************************************************) 112 | $(warning Using '$(CURDIR)/..' as $$INSTALL_PREFIX) 113 | endif 114 | 115 | INSTALL_PREFIX?=$(CURDIR)/.. 116 | 117 | ifneq (,$(findstring debug,$(MAKECMDGOALS))) 118 | OUTDIR=debug 119 | endif 120 | 121 | ifneq (,$(findstring release,$(MAKECMDGOALS))) 122 | OUTDIR=release 123 | endif 124 | 125 | TARGET:=$(shell $(GCC) -dumpmachine) 126 | T_ARCH=$(shell $(GCC) -dumpmachine | cut -f 1 -d - ) 127 | OUTLIB:=$(OUTDIR)/lib/$(TARGET) 128 | OUTBIN:=$(OUTDIR)/bin/$(TARGET) 129 | OUTOBS:=$(OUTDIR)/obs/$(TARGET) 130 | OUTDIRS:=$(OUTLIB) $(OUTBIN) $(OUTOBS) include 131 | 132 | # ###################################################################### 133 | # The architecture has to be manually mapped to a compatible one for 134 | # debian. As I test more arches, I'll put them in here. By default we 135 | # will use whatever gcc gave us. 136 | PACKAGE_ARCH=$(T_ARCH) 137 | ifeq ($(T_ARCH),x86_64) 138 | PACKAGE_ARCH=amd64 139 | endif 140 | 141 | # ###################################################################### 142 | # Declare the final outputs 143 | BINPROGS:=\ 144 | $(foreach fname,$(MAIN_PROGRAM_CSOURCEFILES),$(OUTBIN)/$(fname)$(EXE_EXT))\ 145 | $(foreach fname,$(MAIN_PROGRAM_CPPSOURCEFILES),$(OUTBIN)/$(fname)$(EXE_EXT)) 146 | 147 | DYNLIB:=$(OUTLIB)/lib$(PROJNAME)-$(VERSION)$(LIB_EXT) 148 | STCLIB:=$(OUTLIB)/lib$(PROJNAME)-$(VERSION).a 149 | DYNLNK_TARGET:=lib$(PROJNAME)-$(VERSION)$(LIB_EXT) 150 | STCLNK_TARGET:=lib$(PROJNAME)-$(VERSION).a 151 | DYNLNK_NAME:=$(OUTLIB)/lib$(PROJNAME)$(LIB_EXT) 152 | STCLNK_NAME:=$(OUTLIB)/lib$(PROJNAME).a 153 | 154 | ifneq ($(SWIG_WRAPPERS),) 155 | SWIG_OBJECTS:=swig_$(PROJNAME) 156 | endif 157 | 158 | # ###################################################################### 159 | # Declare the intermediate outputs 160 | BIN_COBS:=\ 161 | $(foreach fname,$(MAIN_PROGRAM_CSOURCEFILES),$(OUTOBS)/$(fname).o) 162 | 163 | BIN_CPPOBS:=\ 164 | $(foreach fname,$(MAIN_PROGRAM_CPPSOURCEFILES),$(OUTOBS)/$(fname).o) 165 | 166 | BINOBS:=$(BIN_COBS) $(BIN_CPPOBS) 167 | 168 | COBS:=\ 169 | $(foreach fname,$(LIBRARY_OBJECT_CSOURCEFILES) $(SWIG_OBJECTS),$(OUTOBS)/$(fname).o) 170 | 171 | CPPOBS:=\ 172 | $(foreach fname,$(LIBRARY_OBJECT_CPPSOURCEFILES),$(OUTOBS)/$(fname).o) 173 | 174 | OBS=$(COBS) $(CPPOBS) 175 | ALL_OBS=$(OBS) $(BINOBS) 176 | DEPS:=\ 177 | $(subst $(OUTOBS),src,$(subst .o,.d,$(ALL_OBS))) 178 | 179 | # ###################################################################### 180 | # Find all the source files so that we can do dependencies properly 181 | SOURCES:=\ 182 | $(shell find . | grep -E "\.(c|cpp)\$$") 183 | 184 | # ###################################################################### 185 | # Declare the build programs 186 | ifndef GCC 187 | GCC=gcc 188 | endif 189 | ifndef GXX 190 | GXX=g++ 191 | endif 192 | ifndef LD_PROG 193 | LD_PROG=gcc 194 | endif 195 | ifndef LD_LIB 196 | LD_LIB=gcc 197 | endif 198 | 199 | # ###################################################################### 200 | # On android targets we must remove the -lpthread from the link flags 201 | # 202 | ifneq (,$(findstring android,$(LD_PROG))) 203 | REAL_EXTRA_LIB_LDFLAGS=$(subst -lpthread,,$(EXTRA_LIB_LDFLAGS)) 204 | else 205 | REAL_EXTRA_LIB_LDFLAGS=$(EXTRA_LIB_LDFLAGS) 206 | endif 207 | 208 | ifneq (,$(findstring android,$(LD_LIB))) 209 | REAL_EXTRA_PROG_LDFLAGS=$(subst -lpthread,,$(EXTRA_PROG_LDFLAGS)) 210 | else 211 | REAL_EXTRA_PROG_LDFLAGS=$(EXTRA_PROG_LDFLAGS) 212 | endif 213 | 214 | # ###################################################################### 215 | # Declare all the flags we need to compile and link 216 | BUILD_TIMESTAMP:=$(shell date +"%Y%m%d%H%M%S") 217 | CC:=$(GCC) 218 | CXX:=$(GXX) 219 | PROG_LD=$(GCC_LD_PROG) 220 | LIB_LD=$(GCC_LD_LIB) 221 | 222 | INCLUDE_DIRS:= -I.\ 223 | -I./src\ 224 | -I$(INSTALL_PREFIX)/include/$(TARGET)\ 225 | $(foreach ipath,$(INCLUDE_PATHS),-I$(ipath)) 226 | 227 | LIBDIRS:=\ 228 | $(foreach lpath,$(LIBRARY_PATHS),-L$(lpath)) 229 | 230 | LIBFILES:=\ 231 | $(foreach lfile,$(LIBRARY_FILES),-l$(lfile)) 232 | 233 | COMMONFLAGS:=\ 234 | $(EXTRA_COMPILER_FLAGS)\ 235 | -W -Wall -c -fPIC \ 236 | -DPLATFORM=$(PLATFORM) -DPLATFORM_$(PLATFORM) \ 237 | -D$(PROJNAME)_version='"$(VERSION)"'\ 238 | -DBUILD_TIMESTAMP='"$(BUILD_TIMESTAMP)"'\ 239 | $(PLATFORM_CFLAGS)\ 240 | $(INCLUDE_DIRS) 241 | 242 | CFLAGS:=$(COMMONFLAGS) $(EXTRA_CFLAGS) 243 | CXXFLAGS:=$(COMMONFLAGS) $(EXTRA_CXXFLAGS) 244 | LD:=$(GCC) 245 | LDFLAGS:= $(LIBDIRS) $(LIBFILES) -lm $(PLATFORM_LDFLAGS) 246 | AR:=ar 247 | ARFLAGS:= rcs 248 | 249 | 250 | .PHONY: help real-help show real-show debug release clean-all deps 251 | 252 | # ###################################################################### 253 | # All the conditional targets 254 | 255 | help: real-help 256 | 257 | debug: CFLAGS+= -ggdb -DDEBUG 258 | debug: CXXFLAGS+= -ggdb -DDEBUG 259 | debug: $(SWIG_WRAPPERS) 260 | debug: all 261 | 262 | release: CFLAGS+= -O3 263 | release: CXXFLAGS+= -O3 264 | release: LDFLAGS+= 265 | 266 | debug: $(SWIG_WRAPPERS) 267 | release: all 268 | 269 | # ###################################################################### 270 | # Finally, build the system 271 | 272 | real-help: 273 | @$(ECHO) "Possible targets:" 274 | @$(ECHO) "help: This message." 275 | @$(ECHO) "show: Display all the variable values that will be" 276 | @$(ECHO) " used during execution. Also 'show debug' or" 277 | @$(ECHO) " 'show release' works." 278 | @$(ECHO) "deps: Make the dependencies only." 279 | @$(ECHO) "debug: Build debug binaries." 280 | @$(ECHO) "release: Build release binaries." 281 | @$(ECHO) "clean-debug: Clean a debug build (release is ignored)." 282 | @$(ECHO) "clean-release: Clean a release build (debug is ignored)." 283 | @$(ECHO) "clean-all: Clean everything." 284 | @$(ECHO) "" 285 | @$(ECHO) "Variables that can be set in build.conf or the environment." 286 | @$(ECHO) "Defaults, if any, are displayed in parenthesis:" 287 | @$(ECHO) "GCC: The C compiler executable." 288 | @$(ECHO) "GXX: The C++ compiler executable." 289 | @$(ECHO) "LD_PROG: The program linker executable (default $$GCC)." 290 | @$(ECHO) "LD_LIB: The library linker executable (default $$GCC)." 291 | @$(ECHO) "INSTALL_PREFIX: The path to where the lib, include and bin dirs" 292 | @$(ECHO) " would be created (default ../)." 293 | 294 | 295 | real-all: $(OUTDIRS) $(DYNLIB) $(STCLIB) $(BINPROGS) 296 | @unlink ./recent || rm -rf ./recent 297 | @ln -s $(OUTDIR) ./recent 298 | 299 | all: $(SWIG_WRAPPERS) real-all 300 | @$(ECHO) "[$(CYAN)Soft linking$(NONE)] [$(STCLNK_TARGET)]" 301 | @ln -f -s $(STCLNK_TARGET) $(STCLNK_NAME) 302 | @$(ECHO) "[$(CYAN)Soft linking$(NONE)] [$(DYNLNK_TARGET)]" 303 | @ln -f -s $(DYNLNK_TARGET) $(DYNLNK_NAME) 304 | @$(ECHO) "[$(CYAN)Copying$(NONE) ] [ -> ./include/]" 305 | @cp -R $(HEADERS) include 306 | @mkdir -p $(INSTALL_PREFIX)/bin/$(TARGET) 307 | @mkdir -p $(INSTALL_PREFIX)/lib/$(TARGET) 308 | @mkdir -p $(INSTALL_PREFIX)/obs/$(TARGET) 309 | @mkdir -p $(INSTALL_PREFIX)/include/$(TARGET) 310 | @$(ECHO) "[$(CYAN)Copying$(NONE) ] [ $(OUTBIN) -> $(INSTALL_PREFIX)/bin/$(TARGET)]" 311 | @cp $(OUTBIN)/* $(INSTALL_PREFIX)/bin/$(TARGET) 312 | @$(ECHO) "[$(CYAN)Copying$(NONE) ] [ $(OUTLIB)-> $(INSTALL_PREFIX)/lib/$(TARGET)]" 313 | @cp $(OUTLIB)/* $(INSTALL_PREFIX)/lib/$(TARGET) 314 | @$(ECHO) "[$(CYAN)Copying$(NONE) ] [ $(OUTOBS) -> $(INSTALL_PREFIX)/obs/$(TARGET)]" 315 | @cp $(OUTOBS)/* $(INSTALL_PREFIX)/obs/$(TARGET) 316 | @$(ECHO) "[$(CYAN)Copying$(NONE) ] [ include -> $(INSTALL_PREFIX)/include/$(TARGET)/]" 317 | @cp -R include/* $(INSTALL_PREFIX)/include/$(TARGET) 318 | @$(ECHO) "$(INV)$(YELLOW)Build completed: `date`$(NONE)" 319 | @$(ECHO) "$(YELLOW)Total build time: $$((`date +"%s"` - $(START_TIME)))s"\ 320 | "$(NONE)" 321 | 322 | 323 | real-show: 324 | @$(ECHO) "$(GREEN)PROJNAME$(NONE) $(PROJNAME)" 325 | @$(ECHO) "$(GREEN)VERSION$(NONE) $(VERSION)" 326 | @$(ECHO) "$(GREEN)MAINTAINER$(NONE) $(MAINTAINER)" 327 | @$(ECHO) "$(GREEN)HOMEPAGE$(NONE) $(HOMEPAGE)" 328 | @$(ECHO) "$(GREEN)INSTALL_PREFIX$(NONE) $(INSTALL_PREFIX)" 329 | @$(ECHO) "$(GREEN)DESCRIPTION$(NONE) $$DESCRIPTION" 330 | @$(ECHO) "$(GREEN)TARGET-ARCH$(NONE) $(T_ARCH)" 331 | @$(ECHO) "$(GREEN)HOME$(NONE) $(HOME)" 332 | @$(ECHO) "$(GREEN)SHELL$(NONE) $(SHELL)" 333 | @$(ECHO) "$(GREEN)EXE_EXT$(NONE) $(EXE_EXT)" 334 | @$(ECHO) "$(GREEN)LIB_EXT$(NONE) $(LIB_EXT)" 335 | @$(ECHO) "$(GREEN)DYNLIB$(NONE) $(DYNLIB)" 336 | @$(ECHO) "$(GREEN)STCLIB$(NONE) $(STCLIB)" 337 | @$(ECHO) "$(GREEN)CC$(NONE) $(CC)" 338 | @$(ECHO) "$(GREEN)CXX$(NONE) $(CXX)" 339 | @$(ECHO) "$(GREEN)CFLAGS$(NONE) $(CFLAGS)" 340 | @$(ECHO) "$(GREEN)CXXFLAGS$(NONE) $(CXXFLAGS)" 341 | @$(ECHO) "$(GREEN)LD_LIB$(NONE) $(LD_LIB)" 342 | @$(ECHO) "$(GREEN)LD_PROG$(NONE) $(LD_PROG)" 343 | @$(ECHO) "$(GREEN)LDFLAGS$(NONE) $(LDFLAGS)" 344 | @$(ECHO) "$(GREEN)LIBDIRS$(NONE) $(LIBDIRS)" 345 | @$(ECHO) "$(GREEN)AR$(NONE) $(AR)" 346 | @$(ECHO) "$(GREEN)ARFLAGS$(NONE) $(ARFLAGS)" 347 | @$(ECHO) "$(GREEN)" 348 | @$(ECHO) "$(GREEN)PLATFORM$(NONE) $(PLATFORM)" 349 | @$(ECHO) "$(GREEN)TARGET$(NONE) $(TARGET)" 350 | @$(ECHO) "$(GREEN)OUTBIN$(NONE) $(OUTBIN)" 351 | @$(ECHO) "$(GREEN)OUTLIB$(NONE) $(OUTLIB)" 352 | @$(ECHO) "$(GREEN)OUTOBS$(NONE) $(OUTOBS)" 353 | @$(ECHO) "$(GREEN)SWIG_OBJECTS$(NONE) $(SWIG_OBJECTS)" 354 | @$(ECHO) "$(GREEN)OUTDIRS$(NONE) " 355 | @for X in $(OUTDIRS); do $(ECHO) " $$X"; done 356 | @$(ECHO) "$(GREEN)DEPS$(NONE) " 357 | @for X in $(DEPS); do $(ECHO) " $$X"; done 358 | @$(ECHO) "$(GREEN)HEADERS$(NONE) " 359 | @for X in $(HEADERS); do $(ECHO) " $$X"; done 360 | @$(ECHO) "$(GREEN)COBS$(NONE) " 361 | @for X in $(COBS); do $(ECHO) " $$X"; done 362 | @$(ECHO) "$(GREEN)CPPOBS$(NONE) " 363 | @for X in $(CPPOBS); do $(ECHO) " $$X"; done 364 | @$(ECHO) "$(GREEN)OBS$(NONE) " 365 | @for X in $(OBS); do $(ECHO) " $$X"; done 366 | @$(ECHO) "$(GREEN)BIN_COBS$(NONE) " 367 | @for X in $(BIN_COBS); do $(ECHO) " $$X"; done 368 | @$(ECHO) "$(GREEN)BIN_CPPOBS$(NONE) " 369 | @for X in $(BIN_CPPOBS); do $(ECHO) " $$X"; done 370 | @$(ECHO) "$(GREEN)BINOBS$(NONE) " 371 | @for X in $(BINOBS); do $(ECHO) " $$X"; done 372 | @$(ECHO) "$(GREEN)BINPROGS$(NONE) " 373 | @for X in $(BINPROGS); do $(ECHO) " $$X"; done 374 | @$(ECHO) "$(GREEN)SOURCES$(NONE) " 375 | @for X in $(SOURCES); do $(ECHO) " $$X"; done 376 | @$(ECHO) "$(GREEN)PWD$(NONE) $(PWD)" 377 | 378 | show: real-show 379 | @$(ECHO) "Only target 'show' selected, ending now." 380 | @false 381 | 382 | $(DEPS): $(HEADERS) 383 | 384 | deps: $(DEPS) 385 | 386 | debian-package: 387 | @mkdir -p $(OUTDIR)/$(PROJNAME)/usr/lib 388 | @mkdir -p $(OUTDIR)/$(PROJNAME)/usr/bin 389 | @mkdir -p $(OUTDIR)/$(PROJNAME)/DEBIAN 390 | @cp -v $(OUTLIB)/*.so $(OUTDIR)/$(PROJNAME)/usr/lib 391 | @cp -v $(OUTLIB)/*.a $(OUTDIR)/$(PROJNAME)/usr/lib 392 | @cp -v $(OUTBIN)/* $(OUTDIR)/$(PROJNAME)/usr/bin 393 | @echo 'Package: $(PROJNAME)' > $(OUTDIR)/$(PROJNAME)/DEBIAN/control 394 | @echo 'Version: $(VERSION)' >> $(OUTDIR)/$(PROJNAME)/DEBIAN/control 395 | @echo 'Section: $(SECTION)' >> $(OUTDIR)/$(PROJNAME)/DEBIAN/control 396 | @echo 'Homepage: $(HOMEPAGE)' >> $(OUTDIR)/$(PROJNAME)/DEBIAN/control 397 | @echo 'Priority: optional' >> $(OUTDIR)/$(PROJNAME)/DEBIAN/control 398 | @echo 'Architecture: $(PACKAGE_ARCH)' >> $(OUTDIR)/$(PROJNAME)/DEBIAN/control 399 | @echo 'Depends: $(DEPENDS)' >> $(OUTDIR)/$(PROJNAME)/DEBIAN/control 400 | @echo 'Essential: no' >> $(OUTDIR)/$(PROJNAME)/DEBIAN/control 401 | @echo "Installed-size: `du -bs $(OUTDIR) | cut -f 1 `" \ 402 | >> $(OUTDIR)/$(PROJNAME)/DEBIAN/control 403 | @echo 'Maintainer: $(MAINTAINER)' >> $(OUTDIR)/$(PROJNAME)/DEBIAN/control 404 | @echo "Description: $$DESCRIPTION" | sed "s/^$$/ ./g; s/^/ /g; s/^ D/D/g" \ 405 | >> $(OUTDIR)/$(PROJNAME)/DEBIAN/control 406 | @dpkg-deb --build $(OUTDIR)/$(PROJNAME) $(OUTDIR)/$(PROJNAME)-$(PACKAGE_ARCH)-$(VERSION).deb 407 | 408 | debug-package: debug debian-package 409 | release-package: release debian-package 410 | 411 | swig_prep: swig-input.swig 412 | @$(ECHO) "[$(CYAN)SWIG$(NONE) ] [ <- ./src/"'*.h]' 413 | @mkdir -p wrappers 414 | 415 | $(SWIG_WRAPPERS): swig_prep 416 | @mkdir -p wrappers/`echo $@ | cut -f 2 -d -`/swig_$(PROJNAME) 417 | @swig -package swig_$(PROJNAME) \ 418 | -o src/swig_$(PROJNAME).c \ 419 | -`echo $@ | cut -f 2 -d -`\ 420 | -outdir wrappers/`echo $@ | cut -f 2 -d -`/swig_$(PROJNAME)\ 421 | swig-input.swig 422 | 423 | src/swig_$(PROJNAME).c: $(SWIG_WRAPPERS) 424 | 425 | src/%.d: src/%.c 426 | @$(ECHO) "[$(RED)Dependency$(NONE) ] [$@]" 427 | @$(CC) $(CFLAGS) -MM -MF $@ -MT $(OUTOBS)/$*.o $< ||\ 428 | ($(ECHO) "$(INV)$(RED)[Depend failure ] [$@]$(NONE)" ; exit 127) 429 | 430 | src/%.d: src/%.cpp 431 | @$(ECHO) "[$(RED)Dependency$(NONE) ] [$@]" 432 | @$(CXX) $(CXXFLAGS) -MM -MF $@ $< ||\ 433 | ($(ECHO) "$(INV)$(RED)[Depend failure ] [$@]$(NONE)" ; exit 127) 434 | 435 | $(BIN_COBS) $(COBS): $(OUTOBS)/%.o: src/%.c src/%.d 436 | @$(ECHO) "[$(BLUE)Building$(NONE) ] [$@]" 437 | @$(CC) $(CFLAGS) -o $@ $< ||\ 438 | ($(ECHO) "$(INV)$(RED)[Compile failure] [$@]$(NONE)" ; exit 127) 439 | 440 | $(BIN_CPPOBS) $(CPPOBS): $(OUTOBS)/%.o: src/%.cpp src/%.d 441 | @$(ECHO) "[$(BLUE)Building$(NONE) ] [$@]" 442 | @$(CXX) $(CXXFLAGS) -o $@ $< ||\ 443 | ($(ECHO) "$(INV)$(RED)[Compile failure] [$@]$(NONE)" ; exit 127) 444 | 445 | $(OUTBIN)/%.exe: $(OUTOBS)/%.o $(OBS) 446 | @$(ECHO) "[$(GREEN)Linking$(NONE) ] [$@]" 447 | @$(LD_PROG) $< $(OBS) -o $@ $(LDFLAGS) $(REAL_EXTRA_PROG_LDFLAGS) ||\ 448 | ($(ECHO) "$(INV)$(RED)[Link failure] [$@]$(NONE)" ; exit 127) 449 | 450 | $(OUTBIN)/%.elf: $(OUTOBS)/%.o $(OBS) 451 | @$(ECHO) "[$(GREEN)Linking$(NONE) ] [$@]" 452 | @$(LD_PROG) $< $(OBS) -o $@ $(LDFLAGS) $(REAL_EXTRA_PROG_LDFLAGS) ||\ 453 | ($(ECHO) "$(INV)$(RED)[Link failure] [$@]$(NONE)" ; exit 127) 454 | 455 | $(DYNLIB): $(OBS) 456 | @$(ECHO) "[$(GREEN)Linking$(NONE) ] [$@]" 457 | @$(LD_LIB) -shared $^ -o $@ $(LDFLAGS) $(REAL_EXTRA_LIB_LDFLAGS) ||\ 458 | ($(ECHO) "$(INV)$(RED)[Link failure] [$@]$(NONE)" ; exit 127) 459 | 460 | $(STCLIB): $(OBS) 461 | @$(ECHO) "[$(GREEN)Linking$(NONE) ] [$@]" 462 | @$(AR) $(ARFLAGS) $@ $^ ||\ 463 | ($(ECHO) "$(INV)$(RED)[Link failure] [$@]$(NONE)" ; exit 127) 464 | 465 | $(OUTDIRS): 466 | @$(ECHO) "[$(CYAN)Creating dir$(NONE)] [$@]" 467 | @mkdir -p $@ ||\ 468 | ($(ECHO) "$(INV)$(RED)[mkdir failure] [$@]$(NONE)" ; exit 127) 469 | 470 | clean-release: 471 | @rm -rfv release wrappers 472 | 473 | clean-debug: 474 | @rm -rfv debug wrappers 475 | 476 | clean-all: clean-release clean-debug 477 | @unlink ./recent || rm -rf ./recent 478 | @rm -rfv include 479 | @rm -rfv `find . | grep "\.d$$"` 480 | 481 | clean: 482 | @$(ECHO) Choose either clean-release or clean-debug 483 | 484 | include $(DEPS) 485 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FRAME 2 | 3 | Estimated time to read this document: 10m 4 | 5 | This is a small utility that I can use from the command-line to keep 6 | track of where in the bigger picture my current efforts fit. 7 | 8 | ## Problem 9 | 10 | Too often I find myself completing some small and intricate part of my 11 | hobby/personal project, and realise that I don't remember where I stopped 12 | last in the project, which I last looked at as an overview two weeks ago. 13 | 14 | I deep-dive into something tiny and complex that takes all of my 15 | attention, and once that is done and integrated, I'm not too sure what 16 | I had planned next. 17 | 18 | ## Solution 19 | 20 | I store context as a tree of nodes. I call each node in this tree a `frame` 21 | (as in *frame of context*, *frame of mind*, or *framing the problem*, etc). At any 22 | given time only a single frame can be active. To see all the notes I made 23 | while working during that frame of context I use `frame status`. 24 | 25 | When I start a task I first do `push frame 'title of task name here'` and 26 | enter a short message, and that new frame is the current frame. When I am done I 27 | do `frame pop` and the current frame switches to the parent of the popped 28 | frame. The popped frame is discarded. 29 | 30 | All frames descend from an initial frame called `root`[^1]. 31 | 32 | 33 | ## Setup for terminal usage 34 | The first thing to do is to integrate the frame name into the shell's 35 | `PS1` variable. The command `frame current` prints the name of the current 36 | frame and the date it was changed, with the two fields separated by a colon. 37 | 38 | ```sh 39 | $ frame current 40 | root/projects/frame: Thu May 11 22:26:29 2023 41 | ``` 42 | 43 | I have the following function which is called within my `PS1` variable: 44 | 45 | ```sh 46 | frame_ps1() { 47 | frame current --quiet | cut -f 1 -d : 48 | } 49 | ``` 50 | 51 | Calling `frame_ps1` within the `PS1` variable prints out the current frame 52 | after each command, in every terminal I am logged into[^2]. 53 | 54 | ```sh 55 | [root/projects/frame]$ echo "Hello World" 56 | Hello World 57 | [root/projects/frame]$ 58 | ``` 59 | 60 | ## Example Usage 61 | Continuing with the frame `root/projects/frame`, in this case I 62 | want to create a GUI browser for this project: 63 | 64 | ```sh 65 | [root/projects/frame]$ frame push 'create GUI' --message=" 66 | Create a GUI for frame browsing 67 | " 68 | Frame 0.1.2, (© 2023 Lelanthran Manickum) 69 | Created new frame [root/projects/frame/create GUI] 70 | [root/projects/frame/create GUI]$ 71 | ``` 72 | 73 | > Had I neglected to provide a `--message` parameter then $EDITOR is opened to 74 | > allow me to enter a message, similar to how `git commit` works when no message 75 | > is specified. Each frame can store a single note of unlimited size; the 76 | > `--message` argument provides the notes when creating a new frame. 77 | 78 | The current frame's name is `create GUI`. It has a parent, `projects`, which 79 | itself has the parent `root`. 80 | 81 | ```mermaid 82 | graph TD 83 | A[root] --> B[projects]; 84 | B --> C[frame]; 85 | C --> D[create GUI]; 86 | ``` 87 | Some useful commands at this point: 88 | 89 | 1. `frame status` returns the notes (message) for this frame. 90 | 2. `frame edit` opens the editor to allow editing the notes for this frame. 91 | 92 | I have some idea of what needs to be done, namely, startup a new Lazarus 93 | project, create the GUI elements, include the `libframe.so` library, ensure 94 | that the build compiles, etc. 95 | 96 | ```sh 97 | [root/projects/frame/create GUI]$ frame append --message=" 98 | 1. Create new lazarus project 99 | 2. Add `libframe.so` to the lazarus project 100 | 3. Create GUI elements 101 | " 102 | Frame 0.1.2, (© 2023 Lelanthran Manickum) 103 | root/frame/create GUI: Sat May 13 08:20:32 2023 104 | ``` 105 | 106 | It takes a few minutes to create the Lazarus project, add the `libframe.so` 107 | file and ensure that the linker is finding and accepting the library. 108 | 109 | My next task is to create the GUI elements. This is much more involved, and 110 | so I create a new frame for it: 111 | 112 | ```sh 113 | [root/projects/frame]$ frame push 'GUI Elements' --message=" 114 | Needs to display: 115 | 1. The history, with a filter 116 | 2. A treeview of all frames from root, with the current frame selected and 117 | expanded. 118 | 3. An editor box that displays the notes for the current frame, and allows the 119 | user to edit those notes. 120 | " 121 | Frame 0.1.2, (© 2023 Lelanthran Manickum) 122 | Created new frame [root/frame/create GUI/GUI Elements] 123 | [root/projects/frame/create GUI/GUI Elements]$ 124 | ``` 125 | 126 | I do the form design (ridiculously easy in Lazarus, takes about 10m). I 127 | attempt to populate the `history` GUI element, and realise I forgot to declare 128 | the `C` functions in the library file. My Lazarus code cannot call undeclared 129 | functions even if the library is linked correctly. 130 | 131 | ```sh 132 | [root/projects/frame/create GUI/GUI Elements]$ frame push 'decls needed" --message=' 133 | Declare all the `C` functions in `libframe.so`. 134 | ' 135 | Frame 0.1.2, (© 2023 Lelanthran Manickum) 136 | Created new frame [root/frame/create GUI/GUI Elements/decls needed] 137 | ``` 138 | 139 | To make things easier, I paste the `C` header file into ChatGPT and ask for 140 | the Lazarus definitions. It provides the definitions, helpfully hallucinating 141 | a few types. and I copy the results into a Lazarus sourcefile and fix all the 142 | compilation errors. 143 | 144 | Finally, the needed declarations are in. To return to whatever I was 145 | previously doing (what was it again): 146 | ```sh 147 | [root/frame/create GUI/GUI Elements/decls needed] $ frame pop 148 | Frame 0.1.2, (© 2023 Lelanthran Manickum) 149 | [src/frm.c:55] Failed to switch to new dir [root/frame/create GUI/GUI Elements/decls needed] 150 | [src/frm.c:1440] Warning: using relative path [root/frame/create GUI/GUI Elements/decls needed] failed, trying absolute path 151 | Current frame 152 | root/frame/create GUI/GUI Elements 153 | 154 | Notes (Sat May 13 08:26:59 2023) 155 | Needs to display: 156 | 1. The history, with a filter 157 | 2. A treeview of all frames from root, with the current frame selected and 158 | expanded. 159 | 3. An editor box that displays the notes for the current frame, and allows the 160 | user to edit those notes. 161 | ``` 162 | 163 | Right, I was on the first item. Maybe create a frame for that: 164 | 165 | ```sh 166 | [root/frame/create GUI/GUI Elements] $ frame push 'populate history element' --message=' 167 | > create a populate_history() procedure 168 | > ' 169 | Frame 0.1.2, (© 2023 Lelanthran Manickum) 170 | Created new frame [root/frame/create GUI/GUI Elements/populate history element] 171 | ``` 172 | 173 | I create the named function, run it, watch in disbelief as it crashes, and 174 | then investigate the crash. Turns out there's a bug in the library. New frame 175 | time: 176 | ```sh 177 | [root/frame/create GUI/GUI Elements/populate history element] $ frame push 'debug frm_history' --message=' 178 | > frm_history, when called multiple times in the same session, has a double-free 179 | > bug. This does not show up in the c/line `frame history` command because the `frame` 180 | > program runs the specified command and then exits. 181 | > ' 182 | Frame 0.1.2, (© 2023 Lelanthran Manickum) 183 | Created new frame [root/frame/create GUI/GUI Elements/populate history element/debug frm_history] 184 | ``` 185 | 186 | I switch to working on the frame library instead. I update the test script to 187 | reproduce the error, and using valgrind, and then some gdb, I come up with an 188 | appropriate fix. 189 | 190 | ```mermaid 191 | graph TD 192 | A[root] -->B[projects]; 193 | B[projects] --> C[frame]; 194 | C --> D[create GUI]; 195 | D --> E[populate history element]; 196 | E --> F[debug frm_history]; 197 | F --> G[update tests to reproduce bugs]; 198 | G --> H[fix bug]; 199 | ``` 200 | 201 | Now that the bug in frm_history has been fixed, I perform multiple `frame pop`s, 202 | until I reach a frame that is still incomplete: `populate history element'. 203 | 204 | After each `pop`, the current frame is **deleted**, never to be seen again. 205 | This helps keep the signal/noise ratio to an acceptable number. 206 | 207 | Once I am at `populate history element`, I relink and rerun the program. It 208 | populates the history just fine. I continue my development in this manner; 209 | `push`ing new frames when I start something even slightly long, `pop`ping 210 | frames once I am done with whatever I am currently working on. 211 | 212 | When I need to record more notes or thoughts related to the current frame, I 213 | use `frame edit` and record my notes in the editor that opens. 214 | 215 | When I come 216 | back to my computer after (for example) a good nights sleep, my terminal will 217 | display the name of the current frame. If that is not sufficient to remind me 218 | where I stopped last, then `frame status` displays all my notes. 219 | 220 | Sometimes I come back to a frame after multiple days; I create a frame, store 221 | my notes, then 'push' new frames diving ever deeper into whatever it is I am 222 | doing, and then two days later I have finally `pop`ped my way back to the 223 | original frame, and thankfully I can use `frame status` to remember what it 224 | was I wanted to do next. 225 | 226 | It's all very linear, and intentionally so. You can create new frames 227 | without immediately switching to the newly created frame by using `frame new`. 228 | I do this when I know in advance that I need to do $X, $Y and $Z: then it's 229 | simpler to just do `frame create $X`, `frame create $Y` and `frame create $Z`, 230 | 231 | ## Not only linear 232 | I create multiple frames off `root`; typically one for each project. The 233 | `frame switch` command accepts a target frame, and attempts to switch to the 234 | sub-branch of that frame that you were last working on. 235 | 236 | For example, having frames 237 | 238 | ```mermaid 239 | graph TD 240 | A[root] --> B[frame]; 241 | A --> C[game]; 242 | B --> D[create GUI]; 243 | D --> E[populate history]; 244 | E --> G[debug crash]; 245 | B --> H[write docs]; 246 | C --> I[Scrabble Clone]; 247 | I --> J[implement basic user mgmt]; 248 | J --> K[email verification on signup]; 249 | I --> L[create board]; 250 | L --> M[store game state]; 251 | ``` 252 | My current frame is *[root/frame/create GUI/populate history]*. The last time I 253 | was in a sub-branch of *[root/game]*, it was in the *[email verification on 254 | signup]* sub-branch. 255 | 256 | When I do `frame switch root/game`, it returns me to the branch 257 | *[root/game/Scrabble Clone/implement basic user mgmt/ email verification on 258 | signup]*. 259 | 260 | The next time I do `frame switch root/frame`, it returns me to 261 | *[root/frame/create GUI/populate history]*. 262 | 263 | ## Command reference 264 | You can get a list of commands using `frame --help`. The important commands 265 | are 266 | 267 | 1. `push ` - push a new child frame with specified title. 268 | 2. `pop` - discard current frame and make parent current. 269 | 3. `edit` - edit the context attached to the current frame. 270 | 4. `switch <path>` - (smart) switch to a new path. 271 | 272 | 273 | [^1]: I made a conscious decision at the start of this project that `root` will 274 | always be explicit and not implicit. This avoids errors where a typo results 275 | in addressing root when the user did not mean to. For example the following 276 | error is impossible when root is explicit: `rm -rf myhomedir/Downloads /lib`. 277 | 278 | [^2]: In reality, my `PS1` variable is a lot more complex than this, as it 279 | includes the git branch (if any) and other information (current directory, 280 | username, etc). My prompt uses a separate line for the `frame` name, as this 281 | name can get quite long. 282 | -------------------------------------------------------------------------------- /android-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Build for android targets 4 | # 5 | 6 | # Use pretty colors for output 7 | export FG_RESET="\033[39m" 8 | export BG_RESET="\033[49m" 9 | export RESET="${FG_RESET}${BG_RESET}" 10 | export FG_BLACK="\033[30m" 11 | export FG_RED="\033[31m" 12 | export FG_GREEN="\033[32m" 13 | export FG_YELLOW="\033[33m" 14 | export FG_BLUE="\033[34m" 15 | export FG_MAGENTA="\033[35m" 16 | export FG_CYAN="\033[36m" 17 | export FG_GREY90="\033[37m" 18 | export BG_BLACK="\033[40m" 19 | export BG_RED="\033[41m" 20 | export BG_GREEN="\033[42m" 21 | export BG_YELLOW="\033[43m" 22 | export BG_BLUE="\033[44m" 23 | export BG_MAGENTA="\033[45m" 24 | export BG_CYAN="\033[46m" 25 | export BG_GREY90="\033[47m" 26 | export BG_GREY50="\033[100m" 27 | export BG_LIGHTRED="\033[101m" 28 | export BG_LIGHTGREEN="\033[102m" 29 | export BG_LIGHTYELLOW="\033[103m" 30 | export BG_LIGHTBLUE="\033[104m" 31 | export BG_PURPLE="\033[105m" 32 | export BG_TEAL="\033[106m" 33 | export BG_WHITE="\033[107m" 34 | 35 | # Quick exit with a message 36 | function die () { 37 | printf "${FG_RED}$@${RESET}\n" 38 | printf "${BG_RED}${FG_BLACK}Aborting ... ${RESET}\n" 39 | exit 127 40 | } 41 | 42 | # Print the variables we are using to locate the GCC we want. 43 | function print_kv () { 44 | export size_name=`echo -ne $1 | wc -c` 45 | export nspaces=$((20 - $size_name)) 46 | printf "${FG_BLUE}${1}${RESET}" 47 | printf "%${nspaces}s" " " 48 | printf "[${FG_GREEN}${2}${RESET}]\n" 49 | } 50 | 51 | # Read the user selections. Whatever is set on the command-line overrides the 52 | # environment variables of the same name. 53 | # 54 | 55 | function highlight () { 56 | echo -ne "${BG_GREY90}${FG_GREEN}${1}${RESET}" 57 | } 58 | 59 | function print_help_message () { 60 | export HELP_MSG=" 61 | android-build.sh [--android-ndk-path=<path>] 62 | [--target-machine=<machine>] 63 | [--android-level=<number>] 64 | <make targets> 65 | 66 | Build for a specified android target architecture. At least one make 67 | target must be specified. Multiple make targets separated by spaces can 68 | be specified. 69 | 70 | Options are set from files, the environment and the command-line. Any 71 | option set later overrides in options that were already set. Options are 72 | set in the following order: 73 | 74 | 1. From the system android-build.conf file ("`highlight /etc/android-build.conf`" 75 | or "`highlight /c/Windows/android-build.conf`" on Windows). 76 | 77 | 2. From the file "`highlight .android-build.conf`" file in the users home directory 78 | (even on Windows): "`highlight \\$HOME/.android-build.conf`" 79 | 80 | 3. From the file '.android-build.conf' in the current directory: 81 | "`highlight \\$PWD/.android-build.conf`" 82 | 83 | 4. From the environment variables (the description of each option below 84 | specifies which variable is used for each option). 85 | 86 | 5. From the command-line, as described below. 87 | 88 | Command-line options override the environment variables, which override 89 | options set in the "`highlight \\$PWD/.android-build.conf`", which overrides 90 | "`highlight \\$HOME/.android-build.conf`" which overrides "`highlight \\$SYSROOT/android-build.conf`". 91 | 92 | --android-ndk-path=<path> 93 | Specify a path. A path must be specified. If no path is specified the 94 | environment variable "`highlight ANDROID_NDK_PATH`" is used. 95 | 96 | --target-machine=<machine> 97 | Specify the machine to build for. The name "`highlight all`" will build for all 98 | machines found in the Android NDK. At least one machine or the word 'all' 99 | must be specified. Multiple target machines may be specified using spaces 100 | to separate the machine names. If a target machine is not specified the 101 | environment variable "`highlight TARGET_MACHINE`" is used. 102 | 103 | --android-level=<number> 104 | Specify the android level to build for. Multiple android levels can be 105 | specified, seperated with a space. If a level is not specified the 106 | environment variable "`highlight ANDROID_LEVEL`" is used. 107 | " 108 | printf "$HELP_MSG\n" 109 | 110 | exit 0 111 | } 112 | 113 | export HOST_MACHINE="`uname -m`" 114 | export HOST_OS="`uname -o`" 115 | export TARGET_OS=linux 116 | export SYSROOT_CONF=/etc/android-build.conf 117 | 118 | # TODO: Double-check this on Windows. 119 | if [ "$HOST_OS" == "msys" ]; then 120 | export SYSROOT_CONF=/c/Windows/android-build.conf 121 | fi 122 | 123 | function source_file () { 124 | if [ -f "$1" ]; then 125 | printf "Reading variables from ${FG_MAGENTA}$1${RESET}\n" 126 | . "$1" 127 | fi 128 | } 129 | 130 | source_file "$SYSROOT_CONF" 131 | source_file "$HOME/.android-build.conf" 132 | source_file "$PWD/.android-build.conf" 133 | 134 | function get_cline_value () { 135 | echo $1 | cut -f 2 -d = 136 | } 137 | 138 | while [ ! -z "$1" ]; do 139 | case $1 in 140 | 141 | --help*) print_help_message 142 | ;; 143 | 144 | --android-ndk-path=*) export ANDROID_NDK_PATH=`get_cline_value $1` 145 | ;; 146 | 147 | --target-machine=*) export TARGET_MACHINE=`get_cline_value "$1"` 148 | 149 | ;; 150 | 151 | --android-level=*) export ANDROID_LEVEL=`get_cline_value "$1"` 152 | ;; 153 | 154 | *) export MAKE_TARGETS="$MAKE_TARGETS $1" 155 | ;; 156 | 157 | esac 158 | shift 1 159 | done 160 | 161 | [ -z "${MAKE_TARGETS}" ] && die "No make targets specified." 162 | [ -z "${ANDROID_NDK_PATH}" ] && die 'Value $ANDROID_NDK_PATH not set. Try --help.' 163 | [ -z "${TARGET_MACHINE}" ] && die 'Value $TARGET_MACHINE not set. Try --help.' 164 | [ -z "${ANDROID_LEVEL}" ] && die 'Value $ANDROID_LEVEL not set. Try --help.' 165 | 166 | # TODO: Add in the one for Windows here 167 | case "$HOST_OS" in 168 | 169 | GNU/Linux) export HOST_OS=linux 170 | ;; 171 | 172 | 173 | *) die "Unknown OS - $HOST_OS" 174 | ;; 175 | esac 176 | 177 | export HOST_BUILD_TOOLS="$ANDROID_NDK_PATH/toolchains/llvm/prebuilt/$HOST_OS-$HOST_MACHINE" 178 | 179 | if [ "$TARGET_MACHINE" == "all" ]; then 180 | export INSTALLED_ARCHES="`ls -1 $HOST_BUILD_TOOLS/bin/*-android*-clang* | sed \"s:$HOST_BUILD_TOOLS/bin/::g\" | cut -f 1 -d - | sort -u`" 181 | echo "Found the following arches: $INSTALLED_ARCHS" 182 | export TARGET_MACHINE=$INSTALLED_ARCHES 183 | fi 184 | 185 | for X in $TARGET_MACHINE; do 186 | export TARGET_EABI=android 187 | if [ `echo $X | grep -c arm` -gt 0 ]; then 188 | export TARGET_EABI=androideabi 189 | echo "TARGET_EABI $TARGET_EABI" 190 | fi 191 | 192 | for Y in $ANDROID_LEVEL; do 193 | export ANDROID_GCC="$HOST_BUILD_TOOLS/bin/${X}-${TARGET_OS}-${TARGET_EABI}${Y}-clang" 194 | export ANDROID_GXX="$HOST_BUILD_TOOLS/bin/${X}-${TARGET_OS}-${TARGET_EABI}${Y}-clang++" 195 | 196 | print_kv MAKE_TARGETS ${MAKE_TARGETS} 197 | print_kv ANDROID_NDK_PATH ${ANDROID_NDK_PATH} 198 | print_kv ANDROID_GCC ${ANDROID_GCC} 199 | print_kv HOST_MACHINE ${HOST_MACHINE} 200 | print_kv HOST_OS ${HOST_OS} 201 | print_kv TARGET_MACHINE ${X} 202 | print_kv ANDROID_LEVEL ${Y} 203 | 204 | $ANDROID_GCC --version &> /dev/null || die "Error executing android compiler [${BG_RED}${FG_BLACK}$ANDROID_GCC${RESET}]" 205 | 206 | export GCC=$ANDROID_GCC 207 | export GXX=$ANDROID_GXX 208 | export LD_PROG=$ANDROID_GCC 209 | export LD_LIB=$ANDROID_GCC 210 | 211 | for Z in $MAKE_TARGETS; do 212 | make $Z -j 4 213 | done 214 | done 215 | done 216 | -------------------------------------------------------------------------------- /build.config: -------------------------------------------------------------------------------- 1 | # ###################################################################### 2 | # First, set your project name and version here. 3 | PROJNAME=frame 4 | VERSION=0.1.4 5 | 6 | # ###################################################################### 7 | # These are used to create packages. 8 | 9 | # First, the maintainer and project details 10 | MAINTAINER=lelanthran <lelanthran@gmail.com> 11 | HOMEPAGE=http://www.lelanthran.com 12 | 13 | # The section that this software belongs in 14 | SECTION=custom 15 | 16 | # The dependencies for this software to run (not build). The dependencies for 17 | # building are specified below in the EXTRA_LIBS section (and entries for build 18 | # dependencies have to be in the HEADERS field). 19 | # 20 | # The dependencies must be specified as a comma-seperated list of package names, 21 | # with an optional version of that package in parenthesis. 22 | DEPENDS= 23 | 24 | # Define a description that will go into the package. 25 | # 26 | define DESCRIPTION 27 | Frame: a context-switch manager for humans. 28 | 29 | Humans are notoriously poor at context-switching between multiple 30 | tasks. Tasks, which may recursively have subtasks, can easily get 31 | forgotten, misremembered or partially completed. 32 | 33 | Frame implements a tree of nodes, in which each node represents some 34 | context. The user can create new nodes to represent a new context, edit 35 | the content of that context, and eventually delete it when done. 36 | 37 | endef 38 | 39 | # ###################################################################### 40 | # Set the main (executable) source files. These are all the source files 41 | # that have a 'main' function. Note that you must specify the filename 42 | # without the extension, so don't specify 'myfile.c', just specify 43 | # 'myfile'. 44 | # 45 | # Note that this list is only for C files. 46 | MAIN_PROGRAM_CSOURCEFILES=\ 47 | frame 48 | 49 | # ###################################################################### 50 | # Set the main (executable) source files. These are all the source files 51 | # that have a 'main' function. Note that you must specify the filename 52 | # without the extension, so don't specify 'myfile.c', just specify 53 | # 'myfile'. 54 | # 55 | # Note that this list is only for C++ files. 56 | MAIN_PROGRAM_CPPSOURCEFILES=\ 57 | 58 | 59 | # ###################################################################### 60 | # Set each of the source files that must be built. These are all those 61 | # source files (both .c and .cpp) that *DON'T* have a main function. All 62 | # of these files will be compiled into a single library (sorry, I do not 63 | # have plans to allow multiple library files to be built). 64 | # 65 | # Note once again that you must not specify the file extension. 66 | # Unfortunately you are not allowed to have two object files that have the 67 | # same name save for the extension. For example, you cannot have 'myfile.c' 68 | # and 'myfile.cpp' in the same project, although it is allowed 69 | # (encouraged even) to have either 'myfile.c' or 'myfile.cpp' together 70 | # with 'myfile.h'. 71 | # 72 | # Note that this list is only for C files. 73 | LIBRARY_OBJECT_CSOURCEFILES=\ 74 | frm\ 75 | ds_str\ 76 | ds_array 77 | 78 | # ###################################################################### 79 | # Set each of the source files that must be built. These are all those 80 | # source files (both .c and .cpp) that *DON'T* have a main function. All 81 | # of these files will be compiled into a single library (sorry, I do not 82 | # have plans to allow multiple library files to be built). 83 | # 84 | # Note once again that you must not specify the file extension. 85 | # Unfortunately you are not allowed to have two object files that have the 86 | # same name save for the extension. For example, you cannot have 'myfile.c' 87 | # and 'myfile.cpp' in the same project, although it is allowed 88 | # (encouraged even) to have either 'myfile.c' or 'myfile.cpp' together 89 | # with 'myfile.h'. 90 | # 91 | # Note that this list is only for C++ files. 92 | LIBRARY_OBJECT_CPPSOURCEFILES=\ 93 | 94 | 95 | 96 | # ###################################################################### 97 | # For now we set the headers manually. In the future I plan to use gcc to 98 | # generate the dependencies that can be included in this file. Simply name 99 | # all the header files you wrote for this project. Note that unlike the 100 | # previous settings, for this setting you must specify the path to the 101 | # headers (relative to this directory). 102 | HEADERS=\ 103 | src/frm.h\ 104 | src/ds_str.h\ 105 | src/ds_array.h\ 106 | 107 | 108 | # ###################################################################### 109 | # Here you must set the list of include paths. Note that the variable 110 | # $(HOME) is available if you have include directories relative to your 111 | # home directory. $(HOME) works correctly on Windows as well. 112 | # 113 | # You can put as many paths in here as you want to. I've put one in as an 114 | # example (to my home directory include) and one to the openjdk on my system 115 | # for jni functionality. 116 | INCLUDE_PATHS=\ 117 | $(HOME)/include\ 118 | /usr/lib/jvm/java-11-openjdk-amd64/include\ 119 | /usr/lib/jvm/java-11-openjdk-amd64/include/linux 120 | 121 | # ###################################################################### 122 | # This is similar to the INCLUDE_PATHS you set above, except that it is 123 | # for the library search paths. $(HOME) is available if you have library 124 | # directories relative to your home directory. $(HOME) works correctly 125 | # on Windows as well. 126 | # 127 | # You can put as many paths in here as you want to. I've put one in as an 128 | # example. See the variable LIBRARY_FILES to set the actual libraries you 129 | # want to link in. 130 | LIBRARY_PATHS=\ 131 | $(INSTALL_PREFIX)/lib/$(TARGET) 132 | 133 | 134 | # ###################################################################### 135 | # This is for specifying extra libraries. Note that you must only specify 136 | # the library name, and neither the extension nor the prefix 'lib'. 137 | # 138 | # These files *MUST* be in the library search path specified with 139 | # LIBRARY_PATHS. 140 | # 141 | # I've put in an example here that is commented out, so that you can see 142 | # how the files are supposed to be specified but, because it is commented 143 | # out, it will not break the build process if this library is not 144 | # installed. 145 | # 146 | # (If it is not commented out, go ahead and comment it out when the build 147 | # fails) 148 | #LIBRARY_FILES=\ 149 | # ds 150 | 151 | 152 | # ###################################################################### 153 | # Here you set extra compiler flags that are common to both the C++ and 154 | # the C compiler. You can comment this line out with no ill-effects. 155 | # 156 | # Note that this does not override the existing flags, it only adds to 157 | # them 158 | EXTRA_COMPILER_FLAGS=\ 159 | -W -Wall\ 160 | 161 | 162 | 163 | # ###################################################################### 164 | # Here you set extra compiler flags for the C compiler only. You can comment 165 | # this line out with no ill-effects. 166 | # 167 | # Note that this does not override the existing flags, it only adds to 168 | # them 169 | EXTRA_CFLAGS=\ 170 | -std=gnu99 171 | 172 | 173 | # ###################################################################### 174 | # Here you set extra compiler flags for the C++ compiler only. You can 175 | # comment this line out with no ill-effects. 176 | # 177 | # Note that this does not override the existing flags, it only adds to 178 | # them 179 | EXTRA_CXXFLAGS=\ 180 | -std=c++11\ 181 | 182 | 183 | # ###################################################################### 184 | # You can add in extra flags to the linker here, for the library. This 185 | # does not override the existing flags, it adds to them. 186 | # 187 | EXTRA_LIB_LDFLAGS=\ 188 | 189 | 190 | 191 | # ###################################################################### 192 | # You can add in extra flags to the linker here, for the programs. This 193 | # does not override the existing flags, it adds to them. 194 | # 195 | EXTRA_PROG_LDFLAGS=\ 196 | 197 | 198 | # ###################################################################### 199 | # The default compilers are gcc and g++. If you want to specify something 200 | # different, this is the place to do it. This is useful if you want to 201 | # cross-compile, or use a different gcc/g++ than the one in your path, or 202 | # simply want to use clang instead. 203 | # 204 | # Note that only clang and gcc are supported (due to reliance on the 205 | # compiler command-line options). 206 | # 207 | # You can comment this out with no ill-effects. 208 | GCC?=gcc 209 | GXX?=g++ 210 | LD_PROG?=gcc 211 | LD_LIB?=gcc 212 | 213 | # ###################################################################### 214 | # If you need need to call your library from other languages, set the 215 | # SWIG targets here, using 'wrap-$language' as a target. The SWIG_WRAPPERS 216 | # can be left empty if this is not needed. 217 | # 218 | # Note that to use SWIG: 219 | # 1. It must be installed, 220 | # 2. The swig-input.swig file must be modified for your project. 221 | # 222 | # An example of basic SWIG usage can be found at: 223 | # http://swig.org/tutorial.html 224 | # 225 | #SWIG_WRAPPERS:=\ 226 | # wrap-java 227 | 228 | 229 | # ###################################################################### 230 | # TODO: Add in a way to override the default linker 231 | # 232 | 233 | 234 | -------------------------------------------------------------------------------- /docs/FrameIntroduction.odp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lelanthran/frame/f1197928963dca50fc4c61b85091d9166b3385b9/docs/FrameIntroduction.odp -------------------------------------------------------------------------------- /frame-browser/FrameBrowser.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lelanthran/frame/f1197928963dca50fc4c61b85091d9166b3385b9/frame-browser/FrameBrowser.ico -------------------------------------------------------------------------------- /frame-browser/FrameBrowser.lpi: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <CONFIG> 3 | <ProjectOptions> 4 | <Version Value="12"/> 5 | <General> 6 | <SessionStorage Value="InProjectDir"/> 7 | <Title Value="FrameBrowser"/> 8 | <Scaled Value="True"/> 9 | <ResourceType Value="res"/> 10 | <UseXPManifest Value="True"/> 11 | <XPManifest> 12 | <DpiAware Value="True"/> 13 | </XPManifest> 14 | <Icon Value="0"/> 15 | </General> 16 | <BuildModes> 17 | <Item Name="Default" Default="True"/> 18 | </BuildModes> 19 | <PublishOptions> 20 | <Version Value="2"/> 21 | <UseFileFilters Value="True"/> 22 | </PublishOptions> 23 | <RunParams> 24 | <FormatVersion Value="2"/> 25 | </RunParams> 26 | <RequiredPackages> 27 | <Item> 28 | <PackageName Value="LazControls"/> 29 | </Item> 30 | <Item> 31 | <PackageName Value="LCL"/> 32 | </Item> 33 | </RequiredPackages> 34 | <Units> 35 | <Unit> 36 | <Filename Value="FrameBrowser.lpr"/> 37 | <IsPartOfProject Value="True"/> 38 | </Unit> 39 | <Unit> 40 | <Filename Value="mainwin.pas"/> 41 | <IsPartOfProject Value="True"/> 42 | <ComponentName Value="frmMain"/> 43 | <HasResources Value="True"/> 44 | <ResourceBaseClass Value="Form"/> 45 | <UnitName Value="mainWin"/> 46 | </Unit> 47 | <Unit> 48 | <Filename Value="framewrapper.pas"/> 49 | <IsPartOfProject Value="True"/> 50 | <UnitName Value="FrameWrapper"/> 51 | </Unit> 52 | </Units> 53 | </ProjectOptions> 54 | <CompilerOptions> 55 | <Version Value="11"/> 56 | <Target> 57 | <Filename Value="FrameBrowser"/> 58 | </Target> 59 | <SearchPaths> 60 | <IncludeFiles Value="$(ProjOutDir)"/> 61 | <Libraries Value="../recent/lib/x86_64-linux-gnu"/> 62 | <OtherUnitFiles Value="../debug/lib/x86_64-linux-gnu"/> 63 | <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/> 64 | </SearchPaths> 65 | <CodeGeneration> 66 | <Optimizations> 67 | <OptimizationLevel Value="0"/> 68 | </Optimizations> 69 | </CodeGeneration> 70 | <Linking> 71 | <Debugging> 72 | <DebugInfoType Value="dsDwarf2"/> 73 | <TrashVariables Value="True"/> 74 | <UseValgrind Value="True"/> 75 | </Debugging> 76 | <Options> 77 | <LinkerOptions Value="lasan"/> 78 | </Options> 79 | </Linking> 80 | </CompilerOptions> 81 | <Debugging> 82 | <Exceptions> 83 | <Item> 84 | <Name Value="EAbort"/> 85 | </Item> 86 | <Item> 87 | <Name Value="ECodetoolError"/> 88 | </Item> 89 | <Item> 90 | <Name Value="EFOpenError"/> 91 | </Item> 92 | </Exceptions> 93 | </Debugging> 94 | </CONFIG> 95 | -------------------------------------------------------------------------------- /frame-browser/FrameBrowser.lpr: -------------------------------------------------------------------------------- 1 | program FrameBrowser; 2 | 3 | {$mode objfpc}{$H+} 4 | 5 | uses 6 | {$IFDEF UNIX} 7 | cthreads, 8 | {$ENDIF} 9 | {$IFDEF HASAMIGA} 10 | athreads, 11 | {$ENDIF} 12 | Interfaces, // this includes the LCL widgetset 13 | Forms, lazcontrols, mainWin, FrameWrapper; 14 | 15 | {$R *.res} 16 | 17 | begin 18 | RequireDerivedFormResource:=True; 19 | Application.Scaled:=True; 20 | Application.Initialize; 21 | Application.CreateForm(TfrmMain, frmMain); 22 | Application.Run; 23 | frm_close(frame_var); 24 | end. 25 | 26 | -------------------------------------------------------------------------------- /frame-browser/FrameBrowser.lps: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <CONFIG> 3 | <ProjectSession> 4 | <Version Value="12"/> 5 | <BuildModes Active="Default"/> 6 | <Units> 7 | <Unit> 8 | <Filename Value="FrameBrowser.lpr"/> 9 | <IsPartOfProject Value="True"/> 10 | <EditorIndex Value="2"/> 11 | <CursorPos Y="18"/> 12 | <UsageCount Value="174"/> 13 | <Loaded Value="True"/> 14 | </Unit> 15 | <Unit> 16 | <Filename Value="mainwin.pas"/> 17 | <IsPartOfProject Value="True"/> 18 | <ComponentName Value="frmMain"/> 19 | <HasResources Value="True"/> 20 | <ResourceBaseClass Value="Form"/> 21 | <UnitName Value="mainWin"/> 22 | <IsVisibleTab Value="True"/> 23 | <TopLine Value="42"/> 24 | <CursorPos X="63" Y="58"/> 25 | <UsageCount Value="174"/> 26 | <Loaded Value="True"/> 27 | <LoadedDesigner Value="True"/> 28 | </Unit> 29 | <Unit> 30 | <Filename Value="framewrapper.pas"/> 31 | <IsPartOfProject Value="True"/> 32 | <UnitName Value="FrameWrapper"/> 33 | <EditorIndex Value="1"/> 34 | <TopLine Value="39"/> 35 | <CursorPos X="26" Y="39"/> 36 | <UsageCount Value="174"/> 37 | <Loaded Value="True"/> 38 | </Unit> 39 | <Unit> 40 | <Filename Value="/usr/share/fpcsrc/3.2.2/rtl/objpas/types.pp"/> 41 | <UnitName Value="Types"/> 42 | <EditorIndex Value="-1"/> 43 | <TopLine Value="22"/> 44 | <CursorPos X="19" Y="56"/> 45 | <UsageCount Value="8"/> 46 | </Unit> 47 | <Unit> 48 | <Filename Value="/usr/share/fpcsrc/3.2.2/packages/rtl-objpas/src/inc/strutils.pp"/> 49 | <UnitName Value="StrUtils"/> 50 | <EditorIndex Value="-1"/> 51 | <TopLine Value="1349"/> 52 | <CursorPos X="21" Y="1377"/> 53 | <UsageCount Value="8"/> 54 | </Unit> 55 | <Unit> 56 | <Filename Value="/usr/share/fpcsrc/3.2.2/rtl/objpas/sysutils/syshelph.inc"/> 57 | <EditorIndex Value="-1"/> 58 | <TopLine Value="148"/> 59 | <CursorPos X="14" Y="171"/> 60 | <UsageCount Value="8"/> 61 | </Unit> 62 | <Unit> 63 | <Filename Value="/usr/share/lazarus/2.2.6/lcl/include/treeview.inc"/> 64 | <EditorIndex Value="-1"/> 65 | <TopLine Value="6193"/> 66 | <CursorPos Y="6214"/> 67 | <UsageCount Value="1"/> 68 | </Unit> 69 | <Unit> 70 | <Filename Value="util.pas"/> 71 | <UnitName Value="Util"/> 72 | <EditorIndex Value="-1"/> 73 | <CursorPos Y="11"/> 74 | <UsageCount Value="13"/> 75 | </Unit> 76 | <Unit> 77 | <Filename Value="/usr/share/lazarus/2.2.6/lcl/lcltype.pp"/> 78 | <UnitName Value="LCLType"/> 79 | <EditorIndex Value="-1"/> 80 | <TopLine Value="637"/> 81 | <CursorPos X="3" Y="654"/> 82 | <UsageCount Value="8"/> 83 | </Unit> 84 | <Unit> 85 | <Filename Value="/usr/share/fpcsrc/3.2.2/rtl/linux/system.pp"/> 86 | <UnitName Value="System"/> 87 | <EditorIndex Value="-1"/> 88 | <TopLine Value="22"/> 89 | <CursorPos Y="22"/> 90 | <UsageCount Value="8"/> 91 | </Unit> 92 | </Units> 93 | <JumpHistory HistoryIndex="29"> 94 | <Position> 95 | <Filename Value="mainwin.pas"/> 96 | <Caret Line="97" TopLine="90"/> 97 | </Position> 98 | <Position> 99 | <Filename Value="mainwin.pas"/> 100 | <Caret Line="112" TopLine="90"/> 101 | </Position> 102 | <Position> 103 | <Filename Value="mainwin.pas"/> 104 | <Caret Line="189" TopLine="172"/> 105 | </Position> 106 | <Position> 107 | <Filename Value="mainwin.pas"/> 108 | <Caret Line="260" Column="32" TopLine="234"/> 109 | </Position> 110 | <Position> 111 | <Filename Value="mainwin.pas"/> 112 | <Caret Line="57" TopLine="39"/> 113 | </Position> 114 | <Position> 115 | <Filename Value="mainwin.pas"/> 116 | <Caret Line="240" Column="39" TopLine="234"/> 117 | </Position> 118 | <Position> 119 | <Filename Value="mainwin.pas"/> 120 | <Caret Line="238" Column="11" TopLine="221"/> 121 | </Position> 122 | <Position> 123 | <Filename Value="mainwin.pas"/> 124 | <Caret Line="245" Column="38" TopLine="221"/> 125 | </Position> 126 | <Position> 127 | <Filename Value="mainwin.pas"/> 128 | <Caret Line="243" Column="19" TopLine="226"/> 129 | </Position> 130 | <Position> 131 | <Filename Value="mainwin.pas"/> 132 | <Caret Line="244" Column="19" TopLine="227"/> 133 | </Position> 134 | <Position> 135 | <Filename Value="mainwin.pas"/> 136 | <Caret Line="245" Column="19" TopLine="228"/> 137 | </Position> 138 | <Position> 139 | <Filename Value="mainwin.pas"/> 140 | <Caret Line="251" Column="19" TopLine="234"/> 141 | </Position> 142 | <Position> 143 | <Filename Value="mainwin.pas"/> 144 | <Caret Line="223" Column="10" TopLine="202"/> 145 | </Position> 146 | <Position> 147 | <Filename Value="mainwin.pas"/> 148 | <Caret Line="174" Column="3" TopLine="170"/> 149 | </Position> 150 | <Position> 151 | <Filename Value="mainwin.pas"/> 152 | <Caret Column="40"/> 153 | </Position> 154 | <Position> 155 | <Filename Value="mainwin.pas"/> 156 | <Caret Line="102" Column="33" TopLine="84"/> 157 | </Position> 158 | <Position> 159 | <Filename Value="mainwin.pas"/> 160 | <Caret Line="117" Column="41" TopLine="97"/> 161 | </Position> 162 | <Position> 163 | <Filename Value="mainwin.pas"/> 164 | <Caret Line="127" Column="44" TopLine="107"/> 165 | </Position> 166 | <Position> 167 | <Filename Value="mainwin.pas"/> 168 | <Caret Line="100" Column="7" TopLine="92"/> 169 | </Position> 170 | <Position> 171 | <Filename Value="mainwin.pas"/> 172 | <Caret Line="105" Column="19" TopLine="92"/> 173 | </Position> 174 | <Position> 175 | <Filename Value="mainwin.pas"/> 176 | <Caret Line="118" Column="32" TopLine="98"/> 177 | </Position> 178 | <Position> 179 | <Filename Value="mainwin.pas"/> 180 | <Caret Line="128" Column="36" TopLine="108"/> 181 | </Position> 182 | <Position> 183 | <Filename Value="mainwin.pas"/> 184 | <Caret Line="83" Column="32" TopLine="71"/> 185 | </Position> 186 | <Position> 187 | <Filename Value="mainwin.pas"/> 188 | <Caret Line="100" Column="19" TopLine="80"/> 189 | </Position> 190 | <Position> 191 | <Filename Value="mainwin.pas"/> 192 | <Caret Line="101" Column="19" TopLine="80"/> 193 | </Position> 194 | <Position> 195 | <Filename Value="mainwin.pas"/> 196 | <Caret Line="117" Column="32" TopLine="96"/> 197 | </Position> 198 | <Position> 199 | <Filename Value="mainwin.pas"/> 200 | <Caret Line="126" Column="12" TopLine="106"/> 201 | </Position> 202 | <Position> 203 | <Filename Value="mainwin.pas"/> 204 | <Caret Line="100" Column="18" TopLine="88"/> 205 | </Position> 206 | <Position> 207 | <Filename Value="mainwin.pas"/> 208 | <Caret Line="116" Column="32" TopLine="96"/> 209 | </Position> 210 | <Position> 211 | <Filename Value="mainwin.pas"/> 212 | <Caret Line="126" Column="15" TopLine="106"/> 213 | </Position> 214 | </JumpHistory> 215 | <RunParams> 216 | <FormatVersion Value="2"/> 217 | <Modes ActiveMode=""/> 218 | </RunParams> 219 | </ProjectSession> 220 | <Debugging> 221 | <Watches> 222 | <Item ClassAutoCast="True"> 223 | <Expression Value="frame_var"/> 224 | <DisplayStyle Value="wdfChar"/> 225 | </Item> 226 | <Item> 227 | <Expression Value="tmp"/> 228 | </Item> 229 | </Watches> 230 | </Debugging> 231 | </CONFIG> 232 | -------------------------------------------------------------------------------- /frame-browser/FrameBrowser.res: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lelanthran/frame/f1197928963dca50fc4c61b85091d9166b3385b9/frame-browser/FrameBrowser.res -------------------------------------------------------------------------------- /frame-browser/framewrapper.pas: -------------------------------------------------------------------------------- 1 | unit FrameWrapper; 2 | 3 | {$mode ObjFPC}{$H+} 4 | 5 | interface 6 | uses 7 | Classes, SysUtils, Ctypes, ComCtrls, Types, StrUtils, 8 | StdCtrls; 9 | 10 | type 11 | va_list = pointer; 12 | 13 | frm_t = pointer; 14 | frm_node_t = Pointer; 15 | 16 | var frame_var: frm_t; 17 | 18 | 19 | function frm_create(dbpath: PAnsiChar): frm_t; cdecl; external 'frame'; 20 | function frm_init(dbpath: PAnsiChar): frm_t; cdecl; external 'frame'; 21 | procedure frm_close(frm: frm_t); cdecl; external 'frame'; 22 | 23 | function frm_history(frm: frm_t; count: csize_t): PAnsiChar; cdecl; external 'frame'; 24 | function frm_current(frm: frm_t): PAnsiChar; cdecl; external 'frame'; 25 | function frm_payload: PAnsiChar; cdecl; external 'frame'; 26 | function frm_date_epoch: UInt64; cdecl; external 'frame'; 27 | function frm_date_str: PAnsiChar; cdecl; external 'frame'; 28 | function frm_lastmsg(frm: frm_t): PAnsiChar; cdecl; external 'frame'; 29 | 30 | function frm_new(frm: frm_t; name, message: PAnsiChar): LongBool; cdecl; external 'frame'; 31 | function frm_push(frm: frm_t; name, message: PAnsiChar): LongBool; cdecl; external 'frame'; 32 | function frm_payload_replace(message: PAnsiChar): LongBool; cdecl; external 'frame'; 33 | function frm_payload_append(message: PAnsiChar): LongBool; cdecl; external 'frame'; 34 | function frm_payload_fname: PAnsiChar; cdecl; external 'frame'; 35 | 36 | function frm_top(frm: frm_t): LongBool; cdecl; external 'frame'; 37 | function frm_up(frm: frm_t): LongBool; cdecl; external 'frame'; 38 | function frm_down(frm: frm_t; target: PAnsiChar): LongBool; cdecl; external 'frame'; 39 | function frm_switch(frm: frm_t; target: PAnsiChar): LongBool; cdecl; external 'frame'; 40 | function frm_switch_direct(frm: frm_t; target: PAnsiChar): LongBool; cdecl; external 'frame'; 41 | function frm_back(frm: frm_t; index: csize_t): LongBool; cdecl; external 'frame'; 42 | function frm_delete(frm: frm_t; target: PAnsiChar): LongBool; cdecl; external 'frame'; 43 | function frm_pop(frm: frm_t; force: LongBool): LongBool; cdecl; external 'frame'; 44 | function frm_rename(frm: frm_t; newname: PAnsiChar): LongBool; cdecl; external 'frame'; 45 | 46 | function frm_list(frm: frm_t; from: PAnsiChar): PPAnsiChar; cdecl; external 'frame'; 47 | function frm_match(frm: frm_t; sterm: PAnsiChar; flags: LongWord): PPAnsiChar; cdecl; external 'frame'; 48 | function frm_match_from_root(frm: frm_t; sterm: PAnsiChar; flags: LongWord): PPAnsiChar; cdecl; external 'frame'; 49 | 50 | procedure frm_mem_free(ptr: Pointer); cdecl; external 'frame'; 51 | procedure frm_strarray_free(array_: PPAnsiChar); cdecl; external 'frame'; 52 | 53 | function frm_readfile(fname: PAnsiChar): PAnsiChar; cdecl; external 'frame'; 54 | function frm_vwritefile(fname: PAnsiChar; data: PAnsiChar; ap: Pointer): LongBool; cdecl; external 'frame'; 55 | function frm_writefile(fname: PAnsiChar; data: PAnsiChar; args: array of const): LongBool; cdecl; varargs; external 'frame'; 56 | function frm_homepath: PAnsiChar; cdecl; external 'frame'; 57 | 58 | function frm_node_create(frm: frm_t): frm_node_t; cdecl; external 'frame'; 59 | procedure frm_node_free(rootnode: frm_node_t); cdecl; external 'frame'; 60 | 61 | function frm_node_name(node: frm_node_t): PAnsiChar; cdecl; external 'frame'; 62 | function frm_node_date(node: frm_node_t): cuint64; cdecl; external 'frame'; 63 | function frm_node_fpath(node: frm_node_t): PChar; cdecl; external 'frame'; 64 | function frm_node_nchildren(node: frm_node_t): csize_t; cdecl; external 'frame'; 65 | function frm_node_child(node: frm_node_t; index: csize_t): frm_node_t; cdecl; external 'frame'; 66 | function frm_node_parent(node: frm_node_t): frm_node_t; cdecl; external 'frame'; 67 | function frm_node_root(node: frm_node_t): frm_node_t; cdecl; external 'frame'; 68 | function frm_node_find(node: frm_node_t; fpath: PChar): frm_node_t; cdecl; external 'frame'; 69 | 70 | 71 | 72 | procedure frame_history_populate(searchTerm: String; tlView: TListView); 73 | procedure frame_frames_populate(tv: TTreeView); 74 | procedure frame_current_populate(edt: TEdit); 75 | procedure frame_notes_populate(memo: TMemo); 76 | procedure frame_set_frames_selected(tv: TTreeView; edt: TEdit); 77 | 78 | implementation 79 | 80 | procedure frame_history_populate(searchTerm: String; tlView: TListView); 81 | var 82 | frame_history: PChar; 83 | i: csize_t; 84 | strItems: TStringDynArray; 85 | listItem: TListItem; 86 | 87 | begin 88 | frame_history := frm_history(frame_var, csize_t($ffffffffffffffff)); 89 | strItems := SplitString(frame_history, #$0a); 90 | 91 | tlView.Items.Clear(); 92 | for i:=0 to (Length(strItems) - 1) do 93 | begin 94 | if (Length(searchTerm) > 2) and (PosEx(searchTerm, strItems[i]) <= 0) then 95 | begin 96 | continue; 97 | end; 98 | listItem := tlView.Items.Add(); 99 | listItem.Caption := Copy(strItems[i], 1, Length(strItems[i])); 100 | end; 101 | frm_mem_free(frame_history); 102 | end; 103 | 104 | 105 | procedure addChild (tv: TTreeView; parent: TTreeNode; frmNode: frm_node_t); 106 | var 107 | name: String; 108 | nchildren: csize_t; 109 | child: frm_node_t; 110 | i: csize_t; 111 | current: TTreeNode; 112 | 113 | begin 114 | name := frm_node_name(frmNode); 115 | current := tv.Items.AddChild(parent, name); 116 | // current.Data := frm_node_fpath(frmNode); 117 | 118 | nchildren := frm_node_nchildren(frmNode); 119 | i := 0; 120 | 121 | while i < nchildren do 122 | begin 123 | child := frm_node_child(frmNode, i); 124 | addChild (tv, current, child); 125 | Inc(i); 126 | end; 127 | end; 128 | 129 | procedure frame_frames_populate(tv: TTreeView); 130 | var 131 | root: pointer; 132 | begin 133 | tv.Items.Clear; 134 | root := frm_node_create(frame_var); 135 | addChild (tv, nil, root); 136 | frm_node_free(root); 137 | end; 138 | 139 | procedure frame_current_populate(edt: TEdit); 140 | begin 141 | edt.Caption := frm_current(frame_var); 142 | end; 143 | 144 | procedure frame_notes_populate(memo: TMemo); 145 | begin 146 | memo.Clear; 147 | memo.Append(frm_payload()); 148 | end; 149 | 150 | procedure frame_set_frames_selected(tv: TTreeView; edt: TEdit); 151 | begin 152 | tv.Selected := tv.Items.FindNodeWithTextPath(edt.Caption); 153 | end; 154 | end. 155 | 156 | -------------------------------------------------------------------------------- /frame-browser/mainwin.lfm: -------------------------------------------------------------------------------- 1 | object frmMain: TfrmMain 2 | Left = 472 3 | Height = 486 4 | Top = 234 5 | Width = 1278 6 | Caption = 'Frame Browser' 7 | ClientHeight = 486 8 | ClientWidth = 1278 9 | OnActivate = FormActivate 10 | OnCreate = FormCreate 11 | LCLVersion = '2.2.6.0' 12 | object Panel1: TPanel 13 | AnchorSideLeft.Control = Owner 14 | AnchorSideTop.Control = Owner 15 | AnchorSideRight.Control = Owner 16 | AnchorSideRight.Side = asrBottom 17 | Left = 0 18 | Height = 32 19 | Top = 0 20 | Width = 1278 21 | Anchors = [akTop, akLeft, akRight] 22 | ClientHeight = 32 23 | ClientWidth = 1278 24 | TabOrder = 0 25 | object bbtnQuit: TBitBtn 26 | AnchorSideLeft.Control = Panel1 27 | AnchorSideTop.Control = Panel1 28 | AnchorSideBottom.Control = Panel1 29 | AnchorSideBottom.Side = asrBottom 30 | Left = 1 31 | Height = 30 32 | Hint = 'Quit FrameBrowser' 33 | Top = 1 34 | Width = 75 35 | Anchors = [akTop, akLeft, akBottom] 36 | Caption = 'Quit' 37 | OnClick = bbtnQuitClick 38 | ParentShowHint = False 39 | ShowHint = True 40 | TabOrder = 0 41 | end 42 | object bbtnHelp: TBitBtn 43 | AnchorSideLeft.Control = bbtnQuit 44 | AnchorSideLeft.Side = asrBottom 45 | AnchorSideTop.Control = Panel1 46 | AnchorSideBottom.Control = Panel1 47 | AnchorSideBottom.Side = asrBottom 48 | Left = 76 49 | Height = 30 50 | Top = 1 51 | Width = 75 52 | Anchors = [akTop, akLeft, akBottom] 53 | Caption = 'Help' 54 | TabOrder = 1 55 | end 56 | end 57 | object Panel2: TScrollBox 58 | AnchorSideLeft.Control = Owner 59 | AnchorSideTop.Control = Panel1 60 | AnchorSideTop.Side = asrBottom 61 | AnchorSideRight.Control = Owner 62 | AnchorSideRight.Side = asrBottom 63 | AnchorSideBottom.Control = Owner 64 | AnchorSideBottom.Side = asrBottom 65 | Left = 0 66 | Height = 454 67 | Top = 32 68 | Width = 1278 69 | HorzScrollBar.Page = 946 70 | HorzScrollBar.Tracking = True 71 | VertScrollBar.Page = 195 72 | Anchors = [akTop, akLeft, akRight, akBottom] 73 | ClientHeight = 452 74 | ClientWidth = 1276 75 | TabOrder = 1 76 | object PairSplitter1: TPairSplitter 77 | AnchorSideLeft.Control = Panel2 78 | AnchorSideTop.Control = Panel2 79 | AnchorSideRight.Control = Panel2 80 | AnchorSideRight.Side = asrBottom 81 | AnchorSideBottom.Control = Panel2 82 | AnchorSideBottom.Side = asrBottom 83 | Left = 0 84 | Height = 452 85 | Top = 0 86 | Width = 1276 87 | Anchors = [akTop, akLeft, akRight, akBottom] 88 | Position = 408 89 | object PairSplitterSide1: TPairSplitterSide 90 | Cursor = crArrow 91 | Left = 0 92 | Height = 452 93 | Top = 0 94 | Width = 408 95 | ClientWidth = 408 96 | ClientHeight = 452 97 | object StaticText2: TStaticText 98 | AnchorSideLeft.Control = PairSplitterSide1 99 | AnchorSideTop.Control = PairSplitterSide1 100 | AnchorSideRight.Control = PairSplitterSide1 101 | AnchorSideRight.Side = asrBottom 102 | Left = 0 103 | Height = 17 104 | Top = 0 105 | Width = 408 106 | Anchors = [akTop, akLeft, akRight] 107 | Caption = 'History' 108 | TabOrder = 0 109 | end 110 | object edtSearchTerm: TEdit 111 | AnchorSideLeft.Control = PairSplitterSide1 112 | AnchorSideTop.Control = StaticText2 113 | AnchorSideTop.Side = asrBottom 114 | AnchorSideRight.Control = PairSplitterSide1 115 | AnchorSideRight.Side = asrBottom 116 | Left = 0 117 | Height = 28 118 | Top = 17 119 | Width = 408 120 | Anchors = [akTop, akLeft, akRight] 121 | OnChange = edtSearchTermChange 122 | TabOrder = 1 123 | end 124 | object lvHistory: TListView 125 | AnchorSideLeft.Control = PairSplitterSide1 126 | AnchorSideTop.Control = edtSearchTerm 127 | AnchorSideTop.Side = asrBottom 128 | AnchorSideRight.Control = PairSplitterSide1 129 | AnchorSideRight.Side = asrBottom 130 | AnchorSideBottom.Control = PairSplitterSide1 131 | AnchorSideBottom.Side = asrBottom 132 | Left = 0 133 | Height = 407 134 | Top = 45 135 | Width = 408 136 | Anchors = [akTop, akLeft, akRight, akBottom] 137 | Columns = <> 138 | Enabled = False 139 | TabOrder = 2 140 | OnSelectItem = lvHistorySelectItem 141 | end 142 | end 143 | object PairSplitterSide2: TPairSplitterSide 144 | Cursor = crArrow 145 | Left = 412 146 | Height = 452 147 | Top = 0 148 | Width = 864 149 | ClientWidth = 864 150 | ClientHeight = 452 151 | object PairSplitter2: TPairSplitter 152 | AnchorSideLeft.Control = PairSplitterSide2 153 | AnchorSideTop.Control = PairSplitterSide2 154 | AnchorSideRight.Control = PairSplitterSide2 155 | AnchorSideRight.Side = asrBottom 156 | AnchorSideBottom.Control = PairSplitterSide2 157 | AnchorSideBottom.Side = asrBottom 158 | Left = 0 159 | Height = 452 160 | Top = 0 161 | Width = 864 162 | Anchors = [akTop, akLeft, akRight, akBottom] 163 | Position = 336 164 | object PairSplitterSide3: TPairSplitterSide 165 | Cursor = crArrow 166 | Left = 0 167 | Height = 452 168 | Top = 0 169 | Width = 336 170 | ClientWidth = 336 171 | ClientHeight = 452 172 | object StaticText1: TStaticText 173 | AnchorSideLeft.Control = PairSplitterSide3 174 | AnchorSideTop.Control = PairSplitterSide3 175 | AnchorSideRight.Control = PairSplitterSide3 176 | AnchorSideRight.Side = asrBottom 177 | Left = 0 178 | Height = 17 179 | Top = 0 180 | Width = 336 181 | Anchors = [akTop, akLeft, akRight] 182 | Caption = 'Current Frame' 183 | TabOrder = 0 184 | end 185 | object tvFrames: TTreeView 186 | AnchorSideLeft.Control = PairSplitterSide3 187 | AnchorSideTop.Control = edtCurrentFrame 188 | AnchorSideTop.Side = asrBottom 189 | AnchorSideRight.Control = PairSplitterSide3 190 | AnchorSideRight.Side = asrBottom 191 | AnchorSideBottom.Control = PairSplitterSide3 192 | AnchorSideBottom.Side = asrBottom 193 | Left = 0 194 | Height = 407 195 | Top = 45 196 | Width = 336 197 | Anchors = [akTop, akLeft, akRight, akBottom] 198 | PopupMenu = ctxMenuCurrentFrame 199 | TabOrder = 2 200 | OnEditingEnd = tvFramesEditingEnd 201 | OnKeyDown = tvFramesKeyDown 202 | OnSelectionChanged = tvFramesSelectionChanged 203 | Items.Data = { 204 | F9FFFFFF020001000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF010000000000 205 | 000001050000004974656D30FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF01000000 206 | 0000000001050000004974656D31FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000 207 | 00000000000000050000004974656D32 208 | } 209 | end 210 | object edtCurrentFrame: TEdit 211 | AnchorSideLeft.Control = PairSplitterSide3 212 | AnchorSideTop.Control = StaticText1 213 | AnchorSideTop.Side = asrBottom 214 | AnchorSideRight.Control = PairSplitterSide3 215 | AnchorSideRight.Side = asrBottom 216 | AnchorSideBottom.Control = tvFrames 217 | Left = 0 218 | Height = 28 219 | Top = 17 220 | Width = 336 221 | Anchors = [akTop, akLeft, akRight] 222 | Enabled = False 223 | TabOrder = 1 224 | end 225 | end 226 | object PairSplitterSide4: TPairSplitterSide 227 | Cursor = crArrow 228 | Left = 340 229 | Height = 452 230 | Top = 0 231 | Width = 524 232 | ClientWidth = 524 233 | ClientHeight = 452 234 | object StaticText3: TStaticText 235 | AnchorSideLeft.Control = PairSplitterSide4 236 | AnchorSideTop.Control = PairSplitterSide4 237 | AnchorSideRight.Control = PairSplitterSide4 238 | AnchorSideRight.Side = asrBottom 239 | Left = 0 240 | Height = 17 241 | Top = 0 242 | Width = 58 243 | Caption = 'Notes' 244 | TabOrder = 0 245 | end 246 | object memoNotes: TMemo 247 | AnchorSideLeft.Control = PairSplitterSide4 248 | AnchorSideTop.Control = StaticText3 249 | AnchorSideTop.Side = asrBottom 250 | AnchorSideRight.Control = PairSplitterSide4 251 | AnchorSideRight.Side = asrBottom 252 | AnchorSideBottom.Control = PairSplitterSide4 253 | AnchorSideBottom.Side = asrBottom 254 | Left = 0 255 | Height = 435 256 | Top = 17 257 | Width = 524 258 | Anchors = [akTop, akLeft, akRight, akBottom] 259 | Font.Height = -13 260 | Font.Name = 'Monospace' 261 | Lines.Strings = ( 262 | '' 263 | ) 264 | OnChange = memoNotesChange 265 | OnEditingDone = memoNotesEditingDone 266 | ParentFont = False 267 | ScrollBars = ssBoth 268 | TabOrder = 1 269 | end 270 | object stxtStatusChanged: TStaticText 271 | AnchorSideTop.Control = PairSplitterSide4 272 | AnchorSideRight.Control = PairSplitterSide4 273 | AnchorSideRight.Side = asrBottom 274 | Left = 330 275 | Height = 17 276 | Top = 0 277 | Width = 194 278 | Anchors = [akTop, akRight] 279 | Caption = 'Notes Changed' 280 | Color = clDefault 281 | Font.Color = clRed 282 | ParentFont = False 283 | ParentColor = False 284 | TabOrder = 2 285 | end 286 | end 287 | end 288 | end 289 | end 290 | end 291 | object sbarStatus: TStatusBar 292 | Left = 0 293 | Height = 18 294 | Top = 468 295 | Width = 1278 296 | Panels = <> 297 | SimpleText = 'help' 298 | end 299 | object ctxMenuCurrentFrame: TPopupMenu 300 | Left = 208 301 | object MenuItem1: TMenuItem 302 | Caption = 'Push Child Frame' 303 | OnClick = MenuItem1Click 304 | end 305 | object MenuItem2: TMenuItem 306 | Caption = 'Pop This Frame' 307 | OnClick = MenuItem2Click 308 | end 309 | object MenuItem3: TMenuItem 310 | Caption = 'Rename This Frame' 311 | OnClick = MenuItem3Click 312 | end 313 | object Separator1: TMenuItem 314 | Caption = '-' 315 | end 316 | object MenuItem4: TMenuItem 317 | Caption = 'Pop Frame and All Children' 318 | OnClick = MenuItem4Click 319 | end 320 | end 321 | end 322 | -------------------------------------------------------------------------------- /frame-browser/mainwin.pas: -------------------------------------------------------------------------------- 1 | unit mainWin; 2 | 3 | {$mode objfpc}{$H+} 4 | 5 | interface 6 | 7 | uses 8 | Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ExtCtrls, Buttons, 9 | StdCtrls, PairSplitter, Types, ComCtrls, TreeFilterEdit, CTypes, Cmem, 10 | LCLType, Menus, 11 | FrameWrapper; 12 | 13 | type 14 | 15 | { TfrmMain } 16 | 17 | TfrmMain = class(TForm) 18 | bbtnQuit: TBitBtn; 19 | bbtnHelp: TBitBtn; 20 | edtSearchTerm: TEdit; 21 | lvHistory: TListView; 22 | memoNotes: TMemo; 23 | MenuItem1: TMenuItem; 24 | MenuItem2: TMenuItem; 25 | MenuItem3: TMenuItem; 26 | MenuItem4: TMenuItem; 27 | PairSplitter1: TPairSplitter; 28 | PairSplitter2: TPairSplitter; 29 | PairSplitterSide1: TPairSplitterSide; 30 | PairSplitterSide2: TPairSplitterSide; 31 | PairSplitterSide3: TPairSplitterSide; 32 | PairSplitterSide4: TPairSplitterSide; 33 | Panel1: TPanel; 34 | Panel2: TScrollBox; 35 | ctxMenuCurrentFrame: TPopupMenu; 36 | Separator1: TMenuItem; 37 | StaticText1: TStaticText; 38 | StaticText2: TStaticText; 39 | StaticText3: TStaticText; 40 | edtCurrentFrame: TEdit; 41 | sbarStatus: TStatusBar; 42 | stxtStatusChanged: TStaticText; 43 | tvFrames: TTreeView; 44 | procedure bbtnQuitClick(Sender: TObject); 45 | procedure edtSearchTermChange(Sender: TObject); 46 | procedure FormActivate(Sender: TObject); 47 | procedure FormCreate(Sender: TObject); 48 | procedure lvHistorySelectItem(Sender: TObject; Item: TListItem; 49 | Selected: Boolean); 50 | procedure memoNotesChange(Sender: TObject); 51 | procedure memoNotesEditingDone(Sender: TObject); 52 | procedure MenuItem1Click(Sender: TObject); 53 | procedure MenuItem2Click(Sender: TObject); 54 | procedure MenuItem3Click(Sender: TObject); 55 | procedure MenuItem4Click(Sender: TObject); 56 | procedure tvFramesEditingEnd(Sender: TObject; Node: TTreeNode; 57 | Cancel: Boolean); 58 | procedure tvFramesKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState 59 | ); 60 | procedure tvFramesSelectionChanged(Sender: TObject); 61 | private 62 | notesChanged: Boolean; 63 | public 64 | 65 | end; 66 | 67 | var 68 | frmMain: TfrmMain; 69 | 70 | implementation 71 | 72 | {$R *.lfm} 73 | 74 | { TfrmMain } 75 | 76 | 77 | procedure Alert(message: String); 78 | var 79 | tmp: String; 80 | begin 81 | ShowMessage(message); 82 | tmp := frm_lastmsg(frame_var); 83 | frmMain.sbarStatus.SimpleText:=frm_lastmsg(frame_var); 84 | end; 85 | 86 | procedure TfrmMain.bbtnQuitClick(Sender: TObject); 87 | begin 88 | frmMain.Close; 89 | end; 90 | 91 | 92 | procedure TfrmMain.edtSearchTermChange(Sender: TObject); 93 | var 94 | sterm: String; 95 | begin 96 | sterm := frmMain.edtSearchTerm.Caption; 97 | frame_history_populate (sterm, frmMain.lvHistory); 98 | end; 99 | 100 | function FrameInit (path: String): Boolean; 101 | begin 102 | frm_close(frame_var); 103 | frame_var := frm_init(Pchar(path)); 104 | if frame_var = nil then 105 | begin 106 | Alert('Failed to open Frame Database at [' + path + ']' 107 | + sLineBreak 108 | + 'Aborting'); 109 | Exit(false); 110 | end; 111 | Exit(true); 112 | end; 113 | 114 | procedure FrameReopen(); 115 | begin 116 | FrameInit(frm_homepath() + '/.framedb'); 117 | frame_history_populate('', frmMain.lvHistory); 118 | frame_current_populate(frmMain.edtCurrentFrame); 119 | frame_notes_populate(frmMain.memoNotes); 120 | frame_frames_populate(frmMain.tvFrames); 121 | frame_set_frames_selected(frmMain.tvFrames, frmMain.edtCurrentFrame); 122 | end; 123 | 124 | procedure TfrmMain.FormActivate(Sender: TObject); 125 | begin 126 | if FrameInit(frm_homepath() + '/.framedb') <> true then 127 | begin 128 | frmMain.Close; 129 | Exit; 130 | end; 131 | frame_history_populate('', frmMain.lvHistory); 132 | frame_current_populate(frmMain.edtCurrentFrame); 133 | frame_notes_populate(frmMain.memoNotes); 134 | frame_frames_populate(frmMain.tvFrames); 135 | frame_set_frames_selected(frmMain.tvFrames, frmMain.edtCurrentFrame); 136 | end; 137 | 138 | 139 | procedure TfrmMain.FormCreate(Sender: TObject); 140 | begin 141 | 142 | end; 143 | 144 | procedure TfrmMain.lvHistorySelectItem(Sender: TObject; Item: TListItem; 145 | Selected: Boolean); 146 | var 147 | fpath: String; 148 | ttnode: TListItem; 149 | begin 150 | ttnode := frmMain.lvHistory.Selected; 151 | if ttnode <> nil then 152 | begin 153 | fpath := ttnode.Caption; 154 | frm_switch_direct(frame_var, PChar(fpath)); 155 | frame_current_populate(frmMain.edtCurrentFrame); 156 | frame_notes_populate(frmMain.memoNotes); 157 | frame_frames_populate(frmMain.tvFrames); 158 | frame_set_frames_selected(frmMain.tvFrames, frmMain.edtCurrentFrame); 159 | end; 160 | end; 161 | 162 | procedure TfrmMain.memoNotesChange(Sender: TObject); 163 | begin 164 | frmMain.notesChanged:=true; 165 | frmMain.stxtStatusChanged.Caption:='Frame notes changed'; 166 | end; 167 | 168 | 169 | procedure TfrmMain.memoNotesEditingDone(Sender: TObject); 170 | var 171 | reply, boxStyle: Integer; 172 | begin 173 | if frmMain.notesChanged = false then 174 | Exit; 175 | 176 | boxStyle := MB_ICONQUESTION + MB_YESNO; 177 | reply := Application.MessageBox('Frame contents changed. Save Changes?', 'Contents Changed', BoxStyle); 178 | if reply = IDYES then 179 | begin 180 | frm_payload_replace(PChar(frmMain.memoNotes.Text)); 181 | end; 182 | frmMain.notesChanged:=false; 183 | frmMain.stxtStatusChanged.Caption:=''; 184 | end; 185 | 186 | procedure TfrmMain.MenuItem1Click(Sender: TObject); 187 | begin 188 | frmMain.sbarStatus.SimpleText:='Pushing new frame'; 189 | if frm_push(frame_var, 'New Frame', 'Enter Contents for new frame') <> true then 190 | begin 191 | Alert('failed to push new frame:' + sLineBreak + frm_lastmsg(frame_var)); 192 | end else 193 | begin 194 | FrameReopen(); 195 | frmMain.tvFrames.Selected := frmMain.tvFrames.Items.FindNodeWithTextPath(frmMain.edtCurrentFrame.Caption); 196 | frmMain.tvFrames.Selected.EditText; 197 | end; 198 | end; 199 | 200 | procedure TfrmMain.MenuItem2Click(Sender: TObject); 201 | begin 202 | frmMain.sbarStatus.SimpleText:='Popping current frame'; 203 | if frm_pop(frame_var, false) <> true then 204 | begin 205 | Alert('failed to pop frame:' + sLineBreak + frm_lastmsg(frame_var)); 206 | end else 207 | begin 208 | FrameReopen(); 209 | end; 210 | end; 211 | 212 | procedure TfrmMain.MenuItem3Click(Sender: TObject); 213 | var 214 | node: TTreeNode; 215 | begin 216 | frmMain.sbarStatus.SimpleText:='Renaming current frame'; 217 | node := frmMain.tvFrames.Selected; 218 | node.EditText(); 219 | end; 220 | 221 | procedure TfrmMain.MenuItem4Click(Sender: TObject); 222 | begin 223 | frmMain.sbarStatus.SimpleText:='Recursively popping current frame'; 224 | if frm_pop(frame_var, true) <> true then 225 | begin 226 | Alert('failed to force pop frame:' + sLineBreak + frm_lastmsg(frame_var)); 227 | end else 228 | begin 229 | FrameReopen(); 230 | end; 231 | end; 232 | 233 | procedure TfrmMain.tvFramesEditingEnd(Sender: TObject; Node: TTreeNode; 234 | Cancel: Boolean); 235 | var 236 | newname: String; 237 | begin 238 | if Cancel = true then 239 | Exit(); 240 | 241 | newname := Node.Text; 242 | if frm_rename(frame_var, PChar(newname)) <> true then 243 | begin 244 | Alert('Failed to rename node:' + sLineBreak + frm_lastmsg(frame_var)); 245 | end else 246 | begin 247 | FrameReopen(); 248 | end; 249 | end; 250 | 251 | procedure TfrmMain.tvFramesKeyDown(Sender: TObject; var Key: Word; 252 | Shift: TShiftState); 253 | begin 254 | if key = VK_F2 then 255 | begin 256 | frmMain.tvFrames.Selected.EditText; 257 | end; 258 | 259 | if key = VK_LCL_ALT then 260 | begin 261 | frmMain.ctxMenuCurrentFrame.PopUp; 262 | end; 263 | end; 264 | 265 | 266 | function TreeNodeFpath(node: TTreeNode): String; 267 | var 268 | parent: String; 269 | begin 270 | if node = nil then 271 | begin 272 | Exit(''); 273 | end; 274 | 275 | parent := TreeNodeFpath(node.Parent); 276 | if Length(parent) > 1 then 277 | begin 278 | parent := parent + '/'; 279 | end; 280 | Exit(parent + node.Text); 281 | end; 282 | 283 | procedure TfrmMain.tvFramesSelectionChanged(Sender: TObject); 284 | var 285 | fpath: String; 286 | ttnode: TTreeNode; 287 | begin 288 | ttnode := frmMain.tvFrames.Selected; 289 | if ttnode <> nil then 290 | begin 291 | fpath := TreeNodeFpath(frmMain.tvFrames.Selected); 292 | frm_switch_direct(frame_var, PChar(fpath)); 293 | frame_history_populate('', frmMain.lvHistory); 294 | frame_current_populate(frmMain.edtCurrentFrame); 295 | frame_notes_populate(frmMain.memoNotes); 296 | frmMain.notesChanged:=false; 297 | frmMain.stxtStatusChanged.Caption:= ''; 298 | end; 299 | end; 300 | 301 | end. 302 | 303 | -------------------------------------------------------------------------------- /frame.bash.autocomplete: -------------------------------------------------------------------------------- 1 | # Source this file to have frame autocompletions in bash 2 | 3 | _frame_completions() { 4 | local cmd="$1" # Always 'frame' 5 | local cur="$2" # Current completion, can be blank 6 | local prev="$3" # Previous word, can be 'frame' 7 | 8 | local opts="--help" 9 | local commands=("status" "switch" "current" "down") 10 | local args 11 | local fpath="`frame current --quiet | cut -f 1 -d :`" 12 | 13 | declare -a COMPREPLY 14 | 15 | pushd $PWD &> /dev/null 16 | 17 | case "${prev}" in 18 | down) 19 | cd "$HOME/.framedb/$fpath" 20 | args="`for X in $cur*/; do printf "%s\n" "$X" | sed 's/ /|/g'; done`" 21 | local INDEX=0; 22 | echo "cur = [$cur]" > /tmp/t 23 | for X in `compgen -W "${args}" ${cur}` 24 | do 25 | COMPREPLY[$INDEX]=`echo -ne $X | sed 's/|/ /g'` 26 | echo $INDEX: ${COMPREPLY[$INDEX]} >> /tmp/t 27 | printf "in loop: %q\n" "${COMPREPLY[@]}" >> /tmp/t 28 | INDEX=$(($INDEX+1)) 29 | done 30 | printf "out loop 1: %q\n" "${COMPREPLY[@]}" >> /tmp/t 31 | printf "out loop 2: %q\n" "${COMPREPLY[*]}" >> /tmp/t 32 | ;; 33 | *) 34 | ;; 35 | esac 36 | 37 | popd &> /dev/null 38 | return 0; 39 | } 40 | 41 | complete -F _frame_completions frame 42 | -------------------------------------------------------------------------------- /src/ds_array.c: -------------------------------------------------------------------------------- 1 | 2 | /* ************************************************************************** * 3 | * Frame (©2023 Lelanthran Manickum) * 4 | * * 5 | * This program comes with ABSOLUTELY NO WARRANTY. This is free software * 6 | * and you are welcome to redistribute it under certain conditions; see * 7 | * the LICENSE file for details. * 8 | * ****************************************************************************/ 9 | 10 | 11 | #include <stdlib.h> 12 | #include <stdbool.h> 13 | #include <string.h> 14 | #include <stdint.h> 15 | 16 | #include "ds_array.h" 17 | 18 | struct ds_array_t { 19 | size_t nitems; 20 | void **array; 21 | }; 22 | 23 | ds_array_t *ds_array_new (void) 24 | { 25 | ds_array_t *ret = calloc (1, sizeof *ret); 26 | if (!(ret->array = calloc (1, sizeof *(ret->array)))) { 27 | free (ret); 28 | ret = NULL; 29 | } 30 | return ret; 31 | } 32 | 33 | void ds_array_del (ds_array_t *ll) 34 | { 35 | if (!ll) 36 | return; 37 | 38 | free (ll->array); 39 | free (ll); 40 | } 41 | 42 | ds_array_t *ds_array_copy (const ds_array_t *src, size_t from_index, size_t to_index) 43 | { 44 | size_t nitems; 45 | ds_array_t *ret = ds_array_new (); 46 | bool error = true; 47 | 48 | if (!src) 49 | return NULL; 50 | 51 | nitems = ds_array_length (src); 52 | 53 | for (size_t i=from_index; i>=from_index && i<to_index && i<nitems; i++) { 54 | if (!(ds_array_ins_tail (ret, src->array[i]))) 55 | goto errorexit; 56 | } 57 | 58 | error = false; 59 | 60 | errorexit: 61 | 62 | if (error) { 63 | ds_array_del (ret); 64 | ret = NULL; 65 | } 66 | 67 | return ret; 68 | } 69 | 70 | size_t ds_array_length (const ds_array_t *ll) 71 | { 72 | if (!ll) 73 | return 0; 74 | 75 | return ll->nitems; 76 | } 77 | 78 | void *ds_array_get (const ds_array_t *ll, size_t i) 79 | { 80 | if (!ll) 81 | return NULL; 82 | 83 | if (i >= ll->nitems) 84 | return NULL; 85 | 86 | return ll->array[i]; 87 | } 88 | 89 | void ds_array_iterate (const ds_array_t *ll, 90 | void (*fptr) (void *, void *), void *param) 91 | { 92 | if (!ll || !fptr) 93 | return; 94 | 95 | for (size_t i=0; ll->array[i]; i++) { 96 | fptr (ll->array[i], param); 97 | } 98 | } 99 | 100 | static bool ds_array_grow (ds_array_t *ll, size_t nelems) 101 | { 102 | if (!ll) 103 | return false; 104 | 105 | void **tmp = realloc (ll->array, (sizeof *ll->array) * (ll->nitems + 1 + nelems + 1)); 106 | if (!tmp) 107 | return false; 108 | 109 | ll->array = tmp; 110 | 111 | memset (&ll->array[ll->nitems], 0, (sizeof *ll->array) * (nelems + 1)); 112 | return true; 113 | } 114 | 115 | void ds_array_shrink_to_fit (ds_array_t *ll) 116 | { 117 | if (!ll) 118 | return; 119 | 120 | void **tmp = realloc (ll->array, (sizeof *ll->array) * (ll->nitems + 1)); 121 | if (!tmp) 122 | return; 123 | 124 | ll->array = tmp; 125 | } 126 | 127 | void *ds_array_ins_tail (ds_array_t *ll, void *el) 128 | { 129 | if (!ll || !el) 130 | return NULL; 131 | 132 | if (!(ds_array_grow (ll, 1))) 133 | return NULL; 134 | 135 | ll->array[ll->nitems++] = el; 136 | 137 | return el; 138 | } 139 | 140 | void *ds_array_ins_head (ds_array_t *ll, void *el) 141 | { 142 | if (!ll || !el) 143 | return NULL; 144 | 145 | size_t endpos = ll->nitems; 146 | 147 | if (!(ds_array_grow (ll, 1))) 148 | return NULL; 149 | 150 | memmove (&ll->array[1], &ll->array[0], (sizeof *(ll->array)) * (endpos + 1)); 151 | 152 | ll->array[0] = el; 153 | ll->nitems++; 154 | 155 | return el; 156 | } 157 | 158 | void *ds_array_rm_tail (ds_array_t *ll) 159 | { 160 | if(!ll || !ll->array[0] || ll->nitems == 0) 161 | return NULL; 162 | 163 | void *ret = ll->array[ll->nitems - 1]; 164 | 165 | ll->nitems--; 166 | ll->array[ll->nitems] = NULL; 167 | 168 | return ret; 169 | } 170 | 171 | void *ds_array_rm_head (ds_array_t *ll) 172 | { 173 | return ds_array_rm (ll, 0); 174 | } 175 | 176 | 177 | void *ds_array_rm (ds_array_t *ll, size_t index) 178 | { 179 | if (!ll || ll->nitems == 0) 180 | return NULL; 181 | 182 | if (index >= ll->nitems) 183 | return NULL; 184 | 185 | void *ret = ll->array[index]; 186 | 187 | memmove (&ll->array[index], &ll->array[index + 1], 188 | (sizeof (void *)) * (ll->nitems - index)); 189 | 190 | return ret; 191 | } 192 | -------------------------------------------------------------------------------- /src/ds_array.h: -------------------------------------------------------------------------------- 1 | 2 | /* ************************************************************************** * 3 | * Frame (©2023 Lelanthran Manickum) * 4 | * * 5 | * This program comes with ABSOLUTELY NO WARRANTY. This is free software * 6 | * and you are welcome to redistribute it under certain conditions; see * 7 | * the LICENSE file for details. * 8 | * ****************************************************************************/ 9 | 10 | 11 | #ifndef H_DS_LL 12 | #define H_DS_LL 13 | 14 | #include <stdlib.h> 15 | 16 | typedef struct ds_array_t ds_array_t; 17 | 18 | // This array stores pointers to objects that must be allocated and freed by the caller. 19 | // Removing an entry from the array does not free the object stored by the caller. The caller 20 | // must free all objects that they have allocated. 21 | #ifdef __cplusplus 22 | extern "C" { 23 | #endif 24 | 25 | // TODO: Change 'remove' to 'rm' 26 | ds_array_t *ds_array_new (void); 27 | void ds_array_del (ds_array_t *ll); 28 | ds_array_t *ds_array_copy (const ds_array_t *src, size_t from_index, size_t to_index); 29 | 30 | size_t ds_array_length (const ds_array_t *ll); 31 | void *ds_array_get (const ds_array_t *ll, size_t i); 32 | void ds_array_iterate (const ds_array_t *ll, 33 | void (*fptr) (void *, void *), void *param); 34 | 35 | void *ds_array_ins_tail (ds_array_t *ll, void *el); 36 | void *ds_array_ins_head (ds_array_t *ll, void *el); 37 | 38 | void *ds_array_rm_tail (ds_array_t *ll); 39 | void *ds_array_rm_head (ds_array_t *ll); 40 | 41 | void *ds_array_rm (ds_array_t *ll, size_t index); 42 | 43 | void ds_array_shrink_to_fit (ds_array_t *ll); 44 | 45 | #ifdef __cplusplus 46 | }; 47 | #endif 48 | 49 | #endif 50 | 51 | 52 | -------------------------------------------------------------------------------- /src/ds_str.c: -------------------------------------------------------------------------------- 1 | 2 | /* ************************************************************************** * 3 | * Frame (©2023 Lelanthran Manickum) * 4 | * * 5 | * This program comes with ABSOLUTELY NO WARRANTY. This is free software * 6 | * and you are welcome to redistribute it under certain conditions; see * 7 | * the LICENSE file for details. * 8 | * ****************************************************************************/ 9 | 10 | 11 | #include <stdio.h> 12 | #include <stdarg.h> 13 | #include <stdbool.h> 14 | #include <stdlib.h> 15 | #include <string.h> 16 | #include <ctype.h> 17 | 18 | #include "ds_str.h" 19 | 20 | /* ******************************************************************** */ 21 | 22 | char *ds_str_dup (const char *src) 23 | { 24 | if (!src) 25 | return NULL; 26 | 27 | size_t len = strlen (src) + 1; 28 | 29 | char *ret = malloc (len); 30 | if (!ret) 31 | return NULL; 32 | 33 | return strcpy (ret, src); 34 | } 35 | 36 | /* ******************************************************************** */ 37 | 38 | char *ds_str_vcat (const char *src, va_list ap) 39 | { 40 | bool error = true; 41 | char *ret = NULL; 42 | const char *tmp = src; 43 | size_t nbytes = 0; 44 | va_list apc; 45 | 46 | va_copy (apc, ap); 47 | 48 | while (tmp) { 49 | nbytes += strlen (tmp); 50 | tmp = va_arg (apc, const char *); 51 | } 52 | 53 | va_end (apc); 54 | 55 | if (!(ret = malloc (nbytes + 1))) 56 | goto errorexit; 57 | 58 | *ret = 0; 59 | 60 | tmp = src; 61 | size_t idx = strlen (ret); 62 | while (tmp) { 63 | // strcat (ret, tmp); 64 | size_t tmplen = strlen (tmp); 65 | memcpy (&ret[idx], tmp, tmplen); 66 | idx += tmplen; 67 | tmp = va_arg (ap, const char *); 68 | } 69 | ret[idx] = 0; 70 | 71 | error = false; 72 | 73 | errorexit: 74 | 75 | if (error) { 76 | free (ret); 77 | ret = NULL; 78 | } 79 | 80 | return ret; 81 | } 82 | 83 | char *ds_str_cat (const char *src, ...) 84 | { 85 | va_list ap; 86 | 87 | va_start (ap, src); 88 | char *ret = ds_str_vcat (src, ap); 89 | va_end (ap); 90 | 91 | return ret; 92 | } 93 | 94 | char *ds_str_vappend (char **dst, const char *s1, va_list ap) 95 | { 96 | bool error = true; 97 | char *ret = NULL; 98 | 99 | 100 | if (!(*dst)) 101 | (*dst) = ds_str_dup (""); 102 | 103 | if (!(*dst)) 104 | return NULL; 105 | 106 | if (!(ret = ds_str_cat ((*dst), s1, NULL))) 107 | goto errorexit; 108 | 109 | size_t idx = strlen (ret); 110 | while ((s1 = va_arg (ap, char *))!=NULL) { 111 | // char *tmp = ds_str_cat (ret, s1, NULL); 112 | // if (!tmp) 113 | // goto errorexit; 114 | 115 | size_t s1_len = strlen (s1); 116 | size_t newlen = idx + s1_len; 117 | char *tmp = realloc (ret, newlen + 1); 118 | if (!tmp) { 119 | free (ret); 120 | return NULL; 121 | } 122 | ret = tmp; 123 | memcpy (&ret[idx], s1, s1_len); 124 | idx += s1_len; 125 | } 126 | 127 | ret[idx] = 0; 128 | 129 | free (*dst); 130 | (*dst) = ret; 131 | 132 | error = false; 133 | 134 | errorexit: 135 | 136 | if (error) { 137 | free (ret); 138 | ret = NULL; 139 | } 140 | 141 | return ret; 142 | } 143 | 144 | char *ds_str_append (char **dst, const char *s1, ...) 145 | { 146 | va_list ap; 147 | 148 | va_start (ap, s1); 149 | char *ret = ds_str_vappend (dst, s1, ap); 150 | va_end (ap); 151 | 152 | return ret; 153 | } 154 | 155 | /* ******************************************************************** */ 156 | 157 | size_t ds_str_vprintf (char **dst, const char *fmt, va_list ap) 158 | { 159 | size_t ret = 0; 160 | size_t tmprc = 0; 161 | char *tmp = NULL; 162 | va_list ac; 163 | 164 | *dst = NULL; 165 | 166 | va_copy (ac, ap); 167 | int rc = vsnprintf (*dst, ret, fmt, ac); 168 | va_end (ac); 169 | 170 | ret = rc + 1; 171 | 172 | if (!(tmp = realloc (*dst, ret))) { 173 | return 0; 174 | } 175 | 176 | *dst = tmp; 177 | rc = vsnprintf (*dst, ret, fmt, ap); 178 | tmprc = rc; 179 | if (tmprc >= ret) { 180 | free (*dst); 181 | *dst = NULL; 182 | return 0; 183 | } 184 | 185 | return ret; 186 | } 187 | 188 | size_t ds_str_printf (char **dst, const char *fmt, ...) 189 | { 190 | va_list ap; 191 | 192 | va_start (ap, fmt); 193 | size_t ret = ds_str_vprintf (dst, fmt, ap); 194 | va_end (ap); 195 | 196 | return ret; 197 | } 198 | 199 | /* ******************************************************************** */ 200 | 201 | char *ds_str_ltrim (char *src) 202 | { 203 | if (!src) 204 | return NULL; 205 | 206 | size_t begin = 0; 207 | size_t slen = strlen (src); 208 | 209 | if (!slen) 210 | return src; 211 | 212 | while ((src[begin]) && (isspace (src[begin]))) { 213 | begin++; 214 | } 215 | if (!src[begin]) { 216 | src[0] = 0; 217 | } 218 | 219 | slen++; 220 | 221 | memmove (&src[0], &src[begin], slen - begin); 222 | return src; 223 | } 224 | 225 | char *ds_str_rtrim (char *src) 226 | { 227 | if (!src) 228 | return NULL; 229 | 230 | size_t slen = strlen (src); 231 | size_t end = slen - 1; 232 | 233 | if (!slen) 234 | return src; 235 | 236 | while (end!=0 && isspace (src[end])) { 237 | src[end--] = 0; 238 | } 239 | if (end==0 && isspace (src[0])) { 240 | src[0] = 0; 241 | } 242 | 243 | return src; 244 | } 245 | 246 | char *ds_str_trim (char *src) 247 | { 248 | return ds_str_rtrim (ds_str_ltrim (src)); 249 | } 250 | 251 | /* ******************************************************************** */ 252 | 253 | char *ds_str_vchsubst (const char *src, int oldc, int newc, va_list ap) 254 | { 255 | char *ret = ds_str_dup (src); 256 | if (!ret) 257 | return NULL; 258 | 259 | while (oldc) { 260 | char *tmp = ret; 261 | while ((tmp = strchr (tmp, (char)oldc))) { 262 | *tmp++ = (char)newc; 263 | } 264 | oldc = va_arg (ap, int); 265 | if (oldc) 266 | newc = va_arg (ap, int); 267 | } 268 | return ret; 269 | } 270 | 271 | char *ds_str_chsubst (const char *src, int oldc, int newc, ...) 272 | { 273 | va_list ap; 274 | 275 | va_start (ap, newc); 276 | char *ret = ds_str_vchsubst (src, oldc, newc, ap); 277 | va_end (ap); 278 | 279 | return ret; 280 | } 281 | 282 | char *ds_str_strsubst (const char *src, 283 | const char *olds, const char *news, ...) 284 | { 285 | va_list ap; 286 | 287 | va_start (ap, news); 288 | char *ret = ds_str_vstrsubst (src, olds, news, ap); 289 | va_end (ap); 290 | 291 | return ret; 292 | } 293 | 294 | static char *ds_str_subst1 (char *src, const char *olds, const char *news) 295 | { 296 | bool error = true; 297 | char *ret = NULL; 298 | 299 | size_t olds_len = strlen (olds); 300 | char *tmp = NULL; 301 | char *startstr = src; 302 | 303 | while ((tmp = strstr (startstr, olds))) { 304 | 305 | *tmp++ = 0; 306 | 307 | if (!(ds_str_append (&ret, startstr, news, NULL))) { 308 | goto errorexit; 309 | } 310 | 311 | tmp += olds_len - 1; 312 | startstr = tmp; 313 | } 314 | 315 | if (!(ds_str_append (&ret, startstr, NULL))) { 316 | goto errorexit; 317 | } 318 | 319 | error = false; 320 | 321 | errorexit: 322 | if (error) { 323 | free (ret); 324 | ret = NULL; 325 | } 326 | 327 | return ret; 328 | } 329 | 330 | char *ds_str_vstrsubst (const char *src, 331 | const char *olds, const char *news, va_list ap) 332 | { 333 | bool error = true; 334 | char *ret = NULL; 335 | char *tmpsrc = NULL; 336 | 337 | error = false; 338 | 339 | if (!(ret = ds_str_dup (src))) 340 | goto errorexit; 341 | 342 | while (olds) { 343 | 344 | char *tmp = ds_str_subst1 (ret, olds, news); 345 | if (!tmp) 346 | goto errorexit; 347 | 348 | free (ret); 349 | ret = tmp; 350 | 351 | if ((olds = va_arg (ap, const char *))) { 352 | if (!(news = va_arg (ap, const char *))) { 353 | break; 354 | } 355 | } 356 | } 357 | 358 | error = false; 359 | 360 | errorexit: 361 | 362 | free (tmpsrc); 363 | 364 | if (error) { 365 | free (ret); 366 | ret = NULL; 367 | } 368 | 369 | return ret; 370 | } 371 | 372 | char *ds_str_substring (const char *src, size_t from_position, size_t nchars) 373 | { 374 | size_t srclen = strlen (src); 375 | size_t lastchar = nchars + from_position; 376 | 377 | if (lastchar > srclen) 378 | lastchar = srclen; 379 | 380 | char *ret = calloc ((lastchar - from_position) + 2, 1); 381 | if (!ret) 382 | return NULL; 383 | 384 | size_t dstidx = 0; 385 | for (size_t i=from_position; i<lastchar && i<srclen; i++) { 386 | ret[dstidx++] = src[i]; 387 | } 388 | 389 | return ret; 390 | } 391 | 392 | -------------------------------------------------------------------------------- /src/ds_str.h: -------------------------------------------------------------------------------- 1 | 2 | /* ************************************************************************** * 3 | * Frame (©2023 Lelanthran Manickum) * 4 | * * 5 | * This program comes with ABSOLUTELY NO WARRANTY. This is free software * 6 | * and you are welcome to redistribute it under certain conditions; see * 7 | * the LICENSE file for details. * 8 | * ****************************************************************************/ 9 | 10 | 11 | #ifndef H_DS_STR 12 | #define H_DS_STR 13 | 14 | #include <stdarg.h> 15 | #include <stdlib.h> 16 | 17 | #ifdef __cplusplus 18 | extern "C" { 19 | #endif 20 | 21 | // Make a copy of the src string, caller must free the result, NULL 22 | // returned on error. 23 | char *ds_str_dup (const char *src); 24 | 25 | // Concatenate all the strings given (ending the parameter list with a 26 | // NULL) into a single string that is returned which the caller must 27 | // free. NULL is returned on error 28 | char *ds_str_cat (const char *src, ...); 29 | char *ds_str_vcat (const char *src, va_list ap); 30 | 31 | // Append all the strings given in '...' (ending with a NULL) to 32 | // parameter '(*dst)'. Parameter '(*dst)' is reallocated as necessary 33 | // and therefore must be reallocatable (returned by malloc() or similar). 34 | // The reallocated '(*dst)' is also returned on success. 35 | // 36 | // NULL is returned on error. 37 | char *ds_str_append (char **dst, const char *s1, ...); 38 | char *ds_str_vappend (char **dst, const char *s1, va_list ap); 39 | 40 | // Perform a printf into a buffer allocated on demand. The parameter 41 | // '*dst' is allocated by this function and must be freed by the caller. 42 | // On success the length of '*dst' is returned, on failure zero is 43 | // returned. 44 | size_t ds_str_printf (char **dst, const char *fmt, ...); 45 | size_t ds_str_vprintf (char **dst, const char *fmt, va_list ap); 46 | 47 | // Trim the leading, the trailing or both the leading and the trailing 48 | // whitespace from the specified string. The operations are performed 49 | // in-place on the string provided. 50 | // 51 | // A pointer to the string is always returned - no errors are possible. 52 | // If the input is NULL then the return value is NULL as well. 53 | char *ds_str_ltrim (char *src); 54 | char *ds_str_rtrim (char *src); 55 | char *ds_str_trim (char *src); 56 | 57 | // Perform a character substitution on the source string. The caller 58 | // must free the returned value. NULL is returned on error. 59 | // 60 | // All occurrences of 'oldc' will be replaced with 'newc'. Thereafter 61 | // every two arguments will be interpreted as a new {oldc,newc} pair 62 | // until oldc is 0. 63 | // 64 | // Note that although oldc and newc are both of type int, they are cast 65 | // to char before usage. 66 | char *ds_str_chsubst (const char *src, int oldc, int newc, ...); 67 | char *ds_str_vchsubst (const char *src, int oldc, int newc, va_list ap); 68 | 69 | 70 | // Perform a string substitution on the source string. The caller must 71 | // free the returned value. NULL is returned on error. 72 | // 73 | // All occurrences of 'olds' will be replaced by 'news'. Thereafter, 74 | // every two arguments will be interpreted as a pair of {olds,news} and 75 | // substitutions continue until an instance of NULL is read for 'olds'. 76 | char *ds_str_strsubst (const char *src, 77 | const char *olds, const char *news, ...); 78 | char *ds_str_vstrsubst (const char *src, 79 | const char *olds, const char *news, va_list ap); 80 | 81 | // Copy nchars from the string src, starting at position from_position. The 82 | // caller is responsible for freeing the returned value. If the nchars results 83 | // in a out of bounds access (i.e. the caller specifies more character to copy 84 | // than exist in the src) the the copy is limited to the number of characters 85 | // available in the string. 86 | // 87 | // If the from_position is out of bounds, then an empty string is returned and 88 | // must still be freed by the caller. 89 | // 90 | // If there is no memory to allocate the return value then NULL is returned. 91 | char *ds_str_substring (const char *src, size_t from_position, size_t nchars); 92 | 93 | #ifdef __cplusplus 94 | }; 95 | #endif 96 | 97 | #endif 98 | 99 | -------------------------------------------------------------------------------- /src/frame.c: -------------------------------------------------------------------------------- 1 | 2 | /* ************************************************************************** * 3 | * Frame (©2023 Lelanthran Manickum) * 4 | * * 5 | * This program comes with ABSOLUTELY NO WARRANTY. This is free software * 6 | * and you are welcome to redistribute it under certain conditions; see * 7 | * the LICENSE file for details. * 8 | * ****************************************************************************/ 9 | 10 | #include <stdio.h> 11 | #include <stdlib.h> 12 | #include <string.h> 13 | #include <stdbool.h> 14 | #include <stdint.h> 15 | #include <time.h> 16 | 17 | #include <unistd.h> 18 | 19 | #include "ds_str.h" 20 | #include "frm.h" 21 | 22 | /* ********************************************************** 23 | * Command line handling: 24 | * prog [options] <command> [options] <subcommand> ... 25 | * 26 | * Essentially, options can come before commands, after commands 27 | * or both before and after commands, and multiple subcommands 28 | * may be present, with options once again appearing anywhere. 29 | */ 30 | 31 | // All options, separated by 0x1e 32 | static char *g_options = NULL; 33 | static void cline_parse_options (int argc, char **argv) 34 | { 35 | for (int i=1; i<argc; i++) { 36 | if (argv[i][0] == '-' && argv[i][1] == '-') { 37 | char *option = &argv[i][2]; 38 | ds_str_append (&g_options, "\x1e", option, NULL); 39 | } 40 | } 41 | ds_str_append (&g_options, "\x1e", NULL); 42 | } 43 | 44 | static char *cline_option_get (const char *name) 45 | { 46 | char *sterm = ds_str_cat ("\x1e", name, NULL); 47 | if (!sterm) { 48 | fprintf (stderr, "OOM error creating option search term\n"); 49 | return NULL; 50 | } 51 | char *position = strstr (g_options, sterm); 52 | // Option not found 53 | if (!position) { 54 | free (sterm); 55 | return NULL; 56 | } 57 | 58 | position += strlen (sterm); 59 | 60 | // Option found, no value found 61 | if (*position != '=') { 62 | free (sterm); 63 | return ds_str_dup (""); 64 | } 65 | 66 | // Option found, value found 67 | position++; 68 | char *end = strchr (position, '\x1e'); 69 | if (!end) { 70 | fprintf (stderr, "Internal error, no option delimiter \\x1e found\n"); 71 | free (sterm); 72 | return NULL; 73 | } 74 | 75 | size_t val_len = (end - position) + 1; 76 | char *value = calloc (val_len, 1); 77 | if (!value) { 78 | fprintf (stderr, "OOM error creating option value\n"); 79 | free (sterm); 80 | return NULL; 81 | } 82 | 83 | memcpy (value, position, val_len - 1); 84 | free (sterm); 85 | return value; 86 | } 87 | 88 | 89 | // All commands, separated by 0x1e 90 | static char *g_commands = NULL; 91 | static void cline_parse_commands (int argc, char **argv) 92 | { 93 | for (int i=1; i<argc; i++) { 94 | if (argv[i][0] != '-' && argv[i][1] != '-') { 95 | ds_str_append (&g_commands, argv[i], "\x1e", NULL); 96 | } 97 | } 98 | ds_str_append (&g_commands, "\x1e", NULL); 99 | } 100 | 101 | static char *cline_command_get (size_t index) 102 | { 103 | char *cmd = g_commands; 104 | for (size_t i=0; i<index; i++) { 105 | cmd = strchr (cmd, '\x1e'); 106 | if (!cmd) 107 | break; 108 | } 109 | if (!cmd) { 110 | fprintf (stderr, "Request for command [%zu] failed\n", index); 111 | return NULL; 112 | } 113 | 114 | if (*cmd == '\x1e') 115 | cmd++; 116 | 117 | char *end = strchr (cmd, '\x1e'); 118 | if (!end) { 119 | fprintf (stderr, "Internal error, no command delimiter \\x1e found\n"); 120 | return NULL; 121 | } 122 | 123 | size_t cmd_len = (end - cmd) + 1; 124 | char *ret = calloc (cmd_len, 1); 125 | if (!ret) { 126 | fprintf (stderr, "OOM error returning command\n"); 127 | return NULL; 128 | } 129 | 130 | memcpy (ret, cmd, cmd_len - 1); 131 | return ret; 132 | } 133 | 134 | static char edlin[1024 * 1024]; 135 | static char *run_editor (const char *default_file_contents) 136 | { 137 | char *message = NULL; 138 | char *editor = getenv ("EDITOR"); 139 | if (!editor || !editor[0]) { 140 | FRM_ERROR ("Warning: no $EDITOR specified.\n"); 141 | printf ("Enter the message, ending with a single period " 142 | "on a line by itself\n"); 143 | while ((fgets (edlin, sizeof edlin -1, stdin))!=NULL) { 144 | if (edlin[0] == '.') 145 | break; 146 | if (!(ds_str_append (&message, edlin, NULL))) { 147 | FRM_ERROR ("OOM reading edlin input\n"); 148 | free (message); 149 | return NULL; 150 | } 151 | } 152 | } else { 153 | char fname[] = "frame-tmpfile-XXXXXX"; 154 | int fd = mkstemp (fname); 155 | if (fd < 0) { 156 | FRM_ERROR ("Failed to create temporary file: %m\n"); 157 | return NULL; 158 | } 159 | close (fd); 160 | if (!default_file_contents) { 161 | default_file_contents = "Enter a description of this frame here"; 162 | } 163 | 164 | if (!(frm_writefile (fname, 165 | "PATH: ", default_file_contents, 166 | "\n", 167 | "\n", 168 | "Replace this content with your message.", 169 | "\n", 170 | "There is no limit on the length of messages\n", 171 | NULL))) { 172 | FRM_ERROR ("Failed to edit temporary file [%s]: %m\n", fname); 173 | return NULL; 174 | } 175 | char *shcmd = ds_str_cat (editor, " ", fname, NULL); 176 | printf ("Waiting for [%s] to return\n", shcmd); 177 | int exitcode = system (shcmd); 178 | free (shcmd); 179 | if (exitcode != 0) { 180 | FRM_ERROR ("Editor aborted, aborting: %m\n"); 181 | if ((unlink (fname))!=0) { 182 | FRM_ERROR ("Error: Failed to remove tmpfile [%s]: %m\n", fname); 183 | } 184 | return NULL; 185 | } 186 | message = frm_readfile (fname); 187 | if ((unlink (fname))!=0) { 188 | FRM_ERROR ("Error: Failed to remove tmpfile [%s]\n", fname); 189 | } 190 | if (!message) { 191 | FRM_ERROR ("Failed to read editor output, aborting\n"); 192 | return NULL; 193 | } 194 | } 195 | return message; 196 | } 197 | 198 | static void print_helpmsg (void) 199 | { 200 | static const char *msg[] = { 201 | " Frame (© 2023 Lelanthran Manickum)", 202 | "", 203 | " This program comes with ABSOLUTELY NO WARRANTY. This is free software", 204 | " and you are welcome to redistribute it under certain conditions; see", 205 | " the LICENSE file for details.", 206 | "", 207 | "frame [options] <command> [options] <subcommand>", 208 | "", 209 | " Options are of the form '--name', '--name=' and '--name=value'. The first", 210 | "two forms are for boolean options which are either set or unset and do not", 211 | "require any value. The third form is for options that require a value.", 212 | "", 213 | " Commands and subcommands are of the form 'command'. Some commands require", 214 | "specific options and/or mandatory subcommands. Commands that require either", 215 | "options or subcommands will be decribed below.", 216 | "", 217 | " Commands must be one of help, create, history, status, push, replace,", 218 | "append, up, down, switch, pop, delete, list or match.", 219 | "", 220 | "Options:", 221 | "", 222 | " --help Print this page and exit.", 223 | "", 224 | " --force Force an action that against the program's wishes,", 225 | " for example popping a frame that is not empty.", 226 | "", 227 | " --frame=<path> Execute in the context of the specified frame. Frame can", 228 | " be specified with a relative path (\"../..\") or an", 229 | " absolute path from root (\"root/frame\").", 230 | " For example, to create a sibling frame, use the command:", 231 | " frame --frame=../ new 'New Task Name'", 232 | "", 233 | " --dbpath=<path> Specify the location of the database path. Defaults to", 234 | " '$HOME/.framedb' unless overridden by this option.", 235 | " The Windows default is '$HOMEDRIVE\\$HOMEPATH\\.framedb'", 236 | "", 237 | " --message=<string> Provides the message for any command that requires a", 238 | " message (such as 'push', 'replace', etc). If this option", 239 | " is not present and the command requires a message, then", 240 | " the editor specified with $EDITOR is used. If $EDITOR", 241 | " is empty, then a prompt for a message is issued on the", 242 | " standard input.", 243 | "", 244 | " --from-root Specify that the match command must search for matches", 245 | " from the root frame. If this option is not present then", 246 | " matches are, by default, made only from the current frame", 247 | " onwards (down the tree).", 248 | "", 249 | " --invert Perform an inverted search when matching using a search", 250 | " term. By default the match command finds all nodes that", 251 | " match the search term provided. Using this flag causes", 252 | " the match command to find all nodes that *DON'T* match", 253 | " the search term.", 254 | "", 255 | " --quiet Suppress all non-functional stdout messages, such as", 256 | " the copyright notice.", 257 | "", 258 | "Commands:", 259 | "", 260 | "help", 261 | " Print this message and exit.", 262 | "", 263 | "create", 264 | " Create a new frame database. If --dbpath is specified then it is used as the", 265 | " location of the new database. If it is not then $HOME/.framdb is used instead.", 266 | "", 267 | "history [count]", 268 | " Display the history of all nodes visited, with a number that can be used", 269 | " in the 'back' command (see 'back' below). The [count] value specifies how", 270 | " many items to display. If [count] is omitted it defaults to 10. To list", 271 | " all items in the history (which may be very large) use '0' as the count.", 272 | "", 273 | "back [number]", 274 | " Jump to the nth item in the history, as specified by [number]. If [number]", 275 | " is omitted then '1' is used. The 'history' command helpfully lists a number", 276 | " next to each element that can be used to determine what number in the", 277 | " history to jump to. Specifying '0' is pointless.", 278 | "", 279 | "status", 280 | " Display the status of the current frame.", 281 | "", 282 | "push", 283 | " Create a new frame as the child of the current frame AND switches to it. If a", 284 | " message is specified with '--message=<string>' then it will be used as the", 285 | " contents of the new frame. If no message is specified with '--message=<string>'", 286 | " then $EDITOR will be started to allow the user to enter a message. If $EDITOR", 287 | " is not set, the user will be prompted for a message.", 288 | "", 289 | "new", 290 | " Create a new frame as the child of the current frame WITHOUT switching to it. If", 291 | " a message is specified with '--message=<string>' then it will be used as the", 292 | " contents of the new frame. If no message is specified with '--message=<string>'", 293 | " then $EDITOR will be started to allow the user to enter a message. If $EDITOR", 294 | " is not set, the user will be prompted for a message.", 295 | "", 296 | "replace", 297 | " Overwrite the content of the current frame with the provided message. See ", 298 | " option '--message=<string>'.", 299 | "", 300 | "append", 301 | " Appends the provided message (see option '--message' and command 'push') to", 302 | " the current frame.", 303 | "", 304 | "top", 305 | " Changes the current frame to root frame (i.e. top of the tree).", 306 | "", 307 | "up", 308 | " Changes the current frame to the parent of the current frame.", 309 | "", 310 | "down <name>", 311 | " Changes the current frame to the child frame named by 'name'.", 312 | "", 313 | "switch <path>", 314 | " Changes the current frame to the non-child frame named by <path>.", 315 | "", 316 | "pop", 317 | " Deletes the current frame and set the current frame to the parent of the", 318 | " deleted frame.", 319 | "", 320 | "delete <path>", 321 | " Deletes the frame named by <path>. The current frame is not changed.", 322 | "", 323 | "list", 324 | " Lists all descendents of the current frame.", 325 | "", 326 | "tree", 327 | " Display a tree of all the nodes starting at the root frame.", 328 | "", 329 | "rename <newname>", 330 | " Rename the current node to <newname>.", 331 | "", 332 | "match <sterm> [--from-root] [--invert]", 333 | " Lists the nodes that match the search term <sterm>, starting at the current", 334 | " frame. If '--from-root' is specified then the search is performed from the", 335 | " root frame and not the current frame. If --invert is specified, then the search", 336 | " is performed for all those nodes *NOT MATCHING* the search term <sterm>.", 337 | "", 338 | NULL, 339 | }; 340 | for (size_t i=0; msg[i]; i++) { 341 | printf ("%s\n", msg[i]); 342 | } 343 | printf ("\n"); 344 | } 345 | 346 | static void status (frm_t *frm) 347 | { 348 | char *current = frm_current (frm); 349 | char *payload = frm_payload (); 350 | char *mtime = frm_date_str (); 351 | 352 | printf ("Current frame\n %s\n", current); 353 | printf ("\nNotes (%s)\n", mtime); 354 | char *sptr = NULL; 355 | char *tok = strtok_r (payload, "\n", &sptr); 356 | do { 357 | printf (" %s\n", tok); 358 | } while ((tok = strtok_r (NULL, "\n", &sptr))); 359 | printf ("\n"); 360 | free (current); 361 | free (mtime); 362 | free (payload); 363 | } 364 | 365 | static void current (frm_t *frm) 366 | { 367 | char *current = frm_current (frm); 368 | char *mtime = frm_date_str (); 369 | 370 | printf ("%s: %s\n", current, mtime); 371 | free (current); 372 | free (mtime); 373 | } 374 | 375 | #ifdef PLATFORM_Windows 376 | static void ctime_r (time_t *date, char *dst) 377 | { 378 | strcpy (dst, ctime (date)); 379 | } 380 | #endif 381 | 382 | int print_tree (const frm_node_t *node, size_t level) 383 | { 384 | #define INDENT(x) for (size_t i=0; i<x; i++) {\ 385 | putchar (' ');\ 386 | }\ 387 | 388 | if (!node) { 389 | fprintf (stderr, "Internal error, accessing NULL object\n"); 390 | return EXIT_FAILURE; 391 | } 392 | 393 | const char *name = frm_node_name (node); 394 | uint64_t date = frm_node_date (node); 395 | char strdate[30]; 396 | 397 | if (!name) { 398 | fprintf (stderr, "Internal error, frame name missing\n"); 399 | return EXIT_FAILURE; 400 | } 401 | if (date == (uint64_t)-1) { 402 | fprintf (stderr, "Internal error, frame date missing\n"); 403 | return EXIT_FAILURE; 404 | } 405 | 406 | ctime_r ((time_t *)&date, strdate); 407 | char *tmp = strchr (strdate, '\n'); 408 | if (tmp) 409 | *tmp = 0; 410 | 411 | INDENT(level); printf ("%s (%s)\n", name, strdate); 412 | 413 | size_t nchildren = frm_node_nchildren (node); 414 | for (size_t i=0; i<nchildren; i++) { 415 | const frm_node_t *child = frm_node_child (node, i); 416 | if ((print_tree (child, level + 3))!=EXIT_SUCCESS) { 417 | fprintf (stderr, "Error printing child %zu of [%s]\n", i, name); 418 | return EXIT_FAILURE; 419 | } 420 | } 421 | #undef INDENT 422 | return EXIT_SUCCESS; 423 | } 424 | 425 | int main (int argc, char **argv) 426 | { 427 | int ret = EXIT_SUCCESS; 428 | cline_parse_options (argc, argv); 429 | cline_parse_commands (argc, argv); 430 | 431 | // TODO: At some point maybe verify that the options specified are applicable 432 | // to the command. 433 | char *command = cline_command_get (0); 434 | char *help = cline_option_get ("help"); 435 | char *force = cline_option_get ("force"); 436 | char *dbpath = cline_option_get ("dbpath"); 437 | char *message = cline_option_get ("message"); 438 | char *from_root = cline_option_get ("from-root"); 439 | char *invert = cline_option_get ("invert"); 440 | char *quiet = cline_option_get ("quiet"); 441 | char *frame = cline_option_get ("frame"); 442 | char *oldpath = NULL; 443 | 444 | frm_t *frm = NULL; 445 | 446 | if (!command || !command[0]) { 447 | free (command); 448 | command = ds_str_dup("status"); 449 | printf ("No command specified (try --help). Defaulting to 'status'\n"); 450 | } 451 | 452 | if ((strcmp (command, "help")==0) || help) { 453 | print_helpmsg (); 454 | ret = EXIT_FAILURE; 455 | goto cleanup; 456 | } 457 | 458 | 459 | // TODO: have a more nuanced determination of when the copyright 460 | // notice should be printed. 461 | if (quiet==NULL) { 462 | printf ("Frame %s, (© 2023 Lelanthran Manickum)\n", frame_version); 463 | } 464 | 465 | if (!dbpath) { 466 | 467 | const char *home = frm_homepath (); 468 | if (!home || !home[0]) { 469 | fprintf (stderr, "No --dbpath specified and $HOME is not set\n"); 470 | ret = EXIT_FAILURE; 471 | goto cleanup; 472 | } 473 | dbpath = ds_str_cat (home, FRM_DIR_SEPARATOR, ".framedb", NULL); 474 | 475 | if (!dbpath) { 476 | fprintf (stderr, "OOM error copying $HOME\n"); 477 | ret = EXIT_FAILURE; 478 | goto cleanup; 479 | } 480 | } 481 | 482 | // Check for each command in turn. Could be done in an array, but I don't care 483 | // enough to do it. 484 | if ((strcmp (command, "create"))==0) { 485 | if ((frm = frm_create (dbpath))) { 486 | fprintf (stderr, "Created framedb at [%s]\n", dbpath); 487 | ret = EXIT_SUCCESS; 488 | } else { 489 | fprintf (stderr, "Failed to create framedb at [%s]: %m\n", dbpath); 490 | ret = EXIT_FAILURE; 491 | } 492 | goto cleanup; 493 | } 494 | 495 | if (!(frm = frm_init (dbpath))) { 496 | fprintf (stderr, "Failed to load db from [%s]\n", dbpath); 497 | ret = EXIT_FAILURE; 498 | goto cleanup; 499 | } 500 | 501 | if (frame && frame[1]) { 502 | oldpath = frm_switch_path (frm, frame); 503 | if (!oldpath) { 504 | fprintf (stderr, "Specified a --frame path that is invalid: [%s]: %m\n", 505 | frame); 506 | ret = EXIT_FAILURE; 507 | goto cleanup; 508 | } 509 | } 510 | 511 | if ((strcmp (command, "history"))==0) { 512 | char *subcmd = cline_command_get (1); 513 | size_t count = 10; 514 | if (subcmd && subcmd[0]) { 515 | if (((sscanf (subcmd, "%zu", &count)))!=1) { 516 | if (!quiet) { 517 | fprintf (stderr, "Specified count of [%s] is invalid\n", subcmd); 518 | fprintf (stderr, "Using default of 10 for history count\n"); 519 | } 520 | count = 10; 521 | } 522 | } else { 523 | if (!quiet) { 524 | fprintf (stderr, "No count specified, listing last 10 items\n"); 525 | } 526 | } 527 | free (subcmd); 528 | 529 | if (count == 0) 530 | count = (size_t)-1; 531 | 532 | char *history = frm_history (frm, count); 533 | char *sptr = NULL; 534 | char *tok = strtok_r (history, "\n", &sptr); 535 | size_t i=0; 536 | printf ("Frame history\n"); 537 | char indicator = '*'; 538 | do { 539 | printf ("%c %5zu: %s\n", indicator, i++, tok); 540 | indicator = ' '; 541 | } while ((tok = strtok_r (NULL, "\n", &sptr))); 542 | printf ("\n"); 543 | free (history); 544 | goto cleanup; 545 | } 546 | 547 | if ((strcmp (command, "status"))==0) { 548 | status (frm); 549 | goto cleanup; 550 | } 551 | 552 | if ((strcmp (command, "current"))==0) { 553 | current (frm); 554 | goto cleanup; 555 | } 556 | 557 | if ((strcmp (command, "push"))==0) { 558 | char *name = cline_command_get(1); 559 | if (!name || !name[0]) { 560 | fprintf (stderr, "Must specify a name for the new frame being pushed\n"); 561 | free (name); 562 | ret = EXIT_FAILURE; 563 | goto cleanup; 564 | } 565 | char *message = cline_option_get ("message"); 566 | if (!message) { 567 | char *current = frm_current (frm); 568 | if (!current) { 569 | fprintf (stderr, "Warning: Failed to get the current path\n"); 570 | } 571 | char *fpath = ds_str_cat (current, "/", name, NULL); 572 | message = run_editor (fpath); 573 | free (fpath); 574 | free (current); 575 | } 576 | if (!message) { 577 | fprintf (stderr, "No edit message, aborting\n"); 578 | free (name); 579 | ret = EXIT_FAILURE; 580 | goto cleanup; 581 | } 582 | 583 | if (!(frm_push (frm, name, message))) { 584 | fprintf (stderr, "Failed to create new frame\n"); 585 | free (name); 586 | free (message); 587 | ret = EXIT_FAILURE; 588 | goto cleanup; 589 | } 590 | free (name); 591 | free (message); 592 | name = frm_current (frm); 593 | printf ("Created new frame [%s]\n", name); 594 | free (name); 595 | goto cleanup; 596 | } 597 | 598 | if ((strcmp (command, "new"))==0) { 599 | char *name = cline_command_get(1); 600 | if (!name || !name[0]) { 601 | fprintf (stderr, "Must specify a name for the new frame\n"); 602 | free (name); 603 | ret = EXIT_FAILURE; 604 | goto cleanup; 605 | } 606 | char *message = cline_option_get ("message"); 607 | if (!message) { 608 | char *current = frm_current (frm); 609 | if (!current) { 610 | fprintf (stderr, "Warning: Failed to get the current path\n"); 611 | } 612 | char *fpath = ds_str_cat (current, "/", name, NULL); 613 | message = run_editor (fpath); 614 | free (fpath); 615 | free (current); 616 | } 617 | if (!message) { 618 | fprintf (stderr, "No edit message, aborting\n"); 619 | free (name); 620 | ret = EXIT_FAILURE; 621 | goto cleanup; 622 | } 623 | 624 | if (!(frm_new (frm, name, message))) { 625 | fprintf (stderr, "Failed to create new frame\n"); 626 | free (name); 627 | free (message); 628 | ret = EXIT_FAILURE; 629 | goto cleanup; 630 | } 631 | free (name); 632 | free (message); 633 | name = frm_current (frm); 634 | printf ("Created new frame [%s]\n", name); 635 | status(frm); 636 | free (name); 637 | goto cleanup; 638 | } 639 | 640 | if ((strcmp (command, "replace"))==0) { 641 | char *message = cline_option_get ("message"); 642 | if (!message) { 643 | message = run_editor (NULL); 644 | } 645 | if (!message) { 646 | fprintf (stderr, "No edit message, aborting\n"); 647 | ret = EXIT_FAILURE; 648 | goto cleanup; 649 | } 650 | 651 | if (!(frm_payload_replace (message))) { 652 | fprintf (stderr, "Failed to replace message of current frame: %m\n"); 653 | ret = EXIT_FAILURE; 654 | } 655 | free (message); 656 | goto cleanup; 657 | } 658 | 659 | if ((strcmp (command, "edit"))==0) { 660 | const char *editor = getenv ("EDITOR"); 661 | if (!editor || !editor[0]) { 662 | fprintf (stderr, "No editor specified in $EDITOR\n"); 663 | ret = EXIT_FAILURE; 664 | goto cleanup; 665 | } 666 | char *fname = frm_payload_fname (); 667 | if (!fname) { 668 | fprintf (stderr, "Failed to retrieve filename of current frame: %m\n"); 669 | ret = EXIT_FAILURE; 670 | goto cleanup; 671 | } 672 | 673 | char *shcmd = ds_str_cat (editor, " '", fname, "'", NULL); 674 | if (!shcmd) { 675 | fprintf (stderr, "OOM error allocating shell command for editor [%s]\n", 676 | editor); 677 | free (fname); 678 | ret = EXIT_FAILURE; 679 | goto cleanup; 680 | } 681 | 682 | ret = EXIT_SUCCESS; 683 | if ((system (shcmd))!=0) { 684 | fprintf (stderr, "Failed to execute shell command [%s]: %m\n", shcmd); 685 | ret = EXIT_FAILURE; 686 | } 687 | 688 | free (fname); 689 | free (shcmd); 690 | current (frm); 691 | goto cleanup; 692 | } 693 | 694 | if ((strcmp (command, "append"))==0) { 695 | char *message = cline_option_get ("message"); 696 | if (!message) { 697 | message = run_editor (NULL); 698 | } 699 | if (!message) { 700 | fprintf (stderr, "No edit message, aborting\n"); 701 | ret = EXIT_FAILURE; 702 | goto cleanup; 703 | } 704 | 705 | if (!(frm_payload_append (message))) { 706 | fprintf (stderr, "Failed to append message to current frame: %m\n"); 707 | ret = EXIT_FAILURE; 708 | } 709 | free (message); 710 | current (frm); 711 | goto cleanup; 712 | } 713 | 714 | if ((strcmp (command, "top"))==0) { 715 | if (!(frm_top (frm))) { 716 | fprintf (stderr, "Failed to switch to top of tree\n"); 717 | ret = EXIT_FAILURE; 718 | } 719 | if (ret == EXIT_SUCCESS) { 720 | status (frm); 721 | } 722 | goto cleanup; 723 | } 724 | 725 | if ((strcmp (command, "up"))==0) { 726 | if (!(frm_up (frm))) { 727 | fprintf (stderr, "Failed to move a frame up the tree\n"); 728 | ret = EXIT_FAILURE; 729 | } 730 | if (ret == EXIT_SUCCESS) { 731 | status (frm); 732 | } 733 | goto cleanup; 734 | } 735 | 736 | if ((strcmp (command, "down"))==0) { 737 | char *target = cline_command_get(1); 738 | if (!target || !target[0]) { 739 | fprintf (stderr, "Must specify name of child frame to switch to\n"); 740 | free (target); 741 | ret = EXIT_FAILURE; 742 | goto cleanup; 743 | } 744 | if (!(frm_down (frm, target))) { 745 | fprintf (stderr, "Failed to switch to frame [%s]\n", target); 746 | ret = EXIT_FAILURE; 747 | } 748 | free (target); 749 | if (ret == EXIT_SUCCESS) { 750 | status (frm); 751 | } 752 | goto cleanup; 753 | } 754 | 755 | if ((strcmp (command, "switch"))==0) { 756 | char *target = cline_command_get(1); 757 | if (!target || !target[0]) { 758 | fprintf (stderr, "Must specify an absolute path to switch to\n"); 759 | free (target); 760 | ret = EXIT_FAILURE; 761 | goto cleanup; 762 | } 763 | if (!(frm_switch (frm, target))) { 764 | fprintf (stderr, "Failed to switch to frame [%s]\n", target); 765 | free (target); 766 | ret = EXIT_FAILURE; 767 | goto cleanup; 768 | } 769 | free (target); 770 | if (ret == EXIT_SUCCESS) { 771 | status (frm); 772 | } 773 | goto cleanup; 774 | } 775 | 776 | if ((strcmp (command, "back"))==0) { 777 | char *subcommand = cline_command_get (1); 778 | if (!subcommand || !subcommand[0]) { 779 | subcommand = ds_str_dup ("1"); 780 | if (!subcommand) { 781 | fprintf (stderr, "OOM error allocating default parameter for 'back'\n"); 782 | ret = EXIT_FAILURE; 783 | goto cleanup; 784 | } 785 | } 786 | 787 | size_t index = 0; 788 | if ((sscanf (subcommand, "%zu", &index))!=1) { 789 | fprintf (stderr, "Invalid number: [%s]\n", subcommand); 790 | free (subcommand); 791 | ret = EXIT_FAILURE; 792 | goto cleanup; 793 | } 794 | 795 | if (!(frm_back (frm, index))) { 796 | fprintf (stderr, "Failed to switch to history item %zu\n", index); 797 | ret = EXIT_FAILURE; 798 | } 799 | free (subcommand); 800 | if (ret == EXIT_SUCCESS) { 801 | status (frm); 802 | } 803 | goto cleanup; 804 | } 805 | 806 | 807 | if ((strcmp (command, "pop"))==0) { 808 | bool force_pop = false; 809 | if (force) { 810 | force_pop = true; 811 | printf ("Force-popping (you may lose subframes of this frame)\n"); 812 | } 813 | 814 | if (!(frm_pop (frm, force_pop))) { 815 | fprintf (stderr, "Failed to pop current frame: %m\n"); 816 | ret = EXIT_FAILURE; 817 | } 818 | if (ret == EXIT_SUCCESS) { 819 | status (frm); 820 | } 821 | goto cleanup; 822 | } 823 | 824 | if ((strcmp (command, "delete"))==0) { 825 | char *target = cline_command_get (1); 826 | if (!target || !target[0]) { 827 | fprintf (stderr, "Must specify a target frame to delete\n"); 828 | ret = EXIT_FAILURE; 829 | } 830 | 831 | if (!(frm_delete (frm, target))) { 832 | fprintf (stderr, "Failed to delete current frame: %m\n"); 833 | ret = EXIT_FAILURE; 834 | } 835 | free (target); 836 | if (ret == EXIT_SUCCESS) { 837 | status (frm); 838 | } 839 | goto cleanup; 840 | } 841 | 842 | if ((strcmp (command, "list"))==0) { 843 | char *from = cline_command_get(1); 844 | 845 | char **results = frm_list (frm, from[0] ? from : NULL); 846 | free (from); 847 | 848 | if (!results) { 849 | fprintf (stderr, "Internal error during listing\n"); 850 | ret = EXIT_FAILURE; 851 | goto cleanup; 852 | } 853 | for (size_t i=0; results[i]; i++) { 854 | printf (" %s\n", results[i]); 855 | free (results[i]); 856 | } 857 | free (results); 858 | goto cleanup; 859 | } 860 | 861 | if ((strcmp (command, "match"))==0) { 862 | char *sterm = cline_command_get (1); 863 | if (!sterm || !sterm[0]) { 864 | if (!quiet) { 865 | fprintf (stderr, "No search term specified, returning everything\n"); 866 | } 867 | free (sterm); 868 | if (!(sterm = ds_str_dup (""))) { 869 | fprintf (stderr, "OOM error allocating search term\n"); 870 | ret = EXIT_FAILURE; 871 | goto cleanup; 872 | } 873 | } 874 | 875 | char **results = NULL; 876 | uint32_t flags = 0; 877 | if (invert) { 878 | flags |= FRM_MATCH_INVERT; 879 | } 880 | 881 | if (!from_root) { 882 | results = frm_match (frm, sterm, flags); 883 | } else { 884 | results = frm_match_from_root (frm, sterm, flags); 885 | } 886 | 887 | if (!results) { 888 | fprintf (stderr, "Internal error searching framedb\n"); 889 | ret = EXIT_FAILURE; 890 | } else { 891 | for (size_t i=0; results[i]; i++) { 892 | printf (" %s\n", results[i]); 893 | free (results[i]); 894 | } 895 | free (results); 896 | } 897 | 898 | free (sterm); 899 | goto cleanup; 900 | } 901 | 902 | if ((strcmp (command, "tree"))==0) { 903 | frm_node_t *root = frm_node_create (frm); 904 | if (!root) { 905 | fprintf (stderr, "Failed to find root frame\n"); 906 | ret = EXIT_FAILURE; 907 | goto cleanup; 908 | } 909 | 910 | ret = print_tree (root, 0); 911 | frm_node_free (root); 912 | goto cleanup; 913 | } 914 | 915 | if ((strcmp (command, "rename"))==0) { 916 | char *newname = cline_command_get(1); 917 | if (!newname || !newname[0]) { 918 | fprintf (stderr, "Must specify a new name for the current node\n"); 919 | free (newname); 920 | ret = EXIT_FAILURE; 921 | goto cleanup; 922 | } 923 | if (!(frm_rename (frm, newname))) { 924 | fprintf (stderr, "Failed to rename frame to [%s]\n", newname); 925 | ret = EXIT_FAILURE; 926 | } 927 | free (newname); 928 | if (ret == EXIT_SUCCESS) { 929 | status (frm); 930 | } 931 | goto cleanup; 932 | } 933 | // The default, with no arguments, is to print out the help message. 934 | // If we got to this point we have a command but it is unrecognised. 935 | fprintf (stderr, "Unrecognised command [%s]\n", command); 936 | ret = EXIT_FAILURE; 937 | 938 | cleanup: 939 | frm_close (frm); 940 | free (command); 941 | free (help); 942 | free (force); 943 | free (dbpath); 944 | free (message); 945 | free (from_root); 946 | free (invert); 947 | free (quiet); 948 | free (frame); 949 | free (oldpath); // No need to change back as we are exiting now. 950 | 951 | free (g_options); 952 | free (g_commands); 953 | return ret; 954 | } 955 | 956 | -------------------------------------------------------------------------------- /src/frm.h: -------------------------------------------------------------------------------- 1 | 2 | /* ************************************************************************** * 3 | * Frame (©2023 Lelanthran Manickum) * 4 | * * 5 | * This program comes with ABSOLUTELY NO WARRANTY. This is free software * 6 | * and you are welcome to redistribute it under certain conditions; see * 7 | * the LICENSE file for details. * 8 | * ****************************************************************************/ 9 | 10 | 11 | #ifndef H_FRM 12 | #define H_FRM 13 | 14 | #define FRM_ERROR(...) do {\ 15 | fprintf (stderr, "[%s:%i] ", __FILE__, __LINE__);\ 16 | fprintf (stderr, __VA_ARGS__);\ 17 | } while (0) 18 | 19 | #define FRM_MATCH_INVERT (0x01 << 0) 20 | 21 | typedef struct frm_t frm_t; 22 | typedef struct frm_node_t frm_node_t; 23 | 24 | 25 | #ifdef PLATFORM_Windows 26 | #define FRM_DIR_SEPARATOR "\\" 27 | #else 28 | #define FRM_DIR_SEPARATOR "/" 29 | #endif 30 | 31 | #ifdef __cplusplus 32 | extern "C" { 33 | #endif 34 | 35 | /* Some memory functions because FreePascal has poor support for 36 | * freeing memory allocated by libraries. 37 | */ 38 | void frm_mem_free (void *ptr); 39 | void frm_strarray_free (char **array); 40 | 41 | /* A few utility functions: simple ways to read and write entire 42 | * files, get the correct home directory regardless of platform. 43 | */ 44 | char *frm_readfile (const char *fname); 45 | bool frm_vwritefile (const char *fname, const char *data, va_list ap); 46 | bool frm_writefile (const char *fname, const char *data, ...); 47 | const char *frm_homepath (void); 48 | 49 | /* Create a new framedb, initialise an existing one and close the 50 | * handle to the framedb. 51 | */ 52 | frm_t *frm_create (const char *dbpath); 53 | frm_t *frm_init (const char *dbpath); 54 | void frm_close (frm_t *frm); 55 | 56 | /* Retrieve information: history, current frame name, current 57 | * frame payload, current frame date (in two formats). 58 | */ 59 | char *frm_history (frm_t *frm, size_t count); 60 | char *frm_current (frm_t *frm); 61 | char *frm_payload (void); 62 | uint64_t frm_date_epoch (void); 63 | char *frm_date_str (void); 64 | const char *frm_lastmsg (frm_t *frm); 65 | 66 | /* Add/create information: new frame (new creates a new one and then 67 | * returns, push creates a new one and switches to it), replace the 68 | * payload, append to payload and return the payload filename. 69 | */ 70 | bool frm_new (frm_t *frm, const char *name, const char *message); 71 | bool frm_push (frm_t *frm, const char *name, const char *message); 72 | bool frm_payload_replace (const char *message); 73 | bool frm_payload_append (const char *message); 74 | char *frm_payload_fname (void); 75 | 76 | /* Navigational functions, including deletion when popping. 77 | */ 78 | bool frm_top (frm_t *frm); 79 | bool frm_up (frm_t *frm); 80 | bool frm_down (frm_t *frm, const char *target); 81 | bool frm_switch (frm_t *frm, const char *target); 82 | bool frm_switch_direct (frm_t *frm, const char *target); 83 | bool frm_back (frm_t *frm, size_t index); 84 | bool frm_delete (frm_t *frm, const char *target); 85 | bool frm_pop (frm_t *frm, bool force); 86 | bool frm_rename (frm_t *frm, const char *newname); 87 | 88 | /* Search/listing functions. 89 | */ 90 | char **frm_list (frm_t *frm, const char *from); 91 | char **frm_match (frm_t *frm, const char *sterm, uint32_t flags); 92 | char **frm_match_from_root (frm_t *frm, const char *sterm, uint32_t flags); 93 | 94 | /* Tree functions. All the other frame functions are designed to 95 | * return one of the following: 96 | * 1. A single value (e.g. frm_current()). 97 | * 2. A list of values (e.g. frm_history()). 98 | * 99 | * Such return values are easy for the caller to consume even if the 100 | * caller is not a C program. The functions below return or operate 101 | * on a navigatable tree. This is necessary when the caller wants to 102 | * present a tree view of the frame tree structure. 103 | */ 104 | 105 | /* Create and destroy the tree of nodes. Creation always returns 106 | * the tree starting at the root node. Freeing anything but the root 107 | * node is gauranteed to leak memory. 108 | */ 109 | frm_node_t *frm_node_create (frm_t *frm); 110 | void frm_node_free (frm_node_t *rootnode); 111 | 112 | /* Get the tree name, date and full path. Full path is useful to 113 | * directly navigate to a particular node. 114 | */ 115 | const char *frm_node_name (const frm_node_t *node); 116 | uint64_t frm_node_date (const frm_node_t *node); 117 | char *frm_node_fpath (const frm_node_t *node); 118 | 119 | /* Functions necessary for recursing. Count the number of children, 120 | * return child node as specifed by the index, return parent node 121 | * if any, return root node of the tree, return the node identified 122 | * by full path. 123 | */ 124 | size_t frm_node_nchildren (const frm_node_t *node); 125 | const frm_node_t *frm_node_child (const frm_node_t *node, size_t index); 126 | const frm_node_t *frm_node_parent (const frm_node_t *node); 127 | const frm_node_t *frm_node_root (const frm_node_t *node); 128 | const frm_node_t *frm_node_find (const frm_node_t *node, const char *fpath); 129 | 130 | /* These functions are not very useful to the caller and should be avoided 131 | * in favour of the functions above. 132 | */ 133 | char *frm_switch_path (frm_t *frm, const char *from); 134 | 135 | #ifdef __cplusplus 136 | }; 137 | #endif 138 | 139 | 140 | #endif 141 | 142 | 143 | -------------------------------------------------------------------------------- /swig-input.swig: -------------------------------------------------------------------------------- 1 | %module frm 2 | %include "src/frm.h" 3 | %include "src/ds_str.h" 4 | %{ 5 | #include "src/phoc.h" 6 | %include "src/ds_str.h" 7 | %} 8 | 9 | 10 | -------------------------------------------------------------------------------- /test.ps1: -------------------------------------------------------------------------------- 1 | 2 | # ########################################################################## # 3 | # Frame (©2023 Lelanthran Manickum) # 4 | # # 5 | # This program comes with ABSOLUTELY NO WARRANTY. This is free software # 6 | # and you are welcome to redistribute it under certain conditions; see # 7 | # the LICENSE file for details. # 8 | # ########################################################################## # 9 | 10 | param ( 11 | [switch]$mono 12 | ) 13 | 14 | $DBPATH="$Env:TEMP\frame" 15 | $PROG="$PWD\recent\bin\x86_64-w64-mingw32\frame.exe" 16 | 17 | if ($mono -eq $false) { 18 | $NC="" 19 | $INV="" 20 | $RED="" 21 | $GREEN="" 22 | $BLUE="" 23 | $CYAN="" 24 | $YELLOW="" 25 | $HI_ON="$RED$NV" 26 | } 27 | 28 | $STMT_NUM=0 29 | 30 | $LIMIT="$1" 31 | 32 | echo Removing $DBPATH 33 | Remove-Item $DBPATH -Recurse -Force 34 | 35 | function Execute-Frame { 36 | Param ( 37 | [string]$p1, 38 | [string]$p2, 39 | [string]$p3, 40 | [string]$p4, 41 | [string]$p5 42 | ) 43 | echo "${RED}Executing $NC$GREEN$global:STMT_NUM$NC$BLUE $p1 $p2 $p3 $p4 $p5$NC" 44 | $global:STMT_NUM++ 45 | & "$p1" "$p2" "$p3" "$p4" "$p5" "--dbpath=$DBPATH" "--quiet" 46 | if ($LastExitCode -ne 0) { 47 | die "Command Failure" 48 | } 49 | 50 | } 51 | 52 | 53 | function die { 54 | Param ( 55 | [string] $msg 56 | ) 57 | echo $msg 58 | exit -1 59 | } 60 | 61 | 62 | Execute-Frame $PROG create 63 | Execute-Frame $PROG status 64 | Execute-Frame $PROG history 65 | 66 | echo Created 67 | 68 | Execute-Frame $PROG replace --message="Root: replacement" 69 | Execute-Frame $PROG status 70 | Execute-Frame $PROG history 71 | 72 | Execute-Frame $PROG push one --message="One: message one" 73 | Execute-Frame $PROG status 74 | Execute-Frame $PROG history 75 | 76 | Execute-Frame $PROG append --message="\nMessage: 1\n" 77 | Execute-Frame $PROG status 78 | Execute-Frame $PROG history 79 | 80 | Execute-Frame $PROG up 81 | Execute-Frame $PROG status 82 | Execute-Frame $PROG history 83 | 84 | Execute-Frame $PROG push two --message="Two: message two" 85 | Execute-Frame $PROG status 86 | Execute-Frame $PROG history 87 | 88 | Execute-Frame $PROG top 89 | Execute-Frame $PROG down one 90 | Execute-Frame $PROG status 91 | Execute-Frame $PROG history 92 | 93 | Execute-Frame $PROG back 1 94 | Execute-Frame $PROG status 95 | Execute-Frame $PROG history 96 | 97 | Execute-Frame $PROG back 1 98 | Execute-Frame $PROG status 99 | Execute-Frame $PROG history 100 | 101 | Execute-Frame $PROG push three --message="three: three" 102 | Execute-Frame $PROG status 103 | Execute-Frame $PROG history 104 | 105 | Execute-Frame $PROG pop 106 | Execute-Frame $PROG status 107 | Execute-Frame $PROG history 108 | 109 | Execute-Frame $PROG push four --message="four: four" 110 | Execute-Frame $PROG status 111 | Execute-Frame $PROG history 112 | 113 | Execute-Frame $PROG delete root/two 114 | Execute-Frame $PROG status 115 | Execute-Frame $PROG history 116 | 117 | Execute-Frame $PROG top 118 | Execute-Frame $PROG status 119 | Execute-Frame $PROG history 120 | 121 | 122 | Execute-Frame $PROG push five --message="five: five" 123 | Execute-Frame $PROG up 124 | Execute-Frame $PROG push six --message="six: six" 125 | Execute-Frame $PROG up 126 | Execute-Frame $PROG push seven --message="seven: seven" 127 | Execute-Frame $PROG up 128 | Execute-Frame $PROG push eight --message="eight: eight" 129 | Execute-Frame $PROG up 130 | Execute-Frame $PROG push eighteen --message="eight: eight" 131 | Execute-Frame $PROG up 132 | Execute-Frame $PROG push eighty --message="eight: eight" 133 | Execute-Frame $PROG up 134 | Execute-Frame $PROG down one 135 | Execute-Frame $PROG push one --message="new one" 136 | Execute-Frame $PROG up 137 | Execute-Frame $PROG push two --message="new one" 138 | Execute-Frame $PROG up 139 | Execute-Frame $PROG push three --message="new one" 140 | Execute-Frame $PROG up 141 | # execute $PROG push four --message="new one" 142 | # execute $PROG up 143 | Execute-Frame $PROG push five --message="new one" 144 | Execute-Frame $PROG up 145 | Execute-Frame $PROG push six --message="new one" 146 | Execute-Frame $PROG up 147 | Execute-Frame $PROG push seven --message="new one" 148 | Execute-Frame $PROG up 149 | Execute-Frame $PROG push eight --message="new one" 150 | Execute-Frame $PROG up 151 | Execute-Frame $PROG push nine --message="new one" 152 | Execute-Frame $PROG up 153 | Execute-Frame $PROG push ten --message="new one" 154 | Execute-Frame $PROG up 155 | Execute-Frame $PROG status 156 | Execute-Frame $PROG list 157 | 158 | # Current node is root/one 159 | Execute-Frame $PROG match --from-root "e" 160 | Execute-Frame $PROG match --from-root "ei" 161 | Execute-Frame $PROG match --from-root "eigh" 162 | 163 | Execute-Frame $PROG match "e" 164 | Execute-Frame $PROG match "ei" 165 | Execute-Frame $PROG match "eigh" 166 | Execute-Frame $PROG match "eigh" --invert 167 | Execute-Frame $PROG status 168 | Execute-Frame $PROG match "one/egh" > t 169 | # if [ `wc -l t | cut -f 1 -d \ ` -ne 1 ]; then 170 | # die expected failed match 171 | # fi 172 | 173 | # Rename root/one/five 174 | Execute-Frame $PROG switch root/one/five 175 | Execute-Frame $PROG rename 'FIVE' 176 | # Current node is root/one/FIVE 177 | Execute-Frame $PROG status 178 | 179 | Execute-Frame $PROG match --from-root "one/ei" 180 | 181 | Execute-Frame $PROG tree 182 | 183 | echo 'Use [sed "s:(.\+)::g"] to strip the dates' 184 | 185 | exit 0 186 | 187 | -------------------------------------------------------------------------------- /test.results.saved: -------------------------------------------------------------------------------- 1 | Executing 0: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet create 2 | Created framedb at [/tmp/frame/] 3 | Executing 1: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet status 4 | Current frame 5 | root 6 | 7 | Notes 8 | ENTER YOUR NOTES HERE 9 | 10 | Executing 2: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet history 11 | Frame history 12 | * 0: root 13 | 14 | Executing 3: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet replace --message=Root: replacement 15 | Executing 4: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet status 16 | Current frame 17 | root 18 | 19 | Notes 20 | Root: 21 | 22 | Executing 5: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet history 23 | Frame history 24 | * 0: root 25 | 26 | Executing 6: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet push one --message=One: message one 27 | Created new frame [root/one] 28 | Executing 7: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet status 29 | Current frame 30 | root/one 31 | 32 | Notes 33 | One: 34 | 35 | Executing 8: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet history 36 | Frame history 37 | * 0: root/one 38 | 1: root 39 | 40 | Executing 9: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet append --message= 41 | Message: 1 42 | 43 | root/one: Sat Jun 10 12:36:55 2023 44 | Executing 10: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet status 45 | Current frame 46 | root/one 47 | 48 | Notes 49 | One: 50 | \nMessage: 51 | 52 | Executing 11: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet history 53 | Frame history 54 | * 0: root/one 55 | 1: root 56 | 57 | Executing 12: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet up 58 | Current frame 59 | root 60 | 61 | Notes 62 | Root: 63 | 64 | Executing 13: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet status 65 | Current frame 66 | root 67 | 68 | Notes 69 | Root: 70 | 71 | Executing 14: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet history 72 | Frame history 73 | * 0: root 74 | 1: root/one 75 | 2: root 76 | 77 | Executing 15: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet push two --message=Two: message two 78 | Created new frame [root/two] 79 | Executing 16: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet status 80 | Current frame 81 | root/two 82 | 83 | Notes 84 | Two: 85 | 86 | Executing 17: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet history 87 | Frame history 88 | * 0: root/two 89 | 1: root 90 | 2: root/one 91 | 3: root 92 | 93 | Executing 18: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet top 94 | Current frame 95 | root 96 | 97 | Notes 98 | Root: 99 | 100 | Executing 19: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet down one 101 | Current frame 102 | root/one 103 | 104 | Notes 105 | One: 106 | \nMessage: 107 | 108 | Executing 20: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet status 109 | Current frame 110 | root/one 111 | 112 | Notes 113 | One: 114 | \nMessage: 115 | 116 | Executing 21: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet history 117 | Frame history 118 | * 0: root/one 119 | 1: root 120 | 2: root/two 121 | 3: root 122 | 4: root/one 123 | 5: root 124 | 125 | Executing 22: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet back 1 126 | Current frame 127 | root/one 128 | 129 | Notes 130 | One: 131 | \nMessage: 132 | 133 | Executing 23: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet status 134 | Current frame 135 | root/one 136 | 137 | Notes 138 | One: 139 | \nMessage: 140 | 141 | Executing 24: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet history 142 | Frame history 143 | * 0: root/one 144 | 1: root/one 145 | 2: root 146 | 3: root/two 147 | 4: root 148 | 5: root/one 149 | 6: root 150 | 151 | Executing 25: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet back 1 152 | Current frame 153 | root/one 154 | 155 | Notes 156 | One: 157 | \nMessage: 158 | 159 | Executing 26: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet status 160 | Current frame 161 | root/one 162 | 163 | Notes 164 | One: 165 | \nMessage: 166 | 167 | Executing 27: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet history 168 | Frame history 169 | * 0: root/one 170 | 1: root/one 171 | 2: root/one 172 | 3: root 173 | 4: root/two 174 | 5: root 175 | 6: root/one 176 | 7: root 177 | 178 | Executing 28: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet push three --message=three: three 179 | Created new frame [root/one/three] 180 | Executing 29: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet status 181 | Current frame 182 | root/one/three 183 | 184 | Notes 185 | three: 186 | 187 | Executing 30: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet history 188 | Frame history 189 | * 0: root/one/three 190 | 1: root/one 191 | 2: root/one 192 | 3: root/one 193 | 4: root 194 | 5: root/two 195 | 6: root 196 | 7: root/one 197 | 8: root 198 | 199 | Executing 31: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet pop 200 | Current frame 201 | root/one 202 | 203 | Notes 204 | One: 205 | \nMessage: 206 | 207 | Executing 32: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet status 208 | Current frame 209 | root/one 210 | 211 | Notes 212 | One: 213 | \nMessage: 214 | 215 | Executing 33: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet history 216 | Frame history 217 | * 0: root/one 218 | 1: root/one/three 219 | 2: root/one 220 | 3: root/one 221 | 4: root/one 222 | 5: root 223 | 6: root/two 224 | 7: root 225 | 8: root/one 226 | 9: root 227 | 228 | Executing 34: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet push four --message=four: four 229 | Created new frame [root/one/four] 230 | Executing 35: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet status 231 | Current frame 232 | root/one/four 233 | 234 | Notes 235 | four: 236 | 237 | Executing 36: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet history 238 | Frame history 239 | * 0: root/one/four 240 | 1: root/one 241 | 2: root/one/three 242 | 3: root/one 243 | 4: root/one 244 | 5: root/one 245 | 6: root 246 | 7: root/two 247 | 8: root 248 | 9: root/one 249 | 250 | Executing 37: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet delete root/two 251 | Current frame 252 | root/one/four 253 | 254 | Notes 255 | four: 256 | 257 | Executing 38: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet status 258 | Current frame 259 | root/one/four 260 | 261 | Notes 262 | four: 263 | 264 | Executing 39: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet history 265 | Frame history 266 | * 0: root/one/four 267 | 1: root/one 268 | 2: root/one/three 269 | 3: root/one 270 | 4: root/one 271 | 5: root/one 272 | 6: root 273 | 7: root/two 274 | 8: root 275 | 9: root/one 276 | 277 | Executing 40: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet top 278 | Current frame 279 | root 280 | 281 | Notes 282 | Root: 283 | 284 | Executing 41: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet status 285 | Current frame 286 | root 287 | 288 | Notes 289 | Root: 290 | 291 | Executing 42: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet history 292 | Frame history 293 | * 0: root 294 | 1: root/one/four 295 | 2: root/one 296 | 3: root/one/three 297 | 4: root/one 298 | 5: root/one 299 | 6: root/one 300 | 7: root 301 | 8: root/two 302 | 9: root 303 | 304 | Executing 43: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet push five --message=five: five 305 | Created new frame [root/five] 306 | Executing 44: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet up 307 | Current frame 308 | root 309 | 310 | Notes 311 | Root: 312 | 313 | Executing 45: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet push six --message=six: six 314 | Created new frame [root/six] 315 | Executing 46: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet up 316 | Current frame 317 | root 318 | 319 | Notes 320 | Root: 321 | 322 | Executing 47: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet push seven --message=seven: seven 323 | Created new frame [root/seven] 324 | Executing 48: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet up 325 | Current frame 326 | root 327 | 328 | Notes 329 | Root: 330 | 331 | Executing 49: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet push eight --message=eight: eight 332 | Created new frame [root/eight] 333 | Executing 50: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet up 334 | Current frame 335 | root 336 | 337 | Notes 338 | Root: 339 | 340 | Executing 51: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet push eighteen --message=eight: eight 341 | Created new frame [root/eighteen] 342 | Executing 52: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet up 343 | Current frame 344 | root 345 | 346 | Notes 347 | Root: 348 | 349 | Executing 53: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet push eighty --message=eight: eight 350 | Created new frame [root/eighty] 351 | Executing 54: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet up 352 | Current frame 353 | root 354 | 355 | Notes 356 | Root: 357 | 358 | Executing 55: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet down one 359 | Current frame 360 | root/one 361 | 362 | Notes 363 | One: 364 | \nMessage: 365 | 366 | Executing 56: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet push one --message=new one 367 | Created new frame [root/one/one] 368 | Executing 57: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet up 369 | Current frame 370 | root/one 371 | 372 | Notes 373 | One: 374 | \nMessage: 375 | 376 | Executing 58: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet push two --message=new one 377 | Created new frame [root/one/two] 378 | Executing 59: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet up 379 | Current frame 380 | root/one 381 | 382 | Notes 383 | One: 384 | \nMessage: 385 | 386 | Executing 60: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet push three --message=new one 387 | Created new frame [root/one/three] 388 | Executing 61: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet up 389 | Current frame 390 | root/one 391 | 392 | Notes 393 | One: 394 | \nMessage: 395 | 396 | Executing 62: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet push five --message=new one 397 | Created new frame [root/one/five] 398 | Executing 63: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet up 399 | Current frame 400 | root/one 401 | 402 | Notes 403 | One: 404 | \nMessage: 405 | 406 | Executing 64: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet push six --message=new one 407 | Created new frame [root/one/six] 408 | Executing 65: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet up 409 | Current frame 410 | root/one 411 | 412 | Notes 413 | One: 414 | \nMessage: 415 | 416 | Executing 66: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet push seven --message=new one 417 | Created new frame [root/one/seven] 418 | Executing 67: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet up 419 | Current frame 420 | root/one 421 | 422 | Notes 423 | One: 424 | \nMessage: 425 | 426 | Executing 68: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet push eight --message=new one 427 | Created new frame [root/one/eight] 428 | Executing 69: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet up 429 | Current frame 430 | root/one 431 | 432 | Notes 433 | One: 434 | \nMessage: 435 | 436 | Executing 70: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet push nine --message=new one 437 | Created new frame [root/one/nine] 438 | Executing 71: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet up 439 | Current frame 440 | root/one 441 | 442 | Notes 443 | One: 444 | \nMessage: 445 | 446 | Executing 72: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet push ten --message=new one 447 | Created new frame [root/one/ten] 448 | Executing 73: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet up 449 | Current frame 450 | root/one 451 | 452 | Notes 453 | One: 454 | \nMessage: 455 | 456 | Executing 74: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet status 457 | Current frame 458 | root/one 459 | 460 | Notes 461 | One: 462 | \nMessage: 463 | 464 | Executing 75: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet list 465 | root/one/eight 466 | root/one/five 467 | root/one/four 468 | root/one/nine 469 | root/one/one 470 | root/one/seven 471 | root/one/six 472 | root/one/ten 473 | root/one/three 474 | root/one/two 475 | Executing 76: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet match --from-root e 476 | root/eight 477 | root/eighteen 478 | root/eighty 479 | root/five 480 | root/one 481 | root/one/eight 482 | root/one/five 483 | root/one/four 484 | root/one/nine 485 | root/one/one 486 | root/one/seven 487 | root/one/six 488 | root/one/ten 489 | root/one/three 490 | root/one/two 491 | root/seven 492 | Executing 77: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet match --from-root ei 493 | root/eight 494 | root/eighteen 495 | root/eighty 496 | root/one/eight 497 | Executing 78: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet match --from-root eigh 498 | root/eight 499 | root/eighteen 500 | root/eighty 501 | root/one/eight 502 | Executing 79: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet match e 503 | root/one 504 | root/one/eight 505 | root/one/five 506 | root/one/four 507 | root/one/nine 508 | root/one/one 509 | root/one/seven 510 | root/one/six 511 | root/one/ten 512 | root/one/three 513 | root/one/two 514 | Executing 80: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet match ei 515 | root/one/eight 516 | Executing 81: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet match eigh 517 | root/one/eight 518 | Executing 82: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet match eigh --invert 519 | root/one 520 | root/one/five 521 | root/one/four 522 | root/one/nine 523 | root/one/one 524 | root/one/seven 525 | root/one/six 526 | root/one/ten 527 | root/one/three 528 | root/one/two 529 | Executing 83: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet status 530 | Current frame 531 | root/one 532 | 533 | Notes 534 | One: 535 | \nMessage: 536 | 537 | Executing 85: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet switch root/one/five 538 | Current frame 539 | root/one/five 540 | 541 | Notes 542 | new 543 | 544 | Executing 86: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet rename FIVE 545 | Current frame 546 | root/one/FIVE 547 | 548 | Notes 549 | new 550 | 551 | Executing 87: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet status 552 | Current frame 553 | root/one/FIVE 554 | 555 | Notes 556 | new 557 | 558 | Executing 88: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet match --from-root one/ei 559 | root/one/eight 560 | Executing 89: ./recent/bin/x86_64-linux-gnu/frame.elf --quiet tree 561 | root 562 | eighty 563 | eighteen 564 | five 565 | six 566 | eight 567 | seven 568 | one 569 | four 570 | FIVE 571 | two 572 | three 573 | six 574 | eight 575 | ten 576 | nine 577 | seven 578 | one 579 | Use [sed "s:::g"] to strip the dates 580 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # ########################################################################## # 4 | # Frame (©2023 Lelanthran Manickum) # 5 | # # 6 | # This program comes with ABSOLUTELY NO WARRANTY. This is free software # 7 | # and you are welcome to redistribute it under certain conditions; see # 8 | # the LICENSE file for details. # 9 | # ########################################################################## # 10 | 11 | 12 | export DBPATH=/tmp/frame/ 13 | export PROG="./recent/bin/x86_64-linux-gnu/frame.elf --quiet" 14 | 15 | if [ ! -z "$VG" ]; then 16 | export VG="valgrind -q --leak-check=full --show-leak-kinds=all --error-exitcode=1" 17 | fi 18 | 19 | die () { 20 | echo testcommand failure: $@ 21 | exit -1 22 | } 23 | 24 | export NC="\e[0m" 25 | export INV="\e[7m" 26 | export RED="\e[31m" 27 | export GREEN="\e[32m" 28 | export BLUE="\e[34m" 29 | export CYAN="\e[36m" 30 | export YELLOW="\e[33m" 31 | export HI_ON="${RED}${INV}" 32 | 33 | if [ ! -t 1 ]; then 34 | export NC="" 35 | export INV="" 36 | export RED="" 37 | export GREEN="" 38 | export BLUE="" 39 | export CYAN="" 40 | export YELLOW="" 41 | export HI_ON="" 42 | fi 43 | 44 | export STMT_NUM=0 45 | 46 | if [ -z "$DEBUG" ]; then 47 | export DEBUG=-1 48 | fi 49 | 50 | execute () { 51 | echo -ne "${RED}Executing${NC} ${GREEN}$STMT_NUM:${NC} " 52 | echo -e "${BLUE}$@${NC}" 53 | if [ ! -z "$VG" ]; then 54 | $VG $@ --dbpath=$DBPATH 55 | export RET=$? 56 | export STMT_NUM=$(($STMT_NUM + 1)) 57 | return $RET 58 | fi 59 | 60 | if [ "$DEBUG" -eq "$STMT_NUM" ]; then 61 | gdb --args $@ --dbpath=$DBPATH 62 | export RET=$? 63 | export STMT_NUM=$(($STMT_NUM + 1)) 64 | return $RET 65 | fi 66 | 67 | export STMT_NUM=$(($STMT_NUM + 1)) 68 | $@ --dbpath=$DBPATH 69 | } 70 | 71 | die () { 72 | echo testcommand failure: $@ 73 | execute $PROG status 74 | exit -1 75 | } 76 | 77 | rm -rf $DBPATH 78 | execute $PROG create || die failed to create 79 | execute $PROG status || die failed status 80 | execute $PROG history || die failed history 81 | 82 | execute $PROG replace --message="Root: replacement" || die failed replace 83 | execute $PROG status || die failed status 84 | execute $PROG history || die failed history 85 | 86 | execute $PROG push one --message="One: message one" || die failed push 87 | execute $PROG status || die failed status 88 | execute $PROG history || die failed history 89 | 90 | execute $PROG append --message="\nMessage: 1\n" || die failed append 91 | execute $PROG status || die failed status 92 | execute $PROG history || die failed history 93 | 94 | execute $PROG up || die failed uptree 95 | execute $PROG status || die failed status 96 | execute $PROG history || die failed history 97 | 98 | execute $PROG push two --message="Two: message two" || die failed push 99 | execute $PROG status || die failed status 100 | execute $PROG history || die failed history 101 | 102 | execute $PROG top || die failed top 103 | execute $PROG down one || die failed down one 104 | execute $PROG status || die failed status 105 | execute $PROG history || die failed history 106 | 107 | execute $PROG back 1 || die failed back 108 | execute $PROG status || die failed status 109 | execute $PROG history || die failed history 110 | 111 | execute $PROG back 1 || die failed back 112 | execute $PROG status || die failed status 113 | execute $PROG history || die failed history 114 | 115 | execute $PROG push three --message="three: three" || die failed push 116 | execute $PROG status || die failed status 117 | execute $PROG history || die failed history 118 | 119 | execute $PROG pop || die failed pop 120 | execute $PROG status || die failed status 121 | execute $PROG history || die failed history 122 | 123 | execute $PROG push four --message="four: four" || die failed push 124 | execute $PROG status || die failed status 125 | execute $PROG history || die failed history 126 | 127 | execute $PROG delete root/two || die failed delete 128 | execute $PROG status || die failed status 129 | execute $PROG history || die failed history 130 | 131 | execute $PROG top || die failed top 132 | execute $PROG status || die failed status 133 | execute $PROG history || die failed history 134 | 135 | 136 | execute $PROG push five --message="five: five" || die failed push 137 | execute $PROG up || die failed up 138 | execute $PROG push six --message="six: six" || die failed push 139 | execute $PROG up || die failed up 140 | execute $PROG push seven --message="seven: seven" || die failed push 141 | execute $PROG up || die failed up 142 | execute $PROG push eight --message="eight: eight" || die failed push 143 | execute $PROG up || die failed up 144 | execute $PROG push eighteen --message="eight: eight" || die failed push 145 | execute $PROG up || die failed up 146 | execute $PROG push eighty --message="eight: eight" || die failed push 147 | execute $PROG up || die failed up 148 | execute $PROG down one || die failed down 149 | execute $PROG push one --message="new one" || die failed push 150 | execute $PROG up || die failed up 151 | execute $PROG push two --message="new one" || die failed push 152 | execute $PROG up || die failed up 153 | execute $PROG push three --message="new one" || die failed push 154 | execute $PROG up || die failed up 155 | # execute $PROG push four --message="new one" || die failed push 156 | # execute $PROG up || die failed up 157 | execute $PROG push five --message="new one" || die failed push 158 | execute $PROG up || die failed up 159 | execute $PROG push six --message="new one" || die failed push 160 | execute $PROG up || die failed up 161 | execute $PROG push seven --message="new one" || die failed push 162 | execute $PROG up || die failed up 163 | execute $PROG push eight --message="new one" || die failed push 164 | execute $PROG up || die failed up 165 | execute $PROG push nine --message="new one" || die failed push 166 | execute $PROG up || die failed up 167 | execute $PROG push ten --message="new one" || die failed push 168 | execute $PROG up || die failed up 169 | execute $PROG status || die failed status 170 | execute $PROG list || die failed list 171 | 172 | # Current node is root/one 173 | execute $PROG match --from-root "e" || die failed match 174 | execute $PROG match --from-root "ei" || die failed match 175 | execute $PROG match --from-root "eigh" || die failed match 176 | 177 | execute $PROG match "e" || die failed match 178 | execute $PROG match "ei" || die failed match 179 | execute $PROG match "eigh" || die failed match 180 | execute $PROG match "eigh" --invert || die failed match 181 | execute $PROG status || die failed status 182 | execute $PROG match "one/egh" > t 183 | if [ `wc -l t | cut -f 1 -d \ ` -ne 1 ]; then 184 | die expected failed match 185 | fi 186 | 187 | # Rename root/one/five 188 | execute $PROG switch root/one/five 189 | execute $PROG rename 'FIVE' || die failed rename 190 | # Current node is root/one/FIVE 191 | execute $PROG status || die failed status 192 | 193 | execute $PROG match --from-root "one/ei" || die failed match 194 | 195 | execute $PROG tree || die failed tree 196 | 197 | echo 'Use [sed "s:(.\+)::g"] to strip the dates' 198 | --------------------------------------------------------------------------------