├── tmp └── restart.txt ├── test ├── test-unit.yml ├── test-query-parameter.rb ├── test-related-entries.rb ├── run-test.rb ├── test-open-search.rb ├── test-normalize-path.rb ├── test-pagination.rb └── rurema-search-test-utils.rb ├── production.yaml.example ├── public ├── favicon.ico ├── favicon.png ├── apple-touch-icon.png ├── images │ ├── class-icon.png │ ├── module-icon.png │ ├── query-icon.png │ ├── type-icon.png │ ├── document-icon.png │ ├── function-icon.png │ ├── library-icon.png │ ├── version-icon.png │ ├── all-items-icon.png │ ├── type-icon-medium.png │ ├── change-query-icon.png │ ├── class-icon-medium.png │ ├── module-icon-medium.png │ ├── query-icon-medium.png │ ├── document-icon-medium.png │ ├── drop-condition-icon.png │ ├── function-icon-medium.png │ ├── instance-method-icon.png │ ├── library-icon-medium.png │ ├── module-function-icon.png │ ├── rurema-search-title.png │ ├── leading-link-icon-medium.png │ ├── instance-method-icon-medium.png │ ├── module-function-icon-medium.png │ ├── leading-link-icon.svg │ ├── function-icon.svg │ ├── drop-condition-icon.svg │ ├── document-icon.svg │ ├── query-icon.svg │ ├── library-icon.svg │ └── rurema-search-title.svg ├── css │ ├── smoothness │ │ └── images │ │ │ ├── ui-icons_222222_256x240.png │ │ │ ├── ui-icons_2e83ff_256x240.png │ │ │ ├── ui-icons_454545_256x240.png │ │ │ ├── ui-icons_888888_256x240.png │ │ │ ├── ui-icons_cd0a0a_256x240.png │ │ │ ├── ui-bg_flat_0_aaaaaa_40x100.png │ │ │ ├── ui-bg_flat_75_ffffff_40x100.png │ │ │ ├── ui-bg_glass_55_fbf9ee_1x400.png │ │ │ ├── ui-bg_glass_65_ffffff_1x400.png │ │ │ ├── ui-bg_glass_75_dadada_1x400.png │ │ │ ├── ui-bg_glass_75_e6e6e6_1x400.png │ │ │ ├── ui-bg_glass_95_fef1ec_1x400.png │ │ │ └── ui-bg_highlight-soft_75_cccccc_1x100.png │ └── rurema.css └── robots.txt ├── document.yaml.example ├── TODO ├── smtp.yaml.example ├── lib ├── rurema_search.rb └── rurema_search │ ├── url_mapper.rb │ ├── groonga_suggest_database.rb │ ├── groonga_indexer.rb │ └── groonga_database.rb ├── .gitignore ├── views ├── error.html.erb ├── search_no_result.html.erb ├── search_header.html.erb ├── open_search_description.xml.erb ├── search.html.erb ├── index.html.erb ├── layout.html.erb └── search_result.html.erb ├── setup.sh ├── bin ├── rurema-search-clear-cache ├── rurema-search-bitclust ├── rurema-search-extract-suggest-data └── bitclust-indexer ├── Gemfile ├── README.rdoc ├── update.sh ├── config.ru └── license └── lgpl-3.0.txt /tmp/restart.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test-unit.yml: -------------------------------------------------------------------------------- 1 | collector: load 2 | -------------------------------------------------------------------------------- /production.yaml.example: -------------------------------------------------------------------------------- 1 | # use_log: 2 | # true 3 | # use_cache: 4 | # true 5 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear-code/rurema-search/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear-code/rurema-search/HEAD/public/favicon.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear-code/rurema-search/HEAD/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/images/class-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear-code/rurema-search/HEAD/public/images/class-icon.png -------------------------------------------------------------------------------- /public/images/module-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear-code/rurema-search/HEAD/public/images/module-icon.png -------------------------------------------------------------------------------- /public/images/query-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear-code/rurema-search/HEAD/public/images/query-icon.png -------------------------------------------------------------------------------- /public/images/type-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear-code/rurema-search/HEAD/public/images/type-icon.png -------------------------------------------------------------------------------- /document.yaml.example: -------------------------------------------------------------------------------- 1 | base_url: 2 | /static/ 3 | remove_dot_from_version: 4 | true 5 | tracking_id: 6 | UA-xxxx-1 7 | -------------------------------------------------------------------------------- /public/images/document-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear-code/rurema-search/HEAD/public/images/document-icon.png -------------------------------------------------------------------------------- /public/images/function-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear-code/rurema-search/HEAD/public/images/function-icon.png -------------------------------------------------------------------------------- /public/images/library-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear-code/rurema-search/HEAD/public/images/library-icon.png -------------------------------------------------------------------------------- /public/images/version-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear-code/rurema-search/HEAD/public/images/version-icon.png -------------------------------------------------------------------------------- /public/images/all-items-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear-code/rurema-search/HEAD/public/images/all-items-icon.png -------------------------------------------------------------------------------- /public/images/type-icon-medium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear-code/rurema-search/HEAD/public/images/type-icon-medium.png -------------------------------------------------------------------------------- /public/images/change-query-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear-code/rurema-search/HEAD/public/images/change-query-icon.png -------------------------------------------------------------------------------- /public/images/class-icon-medium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear-code/rurema-search/HEAD/public/images/class-icon-medium.png -------------------------------------------------------------------------------- /public/images/module-icon-medium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear-code/rurema-search/HEAD/public/images/module-icon-medium.png -------------------------------------------------------------------------------- /public/images/query-icon-medium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear-code/rurema-search/HEAD/public/images/query-icon-medium.png -------------------------------------------------------------------------------- /public/images/document-icon-medium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear-code/rurema-search/HEAD/public/images/document-icon-medium.png -------------------------------------------------------------------------------- /public/images/drop-condition-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear-code/rurema-search/HEAD/public/images/drop-condition-icon.png -------------------------------------------------------------------------------- /public/images/function-icon-medium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear-code/rurema-search/HEAD/public/images/function-icon-medium.png -------------------------------------------------------------------------------- /public/images/instance-method-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear-code/rurema-search/HEAD/public/images/instance-method-icon.png -------------------------------------------------------------------------------- /public/images/library-icon-medium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear-code/rurema-search/HEAD/public/images/library-icon-medium.png -------------------------------------------------------------------------------- /public/images/module-function-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear-code/rurema-search/HEAD/public/images/module-function-icon.png -------------------------------------------------------------------------------- /public/images/rurema-search-title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear-code/rurema-search/HEAD/public/images/rurema-search-title.png -------------------------------------------------------------------------------- /public/images/leading-link-icon-medium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear-code/rurema-search/HEAD/public/images/leading-link-icon-medium.png -------------------------------------------------------------------------------- /public/images/instance-method-icon-medium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear-code/rurema-search/HEAD/public/images/instance-method-icon-medium.png -------------------------------------------------------------------------------- /public/images/module-function-icon-medium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear-code/rurema-search/HEAD/public/images/module-function-icon-medium.png -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | * returns '400 Bad Request' instead of 500 for out of 'page' parameter. 2 | * returns '400 Bad Request' instead of 500 for invalid condition URL. 3 | -------------------------------------------------------------------------------- /public/css/smoothness/images/ui-icons_222222_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear-code/rurema-search/HEAD/public/css/smoothness/images/ui-icons_222222_256x240.png -------------------------------------------------------------------------------- /public/css/smoothness/images/ui-icons_2e83ff_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear-code/rurema-search/HEAD/public/css/smoothness/images/ui-icons_2e83ff_256x240.png -------------------------------------------------------------------------------- /public/css/smoothness/images/ui-icons_454545_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear-code/rurema-search/HEAD/public/css/smoothness/images/ui-icons_454545_256x240.png -------------------------------------------------------------------------------- /public/css/smoothness/images/ui-icons_888888_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear-code/rurema-search/HEAD/public/css/smoothness/images/ui-icons_888888_256x240.png -------------------------------------------------------------------------------- /public/css/smoothness/images/ui-icons_cd0a0a_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear-code/rurema-search/HEAD/public/css/smoothness/images/ui-icons_cd0a0a_256x240.png -------------------------------------------------------------------------------- /public/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear-code/rurema-search/HEAD/public/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png -------------------------------------------------------------------------------- /public/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear-code/rurema-search/HEAD/public/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png -------------------------------------------------------------------------------- /public/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear-code/rurema-search/HEAD/public/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png -------------------------------------------------------------------------------- /public/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear-code/rurema-search/HEAD/public/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png -------------------------------------------------------------------------------- /public/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear-code/rurema-search/HEAD/public/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png -------------------------------------------------------------------------------- /public/css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear-code/rurema-search/HEAD/public/css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png -------------------------------------------------------------------------------- /public/css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear-code/rurema-search/HEAD/public/css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png -------------------------------------------------------------------------------- /smtp.yaml.example: -------------------------------------------------------------------------------- 1 | host: 2 | 127.0.0.1 3 | from: 4 | rurema@example.com 5 | to: 6 | developer@example.com 7 | charset: 8 | iso-2022-jp 9 | subject_label: 10 | "[るりまサーチ]" 11 | -------------------------------------------------------------------------------- /public/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear-code/rurema-search/HEAD/public/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png -------------------------------------------------------------------------------- /lib/rurema_search.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Kouhei Sutou 2 | # 3 | # License: LGPLv3+ 4 | 5 | require 'bitclust' 6 | require 'rurema_search/url_mapper' 7 | require 'rurema_search/groonga_database' 8 | require 'rurema_search/groonga_indexer' 9 | require 'rurema_search/groonga_suggest_database' 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /db-* 2 | /groonga-database 3 | /public/1.* 4 | /public/2.* 5 | /public/3.* 6 | /test-unit 7 | /webrat.log 8 | /test/db-* 9 | /test/fixtures/ruby-refm-* 10 | /test/groonga-database 11 | /test/suggest-database 12 | /test/.test-result 13 | /test/fixtures/doctree 14 | /document.yaml 15 | /smtp.yaml 16 | /var/ 17 | /Gemfile.lock 18 | /.bundle/config 19 | -------------------------------------------------------------------------------- /views/error.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <% if client_error? %> 3 |

入力エラーです。

4 |

<%= h(@exception.message) %>

5 | <% else %> 6 |

ごめんなさい。エラーが発生しました。

7 |

エラーの内容は開発者に通知したのでしばらくしたら直っているかもしれません。

8 |

しばらくたっても直らない場合はruby-reference-manualメーリングリストで報告してください。

9 | <% end %> 10 |
11 | -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | base_dir=$(cd "$(dirname "$0")" && pwd) 4 | : ${RUBY:=ruby} 5 | 6 | run() 7 | { 8 | "$@" 9 | if test $? -ne 0; then 10 | echo "Failed $@" 11 | exit 1 12 | fi 13 | } 14 | 15 | set -x 16 | 17 | run cd ${base_dir}/.. 18 | run git clone https://github.com/rurema/bitclust.git bitclust 19 | run git clone https://github.com/rurema/doctree.git doctree 20 | 21 | run cd rurema-search 22 | 23 | run bundle install 24 | run bundle exec ./update.sh 25 | -------------------------------------------------------------------------------- /views/search_no_result.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

見つかりませんでした。検索条件を変えてみませんか?

3 | 16 |
17 | -------------------------------------------------------------------------------- /views/search_header.html.erb: -------------------------------------------------------------------------------- 1 |

<%= h1 %>

