Merge pull request #14 from Kanezoh/add-comment-for-docs

Add comment for docs
This commit is contained in:
Kanezoh 2021-11-18 12:58:23 +09:00 committed by GitHub
commit 0ceaffc7b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 202 additions and 70 deletions

29
.github/workflows/documentation.yml vendored Normal file
View File

@ -0,0 +1,29 @@
name: website
on:
push:
branches: [ master ]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: Install Crystal
uses: crystal-lang/install-crystal@v1
with:
crystal: nightly
- name: Check out repository code
uses: actions/checkout@v2
- name: Install dependencies
run: shards install
- name: Generate documentation
run: crystal docs
-
name: Deploy to GitHub Pages
if: success()
uses: crazy-max/ghaction-github-pages@v2
with:
target_branch: gh-pages
build_dir: docs
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -1,5 +1,6 @@
# TODO: want to add methods with safe way like Ruby's refinement. # TODO: want to property's without open HTTP::Cookie class.
# This class represents http cookie.
# open HTTP::Cookie class to add origin property. # open HTTP::Cookie class to add origin property.
# origin property represents the origin of the resource. # origin property represents the origin of the resource.
# if cookie's domain attribute isn't designated, # if cookie's domain attribute isn't designated,

View File

@ -1,5 +1,6 @@
require "./base_error" require "./base_error"
# This error means matched elements are not found by *_with method.
class Mechanize::ElementNotFoundError < Mechanize::Error class Mechanize::ElementNotFoundError < Mechanize::Error
getter element : Symbol getter element : Symbol
getter conditions : String getter conditions : String

View File

@ -1,9 +1,15 @@
require "http/client" require "http/client"
class Mechanize class Mechanize
class File abstract class File
# property :body, :filename # property :filename
property :body, :code, uri, :response
# 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) def initialize(uri : URI, response : ::HTTP::Client::Response, body : String, code : Int32)
@uri = uri @uri = uri

View File

@ -8,20 +8,31 @@ require "./form/button"
require "./form/select_list" require "./form/select_list"
require "./utils/element_matcher" require "./utils/element_matcher"
# This class represents the form tag of html.
class Mechanize::Form class Mechanize::Form
include Mechanize::ElementMatcher include Mechanize::ElementMatcher
getter node : Node | Lexbor::Node getter node : Node | Lexbor::Node
getter fields : Array(Mechanize::FormContent::Field) # returns hoge array of `Mechanize::FormContent::Field` in the form.
getter checkboxes : Array(Mechanize::FormContent::CheckBox) getter fields : Array(FormContent::Field)
getter radiobuttons : Array(Mechanize::FormContent::RadioButton) # returns an array of input tags whose type is checkbox in the form.
getter selectboxes : Array(Mechanize::FormContent::MultiSelectList) getter checkboxes : Array(FormContent::CheckBox)
getter buttons : Array(Mechanize::FormContent::Button) # 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 getter enctype : String
# returns form's 'method' attribute.
getter method : String getter method : String
# returns form's 'name' attribute.
getter name : String getter name : String
getter page : Mechanize::Page? # return form's 'action' attribute.
property action : String property action : String
# returns the page which includes the form.
getter page : Mechanize::Page?
def initialize(node : Node | Lexbor::Node, page : Mechanize::Page? = nil) def initialize(node : Node | Lexbor::Node, page : Mechanize::Page? = nil)
@enctype = node.fetch("enctype", "application/x-www-form-urlencoded") @enctype = node.fetch("enctype", "application/x-www-form-urlencoded")
@ -57,7 +68,7 @@ class Mechanize::Form
elements_with "checkbox", "checkboxes" elements_with "checkbox", "checkboxes"
elements_with "button" elements_with "button"
# Returns all fields of type Textarea # Returns all fields of <input type="textarea">
def textareas def textareas
fields.select { |f| f.class == FormContent::Textarea }.map &.as(FormContent::Textarea) fields.select { |f| f.class == FormContent::Textarea }.map &.as(FormContent::Textarea)
end end

View File

@ -1,3 +1,5 @@
# This class represents button related html element.
# <button>, and <input> whose type is button, reset, image, submit.
class Mechanize::FormContent::Button < Mechanize::FormContent::Field class Mechanize::FormContent::Button < Mechanize::FormContent::Field
getter form_node : Node | Lexbor::Node getter form_node : Node | Lexbor::Node

View File

@ -1,4 +1,6 @@
# This class represents <input type="checkbox">
class Mechanize::FormContent::CheckBox < Mechanize::FormContent::RadioButton class Mechanize::FormContent::CheckBox < Mechanize::FormContent::RadioButton
# set checkbox checked
def check def check
@checked = true @checked = true
end end

