diff --git a/.travis.yml b/.travis.yml index dfad1929..266c2bfe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,26 +1,16 @@ -sudo: false language: ruby -cache: bundler -bundler_args: "--deployment --without development" rvm: - - 2.1.10 - - 2.2.3 - - 2.2.5 - 2.3.0 - 2.3.1 services: - mysql before_script: - cp config/database.yml.travis config/database.yml - - mysql -u root -e 'create database IF NOT EXISTS logarchiver_test;' -script: - - bundle exec rake --trace db:create - - bundle exec rake --trace db:migrate - - bundle exec rake test + - mysql -e 'CREATE DATABASE IF NOT EXISTS log_archiver_test;' notifications: irc: use_notice: true - channels: + channels: - 'irc.trpg.net#irc_test' on_success: always on_failure: always diff --git a/Gemfile b/Gemfile index 142de802..11c19d95 100644 --- a/Gemfile +++ b/Gemfile @@ -21,13 +21,18 @@ gem 'coffee-rails', '~> 4.1.0' # Use jquery as the JavaScript library gem 'jquery-rails' # Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks -gem 'turbolinks' +#gem 'turbolinks' # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder gem 'jbuilder', '~> 2.0' # bundle exec rake doc:rails generates the API under doc/api. gem 'sdoc', '~> 0.4.0', group: :doc -gem 'pry-rails' +# デザイン +gem 'bootstrap-sass' +gem 'font-awesome-rails' + +# SEO +gem 'meta-tags' # Use ActiveModel has_secure_password # gem 'bcrypt', '~> 3.1.7' @@ -38,12 +43,19 @@ gem 'pry-rails' # Use Capistrano for deployment # gem 'capistrano-rails', group: :development +gem "simple_calendar", "~> 2.0" + group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug' gem 'factory_girl_rails' gem 'minitest-reporters' + + gem 'guard' + gem 'guard-minitest' + + gem 'coveralls', require: false end group :development do @@ -51,6 +63,8 @@ group :development do gem 'web-console', '~> 2.0' # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring - #gem 'spring' -end + gem 'spring' + # コンソールとして pry を使う + gem 'pry-rails' +end diff --git a/Gemfile.lock b/Gemfile.lock index c9727449..b8fe725d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -38,8 +38,13 @@ GEM tzinfo (~> 1.1) ansi (1.5.0) arel (6.0.3) + autoprefixer-rails (6.4.0.3) + execjs binding_of_caller (0.7.2) debug_inspector (>= 0.0.1) + bootstrap-sass (3.3.7) + autoprefixer-rails (>= 5.2.1) + sass (>= 3.3.4) builder (3.2.2) byebug (9.0.5) cinch (2.3.2) @@ -52,7 +57,14 @@ GEM execjs coffee-script-source (1.10.0) concurrent-ruby (1.0.2) + coveralls (0.8.15) + json (>= 1.8, < 3) + simplecov (~> 0.12.0) + term-ansicolor (~> 1.3) + thor (~> 0.19.1) + tins (>= 1.6.0, < 2) debug_inspector (0.0.2) + docile (1.1.5) erubis (2.7.0) execjs (2.7.0) factory_girl (4.7.0) @@ -60,22 +72,45 @@ GEM factory_girl_rails (4.7.0) factory_girl (~> 4.7.0) railties (>= 3.0.0) + ffi (1.9.14) + font-awesome-rails (4.6.3.1) + railties (>= 3.2, < 5.1) + formatador (0.2.5) globalid (0.3.7) activesupport (>= 4.1.0) + guard (2.14.0) + formatador (>= 0.2.4) + listen (>= 2.7, < 4.0) + lumberjack (~> 1.0) + nenv (~> 0.1) + notiffany (~> 0.0) + pry (>= 0.9.12) + shellany (~> 0.0) + thor (>= 0.18.1) + guard-compat (1.2.1) + guard-minitest (2.4.6) + guard-compat (~> 1.2) + minitest (>= 3.0) i18n (0.7.0) jbuilder (2.6.0) activesupport (>= 3.0.0, < 5.1) multi_json (~> 1.2) - jquery-rails (4.1.1) + jquery-rails (4.2.1) rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) json (1.8.3) + listen (3.1.5) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + ruby_dep (~> 1.2) loofah (2.0.3) nokogiri (>= 1.5.9) lumberjack (1.0.10) mail (2.6.4) mime-types (>= 1.16, < 4) + meta-tags (2.2.0) + actionpack (>= 3.2.0) method_source (0.8.2) mime-types (3.1) mime-types-data (~> 3.2015) @@ -89,9 +124,13 @@ GEM ruby-progressbar multi_json (1.12.1) mysql2 (0.4.4) + nenv (0.3.0) nokogiri (1.6.8) mini_portile2 (~> 2.1.0) pkg-config (~> 1.1.7) + notiffany (0.1.1) + nenv (~> 0.1) + shellany (~> 0.0) pkg-config (1.1.7) pry (0.10.4) coderay (~> 1.1.0) @@ -127,9 +166,13 @@ GEM rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) rake (11.2.2) + rb-fsevent (0.9.7) + rb-inotify (0.9.7) + ffi (>= 0.5.0) rdoc (4.2.2) json (~> 1.4) ruby-progressbar (1.8.1) + ruby_dep (1.4.0) sass (3.4.22) sass-rails (5.0.6) railties (>= 4.0.0, < 6) @@ -140,7 +183,16 @@ GEM sdoc (0.4.1) json (~> 1.7, >= 1.7.7) rdoc (~> 4.0) + shellany (0.0.1) + simple_calendar (2.1.5) + rails (>= 3.0) + simplecov (0.12.0) + docile (~> 1.1.0) + json (>= 1.8, < 3) + simplecov-html (~> 0.10.0) + simplecov-html (0.10.0) slop (3.6.0) + spring (1.7.2) sprockets (3.7.0) concurrent-ruby (~> 1.0) rack (> 1, < 3) @@ -149,15 +201,15 @@ GEM activesupport (>= 4.0) sprockets (>= 3.0.0) sysexits (1.2.0) + term-ansicolor (1.3.2) + tins (~> 1.0) thor (0.19.1) thread_safe (0.3.5) tilt (2.0.5) - turbolinks (5.0.1) - turbolinks-source (~> 5) - turbolinks-source (5.0.0) + tins (1.12.0) tzinfo (1.2.2) thread_safe (~> 0.1) - uglifier (3.0.1) + uglifier (3.0.2) execjs (>= 0.3.0, < 3) web-console (2.3.0) activemodel (>= 4.0) @@ -169,21 +221,28 @@ PLATFORMS ruby DEPENDENCIES + bootstrap-sass byebug cinch coffee-rails (~> 4.1.0) + coveralls factory_girl_rails + font-awesome-rails + guard + guard-minitest jbuilder (~> 2.0) jquery-rails lumberjack + meta-tags minitest-reporters mysql2 (>= 0.3.13, < 0.5) pry-rails rails (= 4.2.6) sass-rails (~> 5.0) sdoc (~> 0.4.0) + simple_calendar (~> 2.0) + spring sysexits - turbolinks uglifier (>= 1.3.0) web-console (~> 2.0) diff --git a/Guardfile b/Guardfile new file mode 100644 index 00000000..99529652 --- /dev/null +++ b/Guardfile @@ -0,0 +1,56 @@ +# vim: filetype=ruby + +guard :minitest, spring: true, all_on_start: false do + watch(%r{^test/(.*)/?(.*)_test\.rb$}) + watch('test/test_helper.rb') { 'test' } + watch('config/routes.rb') { integration_tests } + watch(%r{^app/models/(.*?)\.rb$}) do |matches| + "test/models/#{matches[1]}_test.rb" + end + watch(%r{^app/controllers/(.*?)_controller\.rb$}) do |matches| + resource_tests(matches[1]) + end + watch(%r{^app/views/([^/]*?)/.*\.html\.erb$}) do |matches| + ["test/controllers/#{matches[1]}_controller_test.rb"] + + integration_tests(matches[1]) + end + watch(%r{^app/helpers/(.*?)_helper\.rb$}) do |matches| + integration_tests(matches[1]) + end + watch('app/views/layouts/application.html.erb') do + 'test/integration/site_layout_test.rb' + end + watch('app/helpers/sessions_helper.rb') do + integration_tests << 'test/helpers/sessions_helper_test.rb' + end + watch('app/controllers/sessions_controller.rb') do + ['test/controllers/sessions_controller_test.rb', + 'test/integration/users_login_test.rb'] + end + watch('app/controllers/account_activations_controller.rb') do + 'test/integration/users_signup_test.rb' + end + watch(%r{app/views/users/*}) do + resource_tests('users') + + ['test/integration/microposts_interface_test.rb'] + end +end + +# Returns the integration tests corresponding to the given resource. +def integration_tests(resource = :all) + if resource == :all + Dir["test/integration/*"] + else + Dir["test/integration/#{resource}_*.rb"] + end +end + +# Returns the controller tests corresponding to the given resource. +def controller_test(resource) + "test/controllers/#{resource}_controller_test.rb" +end + +# Returns all tests for the given resource. +def resource_tests(resource) + integration_tests(resource) << controller_test(resource) +end diff --git a/README.md b/README.md index 09ce2050..8992299c 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,16 @@ # Log Archiver [![Build Status](https://travis-ci.org/cre-ne-jp/log-archiver.svg?branch=master)](https://travis-ci.org/cre-ne-jp/log-archiver) +[![Coverage Status](https://coveralls.io/repos/github/cre-ne-jp/log-archiver/badge.svg?branch=master)](https://coveralls.io/github/cre-ne-jp/log-archiver?branch=master) IRC ボットを常駐させることでチャットログをチャンネル単位で RDBMS に直接記録し、Rails アプリケーションにより記録されたログを整形・表示します。 - ## 動作環境 -* Linux または MacOSX - * 現在のところ Windows には未対応。 -* Ruby 2.1.0 以降 +* Linux または OSX +* Ruby 2.3.0 以降 * MySQL または MariaDB - # インストール [MySQL](https://www-jp.mysql.com/) もしくは [MariaDB](https://mariadb.org/) をインストールしていない場合はインストールしてください。 @@ -27,8 +25,7 @@ gem install bundler 上記が完了したら、適当なディレクトリにファイルを設置し、以下を実行して必要な gem(ライブラリ)をインストールしてください。 -なお、gem をインストールするためには、システムにいくつかのライブラリと開発環境がインストールされている必要があります。 -CentOS 7 を最小限構成でセットアップしている場合、以下の追加パッケージが必要です。 +なお、gem をインストールするためには、システムにいくつかのライブラリと開発環境がインストールされている必要があります。CentOS 7 を最小限構成でセットアップしている場合、以下の追加パッケージが必要です。 * make * gcc @@ -37,18 +34,12 @@ CentOS 7 を最小限構成でセットアップしている場合、以下の * zlib-devel * mariadb-devel -```bash -cd /path/to/log-archiver -``` - - ## 設定 * [IRC の接続設定](doc/irc.md) * [データベースの接続設定](doc/database.md) * [ウェブサーバの設定](doc/nginx.md) - ## IRC ボットの起動 IRC ボットを起動するには、以下を実行してください。Ctrl + C を押すと終了します。 @@ -67,13 +58,11 @@ bin/ircbot -c test # /path/to/log-archiver/config/test.yaml を使用する場 systemd による制御を行なう場合は [systemd](doc/systemd.md) を参照してください。 - ## 出力部の起動 出力部は Rails アプリケーションとして実装されています。 -単体で起動させることもできますが、[Apache]() や [nginx]() からのリバースプロキシ設定を行なうことをお勧めします。 - +単体で起動させることもできますが、Apache や nginx からのリバースプロキシ設定を行なうことをお勧めします。 ## 連絡先 @@ -91,3 +80,4 @@ systemd による制御を行なう場合は [systemd](doc/systemd.md) を参照 * 鯉([@koi-chan](https://github.com/koi-chan)) * ocha([@ochaochaocha3](https://github.com/ochaochaocha3)) +* らぁ([@raa0121](https://github.com/raa0121)) diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index e07c5a83..750d3717 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -12,5 +12,4 @@ // //= require jquery //= require jquery_ujs -//= require turbolinks -//= require_tree . +//= require bootstrap-sprockets diff --git a/app/assets/javascripts/channels.coffee b/app/assets/javascripts/channels.coffee deleted file mode 100644 index 24f83d18..00000000 --- a/app/assets/javascripts/channels.coffee +++ /dev/null @@ -1,3 +0,0 @@ -# Place all the behaviors and hooks related to the matching controller here. -# All this logic will automatically be available in application.js. -# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/stylesheets/_bootswatch.scss b/app/assets/stylesheets/_bootswatch.scss new file mode 100644 index 00000000..081f1d50 --- /dev/null +++ b/app/assets/stylesheets/_bootswatch.scss @@ -0,0 +1,443 @@ +// Yeti 3.3.7 +// Bootswatch +// ----------------------------------------------------- + +$web-font-path: "https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,700italic,400,300,700" !default; +@import url($web-font-path); + +// Navbar ===================================================================== + +.navbar { + border: none; + font-size: 13px; + font-weight: 400; + + .navbar-toggle:hover .icon-bar { + background-color: #b3b3b3; + } + + &-collapse { + border-top-color: $dropdown-divider-bg; + @include box-shadow(none); + } + + .btn { + padding-top: 6px; + padding-bottom: 6px; + } + + &-form { + margin-top: 7px; + margin-bottom: 5px; + + .form-control { + height: auto; + padding: $padding-xs-vertical $padding-xs-horizontal; + } + } + + &-text { + margin: 12px 15px; + line-height: 21px; + } + + .dropdown { + + &-menu { + border: none; + + > li > a, + > li > a:focus { + background-color: transparent; + font-size: 13px; + font-weight: 300; + } + } + + &-header { + color: rgba(255, 255, 255, 0.5); + } + + } + + &-default { + + .dropdown-menu { + background-color: $navbar-default-bg; + + > li > a, + > li > a:focus { + color: $navbar-default-color; + } + + > li > a:hover, + > .active > a, + > .active > a:hover { + background-color: $navbar-default-link-hover-bg; + } + } + } + + &-inverse { + + .dropdown-menu { + background-color: $navbar-inverse-bg; + + > li > a, + > li > a:focus { + color: $navbar-inverse-color; + } + + > li > a:hover, + > .active > a, + > .active > a:hover { + background-color: $navbar-inverse-link-hover-bg; + } + } + } +} + +// Buttons ==================================================================== + +.btn { + padding: $padding-base-vertical $padding-base-horizontal; + + &-lg { + padding: $padding-large-vertical $padding-large-horizontal; + } + + &-sm { + padding: $padding-small-vertical $padding-small-horizontal; + } + + &-xs { + padding: $padding-xs-vertical $padding-xs-horizontal; + } +} + +.btn-group { + + .btn ~ .dropdown-toggle { + padding-left: 16px; + padding-right: 16px; + } + + .dropdown-menu { + border-top-width: 0; + } + + &.dropup .dropdown-menu { + border-top-width: 1px; + border-bottom-width: 0; + margin-bottom: 0; + } + + .dropdown-toggle { + + &.btn-default ~ .dropdown-menu { + background-color: $btn-default-bg; + border-color: $btn-default-border; + + > li > a { + color: $btn-default-color; + } + + > li > a:hover { + background-color: darken($btn-default-bg, 8%); + } + } + + &.btn-primary ~ .dropdown-menu { + background-color: $btn-primary-bg; + border-color: $btn-primary-border; + + > li > a { + color: $btn-primary-color; + } + + > li > a:hover { + background-color: darken($btn-primary-bg, 8%); + } + } + + &.btn-success ~ .dropdown-menu { + background-color: $btn-success-bg; + border-color: $btn-success-border; + + > li > a { + color: $btn-success-color; + } + + > li > a:hover { + background-color: darken($btn-success-bg, 8%); + } + } + + &.btn-info ~ .dropdown-menu { + background-color: $btn-info-bg; + border-color: $btn-info-border; + + > li > a { + color: $btn-info-color; + } + + > li > a:hover { + background-color: darken($btn-info-bg, 8%); + } + } + + &.btn-warning ~ .dropdown-menu { + background-color: $btn-warning-bg; + border-color: $btn-warning-border; + + > li > a { + color: $btn-warning-color; + } + + > li > a:hover { + background-color: darken($btn-warning-bg, 8%); + } + } + + &.btn-danger ~ .dropdown-menu { + background-color: $btn-danger-bg; + border-color: $btn-danger-border; + + > li > a { + color: $btn-danger-color; + } + + > li > a:hover { + background-color: darken($btn-danger-bg, 8%); + } + } + } +} + +// Typography ================================================================= + +.lead { + color: $gray; +} + +cite { + font-style: italic; +} + +blockquote { + border-left-width: 1px; + color: $gray; + + &.pull-right { + border-right-width: 1px; + } + + small { + font-size: $font-size-small; + font-weight: 300; + } +} + +// Tables ===================================================================== + +table { +} + +// Forms ====================================================================== + +label, +.control-label, +.help-block, +.checkbox, +.radio { + font-size: $font-size-small; + font-weight: $headings-font-weight; +} + +input[type="radio"], +input[type="checkbox"] { + margin-top: 1px; +} + +// Navs ======================================================================= + +.nav { + .open > a, + .open > a:hover, + .open > a:focus { + border-color: transparent; + } +} + +.nav-tabs { + > li > a { + background-color: $btn-default-bg; + color: $text-color; + } + + .caret { + border-top-color: $text-color; + border-bottom-color: $text-color; + } +} + +.nav-pills { + font-weight: 300; +} + +.breadcrumb { + border: 1px solid $table-border-color; + border-radius: 3px; + font-size: $font-size-small; + font-weight: 300; +} + +.pagination { + font-size: $font-size-small; + font-weight: 300; + color: $gray-light; + + > li { + > a, + > span { + margin-left: 4px; + color: $gray-light; + } + } + + > .active { + > a, + > span { + color: #fff; + } + } + + > li, + > li:first-child, + > li:last-child { + > a, + > span { + border-radius: 3px; + } + } + + &-lg > li > a, + &-lg > li > span { + padding-left: 22px; + padding-right: 22px; + } + + &-sm > li > a, + &-sm > li > span { + padding: 0 5px; + } +} + +.pager { + font-size: $font-size-small; + font-weight: 300; + color: $gray-light; +} + +.list-group { + font-size: $font-size-small; + font-weight: 300; +} + +// Indicators ================================================================= + +.close { + opacity: 0.4; + text-decoration: none; + text-shadow: none; + + &:hover, + &:focus { + opacity: 1; + } +} + +.alert { + font-size: $font-size-small; + font-weight: 300; + + .alert-link { + font-weight: normal; + color: #fff; + text-decoration: underline; + } +} + +.label { + padding-left: 1em; + padding-right: 1em; + border-radius: 0; + font-weight: 300; + + &-default { + background-color: $btn-default-bg; + color: $btn-default-color; + } +} + +.badge { + font-weight: 300; +} + +// Progress bars ============================================================== + +.progress { + height: 22px; + padding: 2px; + background-color: #f6f6f6; + border: 1px solid #ccc; + @include box-shadow(none); +} + +// Containers ================================================================= + +.dropdown { + + &-menu { + padding: 0; + margin-top: 0; + font-size: $font-size-small; + + > li > a { + padding: 12px 15px; + } + } + + &-header { + padding-left: 15px; + padding-right: 15px; + font-size: 9px; + text-transform: uppercase; + } +} + +.popover { + color: #fff; + font-size: 12px; + font-weight: 300; +} + +.panel { + &-heading, + &-footer { + border-top-right-radius: 0; + border-top-left-radius: 0; + } + + &-default { + .close { + color: $text-color; + } + } +} + +.modal { + .close { + color: $text-color; + } +} diff --git a/app/assets/stylesheets/_custom.scss b/app/assets/stylesheets/_custom.scss new file mode 100644 index 00000000..09f48dba --- /dev/null +++ b/app/assets/stylesheets/_custom.scss @@ -0,0 +1,137 @@ +/* vim: :set expandtab ts=2 sts=2 sw=2: */ +body { + padding-top: 70px; +} + +.page-header { + margin-top: $line-height-computed; +} + +h1.title { + text-align: center; + font-weight: normal; +} + +h2 { + padding-bottom: 0.25em; + border-bottom: 1px solid $page-header-border-color; +} + +h3 { + margin-top: 12.5px; +} + +.num-col { + text-align: right; +} + +.message-time { + white-space: nowrap; + width: 1em; +} + +.message-content { + word-break: break-all; +} + +dl.message { + margin: 0; +} + +b.nickname.message-privmsg { + position: relative; + padding: 0em 0.25em; + border-radius: 4px; + background-color: $gray-light; + color: white; +} + +aside { + font-size: $font-size-small; + + h1 { + font-size: $font-size-h5; + } +} + +.simple-calendar { + .calendar-heading, th, .day { + text-align: center; + } +} + +.server-panel { + .panel-title { + font-size: $font-size-h3; + text-align: center; + } + + .progress { + height: $font-size-base; + margin-bottom: 0.5em; + } + + h4 { + margin-bottom: 0.5em; + font-size: $font-size-base; + } +} + +.ratio { + font-size: $font-size-small; + text-align: center; +} + +strong.user-status-full { + color: $brand-danger; + font-weight: bold; +} + +em.user-status-warning { + color: $brand-warning; + font-style: normal; +} + +ul, ol { + padding-left: 2em; +} + +.text-prohibited { + $prohibited-color: #a94442; + color: $prohibited-color; +} + +.prohibited-conduct { + font-weight: bold; +} + +.maintenance_id, .server_id { + text-align: right; +} + +.maintenance_done { + text-align: center; +} + +footer { + margin-top: 2em; + padding: 2em 0 4em; + background-color: $well-bg; + border-top: 1px solid $well-border; + + @media (max-width: $screen-xs-max) { + text-align: center; + } +} + +.list-unstyled.links { + li { + margin-bottom: ($line-height-computed / 2); + } +} + +.copyright { + @media (min-width: $screen-sm-min) { + text-align: right; + } +} diff --git a/app/assets/stylesheets/_variables.scss b/app/assets/stylesheets/_variables.scss new file mode 100644 index 00000000..a552aed2 --- /dev/null +++ b/app/assets/stylesheets/_variables.scss @@ -0,0 +1,870 @@ +$bootstrap-sass-asset-helper: false !default; +// Yeti 3.3.7 +// Variables +// -------------------------------------------------- + + +//== Colors +// +//## Gray and brand colors for use across Bootstrap. + +$gray-base: #000 !default; +$gray-darker: lighten($gray-base, 13.5%) !default; // #222 +$gray-dark: lighten($gray-base, 20%) !default; // #333 +$gray: #6f6f6f !default; // #555 +$gray-light: lighten($gray-base, 60%) !default; // #999 +$gray-lighter: lighten($gray-base, 93.5%) !default; // #eee + +$brand-primary: #008cba !default; +$brand-success: #43ac6a !default; +$brand-info: #5bc0de !default; +$brand-warning: #E99002 !default; +$brand-danger: #F04124 !default; + + +//== Scaffolding +// +//## Settings for some of the most global styles. + +//** Background color for ``. +$body-bg: #FDFDFD !default; +//** Global text color on ``. +$text-color: $gray-darker !default; + +//** Global textual link color. +$link-color: $brand-primary !default; +//** Link hover color set via `darken()` function. +$link-hover-color: $link-color !default; +//** Link hover decoration. +$link-hover-decoration: underline !default; + + +//== Typography +// +//## Font, line-height, and color for body text, headings, and more. + +$font-family-sans-serif: 'Open Sans','Helvetica Neue', Helvetica, 'Arial', 'Hiragino Kaku Gothic ProN', 'ヒラギノ角ゴ ProN W3', Meiryo, 'メイリオ', sans-serif !default; +$font-family-serif: 'Times New Roman', Times, '游明朝体', YuMincho, 'ヒラギノ明朝 ProN W3', 'Hiragino Mincho ProN', 'MS P明朝', 'MS PMincho', 'MS 明朝', serif !default; +//** Default monospace fonts for ``, ``, and `
`.
+$font-family-monospace:   Menlo, Monaco, Consolas, "Courier New", monospace !default;
+$font-family-base:        $font-family-sans-serif !default;
+
+$font-size-base:          16px !default;
+$font-size-large:         ceil(($font-size-base * 1.25)) !default; // ~20px
+$font-size-small:         14px !default; // ~14px
+
+$font-size-h1:            floor(($font-size-base * 2.75)) !default; // ~44px
+$font-size-h2:            floor(($font-size-base * 1.5)) !default; // ~24px
+$font-size-h3:            ceil(($font-size-base * 1.25)) !default; // ~20px
+$font-size-h4:            ceil(($font-size-base * 1.05)) !default; // ~17px
+$font-size-h5:            $font-size-base !default;
+$font-size-h6:            ceil(($font-size-base * 0.85)) !default; // ~13px
+
+//** Unit-less `line-height` for use in components like buttons.
+$line-height-base:        1.6 !default;
+//** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
+$line-height-computed:    floor(($font-size-base * $line-height-base)) !default; // ~26px
+
+//** By default, this inherits from the ``.
+$headings-font-family:    $font-family-base !default;
+$headings-font-weight:    600 !default;
+$headings-line-height:    1.25 !default;
+$headings-color:          inherit !default;
+
+
+//== Iconography
+//
+//## Specify custom location and filename of the included Glyphicons icon font. Useful for those including Bootstrap via Bower.
+
+//** Load fonts from this directory.
+$icon-font-path: if($bootstrap-sass-asset-helper, "bootstrap/", "../fonts/bootstrap/") !default;
+//** File name for all font files.
+$icon-font-name:          "glyphicons-halflings-regular" !default;
+//** Element ID within SVG icon file.
+$icon-font-svg-id:        "glyphicons_halflingsregular" !default;
+
+
+//== Components
+//
+//## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start).
+
+$padding-base-vertical:     8px !default;
+$padding-base-horizontal:   12px !default;
+
+$padding-large-vertical:    16px !default;
+$padding-large-horizontal:  20px !default;
+
+$padding-small-vertical:    8px !default;
+$padding-small-horizontal:  12px !default;
+
+$padding-xs-vertical:       4px !default;
+$padding-xs-horizontal:     6px !default;
+
+$line-height-large:         1.3333333 !default; // extra decimals for Win 8.1 Chrome
+$line-height-small:         1.5 !default;
+
+$border-radius-base:        0 !default;
+$border-radius-large:       0 !default;
+$border-radius-small:       0 !default;
+
+//** Global color for active items (e.g., navs or dropdowns).
+$component-active-color:    #fff !default;
+//** Global background color for active items (e.g., navs or dropdowns).
+$component-active-bg:       $brand-primary !default;
+
+//** Width of the `border` for generating carets that indicate dropdowns.
+$caret-width-base:          4px !default;
+//** Carets increase slightly in size for larger components.
+$caret-width-large:         5px !default;
+
+
+//== Tables
+//
+//## Customizes the `.table` component with basic values, each used across all table variations.
+
+//** Padding for ``s and ``s.
+$table-cell-padding:            8px !default;
+//** Padding for cells in `.table-condensed`.
+$table-condensed-cell-padding:  5px !default;
+
+//** Default background color used for all tables.
+$table-bg:                      transparent !default;
+//** Background color used for `.table-striped`.
+$table-bg-accent:               #f9f9f9 !default;
+//** Background color used for `.table-hover`.
+$table-bg-hover:                #f5f5f5 !default;
+$table-bg-active:               $table-bg-hover !default;
+
+//** Border color for table and cell borders.
+$table-border-color:            #ddd !default;
+
+
+//== Buttons
+//
+//## For each of Bootstrap's buttons, define text, background and border color.
+
+$btn-font-weight:                normal !default;
+
+$btn-default-color:              $gray-dark !default;
+$btn-default-bg:                 #e7e7e7 !default;
+$btn-default-border:             #ccc !default;
+
+$btn-primary-color:              #fff !default;
+$btn-primary-bg:                 $brand-primary !default;
+$btn-primary-border:             darken($btn-primary-bg, 5%) !default;
+
+$btn-success-color:              #fff !default;
+$btn-success-bg:                 $brand-success !default;
+$btn-success-border:             darken($btn-success-bg, 5%) !default;
+
+$btn-info-color:                 #fff !default;
+$btn-info-bg:                    $brand-info !default;
+$btn-info-border:                darken($btn-info-bg, 5%) !default;
+
+$btn-warning-color:              #fff !default;
+$btn-warning-bg:                 $brand-warning !default;
+$btn-warning-border:             darken($btn-warning-bg, 5%) !default;
+
+$btn-danger-color:               #fff !default;
+$btn-danger-bg:                  $brand-danger !default;
+$btn-danger-border:              darken($btn-danger-bg, 5%) !default;
+
+$btn-link-disabled-color:        $gray-light !default;
+
+// Allows for customizing button radius independently from global border radius
+$btn-border-radius-base:         $border-radius-base !default;
+$btn-border-radius-large:        $border-radius-large !default;
+$btn-border-radius-small:        $border-radius-small !default;
+
+
+//== Forms
+//
+//##
+
+//** `` background color
+$input-bg:                       #fff !default;
+//** `` background color
+$input-bg-disabled:              $gray-lighter !default;
+
+//** Text color for ``s
+$input-color:                    $gray !default;
+//** `` border color
+$input-border:                   #ccc !default;
+
+// TODO: Rename `$input-border-radius` to `$input-border-radius-base` in v4
+//** Default `.form-control` border radius
+// This has no effect on ``s in CSS.
+$input-border-radius:            $border-radius-base !default;
+//** Large `.form-control` border radius
+$input-border-radius-large:      $border-radius-large !default;
+//** Small `.form-control` border radius
+$input-border-radius-small:      $border-radius-small !default;
+
+//** Border color for inputs on focus
+$input-border-focus:             #66afe9 !default;
+
+//** Placeholder text color
+$input-color-placeholder:        $gray-light !default;
+
+//** Default `.form-control` height
+$input-height-base:              ($line-height-computed + ($padding-base-vertical * 2) + 2) !default;
+//** Large `.form-control` height
+$input-height-large:             (ceil($font-size-large * $line-height-large) + ($padding-large-vertical * 2) + 2) !default;
+//** Small `.form-control` height
+$input-height-small:             (floor($font-size-small * $line-height-small) + ($padding-small-vertical * 2) + 2) !default;
+
+//** `.form-group` margin
+$form-group-margin-bottom:       15px !default;
+
+$legend-color:                   $gray-dark !default;
+$legend-border-color:            #e5e5e5 !default;
+
+//** Background color for textual input addons
+$input-group-addon-bg:           $gray-lighter !default;
+//** Border color for textual input addons
+$input-group-addon-border-color: $input-border !default;
+
+//** Disabled cursor for form controls and buttons.
+$cursor-disabled:                not-allowed !default;
+
+
+//== Dropdowns
+//
+//## Dropdown menu container and contents.
+
+//** Background for the dropdown menu.
+$dropdown-bg:                    #fff !default;
+//** Dropdown menu `border-color`.
+$dropdown-border:                rgba(0,0,0,.15) !default;
+//** Dropdown menu `border-color` **for IE8**.
+$dropdown-fallback-border:       #ccc !default;
+//** Divider color for between dropdown items.
+$dropdown-divider-bg:            rgba(0,0,0,.2) !default;
+
+//** Dropdown link text color.
+$dropdown-link-color:            #555 !default;
+//** Hover color for dropdown links.
+$dropdown-link-hover-color:      darken($gray-dark, 5%) !default;
+//** Hover background for dropdown links.
+$dropdown-link-hover-bg:         #eee !default;
+
+//** Active dropdown menu item text color.
+$dropdown-link-active-color:     $component-active-color !default;
+//** Active dropdown menu item background color.
+$dropdown-link-active-bg:        $component-active-bg !default;
+
+//** Disabled dropdown menu item background color.
+$dropdown-link-disabled-color:   $gray-light !default;
+
+//** Text color for headers within dropdown menus.
+$dropdown-header-color:          $gray-light !default;
+
+//** Deprecated `$dropdown-caret-color` as of v3.1.0
+$dropdown-caret-color:           #000 !default;
+
+
+//-- Z-index master list
+//
+// Warning: Avoid customizing these values. They're used for a bird's eye view
+// of components dependent on the z-axis and are designed to all work together.
+//
+// Note: These variables are not generated into the Customizer.
+
+$zindex-navbar:            1000 !default;
+$zindex-dropdown:          1000 !default;
+$zindex-popover:           1060 !default;
+$zindex-tooltip:           1070 !default;
+$zindex-navbar-fixed:      1030 !default;
+$zindex-modal-background:  1040 !default;
+$zindex-modal:             1050 !default;
+
+
+//== Media queries breakpoints
+//
+//## Define the breakpoints at which your layout will change, adapting to different screen sizes.
+
+// Extra small screen / phone
+//** Deprecated `$screen-xs` as of v3.0.1
+$screen-xs:                  480px !default;
+//** Deprecated `$screen-xs-min` as of v3.2.0
+$screen-xs-min:              $screen-xs !default;
+//** Deprecated `$screen-phone` as of v3.0.1
+$screen-phone:               $screen-xs-min !default;
+
+// Small screen / tablet
+//** Deprecated `$screen-sm` as of v3.0.1
+$screen-sm:                  768px !default;
+$screen-sm-min:              $screen-sm !default;
+//** Deprecated `$screen-tablet` as of v3.0.1
+$screen-tablet:              $screen-sm-min !default;
+
+// Medium screen / desktop
+//** Deprecated `$screen-md` as of v3.0.1
+$screen-md:                  992px !default;
+$screen-md-min:              $screen-md !default;
+//** Deprecated `$screen-desktop` as of v3.0.1
+$screen-desktop:             $screen-md-min !default;
+
+// Large screen / wide desktop
+//** Deprecated `$screen-lg` as of v3.0.1
+$screen-lg:                  1200px !default;
+$screen-lg-min:              $screen-lg !default;
+//** Deprecated `$screen-lg-desktop` as of v3.0.1
+$screen-lg-desktop:          $screen-lg-min !default;
+
+// So media queries don't overlap when required, provide a maximum
+$screen-xs-max:              ($screen-sm-min - 1) !default;
+$screen-sm-max:              ($screen-md-min - 1) !default;
+$screen-md-max:              ($screen-lg-min - 1) !default;
+
+
+//== Grid system
+//
+//## Define your custom responsive grid.
+
+//** Number of columns in the grid.
+$grid-columns:              12 !default;
+//** Padding between columns. Gets divided in half for the left and right.
+$grid-gutter-width:         30px !default;
+// Navbar collapse
+//** Point at which the navbar becomes uncollapsed.
+$grid-float-breakpoint:     $screen-sm-min !default;
+//** Point at which the navbar begins collapsing.
+$grid-float-breakpoint-max: ($grid-float-breakpoint - 1) !default;
+
+
+//== Container sizes
+//
+//## Define the maximum width of `.container` for different screen sizes.
+
+// Small screen / tablet
+$container-tablet:             (720px + $grid-gutter-width) !default;
+//** For `$screen-sm-min` and up.
+$container-sm:                 $container-tablet !default;
+
+// Medium screen / desktop
+$container-desktop:            (940px + $grid-gutter-width) !default;
+//** For `$screen-md-min` and up.
+$container-md:                 $container-desktop !default;
+
+// Large screen / wide desktop
+$container-large-desktop:      (1140px + $grid-gutter-width) !default;
+//** For `$screen-lg-min` and up.
+$container-lg:                 $container-large-desktop !default;
+
+
+//== Navbar
+//
+//##
+
+// Basics of a navbar
+$navbar-height:                    45px !default;
+$navbar-margin-bottom:             $line-height-computed !default;
+$navbar-border-radius:             $border-radius-base !default;
+$navbar-padding-horizontal:        floor(($grid-gutter-width / 2)) !default;
+$navbar-padding-vertical:          (($navbar-height - $line-height-computed) / 2) !default;
+$navbar-collapse-max-height:       340px !default;
+
+$navbar-default-color:             #fff !default;
+$navbar-default-bg:                $gray-dark !default;
+$navbar-default-border:            darken($navbar-default-bg, 6.5%) !default;
+
+// Navbar links
+$navbar-default-link-color:                #fff !default;
+$navbar-default-link-hover-color:          #fff !default;
+$navbar-default-link-hover-bg:             #272727 !default;
+$navbar-default-link-active-color:         #fff !default;
+$navbar-default-link-active-bg:            #272727 !default;
+$navbar-default-link-disabled-color:       #ccc !default;
+$navbar-default-link-disabled-bg:          transparent !default;
+
+// Navbar brand label
+$navbar-default-brand-color:               $navbar-default-link-color !default;
+$navbar-default-brand-hover-color:         $navbar-default-brand-color !default;
+$navbar-default-brand-hover-bg:            transparent !default;
+
+// Navbar toggle
+$navbar-default-toggle-hover-bg:           transparent !default;
+$navbar-default-toggle-icon-bar-bg:        #fff !default;
+$navbar-default-toggle-border-color:       transparent !default;
+
+
+//=== Inverted navbar
+// Reset inverted navbar basics
+$navbar-inverse-color:                      #fff !default;
+$navbar-inverse-bg:                         $brand-primary !default;
+$navbar-inverse-border:                     darken($navbar-inverse-bg, 10%) !default;
+
+// Inverted navbar links
+$navbar-inverse-link-color:                 #fff !default;
+$navbar-inverse-link-hover-color:           #fff !default;
+$navbar-inverse-link-hover-bg:              darken($navbar-inverse-bg, 10%) !default;
+$navbar-inverse-link-active-color:          #fff !default;
+$navbar-inverse-link-active-bg:             darken($navbar-inverse-bg, 10%) !default;
+$navbar-inverse-link-disabled-color:        #444 !default;
+$navbar-inverse-link-disabled-bg:           transparent !default;
+
+// Inverted navbar brand label
+$navbar-inverse-brand-color:                $navbar-inverse-link-color !default;
+$navbar-inverse-brand-hover-color:          #fff !default;
+$navbar-inverse-brand-hover-bg:             transparent !default;
+
+// Inverted navbar toggle
+$navbar-inverse-toggle-hover-bg:            transparent !default;
+$navbar-inverse-toggle-icon-bar-bg:         #fff !default;
+$navbar-inverse-toggle-border-color:        transparent !default;
+
+
+//== Navs
+//
+//##
+
+//=== Shared nav styles
+$nav-link-padding:                          10px 15px !default;
+$nav-link-hover-bg:                         $gray-lighter !default;
+
+$nav-disabled-link-color:                   $gray-light !default;
+$nav-disabled-link-hover-color:             $gray-light !default;
+
+//== Tabs
+$nav-tabs-border-color:                     $table-border-color !default;
+
+$nav-tabs-link-hover-border-color:          $gray-lighter !default;
+
+$nav-tabs-active-link-hover-bg:             $body-bg !default;
+$nav-tabs-active-link-hover-color:          $gray !default;
+$nav-tabs-active-link-hover-border-color:   $table-border-color !default;
+
+$nav-tabs-justified-link-border-color:            $table-border-color !default;
+$nav-tabs-justified-active-link-border-color:     $body-bg !default;
+
+//== Pills
+$nav-pills-border-radius:                   $border-radius-base !default;
+$nav-pills-active-link-hover-bg:            $component-active-bg !default;
+$nav-pills-active-link-hover-color:         $component-active-color !default;
+
+
+//== Pagination
+//
+//##
+
+$pagination-color:                     $link-color !default;
+$pagination-bg:                        transparent !default;
+$pagination-border:                    transparent !default;
+
+$pagination-hover-color:               $link-hover-color !default;
+$pagination-hover-bg:                  $gray-lighter !default;
+$pagination-hover-border:              transparent !default;
+
+$pagination-active-color:              #fff !default;
+$pagination-active-bg:                 $brand-primary !default;
+$pagination-active-border:             transparent !default;
+
+$pagination-disabled-color:            $gray-light !default;
+$pagination-disabled-bg:               #fff !default;
+$pagination-disabled-border:           transparent !default;
+
+
+//== Pager
+//
+//##
+
+$pager-bg:                             $pagination-bg !default;
+$pager-border:                         $pagination-border !default;
+$pager-border-radius:                  3px !default;
+
+$pager-hover-bg:                       $pagination-hover-bg !default;
+
+$pager-active-bg:                      $pagination-active-bg !default;
+$pager-active-color:                   $pagination-active-color !default;
+
+$pager-disabled-color:                 $gray-light !default;
+
+
+//== Jumbotron
+//
+//##
+
+$jumbotron-padding:              30px !default;
+$jumbotron-color:                inherit !default;
+$jumbotron-bg:                   #fafafa !default;
+$jumbotron-heading-color:        inherit !default;
+$jumbotron-font-size:            ceil(($font-size-base * 1.5)) !default;
+$jumbotron-heading-font-size:    ceil(($font-size-base * 4.5)) !default;
+
+
+//== Form states and alerts
+//
+//## Define colors for form feedback states and, by default, alerts.
+
+$state-success-text:             $brand-success !default;
+$state-success-bg:               #dff0d8 !default;
+$state-success-border:           darken($state-success-text, 5%) !default;
+
+$state-info-text:                $brand-info !default;
+$state-info-bg:                  #d9edf7 !default;
+$state-info-border:              darken($state-info-text, 7%) !default;
+
+$state-warning-text:             $brand-warning !default;
+$state-warning-bg:               #fcf8e3 !default;
+$state-warning-border:           darken($state-warning-text, 5%) !default;
+
+$state-danger-text:              $brand-danger !default;
+$state-danger-bg:                #f2dede !default;
+$state-danger-border:            darken($state-danger-text, 5%) !default;
+
+
+//== Tooltips
+//
+//##
+
+//** Tooltip max width
+$tooltip-max-width:           200px !default;
+//** Tooltip text color
+$tooltip-color:               #fff !default;
+//** Tooltip background color
+$tooltip-bg:                  $gray-dark !default;
+$tooltip-opacity:             .9 !default;
+
+//** Tooltip arrow width
+$tooltip-arrow-width:         5px !default;
+//** Tooltip arrow color
+$tooltip-arrow-color:         $tooltip-bg !default;
+
+
+//== Popovers
+//
+//##
+
+//** Popover body background color
+$popover-bg:                          $gray-dark !default;
+//** Popover maximum width
+$popover-max-width:                   276px !default;
+//** Popover border color
+$popover-border-color:                transparent !default;
+//** Popover fallback border color
+$popover-fallback-border-color:       $gray-dark !default;
+
+//** Popover title background color
+$popover-title-bg:                    $popover-bg !default;
+
+//** Popover arrow width
+$popover-arrow-width:                 10px !default;
+//** Popover arrow color
+$popover-arrow-color:                 $popover-bg !default;
+
+//** Popover outer arrow width
+$popover-arrow-outer-width:           ($popover-arrow-width + 1) !default;
+//** Popover outer arrow color
+$popover-arrow-outer-color:           fadein($popover-border-color, 5%) !default;
+//** Popover outer arrow fallback color
+$popover-arrow-outer-fallback-color:  darken($popover-fallback-border-color, 20%) !default;
+
+
+//== Labels
+//
+//##
+
+//** Default label background color
+$label-default-bg:            $gray-light !default;
+//** Primary label background color
+$label-primary-bg:            $brand-primary !default;
+//** Success label background color
+$label-success-bg:            $brand-success !default;
+//** Info label background color
+$label-info-bg:               $brand-info !default;
+//** Warning label background color
+$label-warning-bg:            $brand-warning !default;
+//** Danger label background color
+$label-danger-bg:             $brand-danger !default;
+
+//** Default label text color
+$label-color:                 #fff !default;
+//** Default text color of a linked label
+$label-link-hover-color:      #fff !default;
+
+
+//== Modals
+//
+//##
+
+//** Padding applied to the modal body
+$modal-inner-padding:         20px !default;
+
+//** Padding applied to the modal title
+$modal-title-padding:         15px !default;
+//** Modal title line-height
+$modal-title-line-height:     $line-height-base !default;
+
+//** Background color of modal content area
+$modal-content-bg:                             #fff !default;
+//** Modal content border color
+$modal-content-border-color:                   rgba(0,0,0,.2) !default;
+//** Modal content border color **for IE8**
+$modal-content-fallback-border-color:          #999 !default;
+
+//** Modal backdrop background color
+$modal-backdrop-bg:           #000 !default;
+//** Modal backdrop opacity
+$modal-backdrop-opacity:      .5 !default;
+//** Modal header border color
+$modal-header-border-color:   #e5e5e5 !default;
+//** Modal footer border color
+$modal-footer-border-color:   $modal-header-border-color !default;
+
+$modal-lg:                    900px !default;
+$modal-md:                    600px !default;
+$modal-sm:                    300px !default;
+
+
+//== Alerts
+//
+//## Define alert colors, border radius, and padding.
+
+$alert-padding:               15px !default;
+$alert-border-radius:         $border-radius-base !default;
+$alert-link-font-weight:      bold !default;
+
+$alert-success-bg:            $brand-success !default;
+$alert-success-text:          #fff !default;
+$alert-success-border:        $state-success-border !default;
+
+$alert-info-bg:               $brand-info !default;
+$alert-info-text:             #fff !default;
+$alert-info-border:           $state-info-border !default;
+
+$alert-warning-bg:            $brand-warning !default;
+$alert-warning-text:          #fff !default;
+$alert-warning-border:        $state-warning-border !default;
+
+$alert-danger-bg:             $brand-danger !default;
+$alert-danger-text:           #fff !default;
+$alert-danger-border:         $state-danger-border !default;
+
+
+//== Progress bars
+//
+//##
+
+//** Background color of the whole progress component
+$progress-bg:                 #f5f5f5 !default;
+//** Progress bar text color
+$progress-bar-color:          #fff !default;
+//** Variable for setting rounded corners on progress bar.
+$progress-border-radius:      $border-radius-base !default;
+
+//** Default progress bar color
+$progress-bar-bg:             $brand-primary !default;
+//** Success progress bar color
+$progress-bar-success-bg:     $brand-success !default;
+//** Warning progress bar color
+$progress-bar-warning-bg:     $brand-warning !default;
+//** Danger progress bar color
+$progress-bar-danger-bg:      $brand-danger !default;
+//** Info progress bar color
+$progress-bar-info-bg:        $brand-info !default;
+
+
+//== List group
+//
+//##
+
+//** Background color on `.list-group-item`
+$list-group-bg:                 #fff !default;
+//** `.list-group-item` border color
+$list-group-border:             $table-border-color !default;
+//** List group border radius
+$list-group-border-radius:      $border-radius-base !default;
+
+//** Background color of single list items on hover
+$list-group-hover-bg:           #f5f5f5 !default;
+//** Text color of active list items
+$list-group-active-color:       $component-active-color !default;
+//** Background color of active list items
+$list-group-active-bg:          $component-active-bg !default;
+//** Border color of active list elements
+$list-group-active-border:      $list-group-active-bg !default;
+//** Text color for content within active list items
+$list-group-active-text-color:  lighten($list-group-active-bg, 40%) !default;
+
+//** Text color of disabled list items
+$list-group-disabled-color:      $gray-light !default;
+//** Background color of disabled list items
+$list-group-disabled-bg:         $gray-lighter !default;
+//** Text color for content within disabled list items
+$list-group-disabled-text-color: $list-group-disabled-color !default;
+
+$list-group-link-color:         #555 !default;
+$list-group-link-hover-color:   $list-group-link-color !default;
+$list-group-link-heading-color: #333 !default;
+
+
+//== Panels
+//
+//##
+
+$panel-bg:                    #fff !default;
+$panel-body-padding:          15px !default;
+$panel-heading-padding:       10px 15px !default;
+$panel-footer-padding:        $panel-heading-padding !default;
+$panel-border-radius:         $border-radius-base !default;
+
+//** Border color for elements within panels
+$panel-inner-border:          $table-border-color !default;
+$panel-footer-bg:             #f5f5f5 !default;
+
+$panel-default-text:          $gray-dark !default;
+$panel-default-border:        $table-border-color !default;
+$panel-default-heading-bg:    #f5f5f5 !default;
+
+$panel-primary-text:          #fff !default;
+$panel-primary-border:        $brand-primary !default;
+$panel-primary-heading-bg:    $brand-primary !default;
+
+$panel-success-text:          #fff !default;
+$panel-success-border:        $state-success-border !default;
+$panel-success-heading-bg:    $brand-success !default;
+
+$panel-info-text:             #fff !default;
+$panel-info-border:           $state-info-border !default;
+$panel-info-heading-bg:       $brand-info !default;
+
+$panel-warning-text:          #fff !default;
+$panel-warning-border:        $state-warning-border !default;
+$panel-warning-heading-bg:    $brand-warning !default;
+
+$panel-danger-text:           #fff !default;
+$panel-danger-border:         $state-danger-border !default;
+$panel-danger-heading-bg:     $brand-danger !default;
+
+
+//== Thumbnails
+//
+//##
+
+//** Padding around the thumbnail image
+$thumbnail-padding:           4px !default;
+//** Thumbnail background color
+$thumbnail-bg:                $body-bg !default;
+//** Thumbnail border color
+$thumbnail-border:            $table-border-color !default;
+//** Thumbnail border radius
+$thumbnail-border-radius:     $border-radius-base !default;
+
+//** Custom text color for thumbnail captions
+$thumbnail-caption-color:     $text-color !default;
+//** Padding around the thumbnail caption
+$thumbnail-caption-padding:   9px !default;
+
+
+//== Wells
+//
+//##
+
+$well-bg:                     $jumbotron-bg !default;
+$well-border:                 darken($well-bg, 7%) !default;
+
+
+//== Badges
+//
+//##
+
+$badge-color:                 #fff !default;
+//** Linked badge text color on hover
+$badge-link-hover-color:      #fff !default;
+$badge-bg:                    $btn-primary-bg !default;
+
+//** Badge text color in active nav link
+$badge-active-color:          $link-color !default;
+//** Badge background color in active nav link
+$badge-active-bg:             #fff !default;
+
+$badge-font-weight:           bold !default;
+$badge-line-height:           1 !default;
+$badge-border-radius:         10px !default;
+
+
+//== Breadcrumbs
+//
+//##
+
+$breadcrumb-padding-vertical:   8px !default;
+$breadcrumb-padding-horizontal: 15px !default;
+//** Breadcrumb background color
+$breadcrumb-bg:                 #f5f5f5 !default;
+//** Breadcrumb text color
+$breadcrumb-color:              $gray-light !default;
+//** Text color of current page in the breadcrumb
+$breadcrumb-active-color:       $gray-dark !default;
+//** Textual separator for between breadcrumb elements
+$breadcrumb-separator:          "/" !default;
+
+
+//== Carousel
+//
+//##
+
+$carousel-text-shadow:                        0 1px 2px rgba(0,0,0,.6) !default;
+
+$carousel-control-color:                      #fff !default;
+$carousel-control-width:                      15% !default;
+$carousel-control-opacity:                    .5 !default;
+$carousel-control-font-size:                  20px !default;
+
+$carousel-indicator-active-bg:                #fff !default;
+$carousel-indicator-border-color:             #fff !default;
+
+$carousel-caption-color:                      #fff !default;
+
+
+//== Close
+//
+//##
+
+$close-font-weight:           bold !default;
+$close-color:                 #fff !default;
+$close-text-shadow:           0 1px 0 #fff !default;
+
+
+//== Code
+//
+//##
+
+$code-color:                  #c7254e !default;
+$code-bg:                     #f9f2f4 !default;
+
+$kbd-color:                   #fff !default;
+$kbd-bg:                      #333 !default;
+
+$pre-bg:                      #f5f5f5 !default;
+$pre-color:                   $gray-dark !default;
+$pre-border-color:            #ccc !default;
+$pre-scrollable-max-height:   340px !default;
+
+
+//== Type
+//
+//##
+
+//** Horizontal offset for forms and lists.
+$component-offset-horizontal: 180px !default;
+//** Text muted color
+$text-muted:                  $gray-light !default;
+//** Abbreviations and acronyms border color
+$abbr-border-color:           $gray-light !default;
+//** Headings small color
+$headings-small-color:        $gray-light !default;
+//** Blockquote small color
+$blockquote-small-color:      $gray !default;
+//** Blockquote font size
+$blockquote-font-size:        ($font-size-base * 1.25) !default;
+//** Blockquote border color
+$blockquote-border-color:     $table-border-color !default;
+//** Page header border color
+$page-header-border-color:    $table-border-color !default;
+//** Width of horizontal description list titles
+$dl-horizontal-offset:        $component-offset-horizontal !default;
+//** Point at which .dl-horizontal becomes horizontal
+$dl-horizontal-breakpoint:    $grid-float-breakpoint !default;
+//** Horizontal line color.
+$hr-border:                   $table-border-color !default;
diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css
deleted file mode 100644
index f9cd5b34..00000000
--- a/app/assets/stylesheets/application.css
+++ /dev/null
@@ -1,15 +0,0 @@
-/*
- * This is a manifest file that'll be compiled into application.css, which will include all the files
- * listed below.
- *
- * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
- * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
- *
- * You're free to add application-wide styles to this file and they'll appear at the bottom of the
- * compiled file so the styles you add here take precedence over styles defined in any styles
- * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
- * file per style scope.
- *
- *= require_tree .
- *= require_self
- */
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
new file mode 100644
index 00000000..e6a87917
--- /dev/null
+++ b/app/assets/stylesheets/application.scss
@@ -0,0 +1,8 @@
+// "bootstrap-sprockets" must be imported before "bootstrap" and "bootstrap/variables"
+@import "bootstrap-sprockets";
+@import "variables";
+@import "bootstrap";
+@import "bootswatch";
+@import "font-awesome";
+
+@import "custom";
diff --git a/app/assets/stylesheets/channels.scss b/app/assets/stylesheets/channels.scss
deleted file mode 100644
index d913fa7d..00000000
--- a/app/assets/stylesheets/channels.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-// Place all the styles related to the channels controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/controllers/channels/days_controller.rb b/app/controllers/channels/days_controller.rb
new file mode 100644
index 00000000..859b8199
--- /dev/null
+++ b/app/controllers/channels/days_controller.rb
@@ -0,0 +1,44 @@
+class Channels::DaysController < ApplicationController
+  def index
+    @channel = Channel.find_by(identifier: params[:identifier])
+    @year = params[:year].to_i
+    @month = params[:month].to_i
+
+    start_date = Date.new(@year, @month, 1)
+    date_range = start_date...(start_date.next_month)
+    @dates = MessageDate.
+      where(channel: @channel,
+            date: date_range).
+      order(:date).
+      pluck(:date)
+    @speech_count = Message.
+      uniq.
+      where(channel: @channel,
+            type: %w(Privmsg Notice),
+            timestamp: date_range).
+      group('DATE(timestamp)').
+      order(:timestamp).
+      count
+  end
+
+  def show
+    target_channels, @other_channels = Channel.all.partition { |channel|
+      channel.identifier == params[:identifier]
+    }
+    @channel = target_channels.first
+
+    @year = params[:year].to_i
+    @month = params[:month].to_i
+    @day = params[:day].to_i
+    @date = Date.new(@year, @month, @day)
+
+    @calendar_start_date = params[:start_date]&.to_date || @date rescue @date
+
+    @messages = Message.
+      includes(:channel, :irc_user).
+      where(channel: @channel,
+            timestamp: @date...(@date.next_day)).
+      order(:timestamp, :id)
+    @message_dates = MessageDate.where(channel: @channel)
+  end
+end
diff --git a/app/controllers/channels/months_controller.rb b/app/controllers/channels/months_controller.rb
new file mode 100644
index 00000000..5ce5e728
--- /dev/null
+++ b/app/controllers/channels/months_controller.rb
@@ -0,0 +1,15 @@
+class Channels::MonthsController < ApplicationController
+  def index
+    @channel = Channel.find_by(identifier: params[:identifier])
+    @year = params[:year].to_i
+
+    start_date = Date.new(@year, 1, 1)
+    @month_count = MessageDate.
+      uniq.
+      where(channel: @channel,
+            date: start_date...(start_date.next_year)).
+      group('MONTH(date)').
+      order(:date).
+      count
+  end
+end
diff --git a/app/controllers/channels_controller.rb b/app/controllers/channels_controller.rb
index 932078c1..554e79f5 100644
--- a/app/controllers/channels_controller.rb
+++ b/app/controllers/channels_controller.rb
@@ -4,6 +4,14 @@ def index
   end
 
   def show
-    @channel = Channel.find_by(params[:id])
+    @channel = Channel.find_by(identifier: params[:identifier])
+    unless @channel
+      raise ArgumentError, "Channel not found: #{params[:identifier]}"
+    end
+
+    @years = MessageDate.
+      uniq.
+      where(channel: @channel).
+      pluck('YEAR(date)')
   end
 end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index de6be794..7327c774 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -1,2 +1,19 @@
 module ApplicationHelper
+  def default_meta_tags
+    {
+      site: 'IRC ログアーカイブ',
+      reverse: true,
+      og: {
+        url: request.url,
+        title: :title,
+        site_name: 'IRC ログアーカイブ',
+        description: :description,
+        locale: 'ja_JP'
+      }
+    }
+  end
+
+  def message_or_period(m)
+    m.message.blank? ? '。' : ":#{m.message}"
+  end
 end
diff --git a/app/helpers/channels/days_helper.rb b/app/helpers/channels/days_helper.rb
new file mode 100644
index 00000000..bb799dbc
--- /dev/null
+++ b/app/helpers/channels/days_helper.rb
@@ -0,0 +1,2 @@
+module Channels::DaysHelper
+end
diff --git a/app/helpers/channels/months_helper.rb b/app/helpers/channels/months_helper.rb
new file mode 100644
index 00000000..9a68a732
--- /dev/null
+++ b/app/helpers/channels/months_helper.rb
@@ -0,0 +1,2 @@
+module Channels::MonthsHelper
+end
diff --git a/app/models/channel.rb b/app/models/channel.rb
index 025556bb..298f51df 100644
--- a/app/models/channel.rb
+++ b/app/models/channel.rb
@@ -1,6 +1,19 @@
 class Channel < ActiveRecord::Base
+  has_many :messages
+  has_many :joins
+  has_many :parts
+  has_many :quits
+  has_many :kicks
+  has_many :nicks
+  has_many :topics
+  has_many :privmsgs
+  has_many :notices
+  has_many :message_dates
+
   validates(:name, presence: true)
-  validates(:identifier, presence: true)
+  validates(:identifier,
+            presence: true,
+            uniqueness: true)
 
   # ログ記録が有効なチャンネル
   scope :logging_enabled, -> { where(logging_enabled: true) }
diff --git a/app/models/irc_user.rb b/app/models/irc_user.rb
new file mode 100644
index 00000000..d02d3563
--- /dev/null
+++ b/app/models/irc_user.rb
@@ -0,0 +1,15 @@
+class IrcUser < ActiveRecord::Base
+  has_many :messages
+
+  validates :user,
+    presence: true,
+    length: { maximum: 64 }
+  validates :host, presence: true
+
+  # ニックネーム・ユーザー名・ホストを含むマスクを返す
+  # @param [String] nick ニックネーム
+  # @return [String]
+  def mask(nick)
+    "#{nick}!#{user}@#{host}"
+  end
+end
diff --git a/app/models/join.rb b/app/models/join.rb
new file mode 100644
index 00000000..2e476054
--- /dev/null
+++ b/app/models/join.rb
@@ -0,0 +1,8 @@
+class Join < Message
+  # Tiarra のログ形式の文字列を返す
+  # @return [String]
+  def to_tiarra_format
+    "#{timestamp.strftime('%T')} + #{nick} " \
+    "(#{irc_user.mask(nick)}) to #{channel.name_with_prefix}"
+  end
+end
diff --git a/app/models/kick.rb b/app/models/kick.rb
new file mode 100644
index 00000000..2aa9437f
--- /dev/null
+++ b/app/models/kick.rb
@@ -0,0 +1,10 @@
+class Kick < Message
+  validates :target, presence: true
+
+  # Tiarra のログ形式の文字列を返す
+  # @return [String]
+  def to_tiarra_format
+    "#{timestamp.strftime('%T')} - #{target} by #{nick} " \
+    "from #{channel.name_with_prefix} (#{message})"
+  end
+end
diff --git a/app/models/message.rb b/app/models/message.rb
new file mode 100644
index 00000000..6434914b
--- /dev/null
+++ b/app/models/message.rb
@@ -0,0 +1,11 @@
+class Message < ActiveRecord::Base
+  belongs_to :channel
+  belongs_to :irc_user
+
+  validates :channel, presence: true
+  validates :timestamp, presence: true
+  validates :nick,
+    presence: true,
+    length: { maximum: 64 }
+  validates :message, length: { maximum: 512 }
+end
diff --git a/app/models/message_date.rb b/app/models/message_date.rb
new file mode 100644
index 00000000..84fa8969
--- /dev/null
+++ b/app/models/message_date.rb
@@ -0,0 +1,4 @@
+class MessageDate < ActiveRecord::Base
+  belongs_to :channel
+  validates :channel, presence: true
+end
diff --git a/app/models/nick.rb b/app/models/nick.rb
new file mode 100644
index 00000000..b0dccb76
--- /dev/null
+++ b/app/models/nick.rb
@@ -0,0 +1,22 @@
+class Nick < Message
+  validates :message, presence: true
+
+  # 新しいニックネームを返す
+  # @return [String]
+  def new_nick
+    message
+  end
+
+  # 新しいニックネームを設定する
+  # @param [String] value 新しいニックネーム
+  # @return [String]
+  def new_nick=(value)
+    self.message = value
+  end
+
+  # Tiarra のログ形式の文字列を返す
+  # @return [String]
+  def to_tiarra_format
+    "#{timestamp.strftime('%T')} #{nick} -> #{new_nick}"
+  end
+end
diff --git a/app/models/notice.rb b/app/models/notice.rb
new file mode 100644
index 00000000..6121f397
--- /dev/null
+++ b/app/models/notice.rb
@@ -0,0 +1,10 @@
+class Notice < Message
+  validates :message, presence: true
+
+  # Tiarra のログ形式の文字列を返す
+  # @return [String]
+  def to_tiarra_format
+    "#{timestamp.strftime('%T')} " \
+      "(#{channel.name_with_prefix}:#{nick}) #{message}"
+  end
+end
diff --git a/app/models/part.rb b/app/models/part.rb
new file mode 100644
index 00000000..f5a7f743
--- /dev/null
+++ b/app/models/part.rb
@@ -0,0 +1,11 @@
+class Part < Message
+  # Tiarra のログ形式の文字列を返す
+  # @return [String]
+  def to_tiarra_format
+    common_part =
+      "#{timestamp.strftime('%T')} - #{nick} " \
+      "from #{channel.name_with_prefix}"
+
+    message.blank? ? common_part : "#{common_part} (#{message})"
+  end
+end
diff --git a/app/models/privmsg.rb b/app/models/privmsg.rb
new file mode 100644
index 00000000..37c18b8b
--- /dev/null
+++ b/app/models/privmsg.rb
@@ -0,0 +1,10 @@
+class Privmsg < Message
+  validates :message, presence: true
+
+  # Tiarra のログ形式の文字列を返す
+  # @return [String]
+  def to_tiarra_format
+    "#{timestamp.strftime('%T')} " \
+      "<#{channel.name_with_prefix}:#{nick}> #{message}"
+  end
+end
diff --git a/app/models/quit.rb b/app/models/quit.rb
new file mode 100644
index 00000000..52a504f2
--- /dev/null
+++ b/app/models/quit.rb
@@ -0,0 +1,7 @@
+class Quit < Message
+  # Tiarra のログ形式の文字列を返す
+  # @return [String]
+  def to_tiarra_format
+    "#{timestamp.strftime('%T')} ! #{nick} (#{message})"
+  end
+end
diff --git a/app/models/topic.rb b/app/models/topic.rb
new file mode 100644
index 00000000..ce702475
--- /dev/null
+++ b/app/models/topic.rb
@@ -0,0 +1,9 @@
+class Topic < Message
+  # Tiarra のログ形式の文字列を返す
+  # @return [String]
+  def to_tiarra_format
+    "#{timestamp.strftime('%T')} Topic of " \
+      "channel #{channel.name_with_prefix} " \
+      "by #{nick}: #{message}"
+  end
+end
diff --git a/app/views/channels/days/index.html.erb b/app/views/channels/days/index.html.erb
new file mode 100644
index 00000000..3473b7b9
--- /dev/null
+++ b/app/views/channels/days/index.html.erb
@@ -0,0 +1,23 @@
+<% year_month_str = '%d-%02d' % [@year, @month] %>
+<% title([@channel.name_with_prefix, year_month_str]) %>
+<%= render('shared/navbar') %>
+
+ <%= render('shared/flash') %> +
+
+ + +

<%= @channel.name_with_prefix %> <%= year_month_str %>

+
    + <% @dates.each do |date| %> +
  • <%= link_to(date.strftime('%F'), channels_day_path(identifier: @channel.identifier, year: @year, month: '%02d' % @month, day: date.strftime('%d'))) %>(<%= @speech_count[date] || 0 %> 発言)
  • + <% end %> +
+
+
+
diff --git a/app/views/channels/days/show.html.erb b/app/views/channels/days/show.html.erb new file mode 100644 index 00000000..539d1ee0 --- /dev/null +++ b/app/views/channels/days/show.html.erb @@ -0,0 +1,39 @@ +<% date_str = @date.strftime('%F') %> +<% title([@channel.name_with_prefix, date_str]) %> +<%= render('shared/navbar') %> +
+ <%= render('shared/flash') %> + +
+ +
+ +
+
+

<%= @channel.name_with_prefix %> <%= date_str %>

+ <%= render('shared/message_list', messages: @messages) %> +
+ +
+
diff --git a/app/views/channels/index.html.erb b/app/views/channels/index.html.erb index 399f70aa..9e552695 100644 --- a/app/views/channels/index.html.erb +++ b/app/views/channels/index.html.erb @@ -1,23 +1,31 @@ -

チャンネル一覧

-

現在 <%= @channels.size %> チャンネルが登録されています。

- - - - - - - - - -<% @channels.each do |channel| %> - - - - - - - - -<% end %> -
IDIRC上でのチャンネル名チャンネル同一性識別名データベース登録名ログ取得状況
<%= channel.id %><%= channel.original %><%= channel.downcase %><%= channel.alphabet %><%= channel.enable ? '取得中' : '停止中' %><%= link_to('詳細', channels_show_path(channel)) %>
- +<% title('チャンネル一覧') %> +<%= render('shared/navbar') %> +
+ <%= render('shared/flash') %> +
+
+

チャンネル一覧

+

現在 <%= @channels.size %> チャンネルが登録されています。

+ + + + + + + + + + + <% @channels.each do |channel| %> + + + + + + + <% end %> + +
#チャンネル名識別子ログ取得状況
<%= channel.id %><%= link_to(channel.name_with_prefix, channel_path(channel.identifier)) %><%= channel.identifier %><%= channel.logging_enabled ? '取得中' : '停止中' %>
+
+
+
diff --git a/app/views/channels/months/index.html.erb b/app/views/channels/months/index.html.erb new file mode 100644 index 00000000..5da5c504 --- /dev/null +++ b/app/views/channels/months/index.html.erb @@ -0,0 +1,21 @@ +<% title([@channel.name_with_prefix, "#{@year}年"]) %> +<%= render('shared/navbar') %> +
+ <%= render('shared/flash') %> +
+
+ + +

<%= @channel.name_with_prefix %> <%= @year %>年

+
    + <% @month_count.each do |month, count| %> +
  • <%= link_to('%d-%02d' % [@year, month], channels_days_path(identifier: @channel.identifier, year: @year, month: '%02d' % month)) %> (<%= count %>)
  • + <% end %> +
+
+
+
diff --git a/app/views/channels/show.html.erb b/app/views/channels/show.html.erb index 81edafd4..898e4658 100644 --- a/app/views/channels/show.html.erb +++ b/app/views/channels/show.html.erb @@ -1,23 +1,40 @@ -

チャンネル登録情報

- - - - - - - - - - - - - - - - - - - - - -
チャンネルID<%= @channel[:id] %>
IRC上でのチャンネル名<%= @channel[:original] %>
チャンネル同一性識別名<%= @channel[:downcase] %>
データベース登録名<%= @channel[:alphabet] %>
ログ取得状況<%= @channel[:enable] ? '取得中' : '取得停止中' %>
+<% title(@channel.name_with_prefix) %> +<%= render('shared/navbar') %> +
+ <%= render('shared/flash') %> +
+
+ + +

チャンネル登録情報

+ + + + + + + + + + + + + + + + + +
#<%= @channel.id %>
チャンネル名<%= @channel.name_with_prefix %>
識別子<%= @channel.identifier %>
ログ取得状況<%= @channel.logging_enabled? ? '取得中' : '取得停止中' %>
+ +

ログ

+
    + <% @years.each do |year| %> +
  • <%= link_to("#{year}年", channels_months_path(identifier: @channel.identifier, year: year)) %>
  • + <% end %> +
+
+
+
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index f679dae5..2a4555ca 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -1,14 +1,21 @@ - - - LogArchiver - <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> - <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> - <%= csrf_meta_tags %> - - - + + + + + + <%= display_meta_tags(default_meta_tags) %> + <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> + <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> + <%= csrf_meta_tags %> + + + + + <%= yield %> - - + diff --git a/app/views/shared/_error_messages.html.erb b/app/views/shared/_error_messages.html.erb new file mode 100644 index 00000000..98c84670 --- /dev/null +++ b/app/views/shared/_error_messages.html.erb @@ -0,0 +1,10 @@ +<% if target.errors.any? %> +
+

以下のエラーがあります。

+
    + <% target.errors.full_messages.each do |msg| %> +
  • <%= msg %>
  • + <% end %> +
+
+<% end %> diff --git a/app/views/shared/_flash.html.erb b/app/views/shared/_flash.html.erb new file mode 100644 index 00000000..34231877 --- /dev/null +++ b/app/views/shared/_flash.html.erb @@ -0,0 +1,9 @@ +<% unless flash.empty? %> +
+
+ <% flash.each do |message_type, message| %> +
<%= message %>
+ <% end %> +
+
+<% end %> diff --git a/app/views/shared/_message_list.html.erb b/app/views/shared/_message_list.html.erb new file mode 100644 index 00000000..f20d0453 --- /dev/null +++ b/app/views/shared/_message_list.html.erb @@ -0,0 +1,55 @@ + + + + + + + + + <% messages.each do |m| %> + + + <% case m %> + <% when Join %> + + <% when Part %> + + <% when Quit %> + + <% when Nick %> + + <% when Kick %> + + <% when Topic %> + + <% when Privmsg %> + + <% when Notice %> + + <% end %> + + <% end %> + +
時刻メッセージ
<%= m.timestamp.strftime('%T') %> + <%= m.nick %><%= m.irc_user ? " (#{m.irc_user.mask(m.nick)})" : '' %> が <%= m.channel.name_with_prefix %> に参加しました。 + + <%= m.nick %><%= m.channel.name_with_prefix %> から退出しました<%= message_or_period(m) %> + + <%= m.nick %> のクライアントが終了しました<%= message_or_period(m) %> + + <%= m.nick %><%= m.new_nick %> + + <%= m.nick %><%= m.channel.name_with_prefix %> から <%= m.target %> を退出させました<%= message_or_period(m) %> + + <%= m.nick %><%= m.channel.name_with_prefix %> のトピックを設定しました:<%= m.message %> + +
+
<%= m.nick %>
+
<%= m.message %>
+
+
+
+
<%= m.nick %>
+
<%= m.message %>
+
+
diff --git a/app/views/shared/_navbar.html.erb b/app/views/shared/_navbar.html.erb new file mode 100644 index 00000000..025e58c9 --- /dev/null +++ b/app/views/shared/_navbar.html.erb @@ -0,0 +1,21 @@ + diff --git a/app/views/simple_calendar/_calendar.html.erb b/app/views/simple_calendar/_calendar.html.erb new file mode 100644 index 00000000..b30355e5 --- /dev/null +++ b/app/views/simple_calendar/_calendar.html.erb @@ -0,0 +1,33 @@ +
+
+ <%= link_to t('simple_calendar.previous', default: 'Previous'), calendar.url_for_previous_view %> + <%= t('date.month_names')[start_date.month] %> <%= start_date.year %> + <%= link_to t('simple_calendar.next', default: 'Next'), calendar.url_for_next_view %> +
+ + + + + <% date_range.slice(0, 7).each do |day| %> + + <% end %> + + + + + <% date_range.each_slice(7) do |week| %> + + <% week.each do |day| %> + <%= content_tag :td, class: calendar.td_classes_for(day) do %> + <% if defined?(Haml) && respond_to?(:block_is_haml?) && block_is_haml?(block) %> + <% capture_haml(day, sorted_events.fetch(day, []), &block) %> + <% else %> + <% block.call day, sorted_events.fetch(day, []) %> + <% end %> + <% end %> + <% end %> + + <% end %> + +
<%= t('date.abbr_day_names')[day.wday] %>
+
diff --git a/app/views/simple_calendar/_month_calendar.html.erb b/app/views/simple_calendar/_month_calendar.html.erb new file mode 100644 index 00000000..ae666d87 --- /dev/null +++ b/app/views/simple_calendar/_month_calendar.html.erb @@ -0,0 +1,33 @@ +
+
+ <%= link_to t('simple_calendar.previous', default: 'Previous'), calendar.url_for_previous_view %> + <%= @channel.name_with_prefix %> <%= start_date.strftime('%Y-%m') %> + <%= link_to t('simple_calendar.next', default: 'Next'), calendar.url_for_next_view %> +
+ + + + + <% date_range.slice(0, 7).each do |day| %> + + <% end %> + + + + + <% date_range.each_slice(7) do |week| %> + + <% week.each do |day| %> + <%= content_tag :td, class: calendar.td_classes_for(day) do %> + <% if defined?(Haml) && respond_to?(:block_is_haml?) && block_is_haml?(block) %> + <% capture_haml(day, sorted_events.fetch(day, []), &block) %> + <% else %> + <% block.call day, sorted_events.fetch(day, []) %> + <% end %> + <% end %> + <% end %> + + <% end %> + +
<%= t('date.abbr_day_names')[day.wday] %>
+
diff --git a/app/views/simple_calendar/_week_calendar.html.erb b/app/views/simple_calendar/_week_calendar.html.erb new file mode 100644 index 00000000..74e49202 --- /dev/null +++ b/app/views/simple_calendar/_week_calendar.html.erb @@ -0,0 +1,33 @@ +
+
+ <%= link_to t('simple_calendar.previous', default: 'Previous'), calendar.url_for_previous_view %> + Week <%= start_date.strftime("%U").to_i %> + <%= link_to t('simple_calendar.next', default: 'Next'), calendar.url_for_next_view %> +
+ + + + + <% date_range.slice(0, 7).each do |day| %> + + <% end %> + + + + + <% date_range.each_slice(7) do |week| %> + + <% week.each do |day| %> + <%= content_tag :td, class: calendar.td_classes_for(day) do %> + <% if defined?(Haml) && respond_to?(:block_is_haml?) && block_is_haml?(block) %> + <% capture_haml(day, sorted_events.fetch(day, []), &block) %> + <% else %> + <% block.call day, sorted_events.fetch(day, []) %> + <% end %> + <% end %> + <% end %> + + <% end %> + +
<%= t('date.abbr_day_names')[day.wday] %>
+
diff --git a/config/application.rb b/config/application.rb index 95884d3e..b97622a8 100644 --- a/config/application.rb +++ b/config/application.rb @@ -15,12 +15,17 @@ class Application < Rails::Application # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. # config.time_zone = 'Central Time (US & Canada)' + config.time_zone = 'Tokyo' + config.active_record.default_timezone = :local # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] - # config.i18n.default_locale = :de + config.i18n.enforce_available_locales = false + config.i18n.default_locale = :ja # Do not swallow errors in after_commit/after_rollback callbacks. config.active_record.raise_in_transactional_callbacks = true + + config.autoload_paths << "#{Rails.root}/lib" end end diff --git a/config/database.yml.example b/config/database.yml.example index bdd3aeba..d478c0ed 100644 --- a/config/database.yml.example +++ b/config/database.yml.example @@ -17,8 +17,8 @@ default: &default development: <<: *default - database: 'logarchiver_dev' - username: 'logarchiver' + database: 'log_archiver_dev' + username: 'log_archiver' password: '' # Warning: The database defined as "test" will be erased and @@ -26,8 +26,8 @@ development: # Do not set this db to the same as development or production. test: <<: *default - database: 'logarchiver_test' - username: 'logarchiver' + database: 'log_archiver_test' + username: 'log_archiver' password: '' # As with config/secrets.yml, you never want to store sensitive information, @@ -51,6 +51,6 @@ test: # production: <<: *default - database: logarchiver_production - username: logarchiver + database: log_archiver_production + username: log_archiver password: <%= ENV['LOG-ARCHIVER_DATABASE_PASSWORD'] %> diff --git a/config/routes.rb b/config/routes.rb index c9a6a5fe..2d113486 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,8 +1,15 @@ Rails.application.routes.draw do - get 'channels/index' + root 'channels#index' + resources :channels, only: %i(index) + get '/channels/:identifier', to: 'channels#show', as: 'channel' - get 'channels/show' - get 'channels/show/:id' => "channels#show" + namespace :channels do + get ':identifier/:year/:month/:day', to: 'days#show', as: 'day', + year: /[1-9][0-9]{3}/, month: /0[1-9]|1[0-2]/, day: /0[1-9]|[12][0-9]|3[01]/ + get ':identifier/:year/:month', to: 'days#index', as: 'days', + year: /[1-9][0-9]{3}/, month: /0[1-9]|1[0-2]/ + get ':identifier/:year', to: 'months#index', as: 'months', year: /[1-9][0-9]{3}/ + end # The priority is based upon order of creation: first created -> highest priority. # See how all your routes lay out with "rake routes". diff --git a/config/version.rb b/config/version.rb index 37ff9cfc..9fef9932 100644 --- a/config/version.rb +++ b/config/version.rb @@ -2,6 +2,6 @@ module LogArchiver class Application - VERSION = '0.0.3' + VERSION = '0.1.0' end end diff --git a/db/migrate/20160813062305_create_irc_users.rb b/db/migrate/20160813062305_create_irc_users.rb new file mode 100644 index 00000000..df7323d9 --- /dev/null +++ b/db/migrate/20160813062305_create_irc_users.rb @@ -0,0 +1,10 @@ +class CreateIrcUsers < ActiveRecord::Migration + def change + create_table :irc_users do |t| + t.string :name, limit: 9, null: false, default: '' + t.string :host, null: false, default: '' + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20160813063427_create_messages.rb b/db/migrate/20160813063427_create_messages.rb new file mode 100644 index 00000000..52597948 --- /dev/null +++ b/db/migrate/20160813063427_create_messages.rb @@ -0,0 +1,18 @@ +class CreateMessages < ActiveRecord::Migration + def change + create_table :messages do |t| + t.references :channel, index: true, foreign_key: true + t.references :irc_user, index: true, foreign_key: true + t.string :type + t.timestamp :timestamp, null: false + t.string :nick, limit: 64, null: false, default: '' + t.text :message + t.string :target, limit: 64 + + t.timestamps null: false + end + add_index :messages, :type + add_index :messages, :timestamp + add_index :messages, :nick + end +end diff --git a/db/migrate/20160813104510_change_limit_of_irc_user_name.rb b/db/migrate/20160813104510_change_limit_of_irc_user_name.rb new file mode 100644 index 00000000..1ef3ea38 --- /dev/null +++ b/db/migrate/20160813104510_change_limit_of_irc_user_name.rb @@ -0,0 +1,5 @@ +class ChangeLimitOfIrcUserName < ActiveRecord::Migration + def change + change_column :irc_users, :name, :string, limit: 16, null: false, default: '' + end +end diff --git a/db/migrate/20160814152036_add_message_indices.rb b/db/migrate/20160814152036_add_message_indices.rb new file mode 100644 index 00000000..de000811 --- /dev/null +++ b/db/migrate/20160814152036_add_message_indices.rb @@ -0,0 +1,7 @@ +class AddMessageIndices < ActiveRecord::Migration + def change + add_index :messages, [:id, :timestamp] + add_index :messages, [:id, :channel_id] + add_index :messages, [:id, :channel_id, :timestamp] + end +end diff --git a/db/migrate/20160815230757_create_message_dates.rb b/db/migrate/20160815230757_create_message_dates.rb new file mode 100644 index 00000000..20803842 --- /dev/null +++ b/db/migrate/20160815230757_create_message_dates.rb @@ -0,0 +1,12 @@ +class CreateMessageDates < ActiveRecord::Migration + def change + create_table :message_dates do |t| + t.references :channel, index: true, foreign_key: true + t.date :date + + t.timestamps null: false + end + add_index :message_dates, :date + add_index :message_dates, [:channel_id, :date] + end +end diff --git a/db/migrate/20160816083847_set_channel_identifier_unique.rb b/db/migrate/20160816083847_set_channel_identifier_unique.rb new file mode 100644 index 00000000..28a71274 --- /dev/null +++ b/db/migrate/20160816083847_set_channel_identifier_unique.rb @@ -0,0 +1,6 @@ +class SetChannelIdentifierUnique < ActiveRecord::Migration + def change + add_index :channels, :identifier, unique: true + add_index :channels, :logging_enabled + end +end diff --git a/db/migrate/20160816110024_reset_limit_of_irc_user_name.rb b/db/migrate/20160816110024_reset_limit_of_irc_user_name.rb new file mode 100644 index 00000000..44c75063 --- /dev/null +++ b/db/migrate/20160816110024_reset_limit_of_irc_user_name.rb @@ -0,0 +1,5 @@ +class ResetLimitOfIrcUserName < ActiveRecord::Migration + def change + change_column :irc_users, :name, :string, limit: 64, null: false, default: '' + end +end diff --git a/db/migrate/20160816112752_set_channel_default_name_and_default_identifier_to_empty.rb b/db/migrate/20160816112752_set_channel_default_name_and_default_identifier_to_empty.rb new file mode 100644 index 00000000..31274734 --- /dev/null +++ b/db/migrate/20160816112752_set_channel_default_name_and_default_identifier_to_empty.rb @@ -0,0 +1,6 @@ +class SetChannelDefaultNameAndDefaultIdentifierToEmpty < ActiveRecord::Migration + def change + change_column_default :channels, :identifier, '' + change_column_default :channels, :name, '' + end +end diff --git a/db/migrate/20160819011055_rename_irc_user_name_to_irc_user_user.rb b/db/migrate/20160819011055_rename_irc_user_name_to_irc_user_user.rb new file mode 100644 index 00000000..b4b8fd06 --- /dev/null +++ b/db/migrate/20160819011055_rename_irc_user_name_to_irc_user_user.rb @@ -0,0 +1,5 @@ +class RenameIrcUserNameToIrcUserUser < ActiveRecord::Migration + def change + rename_column :irc_users, :name, :user + end +end diff --git a/db/schema.rb b/db/schema.rb index 2ae992c3..bf25507c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,14 +11,59 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160810142103) do +ActiveRecord::Schema.define(version: 20160819011055) do create_table "channels", force: :cascade do |t| - t.string "name", limit: 255, default: "irc_test", null: false - t.string "identifier", limit: 255, default: "irc_test", null: false - t.boolean "logging_enabled", default: true, null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.string "name", limit: 255, default: "", null: false + t.string "identifier", limit: 255, default: "", null: false + t.boolean "logging_enabled", default: true, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end + add_index "channels", ["identifier"], name: "index_channels_on_identifier", unique: true, using: :btree + add_index "channels", ["logging_enabled"], name: "index_channels_on_logging_enabled", using: :btree + + create_table "irc_users", force: :cascade do |t| + t.string "user", limit: 64, default: "", null: false + t.string "host", limit: 255, default: "", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "message_dates", force: :cascade do |t| + t.integer "channel_id", limit: 4 + t.date "date" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "message_dates", ["channel_id", "date"], name: "index_message_dates_on_channel_id_and_date", using: :btree + add_index "message_dates", ["channel_id"], name: "index_message_dates_on_channel_id", using: :btree + add_index "message_dates", ["date"], name: "index_message_dates_on_date", using: :btree + + create_table "messages", force: :cascade do |t| + t.integer "channel_id", limit: 4 + t.integer "irc_user_id", limit: 4 + t.string "type", limit: 255 + t.datetime "timestamp", null: false + t.string "nick", limit: 64, default: "", null: false + t.text "message", limit: 65535 + t.string "target", limit: 64 + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "messages", ["channel_id"], name: "index_messages_on_channel_id", using: :btree + add_index "messages", ["id", "channel_id", "timestamp"], name: "index_messages_on_id_and_channel_id_and_timestamp", using: :btree + add_index "messages", ["id", "channel_id"], name: "index_messages_on_id_and_channel_id", using: :btree + add_index "messages", ["id", "timestamp"], name: "index_messages_on_id_and_timestamp", using: :btree + add_index "messages", ["irc_user_id"], name: "index_messages_on_irc_user_id", using: :btree + add_index "messages", ["nick"], name: "index_messages_on_nick", using: :btree + add_index "messages", ["timestamp"], name: "index_messages_on_timestamp", using: :btree + add_index "messages", ["type"], name: "index_messages_on_type", using: :btree + + add_foreign_key "message_dates", "channels" + add_foreign_key "messages", "channels" + add_foreign_key "messages", "irc_users" end diff --git a/lib/ircs/plugins/channel_sync.rb b/lib/ircs/plugins/channel_sync.rb index d111f076..9e988cc6 100644 --- a/lib/ircs/plugins/channel_sync.rb +++ b/lib/ircs/plugins/channel_sync.rb @@ -12,7 +12,7 @@ class ChannelSync < Template set(plugin_name: 'ChannelSync') listen_to(:connect, method: :connect) - timer(1, method: :kickstart) + timer(10, method: :kickstart) # 接続時に、必要なチャンネルに JOIN する # @param [Cinch::Message] m diff --git a/lib/ircs/plugins/save_log.rb b/lib/ircs/plugins/save_log.rb index d5b41ca9..002c664e 100644 --- a/lib/ircs/plugins/save_log.rb +++ b/lib/ircs/plugins/save_log.rb @@ -11,111 +11,163 @@ module Plugin class SaveLog < Template include Cinch::Plugin + RECORD_MESSAGE = :record_message + set(plugin_name: 'SaveLog') listen_to(:join, method: :on_join) listen_to(:part, method: :on_part) listen_to(:kick, method: :on_kick) listen_to(:nick, method: :on_nick) - listen_to(:topic, method: :on_message) - listen_to(:notice, method: :on_message) - listen_to(:privmsg, method: :on_message) + listen_to(:topic, method: :on_topic) + listen_to(:notice, method: :on_notice) + listen_to(:privmsg, method: :on_privmsg) - # チャンネルに(自分を含む)誰かが JOIN したとき - # USER / 接続元アドレスを以下の書式でメッセージ本文として扱う - # nick!user@address + # チャンネルに(自分を含む)誰かが JOIN したとき + # @param [Cinch::Message] m メッセージ def on_join(m) - synchronize(:pp) do - pp({ - time: m.time, - nick: m.user.nick, - user: m.user.user, - mask: m.user.host, - command: m.command, - channel: m.channel.name, - message: m.prefix - }) + record_message(m) do |channel, irc_user| + channel.joins.create!(irc_user: irc_user, + timestamp: m.time, + nick: m.user.nick) end end # チャンネルから(自分を含む)誰かが PART したとき + # @param [Cinch::Message] m メッセージ def on_part(m) - synchronize(:pp) do - pp({ - time: m.time, + record_message(m) do |channel, irc_user| + channel.parts.create!( + irc_user: irc_user, + timestamp: m.time, nick: m.user.nick, - user: m.user.user, - mask: m.user.host, - command: m.command, - channel: m.channel.name, message: m.message != m.channel.name ? m.message : nil - }) + ) end end - # チャンネルから(自分を含む)誰かが QUIT したとき - def on_quit(m, channels) - synchronize(:pp) do - channels.each do |channel| - pp({ - time: m.time, - nick: m.user.nick, - user: m.user.user, - mask: m.user.host, - command: m.command, - channel: channel.name, - message: m.message - }) - end + # チャンネルから(自分を含む)誰かが QUIT したとき + # @param [Cinch::Message] m メッセージ + # @param [Array ] cinch_channels 参加していたチャンネルの配列 + def on_quit(m, cinch_channels) + record_message_to_channels(m, cinch_channels) do |channel, irc_user| + channel.quits.create(irc_user: irc_user, + timestamp: m.time, + nick: m.user.nick, + message: m.message) end end # KICK が使われたとき + # @param [Cinch::Message] m メッセージ def on_kick(m) - synchronize(:pp) do - pp({ - time: m.time, - nick: m.user.nick, - user: m.user.user, - mask: m.user.host, - command: m.command, - channel: m.channel.name, - message: {target: m.params[1], message: m.message} - }) + record_message(m) do |channel, irc_user| + channel.kicks.create!(irc_user: irc_user, + timestamp: m.time, + nick: m.user.nick, + target: m.params[1], + message: m.message) end end # NICK を変えたとき - # 変更後の NICK をメッセージ本文として扱う + # + # 変更後の NICK をメッセージ本文として扱う。 + # @param [Cinch::Message] m メッセージ def on_nick(m) - synchronize(:pp) do - pp({ - time: m.time, - nick: m.user.last_nick, - user: m.user.user, - mask: m.user.host, - command: m.command, - channel: nil, - message: m.user.nick - }) + user = m.user + record_message_to_channels(m, user.channels) do |channel, irc_user| + channel.nicks.create(irc_user: irc_user, + timestamp: m.time, + nick: user.last_nick, + message: user.nick) end end - # TOPIC / NOTICE / PRIVMSG を受信したとき - def on_message(m) - # チャンネル宛ではない(プライベの)場合は保存の対象外 - return if m.channel.nil? + # TOPIC を受信したとき + # @param [Cinch::Message] m メッセージ + def on_topic(m) + record_message(m) do |channel, irc_user| + channel.topics.create!(irc_user: irc_user, + timestamp: m.time, + nick: m.user.nick, + message: m.message) + end + end - synchronize(:pp) do - pp({ - time: m.time, - nick: m.user.nick, - user: m.user.user, - mask: m.user.host, - command: m.command, - channel: m.channel.name, - message: m.message - }) + # NOTICE を受信したとき + # @param [Cinch::Message] m メッセージ + def on_notice(m) + record_message(m) do |channel, irc_user| + channel.notices.create!(irc_user: irc_user, + timestamp: m.time, + nick: m.user.nick, + message: m.message) + end + end + + # PRIVMSG を受信したとき + # @param [Cinch::Message] m メッセージ + def on_privmsg(m) + record_message(m) do |channel, irc_user| + channel.privmsgs.create!(irc_user: irc_user, + timestamp: m.time, + nick: m.user.nick, + message: m.message) + end + end + + private + + # メッセージを記録する + # @param [Cinch::Message] message Cinch から渡された IRC メッセージ + # @yieldparam [::Channel] channel チャンネル + # @yieldparam [IrcUser] irc_user IRC ユーザー + # @return [void] + def record_message(message) + return nil unless message.channel + + synchronize(RECORD_MESSAGE) do + ActiveRecord::Base.connection_pool.with_connection do + channel = ::Channel.find_by(name: message.channel.name[1..-1], + logging_enabled: true) + next nil unless channel + + next nil unless user = message.user + irc_user = IrcUser.find_or_create_by!(user: user.user, host: user.host) + + MessageDate.find_or_create_by!(channel: channel, date: message.time.to_date) + + yield(channel, irc_user) + end + end + end + + # 複数のチャンネルにメッセージを記録する + # @param [Cinch::Message] message Cinch から渡された IRC メッセージ + # @param [Array] cinch_channels Cinch から渡されたチャンネルリスト + # @yieldparam [::Channel] channel チャンネル + # @yieldparam [IrcUser] irc_user IRC ユーザー + # @return [void] + def record_message_to_channels(message, cinch_channels) + channel_names_without_prefix = + cinch_channels.map { |channel| channel.name[1..-1] } + + ActiveRecord::Base.connection_pool.with_connection do + synchronize(RECORD_MESSAGE) do + irc_user = nil + next [] unless user = message.user + channels = ::Channel.where(name: channel_names_without_prefix, + logging_enabled: true) + channels.each.map do |channel| + irc_user ||= IrcUser.find_or_create_by!(user: user.user, + host: user.host) + + MessageDate.find_or_create_by!(channel: channel, date: message.time.to_date) + + yield(channel, irc_user) + end + end end end end diff --git a/lib/tasks/prepare_cre_channels.rb b/lib/tasks/prepare_cre_channels.rb new file mode 100644 index 00000000..e4968acd --- /dev/null +++ b/lib/tasks/prepare_cre_channels.rb @@ -0,0 +1,48 @@ +module Tasks + module PrepareCreChannels + CHANNELS = [ + ['next', 'NEXT'], + ['cre', 'cre'], + ['opentrpg', 'openTRPG'], + ['cat', '猫'], + ['picture', '写真'], + ['computer', 'ぱそ'], + ['baseball', '野球'], + ['cooking', '料理'], + ['mono', 'モノ作り'], + ['millitary', '軍事技術'], + ['sci-tech', '科学技術'], + ['sound', '音づくり'], + ['write', 'もの書き'], + ['write-ex1', 'もの書き予備'], + ['write-ex2', 'もの書き外典'], + ['football', 'フットボール'], + ['boardgame', 'ボードゲーム'], + ['kataribe', 'kataribe'], + ['ka-01', 'KA-01'], + ['ka-02', 'KA-02'], + ['ka-03', 'KA-03'], + ['ka-04', 'KA-04'], + ['ka-05', 'KA-05'], + ['ka-06', 'KA-06'], + ['ha06', 'HA06'], + ['ha06-01', 'HA06-01'], + ['ha06-02', 'HA06-02'], + ['ha21', 'HA21'] + ] + + module_function + def self.execute + CHANNELS.each do |identifier, name| + begin + Channel.find_or_create_by!(identifier: identifier) do |channel| + channel.name = name + channel.logging_enabled = false + end + rescue => e + puts("#{name}: #{e}") + end + end + end + end +end diff --git a/test/controllers/channels/days_controller_test.rb b/test/controllers/channels/days_controller_test.rb new file mode 100644 index 00000000..2a038292 --- /dev/null +++ b/test/controllers/channels/days_controller_test.rb @@ -0,0 +1,4 @@ +require 'test_helper' + +class Channels::DaysControllerTest < ActionController::TestCase +end diff --git a/test/controllers/channels/months_controller_test.rb b/test/controllers/channels/months_controller_test.rb new file mode 100644 index 00000000..1c37deeb --- /dev/null +++ b/test/controllers/channels/months_controller_test.rb @@ -0,0 +1,4 @@ +require 'test_helper' + +class Channels::MonthsControllerTest < ActionController::TestCase +end diff --git a/test/controllers/channels_controller_test.rb b/test/controllers/channels_controller_test.rb index e8c5ed4e..707a760f 100644 --- a/test/controllers/channels_controller_test.rb +++ b/test/controllers/channels_controller_test.rb @@ -1,8 +1,4 @@ require 'test_helper' class ChannelsControllerTest < ActionController::TestCase - test "should get index" do - get :index - assert_response :success - end end diff --git a/test/factories/channels.rb b/test/factories/channels.rb index b01a78a7..9d9f068c 100644 --- a/test/factories/channels.rb +++ b/test/factories/channels.rb @@ -4,7 +4,7 @@ identifier 'irc_test' logging_enabled true - initialize_with { Channel.find_or_create_by(name: name) } + initialize_with { Channel.find_or_create_by(identifier: identifier) } factory :channel_with_camel_case_name do name 'CamelCaseChannel' diff --git a/test/factories/irc_users.rb b/test/factories/irc_users.rb new file mode 100644 index 00000000..86da0a7d --- /dev/null +++ b/test/factories/irc_users.rb @@ -0,0 +1,6 @@ +FactoryGirl.define do + factory :irc_user do + user "rgrb_bot" + host "irc.cre.jp" + end +end diff --git a/test/factories/joins.rb b/test/factories/joins.rb new file mode 100644 index 00000000..53c987c7 --- /dev/null +++ b/test/factories/joins.rb @@ -0,0 +1,8 @@ +FactoryGirl.define do + factory :join do + channel + irc_user + timestamp '2016-04-01 12:34:57' + nick 'rgrb' + end +end diff --git a/test/factories/kicks.rb b/test/factories/kicks.rb new file mode 100644 index 00000000..0191e8fa --- /dev/null +++ b/test/factories/kicks.rb @@ -0,0 +1,10 @@ +FactoryGirl.define do + factory :kick do + channel + irc_user + timestamp '2016-04-01 12:35:00' + nick 'ocha' + target 'rgrb' + message '暴走したので KICK' + end +end diff --git a/test/factories/message_dates.rb b/test/factories/message_dates.rb new file mode 100644 index 00000000..051fe053 --- /dev/null +++ b/test/factories/message_dates.rb @@ -0,0 +1,5 @@ +FactoryGirl.define do + factory :message_date do + date "2016-08-16" + end +end diff --git a/test/factories/messages.rb b/test/factories/messages.rb new file mode 100644 index 00000000..84723d85 --- /dev/null +++ b/test/factories/messages.rb @@ -0,0 +1,11 @@ +FactoryGirl.define do + factory :message do + channel + irc_user + type "" + timestamp "2016-04-01 12:34:56" + nick "rgrb" + message nil + target nil + end +end diff --git a/test/factories/nicks.rb b/test/factories/nicks.rb new file mode 100644 index 00000000..8f3a5741 --- /dev/null +++ b/test/factories/nicks.rb @@ -0,0 +1,9 @@ +FactoryGirl.define do + factory :nick do + channel + irc_user + timestamp '2016-04-01 12:35:01' + nick 'rgrb' + message 'rgrb2' + end +end diff --git a/test/factories/notices.rb b/test/factories/notices.rb new file mode 100644 index 00000000..7d2d1635 --- /dev/null +++ b/test/factories/notices.rb @@ -0,0 +1,9 @@ +FactoryGirl.define do + factory :notice do + channel + irc_user + timestamp '2016-04-01 12:35:04' + nick 'rgrb' + message '通知' + end +end diff --git a/test/factories/parts.rb b/test/factories/parts.rb new file mode 100644 index 00000000..e646d177 --- /dev/null +++ b/test/factories/parts.rb @@ -0,0 +1,9 @@ +FactoryGirl.define do + factory :part do + channel + irc_user + timestamp '2016-04-01 12:34:58' + nick 'rgrb' + message 'Bye!' + end +end diff --git a/test/factories/privmsgs.rb b/test/factories/privmsgs.rb new file mode 100644 index 00000000..ae84251a --- /dev/null +++ b/test/factories/privmsgs.rb @@ -0,0 +1,9 @@ +FactoryGirl.define do + factory :privmsg do + channel + irc_user + timestamp '2016-04-01 12:35:03' + nick 'rgrb' + message 'プライベートメッセージ' + end +end diff --git a/test/factories/quits.rb b/test/factories/quits.rb new file mode 100644 index 00000000..b7072b5d --- /dev/null +++ b/test/factories/quits.rb @@ -0,0 +1,9 @@ +FactoryGirl.define do + factory :quit do + channel + irc_user + timestamp '2016-04-01 12:34:59' + nick 'rgrb' + message 'Bye!' + end +end diff --git a/test/factories/topics.rb b/test/factories/topics.rb new file mode 100644 index 00000000..d1bf1842 --- /dev/null +++ b/test/factories/topics.rb @@ -0,0 +1,9 @@ +FactoryGirl.define do + factory :topic do + channel + irc_user + timestamp '2016-04-01 12:35:02' + nick 'rgrb' + message 'IRC テスト用チャンネル' + end +end diff --git a/test/models/channel_test.rb b/test/models/channel_test.rb index 03c09850..0c92bbc1 100644 --- a/test/models/channel_test.rb +++ b/test/models/channel_test.rb @@ -25,6 +25,15 @@ class ChannelTest < ActiveSupport::TestCase refute(@channel.valid?) end + test 'identifier はユニーク' do + channel2 = Channel.new( + name: '#irc_test2', + identifier: @channel.identifier, + logging_enabled: true + ) + refute(channel2.valid?) + end + test 'name_with_prefix は接頭辞付きのチャンネル名を返す' do assert_equal('#irc_test', @channel.name_with_prefix) end diff --git a/test/models/irc_user_test.rb b/test/models/irc_user_test.rb new file mode 100644 index 00000000..98f2d18a --- /dev/null +++ b/test/models/irc_user_test.rb @@ -0,0 +1,23 @@ +require 'test_helper' + +class IrcUserTest < ActiveSupport::TestCase + setup do + @user = create(:irc_user) + end + + test '有効である' do + assert(@user.valid?) + end + + test 'user は 64 文字以下' do + @user.user = 'a' * 64 + assert(@user.valid?, '64 文字は OK') + + @user.user = 'a' * 65 + refute(@user.valid?, '65 文字は NG') + end + + test 'マスクの形式が正しい' do + assert_equal('rgrb!rgrb_bot@irc.cre.jp', @user.mask('rgrb')) + end +end diff --git a/test/models/join_test.rb b/test/models/join_test.rb new file mode 100644 index 00000000..70cd7d57 --- /dev/null +++ b/test/models/join_test.rb @@ -0,0 +1,16 @@ +require 'test_helper' + +class JoinTest < ActiveSupport::TestCase + setup do + @join = create(:join) + end + + test '有効である' do + assert(@join.valid?) + end + + test 'Tiarra のログ形式が正しい' do + assert_equal('12:34:57 + rgrb (rgrb!rgrb_bot@irc.cre.jp) to #irc_test', + @join.to_tiarra_format) + end +end diff --git a/test/models/kick_test.rb b/test/models/kick_test.rb new file mode 100644 index 00000000..10ea5e4a --- /dev/null +++ b/test/models/kick_test.rb @@ -0,0 +1,26 @@ +require 'test_helper' + +class KickTest < ActiveSupport::TestCase + setup do + @kick = create(:kick) + end + + test '有効である' do + assert(@kick.valid?) + end + + test 'target は必須' do + @kick.target = nil + refute(@kick.valid?) + end + + test 'target は空白のみではならない' do + @kick.target = ' ' * 10 + refute(@kick.valid?) + end + + test 'Tiarra のログ形式が正しい' do + assert_equal('12:35:00 - rgrb by ocha from #irc_test (暴走したので KICK)', + @kick.to_tiarra_format) + end +end diff --git a/test/models/message_date_test.rb b/test/models/message_date_test.rb new file mode 100644 index 00000000..fab33457 --- /dev/null +++ b/test/models/message_date_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class MessageDateTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/models/message_test.rb b/test/models/message_test.rb new file mode 100644 index 00000000..1400c04f --- /dev/null +++ b/test/models/message_test.rb @@ -0,0 +1,47 @@ +require 'test_helper' + +class MessageTest < ActiveSupport::TestCase + setup do + @message = create(:message) + end + + test '有効である' do + assert(@message.valid?) + end + + test 'channel は必須' do + @message.channel = nil + refute(@message.valid?) + end + + test 'timestamp は必須' do + @message.timestamp = nil + refute(@message.valid?) + end + + test 'nick は必須' do + @message.nick = nil + refute(@message.valid?) + end + + test 'nick は空白のみではならない' do + @message.nick = ' ' * 10 + refute(@message.valid?) + end + + test 'nick は 64 文字以内' do + @message.nick = 'a' * 64 + assert(@message.valid?, '64 文字は OK') + + @message.nick = 'a' * 65 + refute(@message.valid?, '65 文字は NG') + end + + test 'message は 512 文字以内' do + @message.message = 'a' * 512 + assert(@message.valid?, '512 文字は OK') + + @message.message = 'a' * 513 + refute(@message.valid?, '513 文字は NG') + end +end diff --git a/test/models/nick_test.rb b/test/models/nick_test.rb new file mode 100644 index 00000000..4babffc9 --- /dev/null +++ b/test/models/nick_test.rb @@ -0,0 +1,34 @@ +require 'test_helper' + +class NickTest < ActiveSupport::TestCase + setup do + @nick = create(:nick) + end + + test '有効である' do + assert(@nick.valid?) + end + + test 'message は必須' do + @nick.message = nil + refute(@nick.valid?) + end + + test 'message は空白のみではならない' do + @nick.message = ' ' * 10 + refute(@nick.valid?) + end + + test 'new_nick は新しいニックネームを返す' do + assert_equal(@nick.new_nick, @nick.message) + end + + test 'new_nick に代入できる' do + @nick.new_nick = 'rgrb3' + assert_equal('rgrb3', @nick.message) + end + + test 'Tiarra のログ形式が正しい' do + assert_equal('12:35:01 rgrb -> rgrb2', @nick.to_tiarra_format) + end +end diff --git a/test/models/notice_test.rb b/test/models/notice_test.rb new file mode 100644 index 00000000..0633e060 --- /dev/null +++ b/test/models/notice_test.rb @@ -0,0 +1,26 @@ +require 'test_helper' + +class NoticeTest < ActiveSupport::TestCase + setup do + @notice = create(:notice) + end + + test '有効である' do + assert(@notice.valid?) + end + + test 'message は必須' do + @notice.message = nil + refute(@notice.valid?) + end + + test 'message は空白のみではならない' do + @notice.message = ' ' * 10 + refute(@notice.valid?) + end + + test 'Tiarra のログ形式が正しい' do + assert_equal('12:35:04 (#irc_test:rgrb) 通知', + @notice.to_tiarra_format) + end +end diff --git a/test/models/part_test.rb b/test/models/part_test.rb new file mode 100644 index 00000000..f261f6d7 --- /dev/null +++ b/test/models/part_test.rb @@ -0,0 +1,21 @@ +require 'test_helper' + +class PartTest < ActiveSupport::TestCase + setup do + @part = create(:part) + end + + test '有効である' do + assert(@part.valid?) + end + + test 'Tiarra のログ形式が正しい:メッセージなし' do + @part.message = '' + assert_equal('12:34:58 - rgrb from #irc_test', @part.to_tiarra_format) + end + + test 'Tiarra のログ形式が正しい:メッセージあり' do + assert_equal('12:34:58 - rgrb from #irc_test (Bye!)', + @part.to_tiarra_format) + end +end diff --git a/test/models/privmsg_test.rb b/test/models/privmsg_test.rb new file mode 100644 index 00000000..abd3ac4b --- /dev/null +++ b/test/models/privmsg_test.rb @@ -0,0 +1,26 @@ +require 'test_helper' + +class PrivmsgTest < ActiveSupport::TestCase + setup do + @privmsg = create(:privmsg) + end + + test '有効である' do + assert(@privmsg.valid?) + end + + test 'message は必須' do + @privmsg.message = nil + refute(@privmsg.valid?) + end + + test 'message は空白のみではならない' do + @privmsg.message = ' ' * 10 + refute(@privmsg.valid?) + end + + test 'Tiarra のログ形式が正しい' do + assert_equal('12:35:03 <#irc_test:rgrb> プライベートメッセージ', + @privmsg.to_tiarra_format) + end +end diff --git a/test/models/quit_test.rb b/test/models/quit_test.rb new file mode 100644 index 00000000..9a3b3960 --- /dev/null +++ b/test/models/quit_test.rb @@ -0,0 +1,15 @@ +require 'test_helper' + +class QuitTest < ActiveSupport::TestCase + setup do + @quit = create(:quit) + end + + test '有効である' do + assert(@quit.valid?) + end + + test 'Tiarra のログ形式が正しい' do + assert_equal('12:34:59 ! rgrb (Bye!)', @quit.to_tiarra_format) + end +end diff --git a/test/models/topic_test.rb b/test/models/topic_test.rb new file mode 100644 index 00000000..1fd22779 --- /dev/null +++ b/test/models/topic_test.rb @@ -0,0 +1,16 @@ +require 'test_helper' + +class TopicTest < ActiveSupport::TestCase + setup do + @topic = create(:topic) + end + + test '有効である' do + assert(@topic.valid?) + end + + test 'Tiarra のログ形式が正しい' do + assert_equal('12:35:02 Topic of channel #irc_test by rgrb: IRC テスト用チャンネル', + @topic.to_tiarra_format) + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index 2ccfb337..7adc0f7d 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -2,6 +2,9 @@ require File.expand_path('../../config/environment', __FILE__) require 'rails/test_help' +require 'coveralls' +Coveralls.wear! + require 'minitest/reporters' Minitest::Reporters.use!