marlens-jira-api 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b0cf8ed2ffd471d5f4c71da18861f1b4da2bb13e91192e61d36d1905139527fa
4
- data.tar.gz: ab15b83bebe592a06b8d1f1636f6045cb2da32b39e2c4939f2207e3f7016c160
3
+ metadata.gz: '08ccc21d16f1e2593da0304416dabc4e08b0d60891b2fcf05f03ccc249244081'
4
+ data.tar.gz: 551652ac549e8034ec3633b9b42db59c6ddf3e44485b8b15b602286f8bf9c525
5
5
  SHA512:
6
- metadata.gz: b5c66a4d1d404a4a3703ae18f80a71f56061003eac7b1eea824b9a3581aa7d7c5ada4808a43abcc39d7a94843a07a41ed8115048e7c2f7e25b044ae0ba2aea56
7
- data.tar.gz: 44853c868153491b53f85549c6266a04a153162ddb0b998b0cfcd4381554de4917275a477fb3c9306aab660409c9d26e09285c0049b6d1b189cc6329da0ccc1c
6
+ metadata.gz: bb82c632c653f9d421849d2d217d3560173a04a4235f0322b243862cadb1658f1ea4dd1d9afca7ea191a7f013112c04f51f91f1e589d0e1ce19ad6abd4ec6ac3
7
+ data.tar.gz: 38def1ee536eba0d1fd8b4283f961737bc0c26ac0e10af78b6fb886372f574f29c0a28359c5dd697ff2f475d8ebddee27eb993a872b3e3ca3bc412d163f0b9a6
data/README.md CHANGED
@@ -39,6 +39,14 @@ client.update_markdown_comment(
39
39
  client.delete_comment(issue_key: "WRAPX-123", comment_id: created.fetch("id"))
40
40
  ```
41
41
 
42
+ ### ADF to Markdown
43
+
44
+ ```ruby
45
+ markdown = Marlens::JiraApi::AdfToMarkdown.call(issue.fetch("fields").fetch("description"))
46
+ ```
47
+
48
+ Supports ADF documents, paragraphs, headings, block quotes, ordered and bullet lists, list items, text, and strong, emphasis, code, link, and strike marks. Unsupported nodes preserve traversable `content`; nodes without traversable content render nothing.
49
+
42
50
  ### Read errors
43
51
 
44
52
  `get_issue` and `list_remote_links` return parsed Jira JSON. A non-2xx response raises `RuntimeError` with the HTTP method, request path, status, and response body. A successful response with malformed JSON raises `JSON::ParserError`.
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Marlens
4
+ module JiraApi
5
+ class AdfToMarkdown
6
+ BLOCK_TYPES = %w[doc paragraph heading blockquote orderedList bulletList listItem].freeze
7
+
8
+ def self.call(document)
9
+ new(document).to_markdown
10
+ end
11
+
12
+ def initialize(document)
13
+ @document = document.is_a?(Hash) ? document : {}
14
+ end
15
+
16
+ def to_markdown
17
+ render_block(@document).strip
18
+ end
19
+
20
+ private
21
+
22
+ def render_block(node)
23
+ case node["type"]
24
+ when "doc"
25
+ render_blocks(children(node)).join("\n\n")
26
+ when "paragraph"
27
+ render_inline_nodes(children(node))
28
+ when "heading"
29
+ "#{"#" * heading_level(node)} #{render_inline_nodes(children(node))}".rstrip
30
+ when "blockquote"
31
+ quote(render_blocks(children(node)).join("\n\n"))
32
+ when "orderedList", "bulletList"
33
+ render_list(node)
34
+ when "listItem"
35
+ render_list_item(node, marker: "-", indent: 0)
36
+ when "text", "hardBreak"
37
+ render_inline(node)
38
+ else
39
+ render_unknown(node)
40
+ end
41
+ end
42
+
43
+ def render_blocks(nodes)
44
+ nodes.filter_map do |node|
45
+ rendered = render_block(node)
46
+ rendered unless rendered.empty?
47
+ end
48
+ end
49
+
50
+ def render_unknown(node)
51
+ content = children(node)
52
+ return "" if content.empty?
53
+
54
+ if content.any? { |child| BLOCK_TYPES.include?(child["type"]) }
55
+ render_blocks(content).join("\n\n")
56
+ else
57
+ render_inline_nodes(content)
58
+ end
59
+ end
60
+
61
+ def render_list(node, indent: 0)
62
+ start = node.dig("attrs", "order").to_i
63
+ start = 1 if start.zero?
64
+
65
+ children(node).each_with_index.map do |item, index|
66
+ marker = node["type"] == "orderedList" ? "#{start + index}." : "-"
67
+ render_list_item(item, marker:, indent:)
68
+ end.join("\n")
69
+ end
70
+
71
+ def render_list_item(node, marker:, indent:)
72
+ item_children = children(node)
73
+ first_child = item_children.shift
74
+ indentation = " " * indent
75
+ prefix = "#{marker} "
76
+
77
+ if first_child&.fetch("type", nil) == "paragraph"
78
+ lines = ["#{indentation}#{prefix}#{render_inline_nodes(children(first_child))}".rstrip]
79
+ else
80
+ lines = ["#{indentation}#{marker}"]
81
+ item_children.unshift(first_child) unless first_child.nil?
82
+ end
83
+
84
+ continuation_indent = indent + prefix.length
85
+ item_children.each do |child|
86
+ rendered = if ["orderedList", "bulletList"].include?(child["type"])
87
+ render_list(child, indent: continuation_indent)
88
+ else
89
+ indent_lines(render_block(child), continuation_indent)
90
+ end
91
+ lines << rendered unless rendered.empty?
92
+ end
93
+
94
+ lines.join("\n")
95
+ end
96
+
97
+ def render_inline_nodes(nodes)
98
+ nodes.map { |node| render_inline(node) }.join
99
+ end
100
+
101
+ def render_inline(node)
102
+ case node["type"]
103
+ when "text"
104
+ apply_marks(node.fetch("text", ""), node["marks"])
105
+ when "hardBreak"
106
+ "\n"
107
+ else
108
+ render_inline_nodes(children(node))
109
+ end
110
+ end
111
+
112
+ def apply_marks(text, marks)
113
+ Array(marks).reduce(text) do |markdown, mark|
114
+ case mark["type"]
115
+ when "strong", "bold"
116
+ "**#{markdown}**"
117
+ when "em", "italic"
118
+ "*#{markdown}*"
119
+ when "code"
120
+ "`#{markdown}`"
121
+ when "link"
122
+ href = mark.dig("attrs", "href")
123
+ href.to_s.empty? ? markdown : "[#{markdown}](#{href})"
124
+ when "strike"
125
+ "~~#{markdown}~~"
126
+ else
127
+ markdown
128
+ end
129
+ end
130
+ end
131
+
132
+ def heading_level(node)
133
+ level = node.dig("attrs", "level").to_i
134
+ level.zero? ? 1 : level
135
+ end
136
+
137
+ def quote(content)
138
+ content.split("\n", -1).map { |line| line.empty? ? ">" : "> #{line}" }.join("\n")
139
+ end
140
+
141
+ def indent_lines(text, indent)
142
+ return "" if text.empty?
143
+
144
+ text.lines(chomp: true).map { |line| "#{" " * indent}#{line}" }.join("\n")
145
+ end
146
+
147
+ def children(node)
148
+ Array(node["content"]).select { |child| child.is_a?(Hash) }
149
+ end
150
+ end
151
+ end
152
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Marlens
4
4
  module JiraApi
5
- VERSION = "0.6.0"
5
+ VERSION = "0.7.0"
6
6
  end
7
7
  end
@@ -3,6 +3,7 @@
3
3
  require_relative "jira_api/version"
4
4
  require_relative "jira_api/client"
5
5
  require_relative "jira_api/markdown_to_adf"
6
+ require_relative "jira_api/adf_to_markdown"
6
7
  require_relative "jira_api/remote_image_attachment_uploader"
7
8
  require_relative "jira_api/cli"
8
9
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: marlens-jira-api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marlen Brunner
@@ -92,6 +92,7 @@ files:
92
92
  - README.md
93
93
  - bin/jira-comment
94
94
  - lib/marlens/jira_api.rb
95
+ - lib/marlens/jira_api/adf_to_markdown.rb
95
96
  - lib/marlens/jira_api/cli.rb
96
97
  - lib/marlens/jira_api/client.rb
97
98
  - lib/marlens/jira_api/markdown_to_adf.rb