Browse Source

added more functionality:

* parser is working
* classes for all the valid elements types
* basic translation working
* add tests for parsing files and some basic translations
* first architecture note about resource sets and resource groups

mpn
theMage 8 months ago
parent
commit
a464f9a992
39 changed files with 1091 additions and 113 deletions
  1. 0 0
      .gitignore
  2. 84 0
      archnotes/001-sets-n-groups.md
  3. 11 63
      lib/Locale/Fluent.pm
  4. 55 0
      lib/Locale/Fluent/Elements.pm
  5. 46 0
      lib/Locale/Fluent/Elements/Argument.pm
  6. 43 0
      lib/Locale/Fluent/Elements/ArgumentList.pm
  7. 27 0
      lib/Locale/Fluent/Elements/Attribute.pm
  8. 19 0
      lib/Locale/Fluent/Elements/AttributeAccessor.pm
  9. 45 0
      lib/Locale/Fluent/Elements/Base.pm
  10. 24 0
      lib/Locale/Fluent/Elements/BlockText.pm
  11. 20 0
      lib/Locale/Fluent/Elements/CallArguments.pm
  12. 6 0
      lib/Locale/Fluent/Elements/DefaultVariant.pm
  13. 22 0
      lib/Locale/Fluent/Elements/FunctionReference.pm
  14. 23 0
      lib/Locale/Fluent/Elements/Identifier.pm
  15. 49 0
      lib/Locale/Fluent/Elements/InlineExpression.pm
  16. 37 0
      lib/Locale/Fluent/Elements/InlinePlaceable.pm
  17. 17 0
      lib/Locale/Fluent/Elements/InlineText.pm
  18. 18 0
      lib/Locale/Fluent/Elements/Message.pm
  19. 23 0
      lib/Locale/Fluent/Elements/MessageReference.pm
  20. 34 0
      lib/Locale/Fluent/Elements/NamedArgument.pm
  21. 18 0
      lib/Locale/Fluent/Elements/NumberLiteral.pm
  22. 32 0
      lib/Locale/Fluent/Elements/Pattern.pm
  23. 12 0
      lib/Locale/Fluent/Elements/PatternElement.pm
  24. 63 0
      lib/Locale/Fluent/Elements/SelectExpression.pm
  25. 18 0
      lib/Locale/Fluent/Elements/StringLiteral.pm
  26. 46 0
      lib/Locale/Fluent/Elements/Term.pm
  27. 48 0
      lib/Locale/Fluent/Elements/TermReference.pm
  28. 18 0
      lib/Locale/Fluent/Elements/Text.pm
  29. 20 0
      lib/Locale/Fluent/Elements/VariableReference.pm
  30. 29 0
      lib/Locale/Fluent/Elements/Variant.pm
  31. 40 9
      lib/Locale/Fluent/Parser.pm
  32. 38 0
      lib/Locale/Fluent/ResourceSet.pm
  33. 46 0
      t/40-parse-file.t
  34. 47 0
      t/45-resource-set.t
  35. 0 24
      t/pod-coverage.t
  36. 0 16
      t/pod.t
  37. 6 1
      test_files/basic.flt
  38. 4 0
      test_files/broken.flt
  39. 3 0
      test_files/empty.flt

ignore.txt → .gitignore


+ 84 - 0
archnotes/001-sets-n-groups.md