View File

@ -1,7 +1,13 @@
# This class represents <input> elements in the form.
class Mechanize::FormContent::Field class Mechanize::FormContent::Field
# returns field's 'value' attribute
property value : String? property value : String?
# returns field's 'name' attribute
getter name : String getter name : String
# returns field's 'type' attribute
getter type : String getter type : String
# returns field's 'value' attribute.
# value property is changeable, but this property stores raw value.
getter raw_value : String? getter raw_value : String?
getter node : Node | Lexbor::Node getter node : Node | Lexbor::Node
@ -17,12 +23,12 @@ class Mechanize::FormContent::Field
[@name, @value || ""] [@name, @value || ""]
end end
# returns DOM 'id' value # returns field's 'id' value
def dom_id def dom_id
node.fetch("id", "") node.fetch("id", "")
end end
# returns DOM 'class' value # returns field's 'class' value
def dom_class def dom_class
node.fetch("class", "") node.fetch("class", "")
end end

View File

@ -1,2 +1,3 @@
# This class represents <input type="hidden">
class Mechanize::FormContent::Hidden < Mechanize::FormContent::Field class Mechanize::FormContent::Hidden < Mechanize::FormContent::Field
end end

View File

@ -1,2 +1,3 @@
# This class represents <input type="image">
class Mechanize::FormContent::ImageButton < Mechanize::FormContent::Button class Mechanize::FormContent::ImageButton < Mechanize::FormContent::Button
end end

View File

@ -1,5 +1,6 @@
require "./option" require "./option"
# This class represents <select multiple>
class Mechanize::FormContent::MultiSelectList class Mechanize::FormContent::MultiSelectList
getter node : Lexbor::Node getter node : Lexbor::Node
getter name : String getter name : String
@ -18,32 +19,37 @@ class Mechanize::FormContent::MultiSelectList
} }
end end
# set all options unchecked
def select_none def select_none
@values = Array(String).new @values = Array(String).new
options.each &.unselect options.each &.unselect
end end
# set all options checked
def select_all def select_all
@values = Array(String).new @values = Array(String).new
options.each &.select options.each &.select
end end
# returns all checked options
def selected_options def selected_options
options.select &.selected? options.select &.selected?
end end
# add new values to options
def values=(new_values) def values=(new_values)
select_none select_none
new_values.each do |value| new_values.each do |value|
option = options.find { |o| o.value == value } option = options.find { |o| o.value == value }
if option.nil? if option.nil?
@value.push(value) @values.push(value)
else else
option.select option.select
end end
end end
end end
# return all option's values.
def values def values
@values + selected_options.map &.value @values + selected_options.map &.value
end end

View File

@ -30,6 +30,7 @@ class Mechanize::FormContent::Option
@selected = !@selected @selected = !@selected
end end
# returns option checked or not
def selected? def selected?
@selected @selected
end end

View File

@ -1,3 +1,4 @@
# This class represents <input type="radio">
class Mechanize::FormContent::RadioButton < Mechanize::FormContent::Field class Mechanize::FormContent::RadioButton < Mechanize::FormContent::Field
property :checked, :form property :checked, :form
@ -7,19 +8,23 @@ class Mechanize::FormContent::RadioButton < Mechanize::FormContent::Field
super(node) super(node)
end end
# set radiobutton checked
def check def check
uncheck_peers uncheck_peers
@checked = true @checked = true
end end
# set radiobutton checked
def uncheck def uncheck
@checked = false @checked = false
end end
# change radiobutton state checked or unchecked
def click def click
checked ? uncheck : check checked ? uncheck : check
end end
# returns radiobutton checked or not
def checked? def checked?
checked checked
end end

View File

@ -1,2 +1,3 @@
# This class represents <input type="reset">
class Mechanize::FormContent::ResetButton < Mechanize::FormContent::Button class Mechanize::FormContent::ResetButton < Mechanize::FormContent::Button
end end

View File

@ -1,5 +1,6 @@
require "./multi_select_list" require "./multi_select_list"
# This class represents <select> which is not multiple
class Mechanize::FormContent::SelectList < Mechanize::FormContent::MultiSelectList class Mechanize::FormContent::SelectList < Mechanize::FormContent::MultiSelectList
def initialize(node) def initialize(node)
super node super node

View File

@ -1,2 +1,3 @@
# This class represents <input type="submit">
class Mechanize::FormContent::SubmitButton < Mechanize::FormContent::Button class Mechanize::FormContent::SubmitButton < Mechanize::FormContent::Button
end end

