Class: Markdowndocs::Documentation

Inherits:
Object
  • Object
show all
Defined in:
app/models/markdowndocs/documentation.rb

Overview

Documentation PORO (Plain Old Ruby Object) Represents markdown documentation files from a configurable directory. Handles metadata extraction, frontmatter parsing, and category associations.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(file_path) ⇒ Documentation

Returns a new instance of Documentation.



10
11
12
13
14
15
# File 'app/models/markdowndocs/documentation.rb', line 10

def initialize(file_path)
  @file_path = file_path
  @slug = derive_slug
  
  @category = assign_category
end

Instance Attribute Details

#categoryObject (readonly)

Returns the value of attribute category.



8
9
10
# File 'app/models/markdowndocs/documentation.rb', line 8

def category
  @category
end

#descriptionObject (readonly)

Returns the value of attribute description.



8
9
10
# File 'app/models/markdowndocs/documentation.rb', line 8

def description
  @description
end

#file_pathObject (readonly)

Returns the value of attribute file_path.



8
9
10
# File 'app/models/markdowndocs/documentation.rb', line 8

def file_path
  @file_path
end

#keywordsObject (readonly)

Returns the value of attribute keywords.



8
9
10
# File 'app/models/markdowndocs/documentation.rb', line 8

def keywords
  @keywords
end

#slugObject (readonly)

Returns the value of attribute slug.



8
9
10
# File 'app/models/markdowndocs/documentation.rb', line 8

def slug
  @slug
end

#titleObject (readonly)

Returns the value of attribute title.



8
9
10
# File 'app/models/markdowndocs/documentation.rb', line 8

def title
  @title
end

Class Method Details

.allObject



17
18
19
20
21
22
23
24
# File 'app/models/markdowndocs/documentation.rb', line 17

def self.all
  docs_path = Markdowndocs.config.resolved_docs_path
  return [] unless docs_path.exist?

  Dir.glob(docs_path.join("*.md")).map do |file|
    new(Pathname.new(file))
  end.sort_by(&:slug)
end

.by_category(category) ⇒ Object



46
47
48
# File 'app/models/markdowndocs/documentation.rb', line 46

def self.by_category(category)
  all.select { |doc| doc.category == category }
end

.find_by_slug(slug, mode: nil) ⇒ Object

When ‘mode:` is given (e.g. “guide” / “technical”), returns nil if the resolved doc’s ‘audience:` frontmatter excludes that mode. Docs without an explicit `audience:` key default to “visible in all modes” — backward compatible with pre-0.6 docs.



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'app/models/markdowndocs/documentation.rb', line 30

def self.find_by_slug(slug, mode: nil)
  return nil if slug.blank?
  return nil if slug.include?("..") || slug.include?("/")

  file_path = Markdowndocs.config.resolved_docs_path.join("#{slug}.md")
  return nil unless file_path.exist?

  doc = new(file_path)
  return nil unless doc.visible_to?(mode)

  doc
rescue => e
  Rails.logger.error("Error finding documentation by slug '#{slug}': #{e.message}")
  nil
end

.grouped_by_category(mode: nil) ⇒ Object

When ‘mode:` is given, filters out docs whose `audience:` excludes that mode AND drops categories that end up empty (so the index sidebar doesn’t render headers with no children).



53
54
55
56
57
58
# File 'app/models/markdowndocs/documentation.rb', line 53

def self.grouped_by_category(mode: nil)
  Markdowndocs.config.categories.each_with_object({}) do |(category, slugs), hash|
    docs = slugs.map { |slug| find_by_slug(slug, mode: mode) }.compact
    hash[category] = docs unless docs.empty?
  end
end

Instance Method Details

#audienceObject

The audience(s) this doc is written for, declared via ‘audience:` frontmatter. Accepts a single string or an array; both are coerced to an Array<String>. When the frontmatter key is missing, defaults to all configured modes — a doc with no audience declaration is visible in every mode (backward compat with pre-0.6 docs).



