ACF 编写指南

来自 Alpine Linux

如何编写 ACF

有关一些示例,请参阅 Alpine Linux git 仓库中的 Web Configuration Framework 项目

https://git.alpinelinux.org/

  • acf-unbound - 一个简单的 ACF,用于控制服务
  • acf-awall - 一个稍微复杂的 ACF,用于防火墙
  • acf-provisioning - 一个基于 ACF 的复杂数据库应用程序
  • ...

从 <nil> 到一个运行的 ACF 示例应用程序

步骤 1 - 编程语言

  • ACF 使用 lua 作为其编程语言。在开始之前,请查看 lua.org

步骤 2 - 应用程序环境

  • 通过运行 setup-acf 设置 ACF Web 应用程序

步骤 3 - 创建开发目录

  • 在您的用户主目录中为您的应用程序创建一个目录(例如 mkdir ~/myapp)
  • 并 cd 进入它(例如 cd ~/myapp)

步骤 4 - MVC,它如何影响我的编码?

ACF 是一个基于 MVC 的框架。这对您意味着什么?您的应用程序分为三个层:模型、视图、控制器 - 每个层可能有一个或多个文件。

  • 控制器:事件分发器。大多数控制器功能由 ACF mvc.lua 代码和一些标准控制器(例如 acf_www-controller.lua 或 acf_cli-controller.lua)处理。对于您新的 ACF 包的控制器层,您必须在名为“myapp-controller.lua”的 lua 模块中为每个操作导出一个 lua 函数。ACF 控制器代码将解释用户交互以加载您的新控制器并触发相应的操作 - 将调用控制器中同名的函数。
  • 视图:视图层定义了您的应用程序的外观。对于大多数操作,例如表单,您的应用程序可以使用内置的自动视图生成。对于其他操作,您可以链接到 acf-core 包中包含的标准视图。对于其他操作,例如数据列表,您可以创建视图文件,每个文件都呈现一个动态 HTML 页面,其中只包含显示从控制器接收的数据所需的代码。
  • 模型:“真正的工作”在模型中完成(例如,修改配置文件、启动/停止服务等)。您的控制器导出的每个操作都将调用模型函数以检索数据并执行操作。

步骤 5 - 开始使用的示例文件

现在让我们看一下我们需要放入应用程序目录中的文件

  • config.mk
  • Makefile
  • myapp-controller.lua
  • myapp-model.lua
  • myapp.roles
  • myapp.menu


config.mk: 用于 Makefile。只需复制/粘贴即可。我们稍后会查看它。

prefix=/usr
datadir=${prefix}/share
sysconfdir=${prefix}/etc
localstatedir=${prefix}/var
acfdir=${datadir}/acf
wwwdir=${acfdir}/www
cgibindir=${acfdir}/cgi-bin
appdir=${acfdir}/app
acflibdir=${acfdir}/lib
sessionsdir=${localstatedir}/lib/acf/sessions


Makefile

调用 Makefile 以安装我们的 ACF 应用程序,以便我们可以看到它在工作。

APP_NAME=myapp
PACKAGE=acf-$(APP_NAME)
VERSION=0.1

APP_DIST=        \
        myapp*        \

EXTRA_DIST=README Makefile config.mk

DISTFILES=$(APP_DIST) $(EXTRA_DIST)

TAR=tar

P=$(PACKAGE)-$(VERSION)
tarball=$(P).tar.bz2
install_dir=$(DESTDIR)/$(appdir)/$(APP_NAME)

all:
clean:
	rm -rf $(tarball) $(P)

dist: $(tarball)

install:
	mkdir -p "$(install_dir)"
	cp -a $(APP_DIST) "$(install_dir)"

$(tarball):     $(DISTFILES)
	rm -rf $(P)
	mkdir -p $(P)
	cp $(DISTFILES) $(P)
	$(TAR) -jcf $@ $(P)
	rm -rf $(P)

# target that creates a tar package, unpacks is and install from package
dist-install: $(tarball)
	$(TAR) -jxf $(tarball)
	$(MAKE) -C $(P) install DESTDIR=$(DESTDIR)
	rm -rf $(P)

include config.mk

.PHONY: all clean dist install dist-install


myapp-controller.lua

-- the myapp controller
local mymodule = {}

mymodule.default_action = "myaction"

mymodule.myaction = function(self)
   -- self.clientdata contains the user data
   -- self.model points to our model
   -- use the helper function to implement our form
   return self.handle_form(self, self.model.getdata, self.model.setdata, self.clientdata, "Submit", "Edit data", "Data Submitted")
end

return mymodule

myapp-model.lua