View File

@ -1,2 +1,3 @@
# This class represents <input type="text">
class Mechanize::FormContent::Text < Mechanize::FormContent::Field class Mechanize::FormContent::Text < Mechanize::FormContent::Field
end end

View File

@ -1,2 +1,3 @@
# This class represents <input type="textarea">
class Mechanize::FormContent::Textarea < Mechanize::FormContent::Field class Mechanize::FormContent::Textarea < Mechanize::FormContent::Field
end end

View File

@ -1,7 +1,11 @@
require "./page" require "./page"
# This class represents the history of http response you sent.
# If you send a request, mechanize saves the history.
class Mechanize class Mechanize
class History class History
# max page size history can save. default is 100.
# as same as `agent.max_history`.
property max_size : Int32 property max_size : Int32
property array : Array(Mechanize::Page) property array : Array(Mechanize::Page)
@ -12,6 +16,7 @@ class Mechanize
@array = array @array = array
end end
# add page to history.
def push(page, uri = nil) def push(page, uri = nil)
@array.push(page) @array.push(page)
while size > @max_size while size > @max_size
@ -20,6 +25,7 @@ class Mechanize
self self
end end
# take the last page out from history.
def pop def pop
if size == 0 if size == 0
# TODO: raise error # TODO: raise error

View File

@ -6,12 +6,13 @@ require "../history"
class Mechanize class Mechanize
module HTTP module HTTP
class Agent class Agent
property :request_headers, :context property request_headers : ::HTTP::Headers
property context : Mechanize?
property history : Mechanize::History property history : Mechanize::History
property user_agent : String property user_agent : String
property request_cookies : ::HTTP::Cookies property request_cookies : ::HTTP::Cookies
def initialize(@context : Mechanize | Nil = nil) def initialize(@context : Mechanize? = nil)
@history = Mechanize::History.new @history = Mechanize::History.new
@request_headers = ::HTTP::Headers.new @request_headers = ::HTTP::Headers.new
@context = context @context = context
@ -19,6 +20,9 @@ class Mechanize
@user_agent = "" @user_agent = ""
end end
# send http request and return page.
# This method is called from Mechanize#get, #post and other methods.
# There's no need to call this method directly.
def fetch(uri, method = :get, headers = ::HTTP::Headers.new, params = Hash(String, String).new, def fetch(uri, method = :get, headers = ::HTTP::Headers.new, params = Hash(String, String).new,
referer = (current_page unless history.empty?)) referer = (current_page unless history.empty?))
uri = resolve_url(uri, referer) uri = resolve_url(uri, referer)
@ -51,7 +55,8 @@ class Mechanize
fetch(uri) fetch(uri)
end end
def http_request(uri, method, params) # send http request
private def http_request(uri, method, params) : ::HTTP::Client::Response?
case uri.scheme.not_nil!.downcase case uri.scheme.not_nil!.downcase
when "http", "https" when "http", "https"
case method case method
@ -63,20 +68,34 @@ class Mechanize
end end
end end
def current_page # returns the page now mechanize visiting.
# ```
# agent.current_page
# ```
def current_page : Mechanize::Page
@history.last @history.last
end end
def back # returns the page mechanize previous visited.
# ```
# agent.back
# ```
def back : Mechanize::Page
@history.pop @history.pop
end end
# Get maximum number of items allowed in the history. The default setting is 100 pages. # Get maximum number of items allowed in the history. The default setting is 100 pages.
def max_history # ```
# agent.max_history # => 100
# ```
def max_history : Int32
@history.max_size @history.max_size
end end
# Set maximum number of items allowed in the history. # Set maximum number of items allowed in the history.
# ```
# agent.max_history = 1000
# ```
def max_history=(length) def max_history=(length)
@history.max_size = length @history.max_size = length
end end

View File

