From 4e0b85e7919f6f7e7f78e52956885bc196ec7e0d Mon Sep 17 00:00:00 2001 From: Kanezoh Date: Tue, 16 Nov 2021 12:29:01 +0900 Subject: [PATCH 01/12] add page.cr comment --- src/mechanize/page.cr | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/mechanize/page.cr b/src/mechanize/page.cr index 9b082bd..eb5a8ad 100644 --- a/src/mechanize/page.cr +++ b/src/mechanize/page.cr @@ -8,6 +8,8 @@ require "./page/link" class MechanizeCr::Page < MechanizeCr::File include MechanizeCr::ElementMatcher + + # look at lexbor document.(https://github.com/kostya/lexbor#readme) delegate :css, to: parser property mech : Mechanize @@ -24,6 +26,9 @@ class MechanizeCr::Page < MechanizeCr::File end # return page title. + # ``` + # page.title # => String + # ``` def title : String title_node = css("title") if title_node.empty? @@ -34,6 +39,9 @@ class MechanizeCr::Page < MechanizeCr::File end # return all forms(`MechanizeCr::Form`) in the page. + # ``` + # page.forms # => Array(MechanizeCr::Form) + # ``` def forms : Array(MechanizeCr::Form) forms = css("form").map do |html_form| form = Form.new(html_form, self) @@ -42,7 +50,10 @@ class MechanizeCr::Page < MechanizeCr::File end.to_a end - # return all links(`MechanizeCr::PageContent::Link) in the page. + # return all links(`MechanizeCr::PageContent::Link`) in the page. + # ``` + # page.links # => Array(MechanizeCr::PageContent::Link) + # ``` def links : Array(MechanizeCr::PageContent::Link) links = %w{a area}.map do |tag| css(tag).map do |node| From f1fbaf6668f6810c24f8db563b2d14381b8ffd03 Mon Sep 17 00:00:00 2001 From: Kanezoh Date: Tue, 16 Nov 2021 12:55:37 +0900 Subject: [PATCH 02/12] add comment page and element_matcher --- src/mechanize/page.cr | 7 +++---- src/mechanize/utils/element_matcher.cr | 15 ++++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/mechanize/page.cr b/src/mechanize/page.cr index eb5a8ad..ed30d25 100644 --- a/src/mechanize/page.cr +++ b/src/mechanize/page.cr @@ -2,10 +2,9 @@ require "./file" require "./utils/element_matcher" require "./page/link" -# This class represents the page of response. -# If you send request, it returns the instance of Page. -# You can get status code, title, and page body, and search html node using css selector. - +# This class represents the result of http response. +# If you send a request, it returns the instance of `MechanizeCr::Page`. +# You can get status code, title, and page body, and search html node using css selector from page instance. class MechanizeCr::Page < MechanizeCr::File include MechanizeCr::ElementMatcher diff --git a/src/mechanize/utils/element_matcher.cr b/src/mechanize/utils/element_matcher.cr index 70c2056..b1a2e5f 100644 --- a/src/mechanize/utils/element_matcher.cr +++ b/src/mechanize/utils/element_matcher.cr @@ -1,18 +1,18 @@ module MechanizeCr::ElementMatcher macro elements_with(singular, plural = "") {% plural = "#{singular.id}s" if plural.empty? %} - # search {{ singular.id }} which matches condition. + # search {{ plural.id }} which match condition. # # Examples # ``` # # if you specify String like "foo", it searches form which name is "foo". - # # like {
} - # page.form_with("foo") + # # like <{{ singular.id }} name="foo"></form> + # page.{{ plural.id }}_with("foo") # - # # you can specify tag's attribute and its' value by NamedTuple or Hash(String, String). - # ex)
- # page.form_with("class" => "foo") - # page.form_with(class: "foo") + # # you can also specify tag's attribute and its' value by NamedTuple or Hash(String, String). + # ex) <{{ singular.id }} class="foo"></form> + # page.{{ plural.id }}_with("class" => "foo") + # page.{{ plural.id }}_with(class: "foo") # ``` def {{plural.id}}_with(criteria : String | NamedTuple | Hash(String,String)) {{plural.id}}_with(criteria){} @@ -43,6 +43,7 @@ module MechanizeCr::ElementMatcher f end + # returns first element of `#{{ plural.id }}_with` def {{singular.id}}_with(criteria) f = {{plural.id}}_with(criteria) # TODO: Write correct error message. From b3c4c466160c71e399eea039c21adb6fb7b496e0 Mon Sep 17 00:00:00 2001 From: Kanezoh Date: Tue, 16 Nov 2021 14:07:33 +0900 Subject: [PATCH 03/12] add history comment --- src/mechanize/history.cr | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/mechanize/history.cr b/src/mechanize/history.cr index 6e1600b..9814694 100644 --- a/src/mechanize/history.cr +++ b/src/mechanize/history.cr @@ -1,6 +1,10 @@ require "./page" +# This class represents the history of http response you sent. +# If you send a request, mechanize saves the history. class MechanizeCr::History + # max page size history can save. default is 100. + # as same as `agent.max_history`. property max_size : Int32 property array : Array(MechanizeCr::Page) @@ -11,6 +15,7 @@ class MechanizeCr::History @array = array end + # add page to history. def push(page, uri = nil) @array.push(page) while size > @max_size @@ -19,6 +24,7 @@ class MechanizeCr::History self end + # take the last page out from history. def pop if size == 0 # TODO: raise error From cdf1299c27e65cd60bc2444fbb9c5aa141951fb6 Mon Sep 17 00:00:00 2001 From: Kanezoh Date: Tue, 16 Nov 2021 17:40:42 +0900 Subject: [PATCH 04/12] add element matcher comment --- src/mechanize/form.cr | 6 ++++++ src/mechanize/utils/element_matcher.cr | 21 ++++++++++++++++++--- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/mechanize/form.cr b/src/mechanize/form.cr index 86da5bb..9be8082 100644 --- a/src/mechanize/form.cr +++ b/src/mechanize/form.cr @@ -8,14 +8,20 @@ require "./form/button" require "./form/select_list" require "./utils/element_matcher" +# THis class represents the form tag of html. class MechanizeCr::Form include MechanizeCr::ElementMatcher getter node : Node | Lexbor::Node + # returns an array of `MechanizeCr::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) getter enctype : String getter method : String diff --git a/src/mechanize/utils/element_matcher.cr b/src/mechanize/utils/element_matcher.cr index b1a2e5f..0172f92 100644 --- a/src/mechanize/utils/element_matcher.cr +++ b/src/mechanize/utils/element_matcher.cr @@ -6,11 +6,26 @@ module MechanizeCr::ElementMatcher # Examples # ``` # # if you specify String like "foo", it searches form which name is "foo". - # # like <{{ singular.id }} name="foo"></form> + {% if ["form", "button"].includes?("#{singular.id}") %} + # # like <{{ singular.id }} name="foo"> + {% elsif "#{singular.id}" == "field" %} + # # like + {% elsif "#{singular.id}" == "radiobutton" %} + # # like + {% else %} + # # like + {% end %} # page.{{ plural.id }}_with("foo") - # # # you can also specify tag's attribute and its' value by NamedTuple or Hash(String, String). - # ex) <{{ singular.id }} class="foo"></form> + {% if ["form", "button"].includes?("#{singular.id}") %} + # # ex) <{{ singular.id }} class="foo"> + {% elsif "#{singular.id}" == "field" %} + # # ex) + {% elsif "#{singular.id}" == "radiobutton" %} + # # ex) + {% else %} + # # ex) + {% end %} # page.{{ plural.id }}_with("class" => "foo") # page.{{ plural.id }}_with(class: "foo") # ``` From 45f14baad04c6288e7d1792766723b7b569c2b98 Mon Sep 17 00:00:00 2001 From: Kanezoh Date: Tue, 16 Nov 2021 19:45:41 +0900 Subject: [PATCH 05/12] add comment --- src/mechanize/form.cr | 6 +- src/mechanize/history.cr | 35 ++++---- src/mechanize/page.cr | 86 +++++++++--------- src/mechanize/utils/element_matcher.cr | 118 ++++++++++++------------- 4 files changed, 128 insertions(+), 117 deletions(-) diff --git a/src/mechanize/form.cr b/src/mechanize/form.cr index 6143ab5..a993e3d 100644 --- a/src/mechanize/form.cr +++ b/src/mechanize/form.cr @@ -9,11 +9,11 @@ require "./form/select_list" require "./utils/element_matcher" # THis class represents the form tag of html. -class MechanizeCr::Form - include MechanizeCr::ElementMatcher +class Mechanize::Form + include Mechanize::ElementMatcher getter node : Node | Lexbor::Node - # returns an array of `MechanizeCr::FormContent::Field` in the form. + # returns an 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) diff --git a/src/mechanize/history.cr b/src/mechanize/history.cr index 6cebd10..5448079 100644 --- a/src/mechanize/history.cr +++ b/src/mechanize/history.cr @@ -1,12 +1,13 @@ require "./page" -# This class represents the history of http response you sent. +# This class represents the history of http response you sent. # If you send a request, mechanize saves the history. -class Mechanize::History - # max page size history can save. default is 100. - # as same as `agent.max_history`. - property max_size : Int32 - property array : Array(Mechanize::Page) +class Mechanize + class History + # max page size history can save. default is 100. + # as same as `agent.max_history`. + property max_size : Int32 + property array : Array(Mechanize::Page) delegate :size, :empty?, :last, to: array @@ -15,17 +16,21 @@ class Mechanize::History @array = array end - # add page to history. - def push(page, uri = nil) - @array.push(page) - while size > @max_size - @array.shift + # add page to history. + def push(page, uri = nil) + @array.push(page) + while size > @max_size + @array.shift + end + self end - # take the last page out from history. - def pop - if size == 0 - # TODO: raise error + # take the last page out from history. + def pop + if size == 0 + # TODO: raise error + end + page = @array.pop end end end diff --git a/src/mechanize/page.cr b/src/mechanize/page.cr index d10e60a..ff952be 100644 --- a/src/mechanize/page.cr +++ b/src/mechanize/page.cr @@ -2,16 +2,15 @@ require "./file" require "./utils/element_matcher" require "./page/link" -# This class represents the result of http response. -# If you send a request, it returns the instance of `Mechanize::Page`. +# This class represents the result of http response. +# 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 Mechanize::Page < Mechanize::File - include Mechanize::ElementMatcher +class Mechanize + class Page < Mechanize::File + include Mechanize::ElementMatcher - # look at lexbor document.(https://github.com/kostya/lexbor#readme) - delegate :css, to: parser - - property mech : Mechanize + # look at lexbor document.(https://github.com/kostya/lexbor#readme) + delegate :css, to: parser property mech : Mechanize @@ -20,41 +19,48 @@ class Mechanize::Page < Mechanize::File super(uri, response, body, code) end - # return page title. - # ``` - # page.title # => String - # ``` - def title : String - title_node = css("title") - if title_node.empty? - "" - else - title_node.first.inner_text + # parser to parse response body. + # TODO: now it's Lexbor::Parser. I want to also support other parsers like JSON. + def parser : Lexbor::Parser + @parser ||= Lexbor::Parser.new(@body) end - # return all forms(`Mechanize::Form`) in the page. - # ``` - # page.forms # => Array(Mechanize::Form) - # ``` - def forms : Array(Mechanize::Form) - forms = css("form").map do |html_form| - form = Form.new(html_form, self) - form.action ||= @uri.to_s - form - end.to_a - end - - # return all links(`Mechanize::PageContent::Link`) in the page. - # ``` - # page.links # => Array(Mechanize::PageContent::Link) - # ``` - def links : Array(Mechanize::PageContent::Link) - links = %w{a area}.map do |tag| - css(tag).map do |node| - PageContent::Link.new(node, @mech, self) + # return page title. + # ``` + # page.title # => String + # ``` + def title : String + title_node = css("title") + if title_node.empty? + "" + else + title_node.first.inner_text end - end.flatten - end + end + + # return all forms(`Mechanize::Form`) in the page. + # ``` + # page.forms # => Array(Mechanize::Form) + # ``` + def forms : Array(Mechanize::Form) + forms = css("form").map do |html_form| + form = Form.new(html_form, self) + form.action ||= @uri.to_s + form + end.to_a + end + + # return all links(`Mechanize::PageContent::Link`) in the page. + # ``` + # page.links # => Array(Mechanize::PageContent::Link) + # ``` + def links : Array(Mechanize::PageContent::Link) + links = %w{a area}.map do |tag| + css(tag).map do |node| + PageContent::Link.new(node, @mech, self) + end + end.flatten + end elements_with "form" end diff --git a/src/mechanize/utils/element_matcher.cr b/src/mechanize/utils/element_matcher.cr index 5ca3f09..f36d262 100644 --- a/src/mechanize/utils/element_matcher.cr +++ b/src/mechanize/utils/element_matcher.cr @@ -1,71 +1,71 @@ class Mechanize module ElementMatcher macro elements_with(singular, plural = "") - {% plural = "#{singular.id}s" if plural.empty? %} - # search {{ plural.id }} which match condition. - # - # Examples - # ``` - # # if you specify String like "foo", it searches form which name is "foo". - {% if ["form", "button"].includes?("#{singular.id}") %} - # # like <{{ singular.id }} name="foo"> - {% elsif "#{singular.id}" == "field" %} - # # like - {% elsif "#{singular.id}" == "radiobutton" %} - # # like - {% else %} - # # like - {% end %} - # page.{{ plural.id }}_with("foo") - # # you can also specify tag's attribute and its' value by NamedTuple or Hash(String, String). - {% if ["form", "button"].includes?("#{singular.id}") %} - # # ex) <{{ singular.id }} class="foo"> - {% elsif "#{singular.id}" == "field" %} - # # ex) - {% elsif "#{singular.id}" == "radiobutton" %} - # # ex) - {% else %} - # # ex) - {% end %} - # page.{{ plural.id }}_with("class" => "foo") - # page.{{ plural.id }}_with(class: "foo") - # ``` - def {{plural.id}}_with(criteria : String | NamedTuple | Hash(String,String)) - {{plural.id}}_with(criteria){} - end + {% plural = "#{singular.id}s" if plural.empty? %} + # search {{ plural.id }} which match condition. + # + # Examples + # ``` + # # if you specify String like "foo", it searches form which name is "foo". + {% if ["form", "button"].includes?("#{singular.id}") %} + # # like <{{ singular.id }} name="foo"> + {% elsif "#{singular.id}" == "field" %} + # # like + {% elsif "#{singular.id}" == "radiobutton" %} + # # like + {% else %} + # # like + {% end %} + # page.{{ plural.id }}_with("foo") + # # you can also specify tag's attribute and its' value by NamedTuple or Hash(String, String). + {% if ["form", "button"].includes?("#{singular.id}") %} + # # ex) <{{ singular.id }} class="foo"> + {% elsif "#{singular.id}" == "field" %} + # # ex) + {% elsif "#{singular.id}" == "radiobutton" %} + # # ex) + {% else %} + # # ex) + {% end %} + # page.{{ plural.id }}_with("class" => "foo") + # page.{{ plural.id }}_with(class: "foo") + # ``` + def {{plural.id}}_with(criteria : String | NamedTuple | Hash(String,String)) + {{plural.id}}_with(criteria){} + end - def {{plural.id}}_with(criteria, &block) - if criteria.is_a?(NamedTuple) - criteria = criteria.to_h - end - if criteria.is_a?(String) - criteria = {"name" => criteria} - else - criteria = criteria.each_with_object(Hash(String,String).new) do |(k, v), h| - k = k.to_s - h[k] = v + def {{plural.id}}_with(criteria, &block) + if criteria.is_a?(NamedTuple) + criteria = criteria.to_h end - end - f = {{plural.id}}.select do |elm| - criteria.all? do |k,v| - if k == "text" - v == elm.node.inner_text - else - v == elm.node.fetch(k,"") + if criteria.is_a?(String) + criteria = {"name" => criteria} + else + criteria = criteria.each_with_object(Hash(String,String).new) do |(k, v), h| + k = k.to_s + h[k] = v end end + f = {{plural.id}}.select do |elm| + criteria.all? do |k,v| + if k == "text" + v == elm.node.inner_text + else + v == elm.node.fetch(k,"") + end + end + end + yield f + f end - yield f - f - end - # returns first element of `#{{ plural.id }}_with` - def {{singular.id}}_with(criteria) - f = {{plural.id}}_with(criteria) - # TODO: Write correct error message. - raise Mechanize::ElementNotFoundError.new(:{{singular.id}}, "") if f.empty? - f.first + # returns first element of `#{{ plural.id }}_with` + def {{singular.id}}_with(criteria) + f = {{plural.id}}_with(criteria) + # TODO: Write correct error message. + raise Mechanize::ElementNotFoundError.new(:{{singular.id}}, "") if f.empty? + f.first + end end end - end end From 8b346642ae8296a3deba8bd8290bead440117966 Mon Sep 17 00:00:00 2001 From: Kanezoh Date: Wed, 17 Nov 2021 11:19:26 +0900 Subject: [PATCH 06/12] add file comment --- src/mechanize/file.cr | 12 +++++++++--- src/mechanize/form.cr | 9 +++++++-- src/mechanize/utils/element_matcher.cr | 2 +- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/mechanize/file.cr b/src/mechanize/file.cr index 02539b4..900d239 100644 --- a/src/mechanize/file.cr +++ b/src/mechanize/file.cr @@ -1,9 +1,15 @@ require "http/client" class Mechanize - class File - # property :body, :filename - property :body, :code, uri, :response + abstract class File + # property :filename + + # returns http response body + getter body : String + # returns http status code + getter code : Int32 + # returns page uri + getter uri : URI def initialize(uri : URI, response : ::HTTP::Client::Response, body : String, code : Int32) @uri = uri diff --git a/src/mechanize/form.cr b/src/mechanize/form.cr index a993e3d..a868ec1 100644 --- a/src/mechanize/form.cr +++ b/src/mechanize/form.cr @@ -13,7 +13,7 @@ class Mechanize::Form include Mechanize::ElementMatcher getter node : Node | Lexbor::Node - # returns an array of `Mechanize::FormContent::Field` in the form. + # 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) @@ -23,11 +23,16 @@ class Mechanize::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 - getter page : Mechanize::Page? + # return form's 'action' attribute. property action : String + # returns the page which includes the form. + getter page : Mechanize::Page? def initialize(node : Node | Lexbor::Node, page : Mechanize::Page? = nil) @enctype = node.fetch("enctype", "application/x-www-form-urlencoded") diff --git a/src/mechanize/utils/element_matcher.cr b/src/mechanize/utils/element_matcher.cr index f36d262..4acd692 100644 --- a/src/mechanize/utils/element_matcher.cr +++ b/src/mechanize/utils/element_matcher.cr @@ -6,7 +6,7 @@ class Mechanize # # Examples # ``` - # # if you specify String like "foo", it searches form which name is "foo". + # # if you specify String like "foo", it searches {{ singular.id }} which name is "foo". {% if ["form", "button"].includes?("#{singular.id}") %} # # like <{{ singular.id }} name="foo"> {% elsif "#{singular.id}" == "field" %} From a70e6eae90653ec195cc47451fbd94f0aae47be2 Mon Sep 17 00:00:00 2001 From: Kanezoh Date: Thu, 18 Nov 2021 09:43:37 +0900 Subject: [PATCH 07/12] add comment button form element --- src/mechanize/form/button.cr | 2 ++ src/mechanize/form/image_button.cr | 1 + src/mechanize/form/reset_button.cr | 1 + src/mechanize/form/submit_button.cr | 1 + 4 files changed, 5 insertions(+) diff --git a/src/mechanize/form/button.cr b/src/mechanize/form/button.cr index 8a610bf..4a55eda 100644 --- a/src/mechanize/form/button.cr +++ b/src/mechanize/form/button.cr @@ -1,3 +1,5 @@ +# This class represents button related html element. +#