summaryrefslogtreecommitdiff
path: root/logic/cartridge/parser.rb
blob: 50b3dc5c69d177645363c98d5ae0167908cee00a (about) (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# frozen_string_literal: true

require 'singleton'

require 'redcarpet'
require 'redcarpet/render_strip'

module NanoBot
  module Logic
    module Cartridge
      module Parser
        def self.parse(raw, format:)
          normalized = format.to_s.downcase.gsub('.', '').strip

          if %w[yml yaml].include?(normalized)
            yaml(raw)
          elsif %w[markdown mdown mkdn md].include?(normalized)
            markdown(raw)
          else
            raise "Unknown cartridge format: '#{format}'"
          end
        end

        def self.markdown(raw)
          yaml_source = []

          tools = []

          blocks = Markdown.new.render(raw).blocks

          previous_block_is_tool = false

          blocks.each do |block|
            if block[:language] == 'yaml'
              parsed = Logic::Helpers::Hash.symbolize_keys(
                YAML.safe_load(block[:source], permitted_classes: [Symbol])
              )

              if parsed.key?(:tools) && parsed[:tools].is_a?(Array) && !parsed[:tools].empty?
                previous_block_is_tool = true

                tools.concat(parsed[:tools])

                parsed.delete(:tools)

                unless parsed.empty?
                  yaml_source << YAML.dump(
                    Logic::Helpers::Hash.stringify_keys(parsed)
                  ).gsub(/^---/, '') # TODO: Is this safe enough?
                end
              else
                yaml_source << block[:source]
                previous_block_is_tool = false
                nil
              end
            elsif previous_block_is_tool
              tools.last[block[:language].to_sym] = block[:source]
              previous_block_is_tool = false
            end
          end

          unless tools.empty?
            yaml_source << YAML.dump(
              Logic::Helpers::Hash.stringify_keys({ tools: })
            ).gsub(/^---/, '') # TODO: Is this safe enough?
          end

          cartridge = {}

          yaml_source.each do |source|
            cartridge = Logic::Helpers::Hash.deep_merge(cartridge, yaml(source))
          end

          cartridge
        end

        def self.yaml(raw)
          Logic::Helpers::Hash.symbolize_keys(
            YAML.safe_load(raw, permitted_classes: [Symbol])
          )
        end

        class Renderer < Redcarpet::Render::Base
          LANGUAGES_MAP = {
            'yml' => 'yaml',
            'yaml' => 'yaml',
            'lua' => 'lua',
            'fnl' => 'fennel',
            'fennel' => 'fennel',
            'clj' => 'clojure',
            'clojure' => 'clojure'
          }.freeze

          LANGUAGES = LANGUAGES_MAP.keys.freeze

          def initialize(...)
            super(...)
            @_nano_bots_blocks = []
          end

          attr_reader :_nano_bots_blocks

          def block_code(code, language)
            key = language.to_s.downcase.strip

            return nil unless LANGUAGES.include?(key)

            @_nano_bots_blocks << { language: LANGUAGES_MAP[key], source: code }

            nil
          end
        end

        class Markdown
          attr_reader :markdown

          def initialize
            @renderer = Renderer.new
            @markdown = Redcarpet::Markdown.new(@renderer, fenced_code_blocks: true)
          end

          def blocks
            @renderer._nano_bots_blocks
          end

          def render(raw)
            @markdown.render(raw.gsub(/```\w/, "\n\n\\0"))
            self
          end
        end
      end
    end
  end
end