WIP: awesomerc.tl should work #85

Draft
Aire-One wants to merge 40 commits from feat/#58 into master
25 changed files with 1770 additions and 204 deletions

4
.gitignore vendored
View File

@ -1,4 +1,6 @@
lua_modules/
generated/
generated/*
!generated/awesomerc.tl
!generated/tlconfig.lua
build/
luacov/

2
.vscode/launch.json vendored
View File

@ -20,7 +20,7 @@
},
"args": [
"test",
"_spec" // Adapt to the spec to debug
"\"--pattern _spec --tags debug\""
],
"scriptFiles": ["${workspaceFolder}/**/*.tl"]
}

View File

@ -9,6 +9,7 @@
"aireone",
"akorn",
"ansicolors",
"awesomerc",
"awesomewm",
"awesomewmdtl",
"buildx",
@ -31,6 +32,7 @@
"Luarocks",
"luasec",
"luasocket",
"metamethods",
"mkdir",
"modname",
"reportfile",
@ -45,6 +47,7 @@
"sublist",
"tablex",
"tbody",
"tlconfig",
"tmpl",
"wibox",
"woodpeckerci",

View File

@ -0,0 +1,15 @@
variables:
- &cyan gitea.aireone.xyz/aire-one/awesomewm.d.tl/cyan:latest
pipeline:
build-and-run:
image: *cyan
commands:
- luarocks make awesomewmdtl-dev-1.rockspec
- awesomewmdtl
- just validate
branches: master
depends_on:
- docker-build

View File

@ -1,23 +0,0 @@
variables:
- &cyan gitea.aireone.xyz/aire-one/awesomewm.d.tl/cyan:latest
pipeline:
build:
image: *cyan
commands:
- just build
# run:
# image: akorn/luarocks:lua5.4-alpine
# commands:
# - apk add just
# - just run
# verify:
# image: alpine:3.16
# commands:
# - apk add tree
# - tree generated
branches: master
depends_on:
- docker-build

View File

