# frozen_string_literal: true
require "blather"
class FormTemplate
def initialize(template, filename="template", **kwargs)
@args = kwargs
@template = template
@filename = filename
freeze
end
def self.for(path, **kwargs)
if path.is_a?(FormTemplate)
raise "Sent args and a FormTemplate" unless kwargs.empty?
return path
end
full_path = File.dirname(__dir__) + "/forms/#{path}.rb"
new(File.read(full_path), full_path, **kwargs)
end
def self.render(path, context=OneRender.new, **kwargs)
self.for(path).render(context, **kwargs)
end
def render(context=OneRender.new, **kwargs)
one = context.merge(**@args).merge(**kwargs)
one.instance_eval(@template, @filename)
one.form
end
class OneRender
def initialize(**kwargs)
kwargs.each do |k, v|
instance_variable_set("@#{k}", v)
end
@__form = Blather::Stanza::X.new
@__builder = Nokogiri::XML::Builder.with(@__form)
end
def merge(**kwargs)
OneRender.new(**to_h.merge(kwargs))
end
def form!
@__type_set = true
@__form.type = :form
end
def result!
@__type_set = true
@__form.type = :result
end
def title(s)
@__form.title = s
end
def instructions(s)
@__form.instructions = s
end
def validate(field, datatype: nil, **kwargs)
Nokogiri::XML::Builder.with(field) do |xml|
xml.validate(
xmlns: "http://jabber.org/protocol/xdata-validate",
datatype: datatype || "xs:string"
) do
xml.basic unless validation_type(xml, **kwargs)
end
end
end
def validation_type(xml, open: false, regex: nil, range: nil)
xml.open if open
xml.range(min: range.first, max: range.last) if range
xml.regex(regex.source) if regex
open || regex || range
end
# Given a map of fields to labels, and a list of objects this will
# produce a table from calling each field's method on every object in the
# list. So, this list is value_semantics / OpenStruct style
def table(list, **fields)
keys = fields.keys
FormTable.new(
list.map { |x| keys.map { |k| x.public_send(k) } },
**fields
).add_to_form(@__form)
end
def field(datatype: nil, open: false, regex: nil, range: nil, **kwargs)
f = Blather::Stanza::X::Field.new(kwargs)
if datatype || open || regex || range
validate(f, datatype: datatype, open: open, regex: regex, range: range)
end
@__form.fields += [f]
end
def context
PartialRender.new(@__form, @__builder, **to_h)
end
def render(path, **kwargs)
FormTemplate.render(path, context, **kwargs)
end
def to_h
instance_variables
.reject { |sym| sym.to_s.start_with?("@__") }
.each_with_object({}) { |var, acc|
name = var.to_s[1..-1]
acc[name.to_sym] = instance_variable_get(var)
}
end
def xml
@__builder
end
def form
raise "Type never set" unless @__type_set
@__form
end
class PartialRender < OneRender
def initialize(form, builder, **kwargs)
kwargs.each do |k, v|
instance_variable_set("@#{k}", v)
end
@__form = form
@__builder = builder
end
def merge(**kwargs)
PartialRender.new(@__form, @__builder, **to_h.merge(kwargs))
end
# As a partial, we are not a complete form
def form; end
def form!
raise "Invalid 'form!' in Partial"
end
def result!
raise "Invalid 'result!' in Partial"
end
def title(_)
raise "Invalid 'title' in Partial"
end
def instructions(_)
raise "Invalid 'instructions' in Partial"
end
end
end
end