App-makefilepl2cpanfile

 view release on metacpan or  search on metacpan

lib/App/makefilepl2cpanfile.pm  view on Meta::CPAN

	Env ≙ [ fs : PATH ⇸ seq BYTE; home : PATH ∪ {⊥} ]
	cfg(E) == E.home ⁀ "/.config/makefilepl2cpanfile.yml"

=head2 parse_prereqs

	parse_prereqs : (Str ∪ {⊥}) -> DepMap

	parse_prereqs(s) ==
	  if s = ⊥ ∨ is_ref(s) then ∅
	  else simple(s') ⊕ₗ structured(s') ⊕ₗ legacy(s')
	  where s' == s with every '#' comment removed

	simple(s)     == ⋃ { {PHASE_MAP(k) ↦ {requires ↦ pairs(b)}}
	                     | k ∈ {PREREQ_PM, BUILD_REQUIRES, TEST_REQUIRES, CONFIGURE_REQUIRES},
	                       b ∈ blocks(k, s) }
	structured(s) == ⋃ { {p ↦ {r ↦ pairs(b)}}
	                     | P ∈ blocks(prereqs, s), (p ↦ (r ↦ b)) ∈ P, p ∈ Phase, r ∈ Rel }
	legacy(s)     == ⋃ { {first(LEGACY(k)) ↦ {second(LEGACY(k)) ↦ pairs(b)}}
	                     | k ∈ dom LEGACY, b ∈ blocks(k, s),
	                       ¬ (∃ P ∈ blocks(prereqs, s) • b ⊆ P) }
	pairs(b)      == { m ↦ ⟨ if v ∈ Requirement then v else 0, comment(m, b) ⟩
	                     | ('m' => v) ∈ b, m ∈ ModName }
	-- comment(m, b): the line's comment if m is the last valid entry on it

	post  ∀ p ↦ R ∈ result • R ≠ ∅ ∧ (∀ r ↦ M ∈ R • M ≠ ∅)
	      ∧ no I/O ∧ no warnings

=head2 generate

	Args ≙ [ content : Str ∪ {⊥}; makefile : Str; existing : Str; with_develop : 𝔹 ]

	generate : Args × Env ⇸ Str

	pre   (a.content ≠ ⊥ ∨ string(a.makefile) ∈ dom E.fs ∧ regular(a.makefile) ∧ readable(a.makefile))
	      ∧ (a.with_develop ⇒ cfg(E) ∉ dom E.fs ∨ ¬ regular(cfg(E)) ∨ yaml_ok(cfg(E)))

	generate(a, E) ==
	  let content == if a.content ≠ ⊥ then a.content else decode_utf8_or_raw(E.fs(a.makefile))
	      deps    == parse_prereqs(content)
	      kept    == { r ↦ { m ↦ ⟨ if v ∈ Requirement then v else 0, ⊥ ⟩ }
	                   | (r m v) ∈ develop_entries(a.existing) \ comments, m ∈ ModName }
	      dev     == deps ⊕ₗ {develop ↦ kept}
	      listed  == ⋃ { dom(dev(develop)(r)) | r ∈ Rel }
	      tools   == if E.home = ⊥ ∨ E.home = "" ∨ ¬ regular(cfg(E)) then DEFAULT_DEVELOP
	                 else valid_entries(yaml(cfg(E)).develop)
	      final   == if a.with_develop
	                 then dev ⊕ₗ {develop ↦ {requires ↦ { m ↦ ⟨v, ⊥⟩ | (m ↦ v) ∈ tools, m ∉ listed }}}
	                 else dev
	  in  emit(final, min_perl(content))

	post  result ∈ Str
	      ∧ prefix(result, HEADER ⁀ "\n") ∧ last(result) = '\n' ∧ ¬ suffix(result, "\n\n")
	      ∧ E′ = E                           -- nothing is written
	      ∧ $@′ = $@ ∧ $!′ = $! ∧ $_′ = $_
	      -- every value from a file or the environment in a message is
	      -- printable(v): control and bidi characters as \x{..}

	-- Failure cases (the function dies):
	a.content = ⊥ ∧ (¬ regular(a.makefile) ∨ ¬ readable(a.makefile))  ⇒  croak("Cannot read '" ⁀ a.makefile ⁀ "'")
	a.with_develop ∧ regular(cfg(E)) ∧ ¬ yaml_ok(cfg(E))  ⇒  croak("Failed to parse " ⁀ cfg(E) ⁀ ": " ⁀ err)
	a.with_develop ∧ stat(cfg(E)) fails with errno ∉ {ENOENT, ENOTDIR}  ⇒  croak("Failed to parse " ⁀ cfg(E) ⁀ ": " ⁀ errno)

=head1 STATE DIAGRAM

The module keeps no state between calls: every call starts at START and
ends at RETURN or at an error.  The diagram shows the states that one
call to C<generate()> passes through.  C<parse_prereqs()> is the single
step PARSE.

	+-------+
	| START |  generate(%args) is called
	+-------+
	    |
	    | makefile is a readable regular file?
	    |---- no ------------------------------------> [DIE] croak "Cannot read '...'"
	    | yes
	    v
	+------------+  read as UTF-8
	| READ_UTF8  |---- decode error --> +-----------+  carp "invalid UTF-8"
	+------------+                      | READ_RAW  |  (read the raw bytes)
	    |     \                         +-----------+
	    |      \                             |---- raw read fails --> [DIE] error passed on
	    |       \---- other I/O error --------------------------> [DIE] error passed on
	    | ok                                 |
	    v                                    |
	+------------+ <-------------------------+
	|   PARSE    |  parse_prereqs(content); find MIN_PERL_VERSION
	+------------+  (pure: no I/O, no warnings)
	    |
	    | existing has an on 'develop' section?
	    |---- no ----------------------------+
	    | yes                                |
	    v                                    |
	+------------+  copy entries; drop       |
	|   MERGE    |  bad names; carp and      |
	+------------+  drop bad versions        |
	    |                                    |
	    +<-----------------------------------+
	    |
	    | with_develop true?
	    |---- no ----------------------------------------------+
	    | yes                                                  |
	    v                                                      |
	+------------+  no home dir, or config path missing        |
	| CONFIG     |  or not a regular file -------> DEFAULTS    |
	+------------+                                   |         |
	    | config is a regular file                   |         |
	    |---- stat/read/YAML error --> [DIE] croak "Failed to parse ..."
	    v                                            |         |
	+------------+  no develop: key --> carp --> DEFAULTS      |
	| VALIDATE   |  bad name    --> carp, skip entry           |
	+------------+  bad version --> carp, version 0            |
	    |                                            |         |
	    v                                            v         |
	+------------+ <---------------------------------+         |
	|   INJECT   |  add tools not already in develop           |
	+------------+                                             |
	    |                                                      |
	    v                                                      |
	+------------+ <-------------------------------------------+
	|    EMIT    |  format the text (sorted, fixed order)



( run in 0.478 second using v1.01-cache-2.11-cpan-28bccf0f6e5 )