@ -57,6 +57,7 @@ build = {
["awesomewmdtl.types.Dag"] = "build/awesomewmdtl/types/Dag.lua",
["awesomewmdtl.types.Node"] = "build/awesomewmdtl/types/Node.lua",
["awesomewmdtl.visitors.module_dependencies"] = "build/awesomewmdtl/visitors/module_dependencies.lua",
["awesomewmdtl.visitors.node_fixer"] = "build/awesomewmdtl/visitors/node_fixer.lua",
["awesomewmdtl.visitors.type_mapping"] = "build/awesomewmdtl/visitors/type_mapping.lua",
["awesomewmdtl.ast"] = "build/awesomewmdtl/ast.lua",
["awesomewmdtl.dag"] = "build/awesomewmdtl/dag.lua",

675
generated/awesomerc.tl Normal file
View File

@ -0,0 +1,675 @@
-- awesome_mode: api-level=4:screen=on
-- If LuaRocks is installed, make sure that packages installed through it are
-- found (e.g. lgi). If LuaRocks is not installed, do nothing.
--- TODO : Write luarocks basic types definitions
-- pcall(require, "luarocks.loader")
-- @DOC_REQUIRE_SECTION@
-- Standard awesome library
local gears = require("gears")
local awful = require("awful")
require("awful.autofocus")
-- Widget and layout library
local wibox = require("wibox")
-- Theme handling library
local beautiful = require("beautiful")
-- Notification library
local naughty = require("naughty")
-- Declarative object management
local ruled = require("ruled")
local menubar = require("menubar")
local hotkeys_popup = require("awful.hotkeys_popup")
-- Enable hotkeys help widget for VIM and other apps
-- when client with a matching name is opened:
require("awful.hotkeys_popup.keys")
-- {{{ Error handling
-- Check if awesome encountered an error during startup and fell back to
-- another config (This code will only ever execute for the fallback config)
-- @DOC_ERROR_HANDLING@
naughty.connect_signal("request::display_error", function(message, startup)
naughty.notification {
urgency = "critical",
title = "Oops, an error happened"
.. (startup and " during startup!" or "!"),
message = message,
}
end)
-- }}}
-- {{{ Variable definitions
-- @DOC_LOAD_THEME@
-- Themes define colours, icons, font and wallpapers.
beautiful.init(gears.filesystem.get_themes_dir() .. "default/theme.lua")
-- @DOC_DEFAULT_APPLICATIONS@
-- This is used later as the default terminal and editor to run.
local terminal = "xterm"
local editor = os.getenv "EDITOR" or "nano"
local editor_cmd = terminal .. " -e " .. editor
-- Default modkey.
-- Usually, Mod4 is the key with a logo between Control and Alt.
-- If you do not like this or do not have such a key,
-- I suggest you to remap Mod4 to another key using xmodmap or other tools.
-- However, you can use another modifier like Mod1, but it may interact with others.
local modkey = "Mod4"
-- }}}
-- {{{ Menu
-- @DOC_MENU@
-- Create a launcher widget and a main menu
local myawesomemenu = {
{
"hotkeys",
function()
hotkeys_popup.show_help(nil, awful.screen.focused())
end,
},
{ "manual", terminal .. " -e man awesome" },
{ "edit config", editor_cmd .. " " .. awesome.conffile },
{ "restart", awesome.restart },
{
"quit",
function()
awesome.quit()
end,
},
}
local mymainmenu = awful.menu {
items = {
{ "awesome", myawesomemenu, beautiful.awesome_icon },
{ "open terminal", terminal },
},
}
local mylauncher = awful.widget.launcher {
image = beautiful.awesome_icon,
menu = mymainmenu,
}
-- Menubar configuration
menubar.utils.terminal = terminal -- Set the terminal for applications that require it
-- }}}
-- {{{ Tag layout
-- @DOC_LAYOUT@
-- Table of layouts to cover with awful.layout.inc, order matters.
tag.connect_signal("request::default_layouts", function()
awful.layout.append_default_layouts {
awful.layout.suit.floating,
awful.layout.suit.tile,
awful.layout.suit.tile.left,
awful.layout.suit.tile.bottom,
awful.layout.suit.tile.top,
awful.layout.suit.fair,
awful.layout.suit.fair.horizontal,
awful.layout.suit.spiral,
awful.layout.suit.spiral.dwindle,
awful.layout.suit.max,
awful.layout.suit.max.fullscreen,
awful.layout.suit.magnifier,
awful.layout.suit.corner.nw,
}
end)
-- }}}
-- {{{ Wallpaper
-- @DOC_WALLPAPER@
screen.connect_signal("request::wallpaper", function(s)
awful.wallpaper {
screen = s,
widget = {
{
image = beautiful.wallpaper,
upscale = true,
downscale = true,
widget = wibox.widget.imagebox,
},
valign = "center",
halign = "center",
tiled = false,
widget = wibox.container.tile,
},
}
end)
-- }}}
-- {{{ Wibar
-- Keyboard map indicator and switcher
local mykeyboardlayout = awful.widget.keyboardlayout()
-- Create a textclock widget
local mytextclock = wibox.widget.textclock()
-- @DOC_FOR_EACH_SCREEN@
screen.connect_signal("request::desktop_decoration", function(s: screen)
-- Each screen has its own tag table.
awful.tag(
{ "1", "2", "3", "4", "5", "6", "7", "8", "9" },
s,
awful.layout.layouts[1]
)
-- Create a promptbox for each screen
s.mypromptbox = awful.widget.prompt()
-- Create an imagebox widget which will contain an icon indicating which layout we're using.
-- We need one layoutbox per screen.
s.mylayoutbox = awful.widget.layoutbox {
screen = s,
buttons = {
awful.button({}, 1, function()
awful.layout.inc(1)
end),
awful.button({}, 3, function()
awful.layout.inc(-1)
end),
awful.button({}, 4, function()
awful.layout.inc(-1)
end),
awful.button({}, 5, function()
awful.layout.inc(1)
end),
},
}
-- Create a taglist widget
s.mytaglist = awful.widget.taglist {
screen = s,
filter = awful.widget.taglist.filter.all,
buttons = {
awful.button({}, 1, function(t: tag)
t:view_only()
end),
awful.button({ modkey }, 1, function(t: tag)
if client.focus then
client.focus:move_to_tag(t)
end
end),
awful.button({}, 3, awful.tag.viewtoggle),
awful.button({ modkey }, 3, function(t: tag)
if client.focus then
client.focus:toggle_tag(t)
end
end),
awful.button({}, 4, function(t: tag)
awful.tag.viewprev(t.screen)
end),
awful.button({}, 5, function(t: tag)
awful.tag.viewnext(t.screen)
end),
},
}
-- @TASKLIST_BUTTON@
-- Create a tasklist widget
s.mytasklist = awful.widget.tasklist {
screen = s,
filter = awful.widget.tasklist.filter.currenttags,
buttons = {
awful.button({}, 1, function(c)
c:activate { context = "tasklist", action = "toggle_minimization" }
end),
awful.button({}, 3, function()
awful.menu.client_list { theme = { width = 250 } }
end),
awful.button({}, 4, function()
awful.client.focus.byidx(-1)
end),
awful.button({}, 5, function()
awful.client.focus.byidx(1)
end),
},
}
-- @DOC_WIBAR@
-- Create the wibox
s.mywibox = awful.wibar {
position = "top",
screen = s,
-- @DOC_SETUP_WIDGETS@
widget = {
layout = wibox.layout.align.horizontal,
{ -- Left widgets
layout = wibox.layout.fixed.horizontal,
mylauncher,
s.mytaglist,
s.mypromptbox,
},
s.mytasklist, -- Middle widget
{ -- Right widgets
layout = wibox.layout.fixed.horizontal,
mykeyboardlayout,
wibox.widget.systray(),
mytextclock,
s.mylayoutbox,
},
},
}
end)
-- }}}
-- {{{ Mouse bindings
-- @DOC_ROOT_BUTTONS@
awful.mouse.append_global_mousebindings {
awful.button({}, 3, function()
mymainmenu:toggle()
end),
awful.button({}, 4, awful.tag.viewprev),
awful.button({}, 5, awful.tag.viewnext),
}
-- }}}
-- {{{ Key bindings
-- @DOC_GLOBAL_KEYBINDINGS@
-- General Awesome keys
awful.keyboard.append_global_keybindings {
awful.key(
{ modkey },
"s",
hotkeys_popup.show_help,
{ description = "show help", group = "awesome" }
),
awful.key({ modkey }, "w", function()
mymainmenu:show()
end, { description = "show main menu", group = "awesome" }),
awful.key(
{ modkey, "Control" },
"r",
awesome.restart,
{ description = "reload awesome", group = "awesome" }
),
awful.key(
{ modkey, "Shift" },
"q",
awesome.quit,
{ description = "quit awesome", group = "awesome" }
),
awful.key({ modkey }, "x", function()
awful.prompt.run {
prompt = "Run Lua code: ",
textbox = awful.screen.focused().mypromptbox.widget,
exe_callback = awful.util.eval,
history_path = awful.util.get_cache_dir() .. "/history_eval",
}
end, { description = "lua execute prompt", group = "awesome" }),
awful.key({ modkey }, "Return", function()
awful.spawn(terminal)
end, { description = "open a terminal", group = "launcher" }),
awful.key({ modkey }, "r", function()
awful.screen.focused().mypromptbox:run()
end, { description = "run prompt", group = "launcher" }),
awful.key({ modkey }, "p", function()
menubar.show()
end, { description = "show the menubar", group = "launcher" }),
}
-- Tags related keybindings
awful.keyboard.append_global_keybindings {
awful.key(
{ modkey },
"Left",
awful.tag.viewprev,
{ description = "view previous", group = "tag" }
),
awful.key(
{ modkey },
"Right",
awful.tag.viewnext,
{ description = "view next", group = "tag" }
),
awful.key(
{ modkey },
"Escape",
awful.tag.history.restore,
{ description = "go back", group = "tag" }
),
}
-- Focus related keybindings
awful.keyboard.append_global_keybindings {
awful.key({ modkey }, "j", function()
awful.client.focus.byidx(1)
end, { description = "focus next by index", group = "client" }),
awful.key({ modkey }, "k", function()
awful.client.focus.byidx(-1)
end, { description = "focus previous by index", group = "client" }),
awful.key({ modkey }, "Tab", function()
awful.client.focus.history.previous()
if client.focus then
client.focus:raise()
end
end, { description = "go back", group = "client" }),
awful.key({ modkey, "Control" }, "j", function()
awful.screen.focus_relative(1)
end, { description = "focus the next screen", group = "screen" }),
awful.key({ modkey, "Control" }, "k", function()
awful.screen.focus_relative(-1)
end, { description = "focus the previous screen", group = "screen" }),
awful.key({ modkey, "Control" }, "n", function()
local c = awful.client.restore()
-- Focus restored client
if c then
c:activate { raise = true, context = "key.unminimize" }
end
end, { description = "restore minimized", group = "client" }),
}
-- Layout related keybindings
awful.keyboard.append_global_keybindings {
awful.key({ modkey, "Shift" }, "j", function()
awful.client.swap.byidx(1)
end, { description = "swap with next client by index", group = "client" }),
awful.key(
{ modkey, "Shift" },
"k",
function()
awful.client.swap.byidx(-1)
end,
{ description = "swap with previous client by index", group = "client" }
),
awful.key(
{ modkey },
"u",
awful.client.urgent.jumpto,
{ description = "jump to urgent client", group = "client" }
),
awful.key({ modkey }, "l", function()
awful.tag.incmwfact(0.05)
end, { description = "increase master width factor", group = "layout" }),
awful.key({ modkey }, "h", function()
awful.tag.incmwfact(-0.05)
end, { description = "decrease master width factor", group = "layout" }),
awful.key({ modkey, "Shift" }, "h", function()
awful.tag.incnmaster(1, nil, true)
end, {
description = "increase the number of master clients",
group = "layout",
}),
awful.key({ modkey, "Shift" }, "l", function()
awful.tag.incnmaster(-1, nil, true)
end, {
description = "decrease the number of master clients",
group = "layout",
}),
awful.key({ modkey, "Control" }, "h", function()
awful.tag.incncol(1, nil, true)
end, { description = "increase the number of columns", group = "layout" }),
awful.key({ modkey, "Control" }, "l", function()
awful.tag.incncol(-1, nil, true)
end, { description = "decrease the number of columns", group = "layout" }),
awful.key({ modkey }, "space", function()
awful.layout.inc(1)
end, { description = "select next", group = "layout" }),
awful.key({ modkey, "Shift" }, "space", function()
awful.layout.inc(-1)
end, { description = "select previous", group = "layout" }),
}
-- @DOC_NUMBER_KEYBINDINGS@
awful.keyboard.append_global_keybindings {
awful.key {
modifiers = { modkey },
keygroup = "numrow",
description = "only view tag",
group = "tag",
on_press = function(index)
local screen = awful.screen.focused()
local tag = screen.tags[index]
if tag then
tag:view_only()
end
end,
},
awful.key {
modifiers = { modkey, "Control" },
keygroup = "numrow",
description = "toggle tag",
group = "tag",
on_press = function(index)
local screen = awful.screen.focused()
local tag = screen.tags[index]
if tag then
awful.tag.viewtoggle(tag)
end
end,
},
awful.key {
modifiers = { modkey, "Shift" },
keygroup = "numrow",
description = "move focused client to tag",
group = "tag",
on_press = function(index)
if client.focus then
local tag = client.focus.screen.tags[index]
if tag then
client.focus:move_to_tag(tag)
end
end
end,
},
awful.key {
modifiers = { modkey, "Control", "Shift" },
keygroup = "numrow",
description = "toggle focused client on tag",
group = "tag",
on_press = function(index)
if client.focus then
local tag = client.focus.screen.tags[index]
if tag then
client.focus:toggle_tag(tag)
end
end
end,
},
awful.key {
modifiers = { modkey },
keygroup = "numpad",
description = "select layout directly",
group = "layout",
on_press = function(index)
local t = awful.screen.focused().selected_tag
if t then
t.layout = t.layouts[index] or t.layout
end
end,
},
}
-- @DOC_CLIENT_BUTTONS@
client.connect_signal("request::default_mousebindings", function()
awful.mouse.append_client_mousebindings {
awful.button({}, 1, function(c)
c:activate { context = "mouse_click" }
end),
awful.button({ modkey }, 1, function(c)
c:activate { context = "mouse_click", action = "mouse_move" }
end),
awful.button({ modkey }, 3, function(c)
c:activate { context = "mouse_click", action = "mouse_resize" }
end),
}
end)
-- @DOC_CLIENT_KEYBINDINGS@
client.connect_signal("request::default_keybindings", function()
awful.keyboard.append_client_keybindings {
awful.key({ modkey }, "f", function(c: client)
c.fullscreen = not c.fullscreen
c:raise()
end, { description = "toggle fullscreen", group = "client" }),
awful.key({ modkey, "Shift" }, "c", function(c: client)
c:kill()
end, { description = "close", group = "client" }),
awful.key(
{ modkey, "Control" },
"space",
awful.client.floating.toggle,
{ description = "toggle floating", group = "client" }
),
awful.key({ modkey, "Control" }, "Return", function(c: client)
c:swap(awful.client.getmaster())
end, { description = "move to master", group = "client" }),
awful.key({ modkey }, "o", function(c: client)
c:move_to_screen()
end, { description = "move to screen", group = "client" }),
awful.key({ modkey }, "t", function(c: client)
c.ontop = not c.ontop
end, { description = "toggle keep on top", group = "client" }),
awful.key({ modkey }, "n", function(c: client)
-- The client currently has the input focus, so it cannot be
-- minimized, since minimized clients can't have the focus.
c.minimized = true
end, { description = "minimize", group = "client" }),
awful.key({ modkey }, "m", function(c: client)
c.maximized = not c.maximized
c:raise()
end, { description = "(un)maximize", group = "client" }),
awful.key({ modkey, "Control" }, "m", function(c: client)
c.maximized_vertical = not c.maximized_vertical
c:raise()
end, { description = "(un)maximize vertically", group = "client" }),
awful.key({ modkey, "Shift" }, "m", function(c: client)
c.maximized_horizontal = not c.maximized_horizontal
c:raise()
end, { description = "(un)maximize horizontally", group = "client" }),
}
end)
-- }}}
-- {{{ Rules
-- Rules to apply to new clients.
-- @DOC_RULES@
ruled.client.connect_signal("request::rules", function()
-- @DOC_GLOBAL_RULE@
-- All clients will match this rule.
ruled.client.append_rule {
id = "global",
rule = {},
properties = {
focus = awful.client.focus.filter,
raise = true,
screen = awful.screen.preferred,
placement = awful.placement.no_overlap + awful.placement.no_offscreen,
},
}
-- @DOC_FLOATING_RULE@
-- Floating clients.
ruled.client.append_rule {
id = "floating",
rule_any = {
instance = { "copyq", "pinentry" },
class = {
"Arandr",
"Blueman-manager",
"Gpick",
"Kruler",
"Sxiv",
"Tor Browser",
"Wpa_gui",
"veromix",
"xtightvncviewer",
},
-- Note that the name property shown in xprop might be set slightly after creation of the client
-- and the name shown there might not match defined rules here.
name = {
"Event Tester", -- xev.
},
role = {
"AlarmWindow", -- Thunderbird's calendar.
"ConfigManager", -- Thunderbird's about:config.
"pop-up", -- e.g. Google Chrome's (detached) Developer Tools.
},
},
properties = { floating = true },
}
-- @DOC_DIALOG_RULE@
-- Add titlebars to normal clients and dialogs
ruled.client.append_rule {
-- @DOC_CSD_TITLEBARS@
id = "titlebars",
rule_any = { type = { "normal", "dialog" } },
properties = { titlebars_enabled = true },
}
-- Set Firefox to always map on the tag named "2" on screen 1.
-- ruled.client.append_rule {
-- rule = { class = "Firefox" },
-- properties = { screen = 1, tag = "2" }
-- }
end)
-- }}}
-- {{{ Titlebars
-- @DOC_TITLEBARS@
-- Add a titlebar if titlebars_enabled is set to true in the rules.
client.connect_signal("request::titlebars", function(c: client)
-- buttons for the titlebar
local buttons = {
awful.button({}, 1, function()
c:activate { context = "titlebar", action = "mouse_move" }
end),
awful.button({}, 3, function()
c:activate { context = "titlebar", action = "mouse_resize" }
end),
}
awful.titlebar(c).widget = {
{ -- Left
awful.titlebar.widget.iconwidget(c),
buttons = buttons,
layout = wibox.layout.fixed.horizontal,
},
{ -- Middle
{ -- Title
halign = "center",
widget = awful.titlebar.widget.titlewidget(c),
},
buttons = buttons,
layout = wibox.layout.flex.horizontal,
},
{ -- Right
awful.titlebar.widget.floatingbutton(c),
awful.titlebar.widget.maximizedbutton(c),
awful.titlebar.widget.stickybutton(c),
awful.titlebar.widget.ontopbutton(c),
awful.titlebar.widget.closebutton(c),
layout = wibox.layout.fixed.horizontal(),
},
layout = wibox.layout.align.horizontal,
}
end)
-- }}}
-- {{{ Notifications
ruled.notification.connect_signal("request::rules", function()
-- All notifications will match this rule.
ruled.notification.append_rule {
rule = {},
properties = {
screen = awful.screen.preferred,
implicit_timeout = 5,
},
}
end)
naughty.connect_signal("request::display", function(n)
naughty.layout.box { notification = n }
end)
-- }}}
-- Enable sloppy focus, so that focus follows mouse.
client.connect_signal("mouse::enter", function(c: client)
c:activate { context = "mouse_enter", raise = false }
end)

4
generated/tlconfig.lua Normal file
View File

@ -0,0 +1,4 @@
return {
source_dir = ".",
global_env_def = "global_env_def",
}

View File

@ -62,16 +62,14 @@ run:
validate:
cd generated && cyan \
check \
--global-env-def "global_env" \
awesomerc.tl
# `find . -type f -iname '*.d.tl' | xargs`
test PATTERN="_spec":
test PARAMETERS="--pattern=_spec":
luarocks \
--lua-version {{ lua_version }} \
test \
{{ rockspec_file }} \
-- --pattern={{ PATTERN }}
-- {{ PARAMETERS }}
local-test PATTERN="_spec":
luarocks \

View File

@ -22,6 +22,7 @@ describe("Teal type definition Printer", function()
{
children = {},
dependencies = {},
descendants = {},
name = "Empty",
token = "module",
},
@ -53,6 +54,7 @@ describe("Teal type definition Printer", function()
},
},
dependencies = {},
descendants = {},
name = "Signal_Module",
token = "module",
},
@ -79,6 +81,7 @@ describe("Teal type definition Printer", function()
}
},
dependencies = {},
descendants = {},
name = "Property_Module",
token = "module",
},
@ -108,12 +111,13 @@ describe("Teal type definition Printer", function()
token = "variable",
},
},
return_types = { "boolean" },
return_types = { { "boolean" }, { "nil", "number" } },
name = "kill",
token = "function",
},
},
dependencies = {},
descendants = {},
name = "Function_Module",
token = "module",
},
@ -121,7 +125,7 @@ describe("Teal type definition Printer", function()
-- This file was auto-generated.
local record Function_Module
kill: function(pid: integer, sig: integer): boolean
kill: function(pid: integer, sig: integer): boolean, nil | number
end
return Function_Module
@ -137,6 +141,7 @@ describe("Teal type definition Printer", function()
}
},
dependencies = {},
descendants = {},
name = "Nested_Module",
token = "module",
},
@ -158,6 +163,7 @@ describe("Teal type definition Printer", function()
dep = "dep",
deeper = "path.dep.deeper",
},
descendants = {},
name = "Module",
token = "module",
},
@ -171,4 +177,104 @@ describe("Teal type definition Printer", function()
return Module
]]))
it("should print __call metamethod", gen(
{
children = {
{
children = {},
name = "Signal",
token = "enum",
},
{
parameters = {
{
types = { "Timer" },
name = "self",
token = "variable",
},
},
return_types = {},
name = "timer",
metamethod = "__call",
token = "metamethod",
}
},
name = "Timer",
module_path = "gears.timer",
dependencies = {},
descendants = {},
global = false,
token = "module",
},
[[
-- This file was auto-generated.
local record Timer
enum Signal
end
metamethod __call: function(self: Timer)
end
return Timer
]]))
it("should print a module with descendants", gen(
{
children = {
{
children = {},
name = "Signal",
token = "enum",
},
{
parameters = {},
return_types = {},
name = "draw_to_cairo_context",
token = "function",
},
{
children = {
{
children = {},
name = "Signal",
token = "enum",
},
{
parameters = {},
return_types = {},
name = "imagebox",
metamethod = "__call",
token = "metamethod",
}
},
name = "Imagebox",
module_path = "wibox.widget.imagebox",
dependencies = {},
global = false,
token = "module",
}
},
name = "Widget",
module_path = "wibox.widget",
dependencies = {},
descendants = {
Imagebox = "wibox.widget.imagebox",
},
global = false,
token = "module",
},
[[
-- This file was auto-generated.
local type Imagebox = require("wibox.widget.imagebox")
local record Widget
enum Signal
end
draw_to_cairo_context: function()
imagebox: Imagebox
end
return Widget
]]))
end)

