# Functions for parsing control files.
#
# Copyright (C) 2012, 2013, 2019  Patrick McDermott
#
# This file is part of the ProteanOS Development Kit.
#
# The ProteanOS Development Kit is free software: you can redistribute
# it and/or modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# The ProteanOS Development Kit is distributed in the hope that it
# will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with the ProteanOS Development Kit.  If not, see
# <http://www.gnu.org/licenses/>.

_parse_control_error()
{
	local file="${1}"
	local line_nr="${2}"
	local msg_id="${3}"
	shift 3
	local file_info=

	if [ ${line_nr} -eq 0 ]; then
		file_info="$(printf '%s' "${file}")"
	else
		file_info="$(printf '%s:%d' "${file}" "${line_nr}")"
	fi

	warn "${file_info}: $(get_msg "${msg_id}")" "${@}"

	return 0
}

parse_control()
{
	local file="${1}"
	local field_cb="${2}"
	local paragraph_cb="${3}"
	shift 3
	local req_fields=
	local line_nr=
	local in_paragraph=
	local line=
	local para_req_fields=
	local got_fields=
	local name=
	local value=

	if [ ${#} -eq 1 ]; then
		req_fields=" ${1} "
		shift 1
	fi

	line_nr=0
	in_paragraph='false'

	while IFS='' read -r line; do
		line_nr=$((${line_nr} + 1))
		case "${line}" in
			'')
				# Paragraph end.
				if ${in_paragraph}; then
					# The first line is blank to consolidate
					# initialization code (see heredocument
					# below).
					in_paragraph='false'
					if [ -n "${name}" ]; then
						if ! "${field_cb}" "${name}" \
								"${value}"; then
							return 0
						fi
					fi
					if ! "${paragraph_cb}"; then
						return 0
					fi
					case "${para_req_fields}" in *[!\ ]*)
						# Missing required control
						# fields.
						sep="$(get_msg \
							'list_item_separator')"
						para_req_fields="$(printf \
							"%s${sep}" \
							${para_req_fields}; \
							printf 'x')"
						para_req_fields="$(: \
							)${para_req_fields%$(: \
							)${sep}x}"
						_parse_control_error \
							"${file}" "${line_nr}" \
							'control_missing_fields'\
							"${para_req_fields}"
					esac
				fi
				para_req_fields="${req_fields}"
				got_fields=' '
				name=''
				value=''
				;;
			'#'*)  # Comment.
				in_paragraph='true'
				;;
			[!\ ]*':'*)  # "Name: Value" line.
				in_paragraph='true'
				if [ -n "${name}" ]; then
					if ! "${field_cb}" "${name}" "${value}"
					then
						return 0
					fi
				fi
				IFS=': 	' read name value <<-EOF
					${line}
					EOF
				if [ -z "${name}" ]; then
					_parse_control_error "${file}" \
						"${line_nr}" 'control_bad_nv'
					continue
				fi
				case "${got_fields}" in *" ${name} "*)
					# Duplicate field.
					_ob_parse_control_error \
						"${file}" "${line_nr}" \
						'control_duplicate_field' \
						"${name}"
					continue
				esac
				got_fields="${got_fields}${name} "
				case "${para_req_fields}" in *" ${name} "*)
					# Required field: remove from list.
					para_req_fields="$(:\
						)${para_req_fields% ${name} *}$(
						:\
						) ${para_req_fields#* ${name} }"
					continue
				esac
				;;
			' '*)  # Continuation line.
				in_paragraph='true'
				if [ -z "${name}" ]; then
					# Expecting a "Name: Value" line.
					_parse_control_error "${file}" \
						"${line_nr}" \
						'control_found_continuation'
					continue
				fi
				value="${value}${LF}${line# }"
				;;
		esac
	done <<-EOF

		$(cat -- "${file}")

		EOF
	# The first blank line above triggers the paragraph end code to
	# consolidate initialization code.
	# The second blank line above is needed because the command substitution
	# removes any trailing newlines.

	return 0
}