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.
# origin property represents the origin of the resource.
# if cookie's domain attribute isn't designated,

View File

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

View File

@ -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

View File

@ -8,20 +8,31 @@ 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
getter node : Node | Lexbor::Node
getter fields : Array(Mechanize::FormContent::Field)
getter checkboxes : Array(Mechanize::FormContent::CheckBox)
getter radiobuttons : Array(Mechanize::FormContent::RadioButton)
getter selectboxes : Array(Mechanize::FormContent::MultiSelectList)
getter buttons : Array(Mechanize::FormContent::Button)
# 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
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")
@ -57,7 +68,7 @@ class Mechanize::Form
elements_with "checkbox", "checkboxes"
elements_with "button"
# Returns all fields of type Textarea
# Returns all fields of <input type="textarea">
def textareas
fields.select { |f| f.class == FormContent::Textarea }.map &.as(FormContent::Textarea)
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
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
# set checkbox checked
def check
@checked = true
end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,11 @@
require "./page"
# This class represents the history of http response you sent.
# If you send a request, mechanize saves the history.
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)
@ -12,6 +16,7 @@ class Mechanize
@array = array
end
# add page to history.
def push(page, uri = nil)
@array.push(page)
while size > @max_size
@ -20,6 +25,7 @@ class Mechanize
self
end
# take the last page out from history.
def pop
if size == 0
# TODO: raise error

View File

@ -6,12 +6,13 @@ require "../history"
class Mechanize
module HTTP
class Agent
property :request_headers, :context
property request_headers : ::HTTP::Headers
property context : Mechanize?
property history : Mechanize::History
property user_agent : String
property request_cookies : ::HTTP::Cookies
def initialize(@context : Mechanize | Nil = nil)
def initialize(@context : Mechanize? = nil)
@history = Mechanize::History.new
@request_headers = ::HTTP::Headers.new
@context = context
@ -19,6 +20,9 @@ class Mechanize
@user_agent = ""
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,
referer = (current_page unless history.empty?))
uri = resolve_url(uri, referer)
@ -51,7 +55,8 @@ class Mechanize
fetch(uri)
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
when "http", "https"
case method
@ -63,20 +68,34 @@ class Mechanize
end
end
def current_page
# returns the page now mechanize visiting.
# ```
# agent.current_page
# ```
def current_page : Mechanize::Page
@history.last
end
def back
# returns the page mechanize previous visited.
# ```
# agent.back
# ```
def back : Mechanize::Page
@history.pop
end
# 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
end
# Set maximum number of items allowed in the history.
# ```
# agent.max_history = 1000
# ```
def max_history=(length)
@history.max_size = length
end

View File

@ -2,13 +2,14 @@ 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 `Mechanize::Page`.
# You can get status code, title, and page body, and search html node using css selector from page instance.
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
@ -25,6 +26,9 @@ class Mechanize
end
# return page title.
# ```
# page.title # => String
# ```
def title : String
title_node = css("title")
if title_node.empty?
@ -35,19 +39,25 @@ class Mechanize
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 = Mechanize::Form.new(html_form, self)
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.
# 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|
Mechanize::PageContent::Link.new(node, @mech, self)
PageContent::Link.new(node, @mech, self)
end
end.flatten
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
getter node : Lexbor::Node
getter page : Mechanize::Page

View File

@ -1,55 +1,73 @@
class Mechanize
# This module is for macros making *_with methods.
# These methods are useful for searching elements by its' attribute.
module ElementMatcher
macro elements_with(singular, plural = "")
{% plural = "#{singular.id}s" if plural.empty? %}
# search {{ singular.id }} which matches condition.
#
# Examples
# ```
# # if you specify String like "foo", it searches form which name is "foo".
# # like {<form name="foo"></form>}
# page.form_with("foo")
#
# # you can specify tag's attribute and its' value by NamedTuple or Hash(String, String).
# ex) <form class="foo"></form>
# page.form_with("class" => "foo")
# page.form_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 {{ singular.id }} which name is "foo".
{% if ["form", "button"].includes?("#{singular.id}") %}
# # like <{{ singular.id }} name="foo"></{{ singular.id }}>
{% elsif "#{singular.id}" == "field" %}
# # like <input name="foo"></input>
{% elsif "#{singular.id}" == "radiobutton" %}
# # like <input type="radio" name="foo"></input>
{% else %}
# # like <input type="{{ singular.id }}" name="foo"></input>
{% 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"></{{ 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)
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
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