From 3c431f59fb068a2c066998f354110334ac297654 Mon Sep 17 00:00:00 2001 From: Seorgiy Date: Mon, 18 Nov 2024 15:52:18 +0000 Subject: [PATCH] happy linter --- rakelib/parse_schema.rake | 75 +++++++++---------------- rakelib/rebuild_types.rake | 110 ++++++++++++++++++++++++------------- 2 files changed, 98 insertions(+), 87 deletions(-) diff --git a/rakelib/parse_schema.rake b/rakelib/parse_schema.rake index 876c0bc..620079d 100644 --- a/rakelib/parse_schema.rake +++ b/rakelib/parse_schema.rake @@ -5,42 +5,21 @@ DRY_TYPES = %w[string integer float decimal array hash symbol boolean date date_ desc 'Parse types from public json, should be up to date https://github.com/ark0f/tg-bot-api' task :parse_schema do - result = {} document = Openapi3Parser.load_url('https://ark0f.github.io/tg-bot-api/openapi.json') - document.components.schemas.each do |schema| - # Input File is literally just a string for our purpose - next if schema[0] == 'InputFile' - - result_schema = {} - - schema[1].properties.each do |property| - required_keys = schema[1].required.to_a || [] - property_name = property[0] - property_schema = property[1] - - attribute_type = - case property_schema.type - when nil then property_name.capitalize.gsub(/_(\w)/) { Regexp.last_match(1).upcase } - when 'object' then property_schema.name - else property_schema.type - end - - attribute = { - type: attribute_type - } + result = document.components.schemas.to_h do |type_name, schema| + type_schema = schema.properties.to_h do |property_name, property_schema| + attribute = { type: parse_initial_type(property_schema, property_name) } unless property_schema.any_of.nil? - attribute[:type] = property_schema.any_of.map do |item| - property_schema.name || item.name || item.type - end.uniq + attribute[:type] = property_schema.any_of.map { |item| property_schema.name || item.name || item.type }.uniq end # Input File is literally just a string for our purpose attribute[:type]&.delete('InputFile') attribute[:type] = attribute[:type].join if attribute[:type]&.length == 1 - attribute[:required] = true if required_keys.include?(property_name) + attribute[:required] = true if required_keys(schema).include?(property_name) # getting required values from the description, no values in json 😔 required_value = property_schema.description&.match(/always “(.+)”|must be \*(.+)\*/) @@ -59,41 +38,37 @@ task :parse_schema do # array of arrays if property_schema&.items&.type == 'array' - attribute[:items] = { - type: 'array', - items: property_schema&.items&.items&.name - } + attribute[:items] = { type: 'array', items: property_schema.items.items.name } end - attribute[:default] = property_schema.default if property_schema.default - # previous line would have been enough, but had to check the description due to issue: https://github.com/kevindew/openapi3_parser/issues/28 - attribute[:default] = false if property_schema.description&.include?('Defaults to *false*') - result_schema[property_name] = attribute + attribute = apply_default(attribute, property_schema) + [property_name, attribute] end - result[schema[0]] = result_schema - # find empty classes - result[schema[0]][:type] = schema[1].any_of.map(&:name) if schema[1].properties.empty? && schema[1].any_of + type_schema[:type] = schema.any_of.map(&:name) if schema.properties.empty? && schema.any_of + [type_name, type_schema] end - File.write "#{__dir__}/../utility/type_attributes.json", JSON.pretty_generate(result) + # Input File is literally just a string for our purpose + File.write "#{__dir__}/../utility/type_attributes.json", JSON.pretty_generate(result.except('InputFile')) end -def add_module_types(type) - return 'Types::Float' if type == 'number' - - DRY_TYPES.include?(type) ? "Types::#{type.capitalize}" : type +def required_keys(schema) + schema.required.to_a || [] end -def underscore(camel_cased_word) - camel_cased_word.to_s.gsub('::', '/') - .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2') - .gsub(/([a-z\d])([A-Z])/, '\1_\2') - .tr('-', '_') - .downcase +def apply_default(attribute, property_schema) + attribute[:default] = property_schema.default unless property_schema.default.nil? + # previous line would have been enough, but had to check the description due to issue: https://github.com/kevindew/openapi3_parser/issues/28 + attribute[:default] = false if property_schema.description&.include?('Defaults to *false*') + attribute end -def typecast_default(type, obj) - type == 'Types::String' ? "'#{obj}'" : obj +def parse_initial_type(property_schema, property_name) + case property_schema.type + when nil then property_name.capitalize.gsub(/_(\w)/) { Regexp.last_match(1).upcase } + when 'object' then property_schema.name + else property_schema.type + end end diff --git a/rakelib/rebuild_types.rake b/rakelib/rebuild_types.rake index 977c817..edc0a61 100644 --- a/rakelib/rebuild_types.rake +++ b/rakelib/rebuild_types.rake @@ -8,46 +8,82 @@ task :rebuild_types do types = JSON.parse(File.read("#{__dir__}/../utility/type_attributes.json"), symbolize_names: true) types.each_pair do |name, attributes| - attributes.delete(:inherited_from) - if attributes[:type].instance_of?(Array) - attributes = attributes[:type].join(" |\n ") - template = ERB.new(File.read("#{__dir__}/../utility/empty_type.erb")) - else - template = ERB.new(File.read("#{__dir__}/../utility/type.erb")) - attributes.each_pair do |attr_name, properties| - attributes[attr_name][:type] = add_module_types(properties[:type]) unless properties[:type].is_a?(Array) - if properties[:type].is_a?(Array) - attributes[attr_name][:type] = properties[:type].map do |type| - add_module_types(type) - end.join(' | ') - end - - if properties[:items].instance_of?(String) - attributes[attr_name][:type] += ".of(#{add_module_types(properties[:items])})" - elsif properties[:items] && properties[:items][:type] == 'array' - attributes[attr_name][:type] += ".of(Types::Array.of(#{properties[:items][:items]}))" - end - original_type = properties[:type] - if properties[:required_value] - attributes[attr_name][:type] += ".constrained(eql: #{typecast_default(original_type, - properties[:required_value])})" - end - if properties[:min_size] || properties[:max_size] - constrain = '.constrained(minmax)' - constrain = properties[:min_size] ? constrain.gsub('min', "min_size: #{properties[:min_size]}, ") : '' - constrain = constrain.gsub('max', "max_size: #{properties[:max_size]}") if properties[:max_size] - attributes[attr_name][:type] += constrain - end - unless properties[:default].nil? - attributes[attr_name][:type] += ".default(#{typecast_default(original_type, - properties[:default])})" - end - attributes[attr_name][:type] = 'Types::True' if attributes[attr_name][:type] == 'Types::Boolean.default(true)' - attributes[attr_name][:type] = attributes[attr_name][:type].gsub('Types::Boolean', 'Types::Bool') + next build_empty_type(name, attributes) if attributes[:type].instance_of?(Array) + + attributes.except(:inherited_from).each_pair do |attr_name, properties| + attributes[attr_name][:type] = add_module_types(properties[:type]) unless properties[:type].is_a?(Array) + if properties[:type].is_a?(Array) + attributes[attr_name][:type] = properties[:type].map do |type| + add_module_types(type) + end.join(' | ') + end + + if properties[:items].instance_of?(String) + attributes[attr_name][:type] += ".of(#{add_module_types(properties[:items])})" + elsif properties[:items] && properties[:items][:type] == 'array' + attributes[attr_name][:type] += ".of(Types::Array.of(#{properties[:items][:items]}))" end + original_type = properties[:type] + + attributes = apply_required(attributes, attr_name, properties, original_type) + attributes = apply_min_max(attributes, attr_name, properties) + attributes = apply_default(attributes, attr_name, properties, original_type) + + attributes[attr_name][:type] = 'Types::True' if attributes[attr_name][:type] == 'Types::Boolean.default(true)' + attributes[attr_name][:type] = attributes[attr_name][:type].gsub('Types::Boolean', 'Types::Bool') end File.write "#{__dir__}/../lib/telegram/bot/types/#{underscore(name)}.rb", - template.result(binding).gsub(" \n", '') + ERB.new(File.read("#{__dir__}/../utility/type.erb")).result(binding).gsub(" \n", '') end end + +def build_empty_type(name, attributes) + attributes = attributes[:type].join(" |\n ") + File.write "#{__dir__}/../lib/telegram/bot/types/#{underscore(name)}.rb", + ERB.new(File.read("#{__dir__}/../utility/empty_type.erb")).result(binding).gsub(" \n", '') +end + +def apply_default(attributes, attr_name, properties, original_type) + return attributes if properties[:default].nil? + + attributes[attr_name][:type] += ".default(#{typecast(original_type, + properties[:default])})" + attributes +end + +def apply_required(attributes, attr_name, properties, original_type) + return attributes unless properties[:required_value] + + attributes[attr_name][:type] += ".constrained(eql: #{typecast(original_type, + properties[:required_value])})" + attributes +end + +def apply_min_max(attributes, attr_name, properties) + return attributes unless properties[:min_size] || properties[:max_size] + + constrain = '.constrained(minmax)' + constrain = properties[:min_size] ? constrain.gsub('min', "min_size: #{properties[:min_size]}, ") : '' + constrain = constrain.gsub('max', "max_size: #{properties[:max_size]}") if properties[:max_size] + attributes[attr_name][:type] += constrain + attributes +end + +def add_module_types(type) + return 'Types::Float' if type == 'number' + + DRY_TYPES.include?(type) ? "Types::#{type.capitalize}" : type +end + +def underscore(camel_cased_word) + camel_cased_word.to_s.gsub('::', '/') + .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2') + .gsub(/([a-z\d])([A-Z])/, '\1_\2') + .tr('-', '_') + .downcase +end + +def typecast(type, obj) + type == 'Types::String' ? "'#{obj}'" : obj +end