From 55a83d9ceb9864cb5695671ccdcd4da5c1b7f585 Mon Sep 17 00:00:00 2001 From: Juerd Waalboer Date: Thu, 18 Jan 2024 18:15:35 +0100 Subject: [PATCH] v6.0.0: big revbank.products syntax change Rationale in UPGRADING.md It's a big change technically, but converting the format won't be hard for admins. There's a compatibility mode with loud warnings in case the file isn't converted. --- UPGRADING.md | 94 +++++++++++++++++++++++++++-- plugins/products | 70 +++++++++++++++++----- plugins/products.pod | 65 ++++++++++++++++---- plugins/statiegeld.pod | 10 ++-- plugins/vat.pod | 4 +- revbank | 2 +- revbank.products | 131 ++++++++++++++++------------------------- 7 files changed, 254 insertions(+), 122 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 7f31153..741a9f7 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -1,3 +1,79 @@ +# When upgrading, always: + +1. Make sure nobody is using RevBank. +2. Make a backup of your RevBank data and code repo(s). +3. Read this file :) + +# (2024-01-20) RevBank 6.0.0 + +Note that the changes to `revbank.products` do NOT apply to `revbank.market` +and other files. + +## Update your `revbank.products` file + +TL;DR: Product descriptions now need `"quotes"` around them. + +This version comes with breaking changes to the `revbank.products` syntax, to +expand the capabilities of the file in a more future-proof way. Bitlair +(Hackerspace Amersfoort) has requested a way to add metadata to products for +automation, which together with recent other additions to the format, made +clear a more structured approach was needed. + +The line format for the products file is now like the input format of the +command line interface. This means that if product descriptions contain spaces, +as they typically do, quotes are needed around them. You can pick between +`"double"` and `'single'` quotes. Any backslashes and quotes within the same +kind of quotes need escaping by adding a `\` in front, e.g. `\"` and `\\`. + +``` +# Old format: +product_id 0.42 Can't think of a good description +addon1 +addon2 + +# New format, recommended style: +product_id 0.42 "Can't think of a good description" +addon1 +addon2 + +# Automatically generated? You may wish to quote all fields: +"product_id" "0.42" "Can't think of a good description" "+addon1" "+addon2" + +# Escaping also works: +product_id 0.42 Can\'t\ think\ of\ a\ good\ description +addon1 +addon2 +``` + +To convert your `revbank.products` file to the recommended style automatically, +you could use: + +```sh +# The following is one command. It was obviously not optimized for readability :) + +perl -i.backupv6 -ple'unless (/^\s*#/ or /^\s*$/) { + my ($pre, $desc) = /(^\s*\S+\s+\S+\s*)(.*)/; $pre .= " " if $pre !~ /\s$/; + my @a; unshift @a, $1 while $desc =~ s/\s\+(\S+)$//; + $desc =~ s/([\"\\])/\\$1/g; $_ = "$pre\"$desc\""; + for my $a (@a) { $_ .= " +$a" } +}' revbank.products +``` + +Note that this will leave commented lines unchanged! If those contain disabled +products, you'll have to add the quotes yourself. + +## New feature: hashtags in `revbank.products` + +After the description field, you can add hashtag fields. These begin with `#` +and may take the form of a lone `#hashtag`, or they may be used as a +`#key=value` pair. The hashtags can be read by plugins. Out of the box, they +currently do nothing. + +``` +8711327538481 0.80 "Ola Liuk" #ah=wi162664 #q=8 +8712100340666 0.45 "Ola Raket" #ah=wi209562 #q=12 +5000112659184,5000112658873 0.95 "Coca-Cola Cola Zero Sugar (33 cl)" #sligro +sb + +# equivalent: +"8711327538481" "0.80" "Ola Liuk" "#ah=wi162664" "#q=8" +``` + +See https://github.com/bitlair/revbank-inflatinator/ for a possible use of adding metadata. + # (2023-12-26) RevBank 5.0.0 This version comes with breaking changes to the command line syntax, to shield @@ -231,19 +307,25 @@ list from within RevBank, add `edit` to `revbank.plugins`. ## Check your `revbank.products` -There's new syntax for `revbank.products`: addons. Check that your lines don't -have `+foo` at the end, where `foo` can be anything. +> Added 2024-01-20 v6.0.0: if you're upgrading to v6.0.0 from a version before +> v3.6, instead of following these instructions, you can just add quotes to the +> descriptions (when using the perl oneliner from the v6.0.0 upgrade +> instructions, check if any `+something` that got placed outside of the quotes +> should have been within the quotes.) + +~~There's new syntax for `revbank.products`: addons. Check that your lines don't +have `+foo` at the end, where `foo` can be anything.~~ -Also check that you don't have any product ids that start with `+`; those can -no longer be entered as this syntax now has special semantics. +~~Also check that you don't have any product ids that start with `+`; those can +no longer be entered as this syntax now has special semantics.~~ -So these don't work as before: +~~So these don't work as before:~~ example_id 1.00 Example product +something +something 1.00 Product id that starts with plus example,+alias 1.00 Alias that starts with plus -These will keep working as they were: +~~These will keep working as they were:~~ example_id1 1.00 Example product+something example_id2 1.00 Example product + something diff --git a/plugins/products b/plugins/products index 55f658d..e1251cc 100644 --- a/plugins/products +++ b/plugins/products @@ -13,16 +13,61 @@ sub read_products() { %products = (); $mtime = -M $filename; - my $line = 0; + my $linenr = 0; + my $warnings = 0; - for (slurp $filename) { - $line++; + for my $line (slurp $filename) { + $linenr++; - s/^\s+|\s+$//g; # trim - next if /^#/; - next if not length; + next if $line =~ m[ + ^\s*\# # comment line + | + ^\s*$ # empty line, or only whitespace + ]x; + + my @split = RevBank::Prompt::split_input($line); + + if (grep /\0SEPARATOR/, @split) { + warn "Invalid character in $filename line $linenr.\n"; + next; + } + if (grep /\0/, @split) { + warn "Invalid value in $filename line $linenr.\n"; + next; + } + + my ($ids, $p, $desc, @extra) = @split; + + my @addon_ids; + my %tags; + + my $compat = 0; + if (@split == 1 and ref $split[0]) { + $compat = 1; + } else { + for (@extra) { + if (/^\+(.*)/) { + push @addon_ids, $1; + } elsif (/^\#(\w+)(=(.*))/) { + $tags{$1} = $2 ? $3 : 1; + } else { + $compat = 1; + last; + } + } + } + + if ($compat) { + $warnings++; + warn "$filename line $linenr: can't parse as new format; assuming old format.\n" if $warnings < 4; + warn "Too many warnings; suppressing the rest. See UPGRADING.md for instructions.\n" if $warnings == 4; + + ($ids, $p, $desc) = split " ", $line, 3; + + @addon_ids = (); + unshift @addon_ids, $1 while $desc =~ s/\s+ \+ (\S+)$//x; + } - my ($ids, $p, $desc) = split " ", $_, 3; my @ids = split /,/, $ids; $p ||= "invalid"; @@ -35,21 +80,17 @@ sub read_products() { if ($percent) { if (grep !/^\+/, @ids) { - warn "Percentage invalid for non-addon at $filename line $line.\n"; + warn "Percentage invalid for non-addon at $filename line $linenr.\n"; next; } $price = 0 + $price; } else { $price = eval { parse_amount($price) }; if (not defined $price) { - warn "Invalid price for '$ids[0]' at $filename line $line.\n"; + warn "Invalid price for '$ids[0]' at $filename line $linenr.\n"; next; } } - - my @addon_ids; - unshift @addon_ids, $1 while $desc =~ s/\s+ \+ (\S+)$//x; - $products{$_} = { id => $ids[0], price => $sign * $price, @@ -57,7 +98,8 @@ sub read_products() { description => $desc, contra => $contra || $default_contra, _addon_ids => \@addon_ids, - line => $line, + line => $linenr, + tags => \%tags, } for @ids; } diff --git a/plugins/products.pod b/plugins/products.pod index bcdf1d6..877fd27 100644 --- a/plugins/products.pod +++ b/plugins/products.pod @@ -4,10 +4,14 @@ products - RevBank plugin for selling products =head1 SYNOPISIS - 8710447032756 0.80 Festini Peer - 4029764001807,clubmate 1.40 Club-Mate +half +pf - pf 0.15@+pfand Pfand NRW-Flasche - +half -50% 50% discount \o/ + # Comments are lines that begin with a # character. + # Empty lines are ignored. + + 8710447032756 0.80 "Festini Peer" + 4029764001807,clubmate 1.40 "Club-Mate" +half +pf + pf 0.15@+pfand "Pfand NRW-Flasche" + +half -50% "50% discount \\o/" + 123 0.42 "Hashtag example" #tag #tag2=42 =head1 DESCRIPTION @@ -63,9 +67,15 @@ C. User accounts are liability accounts.) =head2 Description -The description may contain whitespace. +The description, like other columns, may contain whitespace, but to use +whitespace, either the entire field "needs quotes" around it, or the whitespace +can be escaped with backslashes. + +It is suggested to always use quotes around the description. + +=head2 Additional fields -=head2 Addons +=head3 Addons Addons are products that are added as part of the main product. They are specified after the description, with a C<+> sign that has whitespace before @@ -76,9 +86,9 @@ the product id C is used instead. The difference is that a product id C<+foo> can only be used as an addon for another product, while C can be used either as an addon or a manually entered as a standalone product. - example_id 2.20 Example product +first +second - +first 1.20 First thing - second 0.80 Second thing + example_id 2.20 "Example product" +first +second + +first 1.20 "First thing" + second 0.80 "Second thing" In this example, the final price of the example product will be 4.20. It is not possible to buy the first thing separate, but it is possible to buy the second @@ -97,7 +107,7 @@ listed as a component named "Product". A product can have multiple addons. Addon products themselves can also have further addons, but circular recursion is not supported. -=head3 Percentage addons +=head4 Percentage addons As a special case, an addon's price can be a percentage. In this case, the price is calculated from the sum of the the product components I that have I as the percentage addon. So, given the following example, - example_id 0.90 Example product +some_fee +discount - +some_fee 0.15@+fees Some fee; might be a bottle deposit - +discount -50% Special offer discount! + example_id 0.90 "Example product" +some_fee +discount + +some_fee 0.15@+fees "Some fee; might be a bottle deposit" + +discount -50% "Special offer discount!" only 0.45 is discounted, because the 0.15 has a different contra account. While complicated, this is probably what you want in most cases. There is currently no way to apply a discount to the product with all of its addons. A percentage addon must have a product_id that begins with C<+>. + +=head3 Tags + +Additional metadata can be given in additional fields that begin with C<#> and +the name of the tag, optionally followed by C<=> and a value to turn it into a +key/value pair. If no value is specified, a value of C<1> is used. + +The name of a hashtag must contain only C characters. There must +not be whitespace after the C<#> or around the C<=>. + +Like all the fields, the field can be quoted to contain whitespace. Note, +however, that the quotes must be placed around the entire field, not just the +value part. + + ht1 0.42 "Just one hashtag" #tag + ht2 0.42 "Two hashtags!" #tag #key=value + ht3 0.42 "Surprising syntax" "#x=spaces in value" + +Tags can be accessed by custom plugins, but are currently ignored by upstream +RevBank and its plugins. + +=head3 Other additional fields + +When any field is added after the description, that does not begin with C<+> or +C<#>, RevBank currently assumes it's the old syntax (which is not described in +the current version of this document!), and parses it using the old semantics +while showing a warning. + +This compatibility feature will be removed from a future version of RevBank. diff --git a/plugins/statiegeld.pod b/plugins/statiegeld.pod index 9a31b90..2381722 100644 --- a/plugins/statiegeld.pod +++ b/plugins/statiegeld.pod @@ -6,11 +6,11 @@ statiegeld - RevBank plugin for return deposits revbank.products: - clubmate 1.40 Club-Mate bottle +sb - cola 0.90 Cola can +sc - +sb 0.15@+statiegeld Bottle deposit - +sc 0.25@+statiegeld Can deposit - matecrate 1.50@+statiegeld Mate crate (empty) + clubmate 1.40 "Club-Mate bottle" +sb + cola 0.90 "Cola can" +sc + +sb 0.15@+statiegeld "Bottle deposit" + +sc 0.25@+statiegeld "Can deposit" + matecrate 1.50@+statiegeld "Mate crate (empty)" =head1 DESCRIPTION diff --git a/plugins/vat.pod b/plugins/vat.pod index a702226..04f619f 100644 --- a/plugins/vat.pod +++ b/plugins/vat.pod @@ -13,8 +13,8 @@ C C - 123123123 1.00 Example product that gets the default contra - 42424242 1.00@+sales/products/hoogbtw Example with high VAT rate + 123123123 1.00 "Example product that gets the default contra" + 42424242 1.00@+sales/products/hoogbtw "Example with high VAT rate" =head1 DESCRIPTION diff --git a/revbank b/revbank index 4c58376..e921b53 100755 --- a/revbank +++ b/revbank @@ -18,7 +18,7 @@ use RevBank::Messages; use RevBank::Cart; use RevBank::Prompt; -our $VERSION = "5.1.3"; +our $VERSION = "6.0.0"; our %HELP1 = ( "abort" => "Abort the current transaction", ); diff --git a/revbank.products b/revbank.products index 9c60328..9fa0b30 100644 --- a/revbank.products +++ b/revbank.products @@ -1,83 +1,52 @@ ### THIS IS THE EXAMPLE FILE; DON'T ADD PRODUCTS, BUT MAKE A NEW FILE ### -# Deze in principe niet veranderen want dat heeft invloed op producten -# die al eerder verkocht zijn. Omschrijving aanpassen kan geen kwaad. -+sb 0.15@+statiegeld Statiegeld blikje -+sf 0.15@+statiegeld Statiegeld plastic flesje -+sm 0.15@+statiegeld Statiegeld Mehrwegflasche -matekrat 1.50@+statiegeld Statiegeld matekrat excl. inhoud - -# Let op: als een product een statiegeld addon (bijv. +sb) heeft, dat niet -# meer veranderen, want dan kan het product niet meer worden ingeleverd. - - - -# Empty lines and lines beginning with # are ignored. - -# All other lines should have three fields, whitespace separated. The first two -# fields are barcode/productID and price, the third field is the description. -# Only the last field may have whitespace. - -# Free stuff -649241869825 0.00 Free disgusting stuff (LS) -57063003 0.00 Free Stimorol - -# Water -5400151013112 0.50 Carbonated mineral water -5400155056542 0.50 Mineral water - -# Soda -5449000014535 0.70 Sprite -5449000000996 0.70 Coca-Cola - -# Chips -05414359710322 0.50 Chips -05414359710315 0.50 Chips -zakjechips 0.50 Chips - - -clubmate,4029764001807 1.40 Club-Mate +sm -87124385 1.10 Chocomel -8710447032756 0.80 Festini peer -tostikaas 1.15 Tosti kaas -4001724035848 5.10 Pizza Veggie Mix - -cola,5000112638745 0.90 Coca-Cola Zero, blik 33 cl +korting +sb -pizza 4.50 Pizza +sgo -+korting -50% Yay korting - -dinges 0.00 Dingeskitje +aa +pcb +koppla +j +korting -aa 0.20@+sales/batt Batterij -pcb 0.80 pcb -koppla 5.00 Koppla -j 0.10@juerd Gratis geld voor juerd - -#moo 2,151 bla -#meh 1.00 blaaat +mekker -bla -5 - -#aap 1.00 ding +noot -#noot 9.99 dinges deze niet -#+noot 2.00 dinges +mies -#mies 3.00 donges +aap - -sgo 0.80@+stroomgebruik Stroomgebruik oven - -nietsb -0.15@+statiegeld WHoaaa - -korting -0.10@-korting Korting \o/ - -BOUNTY1 -10.00@-expenses/bounties Bedankt voor dingen!! +j -BOUNTY2 -5.00@-expenses/bounties Bedankt en zo - - - example_id 1.00 Example product +something - +something 1.00 Product that has an id that starts with plus - example,+alias 1.00 Product that has an alias that starts with plus - - - example_id1 1.00 Example product+something - example_id2 1.00 Example product + something - more_stuff 1.00 Example product with +something but not at the end - bbq 1.00 3+ pieces of meat - +# Documentation: perldoc plugins/products.pod +# or https://github.com/revspace/revbank/blob/master/plugins/products.pod + +# Not using statiegeld? Just leave off the +sb etc! + +5000112658873 0.90 "Coca Cola Zero, can 33 cl" +sb +8712800196440 1.10 "Chocomel, can 25 cl" +sb +4337182201458 0.75 "Spuitwater, bottle 50 cl" +sf +4337182093381 0.75 "Plat water, bottle 50 cl" +sf +5000112646719 3.00 "Coca-Cola Zero, bottle 150 cl" +sF +4029764001883 1.15 "Club-Mate Cola, bottle 33 cl" +sm +4029764001401 1.40 "Club-Mate Granat, bottle 50 cl" +sm +4029764001869 1.40 "Club-Mate Winter-Edition, bottle 50cl" +sm +4029764001906 1.40 "Club-Mate Zero, bottle 50cl" +sm +4029764001807 1.40 "Club-Mate, bottle 50 cl" +sm +5000112545326 0.90 "Coca-Cola, can 33 cl (no deposit)" +5740700988349 0.90 "Coca-Cola, can 33 cl (no deposit)" +8710615077206 0.50 "Daelmans Stroopwafel" +THT +8716100202337 0.70 "Katja Apekoppen" +40084077 0.40 "Kinder Maxi" +4001724046196 5.50 "Oetker Ristorante Pizza Wuerstel & Patatine" +4009233016846 5.00 "Wagner Sensazione Pizza Mozzarella" +8710401024605 0.75 "Titan (Raket)" +8718964162758 1.50 "Crappy fietslampje is beter dan niks" +mdf3mm-wit 3.00 "MDF 600x400x3 wit gelakt " +mdf4mm 1.50 "MDF 600x400x4" +populier5mm 3.15 "Populier-timmerplaat 600x400x5" +hardboard3mm 0.65 "Hardboard 600x400x3" + +# Aliases: +8710447032756,Perenijsje 0.80 "Ola Perenijsje" +8712100340666,Raket 0.55 "Raket" +8722700627821,Splitijsje 0.80 "Split" + +# Special "products" +BOUNTY1 -10.00@-expenses/bounties "Thanks for vacuuming!" +BOUNTY3 -25.00@-expenses/bounties "Thanks for mopping!" ++THT -50.00% "Discount (best-before date expired)" +ibutton 15.00@+ibuttonborg "Deposit for new iButton" +ibutton-terug -15.00@+ibuttonborg "Deposit returned for iButton" +contributie 30.00@+sales/contributie "Membership 1 month" + +# For use with the statiegeld plugin: +# (Nothing but descriptions should be changed after using these addons.) ++sb 0.15@+statiegeld "Deposit can" ++sf 0.15@+statiegeld "Deposit bottle" ++sF 0.25@+statiegeld "Deposit large bottle" ++sm 0.15@+statiegeld "Deposit Mehrwegpfand" +#+se 0.25@+statiegeld "Statiegeld Einwegpfand" ++smk,matekrat 1.50@+statiegeld "Deposit crate w/o bottles"