form_template.rb

  1# frozen_string_literal: true
  2
  3require "blather"
  4
  5class FormTemplate
  6	def initialize(template, filename="template", **kwargs)
  7		@args = kwargs
  8		@template = template
  9		@filename = filename
 10		freeze
 11	end
 12
 13	def self.for(path, **kwargs)
 14		if path.is_a?(FormTemplate)
 15			raise "Sent args and a FormTemplate" unless kwargs.empty?
 16
 17			return path
 18		end
 19
 20		full_path = File.dirname(__dir__) + "/forms/#{path}.rb"
 21		new(File.read(full_path), full_path, **kwargs)
 22	end
 23
 24	def self.render(path, context=OneRender.new, **kwargs)
 25		self.for(path).render(context, **kwargs)
 26	end
 27
 28	def render(context=OneRender.new, **kwargs)
 29		one = context.merge(**@args).merge(**kwargs)
 30		one.instance_eval(@template, @filename)
 31		one.form
 32	end
 33
 34	class OneRender
 35		def initialize(**kwargs)
 36			kwargs.each do |k, v|
 37				instance_variable_set("@#{k}", v)
 38			end
 39			@__form = Blather::Stanza::X.new
 40			@__builder = Nokogiri::XML::Builder.with(@__form)
 41		end
 42
 43		def merge(**kwargs)
 44			OneRender.new(**to_h.merge(kwargs))
 45		end
 46
 47		def form!
 48			@__type_set = true
 49			@__form.type = :form
 50		end
 51
 52		def result!
 53			@__type_set = true
 54			@__form.type = :result
 55		end
 56
 57		def title(s)
 58			@__form.title = s
 59		end
 60
 61		def instructions(s)
 62			@__form.instructions = s
 63		end
 64
 65		def validate(field, datatype: nil, **kwargs)
 66			Nokogiri::XML::Builder.with(field) do |xml|
 67				xml.validate(
 68					xmlns: "http://jabber.org/protocol/xdata-validate",
 69					datatype: datatype || "xs:string"
 70				) do
 71					xml.basic unless validation_type(xml, **kwargs)
 72				end
 73			end
 74		end
 75
 76		def validation_type(xml, open: false, regex: nil, range: nil)
 77			xml.open if open
 78			xml.range(min: range.first, max: range.last) if range
 79			xml.regex(regex.source) if regex
 80			open || regex || range
 81		end
 82
 83		def field(datatype: nil, open: false, regex: nil, range: nil, **kwargs)
 84			f = Blather::Stanza::X::Field.new(kwargs)
 85			if datatype || open || regex || range
 86				validate(f, datatype: datatype, open: open, regex: regex, range: range)
 87			end
 88			@__form.fields += [f]
 89		end
 90
 91		def context
 92			PartialRender.new(@__form, @__builder, **to_h)
 93		end
 94
 95		def render(path, **kwargs)
 96			FormTemplate.render(path, context, **kwargs)
 97		end
 98
 99		def to_h
100			instance_variables
101				.reject { |sym| sym.to_s.start_with?("@__") }
102				.each_with_object({}) { |var, acc|
103					name = var.to_s[1..-1]
104					acc[name.to_sym] = instance_variable_get(var)
105				}
106		end
107
108		def xml
109			@__builder
110		end
111
112		def form
113			raise "Type never set" unless @__type_set
114
115			@__form
116		end
117
118		class PartialRender < OneRender
119			def initialize(form, builder, **kwargs)
120				kwargs.each do |k, v|
121					instance_variable_set("@#{k}", v)
122				end
123				@__form = form
124				@__builder = builder
125			end
126
127			def merge(**kwargs)
128				PartialRender.new(@__form, @__builder, **to_h.merge(kwargs))
129			end
130
131			# As a partial, we are not a complete form
132			def form; end
133
134			def form!
135				raise "Invalid 'form!' in Partial"
136			end
137
138			def result!
139				raise "Invalid 'result!' in Partial"
140			end
141
142			def title(_)
143				raise "Invalid 'title' in Partial"
144			end
145
146			def instructions(_)
147				raise "Invalid 'instructions' in Partial"
148			end
149		end
150	end
151end