View File

@ -27,6 +27,8 @@ describe("Scrap documentation", function()
name = "Empty",
module_path = "empty",
dependencies = {},
descendants = {},
global = false,
token = "module",
}))
@ -89,6 +91,8 @@ describe("Scrap documentation", function()
name = "Property_signal",
module_path = "property_signal",
dependencies = {},
descendants = {},
global = false,
token = "module",
}))
@ -202,6 +206,8 @@ describe("Scrap documentation", function()
name = "Property_enum",
module_path = "property_enum",
dependencies = {},
descendants = {},
global = false,
token = "module",
}))
@ -244,6 +250,8 @@ describe("Scrap documentation", function()
name = "Property_string",
module_path = "property_string",
dependencies = {},
descendants = {},
global = false,
token = "module",
}))
@ -311,6 +319,8 @@ describe("Scrap documentation", function()
name = "Tag",
module_path = "awful.tag",
dependencies = {},
descendants = {},
global = false,
token = "module",
}))
@ -363,6 +373,8 @@ describe("Scrap documentation", function()
name = "Signal",
module_path = "signal",
dependencies = {},
descendants = {},
global = false,
token = "module",
}))
@ -450,14 +462,16 @@ describe("Scrap documentation", function()
token = "variable",
},
},
return_types = { "boolean" },
return_types = { { "boolean" }},
name = "kill",
token = "function",
}
},
name = "Awesome",
name = "awesome",
module_path = "awesome",
dependencies = {},
descendants = {},
global = false,
token = "module",
}))
@ -566,6 +580,7 @@ describe("Scrap documentation", function()
}
},
name = "Focused_Args",
global = false,
token = "record",
},
{
@ -576,7 +591,7 @@ describe("Scrap documentation", function()
token = "variable",
},
},
return_types = { "screen" },
return_types = { { "screen" }},
name = "focused",
token = "function",
},
@ -584,6 +599,8 @@ describe("Scrap documentation", function()
name = "Screen",
module_path = "awful.screen",
dependencies = {},
descendants = {},
global = false,
token = "module",
}))
@ -667,7 +684,7 @@ describe("Scrap documentation", function()
token = "variable",
}
},
return_types = { "table" },
return_types = { { "table" }},
name = "crush",
token = "function",
}
@ -675,6 +692,8 @@ describe("Scrap documentation", function()
name = "Table",
module_path = "gears.table",
dependencies = {},
descendants = {},
global = false,
token = "module",
}))
@ -759,7 +778,7 @@ describe("Scrap documentation", function()
token = "variable",
},
},
return_types = { "table" },
return_types = { { "table" } },
name = "tags",
token = "function",
},
@ -767,6 +786,8 @@ describe("Scrap documentation", function()
name = "Client",
module_path = "awful.client",
dependencies = {},
descendants = {},
global = false,
token = "module",
}))
@ -810,14 +831,145 @@ describe("Scrap documentation", function()
name = "Client",
module_path = "awful.client",
dependencies = {},
descendants = {},
global = false,
token = "module",
},
{
{
parameters = {},
return_types = { "integer" },
return_types = { { "integer" } },
name = "client.instances",
token = "function",
}
}))
it("should parse function return union types and multiple return values", test(
[[
<h2 class="section-header "><a name="Static_module_functions"></a>Static module functions</h2>
<dl class="function">
<dt>
<a class="copy-link js-copy-link" name="load_image" href="#load_image">🔗</a>
<strong><span class="function_modname">awesome.</span>load_image <span class="function_args"> <b>(</b>name<b>)</b></span></strong>
<span class="proptype">
<span class="summary_type"> -&gt;&nbsp;(gears.surface, nil <i>or</i> string)</span></span>
<span class="baseclass">
</span>
</dt>
<dd>
Load an image from a given path.
<h3>Returns:</h3>
<ol>
<li>
<span class="types"><span class="type">gears.surface</span></span>
A cairo surface as light user datum.
</li>
<li>
<span class="types"><span class="type">nil</span> or <a class="type" href="https://www.lua.org/manual/5.4/manual.html#6.4" target="_blank">string</a></span>
The error message, if any.
</li>
</ol>
</dd>
</dl>
]],
"awesome",
{
children = {
{
children = {},
name = "Signal",
token = "enum",
},
{
parameters = {},
return_types = { { "gears.surface" }, { "nil", "string" } },
name = "load_image",
token = "function",
}
},
name = "awesome",
module_path = "awesome",
dependencies = {},
descendants = {},
global = false,
token = "module",
}))
it("should produce a __call metamethod for constructor", test(
[[
<h2 class="section-header "><a name="Constructors"></a>Constructors</h2>
<dl class="function">
<dt>
<a class="copy-link js-copy-link" name="gears.timer" href="#gears.timer">🔗</a>
<strong>gears.timer <span class="function_named_args"><b>{</b>[args]<b>}</b></span></strong>
<span class="baseclass"></span>
</dt>
<dd>
</dd>
</dl>
]],
"gears.timer",
{
children = {
{
children = {},
name = "Signal",
token = "enum",
},
{
parameters = {
{
types = { "Timer" },
name = "self",
token = "variable",
},
},
return_types = {},
name = "timer",
metamethod = "__call",
token = "metamethod",
}
},
name = "Timer",
module_path = "gears.timer",
dependencies = {},
descendants = {},
global = false,
token = "module",
}))
it("should produce Variable for content in the Fields section", test(
[[
<h2 class="section-header "><a name="Fields"></a>Fields</h2>
<dl class="function">
<dt>
<a class="copy-link js-copy-link" name="primary" href="#primary">🔗</a>
<strong><span class="function_modname">screen.</span>primary</strong>
<span class="proptype"><span class="summary_type">screen</span></span>
<span class="baseclass"></span>
</dt>
<dd></dd>
</dl>
]],
"field_variable",
{
children = {
{
children = {},
name = "Signal",
token = "enum",
},
{
name = "primary",
types = { "screen" },
token = "variable",
}
},
name = "Field_variable",
module_path = "field_variable",
dependencies = {},
descendants = {},
global = false,
token = "module",
}))
end)

