From ae02fa77b803c666340ed69a605ce684dca7d76c Mon Sep 17 00:00:00 2001 From: Ben Greenman Date: Wed, 4 Jan 2017 03:55:43 -0500 Subject: [PATCH] [doc] add Scribble documentation --- ipoe/docs/ipoe-api.scrbl | 263 +++++++++++++++++++++++++++ ipoe/docs/ipoe-define.scrbl | 185 +++++++++++++++++++ ipoe/docs/ipoe-externals.scrbl | 15 ++ ipoe/docs/ipoe-use.scrbl | 236 ++++++++++++++++++++++++ ipoe/docs/ipoe.scrbl | 32 ++++ ipoe/info.rkt | 15 +- ipoe/poetic-form.rkt | 6 +- ipoe/private/scrape/README.md | 4 + ipoe/private/scrape/scrape-util.rkt | 4 +- ipoe/private/scrape/scrape-words.rkt | 11 +- ipoe/prompt.rkt | 9 - ipoe/scrape.rkt | 16 +- 12 files changed, 774 insertions(+), 22 deletions(-) create mode 100644 ipoe/docs/ipoe-api.scrbl create mode 100644 ipoe/docs/ipoe-define.scrbl create mode 100644 ipoe/docs/ipoe-externals.scrbl create mode 100644 ipoe/docs/ipoe-use.scrbl create mode 100644 ipoe/docs/ipoe.scrbl delete mode 100644 ipoe/prompt.rkt diff --git a/ipoe/docs/ipoe-api.scrbl b/ipoe/docs/ipoe-api.scrbl new file mode 100644 index 0000000..64bb887 --- /dev/null +++ b/ipoe/docs/ipoe-api.scrbl @@ -0,0 +1,263 @@ +#lang scribble/manual +@require[ + scribble/example + (for-label racket/base racket/contract racket/string ipoe/poetic-form sxml net/url)] + +@title[#:tag "ipoe-api"]{iPoe API} + +The following modules have been useful internally to @racketmodname[ipoe] and are stable enough to be used in (or ported to) other projects. +Hopefully, these will eventually re-appear in a more general context (@seclink["top" #:doc '(lib "scribblings/scribble/scribble.scrbl")]{scribble}, @racketmodname[sxml], @url{pkgs.racket-lang.org}, @|etc|). + + +@section{English-Language Tools} +@(define english-eval (make-base-eval '(require ipoe/english))) + +@defmodule[ipoe/english]{ + The @racketmodname[ipoe/english] module provides simple tools for working with the English language. +} + +@defproc[(infer-rhyme=? [str1 string?] [str2 string?]) boolean?]{ + Guess whether the given words rhyme with one another. + + @examples[#:eval english-eval + (infer-rhyme=? "cat" "hat") + (infer-rhyme=? "sauce" "boss") + (infer-rhyme=? "books" "carrots")] +} + +@defproc[(infer-syllables [str string?]) exact-nonnegative-integer?]{ + Guess the number of syllables in the given word. + + Technically, the number of syllables in some words depends on their pronunciation (does ``flower'' have 1 or 2 syllables?), but this function just returns a natural number without thinking very hard. + + @examples[#:eval english-eval + (infer-syllables "month") + (infer-syllables "hour") + (infer-syllables "munificent")] +} + +@defproc[(integer->word* [i exact-integer?]) (listof string?)]{ + Converts an integer to a list of English words. + Raises an exception (specifically, an @racket[exn:fail:contract?]) if @racket[(abs i)] is greater than an arbitrary (but pretty big) limit. + + @examples[#:eval english-eval + (integer->word* 2) + (integer->word* -21)] +} + +@defproc[(integer->word/space [i exact-integer?]) string?]{ + Returns the same result as: + @racketblock[ + (string-join (integer->word* i) " ") + ] +} + +@defproc[(suggest-spelling [str string?] + [#:max-distance d exact-nonnegative-integer? 2] + [#:limit n exact-nonnegative-integer? 10]) (listof string?)]{ + Return a list of common English words with Levenshtein distance (see the @racketmodname[levenshtein] package) at most @racket[d] from the given string. + The result list has no more than @racket[n] elements. + + @examples[#:eval english-eval + (suggest-spelling "tpyo") + (suggest-spelling "balon" #:limit 3) + ] +} + + +@section[#:tag "ipoe-api:form"]{Poetic Form API} + +@defmodule[ipoe/poetic-form]{ + The @racketmodname[ipoe/poetic-form] module provides functions over the internal representation of an @tech{iPoe poem}. +} + +@defproc[(poem? [x any/c]) boolean?]{ + A predicate for @tech{iPoe poems}. +} + +@defproc[(stanza? [x any/c]) boolean?]{ + A predicate for @tech{iPoe stanzas}. +} + +@defproc[(line? [x any/c]) boolean?]{ + A predicate for @tech{iPoe lines}. +} + +@defproc[(word? [x any/c]) boolean?]{ + A predicate for @tech{iPoe words}. +} + +@defproc[(stanza [i integer?] [p poem? (*current-poem*)]) stanza?]{ + Returns the stanza in poem @racket[p] at position @racket[i], where the first stanza is at position @racket[0]. +} + +@defproc[(line [i exact-nonnegative-integer?] [st stanza?]) line?]{ + Returns the line in stanza @racket[st] at position @racket[i]. +} + +@defproc[(word [i exact-nonnegative-integer?] [ln line?]) word?]{ + Returns the word in line @racket[ln] at position @racket[i]. +} + +@defproc[(line=? [ln1 line?] [ln2 line?]) boolean?]{ + Returns @racket[#true] if the given lines contain the same words. +} + +@defproc[(word=? [w1 word?] [w2 word?]) boolean?]{ + Return @racket[#true] if the given words are equal. +} + +@defproc[(line->word* [ln line?]) (sequence/c word?)]{ + Splits a line into words. +} + +@defproc[(poem->stanza* [p poem?]) (sequence/c stanza?)]{ + Splits a poem into stanzas. +} + +@defproc[(poem->word* [p poem?]) (sequence/c word?)]{ + Return a sequence of all words in the given poem. +} + +@defproc[(stanza->line* [st stanza?]) (sequence/c line?)]{ + Return a sequence of lines in the given stanza. +} + +@defproc[(poem-count-stanza* [p poem?]) exact-nonnegative-integer?]{ + Count the number of stanzas in the given poem. +} + +@defproc[(stanza-count-line* [st stanza?]) exact-nonnegative-integer?]{ + Count the number of lines in the given stanza. +} + +@defproc[(last-word [ln line?]) word?]{ + Returns the last word on the given line. +} + +@defproc[(last-stanza [poem poem? *current-poem*]) stanza?]{ + Returns the last stanza of the given poem. +} + +@defproc[(contains-word? [ln line?] [w word?]) boolean?]{ + Returns @racket[#true] if the given line contains the given word. +} + +@defparam[*current-poem* p (or/c poem? #f) #:value #f]{ + Poetic forms defined using @hash-lang[] @racketmodname[ipoe] can assume this parameter holds the poem that is currently being checked. +} + + +@section[#:tag "ipoe-api:scrape"]{Web Scraping} +@(define scrape-eval (make-base-eval '(require ipoe/scrape racket/string sxml))) + +@defmodule[ipoe/scrape]{ + The @racketmodname[ipoe/scrape] module provides tools for scraping word and rhyme data from the internet. +} + +@defproc[(scrape-word [str string?]) (or/c word-result? #f)]{ + Search the internet for information about the given word. +} + +@defproc[(word-result? [x any/c]) boolean?]{ + Predicate for the results of a successful word scrape. +} + +@defproc[(word-scraper? [x any/c]) boolean?]{ + Predicate for a @tech{word scraper}. +} + +@defproc[(make-word-scraper [sym symbol?] + [#:word->url word->url (-> string? string?)] + [#:sxml->definition sxml->defn (-> sxml (or/c string? #f))] + [#:sxml->num-syllables sxml->syll (-> sxml (or/c exact-nonnegative-integer? #f))] + [#:sxml->word sxml->word (-> sxml (or/c string? #f))]) + (-> string? (or/c word-result? #f))]{ + Builds a new @deftech{word scraper}, that is, an opaque structure that scrapes a fixed web resource for information on words. + @itemlist[ + @item{ + @racket[sym] briefly describes the resource being scraped. + } + @item{ + @racket[word->url] converts a word to a URL for a web page that may have information about the word. + } + @item{ + @racket[sxml->definition] parses an @racket[sxml] page for the definition of a word. + } + @item{ + @racket[sxml->num-syllables] parses an @racket[sxml] page for the number of syllables in a word. + } + @item{ + @racket[sxml->word] parses an @racket[sxml] page for the word the page describes. + } + ] + If any of the above procedures fail, they should return @racket[#false]. + When any one procedure returns @racket[#false] during a call @racket[(scraper w)], then the overall result is @racket[#false]. +} + +@deftogether[( + @defthing[dictionary.com word-scraper?]{} + @defthing[american-heritage word-scraper?]{} + @defthing[merriam-webster word-scraper?]{} + @defthing[the-free-dictionary word-scraper?]{} +)]{ + Built-in @tech{word scrapers}. +} + +@defproc[(scrape-rhyme [str string?]) rhyme-result?]{ + Search the internet for words that rhyme and @tech{almost rhyme} with the given word. +} + +@defproc[(rhyme-result? [x any/c]) boolean?]{ + Prefab structure representing the results of scraping for rhymes. +} + +@defproc[(almost-rhymes? [rr rhyme-result?] [str string?]) boolean?]{ + Return @racket[#true] if @racket[str] is listed in @racket[rr] as an ``almost rhyme''. +} + +@defproc[(rhymes? [rr rhyme-result?] [str string?]) boolean?]{ + Return @racket[#true] if @racket[str] is listed in @racket[rr] as a rhyming word. +} + +@defproc[(url->sxml [url (or/c url? string?)]) sxml:top?]{ + Send a GET request to the given @racket[url] and parse the result to SXML. + Return the parsed response. +} + +@defproc[(sxml-200? [sx sxml:top?]) boolean?]{ + Returns @racket[#true] if the given @racket[sxml] object represents a response to a successful HTML request. + Use this on the result of @racket[url->sxml] to see if the request was successful. +} + +@defproc[(id? [id-str string?]) (-> (listof sxml) any/c (listof sxml))]{ + Returns a procedure that filters @racket[sxml] elements, keeping only those elements with an @litchar{id} attribute with value @racket[id-str]. + + I do not know the purpose of the second argument to the procedure returned by @racket[id?]. + + @examples[#:eval scrape-eval + ((sxpath `(// div ,(id? "foo") *text*)) + '(div + (div (#,(string->symbol @"@") id "foo") "foo-text") + (div (#,(string->symbol @"@") id "bar") "bar-text")))] +} + +@defproc[(class? [class-str string?] [ string? string? boolean?) string=?]) (-> (listof sxml) any/c (listof sxml))]{ + Similar to @racket[id?], but filters based on the @litchar{class} attribute. + Override the @racket[symbol @"@") class "abc") "success") + (div (#,(string->symbol @"@") class "def") "failure")))] +} + +@defproc[(contains-text? [pattern regexp?]) (-> (listof sxml) any/c (listof sxml))]{ + Similar to @racket[id?], but filters @racket[sxml] elements to keep only those whose @litchar{*text*} contents match the given @racket[pattern] (in the sense of @racket[regexp-match?]). +} + +@defthing[scrape-logger logger?]{ + A logger that receives iPoe scraping events, such as rejected GET requests. +} + diff --git a/ipoe/docs/ipoe-define.scrbl b/ipoe/docs/ipoe-define.scrbl new file mode 100644 index 0000000..e38d64f --- /dev/null +++ b/ipoe/docs/ipoe-define.scrbl @@ -0,0 +1,185 @@ +#lang scribble/manual +@require[ + scribble/example + scribble/bnf + (for-label racket/base racket/contract (only-in syntax/parse expr)) +] + +@title[#:tag "ipoe-define"]{Defining a new iPoe Language} + +@defmodulelang[ipoe]{ + The @racketmodname[ipoe] language is a specification language for @tech{poetic forms}. +} + +The syntax accepted by the @hash-lang[] @racketmodname[ipoe] reader is keyword-based: +@(let ([lp @litchar{(}] [rp @litchar{)}]) +@BNF[ + (list @nonterm{stmt} + @BNF-seq[@racket[#:name] @racket[identifier]] + @BNF-seq[@racket[#:description] @racket[string?]] + @BNF-seq[@racket[#:syllables] @racket[exact-positive-integer?]] + @BNF-seq[@racket[#:rhyme-scheme] @nonterm{rhyme-scheme}] + @BNF-seq[@racket[#:constraint] @nonterm{constraint-expr}]) + (list @nonterm{rhyme-scheme} @BNF-seq[lp @kleenestar[@nonterm{stanza-scheme}] rp]) + (list @nonterm{stanza-scheme} @BNF-seq[lp @kleenestar[@nonterm{line-scheme}] rp]) + (list @nonterm{line-scheme} @BNF-seq[lp @nonterm{rhyme} @litchar{.} @nonterm{syllable} rp] + @nonterm{syllable} + @nonterm{rhyme}) + (list @nonterm{rhyme} @racket[symbol?] @nonterm{wildcard}) + (list @nonterm{syllable} @racket[exact-positive-integer?] @nonterm{wildcard}) + (list @nonterm{wildcard} @racket['*]) + (list @nonterm{constraint-expr} @racket[expr])]) + +In summary, a @hash-lang[] @racketmodname[ipoe] program is a sequence of statements. +@itemlist[ +@item{ + The @racket[#:name] statement @emph{must} appear once. + This statement declares the name of the poetic form (currently only used for debugging). +} +@item{ + The @racket[#:description] statement may appear at most once; it gives a brief description of the poetic form (for debugging). +} +@item{ + The @racket[#:syllables] statement may appear at most once. + When @racket[#:syllables N] is given, any wildcard syllables in the @nonterm{rhyme-scheme} (implicit or explicit) are replaced with @racket[N]. +} +@item{ + The @racket[#:rhyme-scheme] statement may appear at most once. + A @nonterm{rhyme-scheme} specifies: + @itemlist[ + @item{ + the number of stanzas in the poetic form, + } + @item{ + the number of lines in each stanza, + } + @item{ + the number of syllables in each line, and + } + @item{ + which lines must rhyme with one another. + } + ] + An empty rhyme scheme allows any number of stanzas. + A @nonterm{line-scheme} that omits the @nonterm{rhyme} or @nonterm{syllable} component has an implicit wildcard in that component. + A @nonterm{rhyme} can be any symbol; lines that must rhyme must have equal symbols (in the sense of @racket[eq?]). +} +@item{ + The @racket[#:constraint] statement may appear any number of times. + Each statement may contain arbitrary expressions using (most!) identifiers from the @racketmodname[racket/base] and @racketmodname[ipoe/poetic-form] namespaces. +} +] + +@bold{NOTE:} The syntax of @nonterm{constraint-expr} nonterminals is bad IMO. +Ideally it should be a simple, Racket-like domain-specific-language. +Allowing anything in @racketmodname[racket/base] is probably too much power, but @racket[ipoe/poetic-form] by itself isn't enough to express the constraints in certain poems (@racketmodname[ipoe/sestina]). + +@section[#:tag "ipoe-define:install"]{Installing a Language} + +@subsection{The Fast Way} +Suppose you have a @hash-lang[] @racketmodname[ipoe] program with name @litchar{my-form}. +The easiest way to start writing @litchar{my-form} poems is: +@itemlist[#:style 'ordered +@item{ + Install the @hyperlink["https://pkgd.racket-lang.org/pkgn/package/gnal-lang"]{gnal-lang} package. +} +@item{ + Create a directory @litchar{my-form/} and a sub-directory @litchar{my-form/lang/}. +} +@item{ + Save your program as @litchar{my-form/lang/reader.rkt}. +} +] + +Now you can write programs using the new poetic form: + +@verbatim[#:indent 4]|{ +#lang gnal "my-form" + +Racket is fun because #:fun is a keyword! +}| + +Compiling the program will check the text against the @litchar{my-form} specification. + + +@subsection{The Franchised Way} +A longer but prettier method is: +@itemlist[#:style 'ordered +@item{ + Create the @litchar{my-form/lang/} directory and save the @racketmodname[ipoe] program as @litchar{my-form/lang/reader.rkt}. +} +@item{ + Run @exec{raco pkg install ./my-form}. +} +@item{ + Write programs that start with @tt{#lang my-form}. +} +] + +@subsection{The Directory-Free Way} + +@bold{TODO} should be possible to defined a @racket[reader] submodule, but this doesn't work right now! + + + +@section[#:tag "ipoe-define:examples"]{Example Languages} + +Browse the source code for more examples. + +@hash-lang[] @racket[ipoe/haiku] +@codeblock|{ +#lang ipoe + +#:name haiku +#:rhyme-scheme {[5 7 5]} +#:description +"1 stanza, 3 lines +No rhyme constraints +5 syllables in first line +7 in second +5 in last" +}| + +@hash-lang[] @racket[ipoe/couplet] +@codeblock|{ +#lang ipoe + +#:name couplet +#:rhyme-scheme {[A A]} +}| + +@hash-lang[] @racket[ipoe/english-sonnet] +@codeblock|{ +#lang ipoe + +#:name english-sonnet +#:syllables 10 +#:rhyme-scheme {[A B A B] + [C D C D] + [E F E F] + [G G]} +}| + +@hash-lang[] @racket[ipoe/villanelle] +@codeblock|{ +#lang ipoe + +#:name villanelle +#:rhyme-scheme {[R1 B R2] + [A B R1] + [A B R2] + [A B R1] + [A B R2] + [A B R1 R2]} +#:syllables 10 +#:constraint + (line=? (line 0 (stanza 0)) + (line 2 (stanza 1)) + (line 2 (stanza 3)) + (line 2 (stanza 5))) +#:constraint + (line=? (line 2 (stanza 0)) + (line 2 (stanza 2)) + (line 2 (stanza 4)) + (line 3 (stanza 5))) +}| diff --git a/ipoe/docs/ipoe-externals.scrbl b/ipoe/docs/ipoe-externals.scrbl new file mode 100644 index 0000000..cf1a076 --- /dev/null +++ b/ipoe/docs/ipoe-externals.scrbl @@ -0,0 +1,15 @@ +#lang scribble/manual + +@title[#:tag "ipoe-externals"]{External Links} + +@itemlist[ +@item{ + Code: @url{https://github.com/bennn/ipoe} +} +@item{ + @hyperlink["http://con.racket-lang.org/2015/"]{RacketCon 2015} + slides @hyperlink["http://con.racket-lang.org/2015/greenman.pdf"]{(pdf)}, + writeup @hyperlink["http://con.racket-lang.org/2015/greenman-writeup.pdf"]{(pdf)}, and + video @hyperlink["https://www.youtube.com/watch?v=p3duA-dmv7k&list=PLXr4KViVC0qJAsNuDeQzhFDjMK1gEdls8&index=13"]{(YouTube)}. +} +] diff --git a/ipoe/docs/ipoe-use.scrbl b/ipoe/docs/ipoe-use.scrbl new file mode 100644 index 0000000..00d38dd --- /dev/null +++ b/ipoe/docs/ipoe-use.scrbl @@ -0,0 +1,236 @@ +#lang scribble/manual +@require[scribble/example (for-label racket/base racket/contract/base)] + +@title[#:tag "ipoe-use"]{Using an iPoe Language} + +@section[#:tag "ipoe-tour"]{A Guided Tour} + +Let's write a haiku. +According to @hyperlink["https://en.wikipedia.org/wiki/Haiku_in_English"]{Wikipedia}: + +@centered{@emph{ + A typical haiku is a three-line observation about a fleeting moment involving nature. + .... with 17 syllables arranged in a 5--7--5 pattern. +}} + +Here's a first shot: +@verbatim[#:indent 4]{ +#lang ipoe/haiku + +pit pat pit pat, rain? +or clicking on a keyboard? +it's 3 am I don't care anymore +} + +Save the file as @litchar{./first.haiku} and run @exec{raco make first.haiku} to compile the poem. +Compiling will check the text of the poem against the @racketmodname[ipoe/haiku] @tech{poetic form}. + +If this is your first time using @racketmodname[ipoe], you will see the following message: +@verbatim[#:indent 4]{ +Missing run-time parameter for database username. Please enter a username to connect to the ipoe database. +Enter your database username (or #f to skip): +ipoe> +} + +Just type @litchar{#f} and skip for now. +(If you're feeling brave, try running @exec{raco ipoe init} or browsing @secref{ipoe-use:pragmatics}.) +The next thing you will see is: + +@verbatim[#:indent 4]{ +Starting ipoe without a database connection (in online-only mode) +Word 1 on line 1 of stanza 0 ('clicking') is undefined. Consider adding it to the dictionary. +Word 4 on line 2 of stanza 0 ('dont') is undefined. Consider adding it to the dictionary. +ipoe: Expected 7 syllables on Line 1 of Stanza 0, got 5 syllables + compilation context...: + /Users/ben/code/racket/my-pkgs/ipoe/ipoe/first.rkt +} + +Okay! +This means @racketmodname[ipoe] checked our poem and found a problem: the second line has 5 syllables but was expected to have 7 syllables. +The trouble is that @racketmodname[ipoe] couldn't figure out how many syllables are in the word ``clicking'' (by asking the internet, see the @racketmodname[ipoe/scrape] module). + +We can't add ``clicking'' to the dictionary because we haven't set up a database. +But we can bypass the issue by claiming our @tech{poetic license}. +Change the poem to read: + +@verbatim[#:indent 4]{ +#lang ipoe/haiku +#:poetic-license 10 + +pit pat pit pat, rain? +or clicking on a keyboard? +it's 3 am I don't care no more +} + +Now compile the poem; you should see: + +@verbatim[#:indent 4]{ +Missing run-time parameter for database username. Please enter a username to connect to the ipoe database. +Enter your database username (or #f to skip): +ipoe> #f +Starting ipoe without a database connection (in online-only mode) +Word 1 on line 1 of stanza 0 ('clicking') is undefined. Consider adding it to the dictionary. +Word 4 on line 2 of stanza 0 ('dont') is undefined. Consider adding it to the dictionary. +Finished checking poem. +- Misspelled word 'clicking'; position 1 on line 1 of stanza 0. +- Misspelled word 'dont'; position 4 on line 2 of stanza 0. Maybe you meant 'on'? +- Expected 7 syllables on Line 1 of Stanza 0, got 5 syllables +- Expected 5 syllables on Line 2 of Stanza 0, got 7 syllables +Remaining poetic license: 6 +} + +Success! +And now you've seen the basics of @racketmodname[ipoe]: +@itemlist[ +@item{ + The @hash-lang[] line declares a @tech{poetic form}. + Poetic forms can specify the number of lines, number of stanzas, rhyme scheme, and number of syllables in the following text. + See @secref{ipoe-define} for more details. +} +@item{ + @racketmodname[ipoe] checks the internet to get the spelling, number of syllables, and rhymes for a given word. + If you have an @tech{ipoe database}, it stores this information. + Otherwise, the data for words is cached locally (in @litchar{./compiled/ipoe.cache}). +} +@item{ + Poems can start with a few keyword/value pairs to alter the poem-checker. +} +] + + +@section[#:tag "ipoe-tech"]{iPoe Concepts} + +An @deftech{iPoe poem} is a text file with @litchar{#lang } as its first line, where @litchar{} refers to an iPoe @tech{poetic form}. + +A @deftech{poetic form} is a grammar; it specifies how a class of poems should look and sound. + +A @deftech{rhyme scheme} is an important part of a poetic form; it declares the number of stanzas, number of lines in each stanza, number of syllables in each line, and constrains certain lines to rhyme with one another. + +A @deftech{iPoe stanza} is a group of lines. +Different stanzas are separated by at least 2 consecutive newline characters. + +A @deftech{iPoe line} is a sequence of words. +Different lines are separated by a single newline character. + +A @deftech{iPoe word} is basically a sequence of non-whitespace characters. +Different words are separated by at least one whitespace character. +(This definition is intentionally vague.) + +@deftech{syllables} are ``units of pronunciation'' in a word (thanks Google). + +Two words @deftech{rhyme} if they end with the same sound. + +Two words @deftech{almost rhyme} if they kind-of-but-not-really end with the same sound. + +The rules of a @tech{poetic form} are more like guidelines. +Good poetry doesn't always follow the rules, and poets have a @deftech{poetic license} to break the rules. +@margin-note{ +How much license? +I don't know. +How much does it cost to break a rule? +I don't know, but I tried to set good defaults. +Have fun with this until we have something better. +} + +An @deftech{iPoe database} stores known words, their properties, and the relation between them. +Once you have an iPoe database, it will update itself. +But it tends to ask for too much help, you can disable the help by putting @racket[#:interactive? #f] at the top of your poems or configuration file. + + +@subsection[#:tag "ipoe-forms"]{Built-in Poetic Forms} + +@defmodulelang[ipoe/cinquain] +@defmodulelang[ipoe/clerihew] +@defmodulelang[ipoe/couplet] +@defmodulelang[ipoe/english-sonnet] +@defmodulelang[ipoe/free-verse] +@defmodulelang[ipoe/haiku] +@defmodulelang[ipoe/italian-sonnet] +@defmodulelang[ipoe/limerick] +@defmodulelang[ipoe/quaternion] +@defmodulelang[ipoe/rondelet] +@defmodulelang[ipoe/sestina] +@defmodulelang[ipoe/sextilla] +@defmodulelang[ipoe/tanaga] +@defmodulelang[ipoe/tercet] +@defmodulelang[ipoe/villanelle] + +@section[#:tag "ipoe-use:pragmatics"]{Pragmatics} + +@subsection[#:tag "ipoe-db"]{Connecting to an @tt{ipoe} Database} + +If you install PostgreSQL and start a server, running @exec{raco ipoe init} should start an @tech{iPoe database}. + +@subsection{Configuration} + +Global configuration file: @racket[(build-path (find-system-path 'home-dir) ".ipoe")] + +Local configuration file: @racket{./.ipoe} + +There are many configuration options. +Put these in a configuration file or at the top of a poem. +Global configurations have the least precedence, in-file configurations have the most. + +@itemlist[ +@item{ + @racket[#:user string?] username for the @tech{iPoe database} +} +@item{ + @racket[#:dbname string?] database name for the @tech{iPoe database} +} +@item{ + @racket[#:interactive? boolean?] when @racket[#true], ask permission before peforming most actions. + Otherwise, never ask for user input. +} +@item{ + @racket[#:online? boolean?] when @racket[#true], use the internet to look up information about words. + Otherwise, never use the internet. +} +@item{ + @racket[#:spellcheck? boolean?] when @racket[#true], warn the user about spelling errors. +} +@item{ + @racket[#:grammarcheck? boolean?] when @racket[#true], warn the user about grammar errors (currently does nothing). +} +@item{ + @racket[#:suggest-rhyme? boolean?] when @racket[#true], offer suggestions to replace a word that doesn't rhyme. +} +@item{ + @racket[#:suggest-spelling? boolean?] when @racket[#true], offer suggestions to replace a mis-spelled word. +} +@item{ + @racket[#:poetic-license exact-nonnegative-integer?] set value of initial poetic license +} +@item{ + @racket[#:almost-rhyme-penalty (or/c exact-nonnegative-integer? #f)] set penalty for using an almost-rhyme. + By default, @racket[1]. +} +@item{ + @racket[#:bad-extra-penalty (or/c exact-nonnegative-integer? #f)] set penalty for failing a @racket[#:constraint]. + By default, @racket[10]. +} +@item{ + @racket[#:bad-lines-penalty (or/c exact-nonnegative-integer? #f)] set penalty for using the wrong number of lines. + By default, @racket[#false]; this immediately rejects poems with the wrong number of lines. +} +@item{ + @racket[#:bad-rhyme-penalty (or/c exact-nonnegative-integer? #f)] set the penalty for not rhyming. + By default, @racket[3]. +} +@item{ + @racket[#:bad-stanza-penalty (or/c exact-nonnegative-integer? #f)] set the penalty for using the wrong number of stanzas. + By default, @racket[#false]. +} +@item{ + @racket[#:bad-syllable-penalty (or/c exact-nonnegative-integer? #f)] set the penalty for using the wrong number of syllables. + By default, @racket[1]. +} +@item{ + @racket[#:repeat-rhyme-penalty (or/c exact-nonnegative-integer? #f)] set the penalty for using rhyming a word with itself. + By default, @racket[3]. +} +@item{ + @racket[#:spelling-error-penalty (or/c exact-nonnegative-integer? #f)] set the penalty for misspelling a word. + By default, @racket[0]. +} +] diff --git a/ipoe/docs/ipoe.scrbl b/ipoe/docs/ipoe.scrbl new file mode 100644 index 0000000..14f2913 --- /dev/null +++ b/ipoe/docs/ipoe.scrbl @@ -0,0 +1,32 @@ +#lang scribble/manual + +@title[#:tag "top"]{iPoe: interactive poetry editor} +@author[@hyperlink["https://github.com/bennn"]{Ben Greenman}] + +@defmodule[ipoe]{ + The @racketmodname[ipoe] package is an extensible compiler for poetry. +} + +You can use @racket[ipoe] to: +@itemlist[ +@item{ + Check a poem against one of the built-in @tech{poetic forms} (@secref{ipoe-use}). +} +@item{ + Define a new poetic form (@secref{ipoe-define}). +} +@item{ + Add basic English-language processing to a Racket program (@secref{ipoe-api}). +} +] + +Each poetic form is implemented as a Racket @hash-lang[]. +The syntax of these languages is plain text; their semantics is to check the text for spelling, grammar, and other errors. + +@bold{NOTE:} this package is unstable. +The purpose of this documentation is just to describe how @racketmodname[ipoe] fits together as of January 2017. + +@include-section{ipoe-use.scrbl} +@include-section{ipoe-define.scrbl} +@include-section{ipoe-api.scrbl} +@include-section{ipoe-externals.scrbl} diff --git a/ipoe/info.rkt b/ipoe/info.rkt index 151999f..2b5482a 100644 --- a/ipoe/info.rkt +++ b/ipoe/info.rkt @@ -1,6 +1,6 @@ #lang info -(define collection 'use-pkg-name) +(define collection "ipoe") (define deps '("base" "db-lib" @@ -9,13 +9,18 @@ "html-lib" "rackunit-lib" "readline-lib" + "reprovide-lang" "sxml" )) (define build-deps '("scribble-lib" + "net-doc" + "scribble-doc" + "rackunit-lib" + "rackunit-abbrevs" "racket-doc")) -(define pkg-desc "Family of languages for editing poetry") +(define pkg-desc "Interactive Poetry Editor") (define pkg-authors '(ben)) -(define raco-commands '(("ipoe" (submod ipoe/main main) "console UI" #f))) -;(define scribblings '(("scribblings/ipoe.scrbl"))) -(define version "0.1") +(define raco-commands '(("ipoe" (submod ipoe/main main) "interactive poetry editor" #f))) +(define scribblings '(("docs/ipoe.scrbl" () (experimental)))) +(define version "0.2") diff --git a/ipoe/poetic-form.rkt b/ipoe/poetic-form.rkt index 20d153b..f4c671b 100644 --- a/ipoe/poetic-form.rkt +++ b/ipoe/poetic-form.rkt @@ -7,7 +7,7 @@ quirk?) (only-in ipoe/private/poem - *poem* + [*poem* *current-poem*] poem? [stanza/loc? stanza?] @@ -22,9 +22,9 @@ line->word* poem-count-stanza* poem->stanza* - poem->word/loc* + [poem->word/loc* poem->word*] stanza - stanza-count-lines + [stanza-count-lines stanza-count-line*] stanza->line* word word=?) diff --git a/ipoe/private/scrape/README.md b/ipoe/private/scrape/README.md index 736cb0c..ccdf45c 100644 --- a/ipoe/private/scrape/README.md +++ b/ipoe/private/scrape/README.md @@ -9,3 +9,7 @@ Scrapers for taking information off the internet. Helpers: - `scrape-util.rkt` General tools for scraping tasks. - `html.rkt` Neil VanDyke's html parser + + +TODO +- syllables at https://www.howmanysyllables.com/ diff --git a/ipoe/private/scrape/scrape-util.rkt b/ipoe/private/scrape/scrape-util.rkt index 007f400..15a1b1f 100644 --- a/ipoe/private/scrape/scrape-util.rkt +++ b/ipoe/private/scrape/scrape-util.rkt @@ -82,7 +82,7 @@ (define id-text ((if-car-sxpath '( @ id *text*)) e)) (and id-text (string=? str id-text))) (filter id=str? elem*)) -(require racket/string) + (define ((class? str [string-equal? string=?]) elem* ???) (define (class=str? e) (define class-text ((if-car-sxpath '(@ class *text*)) e)) @@ -93,7 +93,7 @@ (define ((contains-text? pat) elem* ???) (define (contains? e) (define txt ((if-car-sxpath '(*text*)) e)) - (and txt (regexp-match pat txt))) + (and txt (regexp-match? pat txt))) (filter contains? elem*)) ;; ============================================================================= diff --git a/ipoe/private/scrape/scrape-words.rkt b/ipoe/private/scrape/scrape-words.rkt index 8cd726e..d6cd592 100644 --- a/ipoe/private/scrape/scrape-words.rkt +++ b/ipoe/private/scrape/scrape-words.rkt @@ -10,6 +10,7 @@ (struct-out word-result) (struct-out word-scraper) + make-word-scraper dictionary.com american-heritage @@ -48,6 +49,13 @@ ;; ----------------------------------------------------------------------------- ;; Data Definition: Word Scraper +(define (make-word-scraper name + #:word->url word->url + #:sxml->definition sxml->definition + #:sxml->num-syllables sxml->num-syllables + #:sxml->word sxml->word) + (word-scraper word->url sxml->definition sxml->num-syllables sxml->word name)) + (struct word-scraper ( word->url ;; (-> String String) @@ -70,7 +78,8 @@ ;; Symbol ;; The name of this scraper -) #:property prop:procedure +) + #:property prop:procedure ;; Search the web for information on a word. ;; An instance of this struct defines the search protocol for 1 website ;; (-> Word-Scraper String Word-Result) diff --git a/ipoe/prompt.rkt b/ipoe/prompt.rkt deleted file mode 100644 index 1abe21c..0000000 --- a/ipoe/prompt.rkt +++ /dev/null @@ -1,9 +0,0 @@ -#lang reprovide - -;; Utilities for making interactive prompts - -(only-in ipoe/private/ui - read-natural - read-string - read-sql-id - read-yes-or-no) diff --git a/ipoe/scrape.rkt b/ipoe/scrape.rkt index db37953..90d74bb 100644 --- a/ipoe/scrape.rkt +++ b/ipoe/scrape.rkt @@ -1,6 +1,10 @@ #lang reprovide -ipoe/private/scrape/scrape-rhymes +(only-in ipoe/private/scrape/scrape-rhymes + rhyme-result? + almost-rhymes? + rhymes? + scrape-rhyme) (only-in ipoe/private/scrape/scrape-util url->sxml @@ -10,4 +14,12 @@ ipoe/private/scrape/scrape-rhymes contains-text? scrape-logger) -ipoe/private/scrape/scrape-words +(only-in ipoe/private/scrape/scrape-words + word-result? + word-scraper? + make-word-scraper + scrape-word + dictionary.com + american-heritage + merriam-webster + the-free-dictionary)