puppet-forge-server 1.8.0 → 1.9.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 +4 -4
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/lib/puppet_forge_server.rb +4 -0
- data/lib/puppet_forge_server/api/v1/modules.rb +7 -3
- data/lib/puppet_forge_server/api/v3/modules.rb +7 -2
- data/lib/puppet_forge_server/api/v3/releases.rb +9 -4
- data/lib/puppet_forge_server/app/frontend.rb +19 -0
- data/lib/puppet_forge_server/app/public/css/puppetlabs.css +236 -0
- data/lib/puppet_forge_server/app/views/layout.haml +1 -1
- data/lib/puppet_forge_server/app/views/module.haml +71 -0
- data/lib/puppet_forge_server/app/views/modules.haml +1 -1
- data/lib/puppet_forge_server/backends/directory.rb +12 -5
- data/lib/puppet_forge_server/backends/proxy.rb +29 -4
- data/lib/puppet_forge_server/backends/proxy_v3.rb +20 -6
- data/lib/puppet_forge_server/http/http_client.rb +29 -2
- data/lib/puppet_forge_server/logger.rb +9 -2
- data/lib/puppet_forge_server/models/builder.rb +6 -1
- data/lib/puppet_forge_server/models/metadata.rb +1 -1
- data/lib/puppet_forge_server/models/module.rb +2 -2
- data/lib/puppet_forge_server/server.rb +2 -0
- data/lib/puppet_forge_server/utils/archiver.rb +3 -1
- data/lib/puppet_forge_server/utils/cache_provider.rb +42 -0
- data/lib/puppet_forge_server/utils/encoding.rb +46 -0
- data/lib/puppet_forge_server/utils/filtering_inspecter.rb +30 -0
- data/lib/puppet_forge_server/utils/md_renderer.rb +54 -0
- data/lib/puppet_forge_server/utils/option_parser.rb +23 -1
- data/lib/puppet_forge_server/version.rb +1 -1
- data/puppet-forge-server.gemspec +8 -0
- data/spec/spec_helper.rb +41 -1
- data/spec/unit/http/http_client_spec.rb +93 -0
- data/spec/unit/utils/encoding_spec.rb +35 -0
- metadata +110 -2
@@ -20,7 +20,7 @@
|
|
20
20
|
- modules.each do |element|
|
21
21
|
%li{:class => element['private'] ? 'clearfix private' : 'clearfix'}
|
22
22
|
.col
|
23
|
-
%h3= "#{element['owner']['username']}/#{element['name']}"
|
23
|
+
%a{:class => "h3", :href => "/module?name=#{element['current_release']['metadata']['name']}" }= "#{element['owner']['username']}/#{element['name']}"
|
24
24
|
%p= element['current_release']['metadata']['summary']
|
25
25
|
%span.release-info= "Version #{element['current_release']['metadata']['version']}"
|
26
26
|
- if element['current_release']['metadata']['issues_url']
|
@@ -56,9 +56,15 @@ module PuppetForgeServer::Backends
|
|
56
56
|
end
|
57
57
|
|
58
58
|
private
|
59
|
-
def
|
60
|
-
|
61
|
-
JSON.parse(
|
59
|
+
def read_module_data(archive_path)
|
60
|
+
file_contents = read_from_archive(archive_path, %r[^([^/]+/)?(metadata\.json|README\.md)$])
|
61
|
+
metadata = JSON.parse(file_contents.find {|key, value| key =~ /metadata\.json/}[1])
|
62
|
+
begin
|
63
|
+
readme = file_contents.find {|key, value| key =~ /README\.md/}[1]
|
64
|
+
rescue
|
65
|
+
readme = nil
|
66
|
+
end
|
67
|
+
[metadata, readme]
|
62
68
|
rescue => error
|
63
69
|
warn "Error reading from module archive #{archive_path}: #{error}"
|
64
70
|
return nil
|
@@ -80,14 +86,15 @@ module PuppetForgeServer::Backends
|
|
80
86
|
options = ({:with_checksum => true}).merge(options)
|
81
87
|
modules = []
|
82
88
|
Dir["#{@module_dir}/**/#{file_name}"].each do |path|
|
83
|
-
metadata_raw =
|
89
|
+
metadata_raw, readme = read_module_data(path)
|
84
90
|
if metadata_raw
|
85
91
|
modules <<
|
86
92
|
PuppetForgeServer::Models::Module.new({
|
87
93
|
:metadata => parse_dependencies(PuppetForgeServer::Models::Metadata.new(normalize_metadata(metadata_raw))),
|
88
94
|
:checksum => options[:with_checksum] == true ? Digest::MD5.file(path).hexdigest : nil,
|
89
95
|
:path => "/#{File.basename(path)}",
|
90
|
-
:private => ! @readonly
|
96
|
+
:private => ! @readonly,
|
97
|
+
:readme => readme
|
91
98
|
})
|
92
99
|
else
|
93
100
|
@log.error "Failed reading metadata from #{path}"
|
@@ -35,14 +35,18 @@ module PuppetForgeServer::Backends
|
|
35
35
|
|
36
36
|
def get_file_buffer(relative_path)
|
37
37
|
file_name = relative_path.split('/').last
|
38
|
-
File.join(@cache_dir, file_name[0].downcase, file_name)
|
38
|
+
target_file = File.join(@cache_dir, file_name[0].downcase, file_name)
|
39
39
|
path = Dir["#{@cache_dir}/**/#{file_name}"].first
|
40
40
|
unless File.exist?("#{path}")
|
41
41
|
buffer = download("#{@file_path.chomp('/')}/#{relative_path}")
|
42
|
-
File.open(
|
43
|
-
|
42
|
+
File.open(target_file, 'wb') do |file|
|
43
|
+
bytes = buffer.read
|
44
|
+
file.write(bytes)
|
45
|
+
@log.debug("Saved #{bytes.size} bytes in filesystem cache for path: #{relative_path}, target file: #{target_file}")
|
44
46
|
end
|
45
|
-
path =
|
47
|
+
path = target_file
|
48
|
+
else
|
49
|
+
@log.info("Filesystem cache HIT for path: #{relative_path}")
|
46
50
|
end
|
47
51
|
File.open(path, 'rb')
|
48
52
|
rescue => e
|
@@ -59,6 +63,27 @@ module PuppetForgeServer::Backends
|
|
59
63
|
protected
|
60
64
|
attr_reader :log
|
61
65
|
|
66
|
+
def get_non_mutable(relative_url)
|
67
|
+
file_name = relative_url.split('/').last
|
68
|
+
target_file = File.join(@cache_dir, file_name[0].downcase, file_name)
|
69
|
+
path = Dir["#{@cache_dir}/**/#{file_name}"].first
|
70
|
+
unless File.exist?("#{path}")
|
71
|
+
buffer = get(relative_url)
|
72
|
+
File.open(target_file, 'wb') do |file|
|
73
|
+
file.write(buffer)
|
74
|
+
@log.debug("Saved #{buffer} bytes in filesystem cache for url: #{relative_url}, target file: #{target_file}")
|
75
|
+
end
|
76
|
+
path = target_file
|
77
|
+
else
|
78
|
+
@log.info("Filesystem cache HIT for url: #{relative_url}")
|
79
|
+
end
|
80
|
+
File.binread(path)
|
81
|
+
rescue => e
|
82
|
+
@log.error("#{self.class.name} failed getting non-mutable url '#{relative_url}'")
|
83
|
+
@log.error("Error: #{e}")
|
84
|
+
return nil
|
85
|
+
end
|
86
|
+
|
62
87
|
def get(relative_url)
|
63
88
|
@http_client.get(url(relative_url))
|
64
89
|
end
|
@@ -30,11 +30,11 @@ module PuppetForgeServer::Backends
|
|
30
30
|
def get_metadata(author, name, options = {})
|
31
31
|
query ="#{author}-#{name}"
|
32
32
|
begin
|
33
|
-
releases =
|
33
|
+
releases = get_releases(query, options)
|
34
34
|
get_modules(releases)
|
35
35
|
rescue => e
|
36
|
-
@log.
|
37
|
-
@log.
|
36
|
+
@log.error("#{self.class.name} failed querying metadata for '#{query}' with options #{options}")
|
37
|
+
@log.error("Error: #{e}")
|
38
38
|
return nil
|
39
39
|
end
|
40
40
|
end
|
@@ -44,13 +44,26 @@ module PuppetForgeServer::Backends
|
|
44
44
|
releases = get_all_result_pages("/v3/modules?query=#{query}").map {|element| element['current_release']}
|
45
45
|
get_modules(releases)
|
46
46
|
rescue => e
|
47
|
-
@log.
|
48
|
-
@log.
|
47
|
+
@log.error("#{self.class.name} failed querying metadata for '#{query}' with options #{options}")
|
48
|
+
@log.error("Error: #{e}")
|
49
49
|
return nil
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
53
53
|
private
|
54
|
+
|
55
|
+
def get_releases(query, options = {})
|
56
|
+
version = options[:version]
|
57
|
+
unless version.nil?
|
58
|
+
url = "/v3/releases/#{query}-#{version}"
|
59
|
+
buffer = get_non_mutable(url)
|
60
|
+
release = JSON.parse(buffer)
|
61
|
+
[ release ]
|
62
|
+
else
|
63
|
+
get_all_result_pages("/v3/releases?module=#{query}")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
54
67
|
def get_all_result_pages(next_page)
|
55
68
|
results = []
|
56
69
|
begin
|
@@ -80,7 +93,8 @@ module PuppetForgeServer::Backends
|
|
80
93
|
:checksum => element['file_md5'],
|
81
94
|
:path => element['file_uri'].gsub(/^#{@@FILE_PATH}/, ''),
|
82
95
|
:tags => (element['tags'] + (element['metadata']['tags'] ? element['metadata']['tags'] : [])).flatten.uniq,
|
83
|
-
:deleted_at => element['deleted_at']
|
96
|
+
:deleted_at => element['deleted_at'],
|
97
|
+
:readme => element['readme']
|
84
98
|
})
|
85
99
|
end
|
86
100
|
end
|
@@ -23,6 +23,15 @@ require 'net/http/post/multipart'
|
|
23
23
|
|
24
24
|
module PuppetForgeServer::Http
|
25
25
|
class HttpClient
|
26
|
+
include PuppetForgeServer::Utils::CacheProvider
|
27
|
+
include PuppetForgeServer::Utils::FilteringInspecter
|
28
|
+
|
29
|
+
def initialize(cache = nil)
|
30
|
+
cache = cache_instance if cache.nil?
|
31
|
+
cache.extend(PuppetForgeServer::Utils::FilteringInspecter)
|
32
|
+
@log = PuppetForgeServer::Logger.get
|
33
|
+
@cache = cache
|
34
|
+
end
|
26
35
|
|
27
36
|
def post_file(url, file_hash, options = {})
|
28
37
|
options = { :http => {}, :headers => {}}.merge(options)
|
@@ -45,11 +54,29 @@ module PuppetForgeServer::Http
|
|
45
54
|
open_uri(url)
|
46
55
|
end
|
47
56
|
|
57
|
+
def inspect
|
58
|
+
cache_inspected = @cache.inspect_without [ :@data ]
|
59
|
+
cache_inspected.gsub!(/>$/, ", @size=#{@cache.size}>")
|
60
|
+
inspected = inspect_without [ :@cache ]
|
61
|
+
inspected.gsub(/>$/, ", @cache=#{cache_inspected}>")
|
62
|
+
end
|
63
|
+
|
48
64
|
private
|
65
|
+
|
49
66
|
def open_uri(url)
|
50
|
-
|
51
|
-
|
67
|
+
hit_or_miss = @cache.include?(url) ? 'HIT' : 'MISS'
|
68
|
+
@log.info "Cache in RAM memory size: #{@cache.size}, #{hit_or_miss} for url: #{url}"
|
69
|
+
contents = @cache.fetch(url) do
|
70
|
+
tmpfile = ::Timeout.timeout(10) do
|
71
|
+
PuppetForgeServer::Logger.get.debug "Fetching data for url: #{url} from remote server"
|
72
|
+
open(url, 'User-Agent' => "Puppet-Forge-Server/#{PuppetForgeServer::VERSION}", :allow_redirections => :safe)
|
73
|
+
end
|
74
|
+
contents = tmpfile.read
|
75
|
+
tmpfile.close
|
76
|
+
contents
|
52
77
|
end
|
78
|
+
@log.debug "Data for url: #{url} fetched, #{contents.size} bytes"
|
79
|
+
StringIO.new(contents)
|
53
80
|
end
|
54
81
|
end
|
55
82
|
end
|
@@ -16,6 +16,7 @@
|
|
16
16
|
|
17
17
|
|
18
18
|
require 'logger'
|
19
|
+
require 'logger/colors'
|
19
20
|
|
20
21
|
module PuppetForgeServer
|
21
22
|
class Logger
|
@@ -45,7 +46,13 @@ module PuppetForgeServer
|
|
45
46
|
else
|
46
47
|
method_name
|
47
48
|
end
|
48
|
-
|
49
|
+
if args.size > 0
|
50
|
+
# setters
|
51
|
+
@loggers.each { |logger| logger.send(method_name, args.first) }
|
52
|
+
else
|
53
|
+
# getters
|
54
|
+
@loggers.collect { |logger| logger.send(method_name) }
|
55
|
+
end
|
49
56
|
end
|
50
57
|
|
51
58
|
def respond_to?(method_name, include_private = false)
|
@@ -70,4 +77,4 @@ module PuppetForgeServer
|
|
70
77
|
end
|
71
78
|
end
|
72
79
|
end
|
73
|
-
end
|
80
|
+
end
|
@@ -27,6 +27,11 @@ module PuppetForgeServer::Models
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def to_hash(obj = self)
|
30
|
+
# Quickly return if it's not one of ours
|
31
|
+
unless obj.kind_of? Builder
|
32
|
+
return obj
|
33
|
+
end
|
34
|
+
# Otherwise start building hash
|
30
35
|
hash = {}
|
31
36
|
obj.instance_variables.each do |var|
|
32
37
|
var_value = obj.instance_variable_get(var)
|
@@ -41,4 +46,4 @@ module PuppetForgeServer::Models
|
|
41
46
|
hash
|
42
47
|
end
|
43
48
|
end
|
44
|
-
end
|
49
|
+
end
|
@@ -18,7 +18,7 @@
|
|
18
18
|
module PuppetForgeServer::Models
|
19
19
|
class Module < Builder
|
20
20
|
|
21
|
-
attr_accessor :metadata, :checksum, :path, :private, :deleted_at, :tags
|
21
|
+
attr_accessor :metadata, :checksum, :path, :private, :deleted_at, :tags, :readme
|
22
22
|
|
23
23
|
def initialize(attributes)
|
24
24
|
super(attributes)
|
@@ -26,4 +26,4 @@ module PuppetForgeServer::Models
|
|
26
26
|
end
|
27
27
|
|
28
28
|
end
|
29
|
-
end
|
29
|
+
end
|
@@ -19,6 +19,7 @@ require 'rack/mount'
|
|
19
19
|
module PuppetForgeServer
|
20
20
|
class Server
|
21
21
|
include PuppetForgeServer::Utils::OptionParser
|
22
|
+
include PuppetForgeServer::Utils::CacheProvider
|
22
23
|
include PuppetForgeServer::Utils::Http
|
23
24
|
|
24
25
|
def go(args)
|
@@ -27,6 +28,7 @@ module PuppetForgeServer
|
|
27
28
|
begin
|
28
29
|
options = parse_options(args)
|
29
30
|
@log = logging(options)
|
31
|
+
configure_cache(options[:ram_cache_ttl], options[:ram_cache_size])
|
30
32
|
backends = backends(options)
|
31
33
|
server = build(backends, options[:webui_root])
|
32
34
|
announce(options, backends)
|
@@ -22,9 +22,11 @@ module PuppetForgeServer::Utils
|
|
22
22
|
def read_from_archive(archive, name_regex)
|
23
23
|
tar = Gem::Package::TarReader.new(Zlib::GzipReader.open(archive))
|
24
24
|
tar.rewind
|
25
|
+
files = {}
|
25
26
|
tar.each do |obj|
|
26
|
-
|
27
|
+
files[obj.full_name] = obj.read if obj.file? && obj.full_name =~ name_regex
|
27
28
|
end
|
29
|
+
return files unless files.empty?
|
28
30
|
raise "Given name #{name_regex} not found in #{archive}"
|
29
31
|
end
|
30
32
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright 2015 Centralny Osroder Informatyki (gov.pl)
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
require 'lrucache'
|
18
|
+
|
19
|
+
module PuppetForgeServer::Utils
|
20
|
+
module CacheProvider
|
21
|
+
|
22
|
+
opts = PuppetForgeServer::Utils::OptionParser.DEFAULT_OPTIONS
|
23
|
+
@@CACHE = LRUCache.new(:ttl => opts[:ram_cache_ttl], :max_size => opts[:ram_cache_size])
|
24
|
+
|
25
|
+
# Method for fetching application wide cache for fetching HTTP requests
|
26
|
+
#
|
27
|
+
# @return [LRUCache] a instance of cache for application
|
28
|
+
def cache_instance
|
29
|
+
@@CACHE
|
30
|
+
end
|
31
|
+
|
32
|
+
# Configure a application wide cache using LSUCache implementation
|
33
|
+
#
|
34
|
+
# @param [int] ttl a time to live for elements
|
35
|
+
# @param [int] size a maximum size for cache
|
36
|
+
def configure_cache(ttl, size)
|
37
|
+
@@CACHE = LRUCache.new(:ttl => ttl, :max_size => size)
|
38
|
+
PuppetForgeServer::Logger.get.info("Using RAM memory LRUCache with time to live of #{ttl}sec and max size of #{size} elements")
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright 2015 Centralny Osroder Informatyki (gov.pl)
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
require 'iconv'
|
18
|
+
|
19
|
+
module PuppetForgeServer::Utils
|
20
|
+
module Encoding
|
21
|
+
|
22
|
+
@@ic = Iconv.new('UTF-8//IGNORE', 'UTF-8')
|
23
|
+
|
24
|
+
# Converts give text to valid UTF-8
|
25
|
+
# @param [string] text given string, can be null
|
26
|
+
# @return [string] output string in utf-8
|
27
|
+
def to_utf8(text)
|
28
|
+
replaced = text
|
29
|
+
unless replaced.nil?
|
30
|
+
replaced = replaced.force_encoding("UTF-8") if is_ascii_8bit?(replaced)
|
31
|
+
replaced = cleanup_utf8(replaced)
|
32
|
+
end
|
33
|
+
replaced
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def is_ascii_8bit?(text)
|
39
|
+
text.encoding.name == 'ASCII-8BIT'
|
40
|
+
end
|
41
|
+
|
42
|
+
def cleanup_utf8(text)
|
43
|
+
@@ic.iconv(text)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright 2015 Centralny Osroder Informatyki (gov.pl)
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
module PuppetForgeServer::Utils
|
18
|
+
module FilteringInspecter
|
19
|
+
def self.inspect_without(object, variables)
|
20
|
+
filtered = object.instance_variables.reject { |n| variables.include? n }
|
21
|
+
vars = filtered.map { |n| "#{n}=#{object.instance_variable_get(n).inspect}" }
|
22
|
+
oid = object.object_id << 1
|
23
|
+
"#<%s:0x%x %s>" % [ object.class, oid, vars.join(', ') ]
|
24
|
+
end
|
25
|
+
|
26
|
+
def inspect_without(variables)
|
27
|
+
PuppetForgeServer::Utils::FilteringInspecter.inspect_without(self, variables)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright 2015 North Development AB
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
require 'tilt/redcarpet'
|
18
|
+
|
19
|
+
module PuppetForgeServer::Utils
|
20
|
+
module MarkdownRenderer
|
21
|
+
|
22
|
+
class CustomRenderer < Redcarpet::Render::HTML
|
23
|
+
def block_code(code, lang)
|
24
|
+
output = "<pre>"
|
25
|
+
output << "<code class=\"prettyprint lang-puppet\">#{code}</code>"
|
26
|
+
output << "</pre>"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def markdown(text)
|
31
|
+
options = {
|
32
|
+
filter_html: true,
|
33
|
+
with_toc_data: true,
|
34
|
+
hard_wrap: true,
|
35
|
+
prettify: true
|
36
|
+
}
|
37
|
+
|
38
|
+
extensions = {
|
39
|
+
autolink: true,
|
40
|
+
superscript: true,
|
41
|
+
disable_indented_code_blocks: false,
|
42
|
+
fenced_code_blocks: true,
|
43
|
+
strikethrough: true,
|
44
|
+
quote: true,
|
45
|
+
tables: true
|
46
|
+
}
|
47
|
+
|
48
|
+
renderer = CustomRenderer.new(options)
|
49
|
+
markdown = Redcarpet::Markdown.new(renderer, extensions)
|
50
|
+
markdown.render(text)
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|