View File

@ -1,18 +1,21 @@
local type Node = require("awesomewmdtl.types.Node")
local basic_nodes <total>: { Node.Token : function(name: string, ...: any): Node } = {
module = function(name: string, module_path: string): Node
module = function(name: string, module_path: string, global: boolean): Node
return {
token = "module",
global = global,
name = name,
module_path = module_path,
dependencies = {},
descendants = {},
children = {},
}
end,
record = function(name: string): Node
record = function(name: string, global: boolean): Node
return {
token = "record",
global = global,
name = name,
children = {},
}
@ -53,6 +56,14 @@ local basic_nodes <total>: { Node.Token : function(name: string, ...: any): Node
return_types = {},
}
end,
type = function(name: string, types: { string }, global: boolean): Node
return {
token = "type",
global = global,
name = name,
types = types,
}
end
}
local function create_node(token: Node.Token, name: string, ...: any): Node
@ -60,6 +71,47 @@ local function create_node(token: Node.Token, name: string, ...: any): Node
return node
end
-- TODO : be less permissive and allow to merge only module ?
local function merge_nodes(node: Node, other: Node): Node
if node.token ~= other.token then
error("Cannot merge nodes of different types")
end
if node.name ~= other.name then
error("Cannot merge nodes of different names")
end
if node.module_path ~= other.module_path then
error("Cannot merge nodes of different module paths")
end
-- Basic fields should be overwritten by the new values
-- Teal wants us to use enums value to index a record, but it fails to understand my enum with
-- any other key from Node, so I'm forcing it to cooperate for now
local enum NodeField
"types"
end
for _, field in ipairs({"global", "types", "parameters", "return_types", "metamethod"}) do
local f = field as NodeField
node[f] = other[f]
end
-- Children should be merged
local children: { string : integer } = {}
for i, child in ipairs(node.children) do
children[child.name] = i
end
for _, child in ipairs(other.children) do
if children[child.name] ~= nil then
table.remove(node.children, children[child.name])
end
table.insert(node.children, child)
end
-- Dependencies should be merged
for name, path in pairs(other.dependencies) do
node.dependencies[name] = path
end
end
local function iter_children(node: Node): function(): integer, Node
if node.children == nil then
return function(): integer, Node end
@ -67,6 +119,7 @@ local function iter_children(node: Node): function(): integer, Node
return ipairs(node.children)
end
-- TODO : deprecate in favor of the iterator definition ?
local function in_order_visitor(node: Node, visitor: function(Node))
for _, child in iter_children(node) do
in_order_visitor(child, visitor)
@ -74,8 +127,28 @@ local function in_order_visitor(node: Node, visitor: function(Node))
visitor(node)
end
local function iter_in_order(node: Node): function(): Node
local stack: { Node } = {}
local function traverse(n: Node)
for _, child in iter_children(n) do
traverse(child)
end
table.insert(stack, n)
end
traverse(node)
local i = 0
return function(): Node
i = i + 1
return stack[i]
end
end
return {
create_node = create_node,
merge_nodes = merge_nodes,
iter_children = iter_children,
in_order_visitor = in_order_visitor,
iter_in_order = iter_in_order,
}

View File

