legacy2luatest.pl 7.37 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
#!/usr/bin/env perl

use strict;
use warnings;
use 5.010;
use autodie;

use File::Basename;
use File::Spec::Functions;

sub read_in_file {
  my $in_file = $_[0];

  # Will contain lines before first STARTTEST
  # as Lua comments.
  my @description_lines = ();

  # Will contain alternating blocks of lines of textual input
  # (text between ENDTEST and EOF/next STARTTEST) and test commands
  # (commands between STARTTEST and ENDTEST) as Lua code.
  my @test_body_lines = ();

  # Will contain current command block, i.e. lines
  # between STARTTEST and ENDTEST.
  my @command_lines = ();

  # Will contain current input block, i.e. lines
  # between ENDTEST and STARTTEST.
  my @input_lines = ();

  open my $in_file_handle, '<', $in_file;

  use constant EMIT_DESCRIPTION => 0;
  use constant EMIT_COMMAND => 1;
  use constant EMIT_INPUT => 2;
36 37 38 39 40 41 42 43 44

  # Push lines from current input and
  # command blocks into @test_body_lines
  # in the correct order.
  sub end_input {
    my $input_lines = $_[0];
    my $command_lines = $_[1];
    my $test_body_lines = $_[2];

45 46
    # If there are input lines, wrap with an `insert`
    # command and add before the previous command block.
47 48 49
    if (@{$input_lines}) {
      my $last_input_line = pop @{$input_lines};
      unshift @{$command_lines}, '';
50
      unshift @{$command_lines}, $last_input_line . ']=])';
51
      unshift @{$command_lines}, @{$input_lines};
52
      unshift @{$command_lines}, "insert([=[";
53 54 55

      @{$input_lines} = ();
    }
56 57 58 59

    # Output remaining command lines.
    push @{$test_body_lines}, @{$command_lines};
    @{$command_lines} = ();
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
  }

  sub format_comment {
    # Handle empty comments.
    if (/^$/) {
      return '';
    }

    # Capitalize first character and emit as Lua comment.
    my $comment = '-- ' . ucfirst $_;

    # Add trailing dot if not already there.
    $comment .= '.' unless $comment =~ /\.$/;

    return $comment;
  }
76 77 78 79 80 81 82 83 84 85

  my %states = (
    # Add test description to @description_lines.
    EMIT_DESCRIPTION() => sub {
      if (/^STARTTEST/) {
        return EMIT_COMMAND;
      }

      # If not an empty line, emit as Lua comment.
      if (!/^$/) {
86 87 88 89
        # Remove modeline
        s/vim:.*set f\w+=vim//g;
        # Remove trailing ":"
        s/\s*:\s*$//g;
90 91 92 93 94 95 96 97 98 99 100 101 102
        push @description_lines, '-- ' . $_;
      }

      return EMIT_DESCRIPTION;
    },
    # Add test commands to @command_lines.
    EMIT_COMMAND() => sub {
      if (/^ENDTEST/) {
        return EMIT_INPUT;
      }

      # If line starts with ':"', emit a comment.
      if (/^:"/) {
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
        # Remove Vim comment prefix.
        s/^:"\s*//;

        push @command_lines, format_comment $_;

        return EMIT_COMMAND;
      }

      # Extract possible inline comment.
      if (/^[^"]*"[^"]*$/) {
        # Remove command part and prepended whitespace.
        s/^(.*?)\s*"\s*//;

        push @command_lines, format_comment $_;

        # Set implicit variable to command without comment.
        $_ = $1;
      }

      # Only continue if remaining command is not empty.
      if (!/^:?\s*$/) {
        # Replace terminal escape characters with <esc>.
        s/\e/<esc>/g;

        my $startstr = "'";
        my $endstr = "'";

        # If line contains single quotes or backslashes, use double
        # square brackets to wrap string.
        if (/'/ || /\\/) {
133 134 135 136 137 138 139 140 141
            # If the line contains a closing square bracket,
            # wrap it with [=[...]=].
            if (/\]/) {
              $startstr = '[=[';
              $endstr = ']=]';
            } else {
              $startstr = '[[';
              $endstr = ']]';
            }
142 143
        }

144 145 146 147 148 149 150 151 152 153 154 155
        # Emit 'feed' if not a search ('/') or ex (':') command.
        if (!/^\// && !/^:/) {
          # If command does not end with <esc>, insert trailing <cr>.
          my $command = 'feed(' . $startstr . $_;
          $command .= '<cr>' unless /<esc>$/;
          $command .= $endstr . ')';

          push @command_lines, $command;
        } else {
          # Remove prepending ':'.
          s/^://;
          push @command_lines, 'execute(' . $startstr . $_ . $endstr . ')';
156 157 158 159 160 161 162 163
        }
      }

      return EMIT_COMMAND;
    },
    # Add input to @input_lines.
    EMIT_INPUT() => sub {
      if (/^STARTTEST/) {
164 165
        end_input \@input_lines, \@command_lines, \@test_body_lines;
        return EMIT_COMMAND;
166 167
      }

168 169 170 171
      # Skip initial lines if they are empty.
      if (@input_lines or !/^$/) {
        push @input_lines, '  ' . $_;
      }
172 173 174 175 176 177 178 179 180 181 182 183
      return EMIT_INPUT;
    },
  );

  my $state = EMIT_DESCRIPTION;

  while (<$in_file_handle>) {
    # Remove trailing newline character and process line.
    chomp;
    $state = $states{$state}->($_);
  }

184
  # If not all lines have been processed yet,
185
  # do it now.
186
  end_input \@input_lines, \@command_lines, \@test_body_lines;
187 188 189 190 191 192 193 194 195 196 197 198 199

  close $in_file_handle;

  return (\@description_lines, \@test_body_lines);
}

sub read_ok_file {
  my $ok_file = $_[0];
  my @assertions = ();

  if (-f $ok_file) {
    push @assertions, '';
    push @assertions, "-- Assert buffer contents.";
200
    push @assertions, "expect([=[";
201 202 203 204 205 206 207 208 209 210 211

    open my $ok_file_handle, '<', $ok_file;

    while (<$ok_file_handle>) {
      # Remove trailing newline character and process line.
      chomp;
      push @assertions, '  ' . $_;
    }

    close $ok_file_handle;

212
    $assertions[-1] .= "]=])";
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234
  }

  return \@assertions;
}

my $legacy_testfile = $ARGV[0];
my $out_dir = $ARGV[1];

if ($#ARGV != 1) {
  say "Convert a legacy Vim test to a Neovim lua spec.";
  say '';
  say "Usage: $0 legacy-testfile output-directory";
  say '';
  say "legacy-testfile:  Path to .in or .ok file.";
  say "output-directory: Directory where Lua spec will be saved to.";
  say '';
  say "Note: Only works reliably for fairly simple tests.";
  say "      Manual adjustments to generated spec files are required.";
  exit 1;
}

my @legacy_suffixes = ('.in', '.ok');
235 236 237 238 239 240 241
my ($base_name, $base_path, $suffix) = fileparse($legacy_testfile, @legacy_suffixes);
my $in_file = catfile($base_path, $base_name . '.in');
my $ok_file = catfile($base_path, $base_name . '.ok');

# Remove leading 'test'.
my $test_name = $base_name;
$test_name =~ s/^test_?//;
242

243
my $spec_file = do {
244 245
  if ($test_name =~ /^([0-9]+)/) {
    catfile($out_dir,  sprintf('%03d', $1) . '_spec.lua')
246 247 248 249
  } else {
    catfile($out_dir,  $test_name . '_spec.lua')
  }
};
250 251 252 253 254 255 256 257 258 259 260 261 262

if (! -f $in_file) {
  say "Test input file $in_file not found.";
  exit 2;
}

if (! -d $out_dir) {
  say "Output directory $out_dir does not exist.";
  exit 3;
}

if (-f $spec_file) {
  say "Output file $spec_file already exists.";
263 264 265 266 267 268 269
  print "Overwrite (Y/n)? ";
  my $input = <STDIN>;
  chomp($input);
  unless ($input =~ /^y|Y/) {
    say "Aborting.";
    exit 4;
  }
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285
}

# Read .in and .ok files.
my ($description_lines, $test_body_lines) = read_in_file $in_file;
my $assertion_lines = read_ok_file $ok_file;

# Append assertions to test body.
push @{$test_body_lines}, @{$assertion_lines} if @{$assertion_lines};

# Write spec file.
open my $spec_file_handle, ">", $spec_file;

print $spec_file_handle <<"EOS";
@{[join "\n", @{$description_lines}]}

local helpers = require('test.functional.helpers')
286 287
local feed, insert, source = helpers.feed, helpers.insert, helpers.source
local clear, execute, expect = helpers.clear, helpers.execute, helpers.expect
288 289

describe('$test_name', function()
290
  before_each(clear)
291 292 293 294 295 296 297 298

  it('is working', function()
@{[join "\n", map { /^$/ ? '' : '    ' . $_ } @{$test_body_lines}]}
  end)
end)
EOS

close $spec_file_handle;
299 300

say "Written to $spec_file."