diff --git a/assets/javascripts/news.json b/assets/javascripts/news.json
index a0a009fa..043a0401 100644
--- a/assets/javascripts/news.json
+++ b/assets/javascripts/news.json
@@ -1,4 +1,8 @@
[
+ [
+ "2024-01-12",
+ "New documentation: Hammerspoon"
+ ],
[
"2024-01-05",
"New documentation: Bazel"
diff --git a/lib/docs/filters/hammerspoon/clean_html.rb b/lib/docs/filters/hammerspoon/clean_html.rb
new file mode 100644
index 00000000..923ed678
--- /dev/null
+++ b/lib/docs/filters/hammerspoon/clean_html.rb
@@ -0,0 +1,34 @@
+module Docs
+ class Hammerspoon
+ class CleanHtmlFilter < Filter
+ def call
+
+ at_css("#search").parent.remove if at_css("#search")
+
+ # Remove script tags for functionality not needed in DevDocs
+ css("script").remove
+
+ # Remove styles that are not necessary
+ css("style").remove
+
+ # Modify the main title - remove leading "docs » "
+ at_css("h1").content = at_css("h1").content.sub("docs » ", "")
+
+ # add syntax highlighting
+ css("pre").each do |pre|
+ if pre.get_attribute("lang") == "lua"
+ pre.set_attribute("data-language", "lua")
+ pre.remove_attribute("lang")
+ else
+ if pre.get_attribute("lang")
+ # logger.warn("unrecognised pre.get_attribute('lang') = #{pre.get_attribute("lang")}")
+ end
+ end
+ end
+
+ doc
+ end
+
+ end
+ end
+end
diff --git a/lib/docs/filters/hammerspoon/entries.rb b/lib/docs/filters/hammerspoon/entries.rb
new file mode 100644
index 00000000..48e29387
--- /dev/null
+++ b/lib/docs/filters/hammerspoon/entries.rb
@@ -0,0 +1,69 @@
+module Docs
+ class Hammerspoon
+ class EntriesFilter < Docs::EntriesFilter
+ def get_name
+ at_css("h1").content
+ end
+
+ def get_type
+ slug.split("/").first
+ end
+
+ def additional_entries
+ return [] if root_page?
+ entries = []
+
+ # add a base entry
+ entries << [name, nil, name]
+
+ css("section").each do |section|
+ title_node = section.at_css("h5")
+ if title_node.nil?
+ next
+ end
+ entry_name = title_node.content.strip
+ entry_id = section["id"]
+
+ fn_type = section.at_css("a.dashAnchor").get_attribute("name")
+ # this dashAnchor is the most consistent way to get the type of the entry
+ if fn_type.start_with?("//apple_ref/cpp/Function")
+ fn_type = "Function"
+ entry_name << "()"
+ elsif fn_type.start_with?("//apple_ref/cpp/Constructor/")
+ fn_type = "Constructor"
+ entry_name << "()"
+ elsif fn_type.start_with?("//apple_ref/cpp/Method")
+ fn_type = "Method"
+ entry_name << "()"
+ elsif fn_type.start_with?("//apple_ref/cpp/Class")
+ fn_type = "Class"
+ elsif fn_type.start_with?("//apple_ref/cpp/Constant")
+ fn_type = "Constant"
+ elsif fn_type.start_with?("//apple_ref/cpp/Variable")
+ fn_type = "Variable"
+ elsif fn_type.start_with?("//apple_ref/cpp/Deprecated")
+ fn_type = "Deprecated"
+ elsif fn_type.start_with?("//apple_ref/cpp/Field")
+ fn_type = "Field"
+ else
+ fn_type = "Unknown"
+ end
+
+ # Create a new entry for each method/function
+ if fn_type != "Unknown"
+ entries << ["#{name}.#{entry_name}", entry_id, name]
+ end
+
+ end
+
+ entries
+ end
+
+ def include_default_entry?
+ # Decide when to include the default entry
+ # Here we include it unless the page is a module overview or similar
+ !subpath.end_with?("index.lp")
+ end
+ end
+ end
+end
diff --git a/lib/docs/scrapers/hammerspoon.rb b/lib/docs/scrapers/hammerspoon.rb
new file mode 100644
index 00000000..dc42b14d
--- /dev/null
+++ b/lib/docs/scrapers/hammerspoon.rb
@@ -0,0 +1,33 @@
+module Docs
+ class Hammerspoon < UrlScraper
+ self.type = 'hammerspoon'
+ self.root_path = ''
+ self.links = {
+ home: 'https://www.hammerspoon.org',
+ code: 'https://github.com/Hammerspoon/hammerspoon'
+ }
+ self.base_url = 'https://www.hammerspoon.org/docs/'
+ self.release = '0.9.100'
+
+ html_filters.push 'hammerspoon/clean_html', 'hammerspoon/entries'
+
+ # links with no content will still render a page, this is an error in the docs
+ # (see: https://github.com/Hammerspoon/hammerspoon/pull/3579)
+ options[:skip] = ['module.lp/matrix.md']
+ options[:skip_patterns] = [
+ /LuaSkin/,
+ ]
+
+ # Hammerspoon docs don't have a license (MIT specified in the hammerspoon repo)
+ # https://github.com/Hammerspoon/hammerspoon/blob/master/LICENSE
+ options[:attribution] = <<-HTML
+ © 2014–2017 Hammerspoon contributors
+ Licensed under the MIT License.
+ HTML
+
+ def get_latest_version(opts)
+ get_latest_github_release('Hammerspoon', 'hammerspoon', opts)
+ end
+
+ end
+end
diff --git a/public/icons/docs/hammerspoon/16.png b/public/icons/docs/hammerspoon/16.png
new file mode 100644
index 00000000..b78dd788
Binary files /dev/null and b/public/icons/docs/hammerspoon/16.png differ
diff --git a/public/icons/docs/hammerspoon/16@2x.png b/public/icons/docs/hammerspoon/16@2x.png
new file mode 100644
index 00000000..106b50b0
Binary files /dev/null and b/public/icons/docs/hammerspoon/16@2x.png differ
diff --git a/public/icons/docs/hammerspoon/SOURCE b/public/icons/docs/hammerspoon/SOURCE
new file mode 100644
index 00000000..ff6491dc
--- /dev/null
+++ b/public/icons/docs/hammerspoon/SOURCE
@@ -0,0 +1 @@
+https://www.hammerspoon.org/images/hammerspoon.ico
\ No newline at end of file