diff options
-rw-r--r-- | app/controllers/errors_controller.rb | 6 | ||||
-rw-r--r-- | app/controllers/search_controller.rb | 135 | ||||
-rw-r--r-- | app/views/layouts/_base.html.haml | 2 | ||||
-rw-r--r-- | app/views/search/_search.html.haml | 4 | ||||
-rw-r--r-- | app/views/search/search.html.haml | 7 | ||||
-rw-r--r-- | app/views/tweets/_tweets.html.haml | 2 | ||||
-rw-r--r-- | config/routes.rb | 2 |
7 files changed, 63 insertions, 95 deletions
diff --git a/app/controllers/errors_controller.rb b/app/controllers/errors_controller.rb index 08d7f82..86f32d9 100644 --- a/app/controllers/errors_controller.rb +++ b/app/controllers/errors_controller.rb @@ -6,7 +6,6 @@ class ErrorsController < ApplicationController def render_error @exception = env["action_dispatch.exception"] - #@status = ActionDispatch::ExceptionWrapper.new(env, @exception).status_code @title = "?" case @exception @@ -46,10 +45,7 @@ class ErrorsController < ApplicationController @user = @exception.user end - respond_to do |format| - format.html { render status: @status } - format.json { render status: @status } - end + render status: @status end private diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 56aa9a0..7d94393 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -1,97 +1,66 @@ -# -*- coding: utf-8 -*- -require "shellwords" - class SearchController < ApplicationController def search - @show_search = true + @caption = "search" + @tweets = Tweet.where(parse_query(params[:query])).order_by_id.reacted.list(params, force_page: true) + end - # TODO: OR とか () とか対応したいよね - unless params[:query] - @tweets = Tweet.none - render "tweets/_tweets" - return - end - words = Shellwords.shellwords(params[:query]) - dateformat = "(20[0-9]{2})([-_\/]?)([0-9]{2})\\2([0-9]{2})" # $1: year, $3: month, $4: day + private + def parse_query(input) + str = input.dup + strings = [] + str.gsub!(/"((?:\\"|[^"])*?)"/) {|m| strings << $1; "##{strings.size - 1}" } + groups = [] + while str.sub!(/\(([^()]*?)\)/) {|m| groups << $1; "$#{groups.size - 1}" }; end - result = words.inject(Tweet.order_by_id) do |tweets, word| - case word - when /^-?[a-z]+:.+$/ - # 特殊 - key, value = word.split(":", 2) - case key.downcase - when /^-?user$/ - user = User.where(screen_name: value).first - if key[0] == "-" - tweets.where("user_id != ?", user ? user.id : -1) + conv = -> s do + s.scan(/\S+(?: OR \S+)*/).map {|co| + co.split(" OR ").map {|token| + if /^\$(\d+)$/ =~ token + conv.call(groups[$1.to_i]) else - tweets.where(user_id: user ? user.id : -1) + parse_condition(token, strings) end - when /^-?fav/ - search_unless_zero(tweets, "favorites_count", key[0], value) - when /^-?re?t/ - search_unless_zero(tweets, "retweets_count", key[0], value) - when /^-?reactions?$/ - search_unless_zero(tweets, "favorites_count + retweets_count", key[0], value) - when "order" - case value - when "old", /^asc/ - tweets.order("id ASC") - when "reaction" - tweets.order_by_reactions - when /^fav/ - tweets.order_by_favorites - when /^re?t/ - tweets.order_by_retweets - else - tweets - end - when /^-?text$/ - sourcetext = word.split(":", 2).last.gsub("%", "\\%").gsub("*", "%") - sourcetext = "%#{sourcetext}%".gsub(/%+/, "%") - op = key[0] == "-" ? " NOT LIKE " : " LIKE " - tweets.where("text #{op} ?", sourcetext) - when /^-?source$/ - sourcetext = word.split(":", 2).last.gsub("%", "\\%").gsub("*", "%") - op = key[0] == "-" ? " NOT LIKE " : " LIKE " - tweets.where("source #{op} ? OR source #{op} ?", "<url:%:#{sourcetext.gsub(":", "%3A")}>", "<url:%:#{CGI.escape(sourcetext)}>") - else - # unknown command - tweets - end - when /^-?#{dateformat}\.\.#{dateformat}$/ - - since = Time.utc($1.to_i, $3.to_i, $4.to_i) - 9 * 60 * 60 - to = Time.utc($5.to_i, $7.to_i, $8.to_i + 1) - 9 * 60 * 60 - - if word[0] == "-" - tweets.where("id < ? OR id >= ?", first_id_of_time(since), first_id_of_time(to)) - else - tweets.where(id: first_id_of_time(since)...first_id_of_time(to)) - end - else - # TODO: ツイート検索 - tweets.none - end + }.inject(&:or) + }.inject(&:and) end - - @tweets = result.list(params, force_page: true) - render "tweets/_tweets" + conv.call(str) end - private - def first_id_of_time(time) - (time.to_i * 1000 - 1288834974657) << 22 - end - - def search_unless_zero(tweets, column, flag, value) - num = Integer(value) rescue 0 - n = flag == "-" + def parse_condition(token, strings) + tweets = Tweet.arel_table + escape_text = -> str do + str.gsub(/#(\d+)/) { strings[$1.to_i] } + .gsub("%", "\\%") + .gsub("*", "%") + .gsub("_", "\\_") + .gsub("?", "_") + end - unless num == 0 - tweets.where("#{column} #{n ? "<" : ">="} ?", num) + positive = token[0] != "-" + case token + when /^-?(?:user|from):([A-Za-z0-9_]{1,20})$/ + u = User.find_by(screen_name: $1) + uid = u && u.id || 0 + tweets[:user_id].__send__(positive ? :eq :not_eq, uid) + when /^-?date:(\d{4}(-?)\d{2}\2\d{2})(?:\.\.|-)(\d{4}\2\d{2}\2\d{2})$/ # $1: begin, $2: end + tweets[:id].__send__(positive ? :in : :not_in, date_to_id($1)...date_to_id($3, 1)) + when /^-?favs?:(\d+)$/ + tweets[:favorites_count].__send__(positive ? :gteq : :lt, $1.to_i) + when /^-?rts?:(\d+)$/ + tweets[:retweets_count].__send__(positive ? :gteq : :lt, $1.to_i) + when /^-?(?:sum|reactions?):(\d+)$/ + (tweets[:favorites_count] + tweets[:retweets_count]).__send__(positive ? :gteq : :lt, $1.to_i) + when /^(?:source|via):(.+)$/ + source_text = "<url:%:#{escape_text.call($1).gsub(":", "%3A")}>" + tweets[:source].__send__(positive ? :matches : :does_not_match, source_text) else - tweets + search_text = escape_text.call(positive ? token : token[1..-1]) + tweets[:text].__send__(positive ? :matches : :does_not_match, "%#{search_text}%") end end + + def date_to_id(str, offset = 0) + time = (Date.parse(str) + offset).to_datetime + (time.to_i * 1000 - 1288834974657) << 22 + end end diff --git a/app/views/layouts/_base.html.haml b/app/views/layouts/_base.html.haml index 7494f89..ea416f9 100644 --- a/app/views/layouts/_base.html.haml +++ b/app/views/layouts/_base.html.haml @@ -14,6 +14,8 @@ %ul.nav.pull-right %li = link_to "about", about_path + %li + = link_to "search", search_path - if logged_in? %li= link_to "settings", settings_path %li= link_to "logout", logout_path diff --git a/app/views/search/_search.html.haml b/app/views/search/_search.html.haml deleted file mode 100644 index 8c2b4e8..0000000 --- a/app/views/search/_search.html.haml +++ /dev/null @@ -1,4 +0,0 @@ -.search - = form_tag({}, {method: :get}) do - = text_field_tag :query, params[:query] - = submit_tag "search", name: nil diff --git a/app/views/search/search.html.haml b/app/views/search/search.html.haml new file mode 100644 index 0000000..6faf859 --- /dev/null +++ b/app/views/search/search.html.haml @@ -0,0 +1,7 @@ +.search + = form_tag({}, method: "get", class: "form-inline") do + = field_set_tag do + = text_field_tag :query, params[:query] + = submit_tag "search", class: "btn" += render partial: "tweets/tweets" + diff --git a/app/views/tweets/_tweets.html.haml b/app/views/tweets/_tweets.html.haml index 9e9bc0e..d0030b2 100644 --- a/app/views/tweets/_tweets.html.haml +++ b/app/views/tweets/_tweets.html.haml @@ -1,5 +1,3 @@ -- if params[:action] == "search" - = render partial: "search/search" .tweets = render partial: "tweets/tweet", collection: @tweets.includes(:user), as: :tweet .loading diff --git a/config/routes.rb b/config/routes.rb index 333b96c..f92e144 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -9,7 +9,7 @@ Aclog::Application.routes.draw do # HTML only pages scope format: "html" do - get "/search" => "search#search", as: "search" + get "/search" => "search#search", as: "search" # Internals / SessionsController get "/i/import/:id" => "i#import", as: "import" |