22
33require "rexml/document"
44require "fileutils"
5+ require "yaml"
56
67# AddonProcessor processes external add-on repositories (openhab-addons and openhab-webui).
78#
@@ -49,7 +50,7 @@ def self.process_addon_type(features, dest_addons_dir, images_dir, src_bundles_d
4950 end
5051
5152 Dir . foreach ( src_bundles_dir ) do |addon_name |
52- next if addon_name == "." || addon_name == ".."
53+ next if [ "." , ".." ] . include? ( addon_name )
5354
5455 # Match the type in package name structure (org.openhab.<type>...)
5556 parts = addon_name . split ( "." )
@@ -84,7 +85,7 @@ def self.process_addon_type(features, dest_addons_dir, images_dir, src_bundles_d
8485 FileUtils . mkdir_p ( target_dir )
8586
8687 # Copy doc/, cfg/, images/, and icons/ if they exist
87- [ " doc" , " cfg" , " images" , " icons" ] . each do |sub_dir |
88+ %w[ doc cfg images icons ] . each do |sub_dir |
8889 source_sub = File . join ( addon_path , sub_dir )
8990 if id == "zwave" && sub_dir == "doc"
9091 zwave_things_md = File . join ( source_sub , "thing.md" )
@@ -95,9 +96,7 @@ def self.process_addon_type(features, dest_addons_dir, images_dir, src_bundles_d
9596 end
9697 next
9798 end
98- if Dir . exist? ( source_sub )
99- FileUtils . cp_r ( source_sub , target_dir )
100- end
99+ FileUtils . cp_r ( source_sub , target_dir ) if Dir . exist? ( source_sub )
101100 end
102101
103102 # Look for README.md (case-insensitive)
@@ -109,16 +108,17 @@ def self.process_addon_type(features, dest_addons_dir, images_dir, src_bundles_d
109108
110109 # Read and parse readme
111110 readme_text = File . read ( readme_src )
111+ existing_front_matter , readme_text = split_front_matter ( readme_text )
112112
113113 # Determine label from first level 1 header
114114 label = nil
115115 readme_text . each_line do |line |
116- if line . start_with? ( "#" )
117- label = line . gsub ( "#" , "" ) . strip
118- lblremoves . each { | remove | label = label . gsub ( Regexp . new ( remove ) , "" ) }
119- label = label . strip
120- break
121- end
116+ next unless line . start_with? ( "#" )
117+
118+ label = line . gsub ( "#" , "" ) . strip
119+ lblremoves . each { | remove | label = label . gsub ( Regexp . new ( remove ) , "" ) }
120+ label = label . strip
121+ break
122122 end
123123
124124 if label . nil? || label . empty?
@@ -143,18 +143,18 @@ def self.process_addon_type(features, dest_addons_dir, images_dir, src_bundles_d
143143 end
144144
145145 # Build frontmatter hash
146- front = {
146+ front_matter = {
147147 "id" => id ,
148148 "label" => label ,
149149 "title" => "#{ label } #{ suffix } " ,
150150 "type" => type ,
151- "description" => " \" #{ description } \" "
151+ "description" => description
152152 }
153153
154154 if logo_svg
155- front [ "logo" ] = "images/addons/#{ id } .svg"
155+ front_matter [ "logo" ] = "images/addons/#{ id } .svg"
156156 elsif logo_png
157- front [ "logo" ] = "images/addons/#{ id } .png"
157+ front_matter [ "logo" ] = "images/addons/#{ id } .png"
158158 end
159159
160160 # Find install type from features list
@@ -165,20 +165,25 @@ def self.process_addon_type(features, dest_addons_dir, images_dir, src_bundles_d
165165 end
166166
167167 install_attrs = feature_entry ? feature_entry [ 1 ] : { "install" => "manual" }
168- front . merge! ( install_attrs )
169-
170- # Build frontmatter block
171- frontmatter_str = "---\n " + front . map { |k , v | "#{ k } : #{ v } " } . join ( "\n " ) + "\n ---\n \n "
168+ front_matter . merge! ( install_attrs )
169+ # Existing front matter from the readme takes precedence if it exists
170+ front_matter . merge! ( existing_front_matter ) if existing_front_matter . is_a? ( Hash )
172171
173172 # Re-format readme content: remove first heading and replace with custom template
174173 first_h1_match = readme_text . match ( /^# .*/ )
175174 heading = first_h1_match ? first_h1_match [ 0 ] : "# #{ label } "
176175 text_without_heading = first_h1_match ? readme_text . sub ( heading , "" ) : readme_text
177176
178177 addon_logo_tag = ( logo_svg || logo_png ) ? "\n \n <AddonLogo />" : ""
178+ # to_yaml adds "---\n" at the start, we want to remove that and add our own "---" at the start and end
179+ front_matter_str = front_matter . to_yaml ( line_width : -1 ) . sub ( /\A ---\s *\n / , "" ) . strip
179180
180181 final_content = <<~MARKDOWN
181- #{ frontmatter_str } <!-- Attention authors: Do not edit directly. Please add your changes to the appropriate source repository -->
182+ ---
183+ #{ front_matter_str }
184+ ---
185+
186+ <!-- Attention authors: Do not edit directly. Please add your changes to the appropriate source repository -->
182187
183188 #{ heading } #{ addon_logo_tag } #{ text_without_heading }
184189 MARKDOWN
@@ -192,6 +197,23 @@ def self.process_addon_type(features, dest_addons_dir, images_dir, src_bundles_d
192197 puts " ✔ Processed add-on type: #{ type } -> #{ dest_folder_name } "
193198 end
194199
200+ def self . split_front_matter ( content )
201+ match = content . match ( /\A ---\s *\n (?<front_matter>.*?)^---\s *$\n ?(?<content>.*)/m )
202+
203+ if match
204+ front_matter_str = match [ :front_matter ]
205+ begin
206+ front_matter = YAML . safe_load ( front_matter_str ) || { }
207+ rescue Psych ::Exception => e
208+ warn " ⚠️ Failed to parse front matter YAML: #{ e . message } "
209+ front_matter = { }
210+ end
211+ [ front_matter , match [ :content ] ]
212+ else
213+ [ nil , content ]
214+ end
215+ end
216+
195217 def self . process_all ( distro_features_path , snapshot_features_path , addons_bundles_dir , webui_bundles_dir , dest_addons_dir , images_dir )
196218 features = collect_features ( distro_features_path , snapshot_features_path )
197219
0 commit comments