
# api: fpm
# title: PHP Phar target
# description: Chains to PHP for creating a .phar (native/tar/zip) archive
# type: package
# category: target
# version: 0.3
# state: very alpha
# license: MITL
# author: mario#include-once:org
# 
# This packaging target generates simple PHP Phar assemblies. With its
# default stub assuming `__init__.php` for CLI applications, and `index.php`
# as web router. A custom --phar-stub can be set of course.
#
# It honors the output filename, but alternatively allows `--phar-format`
# overriding the packaging format. It flexibly recognizes any concatenation
# of „phar·zip·tar“ with „gz·bz2“, case-insensitively.
#
#  ┌─────────────┬─────────────┬─────────────┬─────────────┬───────────────┐
#  │ Extension   │ Archive     │ Compression │ Envelope    │ Use           │
#  │ / Specifier │ Format      │ Per File    │ Compression │ Cases         │
#  ├─────────────┼─────────────┼─────────────┼─────────────┼───────────────┤
#  │ phar        │ Phar        │ -           │ -           │               │
#  ├─────────────┼─────────────┼─────────────┼─────────────┼───────────────┤
#  │ phar.gz     │ Phar        │ gzip        │ -           │ general       │
#  ├─────────────┼─────────────┼─────────────┼─────────────┼───────────────┤
#  │ phar·bz2    │ Phar        │ bzip2       │ -           │               │
#  ├─────────────┼─────────────┼─────────────┼─────────────┼───────────────┤
#  │ phaZ        │ Phar        │ -           │ gzip        │               │
#  ├─────────────┼─────────────┼─────────────┼─────────────┼───────────────┤
#  │ phaZ.gz     │ Phar        │ gzip        │ gzip        │ (eschew)      │
#  ├─────────────┼─────────────┼─────────────┼─────────────┼───────────────┤
#  │ zip         │ Zip         │ -           │ -           │               │
#  ├─────────────┼─────────────┼─────────────┼─────────────┼───────────────┤
#  │ zip.gz      │ Zip         │ gzip        │ -           │ distribution  │
#  ├─────────────┼─────────────┼─────────────┼─────────────┼───────────────┤
#  │ zip…bz2     │ Zip         │ bzip2       │ -           │               │
#  ├─────────────┼─────────────┼─────────────┼─────────────┼───────────────┤
#  │ tar         │ Pax         │ -           │ -           │               │
#  ├─────────────┼─────────────┼─────────────┼─────────────┼───────────────┤
#  │ tgz         │ Pax         │ -           │ gzip        │               │
#  ├─────────────┼─────────────┼─────────────┼─────────────┼───────────────┤
#  │ tar+bz2     │ Pax         │ -           │ bzip2       │ data bundles  │
#  └─────────────┴─────────────┴─────────────┴─────────────┴───────────────┘
#
# ZIPs and TARs can be read by other languages, but contain PHP/phar-specific
# additions (.phar/stub and a signature).
#

require "fpm/package"
require "fpm/util"
require "fileutils"
require "tempfile"
require "json"

# Phar creation (output only)
class FPM::Package::Phar < FPM::Package

  option "--format", "PHAR.gz/TGZ/ZIP", "Phar package type and compression (append .gz/.bz2 for method)", :default=>"PHAR.GZ"
  option "--stub", "STUB.php", "Apply initialization/stub file", :default=>""
  #option "--nocase", :flag, "Lowercase filenames / implicit `-u lcase=*`", :default=>false
  option "--sign", "PEM_FILE", "Sign package with OpenSSL and key from .pem file", :default=>""
  option "--x", :flag, "Set executable bit on phar package", :default=>false, :hidden=>true

  # Invoke PHP for actual packaging process
  def output(output_path)
    output_check(output_path)

    # Build flags
    p = {
      :name   => name,
      :srcdir => staging_path,
      #:nocase => attributes[:phar_nocase],
      :stub   => attributes[:phar_stub_given?] ? attributes[:phar_stub] : "",
      :sign   => attributes[:phar_sign],
    }
    
    # Retain package meta information, either from fpm attributes, or collected :attr hash (src module, formerly :meta)
    defaults = {
      "id" => @name,
      "version" => @version.to_s,
      "epoch" => @epoch.to_s,
      "iteration" => @iteration.to_s,
      "architecture" => @architecture,
      "category" => @category,
      "author" => @maintainer,
      "url" => @url,
      "license" => @license,
    }.delete_if{ |k,v| ["", nil, "default", "http://example.com/no-uri-given"].include? v }
    p[:meta] = attrs.merge(defaults).delete_if{ |k,v| v.empty? }
    
    # Match format specifier/extension onto type/settings
    fmt = (attributes[:phar_format] + output_path).downcase
    fmt, enc = fmt.match(/zip|phaz|tar|t[gb]z|pz/).to_s||"phar", fmt.match(/gz|bz2/).to_s
    map_keys = [:stdext, :format, :filegz, :extout, :hullgz]
    map = {
        # fmt,  enc          extension   format        per-file-gz   extns ← archive-compr
       ["phar", ""   ] => [ ".phar",     "Phar::PHAR", "Phar::NONE", "",     "" ],
       ["phar", "gz" ] => [ ".phar",     "Phar::PHAR", "Phar::GZ",   "",     "" ],
       ["phar", "bz2"] => [ ".phar",     "Phar::PHAR", "Phar::BZ2",  "",     "" ],
       ["zip",  ""   ] => [ ".phar.zip", "Phar::ZIP",  "Phar::NONE", "",     "" ],
       ["zip",  "gz" ] => [ ".phar.zip", "Phar::ZIP",  "Phar::GZ",   "",     "" ],
       ["zip", "bz2" ] => [ ".phar.zip", "Phar::ZIP",  "Phar::BZ2",  "",     "" ],
       ["tar",  ""   ] => [ ".phar.tar", "Phar::TAR",  "Phar::NONE", "",     "" ],
       ["tar",  "gz" ] => [ ".phar.tar", "Phar::TAR",  "Phar::GZ",   ".gz",  "Phar::GZ" ],
       ["tar", "bz2" ] => [ ".phar.tar", "Phar::TAR",  "Phar::BZ2",  ".bz2", "Phar::BZ2"],
       ["phaz", ""   ] => [ ".phar",     "Phar::PHAR", "Phar::GZ",   ".gz",  "Phar::GZ" ],
       ["phaz", "gz" ] => [ ".phar",     "Phar::PHAR", "Phar::GZ",   ".gz",  "Phar::GZ" ],
       ["phaz", "bz2"] => [ ".phar",     "Phar::PHAR", "Phar::GZ",   ".bz2", "Phar::BZ2"],
    }
    ext2map = {  # map file extensions
       "tgz" => ["tar", "gz"],
       "tbz" => ["tar", "bz2"],
       "pz"  => ["phaz", ""]
    }
    opt = map[[fmt,enc]] || map[ext2map[fmt]] || map[["phar", ""]]
    p.merge! Hash[map_keys.zip opt]

    # Have PHP generate the package
    p[:tmpf] = ::Dir::Tmpname.create(['_\$fpm_phar_', p[:stdext]]) {}
    phargen = template_dir + "/phar.php"
    safesystem("php", "-dphar.readonly=0", "-derror_reporting=~0", "-ddisplay_errors=1", phargen, JSON.generate(p))

    #-- but might end up with suffix, for whole-archive ->compress()ion
    FileUtils.mv(p[:tmpf] + p[:extout], output_path)
    FileUtils.chmod("a+x", output_path) if attributes[:phar_x_given?]
  end

end # class FPM::Package::Phar
