From ed57c3a69663a6e1f9f9cc43a746c0a746134b0f Mon Sep 17 00:00:00 2001 From: Kanezoh Date: Thu, 18 Nov 2021 14:19:07 +0900 Subject: [PATCH] refactor --- src/mechanize/form.cr | 346 +++++++++++++------------ src/mechanize/history.cr | 4 +- src/mechanize/http/agent.cr | 10 +- src/mechanize/page.cr | 6 +- src/mechanize/page/link.cr | 34 +-- src/mechanize/utils/element_matcher.cr | 2 +- 6 files changed, 203 insertions(+), 199 deletions(-) diff --git a/src/mechanize/form.cr b/src/mechanize/form.cr index a6a85d1..fc5e254 100644 --- a/src/mechanize/form.cr +++ b/src/mechanize/form.cr @@ -8,197 +8,199 @@ require "./form/button" require "./form/select_list" require "./utils/element_matcher" -# This class represents the form tag of html. -class Mechanize::Form - include Mechanize::ElementMatcher +class Mechanize + # This class represents the form tag of html. + class Form + include ElementMatcher - getter node : Node | Lexbor::Node - # returns hoge array of `Mechanize::FormContent::Field` in the form. - getter fields : Array(FormContent::Field) - # returns an array of input tags whose type is checkbox in the form. - getter checkboxes : Array(FormContent::CheckBox) - # returns an array of input tags whose type is radio in the form. - getter radiobuttons : Array(FormContent::RadioButton) - # returns an array of input tags whose type is select in the form. - getter selectboxes : Array(FormContent::MultiSelectList) - # returns an array of button tags and input tag whose type is button,submit,reset,image. - getter buttons : Array(FormContent::Button) - # returns form's 'enctype' attribute. - getter enctype : String - # returns form's 'method' attribute. - getter method : String - # returns form's 'name' attribute. - getter name : String - # return form's 'action' attribute. - property action : String - # returns the page which includes the form. - getter page : Mechanize::Page? + getter node : Node | Lexbor::Node + # returns hoge array of `Mechanize::FormContent::Field` in the form. + getter fields : Array(FormContent::Field) + # returns an array of input tags whose type is checkbox in the form. + getter checkboxes : Array(FormContent::CheckBox) + # returns an array of input tags whose type is radio in the form. + getter radiobuttons : Array(FormContent::RadioButton) + # returns an array of input tags whose type is select in the form. + getter selectboxes : Array(FormContent::MultiSelectList) + # returns an array of button tags and input tag whose type is button,submit,reset,image. + getter buttons : Array(FormContent::Button) + # returns form's 'enctype' attribute. + getter enctype : String + # returns form's 'method' attribute. + getter method : String + # returns form's 'name' attribute. + getter name : String + # return form's 'action' attribute. + property action : String + # returns the page which includes the form. + getter page : Page? - def initialize(node : Node | Lexbor::Node, page : Mechanize::Page? = nil) - @enctype = node.fetch("enctype", "application/x-www-form-urlencoded") - @node = node - @fields = Array(Mechanize::FormContent::Field).new - @checkboxes = Array(Mechanize::FormContent::CheckBox).new - @radiobuttons = Array(Mechanize::FormContent::RadioButton).new - @selectboxes = Array(Mechanize::FormContent::MultiSelectList).new - @buttons = Array(Mechanize::FormContent::Button).new - @action = node.fetch("action", "") - @method = node.fetch("method", "GET").upcase - @name = node.fetch("name", "") - @clicked_buttons = Array(Mechanize::FormContent::Button).new - @page = page - # @mech = mech + def initialize(node : Node | Lexbor::Node, page : Page? = nil) + @enctype = node.fetch("enctype", "application/x-www-form-urlencoded") + @node = node + @fields = Array(FormContent::Field).new + @checkboxes = Array(FormContent::CheckBox).new + @radiobuttons = Array(FormContent::RadioButton).new + @selectboxes = Array(FormContent::MultiSelectList).new + @buttons = Array(FormContent::Button).new + @action = node.fetch("action", "") + @method = node.fetch("method", "GET").upcase + @name = node.fetch("name", "") + @clicked_buttons = Array(FormContent::Button).new + @page = page + # @mech = mech - # @encoding = node['accept-charset'] || (page && page.encoding) || nil - # @ignore_encoding_error = false - parse - end + # @encoding = node['accept-charset'] || (page && page.encoding) || nil + # @ignore_encoding_error = false + parse + end - def request_data - query_params = build_query - build_query_string(query_params) - end + def request_data + query_params = build_query + build_query_string(query_params) + end - # generate fields_with and field_with methods. - # These methods are used for finding nodes that matches conditions. - # ex.) field_with("email") finds + # generate fields_with and field_with methods. + # These methods are used for finding nodes that matches conditions. + # ex.) field_with("email") finds - elements_with "field" - elements_with "radiobutton" - elements_with "checkbox", "checkboxes" - elements_with "button" + elements_with "field" + elements_with "radiobutton" + elements_with "checkbox", "checkboxes" + elements_with "button" - # Returns all fields of <input type="textarea"> - def textareas - fields.select { |f| f.class == FormContent::Textarea }.map &.as(FormContent::Textarea) - end + # Returns all fields of <input type="textarea"> + def textareas + fields.select { |f| f.class == FormContent::Textarea }.map &.as(FormContent::Textarea) + end - private def parse - @node.css("input").not_nil!.each do |html_node| - html_node = html_node.as(Lexbor::Node) - type = (html_node["type"]? || "text").downcase - case type - when "checkbox" - checkboxes << Mechanize::FormContent::CheckBox.new(html_node, self) - when "radio" - radiobuttons << Mechanize::FormContent::RadioButton.new(html_node, self) - when "button" - buttons << Mechanize::FormContent::Button.new(html_node, @node) - when "submit" - buttons << Mechanize::FormContent::SubmitButton.new(html_node, @node) - when "reset" - buttons << Mechanize::FormContent::ResetButton.new(html_node, @node) - when "image" - buttons << Mechanize::FormContent::ImageButton.new(html_node, @node) - when "text" - fields << Mechanize::FormContent::Text.new(html_node) - when "hidden" - fields << Mechanize::FormContent::Hidden.new(html_node) - when "textarea" - fields << Mechanize::FormContent::Textarea.new(html_node) - else - fields << Mechanize::FormContent::Field.new(html_node) + private def parse + @node.css("input").not_nil!.each do |html_node| + html_node = html_node.as(Lexbor::Node) + type = (html_node["type"]? || "text").downcase + case type + when "checkbox" + checkboxes << FormContent::CheckBox.new(html_node, self) + when "radio" + radiobuttons << FormContent::RadioButton.new(html_node, self) + when "button" + buttons << FormContent::Button.new(html_node, @node) + when "submit" + buttons << FormContent::SubmitButton.new(html_node, @node) + when "reset" + buttons << FormContent::ResetButton.new(html_node, @node) + when "image" + buttons << FormContent::ImageButton.new(html_node, @node) + when "text" + fields << FormContent::Text.new(html_node) + when "hidden" + fields << FormContent::Hidden.new(html_node) + when "textarea" + fields << FormContent::Textarea.new(html_node) + else + fields << FormContent::Field.new(html_node) + end end - end - # Find all textarea tags - @node.css("textarea").each do |node| - node = node.as(Lexbor::Node) - next if node["name"].empty? - @fields << FormContent::Textarea.new(node, node.inner_text) - end - - @node.css("button").each do |node| - node = node.as(Lexbor::Node) - type = node.fetch("type", "submit").downcase - next if type == "reset" - @buttons << FormContent::Button.new(node, @node) - end - - # Find all select tags - @node.css("select").each do |node| - node = node.as(Lexbor::Node) - next if node["name"].empty? - if node.has_key?("multiple") - @selectboxes << FormContent::MultiSelectList.new(node) - else - @selectboxes << FormContent::SelectList.new(node) + # Find all textarea tags + @node.css("textarea").each do |node| + node = node.as(Lexbor::Node) + next if node["name"].empty? + @fields << FormContent::Textarea.new(node, node.inner_text) end - end - end - private def build_query_string(params : Array(Array(String))) - params.reduce("") do |acc, arr| - hash = {arr[0] => arr[1]} - acc + URI::Params.encode(hash) + '&' - end.rchop - end - - private def build_query - query = [] of Array(String) - successful_controls = Array(FormContent::Field | FormContent::CheckBox).new - fields.each do |elm| - successful_controls << elm - end - checkboxes.each do |elm| - if elm.checked - successful_controls << elm + @node.css("button").each do |node| + node = node.as(Lexbor::Node) + type = node.fetch("type", "submit").downcase + next if type == "reset" + @buttons << FormContent::Button.new(node, @node) end - end - radio_groups = Hash(String, Array(FormContent::RadioButton)).new - radiobuttons.each do |radio| - name = radio.name - radio_groups[name] = Array(FormContent::RadioButton).new unless radio_groups.has_key?(name) - radio_groups[name] << radio - end - radio_groups.each_value do |g| - checked = g.select(&.checked) - if checked.uniq.size > 1 - # values = checked.map(&.value).join(', ').inspect - # name = checked.first.name.inspect - # raise Mechanize::Error, - # "radiobuttons #{values} are checked in the #{name} group, " \ - # "only one is allowed" - raise Mechanize::Error.new - else - successful_controls << checked.first unless checked.empty? - end - end - - @clicked_buttons.each do |b| - successful_controls << b - end - - successful_controls.each do |ctrl| - value = ctrl.query_value - next if value[0] == "" - query.push(value) - end - - @selectboxes.each do |s| - value = s.query_value - if value - value.each do |v| - query.push(v) + # Find all select tags + @node.css("select").each do |node| + node = node.as(Lexbor::Node) + next if node["name"].empty? + if node.has_key?("multiple") + @selectboxes << FormContent::MultiSelectList.new(node) + else + @selectboxes << FormContent::SelectList.new(node) end end end - query - end - - # This method adds a button to the query. If the form needs to be - # submitted with multiple buttons, pass each button to this method. - def add_button_to_query(button) - unless button.form_node == @node - message = "" - "#{button.inspect} does not belong to the same page as " \ - "the form #{@name.inspect} in #{@page.try &.uri}" - message = "not a valid button" - raise ArgumentError.new(message) + private def build_query_string(params : Array(Array(String))) + params.reduce("") do |acc, arr| + hash = {arr[0] => arr[1]} + acc + URI::Params.encode(hash) + '&' + end.rchop end - @clicked_buttons << button + private def build_query + query = [] of Array(String) + successful_controls = Array(FormContent::Field | FormContent::CheckBox).new + fields.each do |elm| + successful_controls << elm + end + checkboxes.each do |elm| + if elm.checked + successful_controls << elm + end + end + radio_groups = Hash(String, Array(FormContent::RadioButton)).new + radiobuttons.each do |radio| + name = radio.name + radio_groups[name] = Array(FormContent::RadioButton).new unless radio_groups.has_key?(name) + radio_groups[name] << radio + end + + radio_groups.each_value do |g| + checked = g.select(&.checked) + if checked.uniq.size > 1 + # values = checked.map(&.value).join(', ').inspect + # name = checked.first.name.inspect + # raise Mechanize::Error, + # "radiobuttons #{values} are checked in the #{name} group, " \ + # "only one is allowed" + raise Error.new + else + successful_controls << checked.first unless checked.empty? + end + end + + @clicked_buttons.each do |b| + successful_controls << b + end + + successful_controls.each do |ctrl| + value = ctrl.query_value + next if value[0] == "" + query.push(value) + end + + @selectboxes.each do |s| + value = s.query_value + if value + value.each do |v| + query.push(v) + end + end + end + + query + end + + # This method adds a button to the query. If the form needs to be + # submitted with multiple buttons, pass each button to this method. + def add_button_to_query(button) + unless button.form_node == @node + message = "" + "#{button.inspect} does not belong to the same page as " \ + "the form #{@name.inspect} in #{@page.try &.uri}" + message = "not a valid button" + raise ArgumentError.new(message) + end + + @clicked_buttons << button + end end end diff --git a/src/mechanize/history.cr b/src/mechanize/history.cr index 95aec47..d729640 100644 --- a/src/mechanize/history.cr +++ b/src/mechanize/history.cr @@ -7,11 +7,11 @@ class Mechanize # max page size history can save. default is 100. # as same as `agent.max_history`. property max_size : Int32 - property array : Array(Mechanize::Page) + property array : Array(Page) delegate :size, :empty?, :last, to: array - def initialize(max_size = 100, array = Array(Mechanize::Page).new) + def initialize(max_size = 100, array = Array(Page).new) @max_size = max_size @array = array end diff --git a/src/mechanize/http/agent.cr b/src/mechanize/http/agent.cr index 0280e7b..1cb1458 100644 --- a/src/mechanize/http/agent.cr +++ b/src/mechanize/http/agent.cr @@ -9,12 +9,12 @@ class Mechanize class Agent property request_headers : ::HTTP::Headers property context : Mechanize? - property history : Mechanize::History + property history : History property user_agent : String property request_cookies : ::HTTP::Cookies def initialize(@context : Mechanize? = nil) - @history = Mechanize::History.new + @history = History.new @request_headers = ::HTTP::Headers.new @context = context @request_cookies = ::HTTP::Cookies.new @@ -73,7 +73,7 @@ class Mechanize # ``` # agent.current_page # ``` - def current_page : Mechanize::Page + def current_page : Page @history.last end @@ -81,7 +81,7 @@ class Mechanize # ``` # agent.back # ``` - def back : Mechanize::Page + def back : Page @history.pop end @@ -116,7 +116,7 @@ class Mechanize end # Sets a Referer header. - def set_request_referer(referer : Mechanize::Page?) + def set_request_referer(referer : Page?) return unless referer request_headers["Referer"] = referer.uri.to_s diff --git a/src/mechanize/page.cr b/src/mechanize/page.cr index b79a4a4..2d4336b 100644 --- a/src/mechanize/page.cr +++ b/src/mechanize/page.cr @@ -7,7 +7,7 @@ class Mechanize # If you send a request, it returns the instance of `Mechanize::Page`. # You can get status code, title, and page body, and search html node using css selector from page instance. class Page < Mechanize::File - include Mechanize::ElementMatcher + include ElementMatcher # look at lexbor document.(https://github.com/kostya/lexbor#readme) delegate :css, to: parser @@ -42,7 +42,7 @@ class Mechanize # ``` # page.forms # => Array(Mechanize::Form) # ``` - def forms : Array(Mechanize::Form) + def forms : Array(Form) forms = css("form").map do |html_form| form = Form.new(html_form, self) form.action ||= @uri.to_s @@ -54,7 +54,7 @@ class Mechanize # ``` # page.links # => Array(Mechanize::PageContent::Link) # ``` - def links : Array(Mechanize::PageContent::Link) + def links : Array(PageContent::Link) links = %w{a area}.map do |tag| css(tag).map do |node| PageContent::Link.new(node, @mech, self) diff --git a/src/mechanize/page/link.cr b/src/mechanize/page/link.cr index 2267f64..8bc9185 100644 --- a/src/mechanize/page/link.cr +++ b/src/mechanize/page/link.cr @@ -1,22 +1,24 @@ # This class represents link element like and . # The instance of this class is clickable. -class Mechanize::PageContent::Link - getter node : Lexbor::Node - getter page : Mechanize::Page - getter mech : Mechanize - getter href : String - getter text : String +class Mechanize + class PageContent::Link + getter node : Lexbor::Node + getter page : Page + getter mech : Mechanize + getter href : String + getter text : String - def initialize(node, mech, page) - @node = node - @page = page - @mech = mech - @href = node.fetch("href", "") - @text = node.inner_text - end + def initialize(node, mech, page) + @node = node + @page = page + @mech = mech + @href = node.fetch("href", "") + @text = node.inner_text + end - # click on this link - def click - @mech.click self + # click on this link + def click + @mech.click self + end end end diff --git a/src/mechanize/utils/element_matcher.cr b/src/mechanize/utils/element_matcher.cr index a653125..9983d8a 100644 --- a/src/mechanize/utils/element_matcher.cr +++ b/src/mechanize/utils/element_matcher.cr @@ -65,7 +65,7 @@ class Mechanize def {{singular.id}}_with(criteria) f = {{plural.id}}_with(criteria) # TODO: Write correct error message. - raise Mechanize::ElementNotFoundError.new(:{{singular.id}}, "") if f.empty? + raise ElementNotFoundError.new(:{{singular.id}}, "") if f.empty? f.first end end