<commit_before>package Mojolicious::Plugin::TagHelpers;
use Mojo::Base 'Mojolicious::Plugin';

use Mojo::ByteStream;
use Mojo::DOM::HTML;
use Scalar::Util 'blessed';

sub register {
  my ($self, $app) = @_;

  # Text field variations
  my @time = qw(date month time week);
  for my $name (@time, qw(color email number range search tel text url)) {
    $app->helper("${name}_field" => sub { _input(@_, type => $name) });
  }
  $app->helper(datetime_field => sub { _input(@_, type => 'datetime-local') });

  my @helpers = (
    qw(csrf_field form_for hidden_field javascript label_for link_to),
    qw(select_field stylesheet submit_button tag_with_error text_area)
  );
  $app->helper($_ => __PACKAGE__->can("_$_")) for @helpers;

  $app->helper(check_box => sub { _input(@_, type => 'checkbox') });
  $app->helper(file_field => sub { _empty_field('file', @_) });
  $app->helper(image => sub { _tag('img', src => shift->url_for(shift), @_) });
  $app->helper(input_tag      => sub { _input(@_) });
  $app->helper(password_field => sub { _empty_field('password', @_) });
  $app->helper(radio_button   => sub { _input(@_, type => 'radio') });

  # "t" is just a shortcut for the "tag" helper
  $app->helper($_ => sub { shift; _tag(@_) }) for qw(t tag);
}

sub _csrf_field {
  my $c = shift;
  return _hidden_field($c, csrf_token => $c->helpers->csrf_token, @_);
}

sub _empty_field {
  my ($type, $c, $name) = (shift, shift, shift);
  return _validation($c, $name, 'input', name => $name, @_, type => $type);
}

sub _form_for {
  my ($c, @url) = (shift, shift);
  push @url, shift if ref $_[0] eq 'HASH';

  # Method detection
  my $r      = $c->app->routes->lookup($url[0]);
  my $method = $r ? $r->suggested_method : 'GET';
  my @post   = $method ne 'GET' ? (method => 'POST') : ();

  my $url = $c->url_for(@url);
  $url->query({_method => $method}) if @post && $method ne 'POST';
  return _tag('form', action => $url, @post, @_);
}

sub _hidden_field {
  my $c = shift;
  return _tag('input', name => shift, value => shift, @_, type => 'hidden');
}

sub _input {
  my ($c, $name) = (shift, shift);
  my %attrs = @_ % 2 ? (value => shift, @_) : @_;

  # Special selection value
  my @values = @{$c->every_param($name)};
  my $type = $attrs{type} || '';
  if (@values && $type ne 'submit') {

    # Checkbox or radiobutton
    if ($type eq 'checkbox' || $type eq 'radio') {
      delete $attrs{checked} if @values;
      my $value = $attrs{value} // 'on';
      $attrs{checked} = undef if grep { $_ eq $value } @values;
    }

    # Others
    else { $attrs{value} = $values[-1] }
  }

  return _validation($c, $name, 'input', name => $name, %attrs);
}

sub _javascript {
  my $c = shift;
  my $content
    = ref $_[-1] eq 'CODE' ? "//<![CDATA[\n" . pop->() . "\n//]]>" : '';
  my @src = @_ % 2 ? (src => $c->url_for(shift)) : ();
  return _tag('script', @src, @_, sub {$content});
}

sub _label_for {
  my ($c, $name) = (shift, shift);
  my $content = ref $_[-1] eq 'CODE' ? pop : shift;
  return _validation($c, $name, 'label', for => $name, @_, $content);
}

sub _link_to {
  my ($c, $content) = (shift, shift);
  my @url = ($content);

  # Content
  unless (ref $_[-1] eq 'CODE') {
    @url = (shift);
    push @_, $content;
  }

  # Captures
  push @url, shift if ref $_[0] eq 'HASH';

  return _tag('a', href => $c->url_for(@url), @_);
}