@ -2,13 +2,14 @@ require "./file"
require "./utils/element_matcher" require "./utils/element_matcher"
require "./page/link" require "./page/link"
# This class represents the page of response. # This class represents the result of http response.
# If you send request, it returns the instance of Page. # 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. # You can get status code, title, and page body, and search html node using css selector from page instance.
class Mechanize class Mechanize
class Page < Mechanize::File class Page < Mechanize::File
include Mechanize::ElementMatcher include Mechanize::ElementMatcher
# look at lexbor document.(https://github.com/kostya/lexbor#readme)
delegate :css, to: parser delegate :css, to: parser
property mech : Mechanize property mech : Mechanize
@ -25,6 +26,9 @@ class Mechanize
end end
# return page title. # return page title.
# ```
# page.title # => String
# ```
def title : String def title : String
title_node = css("title") title_node = css("title")
if title_node.empty? if title_node.empty?
@ -35,19 +39,25 @@ class Mechanize
end end
# return all forms(`Mechanize::Form`) in the page. # return all forms(`Mechanize::Form`) in the page.
# ```
# page.forms # => Array(Mechanize::Form)
# ```
def forms : Array(Mechanize::Form) def forms : Array(Mechanize::Form)
forms = css("form").map do |html_form| forms = css("form").map do |html_form|
form = Mechanize::Form.new(html_form, self) form = Form.new(html_form, self)
form.action ||= @uri.to_s form.action ||= @uri.to_s
form form
end.to_a end.to_a
end end
# return all links(`Mechanize::PageContent::Link) in the page. # return all links(`Mechanize::PageContent::Link`) in the page.
# ```
# page.links # => Array(Mechanize::PageContent::Link)
# ```
def links : Array(Mechanize::PageContent::Link) def links : Array(Mechanize::PageContent::Link)
links = %w{a area}.map do |tag| links = %w{a area}.map do |tag|
css(tag).map do |node| css(tag).map do |node|
Mechanize::PageContent::Link.new(node, @mech, self) PageContent::Link.new(node, @mech, self)
end end
end.flatten end.flatten
end end

View File

@ -1,3 +1,5 @@
# This class represents link element like <a> and <area>.
# The instance of this class is clickable.
class Mechanize::PageContent::Link class Mechanize::PageContent::Link
getter node : Lexbor::Node getter node : Lexbor::Node
getter page : Mechanize::Page getter page : Mechanize::Page

View File

@ -1,55 +1,73 @@
class Mechanize class Mechanize
# This module is for macros making *_with methods.
# These methods are useful for searching elements by its' attribute.
module ElementMatcher module ElementMatcher
macro elements_with(singular, plural = "") macro elements_with(singular, plural = "")
{% plural = "#{singular.id}s" if plural.empty? %} {% plural = "#{singular.id}s" if plural.empty? %}
# search {{ singular.id }} which matches condition. # search {{ plural.id }} which match condition.
# #
# Examples # 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".
# # like {<form name="foo"></form>} {% if ["form", "button"].includes?("#{singular.id}") %}
# page.form_with("foo") # # like <{{ singular.id }} name="foo"></{{ singular.id }}>
# {% elsif "#{singular.id}" == "field" %}
# # you can specify tag's attribute and its' value by NamedTuple or Hash(String, String). # # like <input name="foo"></input>
# ex) <form class="foo"></form> {% elsif "#{singular.id}" == "radiobutton" %}
# page.form_with("class" => "foo") # # like <input type="radio" name="foo"></input>
# page.form_with(class: "foo") {% else %}
# ``` # # like <input type="{{ singular.id }}" name="foo"></input>
def {{plural.id}}_with(criteria : String | NamedTuple | Hash(String,String)) {% end %}
{{plural.id}}_with(criteria){} # page.{{ plural.id }}_with("foo")
end # # 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"></{{ singular.id }}>
{% elsif "#{singular.id}" == "field" %}
# # ex) <input class="foo"></input>
{% elsif "#{singular.id}" == "radiobutton" %}
# # ex) <input type="radio" class="foo"></input>
{% else %}
# # ex) <input type="{{ singular.id }}" class="foo"></input>
{% 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) def {{plural.id}}_with(criteria, &block)
if criteria.is_a?(NamedTuple) if criteria.is_a?(NamedTuple)
criteria = criteria.to_h 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
end end
end if criteria.is_a?(String)
f = {{plural.id}}.select do |elm| criteria = {"name" => criteria}
criteria.all? do |k,v| else
if k == "text" criteria = criteria.each_with_object(Hash(String,String).new) do |(k, v), h|
v == elm.node.inner_text k = k.to_s
else h[k] = v
v == elm.node.fetch(k,"")
end end
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 end
yield f
f
end
def {{singular.id}}_with(criteria) # returns first element of `#{{ plural.id }}_with`
f = {{plural.id}}_with(criteria) def {{singular.id}}_with(criteria)
# TODO: Write correct error message. f = {{plural.id}}_with(criteria)
raise Mechanize::ElementNotFoundError.new(:{{singular.id}}, "") if f.empty? # TODO: Write correct error message.
f.first raise Mechanize::ElementNotFoundError.new(:{{singular.id}}, "") if f.empty?
f.first
end
end end
end end
end
end end