@ -1,3 +1,4 @@
local ast = require("awesomewmdtl.ast")
local type Dag = require("awesomewmdtl.types.Dag")
local type Node = require("awesomewmdtl.types.Node")
local utils <const> = require("awesomewmdtl.utils")
@ -10,16 +11,90 @@ local function new(): Dag
return dag
end
local function push_module(dag: Dag, module_path: string, root_node: Node)
dag.modules[module_path] = root_node
local function push_module(dag: Dag, module_path: string, node: Node)
local parent: Node = { children = dag.modules }
local current_path = ""
for breadcrumb in module_path:gmatch("([^%.]+)") do
current_path = current_path == "" and breadcrumb or current_path .. "." .. breadcrumb
if current_path == node.module_path then
local found = utils.find(parent.children, function(n: Node): boolean
return n.module_path == node.module_path
end)
if found then
ast.merge_nodes(found, node)
else
table.insert(parent.children, node)
end
return
end
local current: Node = nil
for _, n in ipairs(parent.children) do
if n.module_path == current_path then
current = n
break
end
end
if current == nil then
current = {
children = {},
name = utils.capitalize(breadcrumb),
module_path = current_path,
dependencies = {},
descendants = {},
token = "module",
}
push_module(dag, current_path, current)
end
parent = current
end
end
local function push_global_nodes(dag: Dag, nodes: { Node })
utils.spread(dag.global_nodes, nodes)
end
local function iter_modules(dag: Dag): function(): string, Node
return pairs(dag.modules)
local function iter_modules(dag: Dag): (function(): Node)
local stack: { Node } = {}
for _, root in ipairs(dag.modules) do
for node in ast.iter_in_order(root) do
if node.token == "module" then
table.insert(stack, node)
end
end
end
local i = 0
return function(): Node
i = i + 1
return stack[i]
end
end
local function iter_global_nodes(dag: Dag): (function(): integer, Node)
return pairs(dag.global_nodes)
end
local function find_module(dag: Dag, module_path: string): Node
local current_path = ""
local current: Node = { children = dag.modules }
for breadcrumb in module_path:gmatch("([^%.]+)") do
current_path = current_path == "" and breadcrumb or current_path .. "." .. breadcrumb
for _, child in ipairs(current.children) do
if child.module_path == module_path then
return child
end
if child.module_path == current_path then
current = child
goto continue
end
end
break
::continue::
end
return nil
end
return {
@ -27,4 +102,6 @@ return {
push_module = push_module,
push_global_nodes = push_global_nodes,
iter_modules = iter_modules,
iter_global_nodes = iter_global_nodes,
find_module = find_module,
}

View File

@ -9,13 +9,14 @@ local filesystem <const> = require("awesomewmdtl.filesystem")
local printer <const> = require("awesomewmdtl.printer")
local List <const> = require("pl.List")
local logger <const> = require("awesomewmdtl.logger")
local Map <const> = require("pl.Map")
local Module_Info <const> = require("awesomewmdtl.entity.Module_Info")
local module_dependencies <const> = require("awesomewmdtl.visitors.module_dependencies")
local module_descendants <const> = require("awesomewmdtl.visitors.module_descendants")
local type Node = require("awesomewmdtl.types.Node")
local node_fixer <const> = require("awesomewmdtl.visitors.node_fixer")
local property <const> = require("awesomewmdtl.property")
local remove_duplicate_fields <const> = require("awesomewmdtl.visitors.remove_duplicate_fields")
local scraper <const> = require("awesomewmdtl.scraper")
local stringx <const> = require("pl.stringx")
local type_mapping <const> = require("awesomewmdtl.visitors.type_mapping")
local utils <const> = require("awesomewmdtl.utils")
@ -45,53 +46,6 @@ local function module_lists(
return all_module_infos, module_infos, global_module_infos
end
-- The module's children list produced can contain duplicates.
-- We ignore them for now because they are dismissed when building a Map for the printer.
local function modules_tree(modules: List<Module_Info.Module_Info>): Map<string, List<string>>
local tree: Map<string, List<string>> = Map()
for module in modules:iter() do
local parent = module.name:gmatch("(.*)%.(.*)$")()
if parent then
local ancestors = stringx.split(parent, ".")
for i = 1, #ancestors - 1 do
local ancestor = ancestors:slice(1, i):join(".")
if not tree:get(ancestor) then
tree:set(ancestor, List())
end
if not tree:get(ancestor):contains(parent) then
tree:get(ancestor):append(parent)
end
end
local parent_node = tree:get(parent)
if not parent_node then
tree:set(parent, List())
parent_node = tree:get(parent)
end
parent_node:append(module.name)
end
end
return tree
end
--- TODO : rewrite this to use the DAG
local function do_module_init_definition(
module_infos: List<Module_Info.Module_Info>
)
local tree = modules_tree(module_infos)
for module, children in tree:iter() do
-- TODO : this map should be coupled with the all_module_infos list
local requires: Map<string, string> = Map()
for child in children:iter() do
local name = child:gmatch(".*%.(.*)$")()
requires:set(name, child)
end
filesystem.file_writer.write(
printer.module_init_definition.generate_teal(requires),
property.out_directory .. "/" .. stringx.split(module, "."):slice(1, -1):join("/") .. "/init.d.tl"
)
end
end
--- TODO : rewrite the module_info thingy
local all_module_infos, module_infos, global_module_infos = module_lists(
property.base_url .. property.index_uri,
@ -128,36 +82,102 @@ for module in module_infos:iter() do
dag.push_global_nodes(module_dag, other_nodes)
end
-- Push global nodes to the global module
for _, mod in ipairs(property.capi_modules) do
local node: Node = nil
local index: integer
for i, n in ipairs(module_dag.modules) do
if n.module_path == mod then
node = n
index = i
break
end
end
if node then
node.global = true
dag.push_global_nodes(module_dag, { node })
table.remove(module_dag.modules, index)
end
end
-- Run the visitors
for _,root in dag.iter_modules(module_dag) do
for _,root in dag.iter_global_nodes(module_dag) do
ast.in_order_visitor(root, function(node: Node)
node_fixer.visit(node, root)
end)
ast.in_order_visitor(root, function(node: Node)
type_mapping.visit(node, true)
end)
end
for root in dag.iter_modules(module_dag) do
ast.in_order_visitor(root, function(node: Node)
remove_duplicate_fields.visit(node)
end)
ast.in_order_visitor(root, function(node: Node)
node_fixer.visit(node, root)
end)
ast.in_order_visitor(root, function(node: Node)
type_mapping.visit(node)
end)
ast.in_order_visitor(root, function(node: Node)
module_dependencies.visit(node, root, module_dag)
end)
ast.in_order_visitor(root, function(node: Node)
module_descendants.visit(node)
end)
end
-- Build the global module from dag.global_nodes
--- TODO : todo
-- Build the global_env_def.d.tl module from module_dag.global_nodes
local global_nodes_by_record_kind <const>: { string : { Node } } = {}
for _, node in dag.iter_global_nodes(module_dag) do
local record_kind = node.name:gmatch("[^%.]*")() -- get the most left part of the lua name path representation
if not global_nodes_by_record_kind[record_kind] then
global_nodes_by_record_kind[record_kind] = {}
end
table.insert(global_nodes_by_record_kind[record_kind], node)
end
local global_module_ast <const> = ast.create_node("module", "global_env_def", "global_env_def", true)
-- Declare global types
table.insert(global_module_ast.children, ast.create_node("type", "Gears_Shape_Function", { "(function(cr: any, width: integer, height: integer))" }, true))
table.insert(global_module_ast.children, ast.create_node("type", "Color", { "any" }, true))
table.insert(global_module_ast.children, ast.create_node("type", "Cairo_Surface", { "any" }, true))
table.insert(global_module_ast.children, ast.create_node("type", "Cairo_Context", { "any" }, true))
table.insert(global_module_ast.children, ast.create_node("type", "Image", { "any" }, true))
table.insert(global_module_ast.children, ast.create_node("type", "Awful_Placement_Function", { "function(d: any, args: table)" }, true))
for record_kind, nodes in pairs(global_nodes_by_record_kind) do
local record_kind_node = ast.create_node("record", record_kind, true)
for _, node in ipairs(nodes) do
node.name = node.name:gsub(record_kind .. "%.", "")
table.insert(record_kind_node.children, node)
end
table.insert(global_module_ast.children, record_kind_node)
end
--- TODO : this is fun, but we need to do something with it
-- Write the DAG to a file
-- local inspect = require("inspect")
-- filesystem.file_writer.write(
-- inspect(module_dag, { newline = "\n", indent = " ", depth = 2 }),
-- inspect(module_dag, { newline = "\n", indent = " " }),
-- "generated_dag.lua"
-- )
log:info("Preprocessing finished")
-- Write modules types definitions to files
for module_path, root in dag.iter_modules(module_dag) do
for root in dag.iter_modules(module_dag) do
filesystem.file_writer.write(
printer.teal_type_definition.printer(root),
property.out_directory .. "/" .. module_path:gsub("%.", "/") .. ".d.tl"
property.out_directory .. "/" .. root.module_path:gsub("%.", "/") .. ".d.tl"
)
end
do_module_init_definition(module_infos)
-- Write global types definition to file
filesystem.file_writer.write(
printer.teal_type_definition.printer(global_module_ast),
property.out_directory .. "/global_env_def.d.tl"
)
log:info("Module init files generated")

View File

@ -6,11 +6,33 @@ local utils = require("awesomewmdtl.utils")
local log = logger.log("scraper")
local function render_types(types: { string }): string
local GEN_TEXT <const> = "-- This file was auto-generated.\n"
local function render_types(types: { string }, separator: string, with_colon_prefix: boolean): string
if not types or #types == 0 then
return ""
end
return ": " .. table.concat(types, " | ")
return string.format(
"%s%s",
with_colon_prefix and ": " or "",
table.concat(types, separator))
end
local function render_function_return_types(types: { { string } }): string
if not types or #types == 0 then
return ""
end
local positional_return_types = utils.map(types, function (t: { string }): string
return render_types(t, " | ")
end)
return ": " .. table.concat(positional_return_types, ", ")
end
local function render_metamethod_type(metamethod: Node.Metamethod): string
-- Node.Metamethod enum matches the metamethods names, so we can simply print them as-is
return metamethod
end
local function dedent(str: string): string
@ -37,30 +59,63 @@ local function render_require(dependencies: { string : string }): string
return generated
end
local function render_descendant(descendants: { string : string }): string
local generated = ""
for descendant in utils.pairsByKeys(descendants) do
generated = generated .. string.format("%s: %s\n", utils.lowercase(descendant), descendant)
end
return generated
end
local record Node_Printer_Function
on_node: function(node: Node, indent_level: integer): string, integer
before_node: nil | function(node: Node, indent_level: integer): string, integer
after_node: nil | function(node: Node, indent_level: integer): string, integer
on_node: function(node: Node, indent_level: integer, parent_token: Node.Token): string, integer
-- optional functions
before_node: function(node: Node, indent_level: integer, parent_token: Node.Token): string, integer
after_node: function(node: Node, indent_level: integer, parent_token: Node.Token): string, integer
end
-- pre-declare functions to prevent forward reference errors
local print_teal: function(node: Node, indent_level: integer | nil): string
local print_teal: function(node: Node, indent_level: integer, parent_node: Node): string
local print_children: function(node: Node): string
local node_printer <total>: { Node.Token : Node_Printer_Function } = {
["module"] = {
before_node = function(node: Node, indent_level: integer): string, integer
before_node = function(node: Node, indent_level: integer, parent_token: Node.Token): string, integer
if "module" == parent_token then
return "", indent_level
end
if node.global then
return "", indent_level
end
return render_code(
string.format(
"-- This file was auto-generated.\n%s\nlocal record %s",
render_require(node.dependencies), -- last require statement will have a newline
"%s%s\nlocal record %s",
GEN_TEXT,
render_require(
utils.merge_map(
node.dependencies,
node.descendants)), -- last require statement will have a newline
node.name),
indent_level), indent_level + 1
end,
on_node = function(node: Node, indent_level: integer): string, integer
return render_code(print_children(node), indent_level), indent_level
on_node = function(node: Node, indent_level: integer, parent_token: Node.Token): string, integer
if "module" == parent_token then
return "", indent_level
end
return render_code(
string.format(
"%s%s",
render_descendant(node.descendants),
print_children(node)
), indent_level), indent_level
end,
after_node = function(node: Node, indent_level: integer): string, integer
after_node = function(node: Node, indent_level: integer, parent_token: Node.Token): string, integer
if "module" == parent_token then
return "", indent_level
end
if node.global then
return "", indent_level
end
return render_code("end", indent_level - 1) ..
"\n" ..
render_code(
@ -73,7 +128,8 @@ local node_printer <total>: { Node.Token : Node_Printer_Function } = {
before_node = function(node: Node, indent_level: integer): string, integer
return render_code(
string.format(
"record %s",
"%srecord %s",
node.global and "\nglobal " or "",
node.name),
indent_level), indent_level + 1
end,
@ -110,7 +166,7 @@ local node_printer <total>: { Node.Token : Node_Printer_Function } = {
string.format(
"%s%s",
node.name,
render_types(node.types)),
render_types(node.types, " | ", true)),
indent_level), indent_level
end,
},
@ -125,30 +181,56 @@ local node_printer <total>: { Node.Token : Node_Printer_Function } = {
"%s: function(%s)%s",
node.name,
table.concat(args, ", "),
render_types(node.return_types)),
render_function_return_types(node.return_types)
),
indent_level), indent_level
end,
},
["metamethod"] = {
on_node = function(): string, integer
log:warn("Metamethods are not supported yet")
on_node = function(node: Node, indent_level: integer): string, integer
local args = {}
for _, parameter in ipairs(node.parameters) do
table.insert(args, print_teal(parameter):sub(1, -2)) -- need to remove the newline ending
end
return render_code(
string.format(
"metamethod %s: function(%s)%s",
render_metamethod_type(node.metamethod),
table.concat(args, ", "),
render_function_return_types(node.return_types)
),
indent_level), indent_level
end,
}
},
["type"] = {
on_node = function(node: Node, indent_level: integer): string, integer
return render_code(
string.format(
"%stype %s = %s",
node.global and "\nglobal " or "",
node.name,
render_types(node.types, " | ")),
indent_level), indent_level
end,
},
}
function print_teal(node: Node, indent_level: integer | nil): string
function print_teal(node: Node,
-- optional parameters
indent_level: integer, parent_node: Node): string
indent_level = indent_level or 0
local parent_node_token = parent_node and parent_node.token
local printer = node_printer[node.token]
local generated = ""
local full_generated = ""
if printer.before_node then
generated, indent_level = (printer.before_node as function(Node, integer): string, integer)(node, indent_level)
generated, indent_level = printer.before_node(node, indent_level, parent_node_token)
full_generated = generated
end
generated, indent_level = printer.on_node(node, indent_level)
generated, indent_level = printer.on_node(node, indent_level, parent_node_token)
full_generated = full_generated .. generated
if printer.after_node then
generated, indent_level = (printer.after_node as function(Node, integer): string, integer)(node, indent_level)
generated, indent_level = printer.after_node(node, indent_level, parent_node_token)
full_generated = full_generated .. generated
end
return full_generated
@ -157,7 +239,7 @@ end
function print_children(node: Node): string
local generated = ""
for _, child in ast.iter_children(node) do
generated = generated .. print_teal(child)
generated = generated .. print_teal(child, 0, node)
end
return generated
end

