Skip to content

Commit

Permalink
Merge pull request #1427 from okkez/plugin-generator
Browse files Browse the repository at this point in the history
Add fluent-plugin-generate command
  • Loading branch information
repeatedly authored Feb 16, 2017
2 parents 9482bb4 + ecf5fa5 commit f1e8fa4
Show file tree
Hide file tree
Showing 36 changed files with 1,391 additions and 95 deletions.
5 changes: 5 additions & 0 deletions bin/fluent-plugin-config-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env ruby
$LOAD_PATH.unshift(File.join(__dir__, 'lib'))
require 'fluent/command/plugin_config_formatter'

FluentPluginConfigFormatter.new.call
5 changes: 5 additions & 0 deletions bin/fluent-plugin-generate
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env ruby
$LOAD_PATH << File.expand_path(File.join(__dir__, '..', 'lib'))
require 'fluent/command/plugin_generator'

FluentPluginGenerator.new.call
258 changes: 258 additions & 0 deletions lib/fluent/command/plugin_config_formatter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
#
# Fluentd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

require "erb"
require "optparse"
require "pathname"
require "fluent/plugin"
require "fluent/env"
require "fluent/engine"
require "fluent/system_config"
require "fluent/config/element"

class FluentPluginConfigFormatter

AVAILABLE_FORMATS = [:markdown, :txt, :json]
SUPPORTED_TYPES = [
"input", "output", "filter",
"buffer", "parser", "formatter", "storage"
]

def initialize(argv = ARGV)
@argv = argv

@compact = false
@format = :markdown
@verbose = false
@libs = []
@plugin_dirs = []
@options = {}

prepare_option_parser
end

def call
parse_options!
init_libraries
@plugin = Fluent::Plugin.__send__("new_#{@plugin_type}", @plugin_name)
dumped_config = {}
if @plugin.class.respond_to?(:plugin_helpers)
dumped_config[:plugin_helpers] = @plugin.class.plugin_helpers
end
@plugin.class.ancestors.reverse_each do |plugin_class|
next unless plugin_class.respond_to?(:dump_config_definition)
unless @verbose
next if plugin_class.name =~ /::PluginHelper::/
end
dumped_config_definition = plugin_class.dump_config_definition
dumped_config[plugin_class.name] = dumped_config_definition unless dumped_config_definition.empty?
end
case @format
when :txt
puts dump_txt(dumped_config)
when :markdown
puts dump_markdown(dumped_config)
when :json
puts dump_json(dumped_config)
end
end

private

def dump_txt(dumped_config)
dumped = ""
plugin_helpers = dumped_config.delete(:plugin_helpers)
if plugin_helpers && !plugin_helpers.empty?
dumped << "helpers: #{plugin_helpers.join(',')}\n"
end
if @verbose
dumped_config.each do |name, config|
dumped << "#{name}\n"
dumped << dump_section_txt(config)
end
else
configs = dumped_config.values
root_section = configs.shift
configs.each do |config|
root_section.update(config)
end
dumped << dump_section_txt(root_section)
end
dumped
end

def dump_section_txt(base_section, level = 0)
dumped = ""
indent = " " * level
if base_section[:section]
sections = []
params = base_section
else
sections, params = base_section.partition {|_name, value| value[:section] }
end
params.each do |name, config|
next if name == :section
dumped << "#{indent}#{name}: #{config[:type]}: (#{config[:default].inspect})"
dumped << " # #{config[:description]}" if config.key?(:description)
dumped << "\n"
end
sections.each do |section_name, sub_section|
required = sub_section.delete(:required)
multi = sub_section.delete(:multi)
alias_name = sub_section.delete(:alias)
required_label = required ? "required" : "optional"
multi_label = multi ? "multiple" : "single"
alias_label = "alias: #{alias_name}"
dumped << "#{indent}<#{section_name}>: #{required_label}, #{multi_label}"
if alias_name
dumped << ", #{alias_label}\n"
else
dumped << "\n"
end
sub_section.delete(:section)
dumped << dump_section_txt(sub_section, level + 1)
end
dumped
end

def dump_markdown(dumped_config)
dumped = ""
plugin_helpers = dumped_config.delete(:plugin_helpers)
if plugin_helpers && !plugin_helpers.empty?
dumped = "## Plugin helpers\n\n"
plugin_helpers.each do |plugin_helper|
dumped << "* #{plugin_helper}\n"
end
dumped << "\n"
end
dumped_config.each do |name, config|
if name == @plugin.class.name
dumped << "## #{name}\n\n"
dumped << dump_section_markdown(config)
else
dumped << "* See also: #{name}\n\n"
end
end
dumped
end

def dump_section_markdown(base_section, level = 0)
dumped = ""
if base_section[:section]
sections = []
params = base_section
else
sections, params = base_section.partition {|_name, value| value[:section] }
end
params.each do |name, config|
next if name == :section
template_name = @compact ? "param.md-compact.erb" : "param.md.erb"
dumped << ERB.new(template_path(template_name).read, nil, "-").result(binding)
end
dumped << "\n"
sections.each do |section_name, sub_section|
required = sub_section.delete(:required)
multi = sub_section.delete(:multi)
alias_name = sub_section.delete(:alias)
$log.trace(name: section_name, required: required, multi: multi, alias_name: alias_name)
sub_section.delete(:section)
dumped << ERB.new(template_path("section.md.erb").read, nil, "-").result(binding)
end
dumped
end

def dump_json(dumped_config)
if @compact
JSON.generate(dumped_config)
else
JSON.pretty_generate(dumped_config)
end
end

def usage(message = nil)
puts @parser.to_s
puts
puts "Error: #{message}" if message
exit(false)
end

def prepare_option_parser
@parser = OptionParser.new
@parser.banner = <<BANNER
Usage: #{$0} [options] <type> <name>
Output plugin config definitions
Arguments:
\ttype: #{SUPPORTED_TYPES.join(",")}
\tname: registered plugin name
Options:
BANNER
@parser.on("--verbose", "Be verbose") do
@verbose = true
end
@parser.on("-c", "--compact", "Compact output") do
@compact = true
end
@parser.on("-f", "--format=FORMAT", "Specify format. (#{AVAILABLE_FORMATS.join(',')})") do |s|
format = s.to_sym
usage("Unsupported format: #{s}") unless AVAILABLE_FORMATS.include?(format)
@format = format
end
@parser.on("-I PATH", "Add PATH to $LOAD_PATH") do |s|
$LOAD_PATH.unshift(s)
end
@parser.on("-r NAME", "Load library") do |s|
@libs << s
end
@parser.on("-p", "--plugin=DIR", "Add plugin directory") do |s|
@plugin_dirs << s
end
end

def parse_options!
@parser.parse!(@argv)

raise "Must specify plugin type and name" unless @argv.size == 2

@plugin_type, @plugin_name = @argv
@options = {
compact: @compact,
format: @format,
verbose: @verbose,
}
rescue => e
usage(e)
end

def init_libraries
@libs.each do |lib|
require lib
end

@plugin_dirs.each do |dir|
if Dir.exist?(dir)
dir = File.expand_path(dir)
Fluent::Plugin.add_plugin_dir(dir)
end
end
end

def template_path(name)
(Pathname(__dir__) + "../../../templates/plugin_config_formatter/#{name}").realpath
end
end
Loading

0 comments on commit f1e8fa4

Please sign in to comment.