sub _option {
  my ($values, $pair) = @_;

  $pair = [$pair => $pair] unless ref $pair eq 'ARRAY';
  my %attrs = (value => $pair->[1], @$pair[2 .. $#$pair]);
  delete $attrs{selected} if keys %$values;
  $attrs{selected} = undef if $values->{$pair->[1]};

  return _tag('option', %attrs, $pair->[0]);
}

sub _select_field {
  my ($c, $name, $options, %attrs) = (shift, shift, shift, @_);

  my %values = map { $_ => 1 } @{$c->every_param($name)};

  my $groups = '';
  for my $group (@$options) {

    # "optgroup" tag
    if (blessed $group && $group->isa('Mojo::Collection')) {
      my ($label, $values, %attrs) = @$group;
      my $content = join '', map { _option(\%values, $_) } @$values;
      $groups .= _tag('optgroup', label => $label, %attrs, sub {$content});
    }

    # "option" tag
    else { $groups .= _option(\%values, $group) }
  }

  return _validation($c, $name, 'select', name => $name, %attrs, sub {$groups});
}

sub _stylesheet {
  my $c = shift;
  my $content
    = ref $_[-1] eq 'CODE' ? "/*<![CDATA[*/\n" . pop->() . "\n/*]]>*/" : '';
  return _tag('style', @_, sub {$content}) unless @_ % 2;
  return _tag('link', rel => 'stylesheet', href => $c->url_for(shift), @_);
}

sub _submit_button {
  my $c = shift;
  return _tag('input', value => shift // 'Ok', @_, type => 'submit');
}

sub _tag {
  my $tree = ['tag', shift, undef, undef];

  # Content
  if (ref $_[-1] eq 'CODE') { push @$tree, ['raw', pop->()] }
  elsif (@_ % 2) { push @$tree, ['text', pop] }

  # Attributes
  my $attrs = $tree->[2] = {@_};
  if ($attrs->{data} && ref $attrs->{data} eq 'HASH') {
    while (my ($key, $value) = each %{$attrs->{data}}) {
      $key =~ y/_/-/;
      $attrs->{lc "data-$key"} = $value;
    }
    delete $attrs->{data};
  }

  return Mojo::ByteStream->new(Mojo::DOM::HTML::_render($tree));
}

sub _tag_with_error {
  my ($c, $tag) = (shift, shift);
  my ($content, %attrs) = (@_ % 2 ? pop : undef, @_);
  $attrs{class} .= $attrs{class} ? ' field-with-error' : 'field-with-error';
  return _tag($tag, %attrs, defined $content ? $content : ());
}

sub _text_area {
  my ($c, $name) = (shift, shift);

  my $cb = ref $_[-1] eq 'CODE' ? pop : undef;
  my $content = @_ % 2 ? shift : undef;
  $content = $c->param($name) // $content // $cb // '';

  return _validation($c, $name, 'textarea', name => $name, @_, $content);
}

sub _validation {
  my ($c, $name) = (shift, shift);
  return _tag(@_) unless $c->validation->has_error($name);
  return $c->helpers->tag_with_error(@_);
}

1;<commit_msg>filter attributes a little more efficiently<commit_after>package Mojolicious::Plugin::TagHelpers;
use Mojo::Base 'Mojolicious::Plugin';

use Mojo::ByteStream;
use Mojo::DOM::HTML;
use Scalar::Util 'blessed';

sub register {
  my ($self, $app) = @_;

  # Text field variations
  my @time = qw(date month time week);
  for my $name (@time, qw(color email number range search tel text url)) {
    $app->helper("${name}_field" => sub { _input(@_, type => $name) });
  }
  $app->helper(datetime_field => sub { _input(@_, type => 'datetime-local') });

  my @helpers = (
    qw(csrf_field form_for hidden_field javascript label_for link_to),
    qw(select_field stylesheet submit_button tag_with_error text_area)
  );
  $app->helper($_ => __PACKAGE__->can("_$_")) for @helpers;

  $app->helper(check_box => sub { _input(@_, type => 'checkbox') });
  $app->helper(file_field => sub { _empty_field('file', @_) });
  $app->helper(image => sub { _tag('img', src => shift->url_for(shift), @_) });
  $app->helper(input_tag      => sub { _input(@_) });
  $app->helper(password_field => sub { _empty_field('password', @_) });
  $app->helper(radio_button   => sub { _input(@_, type => 'radio') });

  # "t" is just a shortcut for the "tag" helper
  $app->helper($_ => sub { shift; _tag(@_) }) for qw(t tag);
}

sub _csrf_field {
  my $c = shift;
  return _hidden_field($c, csrf_token => $c->helpers->csrf_token, @_);
}

sub _empty_field {
  my ($type, $c, $name) = (shift, shift, shift);
  return _validation($c, $name, 'input', name => $name, @_, type => $type);
}

sub _form_for {
  my ($c, @url) = (shift, shift);
  push @url, shift if ref $_[0] eq 'HASH';

  # Method detection
  my $r      = $c->app->routes->lookup($url[0]);
  my $method = $r ? $r->suggested_method : 'GET';
  my @post   = $method ne 'GET' ? (method => 'POST') : ();

  my $url = $c->url_for(@url);
  $url->query({_method => $method}) if @post && $method ne 'POST';
  return _tag('form', action => $url, @post, @_);
}

sub _hidden_field {
  my $c = shift;
  return _tag('input', name => shift, value => shift, @_, type => 'hidden');
}

sub _input {
  my ($c, $name) = (shift, shift);
  my %attrs = @_ % 2 ? (value => shift, @_) : @_;

  # Special selection value
  my @values = @{$c->every_param($name)};
  my $type = $attrs{type} || '';
  if (@values && $type ne 'submit') {

    # Checkbox or radiobutton
    if ($type eq 'checkbox' || $type eq 'radio') {
      delete $attrs{checked} if @values;
      my $value = $attrs{value} // 'on';
      $attrs{checked} = undef if grep { $_ eq $value } @values;
    }

    # Others
    else { $attrs{value} = $values[-1] }
  }

  return _validation($c, $name, 'input', name => $name, %attrs);
}

sub _javascript {
  my $c = shift;
  my $content
    = ref $_[-1] eq 'CODE' ? "//<![CDATA[\n" . pop->() . "\n//]]>" : '';
  my @src = @_ % 2 ? (src => $c->url_for(shift)) : ();
  return _tag('script', @src, @_, sub {$content});
}

sub _label_for {
  my ($c, $name) = (shift, shift);
  my $content = ref $_[-1] eq 'CODE' ? pop : shift;
  return _validation($c, $name, 'label', for => $name, @_, $content);
}

sub _link_to {
  my ($c, $content) = (shift, shift);
  my @url = ($content);

  # Content
  unless (ref $_[-1] eq 'CODE') {
    @url = (shift);
    push @_, $content;
  }

  # Captures
  push @url, shift if ref $_[0] eq 'HASH';

  return _tag('a', href => $c->url_for(@url), @_);
}

sub _option {
  my ($values, $pair) = @_;

  $pair = [$pair => $pair] unless ref $pair eq 'ARRAY';
  my %attrs = (value => $pair->[1], @$pair[2 .. $#$pair]);
  delete $attrs{selected} if keys %$values;
  $attrs{selected} = undef if $values->{$pair->[1]};

  return _tag('option', %attrs, $pair->[0]);
}

sub _select_field {
  my ($c, $name, $options, %attrs) = (shift, shift, shift, @_);

  my %values = map { $_ => 1 } @{$c->every_param($name)};

  my $groups = '';
  for my $group (@$options) {

    # "optgroup" tag
    if (blessed $group && $group->isa('Mojo::Collection')) {
      my ($label, $values, %attrs) = @$group;
      my $content = join '', map { _option(\%values, $_) } @$values;
      $groups .= _tag('optgroup', label => $label, %attrs, sub {$content});
    }

    # "option" tag
    else { $groups .= _option(\%values, $group) }
  }

  return _validation($c, $name, 'select', name => $name, %attrs, sub {$groups});
}

sub _stylesheet {
  my $c = shift;
  my $content
    = ref $_[-1] eq 'CODE' ? "/*<![CDATA[*/\n" . pop->() . "\n/*]]>*/" : '';
  return _tag('style', @_, sub {$content}) unless @_ % 2;
  return _tag('link', rel => 'stylesheet', href => $c->url_for(shift), @_);
}

sub _submit_button {
  my $c = shift;
  return _tag('input', value => shift // 'Ok', @_, type => 'submit');
}

sub _tag {
  my $tree = ['tag', shift, undef, undef];

  # Content
  if (ref $_[-1] eq 'CODE') { push @$tree, ['raw', pop->()] }
  elsif (@_ % 2) { push @$tree, ['text', pop] }

  # Attributes
  my $attrs = $tree->[2] = {@_};
  if (ref $attrs->{data} eq 'HASH' && (my $data = delete $attrs->{data})) {
    @$attrs{map { y/_/-/; lc "data-$_" } keys %$data} = @$data{keys %$data};
  }

  return Mojo::ByteStream->new(Mojo::DOM::HTML::_render($tree));
}

sub _tag_with_error {
  my ($c, $tag) = (shift, shift);
  my ($content, %attrs) = (@_ % 2 ? pop : undef, @_);
  $attrs{class} .= $attrs{class} ? ' field-with-error' : 'field-with-error';
  return _tag($tag, %attrs, defined $content ? $content : ());
}

sub _text_area {
  my ($c, $name) = (shift, shift);

  my $cb = ref $_[-1] eq 'CODE' ? pop : undef;
  my $content = @_ % 2 ? shift : undef;
  $content = $c->param($name) // $content // $cb // '';

  return _validation($c, $name, 'textarea', name => $name, @_, $content);
}

sub _validation {
  my ($c, $name) = (shift, shift);
  return _tag(@_) unless $c->validation->has_error($name);
  return $c->helpers->tag_with_error(@_);
}

1;
{'fraction_non_alphanumeric': 0.22905266757865936, 'fraction_numerical': 0.00393296853625171, 'mean_word_length': 3.454303122619954, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 172, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7374129', 'n_tokens_mistral': 4453, 'n_tokens_neox': 4383, 'n_words': 1160}