102
103
104
105
106
107
108
109
110
111
112
113
# File 'app/models/markdowndocs/documentation.rb', line 102

def audience
  @audience ||= begin
    parsed = parse_frontmatter
    raw = parsed[:frontmatter]["audience"]
    case raw
    when Array  then raw.map(&:to_s)
    when String then [ raw ]
    when nil    then Markdowndocs.config.modes.dup
    else             Markdowndocs.config.modes.dup
    end
  end
end

#available_modesObject



77
78
79
80
81
82
83
# File 'app/models/markdowndocs/documentation.rb', line 77

def available_modes
  @available_modes ||= begin
    parsed = parse_frontmatter
    modes = parsed[:frontmatter]["modes"]
    modes.is_a?(Array) ? modes.map(&:to_s) : Markdowndocs.config.modes.dup
  end
end

#cache_keyObject



67
68
69
# File 'app/models/markdowndocs/documentation.rb', line 67

def cache_key
  "#{slug}-#{mtime.to_i}"
end

#code_contentObject

Returns text extracted from fenced code blocks for search indexing.



139
140
141
142
143
# File 'app/models/markdowndocs/documentation.rb', line 139

def code_content
  parsed = parse_frontmatter
  blocks = parsed[:markdown].scan(/```\w*\n([\s\S]*?)```/)
  blocks.flatten.join(" ").gsub(/\s+/, " ").strip
end

#contentObject



60
61
62
63
64
65
# File 'app/models/markdowndocs/documentation.rb', line 60

def content
  @content ||= file_path.read
rescue => e
  Rails.logger.error("Error reading documentation file '#{file_path}': #{e.message}")
  ""
end

#default_modeObject



85
86
87
88
89
90
91
# File 'app/models/markdowndocs/documentation.rb', line 85

def default_mode
  @default_mode ||= begin
    parsed = parse_frontmatter
    mode = parsed[:frontmatter]["default_mode"]
    mode.present? ? mode.to_s : Markdowndocs.config.default_mode
  end
end

#mtimeObject



71
72
73
74
75
# File 'app/models/markdowndocs/documentation.rb', line 71

def mtime
  @mtime ||= file_path.mtime
rescue
  Time.current
end

#plain_text_contentObject

Returns content stripped of frontmatter, markdown syntax, and HTML tags for use in search indexing.



125
126
127
128
129
130
131
132
133
134
135
136
# File 'app/models/markdowndocs/documentation.rb', line 125

def plain_text_content
  parsed = parse_frontmatter
  text = parsed[:markdown]
  text = text.gsub(/^#+\s*/, "")          # headings
  text = text.gsub(/\[([^\]]+)\]\([^)]+\)/, '\1') # links
  text = text.gsub(/[*_~`]/, "")          # emphasis markers
  text = text.gsub(/```[\s\S]*?```/, "")  # fenced code blocks
  text = text.gsub(/<[^>]+>/, "")         # HTML tags
  text = text.gsub(/^\s*[-*+]\s/, "")     # list markers
  text = text.gsub(/\n{2,}/, "\n")        # collapse blank lines
  text.strip
end

#supports_mode?(mode) ⇒ Boolean

Returns:

  • (Boolean)


93
94
95
# File 'app/models/markdowndocs/documentation.rb', line 93

def supports_mode?(mode)
  available_modes.include?(mode.to_s)
end

#visible_to?(mode) ⇒ Boolean

Whether this doc should be surfaced to a viewer in the given mode. ‘nil` mode is treated as “no filter” — useful for callers that don’t care about audience (search indexer, admin tools).

Returns:

  • (Boolean)


118
119
120
121
# File 'app/models/markdowndocs/documentation.rb', line 118

def visible_to?(mode)
  return true if mode.nil?
  audience.include?(mode.to_s)
end