View File

@ -18,16 +18,13 @@ local record Property
end
local property: Property = {
-- base_url = "https://awesomewm.org/apidoc",
base_url = "https://awesomewm.org/apidoc",
-- base_url = "file:///usr/share/doc/awesome/doc",
base_url = "file:///home/aireone/documents/prog/awesome/build/doc",
index_uri = "/index.html",
out_directory = "generated",
capi_modules = {
"awesome",
"client and awful.client",
"screen and awful.screen",
"tag and awful.tag",
"drawable",
},
ignored_modules = {
-- Sample files
@ -35,33 +32,33 @@ local property: Property = {
"theme.lua",
-- Utility libraries
"gears.debug",
"gears.filesystem",
"gears.geometry",
"gears.math",
"gears.object",
"gears.protected_call",
"gears.sort",
"gears.string",
"gears.table",
"gears.wallpaper",
-- "gears.debug",
-- "gears.filesystem",
-- "gears.geometry",
-- "gears.math",
-- "gears.object",
-- "gears.protected_call",
-- "gears.sort",
-- "gears.string",
-- "gears.table",
-- "gears.wallpaper",
-- Theme related libraries
"beautiful",
-- "beautiful",
"gears.color",
"gears.shape",
-- "gears.shape",
-- Classes
"awful.widget.common",
"gears.cache",
"gears.matrix",
"menubar.icon_theme",
"menubar.index_theme",
"signals",
"wibox.drawable",
"wibox.hierarchy",
"wibox.widget.base",
"xproperties",
-- "awful.widget.common",
-- "gears.cache",
-- "gears.matrix",
-- "menubar.icon_theme",
-- "menubar.index_theme",
-- "signals",
-- "wibox.drawable",
-- "wibox.hierarchy",
-- "wibox.widget.base",
-- "xproperties",
-- Documentation
"Authors",

View File

@ -18,7 +18,11 @@ local function parse_parameter_types(parameter_type: string): { string }
end
local types = {}
for t in stringx.split(parameter_type, " or "):iter() do
for t in stringx.split(parameter_type)
:filter(
function(s: string): boolean
return s ~= "or" and s ~= ","
end):iter() do
table.insert(types, t)
end
return types
@ -30,7 +34,7 @@ local function extract_item_name(item_name_node: scan.HTMLNode): string, string
end
local module_name_node <const> = scraper_utils.find(item_name_node:outer_html(), "span.function_modname")[1]
local module_name = module_name_node and module_name_node:inner_text():gsub("[%.:]$", "")
local name <const> = item_name_node:inner_text():gsub("^.*[%.:](.+)%s*[%(%{].*[%)%}]", "%1")
local name <const> = item_name_node:inner_text():gsub("(.+)[%(%{].*$", "%1"):gsub("^.*[%.:](.+)", "%1")
return utils.sanitize_string(name), module_name and utils.sanitize_string(module_name) or nil
end
@ -50,6 +54,12 @@ local function extract_function_parameters(table_html: string, function_name: st
local name <const> = extract_node_text(name_node)
local types <const> = parse_parameter_types(extract_node_text(types_node))
-- If the name contains a dot, then it's a nested field in a table
-- We don't support this yet, so we just ignore it
if name:match("%.") then
return nil
end
-- Add a field to the current parameter type record
if tr.attr ~= nil and tr.attr.class == "see_also_sublist" and is_populating_parameters_types then
local field = ast.create_node("variable", name)
@ -77,7 +87,7 @@ local function extract_function_parameters(table_html: string, function_name: st
"%s_%s",
utils.capitalize(function_name),
utils.capitalize(name))
table.insert(parameters_types, ast.create_node("record", record_name))
table.insert(parameters_types, ast.create_node("record", record_name, false))
is_populating_parameters_types = true
field.types = { record_name }
end
@ -93,8 +103,21 @@ local function extract_function_parameters(table_html: string, function_name: st
return parameters, parameters_types
end
local function extract_function_return_types(ol_html: string): { string }
return scraper_utils.scrape(ol_html, "span.types .type", extract_node_text)
local function extract_function_return_types(ol_html: string): { { string } }
local positional_return_types = scraper_utils.find(
ol_html,
"span.types")
local types: { { string } } = {}
for _,prt in ipairs(positional_return_types) do
local ts: { string } = {}
local union_types = scraper_utils.scrape(prt:outer_html(), ".type", extract_node_text)
for _,t in ipairs(union_types) do
table.insert(ts, t)
end
table.insert(types, ts)
end
return types
end
local function extract_property_constraints(property_constraint_node: scan.HTMLNode): { string }
@ -151,11 +174,11 @@ local function extract_section_functions(dl: string, module_name: string | nil):
return functions, other_functions
end
local function extract_section_variables(dl: string): { Node }, { string }
local function extract_section_variables(dl: string, with_constraint: boolean): { Node }, { string }
local query_selectors <const>: { string : string } = {
variable_name = "dt strong",
variable_summary_type = "dt span.summary_type",
variable_property_constraint = "dd span.property_type",
variable_property_constraint = with_constraint and "dd span.property_type",
}
local variables <const>: { Node } = {}
@ -171,11 +194,13 @@ local function extract_section_variables(dl: string): { Node }, { string }
if #node.types == 1 and node.types[1] == "string" then
log:debug("extract variable string with constraints, this is an enum", { name = node.name })
local type_enum <const> = ast.create_node("enum", utils.capitalize(node.name))
for _, constraint in ipairs(extract_property_constraints(nodes[query_selectors.variable_property_constraint])) do
table.insert(
type_enum.children,
ast.create_node("identifier", (constraint:gsub("&quot;", "")))
)
if with_constraint then
for _, constraint in ipairs(extract_property_constraints(nodes[query_selectors.variable_property_constraint])) do
table.insert(
type_enum.children,
ast.create_node("identifier", (constraint:gsub("&quot;", "")))
)
end
end
if #type_enum.children == 0 then
log:debug("Enum has no children, get back to variable", { name = node.name })
@ -198,9 +223,21 @@ local function extract_section_signal(dl: string): { string }
return scraper_utils.scrape(dl, selector, extract_node_text)
end
local function add_self_parameter(function_node: Node, record_name: string)
local self_parameter = ast.create_node("variable", "self")
self_parameter.types = { record_name }
table.insert(function_node.parameters, 1, self_parameter)
end
local function transform_to_call_metamethod(function_node: Node)
function_node.token = "metamethod"
function_node.metamethod = "__call"
end
local enum Section
"Constructors"
"Static module functions"
"Fields"
"Object properties"
"Object methods"
"Signals"
@ -211,11 +248,12 @@ end
-- - Nodes that should be added to the global scope
-- - Strings that should be added to the record Signals
local section_scrapers <total>: { Section : function(html: string, record_name: string, module_name: string): { Node }, { Node }, { string } } = {
["Constructors"] = function(html: string): { Node }, { Node }, { string }
["Constructors"] = function(html: string, record_name: string): { Node }, { Node }, { string }
local constructors <const> = extract_section_functions(html)
for _, constructor in ipairs(constructors) do
if constructor.token == "function" then
constructor.name = "new"
if constructor.token == "function" and constructor.name == utils.lowercase(record_name) then
add_self_parameter(constructor, record_name)
transform_to_call_metamethod(constructor)
end
end
return constructors, {}, {}
@ -224,17 +262,19 @@ local section_scrapers <total>: { Section : function(html: string, record_name:
local static_functions, other_functions = extract_section_functions(html, module_name)
return static_functions, other_functions, {}
end,
["Fields"] = function(html: string): { Node }, { Node }, { string }
local fields = extract_section_variables(html)
return fields, {}, {}
end,
["Object properties"] = function(html: string): { Node }, { Node }, { string }
local properties, signals = extract_section_variables(html)
local properties, signals = extract_section_variables(html, true)
return properties, {}, signals
end,
["Object methods"] = function(html: string, record_name: string): { Node }, { Node }, { string }
local methods <const> = extract_section_functions(html)
for _, method in ipairs(methods) do
if method.token == "function" then
local self_parameter = ast.create_node("variable", "self")
self_parameter.types = { record_name }
table.insert(method.parameters, 1, self_parameter)
add_self_parameter(method, record_name)
end
end
return methods, {}, {}
@ -257,12 +297,14 @@ function module.get_doc_from_page(html: string, module_path: string): Node, { No
"dl.function",
})
if #html_nodes:get "h2.section-header" ~= #html_nodes:get "dl.function" then
if #html_nodes:get("h2.section-header") ~= #html_nodes:get("dl.function") then
error "The list aren't the same size!"
end
local record_name <const> = utils.capitalize((module_path:gsub(".*%.", "")))
local module_root <const> = ast.create_node("module", record_name, module_path)
-- Awesome is a special case, we don't want to capitalize it.
-- I'm too lazy to make a special case for it because this project is in development for far too long and it's getting depressing
local record_name <const> = module_path == "awesome" and "awesome" or utils.capitalize((module_path:gsub(".*%.", "")))
local module_root <const> = ast.create_node("module", record_name, module_path, false)
local other_nodes <const>: { Node } = {}
local module_signals_node <const> = ast.create_node("enum", "Signal")

View File

@ -1,7 +1,7 @@
local type Node = require("awesomewmdtl.types.Node")
local record Dag
modules: { string : Node } -- module_path (AKA "full name" `package.module.name`) -> root_node (token = "module")
modules: { Node } -- list of root modules
global_nodes: { Node }
end

View File

@ -7,6 +7,7 @@ local record Node
"variable"
"function"
"metamethod"
"type"
end
token: Token
name: string
@ -14,16 +15,26 @@ local record Node
-- for "module", "record", "enum"
children: { Node }
-- for "variable"
-- for "variable" and "type"
types: { string }
-- for "function" and "metamethod"
parameters: { Node }
return_types: { string }
return_types: { { string } }
-- for "methamethod"
enum Metamethod
"__call"
end
metamethod: Metamethod
-- for "module"
module_path: string
dependencies: { string : string } -- module_name -> module_path
descendants: { string : string } -- modules in children that should be written in the final type definition. module_name -> module_path
-- for "module", "record", "type"
global: boolean
end
return Node

View File

@ -13,6 +13,16 @@ function utils.has_item(t: table, item: any): any
return nil
end
function utils.find<T>(list: { T }, predicate: function(value: T, position: integer): boolean): T, integer
for position, value in ipairs(list) do
if predicate(value, position) then
return value, position
end
end
return nil
end
function utils.filter<T>(list: { T }, predicate: function(value: T, position: integer): boolean): { T }
local filtered: { T } = {}
@ -101,4 +111,27 @@ function utils.pairsByKeys<Key, Value>(list: { Key : Value }, comp: function(Key
end
end
-- We expect the user to type at the call site
function utils.pipe(...: function): function(...: any): any...
local funcs = { ... }
return function(...: any): any...
local res = ...
for _, f in ipairs(funcs) do
res = f(res)
end
return res
end
end
function utils.merge_map<T, U>(t1: { T : U }, t2: { T : U }): { T : U }
local merged: { T : U } = {}
for k, v in pairs(t1) do
merged[k] = v
end
for k, v in pairs(t2) do
merged[k] = v
end
return merged
end
return utils

View File

@ -1,9 +1,31 @@
local type Dag = require("awesomewmdtl.types.Dag")
local dag = require("awesomewmdtl.dag")
local type Node = require("awesomewmdtl.types.Node")
local utils <const> = require("awesomewmdtl.utils")
local spread = utils.spread
-- Very hacky way to get the module path of a capi class
-- We shouldn't rely on partial record for Node
local capi_class <const>: { string : Node } = {
Client = {
name = "Client",
module_path = "awful.client",
},
Layout = {
name = "Layout",
module_path = "awful.layout",
},
Screen = {
name = "Screen",
module_path = "awful.screen",
},
Tag = {
name = "Tag",
module_path = "awful.tag",
},
}
local function get_all_types_in_node(node: Node): { string }
local parameters_types = {}
if node.parameters then
@ -11,11 +33,17 @@ local function get_all_types_in_node(node: Node): { string }
spread(parameters_types, v.types)
end
end
local return_types = {}
if node.return_types then
for _, rts in ipairs(node.return_types) do
spread(return_types, rts)
end
end
return spread(
{},
node.types or {},
node.return_types or {},
parameters_types or {})
return_types,
parameters_types)
end
local function replace_in_node_type(node: Node, old_type: string, new_type: string)
@ -38,9 +66,11 @@ local function replace_in_node_type(node: Node, old_type: string, new_type: stri
end
if node.return_types then
for i, t in ipairs(node.return_types) do
if t == old_type then
node.return_types[i] = new_type
for i, rt in ipairs(node.return_types) do
for j, t in ipairs(rt) do
if t == old_type then
node.return_types[i][j] = new_type
end
end
end
end
@ -56,10 +86,24 @@ function Module_Dependencies.visit(node: Node, mod: Node, d: Dag)
if type_name == mod.name then
goto continue
end
if utils.has_item({
"any",
"number",
"integer",
"string",
"boolean",
"function",
"nil",
"table",
}, type_name) then
goto continue
end
local dependency = d.modules[type_name] or d.modules[utils.lowercase(type_name)]
local dependency = dag.find_module(d, type_name) or capi_class[type_name]
if dependency then
mod.dependencies[dependency.name] = dependency.module_path
if dependency.name ~= mod.name then
mod.dependencies[dependency.name] = dependency.module_path
end
replace_in_node_type(node, dependency.module_path, dependency.name)
end

View File

@ -0,0 +1,38 @@
local type Node = require("awesomewmdtl.types.Node")
local utils = require("awesomewmdtl.utils")
local capitalize = utils.capitalize
local function get_descendants_module(node: Node): { string : string }
local descendants_by_name: { string : string } = {}
for _, descendant in ipairs(node.children) do
if descendant.token == "module" then
descendants_by_name[descendant.name] = descendant.module_path
end
end
return descendants_by_name
end
local record Module_Descendants
visit: function(node: Node)
end
function Module_Descendants.visit(node: Node)
if node.token ~= "module" then
return
end
local descendants_by_name = get_descendants_module(node)
for _, descendant in ipairs(node.children) do
if descendant.token ~= "module" and descendants_by_name[capitalize(descendant.name)] then
descendants_by_name[capitalize(descendant.name)] = nil
end
end
node.descendants = descendants_by_name
end
return Module_Descendants

View File

@ -0,0 +1,148 @@
local type Node = require("awesomewmdtl.types.Node")
-- Teal can't define nested Map types yet, so we have to define a type for the nested map.
local type New_Node_By_Name = { string : Node }
-- This is a map of module paths to a map of node names to a map of node keys to new values.
-- This is used to fix nodes where either
-- - Teal doesn't have a proper way to handle the types
-- - The Awesome API is too confusing to be translated to Teal
-- EVERYTHING IN HERE IS TEMPORARY AND SHOULD BE REMOVED!
-- EVERYTHING IN HERE IS TEMPORARY AND SHOULD BE REMOVED!
-- EVERYTHING IN HERE IS TEMPORARY AND SHOULD BE REMOVED!
local node_fixer <const>: { string : New_Node_By_Name } = {
["awful.titlebar"] = {
new = {
return_types = { { "Titlebar" } }, -- The Awesome APIDoc have to updated
},
},
["awful.widget.prompt"] = {
set_shape = {
parameters = {
{ token = "variable", name = "self", types = { "Prompt" } },
{ token = "variable", name = "shape", types = { "Gears_Shape_Function" } }, -- cannot discriminate a union between multiple function types: Gears_Shape_Function | function(...: <any type>): <any type>...
},
},
},
["gears.filesystem"] = {
get_themes_dir = {
return_types = { { "string" } }, -- The Awesome APIDoc have to updated
},
},
["gears.string"] = {
split = { -- psplit is documented as split in the APIDoc
parameters = {
{ token = "variable", name = "s", types = { "string" } },
{ token = "variable", name = "sep", types = { "string" } }, -- psplit optional parameter breaks the parser
},
},
},
["gears.surface"] = {
apply_shape_bounding = {
parameters = {
{ token = "variable", name = "draw", types = { "any" } },
{ token = "variable", name = "shape", types = { "Gears_Shape_Function" } },
{ token = "variable", name = "...", types = { "any" } },
},
},
},
["naughty.widget.background"] = {
set_shape = {
parameters = {
{ token = "variable", name = "self", types = { "Background" } },
{ token = "variable", name = "shape", types = { "Gears_Shape_Function" } }, -- cannot discriminate a union between multiple function types: Gears_Shape_Function | function
},
},
},
["wibox.container.background"] = {
background = {
parameters = {
{ token = "variable", name = "self", types = { "Background" } },
{ token = "variable", name = "widget", types = { "Widget" } },
{ token = "variable", name = "bg", types = { "Color" } },
{ token = "variable", name = "shape", types = { "Gears_Shape_Function" } }, -- cannot discriminate a union between multiple function types: Gears_Shape_Function | function
},
},
set_shape = {
parameters = {
{ token = "variable", name = "self", types = { "Background" } },
{ token = "variable", name = "shape", types = { "Gears_Shape_Function" } }, -- cannot discriminate a union between multiple function types: Gears_Shape_Function | function(...: <any type>): <any type>
},
},
},
["wibox.container.tile"] = {
Tile_Args = {
children = { -- widget is documented twice in the APIDoc
{
name = "widget",
token = "variable",
types = { "Widget" }
}, {
name = "halign",
token = "variable",
types = { "string" }
}, {
name = "valign",
token = "variable",
types = { "string" }
}, {
name = "horizontal_spacing",
token = "variable",
types = { "number" }
}, {
name = "vertical_spacing",
token = "variable",
types = { "number" }
}, {
name = "horizontal_crop",
token = "variable",
types = { "boolean" }
}, {
name = "vertical_crop",
token = "variable",
types = { "boolean" }
}, {
name = "tiled",
token = "variable",
types = { "boolean" }
}, {
name = "fill_vertical",
token = "variable",
types = { "boolean" }
}, {
name = "fill_horizontal",
token = "variable",
types = { "boolean" }
}, {
name = "content_fill_vertical",
token = "variable",
types = { "boolean" }
}, {
name = "content_fill_horizontal",
token = "variable",
types = { "boolean" }
}
},
},
},
}
local record Node_Fixer
visit: function(node: Node, mod: Node)
end
function Node_Fixer.visit(node: Node, mod: Node)
if not (node_fixer[mod.module_path] and node_fixer[mod.module_path][node.name]) then
return
end
for k, v in pairs(node_fixer[mod.module_path][node.name] as { string : any }) do
-- Teal can't index `node` with our k,v
local n <const> = node as { string : any }
n[k] = v
end
end
return Node_Fixer

View File

@ -0,0 +1,31 @@
local type Node = require("awesomewmdtl.types.Node")
local utils <const> = require("awesomewmdtl.utils")
local has_item <const> = utils.has_item
-- This is quick hack to remove duplicate fields from a module
-- We can have duplicated fields in a module, because we currently
-- don't make difference between module record and instance record.
-- So static module fields and instance level fields are mixed together.
local record Remove_Duplicate_Fields
visit: function(node: Node)
end
function Remove_Duplicate_Fields.visit(node: Node)
if node.token ~= "module" then
return
end
local fields: { string } = {}
for i, n in ipairs(node.children) do
if has_item(fields, n.name) then
table.remove(node.children, i)
else
table.insert(fields, n.name)
end
end
end
return Remove_Duplicate_Fields

View File

@ -1,31 +1,60 @@
local type Node = require("awesomewmdtl.types.Node")
-- Special types I don't want to deal with for now
local gears_shape_function = "function(cr: any, width: integer, height: integer)"
local type_map <const>: { string : string } = {
awesome = "Awesome",
Awesome = "awesome",
bool = "boolean",
["surface._native"] = "Cairo_Surface",
cairo_context = "Cairo_Context",
["cairo.surface"] = "Cairo_Surface",
color = "Color",
client = "Client",
["gears.shape"] = gears_shape_function,
["gears.surface"] = "Surface",
drawable = "Drawable",
["gears.shape"] = "Gears_Shape_Function",
["gears.surface"] = "Cairo_Surface",
["Gio.InputStream"] = "any", -- We'll probably never have better support for this one, since it's a GI bindings
image = "Image",
int = "integer",
Integer = "integer",
keygrabber = "Keygrabber",
layout = "Layout",
["lgi.Pango.FontDescription"] = "any", -- We'll probably never have better support for this one, since it's a GI bindings
matrix = "Matrix",
["naughty.notification_closed_reason"] = "number", -- Teal doesn't support numeric enums
["Pango.FontDescription"] = "any", -- We'll probably never have better support for this one, since it's a GI bindings
placement = "Awful_Placement_Function",
raw_surface = "Cairo_Surface",
screen = "Screen",
shape = gears_shape_function,
surface = "Surface",
shape = "Gears_Shape_Function",
surface = "Cairo_Surface",
tag = "Tag",
template = "table",
["true"] = "boolean", -- Either Teal should support literal types or maybe Awesome should use booleans instead of true
["wibox.container"] = "table",
["wibox.drawable"] = "Drawable",
["wibox.layout"] = "table",
widget = "wibox.widget",
-- fixes we shouldn't have to do (We need to PR Awesome to fix the doc)
timer = "gears.timer",
font = "string", -- doesn't exist
func = "function", -- doesn't exist
["gears.color"] = "any", -- shouldn't be here
["gears.opacity"] = "number", -- error in the naughty.notification doc
["gears.margin"] = "number", -- error in the naughty.notification doc
gradient = "string", -- doesn't exist
["N/A"] = "any", -- we shouldn't have to do this, what `N/A` supposed to mean?
pattern = "string", -- doesn't exist
raw_curface = "Cairo_Surface", -- typo
shap = "Gears_Shape_Function", -- typo
tale = "table", -- typo
timer = "gears.timer", -- not resolvable
}
local function get_type(t: string): string
return type_map[t] or t
end
local global_type_map <const>: { string : string } = {
screen = "screen", -- global env cannot come from another module
}
local function check_node(node: Node)
local function check_node(node: Node, get_type: function(string): string)
if not node.types then
return
end
@ -35,34 +64,42 @@ local function check_node(node: Node)
end
end
local function check_function_parameters(node: Node)
local function check_function_parameters(node: Node, get_type: function(string): string)
if not node.parameters then
return
end
for _, parameter in ipairs(node.parameters) do
check_node(parameter)
check_node(parameter, get_type)
end
end
local function check_function_returns(node: Node)
local function check_function_returns(node: Node, get_type: function(string): string)
if not node.return_types then
return
end
for i, ret in ipairs(node.return_types) do
node.return_types[i] = get_type(ret)
for i, ret_ts in ipairs(node.return_types) do
for j, t in ipairs(ret_ts) do
node.return_types[i][j] = get_type(t)
end
end
end
local record Type_Mapping
visit: function(node: Node)
visit: function(node: Node, is_global_module: boolean)
end
function Type_Mapping.visit(node: Node)
check_node(node)
check_function_parameters(node)
check_function_returns(node)
function Type_Mapping.visit(node: Node, is_global_module: boolean)
local get_type <const> = is_global_module and function(t: string): string
return global_type_map[t] or type_map[t] or t
end or function(t: string): string
return type_map[t] or t
end
check_node(node, get_type)
check_function_parameters(node, get_type)
check_function_returns(node, get_type)
end
return Type_Mapping