%{lua: -- file: vdb.lua --

if not _vdb_loaded_ then

--[[
    Some older rpms evaluate the file twice, so I added these guards. It looks like it is not
    required any more, at least in rpm 4.15.1.
]]--

_vdb_subpackages_ = {}

--[[------------------------------------------------------------------------------------------------
    String manipulation routines.
--]]------------------------------------------------------------------------------------------------

--[[
    Substitution, or search and replace. `string.gsub` returns *two* results (the modified string
    and the number of replacements done), it may break code, e. g.:

        split( " ", string.gsub( str, "\n", " " ) )
            -- bang! `split` will receive 3 arguments, not 2!

    `subst` returns only one result, it makes nested calls safe:

        split( " ", subst( str, "\n", " " ) )
]]--
function subst( str, ... )
    local subs = { ... }
    if # subs % 2 == 1 then
        push( subs, "" )
    end
    local i = 1
    while i < # subs do
        str = string.gsub( str, subs[ i ], subs[ i + 1 ] )
        i = i + 2
    end
    return str
end

-- Escapes % characters, returns string with all % characters doubled.
function escape( str )
    return subst( str, "%%", "%%%%" )
end

-- Quotes string for using in shell scripts.
function quote( str )
    -- TODO: Do not quote string if it is does not contain spaces and special characters.
    return "'" .. subst( str, "'", "'\\''" ) .. "'"
end

-- Drops leading and trailing white space.
function trim( str )
    return subst( str,
        "^%s+",  "",
        "%s+$",  ""
    )
end

-- Drops trailing newline, if any.
function chomp( str )
    return subst( str, "\n$", "" )
end

-- Drops leading and trailing white space, and shrinks internal white space.
function shrink( str )
    return trim( subst( str, "%s+", " " ) )
end

--[[--
    Returns a concatenation of all hash values in order of their keys.

    Note the difference between join and table.concat:
        *   table.concat process only keys in range [1 .. #has], other keys are ignored. join
            process all keys.
        *   table.concat can't process boolean values, join can.
--]]--
function join( sep, hash )
    -- table.concat doesn't like boolean values, so let's convert all values to strings:
    return table.concat( map( tostring, hash ), sep )
end

-- Perl-like split: split string into items.
function split( sep, str, limit )
    if sep == " " then  -- Special case.
        sep = "%s+"
        str = subst( str, "^" .. sep, "" )
    end
    local list = {}
    if # str == 0 then  -- One more special case.
        return list
    end
    limit = limit or 0
    local strip = limit == 0
    if limit <= 0 then
        limit = math.maxinteger
    end
    local pos = 1
    repeat
        -- I have just one more slot: grab the rest of the string and exit the loop.
        if # list + 1 == limit then
            push( list, string.sub( str, pos ) )
            break
        end
        -- Ok, I have some more slots, I can continue splitting. Try to find the next separator.
        local start, finish = string.find( str, sep, pos + ( # sep == 0 and 1 or 0 ) )
        -- No more separators, grab the rest of the string and exit the loop.
        if start == nil then
            push( list, string.sub( str, pos ) )
            break
        end
        -- Ok, the next separator found. Grab the piece of the string up to the next separator.
        push( list, string.sub( str, pos, start - 1 ) )
        -- And move forward the current position behing the separator.
        pos = finish + 1
    until false
    -- If limit is zero, drop trailing empty items:
    if strip then
        while # list > 0 and list[ # list ] == "" do
            pop( list )
        end
    end
    return list
end

-- Perl-like uc: convert string to upper case.
uc = string.upper

-- Perl-like lc: convert string to lower case.
lc = string.lower

--[[--
    Title case: convert the given string to lower case, then capitalize the first lettwer of every
    word.
--]]--
function tc( str )
    return subst( lc( str ), '^([^%s])', uc, '(%s[^%s])', uc )
end

-- Perl-like sprintf.
sprintf = string.format

--[[--
    Makes identifier (i. e. package name) from a string (e. g. program or font family name). The
    function replaces spaces with dashes and converts everything to lower case. Function accepts
    many arguments. e. g.:

        ident( 'Iosevka', 'Full', 'Ssans' ) -- returns 'iosevka-full-sans'

--]]--
function ident( ... )
    return lc( subst( join( ' ', { ... } ), ' ', '-' ) )
end

--[[--
    Prepends every line of text with prefix.
--]]--
function prepend( prefix, text )
    if text == "" then
        return text
    else
        local nl = string.sub( text, -1 ) == "\n"
        if nl then
            text = string.sub( text, 1, -2 )
        end
        text = prefix .. subst( text, "\n", "\n" .. prefix )
        if nl then
            text = text .. "\n"
        end
        return text
    end
end

--[[
    Reflows text. Text may include multiple paragraphs separated by empty lines. Each paragraph
    will be reflowed. During reflow all leading and trailing spaces and tabs are dropped, spaces
    and tabs are shrinked to exactly one space, words will be (re)distributed between lines so each
    line does not exceed specified width (if possible).

    Current limitations are:
        *   The function is not Unicode-aware.
]]--
function reflow( text, width )
    text = subst( text,
        "^%s+",   "",       -- Drop leading whitespace in the text.
        "%s+$",   "",       -- Drop trailing whitespace in the text.
        "[ \t]+", " ",      -- Shrink spaces and tabs to one space.
        "\n ",    "\n",     -- Drop leading spaces in every line (except the first line).
        " \n",    "\n",     -- Drop trailing spaces in every line (except the last line).
        "\n\n+", "\n\n"     -- Shrink empty lines.
    )
    width = width or 80
    local paragraphs = {}
    for p, paragraph in ipairs( split( "\n\n", text ) ) do
        local words = split( " ", paragraph )
        local lines = {}    -- Lines of the new paragraph.
        local line  = ""    -- Current line
        for w, word in ipairs( words ) do
            if # line == 0 then
                line = word
            elseif # line + 1 + # word <= width then
                line = line .. " " .. word
            else
                push( lines, line )
                line = word
            end
        end
        if # line > 0 then
            push( lines, line )
        end
        push( paragraphs, join( "\n", lines ) )
    end
    text = join( "\n\n", paragraphs )
    return text
end

-- Convert a value to string for debug purposes.
function str( value )
    local t = type( value )
    if t == 'nil' then
        return 'nil'
    elseif t == 'boolean' or t == 'number' then
        return tostring( value )
    elseif t == 'string' then
        return "'" .. subst(
            value,
            "[%c'\\]",
            function ( chr )
                if chr == '\a' then
                    return '\\a'
                elseif chr == '\b' then
                    return '\\b'
                elseif chr == '\f' then
                    return '\\f'
                elseif chr == '\n' then
                    return '\\n'
                elseif chr == '\r' then
                    return '\\r'
                elseif chr == '\t' then
                    return '\\t'
                elseif chr == '\v' then
                    return '\\v'
                elseif chr == '\\' then
                    return '\\\\'
                elseif chr == "'" then
                    return "\\'"
                end
                local result = ''
                for i, k in ipairs( table.pack( string.byte( chr, 1, -1 ) ) ) do
                    result = result .. sprintf( '\\x%02X', k )
                end
                return result
            end
        ) .. "'"
    elseif t == 'table' then
        local lines = {}
        push( lines, '{' )
        for i, k in ipairs( keys( value ) ) do
            push( lines, prepend( '    ', sprintf( '%s → %s,', str( k ), str( value[ k ] ) ) ) )
        end
        if # lines > 1 then
            push( lines, '}' )
        else
            lines[ 1 ] = '{}'
        end
        return join( '\n', lines )
    elseif t == 'function' then
        return tostring( value )
    elseif t == 'userdata' then
        die( 'oops: userdata' )
    else
        die( 'oops: unknown type: %s', t )
    end
end

--[[------------------------------------------------------------------------------------------------
    Array manipulation routines.
--]]------------------------------------------------------------------------------------------------

-- Appends element(s) to the given array.
function push( array, ... )
    for i, v in ipairs( { ... } ) do
        table.insert( array, v )
    end
end

-- Removes the last element from the given array and returns the removed element.
function pop( array )
    local value
    if # array > 0 then
        value = table.remove( array )
    end
    return value
end

-- Removes the first element from the given array, and returns the removed element.
function shift( array )
    local value
    if # array > 0 then
        value = table.remove( array, 1 )
    end
    return value
end

--[[--
    Sorts values of the given array in-place. It is table.sort with custom compare function.
    Default compare function can't compare boolean values, can't compare string with number, etc.
    Custom compare function works well with booleans, numbers and strings: booleans go first
    (false, then true), then go numbers (in natural order), then strings.

    This function should be local, but it made public for testing purposes.
--]]--
function _sort( array )
    _sort_order = {
        [ 'nil'      ] = 0,
        [ 'boolean'  ] = 1,
        [ 'number'   ] = 2,
        [ 'string'   ] = 3,
        [ 'table'    ] = 4,
        [ 'function' ] = 5,
        [ 'thread'   ] = 6,
        [ 'userdata' ] = 7,
    }
    table.sort(
        array,
        function ( a, b )
            local ta = _sort_order[ type( a ) ]
            local tb = _sort_order[ type( b ) ]
            -- Lua can't compare values if their types differ, so let's make sure types
            if ta == tb then
                if ta == 1 then -- Lua can't compare boolean values
                    return ( a and 1 or 0 ) < ( b and 1 or 0 )
                end
                return a < b
            else
                return ta < tb
            end
        end
    )
    return array
end

--[[------------------------------------------------------------------------------------------------
    Hash manipulation routines.
--]]------------------------------------------------------------------------------------------------

--[[--
    Creates a hash.
--]]--
function hash( keys, value )
    if value == nil then
        value = false   -- In Lua, nil value can't be stored in hash, let's use false instead.
    end
    local hash = {}
    for k, v in pairs( keys ) do
        hash[ v ] = value
    end
    return hash
end

--[[--
    Returns sorted array of hash keys.
--]]--
function keys( hash )
    local keys = {}
    for k, v in pairs( hash ) do
        push( keys, k )
    end
    return _sort( keys )
end

--[[--
    Returns array of hash values sorted by their keys.
--]]--
function vals( hash )
    local values = {}
    for i, k in ipairs( keys( hash ) ) do
        push( values, hash[ k ] )
    end
    return values
end

--[[--
    Returns sorted array of hash values.
--]]--
function sort( hash )
    --[[--
        `table.sort` works on elements with keys in range [1 .. #table], other elements are
        ignored. Let's build array of hash values first, so all the values are sorted.
    --]]--
    local values = {}
    for k, v in pairs( hash ) do
        push( values, v )
    end
    return _sort( values )
end

--[[--
    Returns array, constructed from the given hash by applying the given function to each hash item
    (value and key), and pushing the function's result to the array. Hash items are enumerated in
    order defined by their sorted keys.
--]]--
function map( func, hash )
    local result = {}
    for i, k in ipairs( keys( hash ) ) do
        push( result, func( hash[ k ], k ) )
    end
    return result
end

--[[--
    Returns array, constructed from the given hash by applying the given pred function to each hash
    item (value and key), and pushing the hash value to the array if pred returned true for the
    item. If pred returned false, the item does not go into the array. Hash items are enumerated in
    order defined by their sorted keys. If pred is not a function, only values equal to pred go to
    the array.
--]]--
function grep( pred, hash )
    local func
    if type( pred ) == "function" then
        func = pred
    else
        func = function ( it ) return it == pred; end
    end
    local result = {}
    for i, k in ipairs( keys( hash ) ) do
        if func( hash[ k ], k ) then
            push( result, hash[ k ] )
        end
    end
    return result
end

--[[------------------------------------------------------------------------------------------------
    RPM primitive routines.
--]]------------------------------------------------------------------------------------------------

-- Expands rpm macros in the given string, returns result of expansion.
expand = rpm.expand

-- Returns true if rpm macro is defined, and false otherwise.
function defined( name )
    if rpm[ 'isdefined' ] then
        local defined, parametrized = rpm.isdefined( name )
        return defined
    else
        return expand( "%{?" .. escape( name ) .. ":1}" ) == "1"
    end
end

--[[--
    Define rpm macro.

    Note that rpm macros are stacked: (re)defining a macro does not simply overwrite the old value
    but pushes a new value on the stack. See also `unset`.
--]]--
function set( name, value )
    --[[--
        If value is empty, rpm will complain on empty macro value. Let's use `%{nil}` in such a
        case to avoid warning.

        Also, RPM work with strings only, so let's convert Lua boolean values to strings.
    --]]--
    if value == nil or value == "" then
        value = "%{nil}"
    elseif value == true then
        value = "1"
    elseif value == false then
        value = "0"
    end
    value = subst( value,
        "\\", "\\\\",   -- Backslashes in macro body must be doubled.
        "\n", "\\\n"    -- And newlines must be preceeded by backslashes.
    )
    local l = name .. " " .. value
    spc( "%%define %s", l )
    rpm.define( l )
    return value
end

--[[--
    Undefine rpm macro.

    Note that rpm macros are stacked: (re)defining a macro does not simply overwrite the old value
    but pushes a new value on the stack. Unsetting macro pops a value from the top of the stack,
    restoring the previous macro value.

    `count` — how many values to pop from the stack. By default 1. If 0, all the values will be
    popped, leaving the macro undefined.
--]]--
function unset( name, count )
    if count == nil then
        count = 1
    end
    if count > 0 then
        while count > 0 do
            spc( "%%undefine %s", name )
            rpm.undefine( name )
            count = count - 1
        end
    else
        while defined( name ) do
            spc( "%%undefine %s", name )
            rpm.undefine( name )
        end
    end
end

-- Get expanded value of rpm macro. If macro is not defined, default value is returned, and nil if
-- default is not specified. Note, that resul type depends on default: if default is number, result
-- is converted to number too; otherwise result will be either string or nil.
function get( name, default )
    local result
    if defined( name ) then
        result = expand( "%{" .. escape( name ) .. "}" )
        if type( default ) == "number" then
            -- If default value is number, convert result to number.
            result = tonumber( result )
        elseif type( default ) == "boolean" then
            -- If default value is boolean, convert result to number.
            -- Note: empty string and "0" are treated as false, everything else — as true.
            result = not ( result == "" or result == "0" )
        end
    end
    if result == nil then
        result = default
    end
    return result
end

-- Generate spec code. If the first parameter is table, it is interpreted as set of macros, which
-- will be temporary defined.
function spec( ... )
    local lines = { ... }
    -- See if macros are present.
    local macros = {}
    if # lines > 0 and type( lines[ 1 ] ) == "table" then
        macros = shift( lines )
    end
    -- Set macros:
    for name, value in pairs( macros ) do
        set( name, value )
    end
    -- Generate spec code:
    for i, line in ipairs( lines ) do
        local l = expand( line )
        spc( "%s", l )
        if # lines == 1 then
            print( l )
        else
            print( l .. "\n" )
        end
    end
    -- Restore original macro values:
    for name in pairs( macros ) do
        unset( name )
    end
end

--[[------------------------------------------------------------------------------------------------
    OS and filesystem routines.
--]]------------------------------------------------------------------------------------------------

-- Returns true if file exists.
function exist( path )
    return not not posix.stat( path )
end

-- Rename file or directory.
function rename( old, new )
    -- `+` mimics `rpmbuild` output.
    say( "+ mv %s %s", old, new )
    local ok, error = os.rename( old, new )
    if not ok then
        die( "mv: %s", error )
    end
    return ok
end

--[[--
    Read file. Read first `limit` lines of file. If limit is zero or nil, read all the lines, but
    drop empty lines at the end. If `limit` is negative, read all the lines.
--]]--
function slurp( path, limit )
    local lines = {}
    if limit == nil then
        limit = 0
    end
    for line in io.lines( path ) do
        push( lines, line )
        if limit > 0 and # lines >= limit then
            break
        end
    end
    if limit == 0 then
        while # lines > 0 and lines[ # lines ] == "" do
            pop( lines )
        end
    end
    return lines
end

-- Write file.
function spurt( path, ... )
    local temp = path .. ".tmp"
    say( "+ > %s", temp )
    local file, err = io.open( temp, "w" )
    if file == nil then
        die( "Can't open file %s for writing: %s", temp, err )
    end
    local lines = { ... }
    for i, line in ipairs( lines ) do
        file, err = file:write( line .. "\n" )
        if file == nil then
            die( "Can't write file %s: %s", temp, err )
        end
    end
    -- TODO: Does close report errors?
    file:close()
    rename( temp, path )
end

-- Execute shell command.
function shell( command )
    -- `+` mimics `rpmbuild` output.
    say( "+ %s", command )
    local ok, status, code = os.execute( command )
    if not ok then
        if status == "exit" then
            die( "command exited with status %d", code )
        else
            die( "command died with signal %d", code )
        end
    end
    return ok
end

--[[------------------------------------------------------------------------------------------------
    Logging routines.
--]]------------------------------------------------------------------------------------------------

function _say( prefix, format, ... )
    io.stderr:write( prepend( prefix, sprintf( format, ... ) ) .. "\n" )
end

-- Print a message to stderr if debug enabled.
function dbg( format, ... )
    if get( "_vdb_debug_", false ) then
        _say( "# ", format, ... )
    end
end

-- Print a message to stderr if debug enabled.
function spc( format, ... )
    if get( "_vdb_debug_", false ) then
        _say( "> ", format, ... )
    end
end

-- Print a variable.
function dump( value, name )
    if name == nil then
        dbg( "%s", str( value ) )
    else
        dbg( "%s = %s", name, str( value ) )
    end
end

-- Print a message to stderr.
function say( format, ... )
    _say( "", format, ... )
end

function note( format, ... )
    _say( "* ", format, ... )
end

function warn( format, ... )
    _say( "warning: ", format, ... )
end

-- Print a error to stderr.
-- Building will be stopped in the nearest %prologue.
function die( format, ... )
    if format ~= nil then
        _say( "error: ", format, ... )
    end
    error( "oops" )
end





----------------------------------------------------------------------------------------------------

function banner( phase )
    return ": ---------- " .. phase .. " ---------- :"
end

-- Prologue to execute in the beginning of every scriptlet:
local prologue = "set -e; [ -n \"$BASH_VERSION\" ] && set -o pipefail;"
set( "prologue", prologue )

function prep()
    local function var( name, value )
        if type( value ) == "table" then
            return quote( name .. "=(" .. join( " ", map( quote, value ) ) .. ")" )
        else
            return quote( name .. "=" .. quote( value ) )
        end
    end
    spec(
        banner( "PREP" ),
        prologue,
        --[[--
            Someone can run `rpmbuild -bb name.spec` and build rpm files. However, exact name of
            built rpm is not known to the caller because name contains version, release, and
            architecture -- these parameters are encoded into spec and not easily available outside
            of the spec file. The things are even worse if one spec file generates few subpackages.
            Let us write them out to the rc file, so the caller can source the rc file and
            construct rpm name(s), e. g. "$NAME-$VERSION-$RELEASE.$ARCH.rpm".

            Writing rc file is a bit tricky. My original intention was to write rc file by Lua
            code. Unfortunately, I failed to find out value of `BuildArch` tag of the package from
            Lua code. `_build_arch` is just a synonym for `_arch`, which contains archirecture of
            the host CPU regardless of `BuildArch` tag value. The only way to detect `BuildArch:
            noarch` is using `%ifarch noarch` condition.
        --]]--
        "if [[ -n \"$SPEC_RC_FILE\" ]]; then",
        "   {",
        "       echo " .. var( "NAME", get( "name" ) ),
        "       echo " .. var( "VERSION", get( "version" ) ),
        "       echo " .. var( "RELEASE", get( "release" ) ),
        "       %ifarch noarch",
        "           echo " .. var( "ARCH", "noarch" ),
        "       %else",
        "           echo " .. var( "ARCH", get( "_arch" ) ),
        "       %endif",
        "       echo " .. var( "RPMS", get( "_rpmdir" ) ),
        "       echo " .. var( "SRPMS", get( "_srcrpmdir" ) ),
        "       echo " .. var( "SUBPACKAGES", _vdb_subpackages_ ),
        "   } > \"$SPEC_RC_FILE.tmp\"",
        "   %{__mv} \"$SPEC_RC_FILE.tmp\" \"$SPEC_RC_FILE\"",
        "fi"
    )
end

function build()
    spec( banner( "BUILD" ), prologue )
end

function check()
    spec( banner( "CHECK" ), prologue )
end

function install()
    spec( banner( "INSTALL" ), prologue )
end

function clean()
    spec( banner( "CLEAN" ), prologue )
end

----------------------------------------------------------------------------------------------------

-- Redefine %prep, %build, %check and %install macros:
-- Let's add prologue to the every build-time scriptlet.
-- The idea is great but Fedora/RedHat rpmbuild macros also use this trick.
-- So I can't add prologue to %install.
local phases = { "prep", "build", "check", "install", "clean" }
for i, phase in ipairs( phases ) do
    if defined( phase ) then
        warn( "'%s' macro is already defined, can't add prologue.", phase )
    else
        set( phase, "%%" .. phase .. " \n" .. "%{lua: " .. phase .. "();  }" )
    end
end

--[[------------------------------------------------------------------------------------------------
    Let's add prologue to the every install-time scriptlet.
--]]------------------------------------------------------------------------------------------------

-- Install-time scriptlets:
local phases = {
    "pretrans",
    "pre",
    "post",
    "triggerin",
    "triggerun",
    "preun",
    "postun",
    "triggerpostun",
    "posttrans",
}

for i, phase in ipairs( phases ) do
    if defined( phase ) then
        warn( "'%s' macro already defined", phase )
    else
        -- If -p option present present, do not add prologue.
        set( phase .. "(p:)", "%%" .. phase .. " %** \n" .. "%{!-p:%prologue}" )
    end
end

--[[------------------------------------------------------------------------------------------------
    Play the same trick to collect all subpackage names:
--]]------------------------------------------------------------------------------------------------

if defined( "package" ) then
    die( "'%s' macro already defined", "package" )
end
set(
    -- %package macro may have -n option. If -n is present, it defines full name of the subpackage.
    -- Oterwise subpackage name is constructed from package name and the first macro argument.
    "package(n:)",
    [[
        %%package %**
        %{lua:
            dbg( "%%package %s", get( "**" ) )
            push( _vdb_subpackages_, get( "-n*" ) or get( "name" ) .. "-" .. get( "1" ) )
        }
    ]]
)

--[[------------------------------------------------------------------------------------------------
    Find out versions of external programs:
--]]------------------------------------------------------------------------------------------------

-- Find out version of install program:
function install_version()
    if _vdb_install_version_ == nil then
        local program = get( "__install" )
        local output = rpm.expand( "%( %{__install} --version )" )
        if output == "" then
            die( "Can't find version of '%s' program.", program )
        end
        output = split( "\n", output, 2 )[ 1 ] -- Get the first line of output.
        local version = string.match( output, "^install %(GNU coreutils%) ([0-9.]+)$" )
        if version == nil then
            die( "Can't parse '%s' program output: '%s'", program, output )
        end
        dbg( "%s version %s", program, version )
        _vdb_install_version_ = version
    end
    return _vdb_install_version_
end

-- Find out version of rpm program:
function rpm_version()
    if _vdb_rpm_version_ == nil then
        local program = get( "__rpm" )
        local output = expand( "%( %{__rpm} --version )" )
        if output == "" then
            die( "Can't find version of '%s' program.", program )
        end
        output = split( "\n", output, 2 )[ 1 ] -- Get the first line of output.
        local version = string.match( output, "^RPM version ([-0-9a-z.]+)$" )
            -- RPM version could be like `4.0.16` or `4.1.0-alpha1`.
        if version == nil then
            die( "Can't parse '%s' program output: '%s'", program, output )
        end
        dbg( "%s version %s", program, version )
        _vdb_rpm_version_ = version
    end
    return _vdb_rpm_version_
end

--[[------------------------------------------------------------------------------------------------
    Macros to use in spec files:
--]]------------------------------------------------------------------------------------------------

--[[
    Usage:

        %{rem: Long
            multi-line
            comment.
        }
]]--
set( "rem" )

--[[
    Usage:

        %Global NAME VALUE

    Defines global with specified NAME and VALUE, as well as lowercase version (both name and
    value) of the global.

    Example:

        %Global Font Hack
            # equivalent for
            #   %global Font Hack
            #   %global font hack
]]--
set(
    "Global()",
    "%{lua: spec( " ..
        "\"%global \" .. get( 1 ) .. \" \" .. get( 2 ), " ..
        "\"%global \" .. lc( get( 1 ) ) .. \" \" .. lc( get( 2 ) ) " ..
        ") }"
)

--[[
    Usage:

        %description
        %{text -w <width> -- \
            Long description.

            Description may include multiple paragraphs. Description will be reflowed (see `reflow`
            function for details.
        }

        %global common_description %{text -- \
            Long description. ...
        }

    where:

        *   <width> — integer number, desired max width of reformatted text. Option -w may be
            omitted, 80 will be used by default.
]]--
set( "text(w:)",
    "%{lua: spec( reflow( expand( subst( get( \"*\" ), \"^\\\\\" ) ), get( \"-w*\", 80 ) ) ) }"
        -- Depending on usage (in %description body or in macro body) `get( "*" )` can return a
        -- string starting with `\` or not. Let's remove starting `\`.
)

--[[
    Usage:

        %_install_D OPTION... ARGUMENT...

    Runs install program with -D and other specified options and arguments.

    Modern versions of install program treat -D option as instruction to create *all* the
    components of the target directory. However, older install in RHEL 6 and 7 treats -D option
    differently: it creates all the components of target directory *but* the last. _install_D macro
    emulates modern install program on RHEL 6 and 7.

    The macro recognizes only short options (e. g. `-t`), long options (e. g. `--target-directory`)
    are not supported. Also, currenly only `-m`, `-p`, and `-t` options are supported.

    Example:

        %install
        %_install_D -p -t %{buildroot}%{_bindir} build/bin/%{name}
]]--
local version = install_version()
-- install 8.22 does not create the last component of target directory.
-- install 8.29 does create all components.
if rpm.vercmp( version, "8.23" ) > 0 then   -- looks like vercmp returns -1, 0, and 1.
    -- install is recent enough to handle -D properly:
    dbg( "install %s: ok", version )
    set( "_install_D", "%{__install} -D" )
else
    -- install is too old, create target directory before calling install:
    dbg( "install %s: too old", version )
    -- Expanding two macros in adjacent lines:
    --      %_install_D -t dir0 file0
    --      %_install-D -t dir1 file1
    -- gives strange result in EPEL 6:
    --      mkdir -p dir0; install -D -t dir0 file0mkdir -p dir1; install -D -t dir1 file1
    -- The macros are expanded to a single line with neither new line nor space between them.
    -- Other distros (EPEL 7, Fedora, Mageia, OpenSuse) do not have such a problem.
    -- Let's add a semicolon to the end of macro value to avoid error.
    set( "_install_D(m:pt:)",
        "%{-t:%{__mkdir_p} %{-t*}; }%{__install} -D %{-m:-m %{-m*}} %{-p:-p} %{-t:-t %{-t*}} %*; "
    )
end

--[[
    Usage:

        %if %{author_check}
            ...
        %else
            ...
        %endif

    %{author_check} expands to 0 (false) if SPEC_AUTHOR_CHECK environment variable is not set
    or is empty or is zero. Otherwise %{author_check} evaluates to 1 (true).

    Example:

        %check
        %if %{author_check}
            %{_bindir}/appstreamcli validate "%{name}.metainfo.xml"
        %endif
]]--
local author_check = os.getenv( "SPEC_AUTHOR_CHECK" )
if author_check == nil or author_check == "" or author_check == "0" then
    author_check = "0"
else
    author_check = "1"
end
set( "author_check", author_check )

--[[------------------------------------------------------------------------------------------------
    External programs and directories:
--]]------------------------------------------------------------------------------------------------

if not get( "__m4" ) then
    set( "__m4", "%{_bindir}/m4" )
end
set( "__rpm", "%{_bindir}/rpm" )

set( "_licensedir", "%{_datadir}/licenses" )
set( "_fontdir",    "%{_datadir}/fonts" )
if not defined( "_pkgdocdir" ) then
    -- Fedora defines _pkgdocdir, but OpenSuse doesn't.
    set( "_pkgdocdir", "%{_docdir}/%{name}" )
end
if not get( "_metainfodir" ) then
    -- Mageia 6 does not define _metainfodir macro.
    -- As weel as OpenSuse 15.0, 15.1 and tumbleweed.
    set( "_metainfodir", "%{_datadir}/metainfo" )
end

----------------------------------------------------------------------------------------------------

--[[--
    Platform detection:
        CentOS: %{centos} ("8").
        EPEL: %{epel} ("7").
        Fedora: %{fedora} ("34").
        Mageia: %{mageia} ("9").
        OpenMandriva: %{omvver} ("4003000" for rolling, "4050000" for cooker)??
            %{mandriva_branch} ("Cooker" or "Rolling").
        OpenSuse: %{suse_version} ("1500" for leap 15.2 and 15.3, "1550" for tumbleweed).
--]]--

_vdb_loaded_ = 1

end

} # end of file #