@ -0,0 +1,84 @@
1
# Resource Sets and Resource Groups
2
3
While thinking about how to split the code for this lib and what type
4
of functionality I would like to to provide with it, I started to think
5
about the usecases for this.
6
7
The reason I started to write this library was because I was looking for
8
something better than gettext to use for my own framework (SorWeTo) and
9
for my pet projects.
10
11
The way I usually write code, I want to support multiple websites with
12
different default languages and support for user selected languages in a
13
single instance of code, and while I could come up with a way to handle
14
this in the framework itself, I think it would be harder to handle thinks
15
like partial translations and similar in the framework than in the
16
translation code itself.
17
18
I also think similar functionality would be useful have in other projects
19
that may be using this. So, I come up with this structure:
20
21
## ResourceSet
22
23
A ResourceSet is a group of translation resources as defined by
24
[projectfluent.org]. It is a set because it doesn't not allow for multiple
25
definitions of the same result multiple times (other than in the ways
26
provided but the FTL syntax itself.
27
28
I plan on allowing to merge multiple ResultSets into a single ResourceSet,
29
the resulting ResourceSet will still only have one defintion per resource,
30
with the colision resolution method not defined at this time, but likely
31
will support a parameter to define which of "keep existing", "keep new" or
32
"fail" to use.
33
34
## ResourceGroup
35
36
A ResourceGroup is a collection of ResourceSets with defined contexts and
37
a way to decide on the priority of such contexts and how to cascade between
38
different values for the same context. As an example, consider the following
39
contexts:
40
41
```perl
42
{ app       => "default",
43
  language  => "default",
44
},
45
{ app       => "default",
46
  language  => "en",
47
},
48
{ app       => "superapp",
49
  language  => "en",
50
},
51
{ app       => "superapp",
52
  language  => "fr",
53
}
54
```
55
56
When asked for a translation from this ResourceGroup with the context:
57
58
```perl
59
{ app       => "supperapp",
60
  language  => "fr",
61
}
62
```
63
64
The ResourceGroup will look for resources first in the the ResourceSet
65
connect with the context for `app="superapp", language="fr"` and then
66
on the ResourceSet connect with `app="default", language="default"`.
67
68
If the context for translation was for "en", instead of "fr", the search
69
would happen instead in `app="superapp", language="en"`,
70
`app="default", language="en"`, `app="default", language="default"`.
71
72
This will allow the definition of translations at multiple levels, making
73
it possible to override at application level translations from libraries
74
or similar.
75
76
## Translations
77
78
From an initialization point of view, ResourceSet and ResourceGroup will
79
be different, as they represent different usecases, but from a translation
80
point of view, they have the same external behaviour, this way the
81
translation code itself doesn't need to know whether it is working with
82
a ResourceSet or a ResourceGroup.
83
84

+ 11 - 63
lib/Locale/Fluent.pm

@ -4,50 +4,31 @@ use 5.006;
4 4
use strict;
5 5
use warnings;
6 6
7
=head1 NAME
8
9
Locale::Fluent - The great new Locale::Fluent!
10
11
=head1 VERSION
7
our $VERSION = v0.1.1;
12 8
13
Version 0.01
9
use Locale::Fluent::Parser;
14 10
15
=cut
16
17
our $VERSION = '0.01';
18
19
20
=head1 SYNOPSIS
11
#TODO: add methods for the most common locale::fluent operations
21 12
22
Quick summary of what the module does.
23 13
24
Perhaps a little code snippet.
25
26
    use Locale::Fluent;
14
1; # End of Locale::Fluent
27 15
28
    my $foo = Locale::Fluent->new();
29
    ...
30 16
31
=head1 EXPORT
17
__END__
32 18
33
A list of functions that can be exported.  You can delete this section
34
if you don't export anything, such as for a purely object-oriented module.
19
=head1 NAME
35 20
36
=head1 SUBROUTINES/METHODS
21
Locale::Fluent - The great new Locale::Fluent!
37 22
38
=head2 function1
23
=head1 VERSION
39 24
40
=cut
25
Version 0.1.1
41 26
42
sub function1 {
43
}
44 27
45
=head2 function2
28
=head1 SYNOPSIS
46 29
47
=cut
48 30
49
sub function2 {
50
}
31
=head1 EXPORT
51 32
52 33
=head1 AUTHOR
53 34
@ -55,42 +36,10 @@ theMage, C<< <neves at cpan.org> >>
55 36
56 37
=head1 BUGS
57 38
58
Please report any bugs or feature requests to C<bug-locale-fluent at rt.cpan.org>, or through
59
the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Locale-Fluent>.  I will be notified, and then you'll
60
automatically be notified of progress on your bug as I make changes.
61
62
63 39
64 40
65 41
=head1 SUPPORT
66 42
67
You can find documentation for this module with the perldoc command.
68
69
    perldoc Locale::Fluent
70
71
72
You can also look for information at:
73
74
=over 4
75
76
=item * RT: CPAN's request tracker (report bugs here)
77
78
L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Locale-Fluent>
79
80
=item * AnnoCPAN: Annotated CPAN documentation
81
82
L<http://annocpan.org/dist/Locale-Fluent>
83
84
=item * CPAN Ratings
85
86
L<http://cpanratings.perl.org/d/Locale-Fluent>
87
88
=item * Search CPAN
89
90
L<http://search.cpan.org/dist/Locale-Fluent/>
91
92
=back
93
94 43
95 44
=head1 ACKNOWLEDGEMENTS
96 45
@ -138,4 +87,3 @@ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
138 87
139 88
=cut
140 89
141
1; # End of Locale::Fluent

+ 55 - 0
lib/Locale/Fluent/Elements.pm

@ -0,0 +1,55 @@
1
package Locale::Fluent::Elements;
2
3
use Locale::Fluent::Elements::Message;
4
use Locale::Fluent::Elements::Pattern;
5
use Locale::Fluent::Elements::PatternElement;
6
use Locale::Fluent::Elements::InlinePlaceable;
7
use Locale::Fluent::Elements::InlineText;
8
use Locale::Fluent::Elements::InlineExpression;
9
use Locale::Fluent::Elements::BlockText;
10
use Locale::Fluent::Elements::Identifier;
11
use Locale::Fluent::Elements::FunctionReference;
12
use Locale::Fluent::Elements::VariableReference;
13
use Locale::Fluent::Elements::CallArguments;
14
use Locale::Fluent::Elements::ArgumentList;
15
use Locale::Fluent::Elements::Argument;
16
use Locale::Fluent::Elements::NamedArgument;
17
use Locale::Fluent::Elements::MessageReference;
18
use Locale::Fluent::Elements::TermReference;
19
use Locale::Fluent::Elements::AttributeAccessor;
20
use Locale::Fluent::Elements::StringLiteral;
21
use Locale::Fluent::Elements::NumberLiteral;
22
use Locale::Fluent::Elements::Identifier;
23
use Locale::Fluent::Elements::Term;
24
use Locale::Fluent::Elements::SelectExpression;
25
use Locale::Fluent::Elements::Variant;
26
use Locale::Fluent::Elements::DefaultVariant;
27
use Locale::Fluent::Elements::Attribute;
28
29
sub create {
30
  my (undef, $type, $args) = @_;
31
32
  $type = "\u$type";
33
  $type =~ s/_(.)/\u$1/g;
34
35
  my $class = "Locale::Fluent::Elements::$type";
36
37
  my $res;
38
  eval {
39
    $res = $class->new( %$args );
40
41
  } or do {
42
    my ($err) = $@;
43
    print STDERR "err: $err\n"
44
      unless $err =~ m{Can't locate object method "new"};
45
    unless ($type eq 'Text') {
46
      print STDERR "FLT: Missing $class\n";
47
      use Data::Dumper;
48
      print STDERR Dumper( $args);
49
    }
50
  };
51
52
  return $res;
53
}
54
55
1;

+ 46 - 0
lib/Locale/Fluent/Elements/Argument.pm

@ -0,0 +1,46 @@
1
package Locale::Fluent::Elements::Argument;
2
3
use Moo;
4
extends 'Locale::Fluent::Elements::Base';
5
6
has [qw(
7
      named_argument
8
      inline_expression
9
    )] => (
10
  is  => 'ro',
11
  default => sub { undef },
12
);
13
14
around BUILDARGS => sub {
15
  my ($orig, $class, %args) = @_;
16
17
  $args{named_argument}     = delete $args{ NamedArgument };
18
  $args{inline_expression}  = delete $args{ InlineExpression };
19
20
  $class->$orig( %args );
21
};
22
23
sub identifier {
24
  my ($self) = @_;
25
26
  if ($self->named_argument) {
27
    return $self->named_argument->identifier;
28
  }
29
30
  return;
31
}
32
33
sub translate {
34
  my ($self, $variables) = @_;
35
36
  if ($self->named_argument) {
37
    return $self->named_argument->translate( $variables );
38
  } elsif ($self->inline_expression) {
39
    return $self->inline_expression->translate( $variables );
40
  }
41
42
  return;
43
}
44
45
46
1;

+ 43 - 0
lib/Locale/Fluent/Elements/ArgumentList.pm

@ -0,0 +1,43 @@
1
package Locale::Fluent::Elements::ArgumentList;
2
3
use Moo;
4
extends 'Locale::Fluent::Elements::Base';
5
6
has argument => (
7
  is  => 'ro',
8
  default => sub { [] },
9
);
10
11
around BUILDARGS => sub {
12
  my ($orig, $self, %args) = @_;
13
14
  $args{ argument } = delete $args{ Argument };
15
  $args{ argument } = [ $args{ argument } ]
16
    unless ref $args{argument} eq 'ARRAY';
17
18
  $args{ argument } = [map { {Argument => $_ } }
19
                        @{ $args{ argument } } ];
20
21
  $self->$orig( %args );
22
};
23
24
sub to_variables {
25
  my ($self, $variables ) = @_;
26
27
  my %vars;
28
29
  my $pos = 0;
30
  for my $arg (@{ $self->argument // [] }) {
31
    my $id = $arg->identifier;
32
    unless ($id ) {
33
      $id = "position_$pos";
34
      $pos++;
35
    }
36
    $vars{ $id } = $arg->translate( $variables );
37
  }
38
39
  return \%vars;
40
}
41
42
43
1;

+ 27 - 0
lib/Locale/Fluent/Elements/Attribute.pm

@ -0,0 +1,27 @@
1
package Locale::Fluent::Elements::Attribute;
2
3
use Moo;
4
extends 'Locale::Fluent::Elements::Base';
5
6
has [qw(identifier pattern)] => (
7
  is  => 'ro',
8
  default => sub { undef },
9
);
10
11
around BUILDARGS => sub {
12
  my ($orig, $class, %args) = @_;
13
14
  $args{ identifier } = delete $args{ Identifier };
15
  $args{ pattern }    = delete $args{ Pattern };
16
17
  $class->$orig( %args );
18
};
19
20
sub translate {
21
  my ($self, $variables) = @_;
22
23
  return $self->pattern->translate( $variables );
24
}
25
26
27
1;

+ 19 - 0
lib/Locale/Fluent/Elements/AttributeAccessor.pm

@ -0,0 +1,19 @@
1
package Locale::Fluent::Elements::AttributeAccessor;
2
3
use Moo;
4
extends 'Locale::Fluent::Elements::Base';
5
6
has identifier => (
7
  is  => 'ro',
8
  default => sub { undef },
9
);
10
11
around BUILDARGS => sub {
12
  my ($orig, $class, %args) = @_;
13
14
  $args{identifier} = delete $args{ Identifier };
15
16
  $class->$orig( %args );
17
};
18
19
1;

+ 45 - 0
lib/Locale/Fluent/Elements/Base.pm

@ -0,0 +1,45 @@
1
package Locale::Fluent::Elements::Base;
2
3
use Moo;
4
5
around BUILDARGS => sub {
6
  my ($orig, $class, @args) = @_;
7
8
  my %args = ref $args[0] ? %{ $args[0] } : @args;
9
10
  for my $k (keys %args) {
11
    my $val = delete $args{ $k };
12
13
    if (ref $val eq 'HASH') {
14
       
15
      my $res = Locale::Fluent::Elements->create(
16
          $k, $val
17
        );
18
    
19
      $val = $res if $res;
20
21
    } elsif (ref $val eq 'ARRAY') {
22
      my @items;
23
      for my $item ( @$val ) {
24
        my ($type) = keys %$item;
25
        my $itemval = ref $item->{$type}
26
                        ? $item->{$type} 
27
                        : { text => $item->{$type} };
28
        my $res = Locale::Fluent::Elements->create(
29
            $type => $itemval
30
          );
31
32
        push @items, $res ? $res : $item;
33
      }
34
35
      $val = \@items;
36
    }
37
38
    $args{ "\L$k" } = $val;
39
  }
40
41
  return $class->$orig( %args );
42
};
43
44
1;
45

+ 24 - 0
lib/Locale/Fluent/Elements/BlockText.pm

@ -0,0 +1,24 @@
1
package Locale::Fluent::Elements::BlockText;
2
3
use Moo;
4
extends 'Locale::Fluent::Elements::Base';
5
6
has text => (
7
  is  => 'ro',
8
  default => sub { undef },
9
);
10
11
12
around BUILDARGS => sub {
13
  my ($orig, $class, %args) = @_;
14
15
  my $text = ($args{indented_char}//'').($args{inline_text}//'');
16
17
  $class->$orig( text => $text );
18
};
19
20
sub translate {
21
  return "\n".$_[0]->text;
22
}
23
24
1;

+ 20 - 0
lib/Locale/Fluent/Elements/CallArguments.pm

@ -0,0 +1,20 @@
1
package Locale::Fluent::Elements::CallArguments;
2
3
use Moo;
4
extends 'Locale::Fluent::Elements::Base';
5
6
has argument_list => (
7
  is  => 'ro',
8
  default => sub { [] },
9
);
10
11
12
sub to_variables {
13
  my ( $self, $variables ) = @_;
14
15
  return unless $self->argument_list;
16
17
  return $self->argument_list->to_variables;
18
}
19
20
1;

+ 6 - 0
lib/Locale/Fluent/Elements/DefaultVariant.pm

@ -0,0 +1,6 @@
1
package Locale::Fluent::Elements::DefaultVariant;
2
3
use Moo;
4
extends 'Locale::Fluent::Elements::Variant';
5
6
1;

+ 22 - 0
lib/Locale/Fluent/Elements/FunctionReference.pm

@ -0,0 +1,22 @@
1
package Locale::Fluent::Elements::FunctionReference;
2
3
use Moo;
4
extends 'Locale::Fluent::Elements::Base';
5
6
has [qw(
7
      identifier
8
      call_arguments
9
    )] => (
10
  is  => 'ro',
11
  default => sub { undef },
12
);
13
14
around BUILDARGS => sub {
15
  my ($orig, $class, %args) = @_;
16
17
  $args{call_arguments}     = delete $args{ CallArguments };
18
19
  $class->$orig( %args );
20
};
21
22
1;

+ 23 - 0
lib/Locale/Fluent/Elements/Identifier.pm

@ -0,0 +1,23 @@
1
package Locale::Fluent::Elements::Identifier;
2
3
use Moo;
4
extends 'Locale::Fluent::Elements::Base';
5
6
use overload
7
  '""'    => 'identifier',
8
  'bool'  => 'boolify';
9
10
has text => (
11
  is  => 'ro',
12
  default => sub { undef },
13
);
14
15
sub boolify { 1 }
16
17
sub identifier {
18
  my ($self) = @_;
19
20
  return $self->text;
21
}
22
23
1;

+ 49 - 0
lib/Locale/Fluent/Elements/InlineExpression.pm

@ -0,0 +1,49 @@
1
package Locale::Fluent::Elements::InlineExpression;
2
3
use Moo;
4
extends 'Locale::Fluent::Elements::Base';
5
6
has [qw(
7
      string_literal
8
      number_literal
9
      function_reference
10
      message_reference
11
      term_reference
12
      variable_reference
13
      inline_placeable
14
    )] => (
15
  is  => 'ro',
16
  default => sub { undef },
17
);
18
19
around BUILDARGS => sub {
20
  my ($orig, $class, %args) = @_;
21
22
  $args{string_literal}     = delete $args{ StringLiteral };
23
  $args{number_literal}     = delete $args{ NumberLiteral };
24
  $args{function_reference} = delete $args{ FunctionReference };
25
  $args{message_reference}  = delete $args{ MessageReference };
26
  $args{term_reference}     = delete $args{ TermReference };
27
  $args{variable_reference} = delete $args{ VariableReference };
28
  $args{inline_placeable}   = delete $args{ InlinePlaceable };
29
30
  $class->$orig( %args );
31
};
32
33
sub translate {
34
  my ($self, $variables) = @_;
35
36
  my $part
37
    =     $self->string_literal
38
      ||  $self->number_literal
39
      ||  $self->function_reference
40
      ||  $self->message_reference
41
      ||  $self->term_reference
42
      ||  $self->variable_reference
43
      ||  $self->inline_placeable;
44
45
  return ref $part ? $part->translate( $variables ) : $part;
46
  
47
}
48
49
1;

+ 37 - 0
lib/Locale/Fluent/Elements/InlinePlaceable.pm

@ -0,0 +1,37 @@
1
package Locale::Fluent::Elements::InlinePlaceable;
2
3
use Moo;
4
extends 'Locale::Fluent::Elements::Base';
5
6
has [qw(select_expression inline_expression)] => (
7
  is  => 'ro',
8
  default => sub { undef },
9
);
10
11
around BUILDARGS => sub {
12
  my ($orig, $class, %args) = @_;
13
14
  $args{inline_expression} = delete $args{InlineExpression}
15
    if $args{InlineExpression};
16
  $args{select_expression} = delete $args{SelectExpression}
17
    if $args{SelectExpression};
18
 
19
  $class->$orig( %args );
20
};
21
22
sub translate {
23
  my ($self, $variables) = @_;
24
25
  if ($self->select_expression) {
26
    $self->select_expression->translate( $variables );
27
28
  } elsif ($self->inline_expression) {
29
    $self->inline_expression->translate( $variables );
30
31
  } else {
32
    return '';
33
  }
34
}
35
36
37
1;

+ 17 - 0
lib/Locale/Fluent/Elements/InlineText.pm

@ -0,0 +1,17 @@
1
package Locale::Fluent::Elements::InlineText;
2
3
use Moo;
4
extends 'Locale::Fluent::Elements::Base';
5
6
has text => (
7
  is  => 'ro',
8
  default => sub { undef },
9
);
10
11
sub translate {
12
  my ($self) = @_;
13
14
  return $self->text;
15
}
16
17
1;

+ 18 - 0
lib/Locale/Fluent/Elements/Message.pm

@ -0,0 +1,18 @@
1
package Locale::Fluent::Elements::Message;
2
3
use Moo;
4
extends 'Locale::Fluent::Elements::Base';
5
6
has [qw(identifier pattern attributes)] => (
7
  is  => 'ro',
8
  default => sub { undef },
9
);
10
11
sub translate {
12
  my ($self, $variables) = @_;
13
14
  return $self->pattern->translate( $variables );
15
}
16
17
18
1;

+ 23 - 0
lib/Locale/Fluent/Elements/MessageReference.pm

@ -0,0 +1,23 @@
1
package Locale::Fluent::Elements::MessageReference;
2
3
use Moo;
4
extends 'Locale::Fluent::Elements::Base';
5
6
has [qw(
7
      identifier
8
      attribute_accessor
9
    )] => (
10
  is  => 'ro',
11
  default => sub { undef },
12
);
13
14
around BUILDARGS => sub {
15
  my ($orig, $class, %args) = @_;
16
17
  $args{identifier}         = delete $args{ Identifier };
18
  $args{attribute_accessor} = delete $args{ AttributeAccessor };
19
20
  $class->$orig( %args );
21
};
22
23
1;

+ 34 - 0
lib/Locale/Fluent/Elements/NamedArgument.pm

@ -0,0 +1,34 @@
1
package Locale::Fluent::Elements::NamedArgument;
2
3
use Moo;
4
extends 'Locale::Fluent::Elements::Base';
5
6
has [qw(
7
      identifier
8
      string_literal
9
      number_literal
10
    )] => (
11
  is  => 'ro',
12
  default => sub { undef },
13
);
14
15
around BUILDARGS => sub {
16
  my ($orig, $class, %args) = @_;
17
18
  $args{identifier}         = delete $args{ Identifier };
19
  $args{string_literal}     = delete $args{ StringLiteral };
20
  $args{number_literal}     = delete $args{ NumberLiteral };
21
22
  $class->$orig( %args );
23
};
24
25
sub translate {
26
  my ($self) = @_;
27
28
  my $part = $self->string_literal
29
          // $self->number_literal;
30
31
  return ref $part ? $part->translate : $part;
32
}
33
34
1;

+ 18 - 0
lib/Locale/Fluent/Elements/NumberLiteral.pm

@ -0,0 +1,18 @@
1
package Locale::Fluent::Elements::NumberLiteral;
2
3
use Moo;
4
extends 'Locale::Fluent::Elements::Base';
5
6
has text => (
7
  is  => 'ro',
8
  default => sub { undef },
9
);
10
11
12
sub translate {
13
  my ($self, $variables) = @_;
14
15
  return $self->text;
16
}
17
18
1;

+ 32 - 0
lib/Locale/Fluent/Elements/Pattern.pm

@ -0,0 +1,32 @@
1
package Locale::Fluent::Elements::Pattern;
2
3
use Moo;
4
extends 'Locale::Fluent::Elements::Base';
5
6
has [qw(pattern_element)] => (
7
  is  => 'ro',
8
  default => sub { undef },
9
);
10
11
around BUILDARGS => sub {
12
  my ($orig, $class, %args) = @_;
13
14
  $args{Pattern_Element} = delete $args{PatternElement};
15
  $args{Pattern_Element} = [ $args{Pattern_Element} ]
16
    unless ref $args{Pattern_Element} eq 'ARRAY';
17
18
  $class->$orig( %args );
19
};
20
21
sub translate {
22
  my ($self, $variables) = @_; 
23
  
24
  my $res = '';
25
  for my $elem (@{ $self->pattern_element }) {
26
    $res .= $elem->translate( $variables );
27
  }
28
29
  return $res;
30
}
31
32
1;

+ 12 - 0
lib/Locale/Fluent/Elements/PatternElement.pm

@ -0,0 +1,12 @@
1
package Locale::Fluent::Elements::PatternElement;
2
3
use Moo;
4
extends 'Locale::Fluent::Elements::Base';
5
6
has [qw(inline_text block_text inline_placeable block_placeable)] => (
7
  is  => 'ro',
8
  default => sub { undef },
9
);
10
11
12
1;

+ 63 - 0
lib/Locale/Fluent/Elements/SelectExpression.pm

@ -0,0 +1,63 @@
1
package Locale::Fluent::Elements::SelectExpression;
2
3
use Moo;
4
extends 'Locale::Fluent::Elements::Base';
5
6
has [qw(inline_expression default_variant)] => (
7
  is  => 'ro',
8
  default => sub { undef },
9
);
10
11
has variant_list => (
12
  is => 'ro',
13
  default => sub { bless {}, 'Locale::Fluent::Elements::_variantlist' },
14
);
15
16
around BUILDARGS => sub {
17
  my ($orig, $class, %args) = @_;
18
19
  $args{ inline_expression } = $args{ InlineExpression };
20
  $args{ default_variant}    = delete $args{ variant_list }{ DefaultVariant };
21
22
  $args{ default_variant}->{Identifier}
23
      = $args{ default_variant }->{VariantKey}->{Identifier};
24
  delete $args{default_variant}->{VariantKey};
25
26
  my %list;
27
  if ($args{variant_list}->{variant}) {
28
    for my $variant (@{ $args{variant_list}->{variant} }) {
29
      $variant = Locale::Fluent::Elements->create(
30
          Variant => {
31
            Identifier  => $variant->{VariantKey}->{Identifier},
32
            Pattern     => $variant->{Pattern},
33
          }
34
        );
35
      $list{ $variant->identifier } = $variant;
36
    }
37
  }
38
39
  if (%list) {
40
    $args{ variant_list }
41
      = bless \%list, 'Locale::Fluent::Elements::_variantlist';
42
  }
43
44
  $class->$orig( %args );
45
};
46
47
sub translate {
48
  my ($self, $variables) = @_;
49
50
  my $selector = $self->inline_expression->translate( $variables );
51
  
52
  if (my $var = $self->variant_list->{ $selector }) {
53
    return $var->translate( $variables );
54
  } else {
55
    return $self->default_variant->translate( $variables );
56
  }
57
58
}
59
60
61
package Locale::Fluent::Elements::_variantlist;
62
63
1;

+ 18 - 0
lib/Locale/Fluent/Elements/StringLiteral.pm

@ -0,0 +1,18 @@
1
package Locale::Fluent::Elements::StringLiteral;
2
3
use Moo;
4
extends 'Locale::Fluent::Elements::Base';
5
6
has text => (
7
  is  => 'ro',
8
  default => sub { undef },
9
);
10
11
12
sub translate {
13
  my ($self, $variables) = @_;
14
15
  return $self->text;
16
}
17
18
1;

+ 46 - 0
lib/Locale/Fluent/Elements/Term.pm

@ -0,0 +1,46 @@
1
package Locale::Fluent::Elements::Term;
2
3
use Moo;
4
extends 'Locale::Fluent::Elements::Base';
5
6
has [qw(identifier pattern attributes)] => (
7
  is  => 'ro',
8
  default => sub { undef },
9
);
10
11
around BUILDARGS => sub {
12
  my ($orig, $class, %args) = @_;
13
14
  $args{ identifier } = delete $args{ Identifier };
15
  $args{ pattern }    = delete $args{ Pattern };
16
  $args{ attributes}  = delete $args{ Attribute };
17
  $args{ attributes } = [ $args{ attributes } ]
18
    unless ref $args{ attributes } eq 'ARRAY';
19
20
  $args{ attributes }  = [ map { { Attribute => $_ } }
21
                              @{ $args{ attributes } }
22
                         ];
23
24
  $class->$orig( %args );
25
};
26
27
sub translate {
28
  my ($self, $variables) = @_;
29
30
  return $self->pattern->translate( $variables );
31
}
32
33
sub get_attribute_resource {
34
  my ($self, $attr_id) = @_;
35
36
  for my $attr ( @{ $self->attributes } ) {
37
    return $attr
38
      if $attr->identifier eq $attr_id;
39
40
  }
41
42
  return;
43
}
44
45
46
1;

+ 48 - 0
lib/Locale/Fluent/Elements/TermReference.pm

@ -0,0 +1,48 @@
1
package Locale::Fluent::Elements::TermReference;
2
3
use Moo;
4
extends 'Locale::Fluent::Elements::Base';
5
6
has [qw(
7
      identifier
8
      attribute_accessor
9
      call_arguments
10
    )] => (
11
  is  => 'ro',
12
  default => sub { undef },
13
);
14
15
around BUILDARGS => sub {
16
  my ($orig, $class, %args) = @_;
17
18
  $args{identifier}         = delete $args{ Identifier };
19
  $args{attribute_accessor} = delete $args{ AttributeAccessor };
20
  $args{call_arguments}     = delete $args{ CallArguments };
21
22
  $class->$orig( %args );
23
};
24
25
sub translate {
26
  my ($self, $variables) = @_;
27
28
  my $res = $variables->{__resourceset}->get_term( $self->identifier );
29
  return unless $res;
30
31
  if ($self->attribute_accessor) {
32
    $res = $res->get_attribute_resource(
33
                $self->attribute_accessor->identifier
34
              );
35
  }
36
37
  return unless $res;
38
39
  my $vars = $self->call_arguments
40
    ? { %{ $self->call_arguments->to_variables }, 
41
           __resourceset => $variables->{__resourceset}
42
      }
43
    : $variables;
44
45
  return $res->translate( $vars );
46
}
47
48
1;

+ 18 - 0
lib/Locale/Fluent/Elements/Text.pm

@ -0,0 +1,18 @@
1
package Locale::Fluent::Elements::Text;
2
3
use Moo;
4
extends 'Locale::Fluent::Elements::Base';
5
6
has text => (
7
  is  => 'ro',
8
  default => sub { undef },
9
);
10
11
12
sub translate {
13
  my ($self, $variables) = @_;
14
15
  return $self->text;
16
}
17
18
1;

+ 20 - 0
lib/Locale/Fluent/Elements/VariableReference.pm

@ -0,0 +1,20 @@
1
package Locale::Fluent::Elements::VariableReference;
2
3
use Moo;
4
extends 'Locale::Fluent::Elements::Base';
5
6
has identifier => (
7
  is  => 'ro',
8
  default => sub { undef },
9
);
10
11
sub translate {
12
  my ($self, $variables) = @_;
13
14
#  use Data::Dumper;
15
#  print STDERR Dumper($self, $variables);
16
17
  return $variables->{ $self->identifier };
18
}
19
20
1;

+ 29 - 0
lib/Locale/Fluent/Elements/Variant.pm

@ -0,0 +1,29 @@
1
package Locale::Fluent::Elements::Variant;
2
3
use Moo;
4
extends 'Locale::Fluent::Elements::Base';
5
6
has [qw(
7
      identifier
8
      pattern
9
    )] => (
10
  is  => 'ro',
11
  default => sub { undef },
12
);
13
14
around BUILDARGS => sub {
15
  my ($orig, $class, %args) = @_;
16
17
  $args{identifier}   = delete $args{ Identifier };
18
  $args{pattern}      = delete $args{ Pattern };
19
20
  $class->$orig( %args );
21
};
22
23
sub translate {
24
  my ($self, $variables) = @_;
25
26
  return $self->pattern->translate( $variables );
27
}
28
29
1;

+ 40 - 9
lib/Locale/Fluent/Parser.pm

@ -4,8 +4,12 @@ use parent qw'Exporter';
4 4
5 5
our @EXPORT_OK = qw(
6 6
  parse_file
7
  parse_string
7 8
);
8 9
10
use Locale::Fluent::ResourceSet;
11
use Locale::Fluent::Elements;
12
9 13
use Regexp::Grammars;
10 14
11 15
my $fluent_parser = qr<
@ -93,7 +97,10 @@ my $fluent_parser = qr<
93 97
    \[ <.blank>? ( <NumberLiteral> | <Identifier> ) <.blank>? \]
94 98
95 99
  <token: StringLiteral>
96
    \" <.quoted_char>* \"
100
    <.quote> <text> <.quote>
101
102
  <token: text>
103
    <.quoted_char>*
97 104
98 105
  <token: NumberLiteral>
99 106
    \-? [0-9]+ (\. [0-9]+)?
@ -106,7 +113,7 @@ my $fluent_parser = qr<
106 113
    <.blank>? \( <.blank>? <argument_list> <.blank>? \)
107 114
108 115
  <token: argument_list>
109
    (<Argument> <.blank>? , <.blank>? )* <Argument>?
116
    (<[Argument]> <.blank>? , <.blank>? )* <[Argument]>?
110 117
111 118
  <token: Argument>
112 119
    <NamedArgument> | <InlineExpression>
@ -156,9 +163,10 @@ my $fluent_parser = qr<
156 163
  <token: blank>
157 164
    (<.ws> | <.line_end>)+
158 165
159
>sx;
166
  <token: quote>
167
    \"
160 168
161
print $fluent_parser,"\n\n";
169
>sx;
162 170
163 171
sub parse_file {
164 172
  my ($fname) = @_;
@ -167,13 +175,36 @@ sub parse_file {
167 175
168 176
  open my $fh, '<', $fname or die "Error opening file '$fname': $!\n";
169 177
170
  print STDERR "Going to read it\n";
171 178
  my $text = <$fh>;
172 179
173
  print STDERR "Going to parse it\n";
180
  return parse_string( $text );
181
182
}
183
184
sub parse_string {
185
  my ($str) = @_;
186
187
  my $reset = Locale::Fluent::ResourceSet->new();
174 188
175
  if ( $text =~ $fluent_parser ) {
176
    use Data::Dumper;
177
    print Dumper( \%/ );
189
  if ( $str =~ $fluent_parser ) {
190
    my %entries;
191
192
    for my $elm (@{ $/{Resource} } ) {
193
      next unless $elm->{Entry};
194
195
      my ($type) = keys %{ $elm->{Entry} };
196
197
      my $resobj = Locale::Fluent::Elements->create(
198
          $type => $elm->{Entry}->{ $type }
199
        );
200
201
      $reset->add_resource( $resobj );
202
203
    } 
178 204
  }
205
206
  $reset = undef
207
    unless keys %{ $reset->resources };
208
209
  return $reset;
179 210
}

+ 38 - 0
lib/Locale/Fluent/ResourceSet.pm

@ -0,0 +1,38 @@
1
package Locale::Fluent::ResourceSet;
2
3
use Moo;
4
5
has resources => (
6
  is  => 'rw',
7
  default => sub { {} },
8
);
9
10
sub add_resource {
11
  my ($self, $resource) = @_;
12
13
  $self->resources->{ $resource->identifier } = $resource;
14
15
}
16
17
sub translate {
18
  my ($self, $res_id, $variables) = @_;
19
20
  my $res = $self->resources->{ $res_id };
21
22
  return unless $res and $res->isa("Locale::Fluent::Elements::Message");
23
24
  return $res->translate( { %{$variables//{}}, __resourceset => $self} );
25
}
26
27
sub get_term {
28
  my ($self, $term_id) = @_;
29
30
  my $res = $self->resources->{ $term_id };
31
  return unless $res->isa("Locale::Fluent::Elements::Term");
32
33
  return $res;
34
}
35
36
1;
37
38

+ 46 - 0
t/40-parse-file.t

@ -0,0 +1,46 @@
1
#!perl -T
2
use 5.006;
3
use strict;
4
use warnings;
5
use Test::More;
6
7
8
BEGIN {
9
    use_ok( 'Locale::Fluent' ) || print "Bail out!\n";
10
}
11
12
my $path = $0;
13
14
# test with a missing file, to see if it dies.
15
16
$path =~ s{t/40-parse-file.t}{test_files/basic-missing.flt};
17
eval {
18
  my $resource_set = Locale::Fluent::Parser::parse_file( $path );
19
  
20
  fail("should have died with a missing file");
21
  1;
22
} or do {
23
  ok("died when tried to read a missing file");
24
25
};
26
27
$path =~ s{\-missing}{};
28
29
my $resource_set = Locale::Fluent::Parser::parse_file( $path );
30
ok( $resource_set, "Defined resource_set");
31
32
BAIL_OUT("Undefined resource_set")
33
  unless $resource_set;
34
35
isa_ok( $resource_set, "Locale::Fluent::ResourceSet");
36
37
38
$path =~ s{basic.flt}{empty.flt};
39
my $empty_set = Locale::Fluent::Parser::parse_file( $path );
40
is($empty_set, undef, 'file with no definitions should return undef');
41
42
$path =~ s{empty.flt}{broken.flt};
43
my $broken_set = Locale::Fluent::Parser::parse_file( $path );
44
is($broken_set, undef, 'file with only broken definitions should return undef');
45
46
done_testing();

+ 47 - 0
t/45-resource-set.t

@ -0,0 +1,47 @@
1
#!perl -T
2
use 5.006;
3
use strict;
4
use warnings;
5
use Test::More;
6
7
8
BEGIN {
9
    use_ok( 'Locale::Fluent' ) || print "Bail out!\n";
10
}
11
12
my $path = $0;
13
$path =~ s{t/.*.t}{test_files/basic.flt};
14
15
my $resource_set = Locale::Fluent::Parser::parse_file( $path );
16
ok( $resource_set, "Defined resource_set");
17
18
BAIL_OUT("Undefined resource_set")
19
  unless $resource_set;
20
21
isa_ok( $resource_set, "Locale::Fluent::ResourceSet");
22
23
my $fullname = $resource_set->translate("fullname");
24
is( $fullname, 'theMage Merlin mage dude', "Got a proper fullname");
25
26
my $pi = $resource_set->translate('math-pi');
27
is( $pi, '3.1415', "We have got pi: $pi");
28
29
my $piv = $resource_set->translate('math-value-of-pi', {});
30
is( $piv, 'The value of constant pi is 3.1415', "We have got it: [$piv]");
31
32
my $no_message = $resource_set->translate('math-constant',
33
                  {name => 'pi', value => '42'}
34
                );
35
is( $no_message, undef, 'should not get a translation of a term');
36
37
my $missing = $resource_set->translate('this-is-no-resource-at-all');
38
is( $missing, undef, 'should not get a translation of a missing resource');
39
40
41
my $term    = $resource_set->get_term('math-constant');
42
isa_ok( $term, "Locale::Fluent::Elements::Term");
43
44
my $no_term = $resource_set->get_term('math-value-of-pi');
45
is($no_term, undef, 'should not get a message when asking for a term');
46
47
done_testing();

+ 0 - 24
t/pod-coverage.t

@ -1,24 +0,0 @@
1
#!perl -T
2
use 5.006;
3
use strict;
4
use warnings;
5
use Test::More;
6
7
unless ( $ENV{RELEASE_TESTING} ) {
8
    plan( skip_all => "Author tests not required for installation" );
9
}
10
11
# Ensure a recent version of Test::Pod::Coverage
12
my $min_tpc = 1.08;
13
eval "use Test::Pod::Coverage $min_tpc";
14
plan skip_all => "Test::Pod::Coverage $min_tpc required for testing POD coverage"
15
    if $@;
16
17
# Test::Pod::Coverage doesn't require a minimum Pod::Coverage version,
18
# but older versions don't recognize some common documentation styles
19
my $min_pc = 0.18;
20
eval "use Pod::Coverage $min_pc";
21
plan skip_all => "Pod::Coverage $min_pc required for testing POD coverage"
22
    if $@;
23
24
all_pod_coverage_ok();

+ 0 - 16
t/pod.t

@ -1,16 +0,0 @@
1
#!perl -T
2
use 5.006;
3
use strict;
4
use warnings;
5
use Test::More;
6
7
unless ( $ENV{RELEASE_TESTING} ) {
8
    plan( skip_all => "Author tests not required for installation" );
9
}
10
11
# Ensure a recent version of Test::Pod
12
my $min_tp = 1.22;
13
eval "use Test::Pod $min_tp";
14
plan skip_all => "Test::Pod $min_tp required for testing POD" if $@;
15
16
all_pod_files_ok();

+ 6 - 1
test_files/basic.flt

@ -39,11 +39,16 @@ As anything else" }
39 39
40 40
math-pi = { 3.1415 }
41 41
42
-math-constant = The value of constant { $name } is { $value }
43
44
math-value-of-pi = { -math-constant( name: "pi", value: 3.1415 ) }
45
42 46
today = { DATE( "today", tz: "utc" ) }
43 47
44 48
tha_day = { today } is tha day!
45 49
46
fullname = { -name } { -name.surname } { -name.gender(role: "formal") }
50
fullname = { -name } { -name.surname } { -name.gender(role: "formal", "test" ) } { -name.gender( role: "obnoxious" ) }
47 51
48 52
role = { $role }
49 53
54

+ 4 - 0
test_files/broken.flt

@ -0,0 +1,4 @@
1
# this is a broken fluent file for tests
2
3
# a named arg can't be an inline expression
4
test = { some.expression(value: { some.value }) }

+ 3 - 0
test_files/empty.flt

@ -0,0 +1,3 @@
1
# this files just have some comments, but no more!
2
# Just to test the code
3