Class: JiraAdf

Inherits:
Object
  • Object
show all
Defined in:
lib/jira_adf.rb,
lib/jira_adf/version.rb

Overview

A simple ADF (Atlassian Document Format) builder.

Usage example:

result = JiraAdf {
  # Keyword args become "attrs".
  heading(level: 3) { text('An h3 heading') }

  ordered_list {
    list_item { paragraph { text('Paragraph in listItem') } }
    list_item {
      paragraph {
        text('Text with ')

        # Methods chained at the end of the node become "marks". Their
        # keyword args become "attrs" of the mark.
        text('bold and superscript').strong.subsup(type: 'sub')

        text(' styling in the middle.')
      }
    }

    # Use a regular lambda for snippet reuse. Keep in mind that due to how
    # closures work, if you call this lambda in sub-nested scopes, it will
    # still add an item in the scope where it was defined. And it will not
    # exist at higher / neighbor scopes.
    item = -> string { list_item { paragraph { text(string) } } }

    # Now you can use shorter syntax.
    item['Item 3']
    item['Item 4']
    item['Item 5']
  }
}

result.to_h # => Ruby Hash ready for converting to JSON.

Constant Summary collapse

VERSION =
'0.1.0'

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(node = { 'version' => 1, 'type' => 'doc' }, &block) ⇒ JiraAdf

Returns a new instance of JiraAdf.



64
65
66
67
68
69
70
71
72
73
74
# File 'lib/jira_adf.rb', line 64

def initialize(node = { 'version' => 1, 'type' => 'doc' }, &block)
  @node = self.class.format_hash(node)
  instance_eval(&block) if block_given?

  # It's important that this variable is not yet set while instance_eval on
  # the previous line is being executed. This is how method_missing can
  # distinguish whether a method was called on a node, or it was called in an
  # instance_eval block. We use this fact to switch to adding `mark` fields
  # instead of `content` fields.
  @block_evaled_or_not_given = true
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(type, *args, **kwargs, &block) ⇒ Object



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/jira_adf.rb', line 87

def method_missing(type, *args, **kwargs, &block)
  if @block_evaled_or_not_given
    hash = { 'type' => type }
    hash.merge! 'attrs' => kwargs if kwargs.any?
    @node['marks'] ||= []
    @node['marks'] << self.class.new(hash)
    self
  elsif type == :text
    self.class.new('type' => 'text', 'text' => args[0]).tap { |node|
      @node['content'] ||= []
      @node['content'] << node
    }
  else
    hash = { 'type' => type }
    hash.merge! 'attrs' => kwargs if kwargs.any?
    self.class.new(hash, &block).tap { |node|
      @node['content'] ||= []
      @node['content'] << node
    }
  end
end

Class Method Details

.camelize(term) ⇒ Object



58
59
60
61
# File 'lib/jira_adf.rb', line 58

def camelize(term)
  term.to_s.gsub(/(?:^|_+)([^_])/) { $1.upcase }
    .tap { |s| s[0] = s[0].downcase }
end

.format_hash(hash) ⇒ Object



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

def format_hash(hash)
  hash.map { |k, v|
    key = camelize(k)

    [ key,

      if    key == 'type'; camelize(v)
      elsif Hash === v;    format_hash(v)
      elsif Symbol === v;  v.to_s
      else;                v
      end
    ]
  }.to_h
end

Instance Method Details

#to_hObject



76
77
78
79
80
81
82
83
84
85
# File 'lib/jira_adf.rb', line 76

def to_h
  @node.transform_values { |v|
    case v
    when Array;      v.map { |e| self.class === e ? e.to_h : e }
    when Hash;       v.transform_values { |e| self.class === e ? e.to_h : e }
    when self.class; v.to_h
    else;            v
    end
  }
end