2 |
<%= h(catch_phrase) %>
3 |
4 |
5 | 8 | 9 |
10 | 15 |
16 | -------------------------------------------------------------------------------- /views/open_search_description.xml.erb: -------------------------------------------------------------------------------- 1 | 2 | 4 | <%= h(site_title) %> 5 | <%= h(site_description) %> 6 | <%= h(base_url + "favicon.png") %> 7 | UTF-8 8 | 9 | <%= h(version_url) %> 10 | 11 | -------------------------------------------------------------------------------- /bin/rurema-search-clear-cache: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # Copyright (c) 2010 Kouhei Sutou 4 | # 5 | # License: GPLv3+ 6 | 7 | require 'pathname' 8 | 9 | base_dir = Pathname.new(__FILE__).dirname.parent.cleanpath.realpath 10 | 11 | rroonga_dir = base_dir.parent + "rroonga" 12 | rroonga_lib_dir = rroonga_dir + "lib" 13 | rroonga_ext_dir = rroonga_dir + "ext" + "groonga" 14 | racknga_dir = base_dir.parent + "racknga" 15 | racknga_lib_dir = racknga_dir + "lib" 16 | 17 | $LOAD_PATH.unshift(rroonga_ext_dir.to_s) 18 | $LOAD_PATH.unshift(rroonga_lib_dir.to_s) 19 | $LOAD_PATH.unshift(racknga_lib_dir.to_s) 20 | 21 | require 'racknga' 22 | require 'racknga/cache_database' 23 | 24 | cache_database_path = base_dir + "var" + "cache" + "db" 25 | cache_database = Racknga::CacheDatabase.new(cache_database_path.to_s) 26 | cache_database.purge_old_responses 27 | 28 | # TODO: remove me. 29 | `groonga #{cache_database_path} defrag` 30 | -------------------------------------------------------------------------------- /bin/rurema-search-bitclust: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # Copyright (c) 2010 Kouhei Sutou 4 | # 5 | # License: GPLv3+ 6 | 7 | require "bitclust/runner" 8 | 9 | module BitClust 10 | class TemplateScreen 11 | undef search_form 12 | def search_form 13 | <<-FORM 14 | 20 | FORM 21 | end 22 | 23 | undef manual_home_link 24 | def manual_home_link 25 | label = _('Ruby %s Reference Manual', ruby_version) 26 | "#{label}" 27 | end 28 | end 29 | 30 | module Subcommands 31 | class StatichtmlCommand 32 | class URLMapperEx 33 | def opensearchdescription_url 34 | "/version:#{@target_version}/open_search_description.xml" 35 | end 36 | end 37 | end 38 | end 39 | end 40 | 41 | BitClust::Runner.new.run(ARGV) 42 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: Amazonbot 2 | Disallow: / 3 | 4 | User-agent: Baiduspider 5 | Disallow: / 6 | 7 | User-agent: bingbot 8 | Disallow: / 9 | 10 | User-agent: cotoba_bot 11 | Disallow: / 12 | 13 | User-agent: ICC-Crawler 14 | Disallow: / 15 | 16 | User-agent: ImageshiftBot 17 | Disallow: / 18 | 19 | User-agent: Applebot 20 | Disallow: / 21 | 22 | User-agent: SemrushBot 23 | Disallow: / 24 | 25 | User-agent: meta-externalagent 26 | Disallow: / 27 | 28 | User-agent: meta-webindexer 29 | Disallow: / 30 | 31 | User-agent: facebookexternalhit 32 | Disallow: / 33 | 34 | User-agent: PetalBot 35 | Disallow: / 36 | 37 | User-agent: DataForSeoBot 38 | Disallow: / 39 | 40 | User-agent: AwarioRssBot 41 | Disallow: / 42 | 43 | User-agent: AwarioSmartBot 44 | Disallow: / 45 | 46 | User-agent: ClaudeBot 47 | Disallow: / 48 | 49 | User-agent: Claude-SearchBot 50 | Disallow: / 51 | 52 | User-agent: GPTBot 53 | Disallow: / 54 | 55 | User-agent: MJ12bot 56 | Disallow: / 57 | 58 | User-agent: Bytespider 59 | Disallow: / 60 | 61 | User-agent: Googlebot 62 | Disallow: / 63 | 64 | User-agent: GoogleOther 65 | Disallow: / 66 | 67 | User-agent: * 68 | Crawl-delay: 600 69 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # -*- ruby -*- 2 | # 3 | # Copyright (C) 2011-2025 Sutou Kouhei 4 | # 5 | # This library is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU Lesser General Public 7 | # License version 2.1 as published by the Free Software Foundation. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library; if not, write to the Free Software 16 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | 18 | source "https://rubygems.org/" 19 | 20 | plugin "rubygems-requirements-system" 21 | 22 | gem "rroonga" 23 | gem "racknga" 24 | gem "bitclust-core", github: "rurema/bitclust" 25 | gem "bitclust-dev", github: "rurema/bitclust" 26 | 27 | group :development, :test do 28 | gem "test-unit" 29 | gem "test-unit-notify" 30 | gem "test-unit-capybara" 31 | gem "rake" 32 | end 33 | -------------------------------------------------------------------------------- /test/test-query-parameter.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (C) 2010 Kouhei Sutou 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | class QueryParameterTest < Test::Unit::TestCase 18 | include RuremaSearchTestUtils 19 | include ERB::Util 20 | 21 | def test_post_euc_jp 22 | page.driver.post("/", 23 | :query => "クラス変数".encode("euc-jp"), 24 | :encoding => "euc-jp") 25 | assert_equal("#{host}/query:#{u('クラス変数')}/", current_url) 26 | end 27 | 28 | def test_get 29 | visit "/?query=#{u("クラス変数")}" 30 | assert_equal("#{host}/query:#{u('クラス変数')}/", current_url) 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /test/test-related-entries.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2010 Kouhei Sutou 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | class RelatedEnetriesTest < Test::Unit::TestCase 17 | include RuremaSearchTestUtils 18 | 19 | def test_not_show_drilldowned_entry_link 20 | visit "/class:File/" 21 | assert_equal([], related_entry_links("/class:File/")) 22 | end 23 | 24 | def test_remove_same_type_drilldown 25 | visit "/query:File.lstat/" 26 | links = related_entry_links("/query:File.stat/") 27 | assert_equal(["File.stat"], 28 | links.collect {|link| link.text}.uniq) 29 | end 30 | 31 | private 32 | def related_entry_links(href=nil) 33 | links = page.all(:xpath, "//ul[@class='entry-related-entries']/li/a") 34 | return links if href.nil? 35 | links.find_all do |link| 36 | link["href"] == href 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /test/run-test.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # Copyright (C) 2010-2013 Kouhei Sutou 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | $VERBOSE = true 19 | 20 | $KCODE = "u" if RUBY_VERSION < "1.9" 21 | 22 | base_dir = File.expand_path(File.join(File.dirname(__FILE__), "..")) 23 | bitclust_dir = File.expand_path(File.join(base_dir, "..", "bitclust")) 24 | bitclust_lib_dir = File.join(bitclust_dir, "lib") 25 | lib_dir = File.join(base_dir, "lib") 26 | test_dir = File.join(base_dir, "test") 27 | 28 | require "test-unit" 29 | require "test/unit/notify" 30 | 31 | ARGV.unshift("--priority-mode") 32 | ARGV.unshift(File.join(test_dir, "test-unit.yml")) 33 | ARGV.unshift("--config") 34 | 35 | $LOAD_PATH.unshift(bitclust_lib_dir) 36 | 37 | $LOAD_PATH.unshift(lib_dir) 38 | 39 | $LOAD_PATH.unshift(test_dir) 40 | require "rurema-search-test-utils" 41 | 42 | exit Test::Unit::AutoRunner.run(true, test_dir) 43 | -------------------------------------------------------------------------------- /views/search.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
    4 | <% @version_names.each_with_index do |_version, i| %> 5 |
  • 6 | <%= link_version_select(_version, 7 | :label_suffix => h("(#{@version_n_entries[i]})")) %> 8 |
  • 9 | <% end %> 10 |
11 |
12 | 13 |
14 | <%= h("#{@entries.n_records}件ヒット") %> 15 | <% unless @entries.n_records.zero? %> 16 | 17 | <%= h("[#{@entries.start_offset}-#{@entries.end_offset}件を表示]") %> 18 | 19 | <% end %> 20 | <%= h("(%.3f秒)" % @elapsed_time) %> 21 |
22 | 23 |
24 | <%= topic_path %> 25 |
26 |
27 | 28 | <% unless @corrections.empty? %> 29 |
30 |

別のキーワード

31 |
    32 | <% @corrections.each do |item| %> 33 |
  1. <%= link_related_entry(:key => item[:key], :type => "query") %>
  2. 34 | <% end %> 35 |
36 |
37 | <% end %> 38 | 39 | <% unless @suggestions.empty? %> 40 |
41 |

関連するキーワード

42 |
    43 | <% @suggestions.each do |item| %> 44 |
  1. <%= link_drilldown_entry("query", :label => item[:key]) %>
  2. 45 | <% end %> 46 |
47 |
48 | <% end %> 49 | 50 |
51 | <% if @entries.n_records.zero? %> 52 | <%= search_no_result %> 53 | <% else %> 54 | <%= search_result %> 55 | <% end %> 56 |
57 | -------------------------------------------------------------------------------- /test/test-open-search.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (C) 2010 Kouhei Sutou 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | class OpenSearchTest < Test::Unit::TestCase 18 | include RuremaSearchTestUtils 19 | include ERB::Util 20 | 21 | def test_top_level_open_search_description 22 | visit "/open_search_description.xml" 23 | assert_open_search_description("#{host}/") 24 | end 25 | 26 | def test_versioned_open_search_description 27 | visit "/version:1.8.8/open_search_description.xml" 28 | assert_open_search_description("#{host}/version:1.8.8/") 29 | end 30 | 31 | private 32 | def assert_open_search_description(expected_template) 33 | content_type = page.response_headers["Content-Type"] 34 | assert_equal("application/opensearchdescription+xml", 35 | content_type) 36 | xml = Nokogiri::XML(page.source) 37 | url = xml.xpath("//node()[name()='Url']")[0] 38 | assert_equal("#{expected_template}?query={searchTerms}", url["template"]) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/rurema_search/url_mapper.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Kouhei Sutou 2 | # 3 | # License: LGPLv3+ 4 | 5 | module RuremaSearch 6 | class URLMapper < BitClust::URLMapper 7 | def initialize(options) 8 | super 9 | @version = options[:version] 10 | end 11 | 12 | def base_url 13 | "#{@base_url}#{@version}/" 14 | end 15 | 16 | def css_url 17 | "#{base_url}style.css" 18 | end 19 | 20 | def custom_css_url(css) 21 | "#{base_url}#{css}" 22 | end 23 | 24 | def js_url 25 | "#{base_url}t.js" 26 | end 27 | 28 | def custom_js_url(js) 29 | "#{base_url}#{js}" 30 | end 31 | 32 | def favicon_url 33 | "#{base_url}rurema.png" 34 | end 35 | 36 | def library_index_url 37 | "#{base_url}library/" 38 | end 39 | 40 | def library_url(name) 41 | if name == "/" 42 | library_index_url 43 | else 44 | "#{base_url}library/#{encodename_url(name)}.html" 45 | end 46 | end 47 | 48 | def class_url(name) 49 | "#{base_url}class/#{encodename_url(name)}.html" 50 | end 51 | 52 | def method_url(spec) 53 | cname, tmark, mname = *split_method_spec(spec) 54 | "#{base_url}method/#{encodename_url(cname)}/#{typemark2char(tmark)}/#{encodename_url(mname)}.html" 55 | end 56 | 57 | def function_index_url 58 | "#{base_url}function/" 59 | end 60 | 61 | def function_url(name) 62 | "#{base_url}function/#{encodename_url(name)}.html" 63 | end 64 | 65 | def opensearchdescription_url 66 | "#{base_url}open_search_description.xml" 67 | end 68 | 69 | def spec_url(name) 70 | "#{base_url}spec/#{name}.html" 71 | end 72 | 73 | def document_url(name) 74 | "#{base_url}doc/#{encodename_url(name)}.html" 75 | end 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /views/index.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

<%= h1 %>

3 | 4 | 5 | 6 | <% @version_names.each do |name| %> 7 | 10 | <% end %> 11 | 12 | 13 | <% @version_n_entries.each do |n_entries| %> 14 | 17 | <% end %> 18 | 19 |
8 | <%= link_version_select(name) %> 9 |
15 | <%= h(n_entries) %>件 16 |
20 | 21 |

<%= h(catch_phrase) %>

22 |
23 |

24 | 27 |

28 |

29 | 30 |

31 |
32 | 37 |
38 | 39 |
40 | <% @built_in_object_drilldown_items.each do |item| %> 41 |
42 |

<%= h(item[:label]) %>

43 |
44 | <% item[:objects].each do |(label, objects)| %> 45 |
<%= h(label) %>
46 |
47 |
    48 | <% objects.each_with_index do |object, i| %> 49 |
  1. 50 | <%= link_drilldown_entry(item[:type], :label => object["_key"]) %> 51 | (<%= h(object.n_sub_records) %>) 52 |
  2. 53 | <% end %> 54 |
