11# frozen_string_literal: true
22
3+ require_relative "markdown_table_parser"
4+
35module ImportmapUpdate
46 module Parsers
5- # Parses the text output of `bin/importmap outdated`.
6- #
7- # Expected format (see importmap-rails lib/importmap/commands.rb#outdated):
8- #
9- # | Package | Current | Latest |
10- # |---------|---------|--------|
11- # | lodash | 4.17.20 | 4.17.21 |
12- # 1 outdated package found
13- #
14- # When no outdated packages exist, the command prints only:
15- #
16- # No outdated packages found
17- #
18- # The "Latest" column can also contain an error string (e.g. an HTTP
19- # status from a failed lookup) when latest_version is nil on the
20- # underlying OutdatedPackage. Those rows are returned with `error: ...`
21- # set and `latest: nil`, so callers can decide whether to skip them.
227 class OutdatedParser
23- OutdatedPackage = Data . define ( :name , :current , :latest , :error ) do
8+ VERSION_SHAPE_RE = /\A v?\d +\. \d +\. \d +(?:[-+][0-9A-Za-z.-]+)?\z /
9+
10+ OutdatedPackage = Data . define ( :name , :current , :latest_or_error ) do
11+ def latest
12+ return nil unless VERSION_SHAPE_RE . match? ( latest_or_error )
13+
14+ latest_or_error
15+ end
16+
17+ def error
18+ return nil if VERSION_SHAPE_RE . match? ( latest_or_error )
19+
20+ latest_or_error
21+ end
22+
2423 def parseable?
2524 !latest . nil?
2625 end
2726 end
2827
29- EMPTY_MESSAGE = "No outdated packages found"
30- DIVIDER_RE = /\A \| [-|]+\| \z /
31- # Cheap shape check for "looks like a version" — we don't need full
32- # SemVer parsing here, just enough to decide "is this a version or
33- # an error message?". Pre-release tags and `v` prefixes are allowed.
34- VERSION_SHAPE_RE = /\A v?\d +\. \d +\. \d +(?:[-+][0-9A-Za-z.-]+)?\z /
35-
3628 def self . parse ( output )
3729 new ( output ) . parse
3830 end
@@ -42,37 +34,16 @@ def initialize(output)
4234 end
4335
4436 def parse
45- lines = @output . each_line . map ( &:chomp )
46- return [ ] if lines . any? { |l | l . strip == EMPTY_MESSAGE }
47-
48- header_idx = lines . index { |l | l . start_with? ( "|" ) && l . include? ( "Package" ) }
49- return [ ] unless header_idx
50-
51- rows = [ ]
52- lines [ ( header_idx + 1 ) ..] . each do |line |
53- break unless line . start_with? ( "|" )
54- next if DIVIDER_RE . match? ( line )
55- cells = split_row ( line )
56- next unless cells . size >= 3
57- rows << build_row ( cells )
37+ table = MarkdownTableParser . parse ( @output )
38+ return [ ] if table . empty?
39+
40+ table . map do |row |
41+ OutdatedPackage . new (
42+ name : row [ :package ] ,
43+ current : row [ :current ] ,
44+ latest_or_error : row [ :latest ]
45+ )
5846 end
59- rows
60- end
61-
62- private
63-
64- # `| a | b | c |` → ["a", "b", "c"]
65- # We drop the empty strings produced by the leading and trailing pipes.
66- def split_row ( line )
67- line . split ( "|" ) . map ( &:strip ) . reject ( &:empty? )
68- end
69-
70- def build_row ( cells )
71- name , current , latest_or_error = cells
72- latest = latest_or_error if VERSION_SHAPE_RE . match? ( latest_or_error )
73- error = latest_or_error unless VERSION_SHAPE_RE . match? ( latest_or_error )
74-
75- OutdatedPackage . new ( name :, current :, latest :, error :)
7647 end
7748 end
7849 end
0 commit comments