diff --git a/.rubocop.yml b/.rubocop.yml index 7d5252c7c..825b3cd72 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -35,4 +35,5 @@ Style/StringLiteralsInInterpolation: EnforcedStyle: double_quotes Style/TernaryParentheses: EnforcedStyle: require_parentheses_when_complex - +Layout/BlockAlignment: + EnforcedStyleAlignWith: start_of_block diff --git a/.ruby-version b/.ruby-version index 5436ea06e..a8bf53af8 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.3.2 \ No newline at end of file +3.3.11 \ No newline at end of file diff --git a/.vuepress/components/EditPageLink.vue b/.vuepress/components/EditPageLink.vue index 76c28509e..1c577e28b 100644 --- a/.vuepress/components/EditPageLink.vue +++ b/.vuepress/components/EditPageLink.vue @@ -31,7 +31,7 @@ export default { }, editUrl () { if (!this.$page || !this.$page.frontmatter.source) return null - return this.$page.frontmatter.source.replace('/blob/', '/edit/').replace('/developer/', '/developers/') + return this.$page.frontmatter.source.replace('/blob/', '/edit/') } } } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 972977e88..260aee70a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ This website is made with [VuePress](https://vuepress.vuejs.org/). It will be installed as part of the dev dependencies. For the used version of vuepress to work correctly, Node.js 16.20.0 or **older** is needed. Alternatively on newer versions [the legacy OpenSSL provider can be enabled as described in this StackOverflow thread](https://stackoverflow.com/questions/69692842/error-message-error0308010cdigital-envelope-routinesunsupported). -For the Ruby scripts used by the website build, Ruby 3.3.2 is needed. +For the Ruby scripts used by the website build, Ruby 3.3.11 is needed. If you are using a Node.js version manager like [fnm](https://github.com/Schniz/fnm), the provided `.node-version` file should automatically set the correct Node.js version needed. If you are using a Ruby version manager like [rvm](https://rvm.io/), running `rvm use` should automatically set the correct version according to the `.ruby-version` file. @@ -24,23 +24,23 @@ This chapter describes how to setup a local environment to be able to build the The following has been tested on Linux and MacOS (Windows seems to have a few minors that prevent the script to run completely). -#### Ruby 3.3.2 +#### Ruby 3.3.11 It is highly recommended to use the [Ruby Version Manager (RVM)](https://rvm.io). Once installed it will help to automatically download and configure `Ruby`: ```bash -$ rvm install "ruby-3.3.2" +$ rvm install "ruby-3.3.11" $ rvm use -Using /home/foo/.rvm/gems/ruby-3.3.2 +Using /home/foo/.rvm/gems/ruby-3.3.11 ``` If there are no binary packages available for your distribution, `rvm` will compile `Ruby` from the source code: ```bash -$ rvm install "ruby-3.3.2" +$ rvm install "ruby-3.3.11" Searching for binary rubies, this might take some time. -No binary rubies available for: ubuntu/22.04/x86_64/ruby-3.3.2. +No binary rubies available for: ubuntu/22.04/x86_64/ruby-3.3.11. Continuing with compilation. # ... ``` @@ -79,6 +79,5 @@ This will: 1. Run `ruby prepare-docs.rb` as above. 2. Run `vuepress build` which will output the final static files in `vuepress`. -3. Run `ruby postbuild.rb` which removes all prefetch directives inserted by VuePress from `index.html`, which optimizes the initial load performance. The complete build will take between 2 and 5 minutes. diff --git a/Gemfile.lock b/Gemfile.lock index d5d9a5db0..f17561927 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,17 +1,18 @@ GEM remote: https://rubygems.org/ specs: - ast (2.4.2) - json (2.7.2) - language_server-protocol (3.17.0.3) - parallel (1.25.1) - parser (3.3.2.0) + ast (2.4.3) + json (2.19.5) + language_server-protocol (3.17.0.5) + parallel (1.28.0) + parser (3.3.11.1) ast (~> 2.4.1) racc - racc (1.8.0) + prism (1.9.0) + racc (1.8.1) rainbow (3.1.1) - regexp_parser (2.9.2) - rexml (3.3.9) + regexp_parser (2.12.0) + rexml (3.4.4) rubocop (1.64.1) json (~> 2.3) language_server-protocol (>= 3.17.0) @@ -23,10 +24,11 @@ GEM rubocop-ast (>= 1.31.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.31.3) - parser (>= 3.3.1.0) + rubocop-ast (1.49.1) + parser (>= 3.3.7.2) + prism (~> 1.7) ruby-progressbar (1.13.0) - unicode-display_width (2.5.0) + unicode-display_width (2.6.0) PLATFORMS aarch64-linux diff --git a/add-blog-meta.rb b/add-blog-meta.rb deleted file mode 100644 index ff2228220..000000000 --- a/add-blog-meta.rb +++ /dev/null @@ -1,85 +0,0 @@ -# frozen_string_literal: true - -# Adds the OpenGraph & Twitter meta tags to the blog articles -require "fileutils" - -puts "➡️ Adding meta-data to blog posts" - -Dir.glob("blog/*.md") do |file| - next if file =~ /index\.md/ - - in_frontmatter = false - parsing_multiline_excerpt = false - og_title = "openHAB" - og_description = "a vendor and technology agnostic open source automation software for your home" - og_image = nil - - # Feed Meta - fm_author = nil - - FileUtils.mkdir_p(".vuepress/tmp") - FileUtils.mv(file, ".vuepress/tmp/#{File.basename(file)}") - - puts " ➡️ #{file}" - File.open(file, "w+") do |out| - File.open(".vuepress/tmp/#{File.basename(file)}").each do |line| - # FIXME: require "yaml" and parse properly one day... - if parsing_multiline_excerpt - if line =~ /^ / - og_description += " " - og_description += line.gsub(/^ /, "").gsub("\n", "") - next - else - og_description.strip! - parsing_multiline_excerpt = false - end - end - - og_title = line.gsub("title: ", "").gsub("\n", "") if in_frontmatter && line =~ /^title:/ - - if in_frontmatter && line =~ /^excerpt:/ - og_description = line.gsub("excerpt: ", "").gsub("\n", "").gsub("[", "").gsub("]", "").gsub(%r{\(http[:/\-0-9A-Za-z\.]+\)}, "") - if og_description == ">-" - og_description = "" - parsing_multiline_excerpt = true - next - end - end - - og_image = line.gsub("previewimage: ", "").gsub("\n", "") if in_frontmatter && line =~ /^previewimage:/ - - fm_author = line.gsub("author: ", "").gsub("\n", "") if in_frontmatter && line =~ /^author:/ - - if line =~ /^---$/ - if in_frontmatter - # Add OpenGraph tags - out.puts "meta:" - out.puts " - name: twitter:card" - out.puts " content: summary_large_image" - out.puts " - property: og:title" - out.puts " content: \"#{og_title.gsub('"', '\"')}\"" - out.puts " - property: og:description" - out.puts " content: #{og_description}" - out.puts " - property: og:image" if og_image - out.puts " content: https://www.openhab.org#{og_image}" if og_image - - # Add feed meta tags, when something relevant is available - if !fm_author.nil? || !og_image.nil? - - out.puts "feed:" - out.puts " image: https://www.openhab.org#{og_image}" if og_image - out.puts " author:" - out.puts " - " - out.puts " name: #{fm_author}" - end - - in_frontmatter = false - else - in_frontmatter = true - end - end - - out.puts line - end - end -end diff --git a/generate_iconset_doc.rb b/generate_iconset_doc.rb deleted file mode 100644 index 95f230a0d..000000000 --- a/generate_iconset_doc.rb +++ /dev/null @@ -1,79 +0,0 @@ -# frozen_string_literal: true - -# This script re-generate a VuePress-compatible version of an -# iconset's documentation - -require "csv" -require "fileutils" - -$original_iconsets_location = ARGV[0] -$iconset_name = ARGV[1] -$data_dir = ARGV[2] -$out_dir = ARGV[3] - -if !$original_iconsets_location || !$iconset_name || !$data_dir || !$out_dir - puts "Usage: generate_iconset_doc.rb " - exit(1) -end - -$icons_list = [] -$categories_channels = {} -$categories_places = [] -$categories_thing = [] - -Dir.glob("#{$original_iconsets_location}/#{$iconset_name}/src/main/resources/icons/*.svg") do |path| - $icons_list.push(File.basename(path)) -end - -CSV.foreach("#{$data_dir}/categories.csv", headers: true) do |cat| - $categories_channels[cat[0]] = [] unless $categories_channels.include?(cat[0]) - $categories_channels[cat[0]].push(cat[1]) -end - -CSV.foreach("#{$data_dir}/categories_places.csv", headers: true) do |cat| - $categories_places.push(cat[0]) -end - -CSV.foreach("#{$data_dir}/categories_thing.csv", headers: true) do |cat| - $categories_thing.push(cat[0]) -end - -FileUtils.mkdir_p("#{$out_dir}/#{$iconset_name}") -File.open("#{$out_dir}/#{$iconset_name}/readme.md", "w+") do |f| - f.puts "---" - f.puts "title: Icons" - f.puts "categories:" - f.puts " channels:" - $categories_channels.each do |k, c| - f.puts " #{k}:" - c.each do |i| - f.puts " - #{i.downcase}" - end - end - - f.puts " places:" - $categories_places.each do |i| - f.puts " - #{i.downcase}" - end - f.puts " things:" - $categories_thing.each do |i| - f.puts " - #{i.downcase}" - end - - f.puts "---" - f.puts - f.puts "# Icons" - f.puts - f.puts "These are the classic icons from Eclipse SmartHome." - f.puts - f.puts "These icons can be used when describing Items. You can also add your own. See the [instructions](/docs/configuration/items.html#icons) to learn more." - f.puts - f.puts "" -end - -puts " ➡️ File written in #{$out_dir}/#{$iconset_name}/readme.md" - -# FileUtils.mkdir_p(".vuepress/public/iconsets") -FileUtils.cp_r("#{$original_iconsets_location}/#{$iconset_name}/src/main/resources/icons", ".vuepress/public/iconsets/#{$iconset_name}") - -puts " ➡️ Icons copied to .vuepress/public/iconsets/#{$iconset_name}" diff --git a/netlify.toml b/netlify.toml new file mode 100644 index 000000000..884ff9584 --- /dev/null +++ b/netlify.toml @@ -0,0 +1,2 @@ +[build.environment] + MISE_RUBY_COMPILE = "false" diff --git a/package.json b/package.json index 4b7526e84..847abf213 100644 --- a/package.json +++ b/package.json @@ -28,9 +28,9 @@ "scripts": { "predev": "npm install && npx update-browserslist-db@latest", "dev": "npx vuepress dev", - "prepare-docs": "ruby prepare-docs.rb $ARGUMENTS", - "add-blog-meta": "ruby add-blog-meta.rb", - "build": "npm run prepare-docs && ruby add-blog-meta.rb && vuepress build .", + "prepare-docs": "ruby scripts/prepare-docs.rb $ARGUMENTS", + "add-blog-meta": "ruby scripts/add-blog-meta.rb", + "build": "npm run prepare-docs && ruby scripts/add-blog-meta.rb && vuepress build .", "build-only": "vuepress build .", "prebuild-local": "npm run prepare-docs", "build-local": "npm run dev --silent", diff --git a/postbuild.rb b/postbuild.rb deleted file mode 100644 index 8a11af7fe..000000000 --- a/postbuild.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -# Executed after `vuepress build`. -# Performs last minute fixups before publishing. - -# Adds a class to the navbar for the homepage -filepath = "vuepress/index.html" -# IO.write(filepath, File.open(filepath) {|f| f.read.gsub(/$/, "")}) -File.write(filepath, File.open(filepath) { |f| f.read.gsub('class="navbar"', 'class="homepage navbar"') }) diff --git a/prepare-docs.rb b/prepare-docs.rb deleted file mode 100644 index 2a41f39f6..000000000 --- a/prepare-docs.rb +++ /dev/null @@ -1,694 +0,0 @@ -# frozen_string_literal: true - -# This will clone https://github.com/openhab/openhab-docs -# and migrate content into the website with some changes - -require "fileutils" -require "uri" -require "open-uri" -require "json" - -$docs_repo = "https://github.com/openhab/openhab-docs" -$docs_repo_root = "#{$docs_repo}/blob/main" -$docs_repo_branch = "final" -$addons_repo_branch = "main" -$version = "final" -$verbose = false - -$ignore_addons = ["transport.modbus", "transport.feed", "javasound", "webaudio", "oh2"] - -def verbose(message) - puts message if $verbose -end - -def checkout_pull_request(pull_request, target_directory) - pull_request_url = "https://api.github.com/repos/openhab/openhab-docs/pulls/#{pull_request}" - - response = JSON.parse(URI.parse(pull_request_url).read) - repository_url = response["head"]["repo"]["clone_url"] - label = response["head"]["label"] - sha = response["head"]["sha"] - branch = response["head"]["ref"] - title = response["title"] - - puts "➡️ Cloning repository 📦 #{label} ..." - puts " ↪️ PR ##{pull_request}: #{title}" - - system("OH_DOCS_VERSION=#{branch}") - - FileUtils.cd(target_directory, verbose: false) do - system("git clone --depth 1 #{repository_url} --branch #{branch} #{$verbose ? "" : "--quiet"}") - system("git reset ##{sha} #{$verbose ? "" : "--quiet"}") - end -end - -verbose "🧹 Cleaning existing documentation downloads ..." -Dir.glob("javadoc-*.tgz*").select { |file| /pattern/.match file }.each { |file| File.delete(file) } - -$parameter_no_clone = false -$pull_request = nil - -previous_argument = "" -ARGV.each do |arg| - case arg - when "--no-clone" - $parameter_no_clone = true - verbose " --no-clone ➡️ existing clone will be used" - when "--verbose" - $verbose = true - else - case previous_argument - when "--pull-request" - $pull_request = arg - $version = "final" - verbose "➡️ PR #{$pull_request} will be used to build documentation" - end - end - previous_argument = arg -end - -if ENV["OH_DOCS_VERSION"] - $version = ENV["OH_DOCS_VERSION"] - $version += ".0" if $version.split(".").length == 2 -end - -puts "➡️ Generating docs for version #{$version}" - -if $parameter_no_clone && Dir.exist?(".vuepress/openhab-docs") - puts "➡️ Re-using existing clone" -else - verbose "➡️ Deleting .vuepress/openhab-docs if existing..." - FileUtils.rm_rf(".vuepress/openhab-docs") -end - -if $pull_request - # .vuepress/openhab-docs is git-ignored in the website repository, which is why we can clone it into website without any issue. - checkout_pull_request($pull_request, ".vuepress") -elsif !$parameter_no_clone - puts "➡️ Cloning repository #{$docs_repo} 📦 ..." - `git clone --depth 1 --branch #{$version || $docs_repo_branch} #{$docs_repo} .vuepress/openhab-docs` -end - -# Get a list of sub-addons to transform them into links -def get_subs_links(parent_addon_id, search_dir) - sub_addons = [] - Dir.glob("#{search_dir}/#{parent_addon_id}.*/**/readme.md") do |sub_readme| - sub_addon_id = File.dirname(sub_readme).split("/").last - verbose " ➡️ expanding list of sub-addons: #{sub_addon_id}" - File.open(sub_readme).each do |line| - if line =~ /^# / - sub_addons.push([sub_addon_id, line.gsub("# ", "").strip]) - break - end - end - end - - sub_addons -end - -def process_file(indir, file, outdir, source) - in_frontmatter = false - frontmatter_processed = false - has_source = false - has_logo = false - obsolete_binding = false - og_title = "openHAB" - og_description = "a vendor and technology agnostic open source automation software for your home" - - unless File.exist?("#{indir}/#{file}") - verbose "process_file: IGNORING (NON-EXISTING): #{indir}/#{file}" - return - end - - FileUtils.mkdir_p(outdir) - File.open("#{outdir}/#{file}", "w+") do |out| - File.open("#{indir}/#{file}").each do |line| - next if line =~ /^layout: documentation/ - next if line =~ /^layout: tutorial/ - next if line =~ /^layout: developers/ - next if line =~ /^layout: intro/ - next if line =~ /^{% include base.html %}/ - next if line =~ /\{: #/ - next if line =~ /\{::options/ - next if line =~ /TOC/ - next if line =~ /no_toc/ - - has_source = true if in_frontmatter && line =~ /^source:/ - has_logo = true if in_frontmatter && line =~ /^logo:/ - - og_title = line.gsub("title: ", "").gsub("\n", "") if in_frontmatter && line =~ /^title:/ - if in_frontmatter && line =~ /^description:/ - og_description = line.gsub("description: ", "").gsub("\n", "").gsub("[", "").gsub("]", "").gsub( - %r{\(http[:/\-0-9A-Za-z\.]+\)}, "" - ) - end - - if line =~ /^---$/ - if !in_frontmatter - in_frontmatter = true - elsif !frontmatter_processed - if !has_source && source - # Prefer already present source - out.puts "source: #{source}" - elsif !has_source - # Try to determine the source - outdir_parts = outdir.split("/") - outdir_parts[1] = "binding" if outdir_parts[1] == "bindings" - outdir_parts[1] = "transform" if outdir_parts[1] == "transformations" - outdir_parts[1] = "io" if outdir_parts[1] == "integrations" - if outdir_parts[0] == "addons" - addon_type = outdir_parts[1] - addon = file.split("/")[0] - source = "" - if addon_type == "ui" - puts " (add-on type is ui)" - source = "https://github.com/openhab/openhab-webui/blob/#{$addons_repo_branch}/bundles/org.openhab.ui.#{addon}/README.md" - elsif addon == "zigbee" - puts " (add-on is zigbee)" - source = "https://github.com/openhab/org.openhab.binding.zigbee/blob/#{$addons_repo_branch}/org.openhab.binding.zigbee/README.md" - elsif addon == "zwave" && file !~ /things/ - puts " (add-on is zwave)" - source = "https://github.com/openhab/org.openhab.binding.zwave/blob/#{$addons_repo_branch}/README.md" - elsif file !~ /things/ - source = "https://github.com/openhab/openhab-addons/blob/#{$addons_repo_branch}/bundles/org.openhab.#{addon_type}.#{addon}/README.md" - end - - out.puts "source: #{source}" if source != "" - - # For sub-bundles, set the "prev" link to the main add-on - out.puts "prev: ../#{addon.split(".")[0]}/" if addon.include?(".") - - # Prev link to the main binding doc for zwave/doc/things.md - out.puts "prev: ../" if file == "zwave/doc/things.md" - end - end - - # Add OpenGraph tags - out.puts "meta:" - out.puts " - property: og:title" - out.puts " content: \"#{og_title.gsub('"', '\"')}\"" - out.puts " - property: og:description" - out.puts " content: #{og_description}" - - in_frontmatter = false - frontmatter_processed = true - end - end - - # Remove collapsibles in Linux install document and replace them by regular headings - next if line =~ /include collapsible/ && file =~ /linux/ - - line = "##### #{line}" if line =~ /^Apt Based Systems/ && file =~ /linux/ - line = "##### #{line}" if line =~ /^Yum or Dnf Based Systems/ && file =~ /linux/ - line = "##### #{line}" if line =~ /^Systems based on/ && file =~ /linux/ - - # Expand comments with a list of links - # (https://github.com/eclipse/smarthome/issues/5571) - if line =~ // - sub_addons = get_subs_links(file.split("/")[0], indir) - out.puts - sub_addons.each do |sub| - out.puts "- [#{sub[1]}](../#{sub[0]}/)" - end - out.puts - end - - # Replace links to generated docs in ZWave's things.md by links to the internal viewer - line = line.gsub(%r{]\((.*)/(.*)\)}, '](../thing.html?manufacturer=\1&file=\2)') if file == "zwave/doc/things.md" - - # Misc replaces (relative links, remove placeholder interpreted as custom tags) - line = line.gsub("http://docs.openhab.org/addons/uis/habpanel/readme.html", "/docs/configuration/habpanel.html") - line = line.gsub("http://docs.openhab.org/addons/uis/basic/readme.html", "/addons/ui/basic/") - line = line.gsub(%r{http://docs\.openhab\.org/addons/(.*)/(.*)/readme\.html}, '/addons/\1/\2/') - line = line.gsub("http://docs.openhab.org/", "/docs/") - line = line.gsub("/addons/io/", "/addons/integrations/") - line = line.gsub("{{base}}/", "./docs/") - line = line.gsub("(images/", "(./images/") - line = line.gsub("src=\"images/", "src=\"./images/") - line = line.gsub("]:images/", "]:./images/") - line = line.gsub("](doc/", "](./doc/") - line = line.gsub("(diagrams/", "(./diagrams/") - line = line.gsub("./docs/tutorials/beginner/", "/docs/tutorial/") - line = line.gsub("./docs/", "/docs/") - line = line.gsub("", '\') - line = line.gsub("", '\') - line = line.gsub("(?(?!`)", '\') - line = line.gsub("", '\') - line = line.gsub("", "``") if file =~ /amazonechocontrol/ - line = line.gsub("", "<SerialNumber>") if file =~ /airvisualnode/ - line = line.gsub("", "<version>") if file =~ /caldav/ - line = line.gsub("by ", "by ``") if file =~ /ipx8001/ - line = line.gsub("
", "
") - line = line.gsub("':'", "`:`") if file =~ /lametrictime/ - line = line.gsub("", "``") if file =~ /milight/ - line = line.gsub("", "``") if file =~ /milight/ - line = line.gsub("", "``") if file =~ /milight/ - line = line.gsub("", "``") if file =~ /milight/ - line = line.gsub("", "``") if file =~ /milight/ - line = line.gsub("", "``") if file =~ /milight/ - line = line.gsub("[](", "[here](") if file =~ /powermax1/ - line = line.gsub("", "<n>") if file =~ /rfxcom/ - line = line.gsub(" ", " <value> ") if file =~ /zibase/ - line = line.gsub("", "<username>") if file =~ /zoneminder/ - line = line.gsub("", "<password>") if file =~ /zoneminder/ - line = line.gsub("", "<yourzmip>") if file =~ /zoneminder/ - line = line.gsub(" ", " <chatId> ") if file =~ /telegram/ - line = line.gsub(" ", " <token> ") if file =~ /telegram/ - line = line.gsub("", '\') - line = line.gsub('src="images/', 'src="./images/') if outdir =~ /apps/ - line = line.gsub("](/images/", "](./images/") if outdir =~ /google-assistant/ - - line = line.gsub(/\{:(style|target).*\}/, "") # Jekyll inline attributes syntax not supported - - out.puts line - end - - # Add the component for the edit link - out.puts - out.puts "" - end -end - -puts "➡️ Migrating the introduction article" -process_file(".vuepress/openhab-docs", "introduction.md", "docs", "https://github.com/openhab/openhab-docs/blob/main/introduction.md") -FileUtils.mv("docs/introduction.md", "docs/readme.md") - -# puts "➡️ Migrating common images" -# FileUtils.mkdir_p("docs/images") - -puts "➡️ Migrating logos" -FileUtils.cp_r(".vuepress/openhab-docs/images/addons", ".vuepress/public/logos") - -puts "➡️ Migrating the Concepts section" -Dir.glob(".vuepress/openhab-docs/concepts/*.md") do |path| - file = File.basename(path) - verbose " ➡️ #{file}" - process_file(".vuepress/openhab-docs/concepts", file, "docs/concepts", "#{$docs_repo_root}/concepts/#{file}") -end -verbose " ➡️ images and diagrams" -FileUtils.cp_r(".vuepress/openhab-docs/concepts/images", "docs/concepts") -FileUtils.cp_r(".vuepress/openhab-docs/concepts/diagrams", "docs/concepts") - -puts "➡️ Migrating the Installation section" -Dir.glob(".vuepress/openhab-docs/installation/*.md") do |path| - file = File.basename(path) - next if file == "designer.md" - - verbose " ➡️ #{file}" - process_file(".vuepress/openhab-docs/installation", file, "docs/installation", - "#{$docs_repo_root}/installation/#{file}") -end -verbose " ➡️ images" -FileUtils.cp_r(".vuepress/openhab-docs/installation/images", "docs/installation") - -puts "➡️ Migrating the Tutorial section" -Dir.glob(".vuepress/openhab-docs/tutorials/getting_started/*.md") do |path| - file = File.basename(path) - verbose " ➡️ #{file}" - process_file(".vuepress/openhab-docs/tutorials/getting_started", file, "docs/tutorial", - "#{$docs_repo_root}/tutorials/getting_started/#{file}") -end -verbose " ➡️ images" -FileUtils.cp_r(".vuepress/openhab-docs/tutorials/getting_started/images", "docs/tutorial") - -puts "➡️ Migrating the Configuration section" -Dir.glob(".vuepress/openhab-docs/configuration/*.md") do |path| - file = File.basename(path) - next if file == "transform.md" # Useless, copy the one from addons - - verbose " ➡️ #{file}" - process_file(".vuepress/openhab-docs/configuration", file, "docs/configuration", - "#{$docs_repo_root}/configuration/#{file}") -end -verbose " ➡️ images" -FileUtils.cp_r(".vuepress/openhab-docs/configuration/images", "docs/configuration") - -if $version == "final" - puts "➡️ Migrating the YAML Configuration section" - Dir.glob(".vuepress/openhab-docs/configuration/yaml/*.md") do |path| - file = File.basename(path) - - verbose " ➡️ #{file}" - process_file(".vuepress/openhab-docs/configuration/yaml", file, "docs/configuration/yaml", - "#{$docs_repo_root}/configuration/yaml/#{file}") - end -end - -puts "➡️ Migrating the Main UI section" -Dir.glob(".vuepress/openhab-docs/mainui/*.md") do |path| - file = File.basename(path) - verbose " ➡️ #{file}" - process_file(".vuepress/openhab-docs/mainui", file, "docs/mainui", "#{$docs_repo_root}/mainui/#{file}") -end -%w[developer settings].each do |subsection| - Dir.glob(".vuepress/openhab-docs/mainui/#{subsection}/*.md") do |path| - file = File.basename(path) - verbose " ➡️ #{subsection}/#{file}" - process_file(".vuepress/openhab-docs/mainui/#{subsection}", file, "docs/mainui/#{subsection}", - "#{$docs_repo_root}/mainui/#{subsection}/#{file}") - end -end -verbose " ➡️ images" -FileUtils.cp_r(".vuepress/openhab-docs/mainui/images", "docs/mainui") - -puts "➡️ Migrating the Migration Tutorial section" -Dir.glob(".vuepress/openhab-docs/configuration/migration/*.md") do |path| - file = File.basename(path) - verbose " ➡️ #{file}" - process_file(".vuepress/openhab-docs/configuration/migration", file, "docs/configuration/migration", - "#{$docs_repo_root}/configuration/migration/#{file}") -end -verbose " ➡️ images" -# FileUtils.cp_r(".vuepress/openhab-docs/configuration/migration/images", "docs/configuration/migration/") // no images placed yet - -puts "➡️ Migrating the Blockly Tutorial section" -Dir.glob(".vuepress/openhab-docs/configuration/blockly/*.md") do |path| - file = File.basename(path) - verbose " ➡️ #{file}" - process_file(".vuepress/openhab-docs/configuration/blockly", file, "docs/configuration/blockly", - "#{$docs_repo_root}/configuration/blockly/#{file}") -end -verbose " ➡️ images" -# FileUtils.cp_r(".vuepress/openhab-docs/configuration/blockly/images", "docs/configuration/blockly/") // no images placed yet - -puts "➡️ Migrating the UI section" -Dir.glob(".vuepress/openhab-docs/ui/*.md") do |path| - file = File.basename(path) - verbose " ➡️ #{file}" - process_file(".vuepress/openhab-docs/ui", file, "docs/ui", "#{$docs_repo_root}/ui/#{file}") -end -verbose " ➡️ images" -FileUtils.cp_r(".vuepress/openhab-docs/ui/images", "docs/ui") - -verbose " ➡️ habpanel" -FileUtils.mkdir_p("docs/ui/habpanel") -process_file(".vuepress/openhab-docs/_addons_uis/habpanel/doc", "habpanel.md", "docs/ui/habpanel", "") -verbose " ➡️ images" -if Dir.exist?(".vuepress/openhab-docs/_addons_uis/habpanel/doc/images") - FileUtils.cp_r(".vuepress/openhab-docs/_addons_uis/habpanel/doc/images", - "docs/ui/habpanel") -end - -verbose " ➡️ habot" -FileUtils.mkdir_p("docs/ui/habot") -process_file(".vuepress/openhab-docs/_addons_uis/habot", "readme.md", "docs/ui/habot", "") -verbose " ➡️ images" - -verbose " ➡️ components" -FileUtils.mkdir_p("docs/ui/components") -Dir.glob(".vuepress/openhab-docs/_addons_uis/org.openhab.ui/doc/components/*.md") do |path| - file = File.basename(path) - verbose " ➡️ #{file}" - process_file(".vuepress/openhab-docs/_addons_uis/org.openhab.ui/doc/components", file, "docs/ui/components", "https://github.com/openhab/openhab-webui/blob/main/bundles/org.openhab.ui/doc/components/#{file}") -end -verbose " ➡️ images" -if Dir.exist?(".vuepress/openhab-docs/_addons_uis/org.openhab.ui/doc/components/images") - FileUtils.cp_r(".vuepress/openhab-docs/_addons_uis/org.openhab.ui/doc/components/images", - "docs/ui/components") -end - -puts "➡️ Migrating the Apps section" -Dir.glob(".vuepress/openhab-docs/addons/uis/apps/*.md") do |path| - file = File.basename(path) - verbose " ➡️ #{file}" - process_file(".vuepress/openhab-docs/addons/uis/apps", file, "docs/apps", - "#{$docs_repo_root}/addons/uis/apps/#{file}") -end -verbose " ➡️ images" -FileUtils.cp_r(".vuepress/openhab-docs/addons/uis/apps/images", "docs/apps") - -puts "➡️ Migrating the Garmin app section" -Dir.glob(".vuepress/openhab-docs/addons/uis/apps/garmin/*.md") do |path| - file = File.basename(path) - verbose " ➡️ #{file}" - process_file(".vuepress/openhab-docs/addons/uis/apps/garmin", file, "docs/apps/garmin", - "#{$docs_repo_root}/addons/uis/apps/garmin/#{file}") -end -verbose " ➡️ images" -FileUtils.cp_r(".vuepress/openhab-docs/addons/uis/apps/garmin/images", "docs/apps/garmin") - -puts "➡️ Migrating the Sailfish OS app section" -Dir.glob(".vuepress/openhab-docs/addons/uis/apps/sailfishos/*.md") do |path| - file = File.basename(path) - verbose " ➡️ #{file}" - process_file(".vuepress/openhab-docs/addons/uis/apps/sailfishos", file, "docs/apps/sailfishos", - "#{$docs_repo_root}/addons/uis/apps/sailfishos/#{file}") -end -verbose " ➡️ images" -FileUtils.cp_r(".vuepress/openhab-docs/addons/uis/apps/sailfishos/images", "docs/apps/sailfishos") - -puts "➡️ Migrating the Administration section" -Dir.glob(".vuepress/openhab-docs/administration/*.md") do |path| - file = File.basename(path) - verbose " ➡️ #{file}" - process_file(".vuepress/openhab-docs/administration", file, "docs/administration", - "#{$docs_repo_root}/administration/#{file}") -end -verbose " ➡️ images" -FileUtils.cp_r(".vuepress/openhab-docs/administration/images", "docs/administration") - -puts "➡️ Migrating the Developer section" -Dir.glob(".vuepress/openhab-docs/developers/*.md") do |path| - file = File.basename(path) - verbose " ➡️ #{file}" - process_file(".vuepress/openhab-docs/developers", file, "docs/developer", "#{$docs_repo_root}/developer/#{file}") -end -%w[audio bindings ioservices legacy module-types osgi persistence transformations utils - ide].each do |subsection| - Dir.glob(".vuepress/openhab-docs/developers/#{subsection}/*.md") do |path| - file = File.basename(path) - verbose " ➡️ #{subsection}/#{file}" - process_file(".vuepress/openhab-docs/developers/#{subsection}", file, "docs/developer/#{subsection}", - "#{$docs_repo_root}/developer/#{subsection}/#{file}") - end -end - -verbose " ➡️ images" -FileUtils.cp_r(".vuepress/openhab-docs/developers/bindings/images", "docs/developer/bindings") -FileUtils.cp_r(".vuepress/openhab-docs/developers/osgi/images", "docs/developer/osgi") -FileUtils.cp_r(".vuepress/openhab-docs/developers/ide/images", "docs/developer/ide") - -["addons"].each do |subsection| - Dir.glob(".vuepress/openhab-docs/developers/#{subsection}/*.md") do |path| - file = File.basename(path) - verbose " ➡️ #{subsection}/#{file}" - process_file(".vuepress/openhab-docs/developers/#{subsection}", file, "docs/developer/#{subsection}", - "#{$docs_repo_root}/developer/#{subsection}/#{file}") - end -end - -### ADDONS - -# External content is not included for PRs - therefore the _addons_*** folders are not present for PR checks - this section will be skipped. -if $pull_request - puts "" - puts "⚠️ Add-on documentation depends on Jenkins job - will be skipped ..." - puts "" -else - puts "➡️ Migrating add-ons: Automation" - Dir.glob(".vuepress/openhab-docs/_addons_automation/**") do |path| - addon = File.basename(path) - next if $ignore_addons.include?(addon) - - verbose " ➡️ #{addon}" - FileUtils.mkdir_p("addons/automation/#{addon}") - process_file(".vuepress/openhab-docs/_addons_automation", "#{addon}/readme.md", "addons/automation", nil) - - if Dir.exist?(".vuepress/openhab-docs/_addons_automation/#{addon}/doc") - verbose " ➡️ images" - FileUtils.cp_r(".vuepress/openhab-docs/_addons_automation/#{addon}/doc", "addons/automation/#{addon}") - end - end - - puts "➡️ Migrating add-ons: Persistence" - Dir.glob(".vuepress/openhab-docs/_addons_persistences/**") do |path| - addon = File.basename(path) - next if $ignore_addons.include?(addon) - - verbose " ➡️ #{addon}" - FileUtils.mkdir_p("addons/persistence/#{addon}") - process_file(".vuepress/openhab-docs/_addons_persistences", "#{addon}/readme.md", "addons/persistence", nil) - - if Dir.exist?(".vuepress/openhab-docs/_addons_persistences/#{addon}/doc") - verbose " ➡️ images" - FileUtils.cp_r(".vuepress/openhab-docs/_addons_persistences/#{addon}/doc", "addons/persistence/#{addon}") - end - end - - puts "➡️ Migrating add-ons: Transformations" - Dir.glob(".vuepress/openhab-docs/_addons_transformations/**") do |path| - addon = File.basename(path) - next if $ignore_addons.include?(addon) - - verbose " ➡️ #{addon}" - FileUtils.mkdir_p("addons/transformations/#{addon}") - process_file(".vuepress/openhab-docs/_addons_transformations", "#{addon}/readme.md", "addons/transformations", - nil) - end - - puts "➡️ Migrating add-ons: Voice" - Dir.glob(".vuepress/openhab-docs/_addons_voices/**") do |path| - addon = File.basename(path) - next if $ignore_addons.include?(addon) - - verbose " ➡️ #{addon}" - FileUtils.mkdir_p("addons/voice/#{addon}") - process_file(".vuepress/openhab-docs/_addons_voices", "#{addon}/readme.md", "addons/voice", nil) - end - - puts "➡️ Migrating add-ons: IO" - Dir.glob(".vuepress/openhab-docs/_addons_ios/**") do |path| - addon = File.basename(path) - next if $ignore_addons.include?(addon) - - verbose " ➡️ #{addon}" - - FileUtils.mkdir_p("addons/integrations/#{addon}") - process_file(".vuepress/openhab-docs/_addons_ios", "#{addon}/readme.md", "addons/integrations", nil) - if Dir.exist?(".vuepress/openhab-docs/_addons_ios/#{addon}/doc") - verbose " ➡️ images" - FileUtils.cp_r(".vuepress/openhab-docs/_addons_ios/#{addon}/doc", "addons/integrations/#{addon}") - end - if Dir.exist?(".vuepress/openhab-docs/_addons_ios/#{addon}/contrib") - verbose " ➡️ images" - FileUtils.cp_r(".vuepress/openhab-docs/_addons_ios/#{addon}/contrib", "addons/integrations/#{addon}") - end - end - - puts "➡️ Migrating add-ons: UI" - Dir.glob(".vuepress/openhab-docs/_addons_uis/**") do |path| - next if path =~ /org.openhab.ui/ - - addon = File.basename(path) - next if $ignore_addons.include?(addon) - - verbose " ➡️ #{addon}" - FileUtils.mkdir_p("addons/ui/#{addon}") - process_file(".vuepress/openhab-docs/_addons_uis", "#{addon}/readme.md", "addons/ui", nil) - - if Dir.exist?(".vuepress/openhab-docs/_addons_uis/#{addon}/doc") - verbose " ➡️ images" - FileUtils.cp_r(".vuepress/openhab-docs/_addons_uis/#{addon}/doc", "addons/ui/#{addon}") - end - end - - # Handle those three separately - copy them in the "ecosystem" section - puts "➡️ Migrating special ecosystem integrations" - verbose " ➡️ Create folders" - FileUtils.mkdir_p("docs/ecosystem/alexa") - FileUtils.mkdir_p("docs/ecosystem/google-assistant") - - ecosystem_path = "_ecosystem" - - verbose " ➡️ Process alexa-skill docs" - process_file(".vuepress/openhab-docs/#{ecosystem_path}/alexa-skill", "readme.md", "docs/ecosystem/alexa", "https://github.com/openhab/openhab-alexa/blob/master/USAGE.md") - verbose " ➡️ images" - FileUtils.cp_r(".vuepress/openhab-docs/#{ecosystem_path}/alexa-skill/images", "docs/ecosystem/alexa") - - verbose " ➡️ Process google-assistant docs" - process_file(".vuepress/openhab-docs/#{ecosystem_path}/google-assistant", "readme.md", "docs/ecosystem/google-assistant", - "https://github.com/openhab/openhab-google-assistant/blob/master/docs/USAGE.md") - verbose " ➡️ images" - FileUtils.cp_r(".vuepress/openhab-docs/#{ecosystem_path}/google-assistant/images", "docs/ecosystem/google-assistant") - - puts "➡️ Migrating add-ons: Bindings" - Dir.glob(".vuepress/openhab-docs/_addons_bindings/**") do |path| - addon = File.basename(path) - next if $ignore_addons.include?(addon) - - verbose " ➡️ #{addon}" - - FileUtils.mkdir_p("addons/bindings/#{addon}") - process_file(".vuepress/openhab-docs/_addons_bindings", "#{addon}/readme.md", "addons/bindings", nil) - if Dir.exist?(".vuepress/openhab-docs/_addons_bindings/#{addon}/doc") && addon != "zwave" - verbose " ➡️ images" - FileUtils.cp_r(".vuepress/openhab-docs/_addons_bindings/#{addon}/doc", "addons/bindings/#{addon}") - elsif Dir.exist?(".vuepress/openhab-docs/_addons_bindings/#{addon}/contrib") && addon != "zwave" - verbose " ➡️ images" - FileUtils.cp_r(".vuepress/openhab-docs/_addons_bindings/#{addon}/contrib", "addons/bindings/#{addon}") - elsif addon == "zwave" - verbose " ➡️ things.md" - FileUtils.mkdir_p("addons/bindings/zwave/doc") - process_file(".vuepress/openhab-docs/_addons_bindings", "zwave/doc/things.md", "addons/bindings", nil) - end - end - - puts "➡️ Creating Z-Wave thing viewer" - if File.exist?(".vuepress/openhab-docs/_addons_bindings/zwave/doc/things.md") - File.open("addons/bindings/zwave/thing.md", "w+") do |out| - out.puts "---" - out.puts "title: ZWave Thing" - out.puts "prev: ./" - out.puts "---" - out.puts - out.puts "" - end - end - - # puts "➡️ Migrating Z-Wave docs" - # Dir.glob(".vuepress/openhab-docs/_addons_bindings/zwave/doc/*.md") { |path| - # next if path =~ /device\.md/ - # file = File.basename(path) - # puts " -> #{file}" - # FileUtils.mkdir_p("addons/bindings/zwave/doc") - # process_file(".vuepress/openhab-docs/_addons_bindings/zwave/doc", file, "addons/bindings/zwave/doc", nil) - # } -end - -# Write arrays of addons by type to include in VuePress config.js -puts "➡️ Writing add-ons arrays to files for sidebar navigation" -%w[bindings persistence automation integrations transformations voice ui].each do |type| - File.open(".vuepress/addons-#{type}.js", "w+") do |file| - file.puts "module.exports = [" - - if Dir.exist?("addons/#{type}") - Dir.foreach("addons/#{type}") do |dir| - unless dir.include?(".") - # puts dir - found = false - File.readlines("addons/#{type}/#{dir}/readme.md").each do |line| - if line =~ /^label:/ && !found - title = line.gsub("label: ", "").gsub("\n", "") - file.puts "\t['#{type}/#{dir}/', '#{title}']," unless title =~ /1\.x/ - found = true - end - end - end - end - end - - file.puts "]" - end -end - -# External content is not included for PRs - therefore the _addons_iconsets folder is not present for PR checks - this section will be skipped. -if $pull_request - puts "" - puts "⚠️ Iconsets depend on Jenkins job - will be skipped ..." - puts "" -else - # Regenerate the classic iconset docs - puts "➡️ Generating iconset" - system("ruby generate_iconset_doc.rb .vuepress/openhab-docs/_addons_iconsets classic .vuepress/openhab-docs/_data docs/configuration/iconsets") -end - -# Clean-Ups required for repeated local build -verbose "🧹 Cleaning existing JavaDoc ..." -FileUtils.rm Dir.glob("javadoc-latest.*"), force: true -FileUtils.rm_rf Dir.glob(".vuepress/public/javadoc/latest") - -# Publish latest Javadoc -puts "➡️ Downloading and extracting latest Javadoc from Jenkins" -`wget -nv https://ci.openhab.org/job/openHAB-JavaDoc/lastSuccessfulBuild/artifact/target/javadoc-latest.tgz` -`tar xzvf javadoc-latest.tgz --strip 2 && mv apidocs/ .vuepress/public/javadoc/latest` - -# External content is not included for PRs - therefore thing-types.json is not present for PR checks - this section will be skipped. -if $pull_request - puts "" - puts "⚠️ Thing types depend on Jenkins job - will be skipped ..." - puts "" -else - # Copy the thing-types.json file to the proper location - puts "➡️ Copying Thing types" - FileUtils.cp(".vuepress/openhab-docs/.vuepress/thing-types.json", ".vuepress") -end diff --git a/scripts/add-blog-meta.rb b/scripts/add-blog-meta.rb new file mode 100644 index 000000000..632907947 --- /dev/null +++ b/scripts/add-blog-meta.rb @@ -0,0 +1,101 @@ +# frozen_string_literal: true + +# Adds the OpenGraph & Twitter meta tags to the blog articles +require "fileutils" +require "pathname" +require "tmpdir" + +REPO_ROOT = Pathname(__dir__).parent.realpath + +def process_file(src, dst) + in_frontmatter = false + parsing_multiline_excerpt = false + og_title = "openHAB" + og_description = "a vendor and technology agnostic open source automation software for your home" + og_image = nil + + # Feed Meta + fm_author = nil + meta_present = false + + File.open(dst, "w+") do |out| + File.open(src).each do |line| + # FIXME: require "yaml" and parse properly one day... + if parsing_multiline_excerpt + if line =~ /^ / + og_description += " " + og_description += line.gsub(/^ /, "").gsub("\n", "") + next + else + og_description.strip! + parsing_multiline_excerpt = false + end + end + + og_title = line.gsub("title: ", "").gsub("\n", "") if in_frontmatter && line =~ /^title:/ + + # detect if meta: block already exists in frontmatter to avoid duplicate insertion + meta_present = true if in_frontmatter && line =~ /^\s*meta:/ + + if in_frontmatter && line =~ /^excerpt:/ + og_description = line.gsub("excerpt: ", "").gsub("\n", "").gsub("[", "").gsub("]", "").gsub(%r{\(http[:/\-0-9A-Za-z\.]+\)}, "") + if og_description == ">-" + og_description = "" + parsing_multiline_excerpt = true + next + end + end + + og_image = line.gsub("previewimage: ", "").gsub("\n", "") if in_frontmatter && line =~ /^previewimage:/ + + fm_author = line.gsub("author: ", "").gsub("\n", "") if in_frontmatter && line =~ /^author:/ + + if line =~ /^---$/ + if in_frontmatter + # Add OpenGraph tags + unless meta_present + out.puts "meta:" + out.puts " - name: twitter:card" + out.puts " content: summary_large_image" + out.puts " - property: og:title" + out.puts " content: \"#{og_title.gsub('"', '\\"')}\"" + out.puts " - property: og:description" + out.puts " content: #{og_description}" + out.puts " - property: og:image" if og_image + out.puts " content: https://www.openhab.org#{og_image}" if og_image + + # Add feed meta tags, when something relevant is available + if !fm_author.nil? || !og_image.nil? + out.puts "feed:" + out.puts " image: https://www.openhab.org#{og_image}" if og_image + out.puts " author:" + out.puts " - name: #{fm_author}" + end + end + + in_frontmatter = false + else + in_frontmatter = true + meta_present = false + end + end + + out.puts line + end + end +end + +puts "➡️ Adding metadata to blog posts" + +Dir.mktmpdir do |tmp_dir| + tmp_workspace = Pathname(tmp_dir) + + Pathname.glob(REPO_ROOT / "blog/*.md").each do |src| + next if src.basename.fnmatch?("index.md") + + dst = tmp_workspace / src.basename + puts " ➡️ #{src.basename}" + process_file(src, dst) + FileUtils.mv(dst, src) if dst.exist? && !FileUtils.identical?(src, dst) + end +end diff --git a/scripts/generate_iconset_doc.rb b/scripts/generate_iconset_doc.rb new file mode 100644 index 000000000..d0437b624 --- /dev/null +++ b/scripts/generate_iconset_doc.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require_relative "lib/website_utils" + +# This script re-generate a VuePress-compatible version of an +# iconset's documentation + +# Load the list of icons from the original iconset +# Load the categories from the CSV files +# Generate a markdown file with the list of icons and their categories +# Copy the icons to the public folder +$original_iconsets_location = ARGV[0] +$iconset_name = ARGV[1] +$data_dir = ARGV[2] +$out_dir = ARGV[3] + +if !$original_iconsets_location || !$iconset_name || !$data_dir || !$out_dir + puts "Usage: generate_iconset_doc.rb " + exit(1) +end + +process_iconset( + iconset: $iconset_name, + src: $original_iconsets_location, + dst: $out_dir, + data: $data_dir +) diff --git a/scripts/lib/website_utils.rb b/scripts/lib/website_utils.rb new file mode 100644 index 000000000..91c4be3f3 --- /dev/null +++ b/scripts/lib/website_utils.rb @@ -0,0 +1,121 @@ +# frozen_string_literal: true + +require "English" +require "csv" +require "fileutils" + +def verbose(message) + puts message if $verbose +end + +def checkout_pull_request(pull_request, target_directory) + pull_request_url = "https://api.github.com/repos/openhab/openhab-docs/pulls/#{pull_request}" + + response = JSON.parse(URI.parse(pull_request_url).read) + repository_url = response["head"]["repo"]["clone_url"] + label = response["head"]["label"] + sha = response["head"]["sha"] + branch = response["head"]["ref"] + title = response["title"] + + puts "➡️ Cloning repository 📦 #{label} ..." + puts " ↪️ PR ##{pull_request}: #{title}" + + system("OH_DOCS_VERSION=#{branch}") + + FileUtils.cd(target_directory, verbose: false) do + system("git clone --depth 1 #{repository_url} --branch #{branch} #{$verbose ? "" : "--quiet"} .") + system("git reset ##{sha} #{$verbose ? "" : "--quiet"}") + end +end + +def clean_ignored_files(path, dry_run: false) + # -f: Force (delete) + # -n: Dry-run (show what would happen) + # -d: Remove directories + # -X: Remove ONLY ignored files + # -q: Quiet (suppress output) + + mode = dry_run ? "-ndX" : "-fdXq" + + Dir.chdir(path) do + puts "🧹 Cleaning ignored files in #{path} #{dry_run ? "(dry run)" : ""}..." + output = `git clean #{mode}` + puts output if dry_run + $CHILD_STATUS.success? + end +rescue Errno::ENOENT, Errno::EACCES => e + puts "Error accessing #{path}: #{e.message}" + false +end + +# +# Re-generate a VuePress-compatible version of an iconset's documentation +# +# - Load the list of icons from the original iconset +# - Load the categories from the CSV files +# - Generate a markdown file with the list of icons and their categories +# - Copy the icons to the public folder (.vuepress/public/iconsets/) +# +# @param iconset [String] the name of the iconset (e.g. "classic") +# @param src [Pathname, String] the path to the original iconsets (e.g. ".vuepress/openhab-docs/_addons_iconsets") +# @param dst [Pathname, String] the path to the output directory (e.g. "docs/configuration/iconsets") +# @param data [Pathname, String] the path to the CSV data files (e.g. ".vuepress/openhab-docs/_data") +# +def process_iconset(iconset:, src:, dst:, data:) + src = Pathname(src) + data = Pathname(data) + dst = Pathname(dst) + + icons_path = src / iconset / "src/main/resources/icons" + icons_list = icons_path.glob("*.svg").map { |path| path.basename.to_s } + + categories_channels = {} + CSV.foreach(data / "categories.csv", headers: true) do |row| + # Using a standard loop over compact Ruby shorthands for better clarity + type = row["type"] + name = row["name"] + + categories_channels[type] ||= [] + categories_channels[type] << name + end + categories_places = CSV.foreach(data / "categories_places.csv", headers: true).map { |row| row["name"] } + categories_thing = CSV.foreach(data / "categories_thing.csv", headers: true).map { |row| row["name"] } + + iconset_readme = dst / iconset / "readme.md" + iconset_readme.dirname.mkpath + iconset_readme.open("w+") do |f| + f.puts "---" + f.puts "title: Icons" + f.puts "categories:" + + f.puts " channels:" + categories_channels.each do |type, channels| + f.puts " #{type}:" + channels.each { |channel| f.puts " - #{channel.downcase}" } + end + + f.puts " places:" + categories_places.each { |place| f.puts " - #{place.downcase}" } + + f.puts " things:" + categories_thing.each { |thing| f.puts " - #{thing.downcase}" } + + f.puts "---" + f.puts # Blank line + f.puts "# Icons" + f.puts + f.puts "These are the classic icons from Eclipse SmartHome." + f.puts + f.puts "These icons can be used when describing Items. You can also add your own. See the [instructions](/docs/configuration/items.html#icons) to learn more." + f.puts + f.puts "" + end + + puts " ➡️ File written in #{iconset_readme}" + + # FileUtils.mkdir_p(".vuepress/public/iconsets") + FileUtils.cp_r(icons_path, ".vuepress/public/iconsets/#{iconset}") + + puts " ➡️ Icons copied to .vuepress/public/iconsets/#{iconset}" +end diff --git a/scripts/prepare-docs.rb b/scripts/prepare-docs.rb new file mode 100644 index 000000000..85437d468 --- /dev/null +++ b/scripts/prepare-docs.rb @@ -0,0 +1,300 @@ +# frozen_string_literal: true + +# This will clone https://github.com/openhab/openhab-docs +# and migrate content into the website with some changes + +require "optparse" +require "fileutils" +require "pathname" +require "uri" +require "open-uri" +require "json" + +require_relative "lib/website_utils" + +DOCS_REPO_URL = "https://github.com/openhab/openhab-docs" +DOCS_REPO_BRANCH = ENV.fetch("OH_DOCS_VERSION", "final").then { |v| (v.count(".") == 1) ? "#{v}.0" : v } + +DOCS_SRC = Pathname(".vuepress/openhab-docs") +DOCS_DST = Pathname("docs") +ADDONS_DST = Pathname("addons") +LOGOS_DST = Pathname(".vuepress/public/logos") + +$verbose = false + +IGNORED_ADDONS = %w[transport.modbus transport.feed javasound webaudio oh2].freeze # No longer relevant? + +options = {} +OptionParser.new do |opts| + opts.banner = "Usage: prepare-docs.rb [options]" + opts.on("--no-clone", "Don't clone the openhab-docs repository, but use an existing clone if available") do + options[:no_clone] = true + verbose "➡️ no-clone: existing clone will be used" + end + + opts.on("--pull-request PR_NUMBER", "Use a specific pull request from the openhab-docs repository instead of cloning a branch") do |pr_number| + options[:pull_request] = pr_number + verbose "➡️ PR ##{pr_number} will be used to build documentation" + end + + opts.on("--verbose", "Run the script with verbose output") do + $verbose = true + end +end.parse! + +puts "➡️ Generating docs from openhab-docs branch '#{DOCS_REPO_BRANCH}'" + +if options[:no_clone] && DOCS_SRC.exist? + puts "➡️ Re-using existing clone" +else + puts "➡️ Cleaning #{DOCS_SRC}..." + DOCS_SRC.rmtree if DOCS_SRC.exist? +end + +if options[:pull_request] + checkout_pull_request(options[:pull_request], DOCS_SRC) +elsif !DOCS_SRC.exist? + puts "➡️ Cloning repository #{DOCS_REPO_URL}/#{DOCS_REPO_BRANCH} into #{DOCS_SRC} 📦 ..." + `git clone --depth 1 --branch #{DOCS_REPO_BRANCH} #{DOCS_REPO_URL} #{DOCS_SRC}` + + if DOCS_SRC.join("src").exist? # Check if the clone was successful by checking for the existence of the "src" folder + puts "➡️ Clone successful" + else + # Temporarily fetch the src from local dev branch until the PR is merged, as the src folder is required to build the docs + # This is a fallback before the openhab/openhab-docs#2718 PR is merged + puts "➡️ Copying temp source files during development" + + `git clone --depth 1 --branch refactor-prepare-docs "https://github.com/jimtng/openhab-docs.git" /tmp/openhab-docs-dev` + FileUtils.cp_r("/tmp/openhab-docs-dev/src", DOCS_SRC / "src") + FileUtils.rm_rf("/tmp/openhab-docs-dev") # Clean up the temporary clone + + if DOCS_REPO_BRANCH == "final-stable" + # We need to do this because the final-stable branch used in the CI lags behind the contents + # of the dev branch containing the src folder that we copied above + sidebar_js = DOCS_SRC.join(".vuepress/docs-sidebar.js") + sidebar_js.write(URI.open("https://raw.githubusercontent.com/jimtng/openhab-docs/refactor-prepare-docs/.vuepress/docs-sidebar.js").read) # Remove /src/ from the sidebar links, as our temporary src is not in the root of the repository + end + end +end + +# Temporarily sync the openhabian docs from the old location +# Once a new openhabian docs PR is merged, the new action will publish them to src/installation +# and when that happens, this sync can be removed +if Dir.exist?(DOCS_SRC / "installation") + puts "➡️ Syncing openhabian docs from old location to src/installation" + `rsync -a #{DOCS_SRC}/installation/ #{DOCS_SRC}/src/installation/` +end + +raise "Failed to prepare openhab-docs source. Please check if the repository was cloned successfully." unless DOCS_SRC.join("src").exist? + +# Fetch process_utils from the openhab-docs repository if we haven't already - we need it to process the documentation files +process_utils = DOCS_SRC / "scripts/lib/process_utils.rb" +unless process_utils.exist? + process_utils.parent.mkpath + begin + process_utils.write(URI.open("https://raw.githubusercontent.com/openhab/openhab-docs/main/scripts/lib/process_utils.rb").read) + rescue OpenURI::HTTPError => e + raise unless e.io.status[0] == "404" + + # Temporarily fetch this from my local dev branch until the PR is merged, as process_utils is required to build the docs + process_utils.write(URI.open("https://raw.githubusercontent.com/jimtng/openhab-docs/refactor-prepare-docs/scripts/lib/process_utils.rb").read) + end +end + +# TEMP: +# process_utils.write(File.read("../../openhab-docs.worktrees/refactor-prepare-docs/scripts/lib/process_utils.rb")) + +# After we've cloned the openhab-docs, we can require process_utils to use its helper functions +require DOCS_SRC.expand_path / "scripts/lib/process_utils" + +clean_ignored_files(DOCS_DST) +clean_ignored_files(ADDONS_DST) + +puts "➡️ Migrating logos" +LOGOS_DST.rmtree if LOGOS_DST.exist? +FileUtils.cp_r(DOCS_SRC / "images/addons", LOGOS_DST) + +puts "➡️ Migrating the main documentation sections" +process_directory src: DOCS_SRC / "src", + dst: DOCS_DST, + source_root: "https://github.com/openhab/openhab-docs/blob/main/src" + +puts "➡️ Migrating the UI section" +verbose " ➡️ components" +process_directory src: DOCS_SRC.join("_addons_uis/org.openhab.ui/doc/components"), # use join to avoid syntax highlighting bug in vscode + dst: DOCS_DST / "ui/components", + source_root: "https://github.com/openhab/openhab-webui/blob/main/bundles/org.openhab.ui/doc/components" + +verbose " ➡️ habpanel" +# habpanel.md provides its own source: frontmatter +process_directory src: DOCS_SRC / "_addons_uis/habpanel/doc", + dst: DOCS_DST / "ui/habpanel" + +verbose " ➡️ habot" +# habot repo is archived, so we don't set a source root link +process_directory src: DOCS_SRC / "_addons_uis/habot", + dst: DOCS_DST / "ui/habot" + +puts "➡️ Migrating the apps section" +# The external apps docs provide their own `source:` frontmatter +# No need to process individual app. +# This will process everything in the source folder +process_directory src: DOCS_SRC / "addons/uis/apps", + dst: DOCS_DST / "apps" + +### ADDONS + +# External content is not included for PRs - therefore the _addons_*** folders are not present for PR checks - this section will be skipped. +if options[:pull_request] + puts "" + puts "⚠️ Add-on documentation depends on Jenkins job - will be skipped ..." + puts "" +else + puts "➡️ Migrating add-ons: Automation" + process_directory src: DOCS_SRC / "_addons_automation", + dst: ADDONS_DST / "automation" + + puts "➡️ Migrating add-ons: Persistence" + process_directory src: DOCS_SRC / "_addons_persistences", + dst: ADDONS_DST / "persistence" + + puts "➡️ Migrating add-ons: Transformations" + process_directory src: DOCS_SRC / "_addons_transformations", + dst: ADDONS_DST / "transformations" + + puts "➡️ Migrating add-ons: Voice" + process_directory src: DOCS_SRC / "_addons_voices", + dst: ADDONS_DST / "voice" + + puts "➡️ Migrating add-ons: IO" + process_directory src: DOCS_SRC / "_addons_ios", + dst: ADDONS_DST / "integrations" + + puts "➡️ Migrating add-ons: UI" + process_directory src: DOCS_SRC / "_addons_uis", + dst: ADDONS_DST / "ui" do |current_path| + next if current_path.each_filename.include?("org.openhab.ui") + + true + end + + # Handle those three separately - copy them in the "ecosystem" section + puts "➡️ Migrating special ecosystem integrations" + verbose " ➡️ Process alexa-skill docs" + process_directory src: DOCS_SRC / "_ecosystem/alexa-skill", + dst: DOCS_DST / "ecosystem/alexa" + + verbose " ➡️ Process google-assistant docs" + process_directory src: DOCS_SRC / "_ecosystem/google-assistant", + dst: DOCS_DST / "ecosystem/google-assistant" + + puts "➡️ Migrating add-ons: Bindings" + bindings_src = DOCS_SRC / "_addons_bindings" + zwave_src = bindings_src / "zwave" + zwave_docs = [zwave_src / "readme.md", zwave_src / "doc/things.md"] # Only include the readme and the things doc for zwave, as the rest is quite outdated and not maintained anymore + process_directory(src: bindings_src, dst: ADDONS_DST / "bindings") do |current_path| + # Grab the first path that is a child of bindings_src + addon = current_path.descend.find { |p| p.parent == bindings_src }&.basename.to_s + next false if IGNORED_ADDONS.include?(addon) + + if addon == "zwave" && !zwave_docs.include?(current_path) + next false # If it is zwave, only include readme.md and doc/things.md + end + + true # For all other addons, include everything + end + + zwave_things_src = zwave_src / "doc/things.md" + if zwave_things_src.exist? + puts " ➡️ Creating Z-Wave thing viewer" + + zwave_thing_dst = ADDONS_DST / "bindings/zwave/thing.md" + zwave_thing_dst.write <<~MARKDOWN + --- + title: ZWave Thing + prev: ./ + --- + + + MARKDOWN + end + + # Custom fixes + broken_file = Pathname("addons/bindings/shelly/doc/UseCaseSmartRoller.md") + if broken_file.exist? + puts " ➡️ Fixing broken Shelly doc" + lines = broken_file.readlines.reject { |line| line.include?("uiroller_1.png") } + broken_file.write(lines.join) + end +end + +# Write arrays of addons by type to include in VuePress config.js +puts "➡️ Writing add-ons arrays to files for sidebar navigation" +%w[bindings persistence automation integrations transformations voice ui].each do |type| + type_dir = ADDONS_DST / type + next unless type_dir.directory? + + # Find all subdirectories excluding hidden ones + module_exports = type_dir.children.select(&:directory?).filter_map do |addon_path| + readme = addon_path / "readme.md" + next unless readme.exist? + + # Find the first line starting with "label: " + label_line = readme.each_line.find { |line| line.start_with?("label: ") } + next unless label_line + + title = label_line.delete_prefix("label: ").strip + next if title.include?("1.x") + + path = "#{type}/#{addon_path.basename}/" + + # Return the pair for the module_exports array + [path, title] + end + + formatted_exports = module_exports.map { |path, title| " [ '#{path}', '#{title}' ]" }.join(",\n") + + File.write(".vuepress/addons-#{type}.js", <<~JS) + module.exports = [ + #{formatted_exports} + ] + JS +end + +# External content is not included for PRs - therefore the _addons_iconsets folder is not present for PR checks - this section will be skipped. +if options[:pull_request] + puts "" + puts "⚠️ Iconsets depend on Jenkins job - will be skipped ..." + puts "" +else + # Regenerate the classic iconset docs + puts "➡️ Generating iconset" + process_iconset( + iconset: "classic", + src: DOCS_SRC / "_addons_iconsets", + dst: DOCS_DST / "configuration/iconsets", + data: DOCS_SRC / "_data" + ) +end + +# Clean-Ups required for repeated local build +verbose "🧹 Cleaning existing JavaDoc ..." +FileUtils.rm Dir.glob("javadoc-latest.*"), force: true +FileUtils.rm_rf(".vuepress/public/javadoc/latest") + +# Publish latest Javadoc +puts "➡️ Downloading and extracting latest Javadoc from Jenkins" +`wget -nv https://ci.openhab.org/job/openHAB-JavaDoc/lastSuccessfulBuild/artifact/target/javadoc-latest.tgz` +`tar xzvf javadoc-latest.tgz --strip 2 && mv apidocs/ .vuepress/public/javadoc/latest` +FileUtils.rm "javadoc-latest.tgz" + +# External content is not included for PRs - therefore thing-types.json is not present for PR checks - this section will be skipped. +if options[:pull_request] + puts "" + puts "⚠️ Thing types depend on Jenkins job - will be skipped ..." + puts "" +else + # Copy the thing-types.json file to the proper location + puts "➡️ Copying Thing types" + FileUtils.cp(DOCS_SRC / ".vuepress/thing-types.json", ".vuepress") +end