X-Git-Url: http://git.vanrenterghem.biz/git.ikiwiki.info.git/blobdiff_plain/8ad716d92c615864a5293b030afc6535e9d92b6f..4e7b7a178890eb8d28edcd2e6ab2763c9a3988e5:/doc/todo/structured_page_data.mdwn diff --git a/doc/todo/structured_page_data.mdwn b/doc/todo/structured_page_data.mdwn index a0a3a9b84..9f21fab7f 100644 --- a/doc/todo/structured_page_data.mdwn +++ b/doc/todo/structured_page_data.mdwn @@ -1,5 +1,7 @@ This is an idea from [[JoshTriplett]]. --[[Joey]] +* See further discussion at [[forum/an_alternative_approach_to_structured_data]]. + Some uses of ikiwiki, such as for a bug-tracking system (BTS), move a bit away from the wiki end of the spectrum, and toward storing structured data about a page or instead of a page. @@ -82,6 +84,10 @@ See also: > rather than all pages linked from a given page. > >The first use case is handled by having a template in the page creation. You could + + + + >have some type of form to edit the data, but that's just sugar on top of the template. >If you were going to have a web form to edit the data, I can imagine a few ways to do it: > @@ -162,6 +168,96 @@ See also: >> Anyway, here are the plugins. As noted above these are only preliminary, exploratory, attempts. -- [[Will]] +>>>> I've just updated the second of the two patches below. The two patches are not mutually +>>>> exclusive, but I'm leaning towards the second as more useful (for the things I'm doing). -- [[Will]] + +I think it's awesome that you're writing this code to explore the problem +space, [[Will]] -- and these plugins are good stabs at at least part of it. +Let me respond to a few of your comments.. --[[Joey]] + +On use cases, one use case is a user posting a bug report with structured +data in it. A template is one way, but then the user has to deal with the +format used to store the structured data. This is where a edit-time form +becomes essential. + +> This was the idea with the 'form' plugin. With the 'data' plugin I was exploring +> a different approach: try to keep the markup simple enough that the user can edit +> the markup directly, and still have that be ok. I admit it is a stretch, but I thought +> it worth exploring. + +Another use case is, after many such bugs have been filed, +wanting to add a new field to each bug report. To avoid needing to edit +every bug report it would be good if the fields in a bug report were +defined somewhere else, so that just that one place can be edited to add +the new field, and it will show up in each bug report (and in each bug +report's edit page, as a new form field). + +> If I was going to do that, I'd use a perl script on a checked out +> workspace. I think you're describing a rare operation and +> so I'd be happy not having a web interface for it. Having said that, +> if you just wanted to change the form for *new* pages, then you +> can just edit the template used to create new pages. + +Re the form plugin, I'm uncomfortable with tying things into +[[!cpan CGI::FormBuilder]] quite so tightly as you have. + +> Yeah :). But I wanted to explore the space and that was the +> easiest way to start. + +CGI::FormBuilder +could easily change in a way that broke whole wikis full of pages. Also, +needing to sanitize FormBuilder fields with security implications is asking +for trouble, since new FormBuilder features could add new fields, or +add new features to existing fields (FormBuilder is very DWIM) that open +new security holes. + +> There is a list of allowed fields. I only interpret those. + +I think that having a type system, that allows defining specific types, +like "email address", by writing code (that in turn can use FormBuilder), +is a better approach, since it should avoid becoming a security problem. + +> That would be possible. I think an extension to the 'data' plugin might +> work here. + +One specific security hole, BTW, is that if you allow the `validate` field, +FormBuilder will happily treat it as a regexp, and we don't want to expose +arbitrary perl regexps, since they can at least DOS a system, and can +probably be used to run arbitrary perl code. + +> I validate the validate field :). It only allows validate fields that match +> `/^[\w\s]+$/`. This means you can really only use the pre-defined +> validation types in FormBuilder. + +The data plugin only deals with a fairly small corner of the problem space, +but I think does a nice job at what it does. And could probably be useful +in a large number of other cases. + +> I think the data plugin is more likely to be useful than the form plugin. +> I was thinking of extending the data directive by allowing an 'id' parameter. +> When you have an id parameter, then you can display a small form for that +> data element. The submission handler would look through the page source +> for the data directive with the right id parameter and edit it. This would +> make the data directive more like the current 'form' plugin. + +> That is making things significantly more complex for less significant gain though. --[[Will]] + +> Oh, one quick other note. The data plugin below was designed to handle multiple +> data elements in a single directive. e.g. + + \[[!data key="Depends on" link="bugs/bugA" link="bugs/bugB" value=6]] + +> would match `data_eq(Depends on,6)`, `data_link(Depends on,bugs/bugA)`, `data_link(Depends on,bugs/bugB)` +> or, if you applied the patch in [[todo/tracking_bugs_with_dependencies]] then you can use 'defined pagespecs' +> such as `data_link(Depends on,~openBugs)`. The ability to label links like this allows separation of +> dependencies between bugs from arbitrary links. +>> This issue (the need for distinguished kinds of links) has also been brought up in other discussions: [[tracking_bugs_with_dependencies#another_kind_of_links]] (deps vs. links) and [[tag_pagespec_function]] (tags vs. links). --Ivan Z. + +>>> And multiple link types are now supported; plugins can set the link +>>> type when registering a link, and pagespec functions can match on them. --[[Joey]] + +---- + #!/usr/bin/perl # Interpret YAML data to make a web form package IkiWiki::Plugin::form; @@ -171,21 +267,21 @@ See also: use CGI::FormBuilder; use IkiWiki 2.00; - sub import { #{{{ + sub import { hook(type => "getsetup", id => "form", call => \&getsetup); hook(type => "htmlize", id => "form", call => \&htmlize); hook(type => "sessioncgi", id => "form", call => \&cgi_submit); - } # }}} + } - sub getsetup () { #{{{ + sub getsetup () { return plugin => { safe => 1, rebuild => 1, # format plugin }, - } #}}} + } - sub makeFormFromYAML ($$$) { #{{{ + sub makeFormFromYAML ($$$) { my $page = shift; my $YAMLString = shift; my $q = shift; @@ -264,9 +360,9 @@ See also: # IkiWiki::decode_form_utf8($form); return $form; - } #}}} + } - sub htmlize (@) { #{{{ + sub htmlize (@) { my %params=@_; my $content = $params{content}; my $page = $params{page}; @@ -274,9 +370,9 @@ See also: my $form = makeFormFromYAML($page, $content, undef); return $form->render(submit => 'Update Form'); - } # }}} + } - sub cgi_submit ($$) { #{{{ + sub cgi_submit ($$) { my $q=shift; my $session=shift; @@ -339,11 +435,11 @@ See also: } exit; - } #}}} + } package IkiWiki::PageSpec; - sub match_form_eq ($$;@) { #{{{ + sub match_form_eq ($$;@) { my $page=shift; my $argSet=shift; my @args=split(/,/, $argSet); @@ -374,7 +470,7 @@ See also: } else { return IkiWiki::FailReason->new("field value does not match"); } - } #}}} + } 1 @@ -388,21 +484,24 @@ See also: use strict; use IkiWiki 2.00; - sub import { #{{{ + my $inTable = 0; + + sub import { hook(type => "getsetup", id => "data", call => \&getsetup); hook(type => "needsbuild", id => "data", call => \&needsbuild); hook(type => "preprocess", id => "data", call => \&preprocess, scan => 1); - } # }}} + hook(type => "preprocess", id => "datatable", call => \&preprocess_table, scan => 1); # does this need scan? + } - sub getsetup () { #{{{ + sub getsetup () { return plugin => { safe => 1, rebuild => 1, # format plugin }, - } #}}} + } - sub needsbuild (@) { #{{{ + sub needsbuild (@) { my $needsbuild=shift; foreach my $page (keys %pagestate) { if (exists $pagestate{$page}{data}) { @@ -417,27 +516,79 @@ See also: } } - sub preprocess (@) { #{{{ + sub preprocess (@) { + my @argslist = @_; + my %params=@argslist; + + my $html = ''; + my $class = defined $params{class} + ? 'class="'.$params{class}.'"' + : ''; + + if ($inTable) { + $html = "$params{key}:"; + } else { + $html = "$params{key}:"; + } + + while (scalar(@argslist) > 1) { + my $type = shift @argslist; + my $data = shift @argslist; + if ($type eq 'link') { + # store links raw + $pagestate{$params{page}}{data}{$params{key}}{link}{$data} = 1; + my $link=IkiWiki::linkpage($data); + add_depends($params{page}, $link); + $html .= ' ' . htmllink($params{page}, $params{destpage}, $link); + } elsif ($type eq 'data') { + $data = IkiWiki::preprocess($params{page}, $params{destpage}, + IkiWiki::filter($params{page}, $params{destpage}, $data)); + $html .= ' ' . $data; + # store data after processing - allows pagecounts to be stored, etc. + $pagestate{$params{page}}{data}{$params{key}}{data}{$data} = 1; + } + } + + if ($inTable) { + $html .= ""; + } else { + $html .= ""; + } + + return $html; + } + + sub preprocess_table (@) { my %params=@_; - $pagestate{$params{page}}{data}{$params{key}} = $params{value}; - - return IkiWiki::preprocess($params{page}, $params{destpage}, - IkiWiki::filter($params{page}, $params{destpage}, $params{value})) if defined wantarray; - } # }}} + my @lines; + push @lines, defined $params{class} + ? "' + : '
'; + + $inTable = 1; + + foreach my $line (split(/\n/, $params{datalist})) { + push @lines, "" . IkiWiki::preprocess($params{page}, $params{destpage}, + IkiWiki::filter($params{page}, $params{destpage}, $line)) . ""; + } + + $inTable = 0; + push @lines, '
'; + + return join("\n", @lines); + } package IkiWiki::PageSpec; - sub match_data_eq ($$;@) { #{{{ + sub match_data_eq ($$;@) { my $page=shift; my $argSet=shift; my @args=split(/,/, $argSet); my $key=shift @args; my $value=shift @args; - my $file = $IkiWiki::pagesources{$page}; - if (! exists $IkiWiki::pagestate{$page}{data}) { return IkiWiki::FailReason->new("page does not contain any data directives"); } @@ -446,13 +597,37 @@ See also: return IkiWiki::FailReason->new("page does not contain data key '$key'"); } - my $formVal = $IkiWiki::pagestate{$page}{data}{$key}; - - if ($formVal eq $value) { + if ($IkiWiki::pagestate{$page}{data}{$key}{data}{$value}) { return IkiWiki::SuccessReason->new("value matches"); } else { return IkiWiki::FailReason->new("value does not match"); } - } #}}} + } + + sub match_data_link ($$;@) { + my $page=shift; + my $argSet=shift; + my @params=@_; + my @args=split(/,/, $argSet); + my $key=shift @args; + my $value=shift @args; + + if (! exists $IkiWiki::pagestate{$page}{data}) { + return IkiWiki::FailReason->new("page $page does not contain any data directives and so cannot match a link"); + } + + if (! exists $IkiWiki::pagestate{$page}{data}{$key}) { + return IkiWiki::FailReason->new("page $page does not contain data key '$key'"); + } + + foreach my $link (keys %{ $IkiWiki::pagestate{$page}{data}{$key}{link} }) { + # print STDERR "Checking if $link matches glob $value\n"; + if (match_glob($link, $value, @params)) { + return IkiWiki::SuccessReason->new("Data link on page $page with key $key matches glob $value: $link"); + } + } + + return IkiWiki::FailReason->new("No data link on page $page with key $key matches glob $value"); + } 1