-- acf model for myapp
local mymodule = {}

local cfgfile = "/tmp/myfile"

-- This function returns a cfe (table of values) containing the file's
-- value as a string. If the file does not exist, we'll
-- simply return "" (an empty string, but NOT nil)
mymodule.getdata = function(self, clientdata)
   local retval = cfe({ type="group", value={}, label="Data" })
   retval.value.data = cfe({ type="longtext", label="Data" })

   local fileptr = io.open(cfgfile, "r")
   if fileptr ~= nil then
      retval.value.data.value = fileptr:read("*a") or ""
      fileptr:close()
   end

   return retval
end

-- This function will write new contents into our file
-- The newdata parameter receives the same cfe as returned by getdata, now with the user data filled in
mymodule.setdata = function(self, newdata, action)
   fileptr = io.open( cfgfile, "w+" )
   if fileptr ~= nil then
      fileptr:write(newdata.value.data.value)
      fileptr:close()
   else
      newdata.errtxt = "Failed to save data"
   end
   return newdata
end

return mymodule

myapp.roles

GUEST=myapp:myaction

myapp.menu

# Cat   Group   Tab     Action
Test    MyApp   MyAction  myaction

步骤 6 - 它的作用是什么?

该程序仅显示一个 <textarea> 框和一个“提交”按钮。用户可以输入文本,并在按下“提交”后将其保存到文件中。

深入了解

现在让我们仔细看看不同文件的内容

myapp-controller.lua

控制器是事件分发器。因此,您可以在此处定义用户可以调用的或菜单中定义的所有操作。每个操作都是一个单独的函数,它将接收 self 作为唯一参数。

在我们的例子中,操作是 myaction - 一个简单的表单。

此函数可以调用模型的函数来更新和/或检索数据(例如 self.model.getdata())。

此函数返回的任何内容都将传递给视图

myapp-model.lua

控制器可以访问此处定义的函数来更新/设置/检索数据、启动/停止服务,基本上执行任何“实际工作”。

在我们的例子中,我们实现了表单所需的 getdata/setdata 函数。

getdata 函数接收 'self' 的副本、clientdata 表和一个包含提交操作的字符串。它将生成一个“CFE”表,定义表单并包含当前数据。

setdata 函数仅在提交表单时调用,它接收 'self' 的副本和更新后的表单“CFE”,其中现在包含提交的数据。setdata 函数将尝试执行该操作,并返回相同的表单“CFE”。如果出现错误,它将在“CFE”的 errtxt 字段中填写错误信息。

myapp.roles

此文件确定哪些用户有权访问哪些控制器和视图。通常为每个 ACF 定义一个单独的roles文件。该文件的格式如下

group=controller:action[,controller:action]

每一行定义特定组允许的 controller:action 组合。GUEST 是一个特殊组,所有用户(包括匿名用户)都是其成员。

myapp.menu

在此文件中,您定义

  • 类别,您的程序的菜单项将出现在其中
  • ,此控制器的类目下方的菜单名称
  • 标签,控制器页面上的名称
  • 操作,一旦用户单击由类别、组和标签定义的菜单项或选项卡,将调用控制器内的操作。

步骤 7 - 如何启动它?

完成上述所有步骤后,继续进行

  • sudo make install(这将安装您的应用程序)
  • 将您的浏览器指向 https://ip-of-your-dev-host/

更多信息

视图在哪里?

上面的示例不包含任何视图代码。那么,操作是如何显示的呢?

对于您在 myapp-controller.lua 中定义的每个操作,您可以定义一个单独的视图文件,命名为:myapp-action-html.lsp

如果特定操作没有视图文件,应用程序将查找控制器的通用视图文件,命名为:myapp-html.lsp

如果该文件不存在,ACF 控制器将尝试使用内置库函数显示“CFE”。这对于表单非常有效,并且允许我们在此处显示视图。

这是一个视图文件,它使用内置库函数显示我们的操作。它看起来与不存在视图时完全相同。

myapp-myaction-html.lsp

<%
local form, viewlibrary, page_info, session = ...
htmlviewfunctions = require("htmlviewfunctions")

htmlviewfunctions.displayitem(form, page_info)
%>

视图从控制器接收要显示的数据。视图可以访问控制器操作返回的表,以及一个辅助库、一个页面信息表和会话数据(请参阅第二行)。视图还可以加载其他库,但它不应直接访问控制器模型或任何全局变量。

如何在模型-视图-控制器之间交换数据?

为了在模型、视图和控制器之间交换数据,ACF 使用配置框架实体 (CFE)

有关 CFE 的更多详细信息,请参阅 ACF_core_principles

参见