diff --git a/onegov/form/parser/grammar.py b/onegov/form/parser/grammar.py index 2d2f765..84f4682 100644 --- a/onegov/form/parser/grammar.py +++ b/onegov/form/parser/grammar.py @@ -1,4 +1,187 @@ # -*- coding: utf-8 -*- +""" onegov.form includes it's own markdownish form syntax, inspired by +https://github.com/maleldil/wmd + +The goal of this syntax is to enable the creation of forms through the web, +without having to use javascript, html or python code. + +Also, just like Markdown, we want this syntax to be readable by humans. + +Syntax +====== + +Fields +------ + +Every field is identified by a label, an optional 'required' indicator and a +field definition. The Label can be any kind of text, not including ``*`` and +``=``. +The ``*`` indicates that a field is required. The ``=`` separates the +identifier from the definition. + +A required field starts like this:: + + My required field * = + +An optional field starts like this:: + + My optional field = + +Following the identifier is the field definition. For example, this defines +a textfield:: + + My textfield = ___ + +All possible fields are documented further below. + +Fieldsets +--------- + +Fields are grouped into fieldsets. The fieldset of a field is the fieldset +that was last defined:: + + # Fieldset 1 + I belong to Fieldset 1 = ___ + + # Fieldset 2 + I belong to Fieldset 2 = ___ + +If no fieldset is defined, the fields don't belong to a fieldset. To stop +putting fields in a fieldset, define an empty fieldeset:: + + # Fieldset 1 + I belong to Fieldset 1 = ___ + + # ... + I don't belong to a Fieldset = ___ + +Buttons +------- + +Buttons come in two versions. One without address, and one with. If there's no +address, the button POSTs to the current url. If there is an url, the button +POSTs the form the the given url. + +For example:: + + [Submit to current URL] + [Submit to another URL](http://my-post-address.com) + +You'll notice that this looks just like a Markdown link. + +Available Fields +---------------- + +Textfield +~~~~~~~~~ + +A textfield consists of exactly three underscores:: + + I'm a textfield = ___ + +If the textfield is limited in length, the length can be given:: + + I'm a limited textfield = ___[50] + +The length of such textfields is validated. + +Textarea +~~~~~~~~ + +A textarea has no limit and consists of exactly three dots:: + + I'm a textarea = ... + +Optionally, the number of rows can be passed to the field. This changes the +way the textarea looks, not the way it acts:: + + I'm a textarea with 10 rows = ...[10] + +Password +~~~~~~~~ + +A password field consists of exactly three stars:: + + I'm a password = *** + +Radio Buttons +~~~~~~~~~~~~~ + +Radio button fields consist of x radio buttons, out of which one may be +preselected:: + + Gender = ( ) Female ( ) Male (x) I don't want to say + +To improve readability, radio buttons may be listed on multiple lines, as +long as they are properly indented:: + + Gender = ( ) Female + ( ) Male + (x) I don't want to say + +Radio buttons also have the ability to define optional form parts. Those +parts are only shown if a question was answered a certain way. + +Form parts are properly nested if they lign up with the label above them. + +For example:: + + Delivery Method = ( ) Pickup + Pickup Time = ___ + (x) Address + Street * = ___ + Town * = ___ + +Here, the street and the town only need to be provided, if the delivery method +is 'Address'. If the user selects a different option, the fields are not +shown and they will not be required. + +On the other hand, if 'Pickup' is selected, the 'Pickup Time' needs to be +filled out and the address options are hidden. + +This kind of nesting may continue ad infinitum. Meaning you can nest radio +buttons as deeply as you like. Note however, that this is discouraged and that +your users will not be too happy if you present them with a deeply nested +form. + +More than one level of nesting is a clear indicator that your form is too +complex. + +Checkboxes +~~~~~~~~~~ + +Checkboxes work exactly like radio buttons, just that you can select +multiple fields:: + + Extras = [x] Phone insurance + [ ] Phone case + [x] Extra battery + +Just like radiobuttons, checkboxes may be nested to created dependencies:: + + Additional toppings = [ ] Salami + [ ] Olives + [ ] Other + Description = ___ + +Custom Fields +~~~~~~~~~~~~~ + +Custom fields are fields for which there's no special syntax, but which have +a specialized function. + +For example:: + + E-Mail = /E-Mail + Stripe = /Stripe + Social Security Number = /Social-Security-Number + +The availability of these fields depends on how onegov.form is used. The idea +is for external packages to define their own custom fields. + +As of this writing there are no custom fields. + +""" from onegov.form.compat import unicode_characters from pyparsing import ( col, @@ -110,7 +293,8 @@ def mark_enclosed_in(characters): def textfield(): """ Returns a textfield parser. - Example: + Example:: + ____[50] The number defines the maximum length. @@ -127,7 +311,7 @@ def textfield(): def textarea(): """ Returns a textarea parser. - Example: + Example:: ...[5] @@ -146,7 +330,8 @@ def textarea(): def password(): """ Returns a password field parser. - Example: + Example:: + *** """ @@ -163,7 +348,8 @@ def init(self, string, line, tokens): def marker_box(characters, indent_stack=None): """ Returns a marker box: - Example: + Example:: + (x) Male [x] Female {x} What? @@ -184,7 +370,8 @@ def marker_box(characters, indent_stack=None): def radios(): """ Returns a radio buttons parser. - Example: + Example:: + ( ) Male (x) Female ( ) Space Alien """ @@ -194,7 +381,8 @@ def radios(): def checkboxes(): """ Returns a check boxes parser. - Example: + Example:: + [] Android [x] iPhone [x] Dumb Phone """ @@ -207,7 +395,8 @@ def checkboxes(): def custom(): """ Returns a custom field parser. - Examples: + Examples:: + /E-Mail /Stripe @@ -225,7 +414,8 @@ def custom(): def button(): """ Returns a buttons parser. - Examples: + Examples:: + [Send] [Send](http://my-post-address.com) @@ -249,7 +439,7 @@ def button(): def fieldset_title(): """ A fieldset title parser. Fieldset titles are just like headings in - markdown: + markdown:: # My header @@ -270,7 +460,8 @@ def fieldset_title(): def field_identifier(): """ Returns a field identifier parser: - Example: + Example:: + My field * """