├── .gitignore ├── Changes ├── README.mkdn ├── bin └── anki_import ├── dist.ini ├── lib └── Anki │ └── Import.pm ├── out ├── share └── log_config │ ├── anki-import.cfg │ └── test.cfg ├── t ├── 00-use_ok.t ├── 01-class.t ├── 02-cli.t ├── 03-format_tests.t └── data │ ├── code_with_blank_lines.anki │ ├── cs61.anki │ ├── distzilla.anki │ ├── escape_angle_brackets.anki │ ├── perl_modules.anki │ ├── source.anki │ ├── source2.anki │ └── tag_test.anki └── weaver.ini /.gitignore: -------------------------------------------------------------------------------- 1 | #/dist.ini 2 | /MANIFEST.SKIP 3 | /Anki-Import-* 4 | /tmp 5 | !*/tmp 6 | *~ 7 | *.sw* 8 | test.tgz 9 | #*.swo 10 | #weaver.ini 11 | Makefile 12 | Makefile.old 13 | MYMETA.json 14 | MYMETA.yml 15 | blib 16 | pm_to_blib 17 | -------------------------------------------------------------------------------- /Changes: -------------------------------------------------------------------------------- 1 | Revision history for Anki-Import 2 | 3 | 0.030 2018-09-21 16:50:00-04:00 America/New_York 4 | 5 | 0.029 2018-09-17 07:40:04-04:00 America/New_York 6 | 7 | 0.028 2018-09-16 23:56:09-04:00 America/New_York 8 | 9 | 0.027 2018-09-16 09:53:37-04:00 America/New_York 10 | 11 | 0.026 2018-09-13 22:44:41-04:00 America/New_York 12 | 13 | 0.025 2018-09-12 12:45:48-04:00 America/New_York 14 | 15 | 0.024 2018-09-11 23:02:44-04:00 America/New_York 16 | 17 | 0.023 2018-09-11 18:16:10-04:00 America/New_York 18 | 19 | 0.022 2018-09-05 03:27:23-04:00 America/New_York 20 | 21 | 0.021 2018-09-04 14:18:21-04:00 America/New_York 22 | 23 | 0.020 2018-09-04 14:17:06-04:00 America/New_York 24 | 25 | 0.019 2018-09-04 05:57:48-04:00 America/New_York 26 | 27 | 0.018 2018-09-03 02:04:15-04:00 America/New_York 28 | 29 | 0.017 2018-09-03 00:27:17-04:00 America/New_York 30 | 31 | 0.016 2018-09-03 00:06:56-04:00 America/New_York 32 | Require latest version of Log::Log4perl shortcuts 33 | 34 | 0.015 2018-09-02 22:50:02-04:00 America/New_York 35 | 36 | 0.014 2018-09-02 18:48:31-04:00 America/New_York 37 | 38 | 0.013 2018-09-02 00:26:14-04:00 America/New_York 39 | 40 | 0.012 2018-09-01 19:46:59-04:00 America/New_York 41 | 42 | 0.011 2018-08-31 17:27:27-04:00 America/New_York 43 | 44 | 0.007 2018-08-31 10:46:29-04:00 America/New_York 45 | 46 | 0.006 2018-08-31 10:33:46-04:00 America/New_York 47 | First release 48 | 0.005 2018-08-31 09:53:34-04:00 America/New_York 49 | 50 | 0.004 2018-08-31 03:43:40-04:00 America/New_York 51 | 52 | 0.003 2018-08-31 03:30:01-04:00 America/New_York 53 | 54 | 0.002 2018-08-31 03:12:38-04:00 America/New_York 55 | 56 | 0.001 2018-08-31 02:27:31-04:00 America/New_York 57 | -------------------------------------------------------------------------------- /README.mkdn: -------------------------------------------------------------------------------- 1 | # NAME 2 | 3 | Anki::Import - Anki note generation made easy. 4 | 5 | # VERSION 6 | 7 | version 0.030 8 | 9 | # OVERVIEW 10 | 11 | Efficiently generate formatted Anki notes with your 12 | text editor for easy import into Anki. 13 | 14 | # SYNOPSIS 15 | 16 | # Step 1: Create the source file 17 | 18 | # Step 2: Run the anki_import command 19 | supplied by this module... 20 | 21 | # ...from the command line 22 | anki_import path/to/source_file.txt 23 | 24 | # or 25 | 26 | # ...from within a perl script 27 | use Anki::Import; 28 | anki_import('path/to/source_file.txt'); 29 | 30 | # Step 3: Import the resultant files into Anki 31 | 32 | # DESCRIPTION 33 | 34 | Inputting notes into Anki can be a tedious chore. `Anki::Import` lets you 35 | you generate Anki notes with your favorite text editor (e.g. vim, BBEdit, Atom, 36 | etc.) so you can enter formatted notes into Anki's database more efficiently. 37 | 38 | At a minimum, you should have basic familiarity with using your computer's 39 | command line terminal to make use of this program. 40 | 41 | ## Steps for creating, processing and imorting new notes 42 | 43 | ### Step 1: Generate the notes with your text editor 44 | 45 | First, you create a specially formatted source file which 46 | `Anki::Import` will process. The source file is a simple text file with 47 | basic formatting rules you must follow. 48 | 49 | See the ["General description of the source file"](#general-description-of-the-source-file) section for details. 50 | 51 | ### Step 2: Process the notes with `Anki::Import` 52 | 53 | Once the source file is created and saved, run the 54 | `anki_import` command from the command line or from a script to generate the 55 | import files. This will create a new directory called "anki\_import\_files" 56 | containing one text file for each of the note types generated by `Anki::Import` 57 | and which you will import in the next step. By default, the directory is 58 | created in the current directory. 59 | 60 | See the ["USAGE"](#usage) section for more details and options. 61 | 62 | ### Step 3: Import the processed notes with Anki 63 | 64 | In Anki, open the deck you wish to import and hit Ctrl-I or (Cmd-I on a 65 | Mac) to start the import process, navigate to the a file generated by 66 | `Anki::Import` and select one of them. 67 | 68 | Next, check Anki's settings to be sure you are importing notes into the proper 69 | fields, deck and note type. Also ensure you have the "Allow HTML in fields" 70 | option enabled and that you have "Fields separated by: Tab" selected. 71 | 72 | Click "Import" and repeat for each note type you are importing. 73 | 74 | Consult [Anki's documentation](https://apps.ankiweb.net/docs/manual.html#importing) 75 | for more details on importing and managing your notes. 76 | 77 | ## General description of the source file 78 | 79 | The source file contains one or more Anki notes. To make importing easier, 80 | each source file should contain notes that will be imported into the same Anki 81 | deck. 82 | 83 | ### Creating notes and fields in the source file 84 | 85 | Each note in the source file contains fields which should correspond to your 86 | existing note types in Anki. Individual notes in the source file are delineated 87 | by two or more blank lines. Fields are separated by a single blank line. Fields 88 | for each note should be in the same order as your Anki note types to make 89 | importing more automatic. All fields must have content or left intentionally 90 | blank. 91 | 92 | To create an intionally blank field, add a single '\`' (backtick) character on a 93 | line by itself with blank lines before and after the line with the single 94 | backtick. 95 | 96 | See the ["Source file example"](#source-file-example) for more help. 97 | 98 | ### Source file requirements and limitations 99 | 100 | #### Use UTF-8 encoding 101 | 102 | The source file should be a plain text file with UTF-8 encoding. UTF-8 103 | is likely the default encoding method for your editor but check your editor's 104 | settings and documentation for further details. 105 | 106 | #### Avoid tabs 107 | 108 | Since tab characters are used by Anki to split your fields, you should 109 | avoid relying on tab characters in your source file. Any tabs found in your 110 | source file will get converted to four spaces. 111 | 112 | ### Assigning notes to note types 113 | 114 | You can indicate which note type a note belongs to by preceding notes with a 115 | `#note_type` comment at the beginning of a line. You can choose any note type 116 | name you wish but it is recommended that you use note type names similar to 117 | those that exist in your Anki database to make importing the notes easier. 118 | 119 | Note type comments not only assign a note type to the next note, but any 120 | notes therafter until a new note type comment is encountered (see the example 121 | in the next section). So note type comments actually delineate a note type 122 | section. If no note types are indicated in your source file, the 123 | "Basic" note type is used. 124 | 125 | Note types are used to help `Anki::Import` ensure other notes of the same type 126 | have the same number of fields. If the notes assigned to a particular note type 127 | do not all have the same number of fields, an error is thrown so be sure each 128 | note has the correct number of fields. 129 | 130 | Note: note type sections can be split across the file (i.e. you do not have to 131 | group the notes of a particular note type together). 132 | 133 | ### Tagging notes 134 | 135 | Place your space seprated lit of tags in the last field. As long as there is 136 | one more field in the source files that fields in the note you are importing 137 | to, Anki will generate tags from the last field. 138 | 139 | You can automate tag generation by placing a single '^' (caret) character 140 | on a line by itself immediately after your list of tags. These tags will now 141 | be used for all later notes in the file until they are overridden by a new list 142 | of automated tags. Also any new tags you place at the end of a note will be 143 | added to the list of tags that are automatically generated. 144 | 145 | To reset the automated list of tags, place a single '^' (caret) character 146 | in place of the field where your tags will go. 147 | 148 | To suppress the application of an automated tag from the list of automated tags 149 | for a particular note, include that tag in the tag field and it will not be 150 | tagged with that term for that one note. 151 | 152 | To add a new tag to the already existing set of tags, enter the tags on 153 | a line followed by a new line with a single '+' sign on it by itself. 154 | 155 | Note: If you use tags on any of your notes in a parcitular note type, you must 156 | use tags on all of your notes or indicate that the tag field should be left 157 | blank with a '\`' (backtick) character on a line by itself. 158 | 159 | ### Applying text formatting to your notes 160 | 161 | Learning how to format the source file is key to getting Anki to import your 162 | notes properly and getting the most out of `Anki::Import`. 163 | 164 | Following a few simple rules, you can assign notes to a note type, preserve 165 | whitespace in fields, create bold text, create blank lines in your fields, 166 | add tags, create cloze deletions, indicate which fields are blank and 167 | generate simple lists. Study the example below for details. 168 | 169 | Note: Lines containing only whitespace characters are treated as blank lines. 170 | 171 | #### Example source file 172 | 173 | Below is an example of how to format a source data file. Note that the column on 174 | the right containing comments for this example are not permitted in an actual 175 | source data file. 176 | 177 | # Basic # We start a note section here. Any 178 | # notes below here to the next 179 | # note type comment are assigned to 180 | # the 'Basic' note type 181 | 182 | # You can have blank lines between the 183 | # note type comment and the next 184 | # question. 185 | 186 | What is the first day of the week? # Question 1, Field 1 187 | # Blank line here indicates a new field. 188 | Monday. # Question 1, Field 2 189 | 190 | # Add two or more blank lines between 191 | questions 192 | 193 | 194 | 195 | How many days of the week are there? # Question 2, Field 1 196 | 197 | Our caldendar # Question 2, Field 2 198 | has seven days # Answers can run 199 | in a week # across one or more lines but 200 | # will be imported as a single 201 | # line into Anki. 202 | 203 | 204 | 205 | # less_basic # New note type called "less_basic" 206 | # with 3 fields 207 | What is the third day of week? # Question 3, Field 1 208 | 209 | Wednesday # Question 3, Field 2 210 | 211 | Wed. # Question 3, Field 3 212 | 213 | your_tags go_here # We set up automated tags on this note 214 | ^ # with the '^' symbol on a line by itself 215 | # immediately after out tag list. 216 | # These tags will be applied to this and 217 | # all future notes unless overridden. 218 | 219 | 220 | Put {{{another question}}} here. # Surround text with 3 braces for a cloze 221 | 222 | Here is an field that has 223 | ` # Insert a blank line into a field 224 | a blank line in it. # with a single backtick character 225 | # surrounded by lines with text. 226 | go_here # We set autotags in the last note and 227 | # they will carry forward to this note 228 | # except for the exclusions we place 229 | # here. This note will *not* be tagged 230 | # with 'go_here' but it will still be 231 | # tagged under 'your_tags'. 232 | 233 | 234 | 235 | What does this code do? # Another less_basic question 236 | 237 | ``` # Preserve whitespace in a field with 3 238 | This_is_some_code { # backticks on a single line. 239 | ` # You must still backtick blank lines 240 | print 'Whitespace will be # when preserving whitespace, however. 241 | preserved'; 242 | ` # Another blank line. 243 | } 244 | ``` # End whitespace preservation 245 | 246 | This is %comma,delimted,text% # Bullet lists with %item1,item2,item3% 247 | 248 | ' # The tags field is left blank. But all 249 | # the auto tags will still be applied. 250 | 251 | 252 | Another question # Field 1 253 | 254 | ` # Field 2 is blank. 255 | 256 | This is *in bold* # Field 3 has bold words, followed by a 257 | ` # blank line, followed by 258 | %an,unordered,list% # an ordered list. 259 | 260 | new_tags more_new_tags # This and future notes will use these 261 | ^ # newer automated tags. 262 | 263 | 264 | #basic # switch back to a 'basic' note type 265 | Last question 266 | 267 | Last anser 268 | 269 | add_this_tag_to_autotags # We add a new_tag to our autotag list 270 | + # with the '+' sign by itself on a new 271 | # line. 272 | 273 | ### Getting the most from `Anki::Import` 274 | 275 | By itself, `Anki::Import` will make it easier for you to format and 276 | input your notes especially if you do a lot of basic HTML formatting. However, 277 | the huge productivity gains of `Anki::Import` can only be unlocked by getting 278 | proficient wih your text editor of choice. 279 | 280 | For example, you can generate templates for each of the note types you use to 281 | make data entry exceptionally painless. And with a text editor like vim, you 282 | can automate the generation of the formatting codes used by `Anki::Import` 283 | and make Anki note creation joyful, or at least much less tedious. 284 | 285 | Teaching you how to use and optimize your text editor for `Anki::Import` is 286 | well beyond the scope of this document. But if you take the time now and do the 287 | up front work of learning your text editor and tweaking it for use with 288 | `Anki::Import`, you will save a lot of time in the long run. 289 | 290 | In the future, vim configurations and plugins for use with `Anki::Import` 291 | may be released as they are developed to help you get going faster with vim. 292 | Unfortunately, other text editors cannot be supported as there are far too many 293 | and far too little time to get familiar with all their features. 294 | 295 | # USAGE 296 | 297 | `anki_import` can be run from the command line or from within another perl 298 | script. It behaves the same way in both environments. 299 | 300 | ## Command line usage 301 | 302 | The `Anki::Import` module installs the `anki_import` command line command 303 | for generating import files which is used as follow: 304 | 305 | anki_import source_file [parent_dir] [--verbosity_level] 306 | 307 | B anki_import pop_quiz.txt /home/me --verbose 308 | 309 | `anki_import` processes the `source_file` and generates files to be imported into 310 | Anki, one file for each note type. These files are placed in a directory called 311 | `anki_import_files`. This directory is placed in the current working directory 312 | by default. 313 | 314 | Note that previously generated files already located in the `anki_import_files` 315 | directory the command is outputting to will will be overwritten without warning. 316 | Add a unique (`parent_dir` path to help prevent this. 317 | 318 | **`parent_dir`** is an optional argument containing the path you want `Anki::Import` 319 | to save the files for output. You may use a `~` (tilde) to represent the home 320 | directory for the current user. 321 | 322 | **`$verbosity`** options can be set to `--verbose` or `--vverbose` 323 | (very verbose) or `--quiet`. The verbosity options have aliases for your 324 | typing convenience: `-v`, `-V` and `-q`, respectively. 325 | 326 | Use the `--verbose` or `--vverbose` option to help troubleshoot source file 327 | processing issues. The (`--quiet`) option suppresses the success 328 | message printed upon successful processing of the source file. 329 | 330 | ## From a script 331 | 332 | Invoking the `anki_import` function mirrors the arguments used from the 333 | command line: 334 | 335 | ## anki\_import($source\_file, \[$parent\_dir\], \[$verbosity\]); 336 | 337 | Usage in a script is the same as for the command line except that the arguments 338 | must be enclosed in quotes. 339 | 340 | Example: 341 | 342 | anki_import('script_file.txt', '/home/me', '--verbose'); 343 | 344 | See the ["Command line usage"](#command-line-usage) for more details on the optional arguments. By 345 | default, the verbosity output from the function call is (`--quiet`. If you 346 | want the function call to output a success message, use (`--no-quiet`); 347 | 348 | # INSTALLATION 349 | 350 | `Anki::Import` is written in the Perl programming langauge. Therefore, you must 351 | have Perl installed on your system. MacOS and \*nix machines will have 352 | Perl already installed but the Windows operating system does not 353 | come pre-installed with Perl and so you may have to install it first before you 354 | can use `Anki::Import`. 355 | 356 | If you are unsure if you have Perl installed, open a command prompt and type in: 357 | 358 | perl -v 359 | 360 | If Perl is installed, it will report the version of Perl on your machine and 361 | other information. If Perl is not installed, you will get a "not recognized" 362 | error on Windows. 363 | 364 | ## Installing Strawberry Perl on Windows 365 | 366 | If you are on Windows and you do not have Perl installed, you can download a 367 | version of Perl called "Strawberry Perl" from the 368 | [Strawberry Perl website](http://strawberryperl.com/). Be sure to install the 369 | proper version (64 or 32 bit). 370 | 371 | Once installed successfully, see the next section for downloading and installing 372 | `Anki::Import`. 373 | 374 | ## Installing Anki::Import with `cpanm` 375 | 376 | `Anki::Import` is easy to install if you have a Perl module called 377 | [App::cpanimus](https://metacpan.org/pod/App::cpanimus) installed. This module provides a command, `cpanm`, to easily 378 | downloading and installing modules from the Perl module repository called 379 | **CPAN**. Simply run this command from the command line to install 380 | `Anki::Import`: 381 | 382 | cpanm Anki::Import 383 | 384 | Strawberry Perl for Windows has the `cpanm` already installed. 385 | 386 | ## Installing Anki::Import without `cpanm` 387 | 388 | If you do not have the `cpan` command on your computer, you will need to use 389 | either the older CPAN shell method of installation or, as a last resort, perform 390 | manual installation. Refer to the 391 | `Anki::Import` [INSTALL file](https://metacpan.org/source/STEVIED/Anki-Import-0.012/INSTALL) 392 | for further details on these installation methods. 393 | 394 | # REQUIRES 395 | 396 | - [Cwd](https://metacpan.org/pod/Cwd) 397 | - [Exporter](https://metacpan.org/pod/Exporter) 398 | - [Getopt::Args](https://metacpan.org/pod/Getopt::Args) 399 | - [Log::Log4perl::Shortcuts](https://metacpan.org/pod/Log::Log4perl::Shortcuts) 400 | - [strict](https://metacpan.org/pod/strict) 401 | - [warnings](https://metacpan.org/pod/warnings) 402 | 403 | # SUPPORT 404 | 405 | ## Perldoc 406 | 407 | You can find documentation for this module with the perldoc command. 408 | 409 | perldoc Anki::Import 410 | 411 | ## Websites 412 | 413 | The following websites have more information about this module, and may be of help to you. As always, 414 | in addition to those websites please use your favorite search engine to discover more resources. 415 | 416 | - MetaCPAN 417 | 418 | A modern, open-source CPAN search engine, useful to view POD in HTML format. 419 | 420 | [https://metacpan.org/release/Anki-Import](https://metacpan.org/release/Anki-Import) 421 | 422 | ## Source Code 423 | 424 | The code is open to the world, and available for you to hack on. Please feel free to browse it and play 425 | with it, or whatever. If you want to contribute patches, please send me a diff or prod me to pull 426 | from your repository :) 427 | 428 | [https://github.com/sdondley/Anki-Import](https://github.com/sdondley/Anki-Import) 429 | 430 | git clone git://github.com/sdondley/Anki-Import.git 431 | 432 | # BUGS AND LIMITATIONS 433 | 434 | You can make new bug reports, and view existing ones, through the 435 | web interface at [https://github.com/sdondley/Anki-Import/issues](https://github.com/sdondley/Anki-Import/issues). 436 | 437 | # AUTHOR 438 | 439 | Steve Dondley 440 | 441 | # COPYRIGHT AND LICENSE 442 | 443 | This software is copyright (c) 2018 by Steve Dondley. 444 | 445 | This is free software; you can redistribute it and/or modify it under 446 | the same terms as the Perl 5 programming language system itself. 447 | -------------------------------------------------------------------------------- /bin/anki_import: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env perl 2 | #PODNAME: anki_import 3 | use strict; 4 | use warnings; 5 | use Cwd; 6 | use Anki::Import; 7 | 8 | # suppress success_msg 9 | #my $suppress = grep { $_ eq '--quiet' } @ARGV; 10 | #print $suppress . "\n"; 11 | #push @ARGV, '--no-quiet' if $suppress; 12 | 13 | eval { anki_import(@ARGV) }; 14 | 15 | # allow command output 16 | if ($@ =~ /^usage: anki_import/) { 17 | print STDERR $@; 18 | } 19 | 20 | #ABSTRACT: command line command for the Anki::Import module 21 | 22 | __END__ 23 | 24 | =head1 SYNOPSIS 25 | 26 | anki_import path/to/source_file.txt 27 | 28 | =head1 USAGE 29 | 30 | Please see L for usage details. 31 | 32 | 33 | =head1 SEE ALSO 34 | 35 | L 36 | -------------------------------------------------------------------------------- /dist.ini: -------------------------------------------------------------------------------- 1 | name = Anki-Import 2 | author = Steve Dondley 3 | license = Perl_5 4 | copyright_holder = Steve Dondley 5 | copyright_year = 2018 6 | 7 | [Repository] 8 | ;[Bugtracker] 9 | ;web = http://github.com/sdondley/%s/issues 10 | [Git::NextVersion] 11 | [GitHub::Meta] 12 | [PodVersion] 13 | [PkgVersion] 14 | [NextRelease] 15 | [Run::AfterRelease] 16 | run = mv Changes tmp && cp %n-%v/Changes Changes 17 | [InstallGuide] 18 | 19 | [PodWeaver] 20 | [ReadmeAnyFromPod] 21 | type = markdown 22 | location = root 23 | phase = release 24 | 25 | 26 | [Git::Check] 27 | [Git::Commit] 28 | allow_dirty = README.mkdn 29 | allow_dirty = Changes 30 | allow_dirty = INSTALL 31 | [Git::Tag] 32 | [Git::Push] 33 | [Run::AfterRelease / MyAppAfter] 34 | run = mv tmp/Changes Changes 35 | 36 | [GatherDir] 37 | [AutoPrereqs] 38 | [Prereqs / TestRequires] 39 | Getopt::Args = 0 40 | [PruneCruft] 41 | [PruneFiles] 42 | filename = weaver.ini 43 | filename = README.mkdn 44 | filename = dist.ini 45 | filename = .gitignore 46 | [ManifestSkip] 47 | [MetaYAML] 48 | [MetaJSON] 49 | [License] 50 | [Readme] 51 | [ExtraTests] 52 | [ExecDir] 53 | [ShareDir] 54 | [MakeMaker] 55 | [Manifest] 56 | [TestRelease] 57 | 58 | [ConfirmRelease] 59 | [UploadToCPAN] 60 | ;[FakeRelease] 61 | -------------------------------------------------------------------------------- /lib/Anki/Import.pm: -------------------------------------------------------------------------------- 1 | package Anki::Import ; 2 | 3 | use strict; 4 | use warnings; 5 | use Cwd; 6 | use Getopt::Args; 7 | use Log::Log4perl::Shortcuts 0.021 qw(:all); 8 | use Exporter qw(import); 9 | our @EXPORT = qw(anki_import); 10 | 11 | # change log config to test for development for fine-tuned control over log output 12 | set_log_config('anki-import.cfg'); 13 | #set_log_config('test.cfg', __PACKAGE__); 14 | 15 | # set up variables 16 | my @lines; # lines from source file 17 | my $line_count = 0; # count processed lines to give more helpful error msg 18 | my $cline = ''; # current line getting processed 19 | my $lline = ''; # last (previous) line processed 20 | my $ntype = 'Basic'; # default note type 21 | my @notes = (); # array for storing notes 22 | my @autotags = (); # for storing automated tags 23 | 24 | # argument processing 25 | arg file => ( 26 | isa => 'Str', 27 | required => 1, 28 | comment => 'the name of the source file' 29 | ); 30 | arg parent_dir => ( 31 | isa => 'Str', 32 | default => cwd, 33 | comment => 'optional directory to save output files, defaults to current directory', 34 | ); 35 | opt quiet => ( 36 | isa => 'Bool', 37 | alias => 'q', 38 | default => 1, 39 | comment => 'On by default. Use --quiet to override this setting to suppress' 40 | . ' the success message after a successful execution of the command.' 41 | ); 42 | opt verbose => ( 43 | isa => 'Bool', 44 | alias => 'v', 45 | comment => 'provide details on progress of Anki::Import' 46 | ); 47 | opt vverbose => ( 48 | isa => 'Bool', 49 | alias => 'V', 50 | comment => 'verbose information plus debug info' 51 | ); 52 | 53 | # start here 54 | sub anki_import { 55 | my $args = optargs( @_ ); 56 | 57 | my $file = $args->{file}; 58 | if (!$file) { 59 | logf('Aborting: No file passed to Anki::Import.'); 60 | } 61 | 62 | # set parent directory 63 | my $pd = $args->{parent_dir}; 64 | 65 | # set log level as appropriate 66 | if ($args->{verbose}) { 67 | set_log_level('info'); 68 | } elsif ($args->{vverbose}) { 69 | set_log_level('debug'); 70 | } else { 71 | set_log_level('error'); 72 | } 73 | logi('Log level set'); 74 | 75 | # get and load the source file 76 | logi('Loading file'); 77 | my $path = File::Spec->catfile($file); logd($path); 78 | if (! -e $path) { 79 | logf("Aborting: Source file named '$path' does not exist."); 80 | }; 81 | open (my $handle, "<:encoding(UTF-8)", $path) or logf("Could not open $path");; 82 | chomp(@lines = <$handle>); 83 | close $handle; 84 | logi('Source file loaded.'); 85 | 86 | # pad data with a blank line to make it easier to process 87 | push @lines, ''; 88 | 89 | # do the stuff we came here for 90 | validate_src_file(); logd(\@notes); 91 | generate_importable_files($pd); 92 | 93 | # print a success message 94 | unless ($args->{'quiet'}) { 95 | set_log_level('info'); 96 | logi("Success! Your import files are in the $pd" 97 | . '/anki_import_files directory'); 98 | } 99 | 100 | # fin 101 | } 102 | 103 | # functions for first pass parsing of source data 104 | sub validate_src_file { 105 | logi('Validating source file'); 106 | 107 | # throw error if file is empty 108 | logf('Source data file is empty.') if !$lines[0]; 109 | 110 | # outer loop for parsing notes 111 | my %fields; # keeps track of number of fields for each type of note 112 | while (next_line()) { 113 | 114 | # ignore blank lines 115 | next if ($cline =~ /^$|^\s+$/); 116 | 117 | if ($cline =~ /^#\s*(\S+)/) { 118 | $ntype = $1; 119 | logi("Found note type"); 120 | logd($ntype); 121 | next; 122 | } 123 | 124 | logi('Processing new note'); 125 | # get the note 126 | my $note = slurp_note(); 127 | logd($note); 128 | 129 | logi('Checking number of note fields'); 130 | # validaate that notes of the same type have the same number of fields 131 | if (my $number_of_fields = $fields{$ntype}) { 132 | if (scalar (@$note) != $number_of_fields) { 133 | my $field_count = scalar(@$note); 134 | logf("A(n) $ntype note ending on line $line_count" 135 | . " has $field_count fields, a different amount than previous '$ntype' note types." 136 | . " Notes of the same note type must have the same number of fields. One common reason" 137 | . " for this error is that you did not indicate that you wanted to leave a field blank. To leave a field blank," 138 | . " place a single '`' (backtick) on the line by itself in the source file. You may also" 139 | . " have failed to separate notes with two or more blank lines." 140 | . " Check your source file to ensure it is properly formatted.\n\n\tRefer to the" 141 | . " Anki::Import documentation for more help with formatting your source file." 142 | ); 143 | } 144 | } else { 145 | $fields{$ntype} = scalar @$note; 146 | } 147 | 148 | logi('Storing note'); 149 | push @notes, {ntype => $ntype, note => $note}; 150 | } 151 | 152 | } 153 | 154 | sub slurp_note { 155 | my @current_field; 156 | my @note; 157 | push @current_field, $cline; 158 | 159 | # loop over lines in the note 160 | while (next_line()) { 161 | logd($cline, 'cline'); 162 | if ($cline =~ /^$|^\s+$/) { 163 | my @all_fields = @current_field; 164 | push (@note, \@all_fields) if @current_field; 165 | @current_field = (); 166 | if ($lline =~ /^$|^\s+$/) { 167 | last; 168 | } 169 | } else { 170 | push @current_field, $cline; 171 | } 172 | } 173 | return \@note; 174 | } 175 | 176 | sub next_line { 177 | return 0 if !@lines; # last line in file was made blank 178 | $lline = $cline; 179 | $cline = (shift @lines || ''); 180 | 181 | # do some cleanup 182 | chomp $cline; 183 | $cline =~ s/\t/ /g; # replace tabs with spaces 184 | 185 | ++$line_count; 186 | } 187 | 188 | # functions for second pass parsing and formatting of source data 189 | # and creation of import files 190 | sub generate_importable_files { 191 | my $pd = shift; logi('Generating files for import'); 192 | 193 | my %filenames; 194 | 195 | # loop over notes 196 | foreach my $note (@notes) { 197 | logi('Looping over notes'); 198 | 199 | my $line = process_note($note->{note}); 200 | 201 | # add our processed note to our data 202 | my $filename = $note->{ntype} . '_notes_import.txt'; 203 | $filenames{$filename}{content} .= $line; 204 | } 205 | 206 | logi('Writing notes out to file'); 207 | foreach my $file ( keys %filenames ) { 208 | my $dir = File::Spec->catfile($pd, 'anki_import_files'); 209 | mkdir $dir || logf("Could not make directory: $dir, $!"); 210 | logd($dir); 211 | my $out_path = File::Spec->catfile($dir, $file); 212 | open (my $handle, ">>:encoding(UTF-8)", $out_path) or logf("Could not create file: $out_path"); 213 | chomp $filenames{$file}{content}; 214 | print $handle $filenames{$file}{content}; 215 | close $handle; 216 | } 217 | } 218 | 219 | # the meat of the matter 220 | # TODO: break up into shorter functions for readability 221 | sub process_note { 222 | my $note = shift; logd($note, 'note_2b_processed'); 223 | 224 | my @fields = (); 225 | my $new_autotags = 0; # flag raised if autotag line found 226 | 227 | # loop over note fields 228 | foreach my $field (@$note) { 229 | my $ws_mode = 0; # tracks if we are preserving whitespace 230 | my $field_out = ''; 231 | 232 | # loop over lines in field and process accordingly 233 | my @lines = (''); # can't take a reference to nothing 234 | foreach my $line (@$field) { 235 | my $last_line = \$lines[-1]; # just to make it easier to type 236 | 237 | # detect autotags 238 | logd($line); 239 | if ($line =~ /^\+\s*$/ && !$ws_mode) { 240 | push @autotags, split (/\s+/, $$last_line); 241 | $new_autotags = 1; 242 | } 243 | if ($line =~ /^\^\s*$/ && !$ws_mode) { 244 | @autotags = split (/\s+/, $$last_line); 245 | $new_autotags = 1; 246 | next; 247 | } 248 | 249 | # blanks lines not in non-whitespace mode 250 | if ($line =~ /^`\s*$/ && !$ws_mode) { 251 | if ($$last_line && $$last_line !~ /^
+$/) { 252 | $$last_line .= '

'; 253 | } 254 | next; 255 | } 256 | 257 | # enter whitespace mode and adding appropriate HTML 258 | if ($line =~ /^`{3,3}$/ && !$ws_mode) { 259 | $ws_mode = 1; 260 | 261 | # add a couple of blank lines to previous line 262 | if ($$last_line) { 263 | $$last_line .= '

'; 264 | } 265 | 266 | $$last_line .= '
'; 267 | next; 268 | } 269 | 270 | # exit whitespace mode, close out HTML, add blank lines 271 | if ($line =~ /^`{3,3}$/ && $ws_mode) { 272 | $ws_mode = 0; 273 | $$last_line .= "


"; 274 | next; 275 | } 276 | 277 | # handle lines differently based on if we are preserving whitespace 278 | if ($ws_mode) { 279 | # escape characters in preserved text 280 | if ($line =~ /^`\s*$/) { 281 | $$last_line .= '
'; 282 | next; 283 | } 284 | $line =~ s/(?"; 288 | } else { 289 | push @lines, $line; 290 | } 291 | } 292 | logf('A set of backticks (```) is unmatched or you failed to backtick a' 293 | . ' blank line inside of a backtick set. Please correct the source' 294 | . ' file and try again. Run "perldoc Anki::Import" for more help.') if $ws_mode; 295 | 296 | logd($field_out, 'field_out'); 297 | 298 | shift @lines if !$lines[0]; 299 | my $field = join ' ', @lines; 300 | 301 | # clean up dangling breaks 302 | $field =~ s/
<\/div>/<\/div>/g; 303 | 304 | # handle formatting codes in text, preserve escaped characters 305 | 306 | # preserve angle brackets between backticks 307 | my $parts = [ split /[^\\]`|^`/, $field, -1]; 308 | 309 | my $count = 0; 310 | foreach my $part (@$parts) { 311 | $count++; 312 | next if ($count % 2); # only substitute on odd number array items 313 | $part =~ s/$1<\/span>/gm; 320 | $field =~ s/\\`/`/g; 321 | 322 | # bold 323 | $field =~ s/(?$1<\/span>/gm; 324 | $field =~ s/\\\*/*/g; 325 | 326 | # unordered lists 327 | $field =~ s'(?
  • " . join ("
  • ", (split (/,\s*/, $1))) . "
  • <\/ul>"'gme; 328 | $field =~ s/\\%/%/g; 329 | 330 | $field =~ s/(
    )+$//; 331 | push @fields, $field; 332 | 333 | } 334 | 335 | # generate tag field 336 | if (@autotags && !$new_autotags) { 337 | 338 | # get tags from tag field 339 | my @note_tags = split (/\s+/, $fields[-1]); logd(\@note_tags, 'raw_note_tags'); 340 | my @new_tags = (); 341 | 342 | # add tags from tag field 343 | foreach my $note_tag (@note_tags) { 344 | my $in_autotags = grep { $_ eq $note_tag } @autotags; 345 | push @new_tags, $note_tag unless $in_autotags; 346 | } 347 | 348 | # add autotags 349 | foreach my $autotag (@autotags) { 350 | my $discard_autotag = grep { $_ eq $autotag } @note_tags; 351 | push @new_tags, $autotag if !$discard_autotag; 352 | } 353 | 354 | # add combined tags as a field 355 | logd(\@new_tags, 'new_tags'); 356 | my $new_tags = join (' ', @new_tags); 357 | $fields[-1] = $new_tags; 358 | } 359 | $new_autotags = 0; 360 | 361 | my $out = join ("\t", @fields); 362 | 363 | # create cloze fields 364 | my $cloze_count = 1; 365 | # TODO: should probably handle escaped braces just in case 366 | while ($out =~ /\{\{\{(.*?)}}}/) { 367 | $out =~ s/\{\{\{(.*?)}}}/{{c${cloze_count}::$1}}/s; 368 | $cloze_count++; 369 | } 370 | logd($out, 'out'); 371 | 372 | $out .= "\n"; 373 | } 374 | 375 | 1; # Magic true value 376 | # ABSTRACT: Anki note generation made easy. 377 | 378 | __END__ 379 | 380 | =head1 OVERVIEW 381 | 382 | Efficiently generate formatted Anki notes with your 383 | text editor for easy import into Anki. 384 | 385 | =head1 SYNOPSIS 386 | 387 | # Step 1: Create the source file 388 | 389 | # Step 2: Run the anki_import command 390 | supplied by this module... 391 | 392 | # ...from the command line 393 | anki_import path/to/source_file.txt 394 | 395 | # or 396 | 397 | # ...from within a perl script 398 | use Anki::Import; 399 | anki_import('path/to/source_file.txt'); 400 | 401 | # Step 3: Import the resultant files into Anki 402 | 403 | =head1 DESCRIPTION 404 | 405 | Inputting notes into Anki can be a tedious chore. C lets you 406 | you generate Anki notes with your favorite text editor (e.g. vim, BBEdit, Atom, 407 | etc.) so you can enter formatted notes into Anki's database more efficiently. 408 | 409 | At a minimum, you should have basic familiarity with using your computer's 410 | command line terminal to make use of this program. 411 | 412 | =head2 Steps for creating, processing and imorting new notes 413 | 414 | =head3 Step 1: Generate the notes with your text editor 415 | 416 | First, you create a specially formatted source file which 417 | C will process. The source file is a simple text file with 418 | basic formatting rules you must follow. 419 | 420 | See the L section for details. 421 | 422 | =head3 Step 2: Process the notes with C 423 | 424 | Once the source file is created and saved, run the 425 | C command from the command line or from a script to generate the 426 | import files. This will create a new directory called "anki_import_files" 427 | containing one text file for each of the note types generated by C 428 | and which you will import in the next step. By default, the directory is 429 | created in the current directory. 430 | 431 | See the L section for more details and options. 432 | 433 | =head3 Step 3: Import the processed notes with Anki 434 | 435 | In Anki, open the deck you wish to import and hit Ctrl-I or (Cmd-I on a 436 | Mac) to start the import process, navigate to the a file generated by 437 | C and select one of them. 438 | 439 | Next, check Anki's settings to be sure you are importing notes into the proper 440 | fields, deck and note type. Also ensure you have the "Allow HTML in fields" 441 | option enabled and that you have "Fields separated by: Tab" selected. 442 | 443 | Click "Import" and repeat for each note type you are importing. 444 | 445 | Consult L 446 | for more details on importing and managing your notes. 447 | 448 | =head2 General description of the source file 449 | 450 | The source file contains one or more Anki notes. To make importing easier, 451 | each source file should contain notes that will be imported into the same Anki 452 | deck. 453 | 454 | =head3 Creating notes and fields in the source file 455 | 456 | Each note in the source file contains fields which should correspond to your 457 | existing note types in Anki. Individual notes in the source file are delineated 458 | by two or more blank lines. Fields are separated by a single blank line. Fields 459 | for each note should be in the same order as your Anki note types to make 460 | importing more automatic. All fields must have content or left intentionally 461 | blank. 462 | 463 | To create an intionally blank field, add a single '`' (backtick) character on a 464 | line by itself with blank lines before and after the line with the single 465 | backtick. 466 | 467 | See the L for more help. 468 | 469 | =head3 Source file requirements and limitations 470 | 471 | =head4 Use UTF-8 encoding 472 | 473 | The source file should be a plain text file with UTF-8 encoding. UTF-8 474 | is likely the default encoding method for your editor but check your editor's 475 | settings and documentation for further details. 476 | 477 | =head4 Avoid tabs 478 | 479 | Since tab characters are used by Anki to split your fields, you should 480 | avoid relying on tab characters in your source file. Any tabs found in your 481 | source file will get converted to four spaces. 482 | 483 | =head3 Assigning notes to note types 484 | 485 | You can indicate which note type a note belongs to by preceding notes with a 486 | C<#note_type> comment at the beginning of a line. You can choose any note type 487 | name you wish but it is recommended that you use note type names similar to 488 | those that exist in your Anki database to make importing the notes easier. 489 | 490 | Note type comments not only assign a note type to the next note, but any 491 | notes therafter until a new note type comment is encountered (see the example 492 | in the next section). So note type comments actually delineate a note type 493 | section. If no note types are indicated in your source file, the 494 | "Basic" note type is used. 495 | 496 | Note types are used to help C ensure other notes of the same type 497 | have the same number of fields. If the notes assigned to a particular note type 498 | do not all have the same number of fields, an error is thrown so be sure each 499 | note has the correct number of fields. 500 | 501 | Note: note type sections can be split across the file (i.e. you do not have to 502 | group the notes of a particular note type together). 503 | 504 | =head3 Tagging notes 505 | 506 | Place your space seprated lit of tags in the last field. As long as there is 507 | one more field in the source files that fields in the note you are importing 508 | to, Anki will generate tags from the last field. 509 | 510 | You can automate tag generation by placing a single '^' (caret) character 511 | on a line by itself immediately after your list of tags. These tags will now 512 | be used for all later notes in the file until they are overridden by a new list 513 | of automated tags. Also any new tags you place at the end of a note will be 514 | added to the list of tags that are automatically generated. 515 | 516 | To reset the automated list of tags, place a single '^' (caret) character 517 | in place of the field where your tags will go. 518 | 519 | To suppress the application of an automated tag from the list of automated tags 520 | for a particular note, include that tag in the tag field and it will not be 521 | tagged with that term for that one note. 522 | 523 | To add a new tag to the already existing set of tags, enter the tags on 524 | a line followed by a new line with a single '+' sign on it by itself. 525 | 526 | Note: If you use tags on any of your notes in a parcitular note type, you must 527 | use tags on all of your notes or indicate that the tag field should be left 528 | blank with a '`' (backtick) character on a line by itself. 529 | 530 | =head3 Applying text formatting to your notes 531 | 532 | Learning how to format the source file is key to getting Anki to import your 533 | notes properly and getting the most out of C. 534 | 535 | Following a few simple rules, you can assign notes to a note type, preserve 536 | whitespace in fields, create bold text, create blank lines in your fields, 537 | add tags, create cloze deletions, indicate which fields are blank and 538 | generate simple lists. Study the example below for details. 539 | 540 | Note: Lines containing only whitespace characters are treated as blank lines. 541 | 542 | =head4 Example source file 543 | 544 | Below is an example of how to format a source data file. Note that the column on 545 | the right containing comments for this example are not permitted in an actual 546 | source data file. 547 | 548 | # Basic # We start a note section here. Any 549 | # notes below here to the next 550 | # note type comment are assigned to 551 | # the 'Basic' note type 552 | 553 | # You can have blank lines between the 554 | # note type comment and the next 555 | # question. 556 | 557 | What is the first day of the week? # Question 1, Field 1 558 | # Blank line here indicates a new field. 559 | Monday. # Question 1, Field 2 560 | 561 | # Add two or more blank lines between 562 | questions 563 | 564 | 565 | 566 | How many days of the week are there? # Question 2, Field 1 567 | 568 | Our caldendar # Question 2, Field 2 569 | has seven days # Answers can run 570 | in a week # across one or more lines but 571 | # will be imported as a single 572 | # line into Anki. 573 | 574 | 575 | 576 | # less_basic # New note type called "less_basic" 577 | # with 3 fields 578 | What is the third day of week? # Question 3, Field 1 579 | 580 | Wednesday # Question 3, Field 2 581 | 582 | Wed. # Question 3, Field 3 583 | 584 | your_tags go_here # We set up automated tags on this note 585 | ^ # with the '^' symbol on a line by itself 586 | # immediately after out tag list. 587 | # These tags will be applied to this and 588 | # all future notes unless overridden. 589 | 590 | 591 | Put {{{another question}}} here. # Surround text with 3 braces for a cloze 592 | 593 | Here is an field that has 594 | ` # Insert a blank line into a field 595 | a blank line in it. # with a single backtick character 596 | # surrounded by lines with text. 597 | go_here # We set autotags in the last note and 598 | # they will carry forward to this note 599 | # except for the exclusions we place 600 | # here. This note will *not* be tagged 601 | # with 'go_here' but it will still be 602 | # tagged under 'your_tags'. 603 | 604 | 605 | 606 | What does this code do? # Another less_basic question 607 | 608 | ``` # Preserve whitespace in a field with 3 609 | This_is_some_code { # backticks on a single line. 610 | ` # You must still backtick blank lines 611 | print 'Whitespace will be # when preserving whitespace, however. 612 | preserved'; 613 | ` # Another blank line. 614 | } 615 | ``` # End whitespace preservation 616 | 617 | This is %comma,delimted,text% # Bullet lists with %item1,item2,item3% 618 | 619 | ' # The tags field is left blank. But all 620 | # the auto tags will still be applied. 621 | 622 | 623 | Another question # Field 1 624 | 625 | ` # Field 2 is blank. 626 | 627 | This is *in bold* # Field 3 has bold words, followed by a 628 | ` # blank line, followed by 629 | %an,unordered,list% # an ordered list. 630 | 631 | new_tags more_new_tags # This and future notes will use these 632 | ^ # newer automated tags. 633 | 634 | 635 | #basic # switch back to a 'basic' note type 636 | Last question 637 | 638 | Last anser 639 | 640 | add_this_tag_to_autotags # We add a new_tag to our autotag list 641 | + # with the '+' sign by itself on a new 642 | # line. 643 | 644 | =head3 Getting the most from C 645 | 646 | By itself, C will make it easier for you to format and 647 | input your notes especially if you do a lot of basic HTML formatting. However, 648 | the huge productivity gains of C can only be unlocked by getting 649 | proficient wih your text editor of choice. 650 | 651 | For example, you can generate templates for each of the note types you use to 652 | make data entry exceptionally painless. And with a text editor like vim, you 653 | can automate the generation of the formatting codes used by C 654 | and make Anki note creation joyful, or at least much less tedious. 655 | 656 | Teaching you how to use and optimize your text editor for C is 657 | well beyond the scope of this document. But if you take the time now and do the 658 | up front work of learning your text editor and tweaking it for use with 659 | C, you will save a lot of time in the long run. 660 | 661 | In the future, vim configurations and plugins for use with C 662 | may be released as they are developed to help you get going faster with vim. 663 | Unfortunately, other text editors cannot be supported as there are far too many 664 | and far too little time to get familiar with all their features. 665 | 666 | =head1 USAGE 667 | 668 | C can be run from the command line or from within another perl 669 | script. It behaves the same way in both environments. 670 | 671 | =head2 Command line usage 672 | 673 | The C module installs the C command line command 674 | for generating import files which is used as follow: 675 | 676 | anki_import source_file [parent_dir] [--verbosity_level] 677 | 678 | B anki_import pop_quiz.txt /home/me --verbose 679 | 680 | C processes the C and generates files to be imported into 681 | Anki, one file for each note type. These files are placed in a directory called 682 | C. This directory is placed in the current working directory 683 | by default. 684 | 685 | Note that previously generated files already located in the C 686 | directory the command is outputting to will will be overwritten without warning. 687 | Add a unique (C path to help prevent this. 688 | 689 | B> is an optional argument containing the path you want C 690 | to save the files for output. You may use a C<~> (tilde) to represent the home 691 | directory for the current user. 692 | 693 | B> options can be set to C<--verbose> or C<--vverbose> 694 | (very verbose) or C<--quiet>. The verbosity options have aliases for your 695 | typing convenience: C<-v>, C<-V> and C<-q>, respectively. 696 | 697 | Use the C<--verbose> or C<--vverbose> option to help troubleshoot source file 698 | processing issues. The (C<--quiet>) option suppresses the success 699 | message printed upon successful processing of the source file. 700 | 701 | =head2 From a script 702 | 703 | Invoking the C function mirrors the arguments used from the 704 | command line: 705 | 706 | =method anki_import($source_file, [$parent_dir], [$verbosity]); 707 | 708 | Usage in a script is the same as for the command line except that the arguments 709 | must be enclosed in quotes. 710 | 711 | Example: 712 | 713 | anki_import('script_file.txt', '/home/me', '--verbose'); 714 | 715 | See the L for more details on the optional arguments. By 716 | default, the verbosity output from the function call is (C<--quiet>. If you 717 | want the function call to output a success message, use (C<--no-quiet>); 718 | 719 | =head1 INSTALLATION 720 | 721 | C is written in the Perl programming langauge. Therefore, you must 722 | have Perl installed on your system. MacOS and *nix machines will have 723 | Perl already installed but the Windows operating system does not 724 | come pre-installed with Perl and so you may have to install it first before you 725 | can use C. 726 | 727 | If you are unsure if you have Perl installed, open a command prompt and type in: 728 | 729 | perl -v 730 | 731 | If Perl is installed, it will report the version of Perl on your machine and 732 | other information. If Perl is not installed, you will get a "not recognized" 733 | error on Windows. 734 | 735 | =head2 Installing Strawberry Perl on Windows 736 | 737 | If you are on Windows and you do not have Perl installed, you can download a 738 | version of Perl called "Strawberry Perl" from the 739 | L. Be sure to install the 740 | proper version (64 or 32 bit). 741 | 742 | Once installed successfully, see the next section for downloading and installing 743 | C. 744 | 745 | =head2 Installing Anki::Import with C 746 | 747 | C is easy to install if you have a Perl module called 748 | L installed. This module provides a command, C, to easily 749 | downloading and installing modules from the Perl module repository called 750 | B. Simply run this command from the command line to install 751 | C: 752 | 753 | cpanm Anki::Import 754 | 755 | Strawberry Perl for Windows has the C already installed. 756 | 757 | =head2 Installing Anki::Import without C 758 | 759 | If you do not have the C command on your computer, you will need to use 760 | either the older CPAN shell method of installation or, as a last resort, perform 761 | manual installation. Refer to the 762 | C L 763 | for further details on these installation methods. 764 | 765 | =head1 DEVELOPMENT STATUS 766 | 767 | This module is currently in the beta stages and is actively supported and 768 | maintained. Suggestions for improvement are welcome. There are likely bugs 769 | with the text formatting in certain edge cases but it should work well for 770 | normal, intended use. 771 | 772 | L 773 | -------------------------------------------------------------------------------- /out: -------------------------------------------------------------------------------- 1 | [DZ] beginning to build Anki-Import 2 | [Git::NextVersion] Bumping version from 0.021 to 0.022 3 | [PodVersion] couldn't find '=head1 NAME' in bin/anki_import, not adding '=head1 VERSION' 4 | [PodVersion] couldn't find '=head1 NAME' in lib/Anki/Import.pm, not adding '=head1 VERSION' 5 | [GitHub::Meta] Getting GitHub repository info 6 | [DZ] guessing dist's main_module is lib/Anki/Import.pm 7 | [DZ] writing Anki-Import in Anki-Import-0.022 8 | [DZ] building archive with Archive::Tar::Wrapper 9 | [DZ] writing archive to Anki-Import-0.022.tar.gz 10 | [Git::Check] branch master has some untracked files: 11 | [Git::Check] .build/WHdD5VWtGU/Anki-Import-0.022/Changes 12 | [Git::Check] .build/WHdD5VWtGU/Anki-Import-0.022/INSTALL 13 | [Git::Check] .build/WHdD5VWtGU/Anki-Import-0.022/LICENSE 14 | [Git::Check] .build/WHdD5VWtGU/Anki-Import-0.022/MANIFEST 15 | [Git::Check] .build/WHdD5VWtGU/Anki-Import-0.022/META.json 16 | [Git::Check] .build/WHdD5VWtGU/Anki-Import-0.022/META.yml 17 | [Git::Check] .build/WHdD5VWtGU/Anki-Import-0.022/Makefile.PL 18 | [Git::Check] .build/WHdD5VWtGU/Anki-Import-0.022/README 19 | [Git::Check] .build/WHdD5VWtGU/Anki-Import-0.022/bin/anki_import 20 | [Git::Check] .build/WHdD5VWtGU/Anki-Import-0.022/lib/Anki/Import.pm 21 | [Git::Check] .build/WHdD5VWtGU/Anki-Import-0.022/share/log_config/anki-import.cfg 22 | [Git::Check] .build/WHdD5VWtGU/Anki-Import-0.022/share/log_config/test.cfg 23 | [Git::Check] .build/WHdD5VWtGU/Anki-Import-0.022/t/00-use_ok.t 24 | [Git::Check] .build/WHdD5VWtGU/Anki-Import-0.022/t/01-class.t 25 | [Git::Check] .build/WHdD5VWtGU/Anki-Import-0.022/t/02-cli.t 26 | [Git::Check] .build/WHdD5VWtGU/Anki-Import-0.022/t/03-format_tests.t 27 | [Git::Check] .build/WHdD5VWtGU/Anki-Import-0.022/t/data/code_with_blank_lines.anki 28 | [Git::Check] .build/WHdD5VWtGU/Anki-Import-0.022/t/data/cs61.anki 29 | [Git::Check] .build/WHdD5VWtGU/Anki-Import-0.022/t/data/distzilla.anki 30 | [Git::Check] .build/WHdD5VWtGU/Anki-Import-0.022/t/data/perl_modules.anki 31 | [Git::Check] .build/WHdD5VWtGU/Anki-Import-0.022/t/data/source.anki 32 | [Git::Check] .build/WHdD5VWtGU/Anki-Import-0.022/t/data/source2.anki 33 | [Git::Check] .build/WHdD5VWtGU/Anki-Import-0.022/t/data/tag_test.anki 34 | [Git::Check] out 35 | -------------------------------------------------------------------------------- /share/log_config/anki-import.cfg: -------------------------------------------------------------------------------- 1 | log4perl.rootLogger=TRACE, trace, debug, info, warn, error, fatal 2 | #log4perl.logger.Module.Name=TRACE, trace, debug, info, warn, error, fatal 3 | #log4perl.logger.Module2.Name=DEBUG, trace, debug, info, warn, error, fatal 4 | 5 | ### TRACE PACKAGES #### 6 | #log4perl.logger.Module.Name=TRACE, trace, info 7 | #log4perl.logger.Module2.Name=TRACE, trace, info 8 | 9 | 10 | ### DEBUG CATEGORIES ### 11 | #log4perl.logger.function_name=DEBUG, debug 12 | #log4perl.logger.Module.Name.function_name=DEBUG, debug 13 | #log4perl.logger.Module.Name.function_name2=DEBUG, debug 14 | 15 | log4perl.filter.trace = Log::Log4perl::Filter::LevelMatch 16 | log4perl.filter.trace.LevelToMatch = TRACE 17 | log4perl.filter.trace.AcceptOnMatch = true 18 | 19 | log4perl.filter.debug = Log::Log4perl::Filter::LevelMatch 20 | log4perl.filter.debug.LevelToMatch = DEBUG 21 | log4perl.filter.debug.AcceptOnMatch = true 22 | 23 | log4perl.filter.info = Log::Log4perl::Filter::LevelMatch 24 | log4perl.filter.info.LevelToMatch = INFO 25 | log4perl.filter.info.AcceptOnMatch = true 26 | 27 | log4perl.filter.warn = Log::Log4perl::Filter::LevelMatch 28 | log4perl.filter.warn.LevelToMatch = WARN 29 | log4perl.filter.warn.AcceptOnMatch = true 30 | 31 | log4perl.filter.error = Log::Log4perl::Filter::LevelMatch 32 | log4perl.filter.error.LevelToMatch = ERROR 33 | log4perl.filter.error.AcceptOnMatch = true 34 | 35 | log4perl.filter.fatal = Log::Log4perl::Filter::LevelMatch 36 | log4perl.filter.fatal.LevelToMatch = FATAL 37 | log4perl.filter.fatal.AcceptOnMatch = true 38 | 39 | log4perl.appender.trace=Log::Log4perl::Appender::Screen 40 | log4perl.appender.debug=Log::Log4perl::Appender::Screen 41 | log4perl.appender.info=Log::Log4perl::Appender::Screen 42 | log4perl.appender.warn=Log::Log4perl::Appender::Screen 43 | log4perl.appender.error=Log::Log4perl::Appender::Screen 44 | log4perl.appender.fatal=Log::Log4perl::Appender::Screen 45 | #log4perl.appender.error.color.ERROR=red 46 | #log4perl.appender.fatal.color.FATAL=white on_red 47 | #log4perl.appender.warn.color.WARN=yellow 48 | #log4perl.appender.debug.color.DEBUG=white 49 | #log4perl.appender.trace.color.TRACE=cyan 50 | #log4perl.appender.info.color.INFO=green 51 | 52 | #log4perl.appender.trace=Log::Log4perl::Appender::ScreenColoredLevels 53 | #log4perl.appender.debug=Log::Log4perl::Appender::ScreenColoredLevels 54 | #log4perl.appender.info=Log::Log4perl::Appender::ScreenColoredLevels 55 | #log4perl.appender.warn=Log::Log4perl::Appender::ScreenColoredLevels 56 | #log4perl.appender.error=Log::Log4perl::Appender::ScreenColoredLevels 57 | #log4perl.appender.fatal=Log::Log4perl::Appender::ScreenColoredLevels 58 | 59 | log4perl.appender.trace.Filter = trace 60 | log4perl.appender.debug.Filter = debug 61 | log4perl.appender.info.Filter = info 62 | log4perl.appender.warn.Filter = warn 63 | log4perl.appender.error.Filter = error 64 | log4perl.appender.fatal.Filter = fatal 65 | 66 | log4perl.appender.trace.layout=PatternLayout 67 | log4perl.appender.debug.layout=PatternLayout 68 | log4perl.appender.info.layout=PatternLayout 69 | log4perl.appender.warn.layout=PatternLayout 70 | log4perl.appender.error.layout=PatternLayout 71 | log4perl.appender.fatal.layout=PatternLayout 72 | 73 | log4j.PatternLayout.cspec.E = sub { return [caller(5)]->[1]; }; 74 | log4j.PatternLayout.cspec.e = sub { return [caller(5)]->[2]; }; 75 | #log4j.PatternLayout.cspec.D = sub { use Data::Dumper qw(Dumper); return Dumper($_[1]); }; 76 | log4j.PatternLayout.cspec.s = sub { return [caller(6)]->[1] . ': ' . [caller(6)]->[2]; }; 77 | 78 | log4perl.appender.trace.layout.ConversionPattern=[%-5p] %-80m %c: %e%n 79 | log4perl.appender.debug.layout.ConversionPattern=[%-5p] %c: %e%n %8m%n 80 | log4perl.appender.info.layout.ConversionPattern= [%-5p] %-80m %c: %e%n 81 | #log4perl.appender.info.layout.ConversionPattern= [%-5p] %-80m %n 82 | log4perl.appender.warn.layout.ConversionPattern= [%-5p] %-80m 83 | log4perl.appender.error.layout.ConversionPattern=[%-5p] %m%n 84 | log4perl.appender.fatal.layout.ConversionPattern=[%-5p] %-80m 85 | -------------------------------------------------------------------------------- /share/log_config/test.cfg: -------------------------------------------------------------------------------- 1 | log4perl.rootLogger=TRACE, trace, debug, info, warn, error, fatal 2 | #log4perl.logger.Module.Name=TRACE, trace, debug, info, warn, error, fatal 3 | #log4perl.logger.Module2.Name=DEBUG, trace, debug, info, warn, error, fatal 4 | 5 | ### TRACE PACKAGES #### 6 | #log4perl.logger.Module.Name=TRACE, trace, info 7 | #log4perl.logger.Module2.Name=TRACE, trace, info 8 | 9 | 10 | ### DEBUG CATEGORIES ### 11 | #log4perl.logger.function_name=DEBUG, debug 12 | #log4perl.logger.Module.Name.function_name=DEBUG, debug 13 | #log4perl.logger.Module.Name.function_name2=DEBUG, debug 14 | 15 | log4perl.filter.trace = Log::Log4perl::Filter::LevelMatch 16 | log4perl.filter.trace.LevelToMatch = TRACE 17 | log4perl.filter.trace.AcceptOnMatch = true 18 | 19 | log4perl.filter.debug = Log::Log4perl::Filter::LevelMatch 20 | log4perl.filter.debug.LevelToMatch = DEBUG 21 | log4perl.filter.debug.AcceptOnMatch = true 22 | 23 | log4perl.filter.info = Log::Log4perl::Filter::LevelMatch 24 | log4perl.filter.info.LevelToMatch = INFO 25 | log4perl.filter.info.AcceptOnMatch = true 26 | 27 | log4perl.filter.warn = Log::Log4perl::Filter::LevelMatch 28 | log4perl.filter.warn.LevelToMatch = WARN 29 | log4perl.filter.warn.AcceptOnMatch = true 30 | 31 | log4perl.filter.error = Log::Log4perl::Filter::LevelMatch 32 | log4perl.filter.error.LevelToMatch = ERROR 33 | log4perl.filter.error.AcceptOnMatch = true 34 | 35 | log4perl.filter.fatal = Log::Log4perl::Filter::LevelMatch 36 | log4perl.filter.fatal.LevelToMatch = FATAL 37 | log4perl.filter.fatal.AcceptOnMatch = true 38 | 39 | log4perl.appender.trace=Log::Log4perl::Appender::Screen 40 | log4perl.appender.debug=Log::Log4perl::Appender::Screen 41 | log4perl.appender.info=Log::Log4perl::Appender::Screen 42 | log4perl.appender.warn=Log::Log4perl::Appender::Screen 43 | log4perl.appender.error=Log::Log4perl::Appender::Screen 44 | log4perl.appender.fatal=Log::Log4perl::Appender::Screen 45 | #log4perl.appender.error.color.ERROR=red 46 | #log4perl.appender.fatal.color.FATAL=white on_red 47 | #log4perl.appender.warn.color.WARN=yellow 48 | #log4perl.appender.debug.color.DEBUG=white 49 | #log4perl.appender.trace.color.TRACE=cyan 50 | #log4perl.appender.info.color.INFO=green 51 | 52 | #log4perl.appender.trace=Log::Log4perl::Appender::ScreenColoredLevels 53 | #log4perl.appender.debug=Log::Log4perl::Appender::ScreenColoredLevels 54 | #log4perl.appender.info=Log::Log4perl::Appender::ScreenColoredLevels 55 | #log4perl.appender.warn=Log::Log4perl::Appender::ScreenColoredLevels 56 | #log4perl.appender.error=Log::Log4perl::Appender::ScreenColoredLevels 57 | #log4perl.appender.fatal=Log::Log4perl::Appender::ScreenColoredLevels 58 | 59 | log4perl.appender.trace.Filter = trace 60 | log4perl.appender.debug.Filter = debug 61 | log4perl.appender.info.Filter = info 62 | log4perl.appender.warn.Filter = warn 63 | log4perl.appender.error.Filter = error 64 | log4perl.appender.fatal.Filter = fatal 65 | 66 | log4perl.appender.trace.layout=PatternLayout 67 | log4perl.appender.debug.layout=PatternLayout 68 | log4perl.appender.info.layout=PatternLayout 69 | log4perl.appender.warn.layout=PatternLayout 70 | log4perl.appender.error.layout=PatternLayout 71 | log4perl.appender.fatal.layout=PatternLayout 72 | 73 | log4j.PatternLayout.cspec.E = sub { return [caller(5)]->[1]; }; 74 | log4j.PatternLayout.cspec.e = sub { return [caller(5)]->[2]; }; 75 | #log4j.PatternLayout.cspec.D = sub { use Data::Dumper qw(Dumper); return Dumper($_[1]); }; 76 | log4j.PatternLayout.cspec.s = sub { return [caller(6)]->[1] . ': ' . [caller(6)]->[2]; }; 77 | 78 | log4perl.appender.trace.layout.ConversionPattern=[%-5p] %-80m %c: %e%n 79 | log4perl.appender.debug.layout.ConversionPattern=[%-5p] %c: %e%n %8m%n 80 | log4perl.appender.info.layout.ConversionPattern= [%-5p] %-80m %c: %e%n 81 | log4perl.appender.warn.layout.ConversionPattern= [%-5p] %-80m %c: %e%n 82 | log4perl.appender.error.layout.ConversionPattern=[%-5p] %m%n 83 | log4perl.appender.fatal.layout.ConversionPattern=[%-5p] %-80m %c: %e%n 84 | -------------------------------------------------------------------------------- /t/00-use_ok.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | use strict; 3 | use warnings; 4 | 5 | use Test::More tests => 1; 6 | BEGIN { use_ok('Anki::Import') }; 7 | -------------------------------------------------------------------------------- /t/01-class.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | use Test::More; 3 | use Anki::Import; 4 | use Test::Exception; 5 | use Test::Warnings; 6 | use File::Path; 7 | use File::Spec; 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | my $tests = 8; # keep on line 17 for ,i (increment and ,d (decrement) 18 | diag( "Running my tests" ); 19 | 20 | plan tests => $tests; 21 | dies_ok { anki_import(); } 'dies without file getting passed'; 22 | dies_ok { anki_import('askdjfakdewere2332'); } 'dies with bad file name'; 23 | my $path1 = File::Spec->catfile('t', 'data', 'source.anki' ); 24 | my $path2 = File::Spec->catfile( 't', 'data', 'source2.anki' ); 25 | my $path3 = File::Spec->catfile( 't', 'data', 'tag_test.anki' ); 26 | lives_ok { anki_import($path1); } 'lives with good file name'; 27 | lives_ok { anki_import($path1, '-V'); } 'lives with good file n#ame'; 28 | lives_ok { anki_import($path1); } 'lives with good file name'; 29 | dies_ok { anki_import($path2); } 'dies when notes have different nu#mber of fields'; 30 | lives_ok { anki_import($path3, '-V'); } 'lives with good file name'; 31 | #lives_ok { anki_import('t/data/cs61.anki', '-V'); } 'lives with good file name'; 32 | rmtree 'anki_import_files'; 33 | -------------------------------------------------------------------------------- /t/02-cli.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | use Test::More; 3 | use Anki::Import; 4 | use Test::Exception; 5 | use Test::Warnings; 6 | use Test::Output; 7 | use File::Spec; 8 | use File::Path; 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | my $tests = 4; # keep on line 17 for ,i (increment and ,d (decrement) 18 | diag( "Running my tests" ); 19 | 20 | plan tests => $tests; 21 | 22 | my $cmd = File::Spec->catfile('bin', 'anki_import'); 23 | $cmd =~ s/\\/\\\\/; 24 | stderr_like { `$^X $cmd` } qr/usage: anki_import FILE/, 'dies without file'; 25 | 26 | $cmd = File::Spec->catfile('bin', 'anki_import'); 27 | $cmd =~ s/\\/\\\\/; 28 | stderr_like { `$^X $cmd blasdfah` } qr/[FATAL].*does not exist/, 'dies with bad file'; 29 | 30 | $cmd = File::Spec->catfile('bin', 'anki_import'); 31 | $cmd =~ s/\\/\\\\/; 32 | my $path = File::Spec->catfile('t', 'data' , 'source.anki'); 33 | lives_ok { `$^X $cmd $path` } 'can process good file'; 34 | 35 | #lives_ok { `bin/anki_import t/data/distzilla.anki -V` } 'can process good file'; 36 | 37 | rmtree 'anki_import_files'; 38 | -------------------------------------------------------------------------------- /t/03-format_tests.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | use Test::More; 3 | use Anki::Import; 4 | use Test::Warnings; 5 | use File::Spec; 6 | use File::Path; 7 | 8 | my $tests = 14; # keep on line 17 for ,i (increment and ,d (decrement) 9 | diag( "Running my tests" ); 10 | 11 | plan tests => $tests; 12 | 13 | my $data = get_data('code_with_blank_lines', 'basic'); 14 | 15 | is (mcount("\t"), 1, 'got expected number of tabs'); 16 | is (mcount("\n"), 0, 'got expected number of newlines'); 17 | is (mcount("^\t"), 1, 'div tag closed before tab'); 19 | is (mcount("\tAnswer\$"), 1, 'answer properly formatted'); 20 | is (mcount(">Line 1

    "), 1, 'line 1 properly formatted'); 21 | is (mcount(">
    Line 2

    "), 1, 'line 2 properly formatted'); 22 | is (mcount(">Line 3"), 1, 'line 3 does not end in
    '); 23 | 24 | $data = get_data('escape_angle_brackets', 'basic'); 25 | 26 | is (mcount("<"), 4, 'got expected number of html entities'); 27 | is ($data =~ /with <angle/, 1, 'first angle bracket replaced'); 28 | is ($data =~ /one <angle/, 1, 'second angle bracket replaced'); 29 | is ($data =~ /more <of/, 1, 'third angle bracket replaced'); 30 | is ($data =~ /of <them/, 1, 'last angle bracket replaced'); 31 | 32 | use Data::Dumper qw(Dumper); 33 | 34 | print Dumper $data; 35 | 36 | 37 | 38 | my $path = File::Spec->catfile('t', 'data', 'anki_import_files'); 39 | rmtree $path; 40 | 41 | sub get_data { 42 | my $file = shift; 43 | my $type = shift; 44 | my $path1 = File::Spec->catfile('t', 'data', "$file.anki"); 45 | my $path2 = File::Spec->catfile('t', 'data'); 46 | anki_import($path1, $path2, '-V'); 47 | my $path3 = File::Spec->catfile('t', 'data', 'anki_import_files', "${type}_notes_import.txt"); 48 | open (my $data_file, "<:encoding(UTF-8)", $path3) or die "Can't open '$path3' for reading: $!"; 49 | my $content; 50 | { local $/; $content = <$data_file>; } 51 | close $file; 52 | return $content; 53 | } 54 | 55 | sub mcount { 56 | my $regex_str = shift; 57 | my @matches = $data =~ /$regex_str/g; 58 | my $matches = @matches; 59 | 60 | return $matches; 61 | } 62 | -------------------------------------------------------------------------------- /t/data/code_with_blank_lines.anki: -------------------------------------------------------------------------------- 1 | #basic 2 | ``` 3 | Line 1 4 | ` 5 | Line 2 6 | ` 7 | Line 3 8 | ``` 9 | 10 | Answer 11 | -------------------------------------------------------------------------------- /t/data/cs61.anki: -------------------------------------------------------------------------------- 1 | #basic 2 | What is the name of the book used in the course? 3 | 4 | Structure and Interpretation of Computer Programs 5 | 6 | programming_concepts cs61A_lect1 7 | ^ 8 | 9 | 10 | #basic 11 | What programming language is used in the course? 12 | 13 | Scheme 14 | 15 | ` 16 | 17 | 18 | #basic 19 | What is the implementation of scheme used? 20 | 21 | STk 22 | 23 | ` 24 | 25 | 26 | #cloze 27 | Scheme uses {{{post}}}fix operators. 28 | 29 | ` 30 | 31 | scheme 32 | 33 | 34 | 35 | #csnip 36 | ``` 37 | (+ 2 3) 38 | ``` 39 | 40 | adds 2 + 3; results in '5' 41 | 42 | ` 43 | 44 | 45 | #csnip 46 | ``` 47 | (+ 2 2 2) 48 | ``` 49 | 50 | adds 2 + 2 + 2; results in '6' 51 | 52 | ` 53 | 54 | 55 | #csnip 56 | ``` 57 | (+) 58 | ``` 59 | 60 | results in '0'. 61 | 62 | ` 63 | 64 | 65 | #csnip 66 | ``` 67 | (*) 68 | ``` 69 | 70 | results in '1' because that's the identity element for the multiplication 71 | operator 72 | 73 | ` 74 | 75 | 76 | #csnip 77 | ``` 78 | (/) 79 | ``` 80 | 81 | results in error, division by 0 82 | 83 | ` 84 | 85 | 86 | #csnip 87 | ``` 88 | + 89 | ``` 90 | 91 | Does not result in an error. '+' is interpreted as a function. 92 | 93 | ` 94 | 95 | 96 | #cquest 97 | How do you represent a literal '+'? 98 | 99 | ``` 100 | '+ 101 | ``` 102 | 103 | ` 104 | 105 | ` 106 | 107 | 108 | #csnip 109 | ``` 110 | 'hello 111 | ``` 112 | 113 | evaluates to the word "hello" 114 | 115 | ` 116 | 117 | 118 | #cans 119 | Demonstrate how to take the result of one function to produce arguments to 120 | another function (known as composition of functions). 121 | 122 | ``` 123 | (+ ( 3 * 10 ) ( 2 + 3) ) 124 | ``` 125 | 126 | adds 30 + 5, or 35 127 | 128 | ` 129 | 130 | 131 | #csnip 132 | ``` 133 | (first 'hello) 134 | ``` 135 | 136 | evaluates to "h", the first letter of the word 137 | 138 | ` 139 | 140 | 141 | #csnip 142 | ``` 143 | (last 'hello) 144 | ``` 145 | 146 | evaluates to "o", the last letter of the word 147 | 148 | ` 149 | 150 | 151 | #csnip 152 | ``` 153 | (butfirst 'hello) 154 | ``` 155 | 156 | evaluates to "ello', all but the first letter of the word 157 | 158 | ` 159 | 160 | 161 | #csnip 162 | ``` 163 | (butlast 'hello) 164 | ``` 165 | 166 | evaluate to "hell', all but the first letter of the word 167 | 168 | ` 169 | 170 | 171 | #csnip 172 | ``` 173 | (bf 'scheme) 174 | ``` 175 | 176 | evaluates to "scheme", all but the first letter of the word (shorthand for the 177 | `butfirst` command) 178 | 179 | ` 180 | 181 | 182 | #csnip 183 | ``` 184 | (word 'now 'here) 185 | ``` 186 | 187 | evaluates to "nowhere" (concatenates the words) 188 | 189 | ` 190 | 191 | 192 | #cloze 193 | The `word` command is known as a {{{constructor}}} function. 194 | 195 | ` 196 | 197 | ` 198 | 199 | 200 | #csnip 201 | ``` 202 | (sentence 'now 'here') 203 | ``` 204 | 205 | evaluate to ("now here") with a space between the words surrounded by parentheses 206 | 207 | ` 208 | 209 | 210 | #csnip 211 | ``` 212 | '(magical mystery tour' 213 | ``` 214 | 215 | (magical mystery tour) 216 | 217 | ` 218 | 219 | 220 | #csnip 221 | ``` 222 | (first '(got to get you into my life)) 223 | ``` 224 | 225 | evaluate to "got", the first word of the sentence 226 | 227 | ` 228 | 229 | 230 | #csnip 231 | ``` 232 | (first (bf '(a hard days night))) 233 | ``` 234 | 235 | evaluate to "hard" 236 | 237 | ` 238 | 239 | 240 | #csnip 241 | ``` 242 | (first (first (bf '(she loves you)))) 243 | ``` 244 | 245 | evaluates to "l" 246 | 247 | ` 248 | 249 | 250 | #csnip 251 | ``` 252 | (define pi 3.14) 253 | (* pi 5 5) 254 | ``` 255 | 256 | The first line sets the value of `pi` to 3.14. The second line multiplies `pi` 257 | by 25 (5 * 5). 258 | 259 | ` 260 | 261 | 262 | #csnip 263 | ``` 264 | (define (square x) 265 | (* x x) 266 | (square (+ 2 3)) 267 | ``` 268 | 269 | It defines a function called `square` that takes a single argument called `x`. 270 | The body of the function takes `x` and multiplies it by itself. The third line 271 | calls the `square` function with result of the express `2 + 3` (or 5), which 272 | returns an answer of 25 (5 squared). 273 | 274 | ` 275 | 276 | 277 | #basic 278 | Why is the `define` function considered an exception to the way scheme normally 279 | processes arguments? 280 | 281 | Because it does not evaluate the arguments that are passed to it. 282 | 283 | ` 284 | 285 | 286 | #basic 287 | Describe what we mean when we say a function is a *special form* function? 288 | 289 | It is a function that does not process arguments the way other scheme functions 290 | do. The `define` function is an example. 291 | 292 | ` 293 | 294 | 295 | #cquest 296 | What does the following code evaluate to and why? 297 | 298 | ``` 299 | (define hello (+ 2 3)) 300 | hello 301 | ``` 302 | 303 | The first line evaluates the word "hello," the function that was defined. It 304 | does not evaluate to "5" because `define` does not evaluate its arguments. The 305 | second line evaluates to 5, the result of the expression, 2 + 3. 306 | 307 | ` 308 | 309 | 310 | #cquest 311 | What is the code in bold referred to as? 312 | 313 | ``` 314 | (define (square x) 315 | (* x x) 316 | (square (+ 2 3)) 317 | ``` 318 | 319 | the formal parameter, the argument that is passed to the function 320 | 321 | ` 322 | 323 | 324 | 325 | #cquest 326 | What is the code in bold referred to as? 327 | 328 | ``` 329 | (define (square x) 330 | (* x x) 331 | (square (+ 2 3)) 332 | ``` 333 | 334 | the body 335 | 336 | ` 337 | 338 | 339 | 340 | #cquest 341 | What is the code in bold referred to as? 342 | 343 | ``` 344 | (define (square x) 345 | (* x x) 346 | (square (+ 2 3)) 347 | ``` 348 | 349 | the argument expression 350 | 351 | ` 352 | 353 | 354 | #cquest 355 | What is the name for the result of the argument expression, `(+ 2 3) passed to 356 | the function called? 357 | 358 | ``` 359 | (define (square x) 360 | (* x x) 361 | (square (+ 2 3)) 362 | ``` 363 | 364 | the actual argument value 365 | 366 | ` 367 | 368 | 369 | #cquest 370 | What is a predicate function? 371 | 372 | ``` 373 | Example: `(if (equal? (last 'y) 'y)` 374 | ``` 375 | 376 | It is a function that returns true or false and is denoted with a "?" at the 377 | end. 378 | 379 | ` 380 | 381 | 382 | #csnip 383 | ``` 384 | (define (plural wd) 385 | (if (equal? (last wd) 'y) 386 | (word (bl wd) 'ies) 387 | (word wd 's))) 388 | (plural book) 389 | (plural fly) 390 | (plural boys) 391 | ``` 392 | 393 | Line 1 defines a function called `plural` that has one formal parameter called 394 | `wd`. 395 | ` 396 | Line 2 tests to see if `wd` ends in "y". If it does, line 3 will replace the 397 | "y" with "ies," otherwise, line 4 will add an "s" to the word. 398 | ` 399 | Lines 5-7 evaluate to the pluralized versions of the words. However, there is a 400 | bug and with line 7, resulting in "boies." As an exercise, the bug should be 401 | fixed. 402 | 403 | ` 404 | -------------------------------------------------------------------------------- /t/data/distzilla.anki: -------------------------------------------------------------------------------- 1 | #cloze 2 | The Dist::Zilla object represents your 3 | {{{distribution}}}. 4 | 5 | ` 6 | 7 | perl, dist-zilla 8 | ^ 9 | 10 | 11 | Dist::Zilla provides methods that implement dzil 12 | {{{commands}}}. 13 | 14 | ` 15 | 16 | ` 17 | 18 | 19 | The `{{{build}}}` command tells Dist::Zilla to 20 | take all of its {{{configuration 21 | and input}}} and produce a tarball suitable for release. 22 | 23 | ` 24 | 25 | ` 26 | 27 | 28 | The Dist::Zilla build occurs in {{{phases}}}. 29 | 30 | ` 31 | 32 | ` 33 | 34 | 35 | Each of Dist::Zilla's phases is performed by 36 | {{{plugins}}} and perform the {{{role}}} 37 | associated with the phase. 38 | 39 | ` 40 | 41 | ` 42 | 43 | 44 | When one plugin can perform work in a given phase, 45 | the plugins are run in the order they were {{{listed 46 | in configuration}}}. 47 | 48 | ` 49 | 50 | ` 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /t/data/escape_angle_brackets.anki: -------------------------------------------------------------------------------- 1 | #basic 2 | This is some `code with ` here 3 | 4 | `code with one ` 5 | 6 | \`code with ` 7 | 8 | \`code with ` and more ` 9 | 10 | \`code with ` and more of ` blah 11 | -------------------------------------------------------------------------------- /t/data/perl_modules.anki: -------------------------------------------------------------------------------- 1 | #cloze 2 | Perl modules are a set of related functions in a 3 | library file. 4 | 5 | ` 6 | 7 | perl perl_modules 8 | ^ 9 | 10 | 11 | #basic_reverse 12 | Implements a default import method for 13 | modules 14 | 15 | Exporter module 16 | 17 | ` 18 | 19 | 20 | #codediff 21 | require Foo::Bar; 22 | 23 | require "Foo::Bar"; 24 | 25 | changes the '::' into '/' resulting in a search 26 | for 'Foo/Bar.pm' in @INC 27 | 28 | does not change '::' into '/' and so will not 29 | find the module in @INC 30 | 31 | ` 32 | 33 | 34 | #basic 35 | If you want to to do the following: 36 | ' 37 | `$require $class;` 38 | ` 39 | What must you do? 40 | 41 | Wrap the expression in an eval: 42 | ' 43 | `eval "require $class"` 44 | 45 | ` 46 | 47 | 48 | #cloze 49 | Files with a .{{{pmc}}} extension, if found, are 50 | loaded in place of any file ending in a {{{.pm}}} 51 | extension. 52 | 53 | ` 54 | 55 | ` 56 | 57 | 58 | #basic 59 | What are the differences between the `use` and 60 | `require` keywords? 61 | 62 | %`use` is evaluated at compile time whereas 63 | `require` is evaluated at run time,`use` will 64 | import the module as well,`require` can take an 65 | expression while `use` can only take a 66 | bareword,`use` accepts arguments that will be 67 | passed to `import` and `require` does not,`use` 68 | does not behave like a function whereas `require` 69 | does% 70 | 71 | ` 72 | 73 | 74 | 75 | #cloze 76 | The `{{{@INC}}}` array contains the list of places 77 | that the `{{{do 78 | EXPR}}}`, `{{{require}}}`, or `{{{use}}}` constructs look for their 79 | library files. 80 | 81 | ` 82 | 83 | perl_modules perlvar 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /t/data/source.anki: -------------------------------------------------------------------------------- 1 | #basic 2 | How do you do? 3 | %one,two,three,four% 4 | 5 | I `am` *fine* 6 | ``` 7 | This is `some` code 8 | blah blah blah 9 | ``` 10 | 11 | 12 | Are you steve? 13 | 14 | yes, I am 15 | ` 16 | do you like me? 17 | 18 | 19 | Question with blank field 20 | 21 | ` 22 | 23 | 24 | #basic2 25 | Question 1 26 | 27 | ` 28 | 29 | field 3 30 | -------------------------------------------------------------------------------- /t/data/source2.anki: -------------------------------------------------------------------------------- 1 | #basic 2 | How do you do? 3 | 4 | I am fine 5 | 6 | 7 | Are you steve? 8 | 9 | yes, I am 10 | do you like me? 11 | 12 | kjsf 13 | -------------------------------------------------------------------------------- /t/data/tag_test.anki: -------------------------------------------------------------------------------- 1 | question 1 2 | 3 | answer 1 4 | 5 | tag1 tag2 tag3 6 | 7 | 8 | question 2 9 | 10 | answer 2 11 | 12 | tag3 tag4 tag5 13 | 14 | 15 | question 3 16 | 17 | answer 3 18 | 19 | autotag1 autotag2 autotag3 20 | ^ 21 | 22 | 23 | question 4 24 | 25 | answer 4 26 | 27 | ` 28 | 29 | 30 | question 5 31 | 32 | answer 5 33 | 34 | autotag3 35 | 36 | 37 | question 6 38 | 39 | answer 6 40 | 41 | autotag4 autotag5 autotag6 42 | ^ 43 | 44 | 45 | question 7 46 | 47 | {{{answer}}} {{{7}}} 48 | 49 | ` 50 | -------------------------------------------------------------------------------- /weaver.ini: -------------------------------------------------------------------------------- 1 | [@CorePrep] 2 | 3 | [Name] 4 | [Version] 5 | 6 | [Generic / OVERVIEW] 7 | [Generic / SYNOPSIS] 8 | [Generic / DESCRIPTION] 9 | 10 | [Collect / USAGE] 11 | command = method 12 | 13 | [Collect / FUNCTIONS] 14 | command = func 15 | 16 | [Collect / ATTRIBUTES] 17 | command = attr 18 | 19 | [Generic / INSTALLATION] 20 | [Generic / CONFIGURATION AND ENVIRONMENT] 21 | [Requires] 22 | [Support] 23 | websites = metacpan 24 | bugs = none 25 | [BugsAndLimitations] 26 | ;[Installation] 27 | [Generic / DEVELOPER NOTES] 28 | [Generic / SEE ALSO ] 29 | 30 | [Authors] 31 | [Legal] 32 | 33 | [-Transformer] 34 | transformer = List 35 | --------------------------------------------------------------------------------