55 |
56 |
57 | <% end %> 58 |
59 |
60 | <% end %> 61 |
62 | -------------------------------------------------------------------------------- /test/test-normalize-path.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (C) 2010 Kouhei Sutou 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | class NormalizePathTest < Test::Unit::TestCase 18 | include RuremaSearchTestUtils 19 | include ERB::Util 20 | 21 | def test_root 22 | assert_normalize("/", "/") 23 | end 24 | 25 | def test_no_slash 26 | assert_normalize("/version:1.9.2/", "/version:1.9.2/") 27 | end 28 | 29 | def test_slash 30 | assert_normalize("/library:webrick%2Fserver/", 31 | "/library:webrick/server/") 32 | end 33 | 34 | def test_mixed 35 | assert_normalize("/version:1.9.2" + 36 | "/singleton-method:#{u('WEBrick::GenericServer.new')}" + 37 | "/type:singleton-method" + 38 | "/library:#{u('webrick/server')}/", 39 | "/version:1.9.2" + 40 | "/singleton-method:WEBrick::GenericServer.new" + 41 | "/type:singleton-method" + 42 | "/library:webrick/server/") 43 | end 44 | 45 | def test_file 46 | assert_normalize("/open_search_description.xml", 47 | "/open_search_description.xml") 48 | end 49 | 50 | def test_versioned_file 51 | assert_normalize("/version:1.9.2/open_search_description.xml", 52 | "/version:1.9.2/open_search_description.xml") 53 | end 54 | 55 | private 56 | def assert_normalize(expected, path) 57 | assert_equal(expected, app.send(:normalize_path, path)) 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /views/layout.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | <%= google_tag_manager %> 7 | 8 | <%= h(title) %> 9 | <% @version_names.each do |version_name| %> 10 | 14 | <% end %> 15 | 17 | 20 | 21 | 23 | 25 | 26 | 27 |
28 | <%= header %> 29 |
30 |
31 | <%= body %> 32 |
33 | 61 | <%= analyze %> 62 | 63 | 64 | -------------------------------------------------------------------------------- /test/test-pagination.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2010 Kouhei Sutou 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | class PaginateTest < Test::Unit::TestCase 17 | include RuremaSearchTestUtils 18 | 19 | def test_get 20 | visit "/" 21 | assert_paginate(nil) 22 | end 23 | 24 | def test_no_paginate 25 | visit "/type:object/" 26 | assert_paginate(nil) 27 | end 28 | 29 | def test_two_pages 30 | visit "/type:instance-method/class:Object/" 31 | assert_paginate([["paginate-text", nil, "<<"], 32 | ["paginate-current", nil, "1"], 33 | ["paginate-link", "?page=2", "2"], 34 | ["paginate-link", "?page=3", "3"], 35 | ["paginate-link", "?page=2", ">"], 36 | ["paginate-link", "?page=3", ">>"]]) 37 | end 38 | 39 | def test_border_hits 40 | n_entries = 10 41 | visit "/class:ARGF.class/?n_entries=#{n_entries}" 42 | assert_equal(1, page.first("span.total").text.to_i % n_entries) 43 | assert_paginate([["paginate-text", nil, "<<"], 44 | ["paginate-current", nil, "1"], 45 | ["paginate-link", "?page=2;n_entries=#{n_entries}", "2"], 46 | ["paginate-link", "?page=3;n_entries=#{n_entries}", "3"], 47 | ["paginate-text", nil, "..."], 48 | ["paginate-link", "?page=2;n_entries=#{n_entries}", ">"], 49 | ["paginate-link", "?page=11;n_entries=#{n_entries}", ">>"]]) 50 | end 51 | 52 | private 53 | def assert_paginate(expected) 54 | actual = nil 55 | # There are 2 paginate div on top and bottom 56 | paginate = page.all(:xpath, "//div[@class='paginate']") 57 | unless paginate.empty? 58 | actual = paginate.first.all(:css, "span").collect do |node| 59 | a = node.all(:css, "a") 60 | if a.empty? 61 | [node["class"], nil, node.text] 62 | else 63 | [node["class"], a.first["href"], a.first.text] 64 | end 65 | end 66 | end 67 | assert_equal(expected, actual) 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = はじめに 2 | 3 | == 名前 4 | 5 | るりまサーチ 6 | 7 | == 説明 8 | 9 | るりまを全文検索するWebアプリケーションです。 10 | 11 | TODO: もっとちゃんと書く。 12 | 13 | == 作者 14 | 15 | Kouhei Sutou:: 16 | 17 | == ライセンス 18 | 19 | LGPL 3またはそれ以降のバージョンです。詳しくは 20 | license/lgpl-3.0.txtを見てください。 21 | 22 | == 依存ソフトウェア 23 | 24 | * Ruby 1.9.1 25 | * Rack 26 | * BitClust 27 | * groonga 28 | * rroonga 29 | * racknga 30 | 31 | == インストール 32 | 33 | === 個人利用の場合 34 | 35 | ==== Debian GNU/Linux 36 | 37 | % mkdir rurema 38 | % cd rurema 39 | % git clone git://github.com/clear-code/rurema-search.git 40 | % cd rurema-search 41 | % ./setup.sh 42 | % ruby1.9.1 rackup 43 | % www-browser http://localhost:9292/ 44 | 45 | ==== Mac OS X + rbenv 46 | 47 | % mkdir rurema 48 | % cd rurema 49 | % git clone git://github.com/clear-code/rurema-search.git 50 | % cd rurema-search 51 | % rbenv local 2.1.2 52 | % bundle 53 | % RUBY=`which ruby` ./setup.sh 54 | % rackup > /dev/null 2>&1 & 55 | % open http://localhost:9292/ 56 | 57 | ==== WSL Ubuntu-20.04(Windows) + rbenv 58 | 59 | First, install Groonga in advance. 60 | Groonga installation is easy and convenient to {install from Ubuntu PPA}[https://groonga.org/ja/docs/install/ubuntu.html#ubuntu]. 61 | 62 | Depending on your environment, you may need to install additional `zsh` package. 63 | 64 | $ sudo apt install zsh 65 | 66 | Enter the following command lines in the terminal of WSL: 67 | 68 | $ mkdir rurema 69 | $ cd rurema 70 | $ git clone https://github.com/rurema/doctree.git 71 | $ git clone https://github.com/rurema/bitclust.git 72 | $ git clone https://github.com/ranguba/racknga.git 73 | $ git clone https://github.com/clear-code/rurema-search.git 74 | $ rbenv local 2.6.6 75 | $ cd rurema-search 76 | $ gem install groonga 77 | $ gem install bitclust-core bitclust-dev refe2 78 | $ bundle install 79 | $ ./update.sh 80 | $ rackup 81 | 82 | Finally, open http://localhost:9292/ by your Web browser. 83 | 84 | 85 | === サーバ利用の場合 86 | 87 | ==== Debian GNU/Linux wheezy 88 | 89 | インストーラをダウンロードします。 90 | 91 | % wget https://raw.githubusercontent.com/clear-code/rurema-search/master/setup-wheezy.sh 92 | 93 | 以下のようにroot権限でインストーラを実行します。(実行前に中身を確認して実行しても問題ないことを確認すること。) 94 | 95 | % sudo sh setup-wheezy.sh ホスト名 96 | 97 | 「ホスト名」にはサーバのホスト名を指定します。例えば、http://rurema.example.org/でアクセスしたい場合は以下のようにします。 98 | 99 | % sudo sh setup-wheezy.sh rurema.example.org 100 | 101 | これで「rurema」ユーザが作られ、「~rurema/rurema-search/」以下にるりまサーチがセットアップされます。cronも設定するので毎日定期的に最新版にアップデートされます。 102 | 103 | == ドキュメント 104 | 105 | TODO 106 | 107 | == メーリングリスト 108 | 109 | 質問、要望、バグ報告などはるりまのMLにお願いします。 110 | 111 | https://bugs.ruby-lang.org/projects/rurema/wiki/MailingList 112 | 113 | == 感謝 114 | 115 | * okkezさん: バグを報告してくれました。 116 | * sora_hさん: 検索結果のソート順について意見をくれました。 117 | -------------------------------------------------------------------------------- /views/search_result.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <% @drilldown_items.each do |item| %> 3 |
4 |

5 | <%= h(item[:label] || parameter_label(item[:key])) %> 6 |

7 |
    8 | <% item[:entries].each do |entry| %> 9 |
  • 10 | <%= link_drilldown_entry(item[:key], entry) %> 11 | (<%= h(entry[:n_records]) %>) 12 |
  • 13 | <% end %> 14 |
15 |
16 | <% end %> 17 |
18 | 19 |
20 |

検索結果

21 | 31 | 32 | <%= paginate %> 33 | 34 |
35 | <% @grouped_entries.each_with_index do |(represent_entry, entries), i| %> 36 |
37 |

38 | 39 | <%= link_entry_if(entries.size == 1, represent_entry) %> 40 | 41 | (<%= h(represent_entry.score) %>) 42 |

43 |
44 |
45 |
    46 | <% entries.each do |entry| %> 47 |
  • 48 | <%= link_entry(entry, :label => h(entry.version.key)) %> 49 |
  • 50 | <% end %> 51 |
52 | 59 |
60 |

<%= h(remove_markup(represent_entry.summary)) %>

61 |
62 |
63 | <% grouped_entries_description_snippets(entries).each do |snippet| %> 64 |
65 | <%= snippet %> 66 |
67 | <% end %> 68 |
69 |
    70 | <% collect_related_entries(entries).each_with_index do |related_entry, j| %> 71 | <% if j < 20 %> 72 |
  • 73 | <%= link_related_entry(related_entry) %> 74 |
  • 75 | <% else %> 76 |
  • ...
  • 77 | <% break %> 78 | <% end %> 79 | <% end %> 80 |
81 | <% if ((i + 1) % 5).zero? %> 82 |

絞り込み条件を変える

