Module: PlanMyStuff::MetadataParser

Defined in:
lib/plan_my_stuff/metadata_parser.rb

Constant Summary collapse

LEGACY_DEPRECATION_MESSAGE =
'PlanMyStuff: legacy <!-- pms-metadata: ... --> body format detected. It will continue to parse ' \
'until 1.0.0, at which point legacy detection will be removed. New writes already use the visible ' \
'<details> block format introduced in 0.17.0; existing bodies migrate on their next write.'
METADATA_PATTERN =

New format: collapsible <details> block containing a JSON code fence. Renders visibly on GitHub (issue #58) instead of being hidden in an HTML comment.

%r{
  \A<details><summary>pms-metadata</summary>\n\n
  ```json\n(.*?)\n```\n\n
  </details>\n*
}mx
ATTACHMENTS_PATTERN =

Visible attachments block emitted after the metadata block when the metadata carries non-empty attachments (issue #70). Parse strips it so round-trips stay clean.

%r{
  \A<details><summary>attachments\ \(\d+\)</summary>\n\n
  .*?\n\n
  </details>\n*
}mx
LEGACY_METADATA_PATTERN =

Legacy format kept for parsing only - existing issues serialized before 0.17.0 used a hidden HTML comment. They migrate to the new format on the next write.

/\A<!-- pms-metadata:(.*?) -->\n*/m

Class Method Summary collapse

Class Method Details

.attachment_line(attachment) ⇒ String

Parameters:

  • attachment (Hash{Symbol=>String})

Returns:

  • (String)


99
100
101
102
103
104
# File 'lib/plan_my_stuff/metadata_parser.rb', line 99

def attachment_line(attachment)
  url = "https://github.com/#{attachment[:owner]}/#{attachment[:repo]}" \
    "/blob/#{attachment[:sha]}/#{attachment[:path]}"
  safe_filename = attachment[:filename].to_s.gsub(']', '\]')
  "- [#{safe_filename}](#{url})"
end

.attachments_block(metadata) ⇒ String

Renders the visible attachments block when metadata carries non-empty :attachments, otherwise returns an empty string.

Parameters:

  • metadata (Hash)

Returns:

  • (String)


87
88
89
90
91
92
93
# File 'lib/plan_my_stuff/metadata_parser.rb', line 87

def attachments_block()
  attachments = [:attachments]
  return '' if attachments.blank?

  lines = attachments.map { |a| attachment_line(a) }.join("\n")
  "<details><summary>attachments (#{attachments.size})</summary>\n\n#{lines}\n\n</details>\n\n"
end

.matching_pattern(raw_body) ⇒ Regexp?

Returns the pattern that matches at the start of raw_body, or nil when neither matches.

Parameters:

  • raw_body (String)

Returns:

  • (Regexp, nil)

    the pattern that matches at the start of raw_body, or nil when neither matches



110
111
112
113
# File 'lib/plan_my_stuff/metadata_parser.rb', line 110

def matching_pattern(raw_body)
  return METADATA_PATTERN if raw_body.match?(METADATA_PATTERN)
  return LEGACY_METADATA_PATTERN if raw_body.match?(LEGACY_METADATA_PATTERN)
end

.parse(raw_body) ⇒ Hash{Symbol => Hash, String}

Extracts metadata JSON from the raw body

Parameters:

  • raw_body (String, nil)

Returns:

  • (Hash{Symbol => Hash, String})

    :metadata (Hash, empty when absent) and :body (String)



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/plan_my_stuff/metadata_parser.rb', line 42

def parse(raw_body)
  return { metadata: {}, body: '' } if raw_body.blank?

  pattern = matching_pattern(raw_body)
  return { metadata: {}, body: raw_body } if pattern.nil?

  PlanMyStuff.deprecator.warn(LEGACY_DEPRECATION_MESSAGE) if pattern == LEGACY_METADATA_PATTERN

  match = raw_body.match(pattern)
   = JSON.parse(match[1], symbolize_names: true)
  body = raw_body.sub(pattern, '').sub(ATTACHMENTS_PATTERN, '')

  { metadata: , body: body }
rescue JSON::ParserError
  { metadata: {}, body: raw_body }
end

.serialize!(metadata, body) ⇒ String

Serializes a metadata hash and body into the stored format

Parameters:

Returns:

  • (String)

Raises:

  • (ArgumentError)

    if metadata is not a Hash or PlanMyStuff::CustomFields



68
69
70
71
72
73
74
75
76
77
78
# File 'lib/plan_my_stuff/metadata_parser.rb', line 68

def serialize!(, body)
  if !.is_a?(Hash) && !.is_a?(PlanMyStuff::CustomFields)
    raise(ArgumentError, "metadata must be a Hash or PlanMyStuff::CustomFields, got #{.class}")
  end

  hash = .is_a?(PlanMyStuff::CustomFields) ? .to_h : 
  json = JSON.pretty_generate(hash)

  "<details><summary>pms-metadata</summary>\n\n```json\n#{json}\n```\n\n</details>\n\n" \
    "#{attachments_block(hash)}#{body}"
end