83 | <% end %> 84 |
85 | <% end %> 86 |
87 | <%= paginate %> 88 |
89 | -------------------------------------------------------------------------------- /update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | base_dir=$(cd "$(dirname "$0")" && pwd) 4 | : ${RUBY:=ruby} 5 | bitclust_dir=$($RUBY -rrubygems -e 'print Gem::Specification.find_by_name("bitclust-core").gem_dir') 6 | doctree_dir=${base_dir}/../doctree 7 | 8 | PATH=${base_dir}/local/bin:$PATH 9 | 10 | update_rurema=yes 11 | update_index=yes 12 | reset_index=no 13 | reset_suggest=no 14 | load_data=yes 15 | clear_cache=yes 16 | for argument in $*; do 17 | case "$argument" in 18 | "--no-update-rurema") 19 | update_rurema=no 20 | ;; 21 | "--no-update-index") 22 | update_index=no 23 | ;; 24 | "--reset-index") 25 | reset_index=yes 26 | ;; 27 | "--reset-suggest") 28 | reset_suggest=yes 29 | ;; 30 | "--no-load-data") 31 | load_data=no 32 | ;; 33 | "--no-clear-cache") 34 | clear_cache=no 35 | ;; 36 | esac 37 | done 38 | 39 | update_rurema() 40 | { 41 | local version=$1 42 | 43 | nice ${RUBY} \ 44 | -I ${base_dir}/lib \ 45 | -I ${bitclust_dir}/lib \ 46 | ${base_dir}/bin/rurema-search-bitclust \ 47 | --database ${base_dir}/db-${version} \ 48 | init encoding=utf-8 version=${version} 49 | nice ${RUBY} \ 50 | -I ${base_dir}/lib \ 51 | -I ${bitclust_dir}/lib \ 52 | ${base_dir}/bin/rurema-search-bitclust \ 53 | --database ${base_dir}/db-${version} \ 54 | update --stdlibtree ${doctree_dir}/refm/api/src 55 | nice ${RUBY} \ 56 | -I ${base_dir}/lib \ 57 | -I ${bitclust_dir}/lib \ 58 | ${base_dir}/bin/rurema-search-bitclust \ 59 | --database ${base_dir}/db-${version} \ 60 | --capi \ 61 | update ${doctree_dir}/refm/capi/src/**/*.rd 62 | rm -rf ${base_dir}/public/${version}.{old,new} 63 | nice ${RUBY} \ 64 | -I ${base_dir}/lib \ 65 | -I ${bitclust_dir}/lib \ 66 | ${base_dir}/bin/rurema-search-bitclust \ 67 | --database ${base_dir}/db-${version} \ 68 | statichtml \ 69 | --quiet \ 70 | --no-stop-on-syntax-error \ 71 | --fs-casesensitive \ 72 | --outputdir ${base_dir}/public/${version}.new \ 73 | --catalog ${bitclust_dir}/data/bitclust/catalog \ 74 | --template ${bitclust_dir}/data/bitclust/template 75 | if [ $? -eq 0 ]; then 76 | mv ${base_dir}/public/${version}{,.old} 77 | mv ${base_dir}/public/${version}{.new,} 78 | rm -rf ${base_dir}/public/${version}.old 79 | fi 80 | } 81 | 82 | if [ "$update_rurema" = "yes" ]; then 83 | if [ -d ${doctree_dir} ]; then 84 | (cd ${doctree_dir} && git pull --rebase) 85 | else 86 | git clone https://github.com/rurema/doctree.git ${doctree_dir} 87 | fi 88 | 89 | for version in 2.1.0 2.2.0 2.3.0 2.4.0 2.5.0 2.6.0 2.7.0 3.0 3.1 3.2 3.3 3.4; do 90 | update_rurema $version 91 | done 92 | wait 93 | fi 94 | 95 | if [ "$update_index" = "yes" ]; then 96 | reset_argument= 97 | load_data_argument= 98 | if [ "$reset_index" = "yes" ]; then 99 | rm -rf ${base_dir}/groonga-database 100 | rm -rf ${base_dir}/var/lib/suggest 101 | touch ${base_dir}/tmp/restart.txt 102 | reset_argument="--reset" 103 | fi 104 | if [ "$reset_suggest" = "yes" ]; then 105 | rm -rf ${base_dir}/var/lib/suggest 106 | touch ${base_dir}/tmp/restart.txt 107 | fi 108 | if [ "$load_data" != "yes" ]; then 109 | load_data_argument="--no-load-data" 110 | fi 111 | nice ${RUBY} \ 112 | ${base_dir}/bin/bitclust-indexer \ 113 | ${reset_argument} \ 114 | ${load_data_argument} \ 115 | ${base_dir}/db-* 116 | fi 117 | 118 | if [ "$clear_cache" = "yes" ]; then 119 | nice ${RUBY} ${base_dir}/bin/rurema-search-clear-cache 120 | fi 121 | 122 | touch ${base_dir}/tmp/restart.txt 123 | -------------------------------------------------------------------------------- /bin/rurema-search-extract-suggest-data: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # Copyright (c) 2011 Kouhei Sutou 4 | # 5 | # License: GPLv3+ 6 | 7 | require 'pathname' 8 | 9 | base_dir = Pathname.new(__FILE__).dirname.parent.cleanpath.realpath 10 | lib_dir = base_dir + "lib" 11 | 12 | bitclust_dir = base_dir.parent + "bitclust" 13 | bitclust_lib_dir = bitclust_dir + "lib" 14 | rroonga_dir = base_dir.parent + "rroonga" 15 | rroonga_lib_dir = rroonga_dir + "lib" 16 | rroonga_ext_dir = rroonga_dir + "ext" + "groonga" 17 | racknga_dir = base_dir.parent + "racknga" 18 | racknga_lib_dir = racknga_dir + "lib" 19 | 20 | $LOAD_PATH.unshift(bitclust_lib_dir.to_s) 21 | $LOAD_PATH.unshift(rroonga_ext_dir.to_s) 22 | $LOAD_PATH.unshift(rroonga_lib_dir.to_s) 23 | $LOAD_PATH.unshift(racknga_lib_dir.to_s) 24 | $LOAD_PATH.unshift(lib_dir.to_s) 25 | 26 | require "json" 27 | require "cgi" 28 | require "optparse" 29 | require "racknga" 30 | require "rurema_search" 31 | require "digest" 32 | begin 33 | require "MeCab" 34 | rescue LoadError 35 | end 36 | 37 | @hide_individual_data = true 38 | 39 | option_parser = OptionParser.new 40 | option_parser.on("--[no-]hide-individual-data", 41 | "Hide individual data for each request", 42 | "(#{@hide_individual_data})") do |boolean| 43 | @hide_individual_data = boolean 44 | end 45 | option_parser.parse! 46 | 47 | def generate_id(entry) 48 | id = [entry.remote_address, entry.http_user_agent].join(" ") 49 | if @hide_individual_data 50 | id = Digest::SHA1.hexdigest(id).force_encoding("UTF-8") 51 | end 52 | id 53 | end 54 | 55 | def print_events(id, entry, keyword, events) 56 | return if events.nil? 57 | events << {:item => keyword, 58 | :time => entry.time_local, 59 | :type => "submit"} 60 | events.each do |event| 61 | puts(",") 62 | print(JSON.generate([id, event[:time].to_f, event[:item], event[:type]])) 63 | end 64 | end 65 | 66 | if defined?(MeCab::Tagger) 67 | @tagger = MeCab::Tagger.new("-Oyomi") 68 | def katakana(string) 69 | @tagger.parse(string).force_encoding("UTF-8").strip 70 | end 71 | else 72 | def katakana(string) 73 | NKF.nkf("-w -katakana", string) 74 | end 75 | end 76 | 77 | dataset = RuremaSearch::GroongaSuggestDatabase::DATASET 78 | 79 | puts("load " + 80 | "--table event_#{dataset} " + 81 | "--each 'suggest_preparer(_id, type, item, sequence, time, pair_#{dataset})'") 82 | puts("[") 83 | print("[\"sequence\", \"time\", \"item\", \"type\"]") 84 | parser = Racknga::AccessLogParser.new(ARGF.each_line) 85 | events = {} 86 | posts = {} 87 | keywords = [] 88 | begin 89 | parser.each do |entry| 90 | id = generate_id(entry) 91 | case entry.request 92 | when /\AGET \/api:internal\/auto-complete\/\?term=(.+) HTTP\/1\.\d\z/ 93 | input = $1 94 | events[id] ||= [] 95 | events[id] << {:item => CGI.unescape(input), 96 | :time => entry.time_local} 97 | when /\APOST (.+) HTTP\/1\.\d\z/ 98 | posts[id] = true 99 | when /\AGET (.+) HTTP\/1\.\d\z/ 100 | path = $1 101 | next unless posts[id] 102 | posts[id] = false 103 | if entry.status == 200 104 | queries = [] 105 | path.split(/\//).each do |component| 106 | key, value = component.split(/:/, 2) 107 | queries << CGI.unescape(value) if key == "query" 108 | end 109 | unless queries.empty? 110 | print_events(id, entry, queries.join(" "), events[id]) 111 | keywords.concat(queries) 112 | end 113 | end 114 | events[id].clear if events[id] 115 | end 116 | end 117 | rescue Racknga::AccessLogParser::FormatError 118 | puts $! 119 | retry 120 | end 121 | puts 122 | puts("]") 123 | 124 | puts 125 | 126 | puts("load --table item_#{dataset}") 127 | puts("[") 128 | print("[\"_key\", \"kana\"]") 129 | keywords.uniq.each do |keyword| 130 | puts(",") 131 | if keyword.ascii_only? 132 | keyword_in_katakana = keyword 133 | else 134 | keyword_in_katakana = katakana(keyword) 135 | end 136 | print(JSON.generate([keyword, keyword_in_katakana])) 137 | end 138 | puts 139 | puts("]") 140 | -------------------------------------------------------------------------------- /bin/bitclust-indexer: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # Copyright (c) 2010 Kouhei Sutou 4 | # 5 | # License: GPLv3+ 6 | 7 | require 'pathname' 8 | 9 | base_dir = Pathname.new(__FILE__).dirname.parent.cleanpath.realpath 10 | lib_dir = base_dir + "lib" 11 | 12 | bitclust_dir = base_dir.parent + "bitclust" 13 | bitclust_lib_dir = bitclust_dir + "lib" 14 | rroonga_dir = base_dir.parent + "rroonga" 15 | rroonga_lib_dir = rroonga_dir + "lib" 16 | rroonga_ext_dir = rroonga_dir + "ext" + "groonga" 17 | 18 | $LOAD_PATH.unshift(bitclust_lib_dir.to_s) 19 | $LOAD_PATH.unshift(rroonga_ext_dir.to_s) 20 | $LOAD_PATH.unshift(rroonga_lib_dir.to_s) 21 | $LOAD_PATH.unshift(lib_dir.to_s) 22 | 23 | require 'rurema_search' 24 | require 'pp' 25 | require 'optparse' 26 | 27 | def load_bitclust_data(database, bitclust_database_paths) 28 | base_time = Time.now 29 | bitclust_database_paths.each do |bitclust_database_path| 30 | method_database = BitClust::MethodDatabase.new(bitclust_database_path) 31 | function_database = BitClust::FunctionDatabase.new(bitclust_database_path) 32 | indexer = RuremaSearch::GroongaIndexer.new(database, 33 | method_database, 34 | function_database) 35 | indexer.base_time = base_time 36 | indexer.index 37 | end 38 | database.purge_old_records(base_time) 39 | end 40 | 41 | def load_suggest_data(database, suggest_database) 42 | entries = database.entries 43 | entries.each do |entry| 44 | local_name = entry.local_name 45 | next if local_name.nil? 46 | 47 | related_names = [] 48 | ["class", "module", "object", "library"].each do |column_name| 49 | related_names << entry[column_name].key if entry[column_name] 50 | end 51 | entry.related_names.each do |name| 52 | next if name.nil? 53 | name = name.key 54 | related_names << name 55 | splited_names = name.split(/(?:::|.|#|.#|\$|\/)/) 56 | related_names.concat(splited_names) if splited_names.size > 1 57 | end 58 | 59 | type = entry.type.key 60 | case type 61 | when "class", "module", "object", "library" 62 | related_entries = entries.select do |record| 63 | record[type] =~ entry["name._key"] 64 | end 65 | related_entries.each do |related_entry| 66 | related_names << related_entry.local_name.key 67 | end 68 | end 69 | suggest_database.register_keyword(local_name.key, related_names) 70 | end 71 | suggest_database.truncate_temporary_records 72 | end 73 | 74 | def update_suggest_database(database, suggest_database_path, reset, load_data) 75 | suggest_database = RuremaSearch::GroongaSuggestDatabase.new 76 | suggest_database.open(suggest_database_path) do 77 | suggest_database.purge if reset 78 | load_suggest_data(database, suggest_database) if load_data 79 | end 80 | end 81 | 82 | def main(database_path, suggest_database_path) 83 | reset = false 84 | load_data = true 85 | parser = OptionParser.new 86 | parser.banner += " bitclust-database1,..." 87 | parser.on('-d', '--database=PATH', 88 | 'groonga database path.', 89 | "(#{database_path})") do |path| 90 | database_path = path 91 | end 92 | parser.on('--suggest-database=PATH', 93 | 'groonga suggest database path.', 94 | "(#{suggest_database_path})") do |path| 95 | suggest_database_path = path 96 | end 97 | parser.on('--[no-]reset', 98 | 'reset groonga database before indexing.', 99 | "(#{reset})") do |boolean| 100 | reset = boolean 101 | end 102 | parser.on('--[no-]load-data', 103 | 'load data after creating database.', 104 | "(#{load_data})") do |boolean| 105 | load_data = boolean 106 | end 107 | parser.on('--help', 'Prints this message and quit.') do 108 | puts(parser) 109 | exit(true) 110 | end 111 | 112 | bitclust_database_paths = parser.parse! 113 | database = RuremaSearch::GroongaDatabase.new 114 | database.open(database_path, "utf-8") do 115 | database.purge if reset 116 | load_bitclust_data(database, bitclust_database_paths) if load_data 117 | update_suggest_database(database, suggest_database_path, reset, load_data) 118 | end 119 | end 120 | 121 | main((base_dir + "groonga-database").to_s, 122 | (base_dir + "var" + "lib" + "suggest").to_s) 123 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # -*- ruby -*- 2 | # 3 | # Copyright (C) 2010-2016 Kouhei Sutou 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | Encoding.default_external = "utf-8" 19 | 20 | require "pathname" 21 | 22 | base_dir = Pathname.new(__FILE__).dirname.cleanpath.realpath 23 | lib_dir = base_dir + "lib" 24 | 25 | bitclust_dir = base_dir.parent + "bitclust" 26 | bitclust_lib_dir = bitclust_dir + "lib" 27 | rroonga_dir = base_dir.parent + "rroonga" 28 | rroonga_lib_dir = rroonga_dir + "lib" 29 | rroonga_ext_dir = rroonga_dir + "ext" + "groonga" 30 | racknga_dir = base_dir.parent + "racknga" 31 | racknga_lib_dir = racknga_dir + "lib" 32 | 33 | $LOAD_PATH.unshift(bitclust_lib_dir.to_s) 34 | $LOAD_PATH.unshift(rroonga_ext_dir.to_s) 35 | $LOAD_PATH.unshift(rroonga_lib_dir.to_s) 36 | $LOAD_PATH.unshift(racknga_lib_dir.to_s) 37 | $LOAD_PATH.unshift(lib_dir.to_s) 38 | 39 | require "groonga" 40 | 41 | keep_n_latest_logs = 10 42 | log_dir = base_dir + "var" + "log" 43 | log_dir.mkpath 44 | logs = Pathname.glob(log_dir + "groonga.log.*") 45 | logs.sort[0..(-keep_n_latest_logs + -1)].each do |old_log| 46 | old_log.delete 47 | end 48 | Groonga::Logger.path = (log_dir + "groonga.log").to_s 49 | Groonga::Logger.rotate_threshold_size = 1 * 1024 * 1024 50 | 51 | require "racknga" 52 | require "racknga/middleware/log" 53 | require "racknga/middleware/cache" 54 | 55 | require "rurema_search" 56 | require "rurema_search/groonga_searcher" 57 | 58 | database = RuremaSearch::GroongaDatabase.new 59 | database.open((base_dir + "groonga-database").to_s, "utf-8") 60 | 61 | suggest_database = RuremaSearch::GroongaSuggestDatabase.new 62 | suggest_database.open((base_dir + "var" + "lib" + "suggest").to_s) 63 | 64 | if defined?(PhusionPassenger) 65 | PhusionPassenger.on_event(:starting_worker_process) do |forked| 66 | if forked 67 | database.reopen 68 | suggest_database.reopen 69 | end 70 | end 71 | end 72 | 73 | environment = ENV["RACK_ENV"] || "development" 74 | 75 | searcher_options = {} 76 | 77 | load_yaml = Proc.new do |file_name| 78 | configuration_file = base_dir + file_name 79 | if configuration_file.exist? 80 | require "yaml" 81 | YAML.load(configuration_file.read) 82 | else 83 | nil 84 | end 85 | end 86 | 87 | load_searcher_option = Proc.new do |key, file_name| 88 | configuration = load_yaml.call(file_name) 89 | if configuration 90 | require "yaml" 91 | searcher_options[key] = configuration 92 | end 93 | end 94 | 95 | load_searcher_option.call(:document, "document.yaml") 96 | 97 | configuration = load_yaml.call("#{environment}.yaml") || {} 98 | 99 | searcher = RuremaSearch::GroongaSearcher.new(database, 100 | suggest_database, 101 | base_dir.to_s, 102 | searcher_options) 103 | case environment 104 | when "production" 105 | show_error_page = Class.new do 106 | def initialize(app, options={}) 107 | @app = app 108 | @searcher = options[:searcher] 109 | @target_exception = options[:target_exception] || Exception 110 | end 111 | 112 | def call(env) 113 | @app.call(env) 114 | rescue @target_exception => exception 115 | @searcher.error_page(env, exception) 116 | end 117 | end 118 | use show_error_page, :searcher => searcher 119 | 120 | load_searcher_option.call(:smtp, "smtp.yaml") 121 | notifiers = [Racknga::ExceptionMailNotifier.new(searcher_options[:smtp])] 122 | use Racknga::Middleware::ExceptionNotifier, :notifiers => notifiers 123 | 124 | options = { 125 | :searcher => searcher, 126 | :target_exception => RuremaSearch::GroongaSearcher::ClientError, 127 | } 128 | use show_error_page, options 129 | end 130 | 131 | if configuration["use_log"] 132 | log_database_path = base_dir + "var" + "log" + "db" 133 | use Racknga::Middleware::Log, :database_path => log_database_path.to_s 134 | end 135 | 136 | use Rack::Runtime 137 | use Rack::ContentLength 138 | 139 | urls = [ 140 | "/favicon.", 141 | "/css/", 142 | "/images/", 143 | "/javascripts/", 144 | "/1.", 145 | "/2.", 146 | "/3.", 147 | ] 148 | 149 | case environment 150 | when "development" 151 | use Rack::Static, 152 | :urls => urls, 153 | :root => (base_dir + "public").to_s, 154 | :index => "index.html" 155 | end 156 | 157 | use Racknga::Middleware::Deflater 158 | use Rack::Lint 159 | use Rack::Head 160 | use Rack::ConditionalGet 161 | 162 | use Racknga::Middleware::JSONP 163 | 164 | if configuration["use_cache"] 165 | cache_database_path = base_dir + "var" + "cache" + "db" 166 | use Racknga::Middleware::Cache, :database_path => cache_database_path.to_s 167 | end 168 | 169 | use Racknga::Middleware::InstanceName, :application_name => "Rurema Search" 170 | 171 | run searcher 172 | -------------------------------------------------------------------------------- /lib/rurema_search/groonga_suggest_database.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011 Kouhei Sutou 2 | # 3 | # License: LGPLv3+ 4 | 5 | require 'shellwords' 6 | require 'json' 7 | require 'groonga' 8 | 9 | module RuremaSearch 10 | class GroongaSuggestDatabase 11 | DATASET = "rurema" 12 | 13 | def initialize 14 | @database = nil 15 | @dataset_name = DATASET 16 | end 17 | 18 | def open(base_path) 19 | @context = Groonga::Context.new(:encoding => :utf8) 20 | path = File.join(base_path, "suggest.db") 21 | if File.exist?(path) 22 | @database = @context.open_database(path) 23 | else 24 | FileUtils.mkdir_p(base_path) 25 | populate(path) 26 | end 27 | if block_given? 28 | begin 29 | yield(self) 30 | ensure 31 | close unless closed? 32 | end 33 | end 34 | end 35 | 36 | def reopen 37 | base_path = File.dirname(@database.path) 38 | close 39 | open(base_path) 40 | end 41 | 42 | def purge 43 | path = @database.path 44 | encoding = @database.encoding 45 | @database.remove 46 | directory = File.dirname(path) 47 | FileUtils.rm_rf(directory) 48 | FileUtils.mkdir_p(directory) 49 | @context = Groonga::Context.new(:encoding => encoding) 50 | populate(path) 51 | end 52 | 53 | def register_keyword(keyword, related_keywords) 54 | return if keyword.nil? or keyword.empty? 55 | related_keywords = related_keywords.reject do |related_keyword| 56 | related_keyword.empty? 57 | end 58 | 59 | event_values = [] 60 | event_values.concat(generate_input_event_values(next_id, keyword)) 61 | related_keywords.each do |related_keyword| 62 | combined_keyword = [keyword, related_keyword].join(" ") 63 | event_values.concat(generate_input_event_values(next_id, 64 | combined_keyword)) 65 | end 66 | @context.send("load " + 67 | "--table #{table_name('event')} " + 68 | "--each 'suggest_preparer(_id, type, item, " + 69 | "sequence, time, #{table_name('pair')})'") 70 | @context.send(JSON.generate(event_values)) 71 | @context.receive 72 | 73 | item_values = [] 74 | item_values << {"_key" => keyword, "kana" => [keyword]} 75 | related_keywords.each do |related_keyword| 76 | item_values << {"_key" => related_keyword, "kana" => [related_keyword]} 77 | end 78 | @context.send("load --table #{table_name('item')}") 79 | @context.send(JSON.generate(item_values)) 80 | @context.receive 81 | end 82 | 83 | def truncate_temporary_records 84 | temporary_table_names = [ 85 | table_name("event"), 86 | table_name("sequence"), 87 | table_name("pair"), 88 | ] 89 | temporary_table_names.each do |name| 90 | table = @context[name] 91 | table.truncate 92 | end 93 | end 94 | 95 | def corrections(query, options={}) 96 | suggest("correct", query, options) 97 | end 98 | 99 | def suggestions(query, options={}) 100 | suggest("suggest", query, options) 101 | end 102 | 103 | def completions(query, options={}) 104 | suggest("complete", query, options) 105 | end 106 | 107 | def close 108 | @database.close 109 | @database = nil 110 | @context.close 111 | @context = nil 112 | end 113 | 114 | def closed? 115 | @database.nil? or @database.closed? 116 | end 117 | 118 | def push_memory_pool(&block) 119 | @context.push_memory_pool(&block) 120 | end 121 | 122 | private 123 | def populate(path) 124 | escaped_path = Shellwords.escape(path) 125 | command = "groonga-suggest-create-dataset #{escaped_path} rurema" 126 | result = `#{command}` 127 | unless $?.success? 128 | raise "failed to create suggest dataset: <#{command}>: <#{result}>" 129 | end 130 | 131 | @database = @context.open_database(path) 132 | end 133 | 134 | def normalize_suggest_entries(entries) 135 | n_entries, headers, *values = entries 136 | values.collect do |key, score| 137 | {:key => key, :score => score} 138 | end 139 | end 140 | 141 | def next_id 142 | Time.now.to_f.to_s 143 | end 144 | 145 | def keyword_input_patterns(keyword) 146 | patterns = [] 147 | partial_keyword = "" 148 | keyword.each_char do |char| 149 | partial_keyword << char 150 | patterns << partial_keyword.dup 151 | end 152 | patterns 153 | end 154 | 155 | def generate_input_event_values(id, keyword) 156 | values = [] 157 | now = Time.now 158 | time_stamp = now + 1 159 | keyword_input_patterns(keyword).each do |partial_keyword| 160 | value = { 161 | "item" => partial_keyword, 162 | "sequence" => id, 163 | "time" => time_stamp.to_f, 164 | } 165 | values << value 166 | time_stamp += 1 167 | end 168 | values << { 169 | "item" => keyword, 170 | "sequence" => id, 171 | "time" => time_stamp.to_f, 172 | "type" => "submit" 173 | } 174 | values 175 | end 176 | 177 | def table_name(name) 178 | "#{name}_#{@dataset_name}" 179 | end 180 | 181 | def suggest(type, query, options={}) 182 | @context.send("/d/suggest?" + 183 | "table=item_#{@dataset_name}&" + 184 | "column=kana&" + 185 | "limit=#{(options[:limit] || 10).to_i}&" + 186 | "types=#{Rack::Utils.escape(type)}&" + 187 | "threshold=0&" + 188 | "query=#{Rack::Utils.escape(query)}") 189 | id, json = @context.receive 190 | normalize_suggest_entries(JSON.parse(json)[type]) 191 | end 192 | end 193 | end 194 | -------------------------------------------------------------------------------- /test/rurema-search-test-utils.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2010-2013 Kouhei Sutou 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | require "open-uri" 17 | require "webrick/httpstatus" 18 | 19 | require "rubygems" 20 | require "rack" 21 | require "time" 22 | 23 | require "rurema_search" 24 | require "rurema_search/groonga_searcher" 25 | 26 | require "rack/test" 27 | require "test/unit/capybara" 28 | 29 | module RuremaSearchTestUtils 30 | include Rack::Test::Methods 31 | include Capybara::DSL 32 | 33 | class << self 34 | def included(base) 35 | base.class_eval do 36 | setup :setup_app 37 | setup :setup_tmp_dir 38 | teardown :teardown_tmp_dir 39 | 40 | setup :setup_host 41 | end 42 | end 43 | 44 | def database 45 | @database ||= ensure_database 46 | end 47 | 48 | def suggest_database 49 | @suggest_database ||= ensure_suggest_database 50 | end 51 | 52 | private 53 | def ensure_database 54 | updated = ensure_bitclust_database 55 | database_dir = test_dir + "groonga-database" 56 | database_file = database_dir + "bitclust.db" 57 | bitclust_database_dir = test_dir + "db-2.0.0" 58 | if !database_file.exist? or 59 | database_file.mtime < bitclust_database_dir.mtime 60 | FileUtils.rm_rf(database_dir.to_s) 61 | database_dir.mkpath 62 | ruby = File.join(RbConfig::CONFIG["bindir"], 63 | RbConfig::CONFIG["ruby_install_name"]) 64 | ruby << RbConfig::CONFIG["EXEEXT"] 65 | indexer = base_dir + "bin" + "bitclust-indexer" 66 | command = "#{ruby} #{indexer} " 67 | command << "--database #{database_dir} #{test_dir + 'db-*'}" 68 | print("creating database by '#{command}'...") 69 | result = `#{command} 2>&1` 70 | unless $?.success? 71 | FileUtils.rm_rf(database_dir.to_s) 72 | raise "failed to create test database: " + 73 | "<#{command}>: <#{$?.to_i}>: <#{result}>" 74 | end 75 | puts "done." 76 | end 77 | _database = RuremaSearch::GroongaDatabase.new 78 | _database.open(database_dir.to_s, "utf-8") 79 | _database 80 | end 81 | 82 | def ensure_bitclust_database 83 | ensure_doctree 84 | doctree_dir = fixtures_dir + "doctree" 85 | source_dir = doctree_dir + "refm/api/src" 86 | servicing_versions = ["1.8.7", "1.9.3", "2.0.0"] 87 | servicing_versions.each do |version| 88 | bitclust_database_dir = test_dir + "db-#{version}" 89 | if !bitclust_database_dir.exist? or 90 | bitclust_database_dir.mtime < last_commit_time(doctree_dir) 91 | system("bundle", "exec", 92 | "bitclust", "--database", bitclust_database_dir.to_s, 93 | "init", "encoding=utf-8", "version=#{version}") 94 | system("bundle", "exec", 95 | "bitclust", "--database", bitclust_database_dir.to_s, 96 | "update", "--stdlibtree", source_dir.to_s) 97 | end 98 | end 99 | end 100 | 101 | def ensure_doctree 102 | target_date = "2013-07-14T00:00:00+00:00" 103 | doctree_dir = fixtures_dir + "doctree" 104 | if doctree_dir.exist? 105 | if last_commit_time(doctree_dir) < Time.parse(target_date) 106 | system("git", "pull", "--rebase", 107 | :err => :out, :chdir => doctree_dir.to_s) 108 | end 109 | else 110 | system("git", "clone", 111 | "git://github.com/rurema/doctree.git", doctree_dir.to_s, 112 | :err => :out) 113 | end 114 | system("git", "checkout", "master@{#{target_date}}", 115 | :chdir => doctree_dir.to_s) 116 | end 117 | 118 | def ensure_suggest_database 119 | database_dir = test_dir + "suggest-database" 120 | _database = RuremaSearch::GroongaSuggestDatabase.new 121 | _database.open(database_dir.to_s) 122 | _database 123 | end 124 | 125 | def last_commit_time(git_dir) 126 | commit_time = nil 127 | last_commit_time = Dir.chdir(git_dir.to_s) do 128 | commit_time = `git log --max-count=1 --format=format:%cd`.chomp 129 | end 130 | Time.parse(commit_time) 131 | end 132 | end 133 | 134 | def setup_app 135 | Capybara.app = app 136 | end 137 | 138 | def setup_tmp_dir 139 | FileUtils.mkdir_p(tmp_dir.to_s) 140 | end 141 | 142 | def teardown_tmp_dir 143 | FileUtils.rm_rf(tmp_dir.to_s) 144 | end 145 | 146 | def setup_host 147 | header("Host", host) 148 | end 149 | 150 | def app 151 | RuremaSearch::GroongaSearcher.new(database, suggest_database, base_dir) 152 | end 153 | 154 | private 155 | def current_dom 156 | #webrat.current_dom 157 | nil 158 | end 159 | 160 | def assert_response(code) 161 | #assert_equal(resolve_status(code), resolve_status(webrat.response_code)) 162 | end 163 | 164 | def resolve_status(code_or_message) 165 | messages = WEBrick::HTTPStatus::StatusMessage 166 | if code_or_message.is_a?(String) 167 | message = code_or_message 168 | [(messages.find {|key, value| value == message} || [])[0], 169 | message] 170 | else 171 | code = code_or_message 172 | [code, messages[code]] 173 | end 174 | end 175 | 176 | def database 177 | RuremaSearchTestUtils.database 178 | end 179 | 180 | def suggest_database 181 | RuremaSearchTestUtils.suggest_database 182 | end 183 | 184 | def host 185 | Capybara.default_host 186 | end 187 | 188 | module_function 189 | def test_dir 190 | @test_dir ||= Pathname(__FILE__).dirname 191 | end 192 | 193 | def base_dir 194 | @base_dir ||= test_dir.parent 195 | end 196 | 197 | def tmp_dir 198 | @tmp_dir ||= test_dir + "tmp" 199 | end 200 | 201 | def fixtures_dir 202 | @fixtures_dir ||= test_dir + "fixtures" 203 | end 204 | end 205 | -------------------------------------------------------------------------------- /license/lgpl-3.0.txt: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /public/images/leading-link-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 24 | 26 | 33 | 35 | 39 | 43 | 44 | 47 | 51 | 55 | 56 | 59 | 63 | 67 | 68 | 79 | 90 | 101 | 102 | 124 | 126 | 127 | 129 | image/svg+xml 130 | 132 | 133 | 134 | Jakub Steiner 135 | 136 | 137 | http://jimmac.musichall.cz 138 | 140 | Go Down 141 | 142 | 143 | go 144 | lower 145 | down 146 | arrow 147 | pointer 148 | > 149 | 150 | 151 | 152 | 153 | Andreas Nilsson 154 | 155 | 156 | 157 | 159 | 161 | 163 | 165 | 166 | 167 | 168 | 173 | 183 | 186 | 191 | 196 | 201 | 202 | 203 | 204 | -------------------------------------------------------------------------------- /lib/rurema_search/groonga_indexer.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Kouhei Sutou 2 | # 3 | # License: LGPLv3+ 4 | 5 | module RuremaSearch 6 | class GroongaIndexer 7 | attr_accessor :base_time 8 | def initialize(database, method_database, function_database) 9 | @database = database 10 | @method_database = method_database 11 | @function_database = function_database 12 | @base_time = nil 13 | @n_added_entries = 0 14 | end 15 | 16 | def index 17 | @method_database.classes.each do |klass| 18 | index_class(klass) 19 | klass.unload 20 | end 21 | @method_database.docs.each do |doc| 22 | index_document(doc) 23 | doc.unload 24 | end 25 | @method_database.libraries.each do |library| 26 | index_library(library) 27 | library.unload 28 | end 29 | @function_database.functions.each do |function| 30 | index_function(function) 31 | function.unload 32 | end 33 | end 34 | 35 | private 36 | def version 37 | @version ||= @method_database.properties["version"] 38 | end 39 | 40 | def encoding 41 | @encoding ||= @method_database.properties["encoding"] 42 | end 43 | 44 | def index_class(klass) 45 | add_class(klass) 46 | klass.entries.each do |entry| 47 | add_class_entry(klass, entry) 48 | add_spec(klass, entry) 49 | end 50 | end 51 | 52 | def index_document(document) 53 | source = entry_source(document) 54 | attributes = { 55 | :name => document.name, 56 | :local_name => document.title, 57 | :label => document.title, 58 | :type => "document", 59 | :document => source, 60 | :description => "#{document.title} #{source}", 61 | :version => version, 62 | } 63 | add_entry("#{version}:#{document.name}", attributes) 64 | end 65 | 66 | def index_library(library) 67 | source = entry_source(library) 68 | related_names = [] 69 | [library.requires, library.classes, library.methods, 70 | library.sublibraries].each do |entries| 71 | entries.each do |entry| 72 | related_names << entry.name 73 | end 74 | end 75 | library_name = library.name 76 | library_type = library.type_id.to_s 77 | attributes = { 78 | :name => library_name, 79 | :local_name => library_name, 80 | :label => library_name, 81 | :type => library_type, 82 | :document => source, 83 | :description => source, 84 | :version => version, 85 | :related_names => related_names, 86 | } 87 | add_entry("#{version}:#{library.name}", attributes) 88 | @database.libraries.add(library_name, :type => library_type) 89 | end 90 | 91 | def index_function(function) 92 | source = entry_source(function) 93 | attributes = { 94 | :name => function.name, 95 | :label => function.header, 96 | :local_name => function.name, 97 | :type => normalize_type_label(function.type_label), 98 | :version => version, 99 | :document => source, 100 | :signature => function.header, 101 | :description => source, 102 | :visibility => function.private? ? "private" : "public", 103 | } 104 | add_entry("#{version}:#{function.header}", attributes) 105 | end 106 | 107 | def add_class(klass) 108 | source = entry_source(klass) 109 | attributes = { 110 | :name => klass.name, 111 | :local_name => klass.name.split(/::/).last, 112 | :label => klass.name, 113 | :type => klass.type.to_s, 114 | :version => version, 115 | :document => source, 116 | :description => source, 117 | } 118 | library = klass.library 119 | attributes[:library] = library.name if library 120 | add_entry("#{version}:#{klass.name}", attributes) 121 | @database.specs.add(klass.name, :type => klass.type.to_s) 122 | end 123 | 124 | def add_class_entry(klass, entry) 125 | source = entry_source(entry) 126 | foreach_method_chunk(source) do |signatures, description| 127 | signatures.each do |signature| 128 | attributes = { 129 | :name => entry.spec_string, 130 | :label => "#{klass.name}#{entry.typemark}#{signature}", 131 | :local_name => signature.name, 132 | :type => normalize_type_label(entry.type_label), 133 | :version => version, 134 | :document => source, 135 | :signature => signature.to_s, 136 | :description => description, 137 | :visibility => entry.visibility.to_s, 138 | } 139 | klass_name = klass.name 140 | klass_type = normalize_type_label(klass.type.to_s) 141 | if klass.class? 142 | attributes[:class] = klass_name 143 | @database.classes.add(klass_name, :type => klass_type) 144 | elsif klass.module? 145 | attributes[:module] = klass_name 146 | @database.modules.add(klass_name, :type => klass_type) 147 | else 148 | attributes[:object] = klass_name 149 | @database.objects.add(klass_name, :type => klass_type) 150 | end 151 | library = entry.library 152 | attributes[:library] = library.name if library 153 | add_entry("#{version}:#{entry.spec_string}:#{signature}", 154 | attributes) 155 | end 156 | end 157 | end 158 | 159 | def add_spec(klass, entry) 160 | @database.specs.add(entry.spec_string, 161 | :type => normalize_type_label(entry.type_label)) 162 | end 163 | 164 | def add_entry(key, attributes) 165 | attributes[:last_modified] = @base_time 166 | related_names = attributes[:related_names] || [] 167 | extracted_attributes = extract_attributes(attributes[:description]) 168 | attributes = extracted_attributes.merge(attributes) 169 | attributes[:related_names].concat(related_names) 170 | attributes[:name_raw] ||= attributes[:name] 171 | attributes[:local_name_raw] ||= attributes[:local_name] 172 | attributes[:normalized_class] ||= attributes[:class] 173 | attributes[:normalized_module] ||= attributes[:module] 174 | attributes[:normalized_object] ||= attributes[:object] 175 | @database.entries.add(key, attributes) 176 | @n_added_entries += 1 177 | if (@n_added_entries % 1000).zero? 178 | @database.reopen 179 | GC.start 180 | end 181 | end 182 | 183 | def normalize_type_label(label) 184 | label.gsub(/ /, '-') 185 | end 186 | 187 | def foreach_method_chunk(source, &block) 188 | input = LineInput.for_string(source) 189 | while input.next? 190 | signatures = input.span(/\A---/).collect do |line| 191 | BitClust::MethodSignature.parse(line.rstrip) 192 | end 193 | body = input.break(/\A---/).join 194 | yield signatures, body 195 | end 196 | end 197 | 198 | def entry_source(entry) 199 | source = entry.source 200 | if source.respond_to?(:force_encoding) 201 | source.force_encoding(@method_database.encoding) 202 | end 203 | source 204 | end 205 | 206 | def extract_attributes(source) 207 | extractor = RDAttributesExtractor.new 208 | extractor.extract(source) 209 | end 210 | 211 | class RDAttributesExtractor < BitClust::RDCompiler 212 | def initialize 213 | opt = {:stop_on_syntax_error => false} 214 | super(nil, 1, opt) 215 | end 216 | 217 | def extract(src) 218 | @related_names = [] 219 | compile(src) 220 | { 221 | :summary => src.split(/\n\n/, 2).first, 222 | :related_names => @related_names.uniq, 223 | } 224 | end 225 | 226 | private 227 | def add_related_name(name) 228 | return if name.nil? 229 | return if name.empty? 230 | @related_names << name 231 | end 232 | 233 | def library_link(name, label=nil, fragment=nil) 234 | add_related_name(name) 235 | end 236 | 237 | def class_link(name, label=nil, fragment=nil) 238 | add_related_name(name) 239 | end 240 | 241 | def method_link(spec, label=nil, fragment=nil) 242 | add_related_name(name) 243 | end 244 | 245 | def function_link(name, label=nil, fragment=nil) 246 | add_related_name(name) 247 | end 248 | 249 | def document_link(name, label=nil, fragment=nil) 250 | add_related_name(name) 251 | end 252 | end 253 | end 254 | end 255 | -------------------------------------------------------------------------------- /public/images/function-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 24 | 26 | 33 | 35 | 39 | 43 | 44 | 51 | 55 | 59 | 60 | 70 | 81 | 82 | 102 | 104 | 105 | 107 | image/svg+xml 108 | 110 | Executable 111 | 112 | 113 | Jakub Steiner 114 | 115 | 116 | http://jimmac.musichall.cz/ 117 | 118 | 119 | executable 120 | program 121 | binary 122 | bin 123 | script 124 | shell 125 | 126 | 127 | 129 | 130 | 132 | 134 | 136 | 138 | 139 | 140 | 141 | 145 | 155 | 156 | 160 | 162 | 166 | 171 | 175 | 179 | 184 | 185 | 186 | 187 | -------------------------------------------------------------------------------- /public/images/drop-condition-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 24 | 26 | 33 | 44 | 47 | 51 | 55 | 56 | 67 | 69 | 73 | 77 | 81 | 82 | 92 | 94 | 98 | 102 | 103 | 106 | 110 | 114 | 115 | 126 | 137 | 144 | 145 | 167 | 169 | 170 | 172 | image/svg+xml 173 | 175 | 176 | 177 | Jakub Steiner 178 | 179 | 180 | http://jimmac.musichall.cz 181 | 183 | 184 | 185 | 186 | emblem 187 | symbolic 188 | link 189 | pointer 190 | io 191 | file 192 | 193 | 194 | 195 | 197 | 199 | 201 | 203 | 204 | 205 | 206 | 211 | 214 | 221 | 226 | 231 | 232 | 241 | 250 | 258 | 266 | 267 | 268 | -------------------------------------------------------------------------------- /lib/rurema_search/groonga_database.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Kouhei Sutou 2 | # 3 | # License: LGPLv3+ 4 | 5 | require 'groonga' 6 | 7 | module RuremaSearch 8 | class GroongaDatabase 9 | def initialize 10 | @context = nil 11 | @database = nil 12 | check_availability 13 | end 14 | 15 | def available? 16 | @available 17 | end 18 | 19 | def open(base_path, encoding) 20 | reset_context(encoding) 21 | path = File.join(base_path, "bitclust.db") 22 | if File.exist?(path) 23 | @database = @context.open_database(path) 24 | populate_schema 25 | else 26 | FileUtils.mkdir_p(base_path) 27 | populate(path) 28 | end 29 | if block_given? 30 | begin 31 | yield(self) 32 | ensure 33 | close unless closed? 34 | end 35 | end 36 | end 37 | 38 | def reopen 39 | base_path = File.dirname(@database.path) 40 | encoding = @database.encoding 41 | close 42 | open(base_path, encoding) 43 | end 44 | 45 | def purge 46 | path = @database.path 47 | encoding = @database.encoding 48 | @database.remove 49 | directory = File.dirname(path) 50 | FileUtils.rm_rf(directory) 51 | FileUtils.mkdir_p(directory) 52 | reset_context(encoding) 53 | populate(path) 54 | end 55 | 56 | def close 57 | @database.close 58 | @database = nil 59 | @context.close 60 | @context = nil 61 | end 62 | 63 | def closed? 64 | @database.nil? or @database.closed? 65 | end 66 | 67 | def names 68 | @context["Names"] 69 | end 70 | 71 | def entries 72 | @context["Entries"] 73 | end 74 | 75 | def specs 76 | @context["Specs"] 77 | end 78 | 79 | def classes 80 | @context["Classes"] 81 | end 82 | 83 | def modules 84 | @context["Modules"] 85 | end 86 | 87 | def objects 88 | @context["Objects"] 89 | end 90 | 91 | def libraries 92 | @context["Libraries"] 93 | end 94 | 95 | def singleton_methods 96 | @context["SingletonMethods"] 97 | end 98 | 99 | def instance_methods 100 | @context["InstanceMethods"] 101 | end 102 | 103 | def module_functions 104 | @context["ModuleFunctions"] 105 | end 106 | 107 | def constants 108 | @context["Constants"] 109 | end 110 | 111 | def special_variables 112 | @context["SpecialVariables"] 113 | end 114 | 115 | def versions 116 | @context["Versions"] 117 | end 118 | 119 | def purge_old_records(base_time) 120 | old_entries = entries.select do |record| 121 | record.last_modified < base_time 122 | end 123 | old_entries.each do |record| 124 | real_record = record.key 125 | real_record.delete 126 | end 127 | end 128 | 129 | def push_memory_pool(&block) 130 | @context.push_memory_pool(&block) 131 | end 132 | 133 | private 134 | def check_availability 135 | begin 136 | require 'groonga' 137 | @available = true 138 | rescue LoadError 139 | @available = false 140 | end 141 | end 142 | 143 | def reset_context(encoding) 144 | @context.close if @context 145 | @context = Groonga::Context.new(:encoding => encoding) 146 | end 147 | 148 | def populate(path) 149 | @database = @context.create_database(path) 150 | populate_schema 151 | end 152 | 153 | def populate_schema 154 | Groonga::Schema.define(:context => @context) do |schema| 155 | schema.create_table("Names", 156 | :type => :patricia_trie, 157 | :key_type => "ShortText") do |table| 158 | end 159 | 160 | schema.create_table("LocalNames", 161 | :type => :patricia_trie, 162 | :key_type => "ShortText") do |table| 163 | end 164 | 165 | schema.create_table("Types", 166 | :type => :hash, 167 | :key_type => "ShortText") do |table| 168 | end 169 | 170 | schema.create_table("Versions", 171 | :type => :patricia_trie, 172 | :key_type => "ShortText") do |table| 173 | end 174 | 175 | schema.create_table("Visibilities", 176 | :type => :hash, 177 | :key_type => "ShortText") do |table| 178 | end 179 | 180 | schema.create_table("Classes", 181 | :type => :patricia_trie, 182 | :key_type => "ShortText") do |table| 183 | table.reference("type", "Types") 184 | end 185 | 186 | schema.create_table("NormalizedClasses", 187 | :type => :patricia_trie, 188 | :key_type => "ShortText", 189 | :key_normalize => true) do |table| 190 | end 191 | 192 | schema.create_table("Modules", 193 | :type => :patricia_trie, 194 | :key_type => "ShortText") do |table| 195 | table.reference("type", "Types") 196 | end 197 | 198 | schema.create_table("NormalizedModules", 199 | :type => :patricia_trie, 200 | :key_type => "ShortText", 201 | :key_normalize => true) do |table| 202 | end 203 | 204 | schema.create_table("Objects", 205 | :type => :patricia_trie, 206 | :key_type => "ShortText") do |table| 207 | table.reference("type", "Types") 208 | end 209 | 210 | schema.create_table("NormalizedObjects", 211 | :type => :patricia_trie, 212 | :key_type => "ShortText", 213 | :key_normalize => true) do |table| 214 | end 215 | 216 | schema.create_table("Libraries", 217 | :type => :patricia_trie, 218 | :key_type => "ShortText") do |table| 219 | table.reference("type", "Types") 220 | end 221 | 222 | schema.create_table("SingletonMethods", 223 | :type => :hash, 224 | :key_type => "ShortText") do |table| 225 | end 226 | 227 | schema.create_table("InstanceMethods", 228 | :type => :hash, 229 | :key_type => "ShortText") do |table| 230 | end 231 | 232 | schema.create_table("ModuleFunctions", 233 | :type => :hash, 234 | :key_type => "ShortText") do |table| 235 | end 236 | 237 | schema.create_table("Constants", 238 | :type => :hash, 239 | :key_type => "ShortText") do |table| 240 | end 241 | 242 | schema.create_table("SpecialVariables", 243 | :type => :hash, 244 | :key_type => "ShortText") do |table| 245 | end 246 | 247 | schema.create_table("Entries", 248 | :type => :hash, 249 | :key_type => "ShortText") do |table| 250 | table.reference("name", "Names") 251 | table.reference("local_name", "LocalNames") 252 | table.short_text("name_raw") 253 | table.short_text("local_name_raw") 254 | table.short_text("label") 255 | table.text("document") 256 | table.text("signature") 257 | table.text("summary") 258 | table.text("description") 259 | table.reference("type", "Types") 260 | table.reference("class", "Classes") 261 | table.reference("normalized_class", "NormalizedClasses") 262 | table.reference("module", "Modules") 263 | table.reference("normalized_module", "NormalizedModules") 264 | table.reference("object", "Objects") 265 | table.reference("normalized_object", "NormalizedObjects") 266 | table.reference("library", "Libraries") 267 | table.reference("version", "Versions") 268 | table.reference("visibility", "Visibilities") 269 | table.reference("related_names", "Names", :type => :vector) 270 | table.time("last_modified") 271 | end 272 | 273 | schema.create_table("Specs", 274 | :type => :patricia_trie, 275 | :key_type => "ShortText") do |table| 276 | table.reference("type", "Types") 277 | end 278 | 279 | schema.create_table("Bigram", 280 | :type => :patricia_trie, 281 | :key_type => "ShortText", 282 | :default_tokenizer => "TokenBigram", 283 | :key_normalize => true) do |table| 284 | table.index("Entries.document") 285 | table.index("Entries.summary") 286 | table.index("Entries.description") 287 | end 288 | 289 | schema.create_table("BigramAlphabet", 290 | :type => :patricia_trie, 291 | :key_type => "ShortText", 292 | :default_tokenizer => "TokenBigramSplitSymbolAlphaDigit", 293 | :key_normalize => true) do |table| 294 | table.index("Entries.name_raw") 295 | table.index("Entries.local_name_raw") 296 | table.index("Entries.label") 297 | table.index("Entries.signature") 298 | end 299 | 300 | schema.change_table("Names") do |table| 301 | table.index("Entries.name") 302 | end 303 | 304 | schema.change_table("LocalNames") do |table| 305 | table.index("Entries.local_name") 306 | end 307 | 308 | schema.change_table("Versions") do |table| 309 | table.index("Entries.version") 310 | end 311 | 312 | schema.change_table("Classes") do |table| 313 | table.index("Entries.class") 314 | end 315 | 316 | schema.change_table("NormalizedClasses") do |table| 317 | table.index("Entries.normalized_class") 318 | end 319 | 320 | schema.change_table("Modules") do |table| 321 | table.index("Entries.module") 322 | end 323 | 324 | schema.change_table("NormalizedModules") do |table| 325 | table.index("Entries.normalized_module") 326 | end 327 | 328 | schema.change_table("Objects") do |table| 329 | table.index("Entries.object") 330 | end 331 | 332 | schema.change_table("NormalizedObjects") do |table| 333 | table.index("Entries.normalized_object") 334 | end 335 | 336 | schema.change_table("Libraries") do |table| 337 | table.index("Entries.library") 338 | end 339 | end 340 | end 341 | end 342 | end 343 | -------------------------------------------------------------------------------- /public/images/document-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | 22 | 29 | 40 | 43 | 47 | 51 | 52 | 63 | 65 | 69 | 73 | 77 | 78 | 88 | 91 | 95 | 99 | 100 | 111 | 113 | 117 | 121 | 125 | 126 | 136 | 139 | 143 | 147 | 148 | 158 | 161 | 165 | 169 | 170 | 180 | 182 | 186 | 190 | 191 | 201 | 202 | 221 | 223 | 224 | 226 | image/svg+xml 227 | 229 | Justify Center 230 | 2005-10-29 231 | 232 | 233 | Andreas Nilsson 234 | 235 | 236 | 237 | 238 | justify 239 | center 240 | format 241 | 242 | 243 | 245 | 246 | 248 | 250 | 252 | 254 | 255 | 256 | 257 | 261 | 265 | 272 | 277 | 282 | 283 | 292 | 300 | 308 | 316 | 324 | 332 | 341 | 342 | 343 | -------------------------------------------------------------------------------- /public/images/query-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 24 | 26 | 33 | 35 | 39 | 43 | 44 | 46 | 50 | 54 | 58 | 59 | 62 | 66 | 70 | 71 | 74 | 78 | 82 | 83 | 85 | 89 | 93 | 94 | 96 | 100 | 104 | 105 | 107 | 111 | 115 | 119 | 120 | 130 | 140 | 151 | 162 | 171 | 182 | 191 | 192 | 214 | 216 | 217 | 219 | image/svg+xml 220 | 222 | 223 | 224 | Jakub Steiner 225 | 226 | 227 | http://jimmac.musichall.cz 228 | 230 | 231 | 232 | 234 | 236 | 238 | 240 | 241 | 242 | 243 | 248 | 251 | 261 | 266 | 270 | 275 | 285 | 295 | 305 | 315 | 319 | 320 | 321 | 322 | -------------------------------------------------------------------------------- /public/css/rurema.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | html, 4 | body 5 | { 6 | background-color: white; 7 | color: black; 8 | padding: 0; 9 | margin: 0; 10 | } 11 | 12 | a 13 | { 14 | color: #4c4cc6; 15 | } 16 | 17 | a:visited 18 | { 19 | color: #9191ec; 20 | } 21 | 22 | hr.separator 23 | { 24 | visibility: hidden; 25 | clear: both; 26 | } 27 | 28 | div.list-box 29 | { 30 | margin-bottom: 15px; 31 | background: #d8ffb2; 32 | border: 1px solid #abf562; 33 | -moz-border-radius: 5px; 34 | -webkit-border-radius: 5px; 35 | border-radius: 5px; 36 | -moz-box-shadow: 0.2em 0.2em 0.5em rgb(100, 100, 100); 37 | -webkit-box-shadow: 0.2em 0.2em 0.5em rgb(100, 100, 100); 38 | box-shadow: 0.2em 0.2em 0.5em rgb(100, 100, 100); 39 | } 40 | 41 | div.header 42 | { 43 | position: static; 44 | } 45 | 46 | div.header h1 47 | { 48 | padding: 0; 49 | margin: 0; 50 | margin-right: 0.5em; 51 | } 52 | 53 | div.header h1 a img 54 | { 55 | border-style: none; 56 | } 57 | 58 | div.header div.description 59 | { 60 | position: absolute; 61 | top: 0.2em; 62 | left: 260px; 63 | font-size: smaller; 64 | color: #aaa; 65 | } 66 | 67 | div.header div.search-form 68 | { 69 | position: absolute; 70 | left: 260px; 71 | top: 1.5em; 72 | } 73 | 74 | div.header div.search-form form input.query 75 | { 76 | width: 20em; 77 | } 78 | 79 | div.content 80 | { 81 | clear: both; 82 | } 83 | 84 | div.content div.top-page-drilldowns, 85 | div.content div.search-result 86 | { 87 | line-height: 140%; 88 | } 89 | 90 | div.content div.top-search-form 91 | { 92 | width: 40em; 93 | margin: 70px auto 35px; 94 | } 95 | 96 | div.content div.top-search-form h1 97 | { 98 | text-align: center; 99 | margin-right: 40px; 100 | } 101 | 102 | div.content div.top-search-form h1 a img 103 | { 104 | border-style: none; 105 | } 106 | 107 | div.content div.top-search-form h2 108 | { 109 | text-align: center; 110 | font-size: small; 111 | color: #999; 112 | } 113 | 114 | div.content div.top-search-form p 115 | { 116 | text-align: center; 117 | padding: 0; 118 | margin: 10px 0; 119 | } 120 | 121 | div.content div.top-search-form p.input-form input 122 | { 123 | width: 30em; 124 | } 125 | 126 | div.content div.top-search-form p.button input 127 | { 128 | width: 10em; 129 | } 130 | 131 | div.content div.top-search-form table.version-select 132 | { 133 | margin: 0 auto; 134 | } 135 | 136 | div.content div.top-search-form table.version-select th 137 | { 138 | padding: 0 10px; 139 | } 140 | 141 | div.content div.top-search-form table.version-select th.version-select-current span 142 | { 143 | padding-left: 20px; 144 | background: url(../images/version-icon.png) no-repeat left bottom; 145 | } 146 | 147 | div.content div.top-search-form table.version-select td 148 | { 149 | text-align: center; 150 | } 151 | 152 | div.content div.top-search-form table.version-select td span 153 | { 154 | font-size: x-small; 155 | color: #999; 156 | } 157 | 158 | div.content div.top-page-drilldowns div.drilldown 159 | { 160 | width: 650px; 161 | margin: 30px auto; 162 | margin-bottom: 50px; 163 | } 164 | 165 | div.content div.top-page-drilldowns div.drilldown h2 166 | { 167 | font-size: larger; 168 | text-align: center; 169 | margin: 0; 170 | padding: 0; 171 | padding-top: 10px; 172 | padding-bottom: 5px; 173 | } 174 | 175 | div.content div.top-page-drilldowns div.drilldown h2.drilldown-type 176 | { 177 | margin-left: 15em; 178 | margin-right: 15em; 179 | background-position: left top; 180 | } 181 | 182 | div.content div.top-page-drilldowns div.drilldown dl 183 | { 184 | margin: 0; 185 | padding: 0; 186 | } 187 | 188 | div.content div.top-page-drilldowns div.drilldown dl dt 189 | { 190 | clear: both; 191 | text-align: center; 192 | background-color: #abf562; 193 | padding: 0; 194 | margin: 0 24px; 195 | font-size: smaller; 196 | color: #468c00; 197 | font-weight: bold; 198 | border: 1px solid #d8ffb2; 199 | -moz-border-radius: 3px; 200 | -webkit-border-radius: 3px; 201 | border-radius: 3px; 202 | } 203 | 204 | div.content div.top-page-drilldowns div.drilldown dl dd 205 | { 206 | margin: 0; 207 | padding: 0; 208 | padding-bottom: 5px; 209 | } 210 | 211 | div.content div.top-page-drilldowns div.drilldown ol 212 | { 213 | margin: 0; 214 | padding: 0; 215 | } 216 | 217 | div.content div.top-page-drilldowns div.drilldown ol li 218 | { 219 | display: block; 220 | float: left; 221 | width: 275px; 222 | padding-left: 25px; 223 | padding-right: 25px; 224 | } 225 | 226 | div.content div.top-page-drilldowns div.drilldown ul 227 | { 228 | margin-left: 5em; 229 | } 230 | 231 | div.content div.top-page-drilldowns div.drilldown ul li 232 | { 233 | display: inline; 234 | } 235 | 236 | div.content div.search-header div.version-select 237 | { 238 | margin: 0; 239 | padding: 0; 240 | padding-bottom: 2px; 241 | margin-top: 5px; 242 | margin-bottom: -1px; 243 | border-bottom: 1px solid #c1c1ec; 244 | } 245 | 246 | div.content div.search-header div.version-select ul, 247 | div.content div.search-header div.version-select ul li 248 | { 249 | margin: 0; 250 | padding: 0; 251 | } 252 | 253 | div.content div.search-header div.version-select ul 254 | { 255 | padding-left: 10px; 256 | } 257 | 258 | div.content div.search-header div.version-select ul li 259 | { 260 | display: inline; 261 | } 262 | 263 | div.content div.search-header div.version-select ul li span, 264 | div.content div.search-header div.version-select ul li a 265 | { 266 | padding-left: 0.5em; 267 | padding-right: 0.5em; 268 | padding-top: 2px; 269 | padding-bottom: 2px; 270 | border: 1px solid #c1c1ec; 271 | border-bottom-color: #e9e9f8; 272 | -moz-border-radius: 5px 5px 0 0; 273 | -webkit-border-radius: 5px 5px 0 0; 274 | border-radius: 5px 5px 0 0; 275 | /* margin-right: -5px; */ /* This will be broken on IE. (not confirmed) */ 276 | } 277 | 278 | div.content div.search-header div.version-select ul li span 279 | { 280 | color: white; 281 | background-color: #4c4cc6; 282 | } 283 | 284 | div.content div.search-header div.version-select ul li span.version-n-entries 285 | { 286 | display: none; 287 | } 288 | 289 | div.content div.search-header div.version-select ul li a 290 | { 291 | text-decoration: none; 292 | } 293 | 294 | div.content div.search-header div.version-select ul li a:hover, 295 | div.content div.search-header div.version-select ul li a:focus 296 | { 297 | color: white; 298 | background-color: #4c4cc6; 299 | } 300 | 301 | div.content div.statistics 302 | { 303 | text-align: right; 304 | background-color: #e9e9f8; 305 | border-bottom: 1px solid #c1c1ec; 306 | padding-bottom: 3px; 307 | margin-bottom: 1em; 308 | text-shadow: 0.075em 0.075em 0.075em rgb(100, 100, 100); 309 | -moz-box-shadow: 0 0.1em 0.5em rgb(100, 100, 100); 310 | -webkit-box-shadow: 0 0.1em 0.5em rgb(100, 100, 100); 311 | box-shadow: 0 0.1em 0.5em rgb(100, 100, 100); 312 | } 313 | 314 | div.topic-path 315 | { 316 | margin-left: 10px; 317 | } 318 | 319 | div.topic-path span.all-items 320 | { 321 | padding-left: 20px; 322 | background: url(../images/all-items-icon.png) no-repeat left bottom; 323 | } 324 | 325 | div.topic-path a.drop-condition 326 | { 327 | margin: 0 3px; 328 | } 329 | 330 | div.topic-path a.drop-condition img 331 | { 332 | border: none; 333 | } 334 | 335 | div.corrections, 336 | div.suggestions 337 | { 338 | margin-top: 5px; 339 | margin-left: 230px; 340 | } 341 | 342 | div.corrections h2, 343 | div.suggestions h2 344 | { 345 | display: inline; 346 | font-size: medium; 347 | padding: 0; 348 | margin: 0; 349 | margin-right: 5px; 350 | } 351 | 352 | div.corrections ol, 353 | div.corrections ol li, 354 | div.suggestions ol, 355 | div.suggestions ol li 356 | { 357 | display: inline; 358 | padding: 0; 359 | margin: 0; 360 | } 361 | 362 | div.corrections ol li, 363 | div.suggestions ol li 364 | { 365 | margin-left: 5px; 366 | } 367 | 368 | div.search-result 369 | { 370 | position: relative; 371 | margin-top: 15px; 372 | } 373 | 374 | div.result, 375 | div.no-result 376 | { 377 | clear: both; 378 | padding-left: 20px; 379 | padding-right: 20px; 380 | margin-bottom: 1em; 381 | } 382 | 383 | div.result 384 | { 385 | margin-left: 200px; 386 | } 387 | 388 | div.drilldowns 389 | { 390 | position: absolute; 391 | left: 10px; 392 | width: 190px; 393 | font-size: smaller; 394 | } 395 | 396 | div.drilldown h2 397 | { 398 | margin: 0; 399 | padding: 0; 400 | padding-left: 20px; 401 | margin-top: 6px; 402 | margin-left: 7.5px; 403 | font-size: medium; 404 | } 405 | 406 | div.drilldown h2.drilldown-type 407 | { 408 | background: url(../images/type-icon.png) no-repeat left center; 409 | } 410 | 411 | div.drilldown h2.drilldown-class 412 | { 413 | background: url(../images/class-icon.png) no-repeat left center; 414 | } 415 | 416 | div.drilldown h2.drilldown-module 417 | { 418 | background: url(../images/module-icon.png) no-repeat left center; 419 | } 420 | 421 | div.drilldown h2.drilldown-library 422 | { 423 | background: url(../images/library-icon.png) no-repeat left center; 424 | } 425 | 426 | div.drilldown h2.drilldown-query 427 | { 428 | background: url(../images/query-icon.png) no-repeat left center; 429 | } 430 | 431 | div.drilldown ul.drilldown-items, 432 | div.drilldown ul.drilldown-items li 433 | { 434 | display: block; 435 | margin: 0; 436 | padding: 0; 437 | } 438 | 439 | div.drilldown ul.drilldown-items li 440 | { 441 | padding-left: 7.5px; 442 | } 443 | 444 | div.drilldown ul.drilldown-items 445 | { 446 | margin-top: 5px; 447 | margin-bottom: 5px; 448 | } 449 | 450 | div.leading-link-box 451 | { 452 | margin-top: 15px; 453 | padding-top: 10px; 454 | border-top: 1px solid #c1c1ec; 455 | border-bottom: 1px solid #c1c1ec; 456 | padding-bottom: 10px; 457 | margin-bottom: 15px; 458 | } 459 | 460 | div.leading-link-box h3.leading-link-title 461 | { 462 | padding: 0; 463 | margin: 0; 464 | margin-left: 8px; 465 | padding-left: 30px; 466 | background: url(../images/leading-link-icon-medium.png) no-repeat left center; 467 | } 468 | 469 | div.leading-link-box ol.leading-links 470 | { 471 | padding-top: 0; 472 | padding-bottom: 0; 473 | margin-top: 0; 474 | margin-bottom: 5px; 475 | } 476 | 477 | div.result h2 478 | { 479 | margin: 0; 480 | padding: 0; 481 | padding-left: 15px; 482 | display: none; 483 | } 484 | 485 | dl.entries 486 | { 487 | border-collapse: separate; 488 | border-spacing: 0; 489 | border-bottom: 1px solid #c1c1ec; 490 | margin-top: 1em; 491 | margin-bottom: 1em; 492 | } 493 | 494 | dl.entries dt 495 | { 496 | border-top: 1px solid #c1c1ec; 497 | padding-top: 10px; 498 | padding-bottom: 0; 499 | padding-left: 15px; 500 | } 501 | 502 | dl.entries dt h3 503 | { 504 | margin: 0; 505 | padding: 0; 506 | } 507 | 508 | dl.entries dd 509 | { 510 | margin-top: 0; 511 | margin-bottom: 1em; 512 | margin-left: 0; 513 | margin-left: 15px; 514 | padding-left: 0; 515 | } 516 | 517 | dl.entries dd ul.entry-links, 518 | dl.entries dd ul.entry-metadata, 519 | dl.entries dd ul.entry-related-entries 520 | { 521 | margin: 0; 522 | padding: 0; 523 | } 524 | 525 | dl.entries dd ul.entry-links li, 526 | dl.entries dd ul.entry-metadata li, 527 | dl.entries dd ul.entry-related-entries li 528 | { 529 | display: inline; 530 | } 531 | 532 | dl.entries dd ul.entry-links 533 | { 534 | margin-bottom: 1em; 535 | } 536 | 537 | dl.entries dd ul.entry-links li.entry-version 538 | { 539 | padding-left: 20px; 540 | background: url(../images/version-icon.png) no-repeat left bottom; 541 | } 542 | 543 | dl.entries dd ul.entry-metadata li.entry-type 544 | { 545 | padding-left: 20px; 546 | background: url(../images/type-icon.png) no-repeat left 3px; 547 | } 548 | 549 | /* 550 | dl.entries dd ul.entry-metadata li.entry-type span.entry-instance-method, 551 | dl.entries dd ul.entry-related-entries li.entry-related-entry-instance-method, 552 | div.drilldown ul.drilldown-items li.drilldown-item-instance-method 553 | { 554 | padding-left: 20px; 555 | background: url(../images/instance-method-icon.png) no-repeat left 3px; 556 | } 557 | 558 | dl.entries dd ul.entry-metadata li.entry-type span.entry-class, 559 | dl.entries dd ul.entry-related-entries li.entry-related-entry-class, 560 | div.drilldown ul.drilldown-items li.drilldown-item-class 561 | { 562 | padding-left: 20px; 563 | background: url(../images/class-icon.png) no-repeat left 3px; 564 | } 565 | 566 | dl.entries dd ul.entry-metadata li.entry-type span.entry-function, 567 | dl.entries dd ul.entry-related-entries li.entry-related-entry-function, 568 | div.drilldown ul.drilldown-items li.drilldown-item-function 569 | { 570 | padding-left: 20px; 571 | background: url(../images/function-icon.png) no-repeat left 3px; 572 | } 573 | 574 | dl.entries dd ul.entry-metadata li.entry-type span.entry-module-function, 575 | dl.entries dd ul.entry-related-entries li.entry-related-entry-module-function, 576 | div.drilldown ul.drilldown-items li.drilldown-item-module-function 577 | { 578 | padding-left: 20px; 579 | background: url(../images/module-function-icon.png) no-repeat left 3px; 580 | } 581 | 582 | dl.entries dd ul.entry-metadata li.entry-type span.entry-library, 583 | dl.entries dd ul.entry-related-entries li.entry-related-entry-library, 584 | div.drilldown ul.drilldown-items li.drilldown-item-library 585 | { 586 | padding-left: 20px; 587 | background: url(../images/library-icon.png) no-repeat left 3px; 588 | } 589 | 590 | dl.entries dd ul.entry-metadata li.entry-type span.entry-module, 591 | dl.entries dd ul.entry-related-entries li.entry-related-entry-module, 592 | div.drilldown ul.drilldown-items li.drilldown-item-module 593 | { 594 | padding-left: 20px; 595 | background: url(../images/module-icon.png) no-repeat left 3px; 596 | } 597 | */ 598 | 599 | dl.entries dd div.entry-document 600 | { 601 | margin-left: 1em; 602 | } 603 | 604 | dl.entries dd div.entry-document span.entry-link a 605 | { 606 | padding-left: 20px; 607 | background: url(../images/document-icon.png) no-repeat left bottom; 608 | } 609 | 610 | dl.entries dd div.entry-document div.entry-snippets 611 | { 612 | margin-bottom: 30px; 613 | background: white; 614 | border: 1px solid #c1c1ec; 615 | -moz-border-radius: 5px; 616 | -webkit-border-radius: 5px; 617 | border-radius: 5px; 618 | -moz-box-shadow: 0.2em 0.2em 0.5em rgb(100, 100, 100); 619 | -webkit-box-shadow: 0.2em 0.2em 0.5em rgb(100, 100, 100); 620 | box-shadow: 0.2em 0.2em 0.5em rgb(100, 100, 100); 621 | padding: 10px; 622 | } 623 | 624 | dl.entries dd div.entry-document div.entry-snippets div.snippet 625 | { 626 | } 627 | 628 | span.score 629 | { 630 | color: #ccc; 631 | font-size: smaller; 632 | } 633 | 634 | span.keyword 635 | { 636 | font-weight: bold; 637 | } 638 | 639 | div.snippets 640 | { 641 | margin-right: 3em; 642 | margin-left: 20px; 643 | margin-bottom: 0.25em; 644 | } 645 | 646 | div.snippet 647 | { 648 | margin-top: 0.25em; 649 | margin-bottom: 0.25em; 650 | } 651 | 652 | div.snippet span.separator 653 | { 654 | font-weight: bold; 655 | } 656 | 657 | div.paginate 658 | { 659 | margin-left: auto; 660 | margin-right: auto; 661 | text-align: center; 662 | } 663 | 664 | div.paginate a 665 | { 666 | text-decoration: none; 667 | } 668 | 669 | div.paginate span.paginate-link a, 670 | div.paginate span.paginate-text, 671 | div.paginate span.paginate-current 672 | { 673 | border: 1px solid #c1c1ec; 674 | vertical-align: middle; 675 | padding: 0.2em 0.4em; 676 | } 677 | 678 | div.paginate span.paginate-link a:hover, 679 | div.paginate span.paginate-link a:focus 680 | { 681 | color: white; 682 | background-color: #4c4cc6; 683 | } 684 | 685 | div.paginate span.paginate-text 686 | { 687 | color: #aaa; 688 | } 689 | 690 | div.paginate span.paginate-current 691 | { 692 | color: white; 693 | background-color: #4c4cc6; 694 | } 695 | 696 | div.no-result 697 | { 698 | padding-left: 1em; 699 | } 700 | 701 | div.message 702 | { 703 | border-top: 1px solid #c1c1ec; 704 | margin: 0; 705 | padding: 5em; 706 | } 707 | 708 | div.footer 709 | { 710 | border-top: 1px solid #c1c1ec; 711 | padding-top: 0.5em; 712 | font-size: smaller; 713 | color: #aaa; 714 | } 715 | 716 | div.footer p 717 | { 718 | padding: 0; 719 | margin: 0; 720 | text-align: center; 721 | } 722 | 723 | p.jump-to-top 724 | { 725 | text-align: right; 726 | } 727 | 728 | p.jump-to-top a 729 | { 730 | padding-left: 20px; 731 | background: url(../images/change-query-icon.png) no-repeat left bottom; 732 | } 733 | -------------------------------------------------------------------------------- /public/images/library-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 24 | 26 | 33 | 36 | 40 | 44 | 45 | 47 | 51 | 55 | 59 | 60 | 62 | 66 | 70 | 74 | 75 | 85 | 88 | 92 | 96 | 97 | 99 | 103 | 107 | 108 | 110 | 114 | 118 | 119 | 121 | 125 | 129 | 130 | 140 | 150 | 160 | 170 | 181 | 191 | 202 | 212 | 223 | 234 | 235 | 257 | 259 | 260 | 262 | image/svg+xml 263 | 265 | Addess Book - New 266 | 267 | 268 | 269 | Jakub Steiner 270 | 271 | 272 | http://jimmac.musichall.cz 273 | 274 | 275 | address 276 | contact 277 | book 278 | 279 | 280 | 282 | 283 | 285 | 287 | 289 | 291 | 292 | 293 | 294 | 298 | 303 | 308 | 313 | 318 | 323 | 328 | 331 | 338 | 343 | 348 | 349 | 354 | 358 | 363 | 371 | 376 | 377 | 378 | -------------------------------------------------------------------------------- /public/images/rurema-search-title.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 26 | 30 | 34 | 35 | 42 | 51 | 62 | 71 | 82 | 93 | 103 | 113 | 115 | 119 | 123 | 127 | 128 | 130 | 134 | 138 | 139 | 141 | 145 | 149 | 150 | 153 | 157 | 161 | 162 | 165 | 169 | 173 | 174 | 176 | 180 | 184 | 188 | 189 | 191 | 195 | 199 | 200 | 207 | 218 | 229 | 231 | 235 | 239 | 240 | 242 | 246 | 250 | 251 | 253 | 257 | 261 | 262 | 264 | 268 | 272 | 276 | 277 | 279 | 283 | 287 | 291 | 292 | 299 | 306 | 310 | 311 | 312 | 330 | 332 | 333 | 335 | image/svg+xml 336 | 338 | 339 | 340 | 341 | 342 | 347 | るりまサーチ 358 | るりまサーチ 369 | 373 | 375 | 385 | 390 | 394 | 399 | 409 | 419 | 429 | 439 | 443 | 444 | 445 | 446 | 447 | --------------------------------------------------------------------------------