├── lib └── tasks │ ├── .gitkeep │ ├── cron.rake │ └── import.rake ├── public ├── favicon.ico ├── stylesheets │ ├── .gitkeep │ └── smoothness │ │ ├── images │ │ ├── ui-icons_222222_256x240.png │ │ ├── ui-icons_2e83ff_256x240.png │ │ ├── ui-icons_454545_256x240.png │ │ ├── ui-icons_888888_256x240.png │ │ ├── ui-icons_cd0a0a_256x240.png │ │ ├── ui-bg_flat_0_aaaaaa_40x100.png │ │ ├── ui-bg_flat_75_ffffff_40x100.png │ │ ├── ui-bg_glass_55_fbf9ee_1x400.png │ │ ├── ui-bg_glass_65_ffffff_1x400.png │ │ ├── ui-bg_glass_75_dadada_1x400.png │ │ ├── ui-bg_glass_75_e6e6e6_1x400.png │ │ ├── ui-bg_glass_95_fef1ec_1x400.png │ │ └── ui-bg_highlight-soft_75_cccccc_1x100.png │ │ └── jquery-ui-1.8.7.custom.css ├── javascripts │ ├── application.js │ └── excanvas.min.js ├── images │ └── rails.png ├── robots.txt ├── 422.html ├── 404.html └── 500.html ├── vendor └── plugins │ └── .gitkeep ├── .ruby-version ├── app ├── helpers │ ├── index_helper.rb │ └── application_helper.rb ├── controllers │ ├── application_controller.rb │ ├── index_controller.rb │ └── repository_controller.rb ├── models │ ├── repository_stats.rb │ ├── count.rb │ ├── json_array_length_sampler.rb │ ├── json_hash_key_count_sampler.rb │ ├── json_sampler.rb │ ├── json_path_array_length_sampler.rb │ ├── json_path_sampler.rb │ ├── regex_sampler.rb │ ├── csv_export.rb │ ├── sampler.rb │ └── repository.rb └── views │ ├── layouts │ └── application.html.erb │ └── index │ └── index.html.erb ├── Procfile ├── bin ├── rake ├── bundle ├── rails ├── update └── setup ├── test ├── model │ ├── helpers │ │ └── index_helper_test.rb │ ├── count_test.rb │ ├── repository_test.rb │ └── repository_stats_test.rb ├── controller │ ├── index_controller_test.rb │ └── repository_controller_test.rb ├── fixtures │ ├── repositories.yml │ ├── counts.yml │ └── repository_stats.yml └── test_helper.rb ├── config ├── boot.rb ├── initializers │ ├── cookies_serializer.rb │ ├── session_store.rb │ ├── mime_types.rb │ ├── application_controller_renderer.rb │ ├── request_forgery_protection.rb │ ├── filter_parameter_logging.rb │ ├── backtrace_silencers.rb │ ├── secret_token.rb │ ├── assets.rb │ ├── wrap_parameters.rb │ ├── cors.rb │ └── inflections.rb ├── environment.rb ├── routes.rb ├── unicorn.rb ├── locales │ └── en.yml ├── secrets.yml ├── application.rb ├── database.yml └── environments │ ├── test.rb │ ├── development.rb │ └── production.rb ├── .gitignore ├── config.ru ├── db ├── migrate │ ├── 20170317150818_add_hidden_to_repositories.rb │ ├── 20101219170711_add_graph_flag_to_repositories.rb │ ├── 20180603170755_fix_drupal.rb │ ├── 20180603171504_fix_dub.rb │ ├── 20190623191817_lua_page_design.rb │ ├── 20170806122556_change_dlang_regex.rb │ ├── 20201119115702_fix_pypi_2020.rb │ ├── 20170317151210_hide_godoc_and_bower.rb │ ├── 20160530141025_fix_hex_pm.rb │ ├── 20101209035103_create_counts.rb │ ├── 20121021155545_clean_up_undercounted_npm_values.rb │ ├── 20190623190616_julia_pagination.rb │ ├── 20130330150618_show_packagist_by_default.rb │ ├── 20140908125438_change.rb │ ├── 20180603170145_fix_pypi.rb │ ├── 20160408114700_rubygems_requires_https.rb │ ├── 20160408130459_npm_redesign.rb │ ├── 20111214013904_create_samplers.rb │ ├── 20170910124716_fix_crates_sampling.rb │ ├── 20140622144911_fix_cran_url.rb │ ├── 20110402134444_update_cpan_regex.rb │ ├── 20111214024049_load_regex_samplers.rb │ ├── 20110120035756_add_cpan_search.rb │ ├── 20101209034739_create_repositories.rb │ ├── 20110402135956_add_cran.rb │ ├── 20130228022158_create_csv_exports.rb │ ├── 20110402141640_add_hackage.rb │ ├── 20140202221650_add_melpa.rb │ ├── 20111214031405_add_npm.rb │ ├── 20120225161251_add_nuget.rb │ ├── 20121021154949_update_npm_regex.rb │ ├── 20101211041442_create_repository_stats.rb │ ├── 20160528132306_add_dub.rb │ ├── 20121125152055_add_clojars.rb │ ├── 20130228130010_add_packagist_repository.rb │ ├── 20140622153214_add_go_lang.rb │ ├── 20170402164232_add_julia.rb │ ├── 20140202222246_update_hackage_to_new_server.rb │ ├── 20170317145504_add_gopm_io.rb │ ├── 20180304145537_add_crystal_shards.rb │ ├── 20150101130359_new_npm_website.rb │ ├── 20130330144314_add_maven.rb │ ├── 20140725132816_add_bower.rb │ ├── 20141125120004_add_crates_rust.rb │ ├── 20150309132532_add_luarocks.rb │ ├── 20170514173011_add_vim_scripts.rb │ ├── 20140725143533_add_hex_pm.rb │ ├── 20150715115329_hex_pm_new_page_design.rb │ ├── 20150715125133_update_for_packagist_new_design.rb │ ├── 20151127170059_add_perl_6.rb │ ├── 20170910132602_add_nim_lang.rb │ ├── 20151127171342_add_drupal.rb │ ├── 20120826135117_new_npm_site_design.rb │ ├── 20141123122505_rubygems_new_site_design.rb │ ├── 20120826141756_change_which_repos_to_show.rb │ ├── 20151127163305_update_nuget.rb │ ├── 20151127161844_update_luarocks_url.rb │ ├── 20130330135629_move_cran.rb │ └── 20101209040416_add_repositories.rb ├── seeds.rb └── schema.rb ├── doc └── README_FOR_APP ├── Rakefile ├── script └── rails ├── Gemfile ├── README.md └── Gemfile.lock /lib/tasks/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/plugins/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | ruby-2.4.1 2 | -------------------------------------------------------------------------------- /public/stylesheets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/javascripts/application.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/helpers/index_helper.rb: -------------------------------------------------------------------------------- 1 | module IndexHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: bundle exec puma -t 5:5 -p ${PORT:-3000} -e ${RACK_ENV:-development} 2 | -------------------------------------------------------------------------------- /public/images/rails.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edebill/modulecounts/HEAD/public/images/rails.png -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative '../config/boot' 3 | require 'rake' 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /test/model/helpers/index_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class IndexHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | protect_from_forgery 3 | end 4 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 2 | 3 | require 'bundler/setup' # Set up gems listed in the Gemfile. 4 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path('../../config/application', __FILE__) 3 | require_relative '../config/boot' 4 | require 'rails/commands' 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .bundle 2 | db/*.sqlite3 3 | log/*.log 4 | tmp/**/* 5 | *~ 6 | .*# 7 | #* 8 | .#* 9 | /capybara-* 10 | **\#* 11 | .env 12 | *.swp 13 | .DS_Store 14 | 15 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Modulecounts::Application 5 | -------------------------------------------------------------------------------- /config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.action_dispatch.cookies_serializer = :marshal 4 | -------------------------------------------------------------------------------- /public/stylesheets/smoothness/images/ui-icons_222222_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edebill/modulecounts/HEAD/public/stylesheets/smoothness/images/ui-icons_222222_256x240.png -------------------------------------------------------------------------------- /public/stylesheets/smoothness/images/ui-icons_2e83ff_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edebill/modulecounts/HEAD/public/stylesheets/smoothness/images/ui-icons_2e83ff_256x240.png -------------------------------------------------------------------------------- /public/stylesheets/smoothness/images/ui-icons_454545_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edebill/modulecounts/HEAD/public/stylesheets/smoothness/images/ui-icons_454545_256x240.png -------------------------------------------------------------------------------- /public/stylesheets/smoothness/images/ui-icons_888888_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edebill/modulecounts/HEAD/public/stylesheets/smoothness/images/ui-icons_888888_256x240.png -------------------------------------------------------------------------------- /public/stylesheets/smoothness/images/ui-icons_cd0a0a_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edebill/modulecounts/HEAD/public/stylesheets/smoothness/images/ui-icons_cd0a0a_256x240.png -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.session_store :cookie_store, key: '_modulecounts_session' 4 | -------------------------------------------------------------------------------- /public/stylesheets/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edebill/modulecounts/HEAD/public/stylesheets/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png -------------------------------------------------------------------------------- /public/stylesheets/smoothness/images/ui-bg_flat_75_ffffff_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edebill/modulecounts/HEAD/public/stylesheets/smoothness/images/ui-bg_flat_75_ffffff_40x100.png -------------------------------------------------------------------------------- /public/stylesheets/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edebill/modulecounts/HEAD/public/stylesheets/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png -------------------------------------------------------------------------------- /public/stylesheets/smoothness/images/ui-bg_glass_65_ffffff_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edebill/modulecounts/HEAD/public/stylesheets/smoothness/images/ui-bg_glass_65_ffffff_1x400.png -------------------------------------------------------------------------------- /public/stylesheets/smoothness/images/ui-bg_glass_75_dadada_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edebill/modulecounts/HEAD/public/stylesheets/smoothness/images/ui-bg_glass_75_dadada_1x400.png -------------------------------------------------------------------------------- /public/stylesheets/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edebill/modulecounts/HEAD/public/stylesheets/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png -------------------------------------------------------------------------------- /public/stylesheets/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edebill/modulecounts/HEAD/public/stylesheets/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /test/model/count_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class CountTest < ActiveSupport::TestCase 4 | # Replace this with your real tests. 5 | test "the truth" do 6 | assert true 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /public/stylesheets/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edebill/modulecounts/HEAD/public/stylesheets/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png -------------------------------------------------------------------------------- /config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | ## Change renderer defaults here. 2 | # 3 | # ApplicationController.renderer.defaults.merge!( 4 | # http_host: 'example.org', 5 | # https: false 6 | # ) 7 | -------------------------------------------------------------------------------- /test/model/repository_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class RepositoryTest < ActiveSupport::TestCase 4 | # Replace this with your real tests. 5 | test "the truth" do 6 | assert true 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /db/migrate/20170317150818_add_hidden_to_repositories.rb: -------------------------------------------------------------------------------- 1 | class AddHiddenToRepositories < ActiveRecord::Migration[5.0] 2 | def change 3 | add_column :repositories, :hidden, :boolean, default: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /test/model/repository_stats_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class RepositoryStatsTest < ActiveSupport::TestCase 4 | # Replace this with your real tests. 5 | test "the truth" do 6 | assert true 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /doc/README_FOR_APP: -------------------------------------------------------------------------------- 1 | Use this README file to introduce your application and point to useful places in the API for learning more. 2 | Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries. 3 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-Agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /config/initializers/request_forgery_protection.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Enable origin-checking CSRF mitigation. 4 | Rails.application.config.action_controller.forgery_protection_origin_check = true 5 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /db/migrate/20101219170711_add_graph_flag_to_repositories.rb: -------------------------------------------------------------------------------- 1 | class AddGraphFlagToRepositories < ActiveRecord::Migration[4.2] 2 | def up 3 | # add_column :repositories, :graph, :boolean 4 | end 5 | 6 | def down 7 | # remove_column :repositories, :graph 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20180603170755_fix_drupal.rb: -------------------------------------------------------------------------------- 1 | class FixDrupal < ActiveRecord::Migration[5.1] 2 | def up 3 | repo = Repository.where(name: "Drupal (php)").first 4 | sampler = repo.sampler 5 | sampler.regex = ">([0-9,]+) modules match your search" 6 | sampler.save! 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /db/migrate/20180603171504_fix_dub.rb: -------------------------------------------------------------------------------- 1 | class FixDub < ActiveRecord::Migration[5.1] 2 | def up 3 | repo = Repository.where(name: "DUB (dlang)").first 4 | sampler = repo.sampler 5 | 6 | sampler.regex = "([1-9,]+) packages" 7 | sampler.save! 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require File.expand_path('../config/application', __FILE__) 5 | require 'rake' 6 | 7 | Modulecounts::Application.load_tasks 8 | -------------------------------------------------------------------------------- /db/migrate/20190623191817_lua_page_design.rb: -------------------------------------------------------------------------------- 1 | class LuaPageDesign < ActiveRecord::Migration[5.2] 2 | def change 3 | r = Repository.find_by(name: "LuaRocks (Lua)") 4 | sampler = r.sampler 5 | sampler.regex = "span class=\"sub\">\\(([0-9,]+)\\) true 5 | validates_numericality_of :total, :integer => true 6 | validates_numericality_of :modules_day, :integer => true 7 | 8 | end 9 | -------------------------------------------------------------------------------- /app/models/count.rb: -------------------------------------------------------------------------------- 1 | class Count < ActiveRecord::Base 2 | belongs_to :repository 3 | 4 | validates_presence_of :repository_id, :record_date 5 | 6 | 7 | validates_numericality_of :value, :integer => true 8 | 9 | def self.earliest 10 | Count.order("record_date asc").limit(1).first 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20170806122556_change_dlang_regex.rb: -------------------------------------------------------------------------------- 1 | class ChangeDlangRegex < ActiveRecord::Migration[5.1] 2 | def change 3 | repository = Repository.where(name: "DUB (dlang)").first 4 | sampler = repository.sampler 5 | sampler.regex = "of (\\d+) packages found" 6 | sampler.save! 7 | 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /script/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. 3 | 4 | APP_PATH = File.expand_path('../../config/application', __FILE__) 5 | require File.expand_path('../../config/boot', __FILE__) 6 | require 'rails/commands' 7 | -------------------------------------------------------------------------------- /db/migrate/20201119115702_fix_pypi_2020.rb: -------------------------------------------------------------------------------- 1 | class FixPypi2020 < ActiveRecord::Migration[5.2] 2 | def up 3 | repo = Repository.where(name: "PyPI").first 4 | 5 | sampler = repo.sampler 6 | sampler.data_url = "https://pypi.org" 7 | sampler.regex = "\s([\\d,]+) projects" 8 | sampler.save! 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20170317151210_hide_godoc_and_bower.rb: -------------------------------------------------------------------------------- 1 | class HideGodocAndBower < ActiveRecord::Migration[5.0] 2 | def change 3 | r = Repository.where(name: "Bower (JS)").first 4 | r.hidden = true 5 | r.save 6 | 7 | r = Repository.where(name: "GoDoc (Go)").first 8 | r.hidden = true 9 | r.save 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/models/json_array_length_sampler.rb: -------------------------------------------------------------------------------- 1 | class JsonArrayLengthSampler < Sampler 2 | def sample 3 | response = HTTParty.get(self.data_url) 4 | 5 | count = JSON.parse(response.body).length 6 | 7 | if self.config(:offset) 8 | count = count.to_i + self.config(:offset) 9 | end 10 | 11 | count 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /db/migrate/20160530141025_fix_hex_pm.rb: -------------------------------------------------------------------------------- 1 | class FixHexPm < ActiveRecord::Migration[5.0] 2 | def change 3 | r = Repository.where(name: "Hex.pm (Elixir/Erlang)").first 4 | sampler = r.sampler 5 | sampler.data_url = "https://hex.pm/packages" 6 | sampler.regex = ">(\\d+) Results Found" 7 | sampler.save 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/models/json_hash_key_count_sampler.rb: -------------------------------------------------------------------------------- 1 | class JsonHashKeyCountSampler < Sampler 2 | def sample 3 | response = HTTParty.get(self.data_url) 4 | 5 | count = JSON.parse(response.body).keys.count 6 | 7 | if self.config(:offset) 8 | count = count.to_i + self.config(:offset) 9 | end 10 | 11 | count 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /db/migrate/20101209035103_create_counts.rb: -------------------------------------------------------------------------------- 1 | class CreateCounts < ActiveRecord::Migration[4.2] 2 | def up 3 | create_table :counts do |t| 4 | t.integer :repository_id 5 | t.integer :value 6 | t.datetime :record_date 7 | 8 | t.timestamps 9 | end 10 | end 11 | 12 | def down 13 | drop_table :counts 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /db/migrate/20121021155545_clean_up_undercounted_npm_values.rb: -------------------------------------------------------------------------------- 1 | class CleanUpUndercountedNpmValues < ActiveRecord::Migration[4.2] 2 | def up 3 | # r = Repository.find_by_name('npm (node.js)') 4 | # Count.destroy_all(:repository_id => r.id, :value => [15, 16]) 5 | end 6 | 7 | def down 8 | # nothing to be done - this was destructive 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20190623190616_julia_pagination.rb: -------------------------------------------------------------------------------- 1 | class JuliaPagination < ActiveRecord::Migration[5.2] 2 | def change 3 | r = Repository.find_by(name: "Julia") 4 | new_sampler = JsonHashKeyCountSampler.new(data_url: "https://pkg.julialang.org/docs/pkgs.json", 5 | offset: -4) 6 | r.sampler = new_sampler 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /db/migrate/20130330150618_show_packagist_by_default.rb: -------------------------------------------------------------------------------- 1 | class ShowPackagistByDefault < ActiveRecord::Migration[4.2] 2 | def up 3 | pi = Repository.find_by_name("Packagist (PHP)") 4 | pi.graph = true 5 | pi.save! 6 | end 7 | 8 | def down 9 | pi = Repository.find_by_name("Packagist (PHP)") 10 | pi.graph = false 11 | pi.save! 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /db/migrate/20140908125438_change.rb: -------------------------------------------------------------------------------- 1 | class Change < ActiveRecord::Migration[4.2] 2 | def up 3 | r = Repository.find_by_name "Bower (JS)" 4 | sampler = r.sampler 5 | 6 | sampler.data_url = "https://bower-server-etl.herokuapp.com/api/1/data/all" 7 | sampler.parse_sampler_configuration 8 | sampler.save 9 | end 10 | 11 | def down 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /db/migrate/20180603170145_fix_pypi.rb: -------------------------------------------------------------------------------- 1 | class FixPypi < ActiveRecord::Migration[5.1] 2 | def up 3 | repo = Repository.where(name: "PyPI").first 4 | repo.url = "https://pypi.org" 5 | repo.save! 6 | 7 | sampler = repo.sampler 8 | sampler.data_url = "https://pypi.org" 9 | sampler.regex = ">([\\d,]+) projects" 10 | sampler.save! 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }]) 7 | # Mayor.create(:name => 'Daley', :city => cities.first) 8 | -------------------------------------------------------------------------------- /db/migrate/20160408114700_rubygems_requires_https.rb: -------------------------------------------------------------------------------- 1 | class RubygemsRequiresHttps < ActiveRecord::Migration[5.0] 2 | def up 3 | repo = Repository.where(name: "Rubygems.org").first 4 | sampler = repo.sampler 5 | sampler.data_url = "https://rubygems.org/stats" 6 | sampler.regex = "Total gems[^<]*<[^<]*stat__count\">([0-9,]+)<" 7 | sampler.save! 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20160408130459_npm_redesign.rb: -------------------------------------------------------------------------------- 1 | class NpmRedesign < ActiveRecord::Migration[5.0] 2 | def up 3 | repo = Repository.where(name: "npm (node.js)").first 4 | sampler = repo.sampler 5 | sampler.destroy 6 | ns = JsonSampler.new 7 | ns.key = "doc_count" 8 | ns.data_url = "https://skimdb.npmjs.com/registry" 9 | repo.sampler = ns 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20111214013904_create_samplers.rb: -------------------------------------------------------------------------------- 1 | class CreateSamplers < ActiveRecord::Migration[4.2] 2 | def up 3 | create_table :samplers do |t| 4 | t.integer :repository_id 5 | t.string :type 6 | t.string :data_url 7 | t.text :configuration_json 8 | t.timestamps 9 | end 10 | end 11 | 12 | def down 13 | drop_table :samplers 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /test/controller/index_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class IndexControllerTest < ActionController::TestCase 4 | test "should get index" do 5 | get :index 6 | assert_response :success 7 | end 8 | 9 | test "should get csv" do 10 | CsvExport.create(csv: "1,2,3\n4,5,6") 11 | get :index, format: :csv 12 | assert_response :success 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /db/migrate/20170910124716_fix_crates_sampling.rb: -------------------------------------------------------------------------------- 1 | class FixCratesSampling < ActiveRecord::Migration[5.1] 2 | def change 3 | repository = Repository.where(name: "Crates.io (Rust)").first 4 | repository.counts.where(value: 0).destroy_all 5 | 6 | sampler = repository.sampler 7 | sampler.data_url = "https://crates.io/api/v1/summary" 8 | sampler.save! 9 | 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20140622144911_fix_cran_url.rb: -------------------------------------------------------------------------------- 1 | class FixCranUrl < ActiveRecord::Migration[4.2] 2 | def up 3 | repo = Repository.where(:name => "CRAN (R)").first 4 | repo.url = 'http://cran.r-project.org' 5 | repo.save! 6 | end 7 | 8 | def down 9 | repo = Repository.where(:name => "CRAN (R)").first 10 | repo.url = 'http://cran.rproject.org' 11 | repo.save! 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /db/migrate/20110402134444_update_cpan_regex.rb: -------------------------------------------------------------------------------- 1 | class UpdateCpanRegex < ActiveRecord::Migration[4.2] 2 | def up 3 | cpan = Repository.where(:name => 'CPAN').first 4 | cpan.regex = "in ([\\d,]+) distributions" 5 | cpan.save 6 | end 7 | 8 | def down 9 | cpan = Repository.where(:name => 'CPAN').first 10 | cpan.regex = "authors ([\\d,]+) modules" 11 | cpan.save 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /db/migrate/20111214024049_load_regex_samplers.rb: -------------------------------------------------------------------------------- 1 | class LoadRegexSamplers < ActiveRecord::Migration[4.2] 2 | def up 3 | Repository.find_each do |r| 4 | next if r.sampler 5 | s = RegexSampler.new 6 | s.regex = r.regex 7 | s.data_url = r.url 8 | r.sampler = s 9 | r.save! 10 | end 11 | end 12 | 13 | def down 14 | # nothing to be done 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /db/migrate/20110120035756_add_cpan_search.rb: -------------------------------------------------------------------------------- 1 | class AddCpanSearch < ActiveRecord::Migration[4.2] 2 | def up 3 | Repository.create!(:name => "CPAN (search)", 4 | :url => "http://search.cpan.org", 5 | :regex => ",\\s+(\\d+)\\sDistributions") 6 | end 7 | 8 | def down 9 | Repository.where(:name => "CPAN (search)").last.try(:destroy) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/fixtures/repositories.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html 2 | 3 | ada: 4 | id: 1 5 | name: Ada 6 | url: MyString 7 | regex: MyString 8 | created_at: <%= Time.now %> 9 | updated_at: <%= Time.now %> 10 | 11 | cobol: 12 | id: 2 13 | name: Cobol 14 | url: MyString 15 | regex: MyString 16 | created_at: <%= Time.now %> 17 | updated_at: <%= Time.now %> 18 | -------------------------------------------------------------------------------- /db/migrate/20101209034739_create_repositories.rb: -------------------------------------------------------------------------------- 1 | class CreateRepositories < ActiveRecord::Migration[4.2] 2 | def up 3 | create_table :repositories do |t| 4 | t.string :name 5 | t.string :url 6 | t.string :regex 7 | t.datetime :last_checked 8 | t.boolean :graph 9 | 10 | t.timestamps 11 | end 12 | end 13 | 14 | def down 15 | drop_table :repositories 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /db/migrate/20110402135956_add_cran.rb: -------------------------------------------------------------------------------- 1 | class AddCran < ActiveRecord::Migration[4.2] 2 | def up 3 | Repository.create!(:name => "CRAN (R)", 4 | :url => "http://cran.opensourceresources.org/web/packages/", 5 | :regex => "features\\s+([\\d,]+)\\s+available packages") 6 | end 7 | 8 | def down 9 | Repository.where(:name => "CRAN (R)").last.try(:destroy) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20130228022158_create_csv_exports.rb: -------------------------------------------------------------------------------- 1 | class CreateCsvExports < ActiveRecord::Migration[4.2] 2 | def up 3 | create_table :csv_exports do |t| 4 | t.text :csv 5 | 6 | t.timestamps 7 | end 8 | 9 | export = CsvExport.new 10 | export.csv = "" # no data - rake cron will fill some in 11 | export.save! 12 | end 13 | 14 | def down 15 | drop_table :csv_exports 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /db/migrate/20110402141640_add_hackage.rb: -------------------------------------------------------------------------------- 1 | class AddHackage < ActiveRecord::Migration[4.2] 2 | def up 3 | Repository.create!(:name => "Hackage (Haskell)", 4 | :url => "http://holumbus.fh-wedel.de/hayoo/hayoo.html", 5 | :regex => "more than ([\\d,.]+) packages") 6 | end 7 | 8 | def down 9 | Repository.where(:name => "Hackage (Haskell)").last.try(:destroy) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/tasks/cron.rake: -------------------------------------------------------------------------------- 1 | desc "This task runs nightly" 2 | task :cron => :environment do 3 | Repository.all.each do |r| 4 | begin 5 | puts "updating #{r.name}" 6 | r.update_count 7 | r.update_stats 8 | rescue => e 9 | puts " oops! - #{e}" 10 | Rails.logger.error("error updating #{r.name} - #{e.inspect}") 11 | end 12 | end 13 | 14 | puts "making CSV export" 15 | CsvExport.update 16 | end 17 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html 3 | 4 | root to: "index#index" 5 | get "modulecounts.csv" => "index#index", format: "csv" 6 | get "repositories" => "repository#index", as: "index" 7 | get "repositories/:id/counts" => "repository#counts" 8 | get "repositories/:id/counts/:start/:finish" => "repository#counts" 9 | end 10 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /test/fixtures/counts.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html 2 | 3 | one: 4 | repository_id: 1 5 | value: 3 6 | record_date: <%= 1.days.ago %> 7 | 8 | two: 9 | repository_id: 1 10 | value: 2 11 | record_date: <%= 2.days.ago %> 12 | 13 | three: 14 | repository_id: 1 15 | value: 1 16 | record_date: <%= 3.days.ago %> 17 | 18 | four: 19 | repository_id: 2 20 | value: 5 21 | record_date: <%= 2.days.ago %> 22 | -------------------------------------------------------------------------------- /app/models/json_sampler.rb: -------------------------------------------------------------------------------- 1 | class JsonSampler < Sampler 2 | def key 3 | self.config(:key) 4 | end 5 | 6 | def key=(k) 7 | self.set_config(:key,k) 8 | end 9 | 10 | def sample 11 | response = HTTParty.get(self.data_url) 12 | 13 | count = JSON.parse(response.body)[self.key].to_s.gsub(/[, ]/, "").to_i 14 | 15 | if self.config(:offset) 16 | count = count.to_i + self.config(:offset) 17 | end 18 | 19 | count 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /db/migrate/20140202221650_add_melpa.rb: -------------------------------------------------------------------------------- 1 | class AddMelpa < ActiveRecord::Migration[4.2] 2 | def up 3 | r = Repository.new(:name => 'Melpa (Emacs)', :url => 'http://melpa.milkbox.net') 4 | s = JsonHashKeyCountSampler.new 5 | s.data_url = "http://melpa.milkbox.net/recipes.json" 6 | s.offset = 0 7 | r.sampler = s 8 | 9 | r.save! 10 | end 11 | 12 | def down 13 | r = Repository.find_by_name('Melpa (Emacs)') 14 | r.destroy 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /db/migrate/20111214031405_add_npm.rb: -------------------------------------------------------------------------------- 1 | class AddNpm < ActiveRecord::Migration[4.2] 2 | def up 3 | r = Repository.new(:name => 'npm (node.js)', :url => 'http://npmjs.org') 4 | s = JsonSampler.new 5 | s.data_url = "http://search.npmjs.org/api/_all_docs?limit=0" 6 | s.key = 'total_rows' 7 | s.offset = -1 8 | r.sampler = s 9 | 10 | r.save! 11 | end 12 | 13 | def down 14 | r = Repository.find(:name => 'npm') 15 | r.destroy 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /db/migrate/20120225161251_add_nuget.rb: -------------------------------------------------------------------------------- 1 | class AddNuget < ActiveRecord::Migration[4.2] 2 | def up 3 | r = Repository.new(:name => 'nuget (.NET)', :url => 'http://nuget.org') 4 | s = RegexSampler.new 5 | s.data_url = "http://nuget.org/packages" 6 | s.regex = 'There are (\d+) packages' 7 | s.offset = 0 8 | r.sampler = s 9 | 10 | r.save! 11 | end 12 | 13 | def down 14 | r = Repository.find(:name => 'nuget') 15 | r.destroy 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /db/migrate/20121021154949_update_npm_regex.rb: -------------------------------------------------------------------------------- 1 | class UpdateNpmRegex < ActiveRecord::Migration[4.2] 2 | def up 3 | r = Repository.find_by_name('npm (node.js)') 4 | s = r.sampler 5 | s.regex = "Total Packages:\\s+(\\d.*\\d)" # they started putting spaces between 1000s groups 6 | s.save! 7 | end 8 | 9 | def down 10 | r = Repository.find_by_name('npm (node.js)') 11 | s = r.sampler 12 | s.regex = "Total Packages:\\s+(\\d+)" 13 | s.save! 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | ENV["RAILS_ENV"] = "test" 2 | require File.expand_path('../../config/environment', __FILE__) 3 | require 'rails/test_help' 4 | 5 | class ActiveSupport::TestCase 6 | # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order. 7 | # 8 | # Note: You'll currently still have to declare fixtures explicitly in integration tests 9 | # -- they do not yet inherit this setting 10 | fixtures :all 11 | 12 | # Add more helper methods to be used by all tests here... 13 | end 14 | -------------------------------------------------------------------------------- /test/fixtures/repository_stats.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html 2 | 3 | one: 4 | total: 1 5 | last_updated: 2010-12-10 22:14:42 6 | modules_day: 1 7 | days_to_crossover: 1 8 | created_at: <%= Time.now %> 9 | updated_at: <%= Time.now %> 10 | repository_id: 1 11 | 12 | two: 13 | total: 1 14 | last_updated: 2010-12-10 22:14:42 15 | modules_day: 1 16 | days_to_crossover: 1 17 | created_at: <%= Time.now %> 18 | updated_at: <%= Time.now %> 19 | repository_id: 2 20 | -------------------------------------------------------------------------------- /db/migrate/20101211041442_create_repository_stats.rb: -------------------------------------------------------------------------------- 1 | class CreateRepositoryStats < ActiveRecord::Migration[4.2] 2 | def up 3 | create_table :repository_stats do |t| 4 | t.integer :repository_id 5 | t.integer :total 6 | t.datetime :last_updated 7 | t.integer :modules_day 8 | t.integer :days_to_crossover 9 | 10 | t.timestamps 11 | end 12 | 13 | add_index :repository_stats, :repository_id, :unique => true 14 | end 15 | 16 | def down 17 | drop_table :repository_stats 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /db/migrate/20160528132306_add_dub.rb: -------------------------------------------------------------------------------- 1 | class AddDub < ActiveRecord::Migration[5.0] 2 | def up 3 | @name = 'DUB (dlang)' 4 | @url = 'https://code.dlang.org' 5 | r = Repository.new(:name => @name, :url => @url) 6 | s = RegexSampler.new 7 | s.data_url = @url 8 | s.regex = '

Found (\d+) packages.<\/p>' 9 | s.offset = 0 10 | r.sampler = s 11 | r.save! 12 | end 13 | 14 | def down 15 | @name = 'DUB (dlang)' 16 | 17 | r = Repository.where(:name => @name).first 18 | r.destroy 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/controllers/index_controller.rb: -------------------------------------------------------------------------------- 1 | class IndexController < ApplicationController 2 | def index 3 | @repositories = Repository.where(hidden: false).all.sort { |a,b| a.name.downcase <=> b.name.downcase } 4 | @latest = Count.order("record_date desc").limit(1).first 5 | 6 | response.headers['Cache-Control'] = 'public, max-age=300' 7 | respond_to do |format| 8 | format.html { render } 9 | format.csv { 10 | render content_type: 'text/csv', inline: CsvExport.first.csv 11 | } 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /config/initializers/secret_token.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | # Make sure the secret is at least 30 characters and all random, 6 | # no regular words or you'll be exposed to dictionary attacks. 7 | Modulecounts::Application.config.secret_token = 'c9a388065f036d983660078df41fd81e2a2db24b7d949a7824a6459be451e46455de1a6ecbd567a7486d9ca71cd89de63825c4fba918ef7f1bb920b995db4072' 8 | -------------------------------------------------------------------------------- /db/migrate/20121125152055_add_clojars.rb: -------------------------------------------------------------------------------- 1 | class AddClojars < ActiveRecord::Migration[4.2] 2 | def up 3 | r = Repository.new(:name => 'Clojars (Clojure)', :url => 'http://clojars.org') 4 | s = RegexSampler.new 5 | s.data_url = "http://clojars.org/projects" 6 | s.regex = 'Displaying projects .* of \s*(\d+)\s*\s*' 7 | s.offset = 0 8 | r.sampler = s 9 | 10 | r.save! 11 | end 12 | 13 | def down 14 | r = Repository.where(:name => 'Clojars (Clojure)').first 15 | r.destroy 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /db/migrate/20130228130010_add_packagist_repository.rb: -------------------------------------------------------------------------------- 1 | class AddPackagistRepository < ActiveRecord::Migration[4.2] 2 | def up 3 | r = Repository.new(:name => 'Packagist (PHP)', :url => 'http://packagist.org') 4 | s = RegexSampler.new 5 | s.data_url = "http://packagist.org/statistics" 6 | s.regex = '(\d+\s+\d+) packages registered' 7 | s.offset = 0 8 | r.sampler = s 9 | 10 | r.save! 11 | end 12 | 13 | def down 14 | r = Repository.where(:name => 'Packagist (PHP)').first 15 | r.destroy 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = '1.0' 5 | 6 | # Add additional assets to the asset load path 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | 9 | # Precompile additional assets. 10 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. 11 | # Rails.application.config.assets.precompile += %w( search.js ) 12 | -------------------------------------------------------------------------------- /db/migrate/20140622153214_add_go_lang.rb: -------------------------------------------------------------------------------- 1 | class AddGoLang < ActiveRecord::Migration[4.2] 2 | def up 3 | @name = 'GoDoc (Go)' 4 | @url = 'http://godoc.org/-/index' 5 | r = Repository.new(:name => @name, :url => @url) 6 | s = RegexSampler.new 7 | s.data_url = @url 8 | s.regex = 'Number of packages: (\d+)\.' 9 | s.offset = 0 10 | r.sampler = s 11 | 12 | r.save! 13 | end 14 | 15 | def down 16 | @name = 'GoDoc (Go)' 17 | 18 | r = Repository.where(:name => @name).first 19 | r.destroy 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /db/migrate/20170402164232_add_julia.rb: -------------------------------------------------------------------------------- 1 | class AddJulia < ActiveRecord::Migration[5.0] 2 | def up 3 | @name = 'Julia' 4 | @url = 'https://julialang.org/' 5 | r = Repository.new(:name => @name, :url => @url) 6 | s = RegexSampler.new 7 | s.data_url = 'http://pkg.julialang.org' 8 | s.regex = 'Listing all ([0-9,]+)\s+' 9 | s.offset = 0 10 | r.sampler = s 11 | r.save! 12 | end 13 | 14 | def down 15 | @name = 'Julia' 16 | 17 | r = Repository.where(:name => @name).first 18 | r.destroy 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /db/migrate/20140202222246_update_hackage_to_new_server.rb: -------------------------------------------------------------------------------- 1 | class UpdateHackageToNewServer < ActiveRecord::Migration[4.2] 2 | def up 3 | r = Repository.find_by_name("Hackage (Haskell)") 4 | r.url = "http://hackage.haskell.org/" 5 | r.save! 6 | 7 | r.sampler.destroy 8 | s = JsonArrayLengthSampler.new(:data_url => "http://hackage.haskell.org/packages/.json") 9 | s.offset = 0 10 | r.sampler = s 11 | r.save! 12 | end 13 | 14 | def down 15 | # not going down, but you can re-run w/out borking too much 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /db/migrate/20170317145504_add_gopm_io.rb: -------------------------------------------------------------------------------- 1 | class AddGopmIo < ActiveRecord::Migration[5.0] 2 | def up 3 | @name = 'Gopm (go)' 4 | @url = 'https://gopm.io/' 5 | r = Repository.new(:name => @name, :url => @url) 6 | s = RegexSampler.new 7 | s.data_url = @url 8 | s.regex = 'Statistic.*Packages.*>([,\d]+)<.*PACKAGES' 9 | s.offset = 0 10 | r.sampler = s 11 | r.save! 12 | end 13 | 14 | def down 15 | @name = 'Gopm (go)' 16 | 17 | r = Repository.where(:name => @name).first 18 | r.destroy 19 | end 20 | 21 | end 22 | -------------------------------------------------------------------------------- /db/migrate/20180304145537_add_crystal_shards.rb: -------------------------------------------------------------------------------- 1 | class AddCrystalShards < ActiveRecord::Migration[5.1] 2 | def up 3 | @name = 'Crystal Shards' 4 | @url = 'https://crystalshards.xyz' 5 | r = Repository.new(:name => @name, :url => @url) 6 | s = RegexSampler.new 7 | s.data_url = @url 8 | s.regex = '(\d+) total shards' 9 | s.offset = 0 10 | r.sampler = s 11 | r.save! 12 | end 13 | 14 | def down 15 | @name = 'Crystal Shards' 16 | 17 | r = Repository.where(:name => @name).first 18 | r.destroy 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /db/migrate/20150101130359_new_npm_website.rb: -------------------------------------------------------------------------------- 1 | class NewNpmWebsite < ActiveRecord::Migration[4.2] 2 | def up 3 | r = Repository.find_by_name("npm (node.js)") 4 | sampler = r.sampler 5 | 6 | sampler.regex = ">(\\d+)\\s+total packages" 7 | sampler.save! 8 | 9 | Count.where(:value => 0).all.each { |c| c.destroy } 10 | end 11 | 12 | def down 13 | r = Repository.find_by_name("npm (node.js)") 14 | sampler = r.sampler 15 | 16 | sampler.regex = "Total Packages:\\s+(\\d.*\\d)" 17 | sampler.save! 18 | 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/models/json_path_array_length_sampler.rb: -------------------------------------------------------------------------------- 1 | class JsonPathArrayLengthSampler < Sampler 2 | def path 3 | self.config(:path) 4 | end 5 | 6 | def path=(p) 7 | self.set_config(:path,p) 8 | end 9 | 10 | def sample 11 | response = HTTParty.get(self.data_url, { :headers => { 'Accept' => 'Application/json'}}) 12 | 13 | json_path = JsonPath.new(self.path) 14 | 15 | count = json_path.on(response.body).length 16 | 17 | if self.config(:offset) 18 | count = count.to_i + self.config(:offset) 19 | end 20 | 21 | count 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /db/migrate/20130330144314_add_maven.rb: -------------------------------------------------------------------------------- 1 | class AddMaven < ActiveRecord::Migration[4.2] 2 | def up 3 | r = Repository.new(:name => 'Maven Central (Java)', 4 | :url => 'http://www.maven.org', 5 | :graph => true) 6 | 7 | s = JsonSampler.new 8 | s.data_url = "http://search.maven.org/quickstats" 9 | s.key = 'gaNumber' 10 | s.offset = 0 11 | r.sampler = s 12 | 13 | r.save! 14 | end 15 | 16 | def down 17 | r = Repository.where(:name => 'Maven Central (Java)').first 18 | r.destroy 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /db/migrate/20140725132816_add_bower.rb: -------------------------------------------------------------------------------- 1 | class AddBower < ActiveRecord::Migration[4.2] 2 | def up 3 | @repo_name = 'Bower (JS)' 4 | @url = 'http://bower.io/' 5 | r = Repository.new(:name => @repo_name, :url => @url) 6 | s = JsonPathSampler.new 7 | s.data_url = "http://stats.bower.io/api/1/data/all" 8 | s.path = '$..totalPackages' 9 | s.offset = 0 10 | r.sampler = s 11 | 12 | r.save! 13 | end 14 | 15 | def down 16 | @repo_name = 'Bower (JS)' 17 | r = Repository.where(:name => @repo_name).first 18 | r.destroy 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /config/initializers/cors.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Avoid CORS issues when API is called from the frontend app. 4 | # Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests. 5 | 6 | # Read more: https://github.com/cyu/rack-cors 7 | 8 | # Rails.application.config.middleware.insert_before 0, Rack::Cors do 9 | # allow do 10 | # origins 'example.com' 11 | # 12 | # resource '*', 13 | # headers: :any, 14 | # methods: [:get, :post, :put, :patch, :delete, :options, :head] 15 | # end 16 | # end 17 | -------------------------------------------------------------------------------- /db/migrate/20141125120004_add_crates_rust.rb: -------------------------------------------------------------------------------- 1 | class AddCratesRust < ActiveRecord::Migration[4.2] 2 | def up 3 | @repo_name = 'Crates.io (Rust)' 4 | @url = 'https://crates.io/' 5 | r = Repository.new(:name => @repo_name, :url => @url) 6 | s = JsonPathSampler.new 7 | s.data_url = "https://crates.io/summary" 8 | s.path = '$.num_crates' 9 | s.offset = 0 10 | r.sampler = s 11 | 12 | r.save! 13 | end 14 | 15 | def down 16 | @repo_name = 'Crates.io (Rust)' 17 | r = Repository.where(:name => @repo_name).first 18 | r.destroy 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /db/migrate/20150309132532_add_luarocks.rb: -------------------------------------------------------------------------------- 1 | class AddLuarocks < ActiveRecord::Migration[4.2] 2 | def up 3 | @name = 'LuaRocks (Lua)' 4 | @url = 'https://rocks.moonscript.org' 5 | r = Repository.new(:name => @name, :url => @url) 6 | s = RegexSampler.new 7 | s.data_url = @url + '/m/root' 8 | s.regex = '\\((\\d+)\\)' 9 | s.offset = 0 10 | r.sampler = s 11 | r.save! 12 | end 13 | 14 | def down 15 | @name = 'LuaRocks (Lua)' 16 | 17 | r = Repository.where(:name => @name).first 18 | r.destroy 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /db/migrate/20170514173011_add_vim_scripts.rb: -------------------------------------------------------------------------------- 1 | class AddVimScripts < ActiveRecord::Migration[5.0] 2 | def up 3 | @name = 'Vim Scripts' 4 | @url = 'http://www.vim.org/scripts' 5 | r = Repository.new(:name => @name, :url => @url) 6 | s = RegexSampler.new 7 | s.data_url = 'https://vim.sourceforge.io/scripts/' 8 | s.regex = 'Displaying \d+ of\s+([0-9,]+)<' 9 | s.offset = 0 10 | r.sampler = s 11 | r.save! 12 | end 13 | 14 | def down 15 | @name = 'Vim Scripts' 16 | 17 | r = Repository.where(:name => @name).first 18 | r.destroy 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /db/migrate/20140725143533_add_hex_pm.rb: -------------------------------------------------------------------------------- 1 | class AddHexPm < ActiveRecord::Migration[4.2] 2 | def up 3 | @repo_name = 'Hex.pm (Elixir/Erlang)' 4 | @url = 'https://hex.pm/' 5 | r = Repository.new(:name => @repo_name, :url => @url) 6 | s = RegexSampler.new 7 | s.data_url = "https://hex.pm" 8 | s.regex = '(\d+)<\/div>\s*]*>packages' 9 | s.offset = 0 10 | r.sampler = s 11 | 12 | r.save! 13 | end 14 | 15 | def down 16 | @repo_name = 'Hex.pm (Elixir/Erlang)' 17 | r = Repository.where(:name => @repo_name).first 18 | r.destroy 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /db/migrate/20150715115329_hex_pm_new_page_design.rb: -------------------------------------------------------------------------------- 1 | class HexPmNewPageDesign < ActiveRecord::Migration[4.2] 2 | def up 3 | repo_name = 'Hex.pm (Elixir/Erlang)' 4 | repo = Repository.where(name: repo_name).first 5 | sampler = repo.sampler 6 | sampler.regex = ">([ 0-9]+)<\/td>\\s*]+>packages<" 7 | sampler.save! 8 | end 9 | 10 | def down 11 | repo_name = 'Hex.pm (Elixir/Erlang)' 12 | repo = Repository.where(name: repo_name).first 13 | sampler = repo.sampler 14 | sampler.regex = '(\d+)<\/div>\s*]*>packages' 15 | sampler.save 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /db/migrate/20150715125133_update_for_packagist_new_design.rb: -------------------------------------------------------------------------------- 1 | class UpdateForPackagistNewDesign < ActiveRecord::Migration[4.2] 2 | def up 3 | repo_name = 'Packagist (PHP)' 4 | repo = Repository.where(name: repo_name).first 5 | sampler = repo.sampler 6 | sampler.regex = "Packages registered<\\/dt>[^>]+>(\\d[^<]+)<\\/dd" 7 | sampler.save! 8 | end 9 | 10 | def down 11 | repo_name = 'Packagist (PHP)' 12 | repo = Repository.where(name: repo_name).first 13 | sampler = repo.sampler 14 | sampler.regex = "(\\d+\\s+\\d+) packages registered" 15 | sampler.save! 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /db/migrate/20151127170059_add_perl_6.rb: -------------------------------------------------------------------------------- 1 | class AddPerl6 < ActiveRecord::Migration[4.2] 2 | def up 3 | r = Repository.new(name: repo_name, 4 | url: "http://modules.perl6.org/", 5 | graph: false) 6 | s = RegexSampler.new 7 | s.data_url = "http://modules.perl6.org/total" 8 | s.regex = "(\\d+)" 9 | s.offset = 0 10 | r.sampler = s 11 | 12 | r.save! 13 | end 14 | 15 | def down 16 | r = Repository.where(:name => repo_name).first 17 | r.destroy 18 | end 19 | 20 | def repo_name 21 | "Perl 6 Ecosystem (perl 6)" 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /db/migrate/20170910132602_add_nim_lang.rb: -------------------------------------------------------------------------------- 1 | class AddNimLang < ActiveRecord::Migration[5.1] 2 | def up 3 | @repo_name = 'Nimble (Nim)' 4 | @url = 'https://github.com/nim-lang/nimble/' 5 | r = Repository.new(:name => @repo_name, :url => @url) 6 | s = JsonPathArrayLengthSampler.new 7 | s.data_url = "https://raw.githubusercontent.com/nim-lang/packages/master/packages.json" 8 | s.path = '$..url' 9 | s.offset = 0 10 | r.sampler = s 11 | 12 | r.save! 13 | end 14 | 15 | def down 16 | @repo_name = 'Nimble (Nim)' 17 | r = Repository.where(:name => @repo_name).first 18 | r.destroy 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /db/migrate/20151127171342_add_drupal.rb: -------------------------------------------------------------------------------- 1 | class AddDrupal < ActiveRecord::Migration[5.0] 2 | def up 3 | r = Repository.new(name: repo_name, 4 | url: "https://www.drupal.org/project/project_module", 5 | graph: false) 6 | s = RegexSampler.new 7 | s.data_url = "https://www.drupal.org/project/project_module" 8 | s.regex = ">([0-9,]+) Modules match your search" 9 | s.offset = 0 10 | r.sampler = s 11 | 12 | r.save! 13 | end 14 | 15 | def down 16 | r = Repository.where(:name => repo_name).first 17 | r.destroy 18 | end 19 | 20 | def repo_name 21 | "Drupal (php)" 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /db/migrate/20120826135117_new_npm_site_design.rb: -------------------------------------------------------------------------------- 1 | class NewNpmSiteDesign < ActiveRecord::Migration[4.2] 2 | def up 3 | r = Repository.find_by_name('npm (node.js)') 4 | r.sampler.destroy 5 | 6 | s = RegexSampler.new 7 | s.data_url = 'https://npmjs.org' 8 | s.regex = 'Total Packages:\s+(\d+)' 9 | s.offset = 0 10 | 11 | r.sampler = s 12 | r.save! 13 | end 14 | 15 | def down 16 | r = Repository.find_by_name('npm (node.js)') 17 | s = JsonSampler.new 18 | s.data_url = "http://search.npmjs.org/api/_all_docs?limit=0" 19 | s.key = 'total_rows' 20 | s.offset = -1 21 | r.sampler = s 22 | 23 | r.save! 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /db/migrate/20141123122505_rubygems_new_site_design.rb: -------------------------------------------------------------------------------- 1 | class RubygemsNewSiteDesign < ActiveRecord::Migration[4.2] 2 | def up 3 | rubygems = Repository.where(:name => "Rubygems.org").first 4 | sampler = rubygems.sampler 5 | 6 | sampler.data_url = "http://rubygems.org/stats" 7 | sampler.regex = "Total Gems[^<]*<[^<]*stat__count\">([0-9,]+)<" 8 | sampler.save! 9 | end 10 | 11 | def down 12 | rubygems = Repository.where(:name => "Rubygems.org").first 13 | sampler = rubygems.sampler 14 | 15 | sampler.data_url = "http://rubygems.org" 16 | sampler.regex = "{\"regex\":\"of ([\\\\d,]+) gems cut since\"}" 17 | sampler.save! 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /db/migrate/20120826141756_change_which_repos_to_show.rb: -------------------------------------------------------------------------------- 1 | class ChangeWhichReposToShow < ActiveRecord::Migration[4.2] 2 | def up 3 | ['npm (node.js)', 'nuget (.NET)'].each do |name| 4 | r = Repository.find_by_name(name) 5 | r.graph = true 6 | r.save 7 | end 8 | 9 | r = Repository.find_by_name('CPAN (search)') 10 | r.graph = false 11 | r.save 12 | end 13 | 14 | def down 15 | ['npm (node.js)', 'nuget (.NET)'].each do |name| 16 | r = Repository.find_by_name(name) 17 | r.graph = false 18 | r.save 19 | end 20 | 21 | r = Repository.find_by_name('CPAN (search)') 22 | r.graph = true 23 | r.save 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /app/models/json_path_sampler.rb: -------------------------------------------------------------------------------- 1 | class JsonPathSampler < Sampler 2 | def path 3 | self.config(:path) 4 | end 5 | 6 | def path=(p) 7 | self.set_config(:path,p) 8 | end 9 | 10 | def sample 11 | response = HTTParty.get( 12 | self.data_url, 13 | { 14 | :headers => { 15 | 'Accept' => 'Application/json', 16 | 'User-Agent' => 'modulecounts.com' 17 | } 18 | } 19 | ) 20 | 21 | json_path = JsonPath.new(self.path) 22 | 23 | count = json_path.on(response.body).first.to_i 24 | 25 | if self.config(:offset) 26 | count = count.to_i + self.config(:offset) 27 | end 28 | 29 | count 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /db/migrate/20151127163305_update_nuget.rb: -------------------------------------------------------------------------------- 1 | class UpdateNuget < ActiveRecord::Migration[4.2] 2 | def up 3 | r = Repository.where(name: 'nuget (.NET)').first 4 | r.sampler.destroy 5 | s = JsonSampler.new 6 | s.data_url = "http://www.nuget.org/stats/totals" 7 | s.key = "UniquePackages" 8 | s.offset = 0 9 | r.sampler = s 10 | 11 | r.save! 12 | end 13 | 14 | def down 15 | r = Repository.where(name: 'nuget (.NET)').first 16 | r.sampler.destroy 17 | s = RegexSampler.new 18 | s.data_url = "http://nuget.org/packages" 19 | s.regex = 'There are (\d+) packages' 20 | s.offset = 0 21 | r.sampler = s 22 | 23 | r.save! 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | ruby "2.4.1" 3 | 4 | gem 'rails', '5.2.3' 5 | 6 | gem 'rake', '~> 12.3.2' 7 | gem 'rails_12factor', group: :production 8 | 9 | gem 'nokogiri', '~> 1.8.1' 10 | gem 'parallel' 11 | 12 | # Bundle edge Rails instead: 13 | # gem 'rails', :git => 'git://github.com/rails/rails.git' 14 | 15 | gem 'pg', '~> 1.1.4' 16 | 17 | 18 | gem 'puma' 19 | 20 | # Bundle gems for the local environment. Make sure to 21 | # put test-only gems in this group so their generators 22 | # and rake tasks are available in development mode: 23 | group :development, :test do 24 | gem 'gerbilcharts' 25 | gem 'pry' 26 | end 27 | 28 | 29 | gem 'httparty' 30 | gem 'httpclient' 31 | gem 'jsonpath' 32 | -------------------------------------------------------------------------------- /config/unicorn.rb: -------------------------------------------------------------------------------- 1 | # config/unicorn.rb 2 | worker_processes Integer(ENV["WEB_CONCURRENCY"] || 3) 3 | timeout 15 4 | preload_app true 5 | 6 | before_fork do |server, worker| 7 | Signal.trap 'TERM' do 8 | puts 'Unicorn master intercepting TERM and sending myself QUIT instead' 9 | Process.kill 'QUIT', Process.pid 10 | end 11 | 12 | defined?(ActiveRecord::Base) and 13 | ActiveRecord::Base.connection.disconnect! 14 | end 15 | 16 | after_fork do |server, worker| 17 | Signal.trap 'TERM' do 18 | puts 'Unicorn worker intercepting TERM and doing nothing. Wait for master to send QUIT' 19 | end 20 | 21 | defined?(ActiveRecord::Base) and 22 | ActiveRecord::Base.establish_connection 23 | end 24 | 25 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # To learn more, please read the Rails Internationalization guide 20 | # available at http://guides.rubyonrails.org/i18n.html. 21 | 22 | en: 23 | hello: "Hello world" 24 | -------------------------------------------------------------------------------- /db/migrate/20151127161844_update_luarocks_url.rb: -------------------------------------------------------------------------------- 1 | class UpdateLuarocksUrl < ActiveRecord::Migration[4.2] 2 | def up 3 | repo = Repository.where(name: "LuaRocks (Lua)").first 4 | sampler = repo.sampler 5 | sampler.data_url = "https://luarocks.org/m/root" 6 | sampler.configuration_json = "{}" 7 | sampler.regex = '\\((\\d+)\\)' 8 | sampler.save! 9 | end 10 | 11 | def down 12 | repo = Repository.where(name: "LuaRocks (Lua)").first 13 | sampler = repo.sampler 14 | sampler.data_url = "https://rocks.moonscript.org/m/root" 15 | sampler.configuration_json = "{}" 16 | sampler.regex = '\\((\\d+)\\)' 17 | sampler.save! 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /db/migrate/20130330135629_move_cran.rb: -------------------------------------------------------------------------------- 1 | class MoveCran < ActiveRecord::Migration[4.2] 2 | def up 3 | cran = Repository.find_by_name('CRAN (R)') 4 | cran.url = 'http://cran.rproject.org' 5 | cran.save! 6 | 7 | sampler = cran.sampler 8 | 9 | sampler.data_url = 'http://cran.r-project.org/web/packages/' 10 | sampler.regex = "features\\s+([\\d,]+)\\s+available packages" 11 | sampler.save! 12 | end 13 | 14 | def down 15 | cran = Repository.find_by_name('CRAN (R)') 16 | cran.url = 'http://cran.opensourceresources.org' 17 | cran.save! 18 | 19 | sampler = cran.sampler 20 | 21 | sampler.data_url = "http://cran.opensourceresources.org/web/packages/" 22 | sampler.regex = "features\\s+([\\d,]+)\\s+available packages" 23 | sampler.save! 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 17 | 18 | 19 | 20 | 21 |

22 |

The change you wanted was rejected.

23 |

Maybe you tried to change something you didn't have access to.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The page you were looking for doesn't exist.

23 |

You may have mistyped the address or the page may have moved.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

We're sorry, but something went wrong.

23 |

We've been notified about this issue and we'll take a look at it shortly.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /db/migrate/20101209040416_add_repositories.rb: -------------------------------------------------------------------------------- 1 | class AddRepositories < ActiveRecord::Migration[4.2] 2 | def up 3 | Repository.create!(:name => "Rubygems.org", 4 | :url => "http://rubygems.org", 5 | :regex => "of ([\\d,]+) gems cut since") 6 | 7 | Repository.create!(:name => "CPAN", 8 | :url => "http://www.cpan.org", 9 | :regex => "authors ([\\d,]+) modules") 10 | 11 | Repository.create!(:name => "PyPI", 12 | :url => "http://pypi.python.org/pypi", 13 | :regex => "currently\\s*([\\d,]+)\\s*packages") 14 | 15 | end 16 | 17 | def down 18 | Repository.where(:name => "Rubygems.org").destroy 19 | Repository.where(:name => "CPAN").destroy 20 | Repository.where(:name => "PyPI").destroy 21 | 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/tasks/import.rake: -------------------------------------------------------------------------------- 1 | desc "This task will import a csv file" 2 | task :import_csv => :environment do 3 | require 'csv' 4 | repos = { "CPAN" => Repository.where(:name => "CPAN").first, 5 | "RubyGems.org" => Repository.where(:name => "Rubygems.org").first, 6 | "PyPI" => Repository.where(:name => "PyPI").first } 7 | 8 | CSV.foreach("modulecounts.csv", :headers => true, :skip_blanks => true) do |row| 9 | record_date = Time.parse("#{row['date']} 09:00 CSV") 10 | if record_date 11 | ["CPAN", "RubyGems.org", "PyPI"].each do |repo_name| 12 | if row[repo_name] 13 | repos[repo_name].counts.create(:value => row[repo_name], 14 | :record_date => record_date) 15 | puts "imported #{repo_name} #{record_date}, #{row[repo_name]}" 16 | end 17 | end 18 | end 19 | end 20 | 21 | end 22 | -------------------------------------------------------------------------------- /app/models/regex_sampler.rb: -------------------------------------------------------------------------------- 1 | class RegexSampler < Sampler 2 | 3 | def regex 4 | self.config(:regex) 5 | end 6 | 7 | def regex=(rx) 8 | self.set_config(:regex,rx) 9 | end 10 | 11 | def sample 12 | response = HTTParty.get(self.data_url) 13 | 14 | begin 15 | count = extract(response.body, self.regex) 16 | rescue ArgumentError 17 | count = extract(response.body.force_encoding("ISO-8859-1"), self.regex) 18 | end 19 | 20 | if self.config(:offset) 21 | count = count.to_i + self.config(:offset) 22 | end 23 | 24 | count 25 | end 26 | 27 | def extract(body, regexp) 28 | if md = Regexp.new(regexp, Regexp::MULTILINE).match(body) 29 | md[1].gsub(/&#\d+;/, "").gsub(/\D/, "").to_i 30 | else 31 | fail "RegexSampler didn't find count (sampler #{self.id}, #{self.repository.name})" 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /bin/update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | require 'fileutils' 4 | include FileUtils 5 | 6 | # path to your application root. 7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 8 | 9 | def system!(*args) 10 | system(*args) || abort("\n== Command #{args} failed ==") 11 | end 12 | 13 | chdir APP_ROOT do 14 | # This script is a way to update your development environment automatically. 15 | # Add necessary update steps to this file. 16 | 17 | puts '== Installing dependencies ==' 18 | system! 'gem install bundler --conservative' 19 | system 'bundle check' or system! 'bundle install' 20 | 21 | puts "\n== Updating database ==" 22 | system! 'bin/rake db:migrate' 23 | 24 | puts "\n== Removing old logs and tempfiles ==" 25 | system! 'bin/rake log:clear tmp:clear' 26 | 27 | puts "\n== Restarting application server ==" 28 | system! 'bin/rake restart' 29 | end 30 | -------------------------------------------------------------------------------- /app/models/csv_export.rb: -------------------------------------------------------------------------------- 1 | class CsvExport < ActiveRecord::Base 2 | def self.generate_csv 3 | @repositories = Repository.all.sort { |a,b| a.name.downcase <=> b.name.downcase } 4 | @latest = Count.order("record_date desc").limit(1).first 5 | 6 | csv = [] 7 | ((Count.earliest.record_date.to_date + 1.day)..Time.now.to_date).each do |date| 8 | counts = Count.where("date_trunc('day', counts.record_date) = date(?)", date).all 9 | row = @repositories.map { |r| counts.select { |c| c.repository_id == r.id }.first.try(:value) } 10 | 11 | # there might not be data for this date, but we'll include a row anyway 12 | csv.push row.unshift(date.strftime("%Y/%m/%d")).to_csv 13 | 14 | end 15 | 16 | csv.unshift @repositories.map { |r| r.name }.unshift("date").to_csv 17 | 18 | csv.join("") 19 | end 20 | 21 | def self.update 22 | export = CsvExport.first 23 | export.csv = CsvExport.generate_csv 24 | export.save 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Modulecounts 2 | ------------ 3 | 4 | Uses 'rake cron' to poll all known repos for data each night. 5 | 6 | May fail in development if you have ipv6 enabled - seems to cause 7 | problems connecting to some repositories. 8 | 9 | 10 | Dev Setup 11 | --------- 12 | 13 | Runs on Heroku cedar stack with MRI 2.3.2 14 | 15 | To run bundle install: 16 | 17 | PATH=$PATH:/Library/PostgreSQL/9.3/bin/ bundle 18 | 19 | 20 | A Note on Migrations 21 | -------------------- 22 | 23 | A couple notes after going through and fixing it so migrations work from an 24 | empty database again: 25 | 26 | - Always save! or equivalent so that exceptions will be raised. 27 | - Do not update stats or counts in the migrations. This breaks when 28 | the db structure changes later on - you'll end up trying to work on 29 | tables that haven't been created yet. 30 | - Plan on running rake cron to load data immediately after rake 31 | db:migrate since you can't do an update_counts from within 32 | migrations. 33 | -------------------------------------------------------------------------------- /test/controller/repository_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class RepositoryControllerTest < ActionController::TestCase 4 | test "gets list of repositories" do 5 | get :index 6 | 7 | assert_response :success 8 | 9 | repos = JSON.parse(response.body) 10 | 11 | assert_equal(2, repos.length, "wrong number of repos") 12 | end 13 | 14 | test "get all counts for a repository" do 15 | get :counts, params: { id: 1 }, format: "json" 16 | 17 | assert_response :success 18 | 19 | counts = JSON.parse(response.body) 20 | assert_equal(3, counts.length) 21 | end 22 | 23 | test "get 2 counts for a repository" do 24 | params = { 25 | id: 1, 26 | start: 2.days.ago.to_date.iso8601, 27 | finish: 1.days.ago.to_date.iso8601, 28 | } 29 | 30 | get :counts, params: params, format: "json" 31 | 32 | assert_response :success 33 | 34 | counts = JSON.parse(response.body) 35 | assert_equal(2, counts.length) 36 | assert_equal(2, counts.dig(0, "value")) 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /config/secrets.yml: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rake secret` to generate a secure secret key. 9 | 10 | # Make sure the secrets in this file are kept private 11 | # if you're sharing your code publicly. 12 | 13 | development: 14 | secret_key_base: 9605a5c7da2dc990728c874c0c9eb962c51ead4f26fab9bb9b1b0718c60b799ecfd20890f42404d0886170b0e17cc4c14958c5eadcc4f68cd9822c1072078f2b 15 | 16 | test: 17 | secret_key_base: b23618236babe3cb7ef3b493d3c3664f0ae79be9598f59958912dd73cc388547d574a88cdf327c7b27092ee79be507100cd28ae489338f6d04a8ce087f84d3a3 18 | 19 | # Do not keep production secrets in the repository, 20 | # instead read values from the environment. 21 | production: 22 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 23 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | require 'rails/all' 4 | 5 | # Require the gems listed in Gemfile, including any gems 6 | # you've limited to :test, :development, or :production. 7 | Bundler.require(*Rails.groups) 8 | 9 | module Modulecounts 10 | class Application < Rails::Application 11 | # Settings in config/environments/* take precedence over those specified here. 12 | # Application configuration should go into files in config/initializers 13 | # -- all .rb files in that directory are automatically loaded. 14 | 15 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 16 | # Run "rake time:zones:all" for a time zone names list. Default is UTC. 17 | # config.time_zone = 'Central Time (US & Canada)' 18 | 19 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 20 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 21 | # config.i18n.default_locale = :de 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | require 'fileutils' 4 | include FileUtils 5 | 6 | # path to your application root. 7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 8 | 9 | def system!(*args) 10 | system(*args) || abort("\n== Command #{args} failed ==") 11 | end 12 | 13 | chdir APP_ROOT do 14 | # This script is a starting point to setup your application. 15 | # Add necessary setup steps to this file. 16 | 17 | puts '== Installing dependencies ==' 18 | system! 'gem install bundler --conservative' 19 | system('bundle check') or system!('bundle install') 20 | 21 | # puts "\n== Copying sample files ==" 22 | # unless File.exist?('config/database.yml') 23 | # cp 'config/database.yml.sample', 'config/database.yml' 24 | # end 25 | 26 | puts "\n== Preparing database ==" 27 | system! 'ruby bin/rake db:setup' 28 | 29 | puts "\n== Removing old logs and tempfiles ==" 30 | system! 'ruby bin/rake log:clear tmp:clear' 31 | 32 | puts "\n== Restarting application server ==" 33 | system! 'ruby bin/rake restart' 34 | end 35 | -------------------------------------------------------------------------------- /app/models/sampler.rb: -------------------------------------------------------------------------------- 1 | class Sampler < ActiveRecord::Base 2 | validates_presence_of :repository_id, :type, :data_url 3 | 4 | belongs_to :repository 5 | 6 | before_save :serialize_configuration 7 | 8 | attr_accessor :configuration 9 | 10 | def sample 11 | raise "you need to subclass and implement this" 12 | end 13 | 14 | def config(key) 15 | ensure_configured 16 | 17 | @config[key.to_s] 18 | end 19 | 20 | def set_config(key, value) 21 | ensure_configured 22 | 23 | @config[key.to_s] = value 24 | end 25 | 26 | 27 | def offset 28 | self.config(:offset) 29 | end 30 | 31 | def offset=(k) 32 | self.set_config(:offset, k) 33 | end 34 | 35 | 36 | def ensure_configured 37 | @configured || parse_sampler_configuration 38 | end 39 | 40 | def parse_sampler_configuration 41 | if self.configuration_json 42 | @config = JSON.parse(self.configuration_json) 43 | else 44 | @config = {} 45 | end 46 | @configured = true 47 | end 48 | 49 | def serialize_configuration 50 | self.configuration_json = @config.to_json if @config 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /app/controllers/repository_controller.rb: -------------------------------------------------------------------------------- 1 | class RepositoryController < ApplicationController 2 | def index 3 | repos = Repository.where(hidden: false).order(:name).all 4 | json = repos.map do |repo| 5 | { 6 | id: repo.id, 7 | name: repo.name, 8 | url: repo.url, 9 | first_checked: repo.counts.order(record_date: :asc).first.record_date.to_date.iso8601, 10 | } 11 | end 12 | 13 | response.headers['Cache-Control'] = 'public, max-age=300' 14 | render json: json 15 | end 16 | 17 | def counts 18 | repo = Repository.find(params[:id]) 19 | 20 | counts = if params[:start] && params[:finish] 21 | repo.counts.where("record_date > ? and record_date < ?", 22 | Date.parse(params[:start]).beginning_of_day, 23 | Date.parse(params[:finish]).end_of_day 24 | ).order(:record_date).all 25 | else 26 | repo.counts.order(:record_date).all 27 | end 28 | 29 | json = counts.map do |count| 30 | { 31 | value: count.value, 32 | date: count.record_date.to_date.iso8601 33 | } 34 | end 35 | 36 | response.headers['Cache-Control'] = 'public, max-age=300' 37 | render json: json 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | # PostgreSQL. Versions 7.4 and 8.x are supported. 2 | # 3 | # Install the pg driver: 4 | # gem install pg 5 | # On Mac OS X with macports: 6 | # gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config 7 | # On Windows: 8 | # gem install pg 9 | # Choose the win32 build. 10 | # Install PostgreSQL and put its /bin directory on your path. 11 | development: 12 | adapter: postgresql 13 | encoding: unicode 14 | database: modulecounts_development 15 | pool: 5 16 | username: postgres 17 | password: sergtsop 18 | 19 | # Connect on a TCP socket. Omitted by default since the client uses a 20 | # domain socket that doesn't need configuration. Windows does not have 21 | # domain sockets, so uncomment these lines. 22 | host: localhost 23 | port: 5432 24 | 25 | # Schema search path. The server defaults to $user,public 26 | #schema_search_path: myapp,sharedapp,public 27 | 28 | # Minimum log levels, in increasing order: 29 | # debug5, debug4, debug3, debug2, debug1, 30 | # log, notice, warning, error, fatal, and panic 31 | # The server defaults to notice. 32 | #min_messages: warning 33 | 34 | # Warning: The database defined as "test" will be erased and 35 | # re-generated from your development database when you run "rake". 36 | # Do not set this db to the same as development or production. 37 | test: 38 | adapter: postgresql 39 | encoding: unicode 40 | database: modulecounts_test 41 | pool: 5 42 | username: postgres 43 | password: sergtsop 44 | host: localhost 45 | port: 5432 46 | 47 | production: 48 | adapter: postgresql 49 | encoding: unicode 50 | database: modulecounts_production 51 | pool: 5 52 | username: modulecounts 53 | password: 54 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure public file server for tests with Cache-Control for performance. 16 | config.public_file_server.enabled = true 17 | config.public_file_server.headers = { 18 | 'Cache-Control' => 'public, max-age=3600' 19 | } 20 | 21 | # Show full error reports and disable caching. 22 | config.consider_all_requests_local = true 23 | config.action_controller.perform_caching = false 24 | 25 | # Raise exceptions instead of rendering exception templates. 26 | config.action_dispatch.show_exceptions = false 27 | 28 | # Disable request forgery protection in test environment. 29 | config.action_controller.allow_forgery_protection = false 30 | 31 | # Tell Action Mailer not to deliver emails to the real world. 32 | # The :test delivery method accumulates sent emails in the 33 | # ActionMailer::Base.deliveries array. 34 | config.action_mailer.delivery_method = :test 35 | 36 | # Randomize the order test cases are executed. 37 | config.active_support.test_order = :random 38 | 39 | # Print deprecation notices to the stderr. 40 | config.active_support.deprecation = :stderr 41 | 42 | # Raises error for missing translations 43 | # config.action_view.raise_on_missing_translations = true 44 | end 45 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports. 13 | config.consider_all_requests_local = true 14 | 15 | # Enable/disable caching. By default caching is disabled. 16 | if Rails.root.join('tmp/caching-dev.txt').exist? 17 | config.action_controller.perform_caching = true 18 | config.cache_store = :memory_store 19 | config.public_file_server.headers = { 20 | 'Cache-Control' => 'public, max-age=172800' 21 | } 22 | else 23 | config.action_controller.perform_caching = false 24 | config.cache_store = :null_store 25 | end 26 | 27 | 28 | # Don't care if the mailer can't send. 29 | config.action_mailer.raise_delivery_errors = false 30 | 31 | # Print deprecation notices to the Rails logger. 32 | config.active_support.deprecation = :log 33 | 34 | # Raise an error on page load if there are pending migrations. 35 | config.active_record.migration_error = :page_load 36 | 37 | # Debug mode disables concatenation and preprocessing of assets. 38 | # This option may cause significant delays in view rendering with a large 39 | # number of complex assets. 40 | config.assets.debug = true 41 | 42 | # Asset digests allow you to set far-future HTTP expiration dates on all assets, 43 | # yet still be able to expire them through the digest params. 44 | config.assets.digest = true 45 | 46 | # Adds additional error checking when serving assets at runtime. 47 | # Checks for improperly declared sprockets dependencies. 48 | # Raises helpful error messages. 49 | config.assets.raise_runtime_errors = true 50 | 51 | # Raises error for missing translations 52 | # config.action_view.raise_on_missing_translations = true 53 | end 54 | -------------------------------------------------------------------------------- /app/models/repository.rb: -------------------------------------------------------------------------------- 1 | class Repository < ActiveRecord::Base 2 | validates_presence_of :name, :url 3 | 4 | has_many :counts, :dependent => :destroy 5 | has_one :repository_stats, :dependent => :destroy 6 | has_one :sampler, :dependent => :destroy 7 | 8 | def js_timeseries(start=nil, finish=nil) 9 | scope = counts 10 | if start 11 | scope = scope.where("record_date > ?", start) 12 | end 13 | 14 | if finish 15 | scope = scope.where("record_date < ?", finish) 16 | end 17 | 18 | scope.order(:record_date).collect { |c| [c.record_date.to_i * 1000, c.value] } 19 | end 20 | 21 | def last_week 22 | day_sequence(Time.now.utc - 6.days, 7) 23 | end 24 | 25 | def day_sequence(start, days) 26 | start_date = start.to_date 27 | records = self.counts.where("record_date > ? and record_date < ?", start_date, start_date + days.days).order(:record_date) 28 | sequence = [] 29 | (0 ..days - 1 ).collect do |i| 30 | day = (start_date + i.days).day 31 | last = nil 32 | records.each do |r| 33 | if r.record_date.day == day 34 | last = r 35 | end 36 | end 37 | 38 | sequence << last 39 | end 40 | 41 | sequence 42 | end 43 | 44 | def fetch_count 45 | self.sampler.sample 46 | end 47 | 48 | def update_count 49 | self.counts.create(:value => self.fetch_count, 50 | :record_date => Time.now) 51 | end 52 | 53 | def latest_count 54 | self.counts.order("record_date desc").first 55 | end 56 | 57 | 58 | def update_stats 59 | last_week = self.counts.where("record_date > ?", Time.now - 7.days).order(:record_date) 60 | 61 | if last_week.length > 0 62 | 63 | stats = self.repository_stats || RepositoryStats.new(:repository_id => self.id, :modules_day => 0, :total => 0) 64 | 65 | stats.total = last_week.last.value 66 | 67 | if last_week.length > 1 68 | stats.modules_day = ((last_week.last.value - last_week.first.value) / ((last_week.last.record_date - last_week.first.record_date) / 1.day )).round 69 | end 70 | 71 | stats.save 72 | end 73 | end 74 | 75 | def count_for_date(date) 76 | self.counts.where("date_trunc('day', counts.record_date) = date(?)", date).first 77 | end 78 | 79 | end 80 | -------------------------------------------------------------------------------- /db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # Note that this schema.rb definition is the authoritative source for your 6 | # database schema. If you need to create the application database on another 7 | # system, you should be using db:schema:load, not running all the migrations 8 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 9 | # you'll amass, the slower it'll run and the greater likelihood for issues). 10 | # 11 | # It's strongly recommended that you check this file into your version control system. 12 | 13 | ActiveRecord::Schema.define(version: 2020_11_19_115702) do 14 | 15 | # These are extensions that must be enabled in order to support this database 16 | enable_extension "plpgsql" 17 | 18 | create_table "counts", id: :serial, force: :cascade do |t| 19 | t.integer "repository_id" 20 | t.integer "value" 21 | t.datetime "record_date" 22 | t.datetime "created_at" 23 | t.datetime "updated_at" 24 | end 25 | 26 | create_table "csv_exports", id: :serial, force: :cascade do |t| 27 | t.text "csv" 28 | t.datetime "created_at" 29 | t.datetime "updated_at" 30 | end 31 | 32 | create_table "repositories", id: :serial, force: :cascade do |t| 33 | t.string "name" 34 | t.string "url" 35 | t.string "regex" 36 | t.datetime "last_checked" 37 | t.boolean "graph" 38 | t.datetime "created_at" 39 | t.datetime "updated_at" 40 | t.boolean "hidden", default: false 41 | end 42 | 43 | create_table "repository_stats", id: :serial, force: :cascade do |t| 44 | t.integer "repository_id" 45 | t.integer "total" 46 | t.datetime "last_updated" 47 | t.integer "modules_day" 48 | t.integer "days_to_crossover" 49 | t.datetime "created_at" 50 | t.datetime "updated_at" 51 | t.index ["repository_id"], name: "index_repository_stats_on_repository_id", unique: true 52 | end 53 | 54 | create_table "samplers", id: :serial, force: :cascade do |t| 55 | t.integer "repository_id" 56 | t.string "type" 57 | t.string "data_url" 58 | t.text "configuration_json" 59 | t.datetime "created_at" 60 | t.datetime "updated_at" 61 | end 62 | 63 | end 64 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Modulecounts 5 | <%= stylesheet_link_tag "/stylesheets/smoothness/jquery-ui-1.8.7.custom.css" %> 6 | <%= javascript_include_tag "/javascripts/jquery-1.4.4.min.js" %> 7 | <%= javascript_include_tag "/javascripts/jquery-ui-1.8.7.custom.min.js" %> 8 | <%= javascript_include_tag "/javascripts/excanvas.min.js" %> 9 | <%= javascript_include_tag "/javascripts/application.js" %> 10 | <%= javascript_include_tag "/javascripts/jquery.flot.js" %> 11 | <%= csrf_meta_tag %> 12 | 34 | 74 | 75 | 76 |
77 |
78 | 79 | <%= yield %> 80 | 81 |
82 |
104 |
105 | 106 | 107 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # Code is not reloaded between requests. 5 | config.cache_classes = true 6 | 7 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both threaded web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Disable serving static files from the `/public` folder by default since 18 | # Apache or NGINX already handles this. 19 | config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? 20 | 21 | # Compress JavaScripts and CSS. 22 | config.assets.js_compressor = :uglifier 23 | # config.assets.css_compressor = :sass 24 | 25 | # Do not fallback to assets pipeline if a precompiled asset is missed. 26 | config.assets.compile = false 27 | 28 | # Asset digests allow you to set far-future HTTP expiration dates on all assets, 29 | # yet still be able to expire them through the digest params. 30 | config.assets.digest = true 31 | 32 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb 33 | 34 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 35 | # config.action_controller.asset_host = 'http://assets.example.com' 36 | 37 | # Specifies the header that your server uses for sending files. 38 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache 39 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX 40 | 41 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 42 | # config.force_ssl = true 43 | 44 | # Use the lowest log level to ensure availability of diagnostic information 45 | # when problems arise. 46 | config.log_level = :debug 47 | 48 | # Prepend all log lines with the following tags. 49 | # config.log_tags = [ :subdomain, :request_id ] 50 | 51 | # Use a different logger for distributed setups. 52 | # require 'syslog/logger' 53 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') 54 | 55 | # Use a different cache store in production. 56 | # config.cache_store = :mem_cache_store 57 | 58 | # Use a real queuing backend for Active Job (and separate queues per environment) 59 | # config.active_job.queue_adapter = :resque 60 | # config.active_job.queue_name_prefix = "modulecounts_#{Rails.env}" 61 | 62 | # Ignore bad email addresses and do not raise email delivery errors. 63 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 64 | # config.action_mailer.raise_delivery_errors = false 65 | 66 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 67 | # the I18n.default_locale when a translation cannot be found). 68 | config.i18n.fallbacks = true 69 | 70 | # Send deprecation notices to registered listeners. 71 | config.active_support.deprecation = :notify 72 | 73 | # Use default logging formatter so that PID and timestamp are not suppressed. 74 | config.log_formatter = ::Logger::Formatter.new 75 | 76 | # Do not dump schema after migrations. 77 | config.active_record.dump_schema_after_migration = false 78 | end 79 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: http://rubygems.org/ 3 | specs: 4 | actioncable (5.2.3) 5 | actionpack (= 5.2.3) 6 | nio4r (~> 2.0) 7 | websocket-driver (>= 0.6.1) 8 | actionmailer (5.2.3) 9 | actionpack (= 5.2.3) 10 | actionview (= 5.2.3) 11 | activejob (= 5.2.3) 12 | mail (~> 2.5, >= 2.5.4) 13 | rails-dom-testing (~> 2.0) 14 | actionpack (5.2.3) 15 | actionview (= 5.2.3) 16 | activesupport (= 5.2.3) 17 | rack (~> 2.0) 18 | rack-test (>= 0.6.3) 19 | rails-dom-testing (~> 2.0) 20 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 21 | actionview (5.2.3) 22 | activesupport (= 5.2.3) 23 | builder (~> 3.1) 24 | erubi (~> 1.4) 25 | rails-dom-testing (~> 2.0) 26 | rails-html-sanitizer (~> 1.0, >= 1.0.3) 27 | activejob (5.2.3) 28 | activesupport (= 5.2.3) 29 | globalid (>= 0.3.6) 30 | activemodel (5.2.3) 31 | activesupport (= 5.2.3) 32 | activerecord (5.2.3) 33 | activemodel (= 5.2.3) 34 | activesupport (= 5.2.3) 35 | arel (>= 9.0) 36 | activestorage (5.2.3) 37 | actionpack (= 5.2.3) 38 | activerecord (= 5.2.3) 39 | marcel (~> 0.3.1) 40 | activesupport (5.2.3) 41 | concurrent-ruby (~> 1.0, >= 1.0.2) 42 | i18n (>= 0.7, < 2) 43 | minitest (~> 5.1) 44 | tzinfo (~> 1.1) 45 | arel (9.0.0) 46 | builder (3.2.3) 47 | coderay (1.1.2) 48 | concurrent-ruby (1.1.5) 49 | crass (1.0.4) 50 | erubi (1.8.0) 51 | gerbilcharts (0.10.16) 52 | globalid (0.4.2) 53 | activesupport (>= 4.2.0) 54 | httparty (0.17.0) 55 | mime-types (~> 3.0) 56 | multi_xml (>= 0.5.2) 57 | httpclient (2.8.3) 58 | i18n (1.6.0) 59 | concurrent-ruby (~> 1.0) 60 | jsonpath (1.0.4) 61 | multi_json 62 | to_regexp (~> 0.2.1) 63 | loofah (2.2.3) 64 | crass (~> 1.0.2) 65 | nokogiri (>= 1.5.9) 66 | mail (2.7.1) 67 | mini_mime (>= 0.1.1) 68 | marcel (0.3.3) 69 | mimemagic (~> 0.3.2) 70 | method_source (0.9.2) 71 | mime-types (3.2.2) 72 | mime-types-data (~> 3.2015) 73 | mime-types-data (3.2019.0331) 74 | mimemagic (0.3.3) 75 | mini_mime (1.0.1) 76 | mini_portile2 (2.3.0) 77 | minitest (5.11.3) 78 | multi_json (1.13.1) 79 | multi_xml (0.6.0) 80 | nio4r (2.3.1) 81 | nokogiri (1.8.5) 82 | mini_portile2 (~> 2.3.0) 83 | parallel (1.17.0) 84 | pg (1.1.4) 85 | pry (0.12.2) 86 | coderay (~> 1.1.0) 87 | method_source (~> 0.9.0) 88 | puma (3.12.1) 89 | rack (2.0.7) 90 | rack-test (1.1.0) 91 | rack (>= 1.0, < 3) 92 | rails (5.2.3) 93 | actioncable (= 5.2.3) 94 | actionmailer (= 5.2.3) 95 | actionpack (= 5.2.3) 96 | actionview (= 5.2.3) 97 | activejob (= 5.2.3) 98 | activemodel (= 5.2.3) 99 | activerecord (= 5.2.3) 100 | activestorage (= 5.2.3) 101 | activesupport (= 5.2.3) 102 | bundler (>= 1.3.0) 103 | railties (= 5.2.3) 104 | sprockets-rails (>= 2.0.0) 105 | rails-dom-testing (2.0.3) 106 | activesupport (>= 4.2.0) 107 | nokogiri (>= 1.6) 108 | rails-html-sanitizer (1.0.4) 109 | loofah (~> 2.2, >= 2.2.2) 110 | rails_12factor (0.0.3) 111 | rails_serve_static_assets 112 | rails_stdout_logging 113 | rails_serve_static_assets (0.0.5) 114 | rails_stdout_logging (0.0.5) 115 | railties (5.2.3) 116 | actionpack (= 5.2.3) 117 | activesupport (= 5.2.3) 118 | method_source 119 | rake (>= 0.8.7) 120 | thor (>= 0.19.0, < 2.0) 121 | rake (12.3.2) 122 | sprockets (3.7.2) 123 | concurrent-ruby (~> 1.0) 124 | rack (> 1, < 3) 125 | sprockets-rails (3.2.1) 126 | actionpack (>= 4.0) 127 | activesupport (>= 4.0) 128 | sprockets (>= 3.0.0) 129 | thor (0.20.3) 130 | thread_safe (0.3.6) 131 | to_regexp (0.2.1) 132 | tzinfo (1.2.5) 133 | thread_safe (~> 0.1) 134 | websocket-driver (0.7.1) 135 | websocket-extensions (>= 0.1.0) 136 | websocket-extensions (0.1.4) 137 | 138 | PLATFORMS 139 | ruby 140 | 141 | DEPENDENCIES 142 | gerbilcharts 143 | httparty 144 | httpclient 145 | jsonpath 146 | nokogiri (~> 1.8.1) 147 | parallel 148 | pg (~> 1.1.4) 149 | pry 150 | puma 151 | rails (= 5.2.3) 152 | rails_12factor 153 | rake (~> 12.3.2) 154 | 155 | RUBY VERSION 156 | ruby 2.4.1p111 157 | 158 | BUNDLED WITH 159 | 1.17.3 160 | -------------------------------------------------------------------------------- /app/views/index/index.html.erb: -------------------------------------------------------------------------------- 1 | 76 | 144 |

Module Counts

145 |
146 |
147 |
148 |
149 |
150 |
    151 |
  • Include
  • 152 | 153 | <% @repositories.each do |r| %> 154 |
  • checked<% end %>> <%= r.name %>
  • 156 | <% end %> 157 |
158 |
159 |
160 |
    161 |
  • time period
  • 162 | <% if Count.earliest.record_date < Time.now - 1.year %> 163 | <% all_time_checked = '' %> 164 | <% last_year_checked = 'checked' %> 165 | <% else %> 166 | <% all_time_checked = 'checked' %> 167 | <% last_year_checked = '' %> 168 | <% end %> 169 |
  • > all time
  • 170 |
  • > last year
  • 171 | <% [90, 30, 7].each do |days| %> 172 |
  • last <%= days %> days
  • 173 | <% end %> 174 |
175 |
176 |
177 |
178 |
179 | 180 | 181 | 182 | <% days_to_show = 7 %> 183 | <% (0..(days_to_show - 1)).each do |i| %> 184 | <% index = days_to_show - i - 1 %> 185 | 186 | <% end %> 187 | 188 | 189 | <% @repositories.each do |r| %> 190 | 191 | 192 | <% recent = r.day_sequence(@latest.record_date - 6.days, 7) %> 193 | <% (0..(days_to_show - 1)).each do |i| %> 194 | <% index = - days_to_show + i %> 195 | 196 | <% end %> 197 | 198 | 199 | <% end %> 200 |
<%= (@latest.record_date - (index).days).strftime "%b %e" %>Avg Growth
<%= r.name %><%= recent[index].try(:value) %><%= r.repository_stats.try(:modules_day).to_i %>/day
201 | 202 |

Data is collected by scraping the relevant websites once a day via 203 | a cron job and then stored in a Postgresql database for later 204 | retrieval. Growth rates are calculated by averaging data over the last 205 | week. I'm gathering counts of separate modules, so multiple versions 206 | of the same module/package/gem only count once (foo-1.2, foo-1.3 and 207 | bar-1.0 would count as 2 total). 208 |

209 |

(Jun 23, 2019) Update Crates.io, Julia, and LuaRocks after site changes. Also upgrade LOTS of ruby gems and get off a deprecated Heroku stack. 210 |

211 |

(Jun 3, 2018) Update Drupal, DUB, and PyPI after their sites updated. 212 |

213 |

(Mar 4, 2018) Added Crystal Shards, as well as JSON endpoints 214 | /repositories and /repositories/:id/counts/:start/:finish 215 | as first steps towards revamping JS front end. (e.g. /repositories/1/counts/2018-02-01/2018-02-28) 216 |

(Sept 10, 2017) Update Crates.io for their new data URL and add 217 | Nimble, the Nim package manager. Many thanks to Alex Libman for pointing 218 | me to it. 219 |

220 |

I'd like to add more repositories. If you have suggestions, please send them 221 | to erik@debill.org. 222 |

223 |

224 | CPAN and CPAN (search) used to be two conflicting sources for data 225 | about how many modules are in CPAN. During spring of 2011 CPAN got a 226 | site refresh and the numbers came into line with each other. It looks 227 | funny on the graph, but it's an interesting bit of history. 228 |

229 |

230 | GoDoc is weird. It's not a true package repository in the same sense 231 | as all the others, but as far as I can tell it's the closest GoLang 232 | has. Be aware that it pretty drastically overcounts the number of 233 | packages. Don't use the raw numbers to compare with other 234 | languages. You can still watch the line to see changes in velocity, 235 | though. 236 |

237 |

238 | If you'd like to check out the data in more detail, you are welcome to download it in a CSV file. 239 |

240 | 241 | -------------------------------------------------------------------------------- /public/javascripts/excanvas.min.js: -------------------------------------------------------------------------------- 1 | if(!document.createElement("canvas").getContext){(function(){var z=Math;var K=z.round;var J=z.sin;var U=z.cos;var b=z.abs;var k=z.sqrt;var D=10;var F=D/2;function T(){return this.context_||(this.context_=new W(this))}var O=Array.prototype.slice;function G(i,j,m){var Z=O.call(arguments,2);return function(){return i.apply(j,Z.concat(O.call(arguments)))}}function AD(Z){return String(Z).replace(/&/g,"&").replace(/"/g,""")}function r(i){if(!i.namespaces.g_vml_){i.namespaces.add("g_vml_","urn:schemas-microsoft-com:vml","#default#VML")}if(!i.namespaces.g_o_){i.namespaces.add("g_o_","urn:schemas-microsoft-com:office:office","#default#VML")}if(!i.styleSheets.ex_canvas_){var Z=i.createStyleSheet();Z.owningElement.id="ex_canvas_";Z.cssText="canvas{display:inline-block;overflow:hidden;text-align:left;width:300px;height:150px}"}}r(document);var E={init:function(Z){if(/MSIE/.test(navigator.userAgent)&&!window.opera){var i=Z||document;i.createElement("canvas");i.attachEvent("onreadystatechange",G(this.init_,this,i))}},init_:function(m){var j=m.getElementsByTagName("canvas");for(var Z=0;Z1){j--}if(6*j<1){return i+(Z-i)*6*j}else{if(2*j<1){return Z}else{if(3*j<2){return i+(Z-i)*(2/3-j)*6}else{return i}}}}function Y(Z){var AE,p=1;Z=String(Z);if(Z.charAt(0)=="#"){AE=Z}else{if(/^rgb/.test(Z)){var m=g(Z);var AE="#",AF;for(var j=0;j<3;j++){if(m[j].indexOf("%")!=-1){AF=Math.floor(C(m[j])*255)}else{AF=Number(m[j])}AE+=I[N(AF,0,255)]}p=m[3]}else{if(/^hsl/.test(Z)){var m=g(Z);AE=c(m);p=m[3]}else{AE=B[Z]||Z}}}return{color:AE,alpha:p}}var L={style:"normal",variant:"normal",weight:"normal",size:10,family:"sans-serif"};var f={};function X(Z){if(f[Z]){return f[Z]}var m=document.createElement("div");var j=m.style;try{j.font=Z}catch(i){}return f[Z]={style:j.fontStyle||L.style,variant:j.fontVariant||L.variant,weight:j.fontWeight||L.weight,size:j.fontSize||L.size,family:j.fontFamily||L.family}}function P(j,i){var Z={};for(var AF in j){Z[AF]=j[AF]}var AE=parseFloat(i.currentStyle.fontSize),m=parseFloat(j.size);if(typeof j.size=="number"){Z.size=j.size}else{if(j.size.indexOf("px")!=-1){Z.size=m}else{if(j.size.indexOf("em")!=-1){Z.size=AE*m}else{if(j.size.indexOf("%")!=-1){Z.size=(AE/100)*m}else{if(j.size.indexOf("pt")!=-1){Z.size=m/0.75}else{Z.size=AE}}}}}Z.size*=0.981;return Z}function AA(Z){return Z.style+" "+Z.variant+" "+Z.weight+" "+Z.size+"px "+Z.family}function t(Z){switch(Z){case"butt":return"flat";case"round":return"round";case"square":default:return"square"}}function W(i){this.m_=V();this.mStack_=[];this.aStack_=[];this.currentPath_=[];this.strokeStyle="#000";this.fillStyle="#000";this.lineWidth=1;this.lineJoin="miter";this.lineCap="butt";this.miterLimit=D*1;this.globalAlpha=1;this.font="10px sans-serif";this.textAlign="left";this.textBaseline="alphabetic";this.canvas=i;var Z=i.ownerDocument.createElement("div");Z.style.width=i.clientWidth+"px";Z.style.height=i.clientHeight+"px";Z.style.overflow="hidden";Z.style.position="absolute";i.appendChild(Z);this.element_=Z;this.arcScaleX_=1;this.arcScaleY_=1;this.lineScale_=1}var M=W.prototype;M.clearRect=function(){if(this.textMeasureEl_){this.textMeasureEl_.removeNode(true);this.textMeasureEl_=null}this.element_.innerHTML=""};M.beginPath=function(){this.currentPath_=[]};M.moveTo=function(i,Z){var j=this.getCoords_(i,Z);this.currentPath_.push({type:"moveTo",x:j.x,y:j.y});this.currentX_=j.x;this.currentY_=j.y};M.lineTo=function(i,Z){var j=this.getCoords_(i,Z);this.currentPath_.push({type:"lineTo",x:j.x,y:j.y});this.currentX_=j.x;this.currentY_=j.y};M.bezierCurveTo=function(j,i,AI,AH,AG,AE){var Z=this.getCoords_(AG,AE);var AF=this.getCoords_(j,i);var m=this.getCoords_(AI,AH);e(this,AF,m,Z)};function e(Z,m,j,i){Z.currentPath_.push({type:"bezierCurveTo",cp1x:m.x,cp1y:m.y,cp2x:j.x,cp2y:j.y,x:i.x,y:i.y});Z.currentX_=i.x;Z.currentY_=i.y}M.quadraticCurveTo=function(AG,j,i,Z){var AF=this.getCoords_(AG,j);var AE=this.getCoords_(i,Z);var AH={x:this.currentX_+2/3*(AF.x-this.currentX_),y:this.currentY_+2/3*(AF.y-this.currentY_)};var m={x:AH.x+(AE.x-this.currentX_)/3,y:AH.y+(AE.y-this.currentY_)/3};e(this,AH,m,AE)};M.arc=function(AJ,AH,AI,AE,i,j){AI*=D;var AN=j?"at":"wa";var AK=AJ+U(AE)*AI-F;var AM=AH+J(AE)*AI-F;var Z=AJ+U(i)*AI-F;var AL=AH+J(i)*AI-F;if(AK==Z&&!j){AK+=0.125}var m=this.getCoords_(AJ,AH);var AG=this.getCoords_(AK,AM);var AF=this.getCoords_(Z,AL);this.currentPath_.push({type:AN,x:m.x,y:m.y,radius:AI,xStart:AG.x,yStart:AG.y,xEnd:AF.x,yEnd:AF.y})};M.rect=function(j,i,Z,m){this.moveTo(j,i);this.lineTo(j+Z,i);this.lineTo(j+Z,i+m);this.lineTo(j,i+m);this.closePath()};M.strokeRect=function(j,i,Z,m){var p=this.currentPath_;this.beginPath();this.moveTo(j,i);this.lineTo(j+Z,i);this.lineTo(j+Z,i+m);this.lineTo(j,i+m);this.closePath();this.stroke();this.currentPath_=p};M.fillRect=function(j,i,Z,m){var p=this.currentPath_;this.beginPath();this.moveTo(j,i);this.lineTo(j+Z,i);this.lineTo(j+Z,i+m);this.lineTo(j,i+m);this.closePath();this.fill();this.currentPath_=p};M.createLinearGradient=function(i,m,Z,j){var p=new v("gradient");p.x0_=i;p.y0_=m;p.x1_=Z;p.y1_=j;return p};M.createRadialGradient=function(m,AE,j,i,p,Z){var AF=new v("gradientradial");AF.x0_=m;AF.y0_=AE;AF.r0_=j;AF.x1_=i;AF.y1_=p;AF.r1_=Z;return AF};M.drawImage=function(AO,j){var AH,AF,AJ,AV,AM,AK,AQ,AX;var AI=AO.runtimeStyle.width;var AN=AO.runtimeStyle.height;AO.runtimeStyle.width="auto";AO.runtimeStyle.height="auto";var AG=AO.width;var AT=AO.height;AO.runtimeStyle.width=AI;AO.runtimeStyle.height=AN;if(arguments.length==3){AH=arguments[1];AF=arguments[2];AM=AK=0;AQ=AJ=AG;AX=AV=AT}else{if(arguments.length==5){AH=arguments[1];AF=arguments[2];AJ=arguments[3];AV=arguments[4];AM=AK=0;AQ=AG;AX=AT}else{if(arguments.length==9){AM=arguments[1];AK=arguments[2];AQ=arguments[3];AX=arguments[4];AH=arguments[5];AF=arguments[6];AJ=arguments[7];AV=arguments[8]}else{throw Error("Invalid number of arguments")}}}var AW=this.getCoords_(AH,AF);var m=AQ/2;var i=AX/2;var AU=[];var Z=10;var AE=10;AU.push(" ','","");this.element_.insertAdjacentHTML("BeforeEnd",AU.join(""))};M.stroke=function(AM){var m=10;var AN=10;var AE=5000;var AG={x:null,y:null};var AL={x:null,y:null};for(var AH=0;AHAL.x){AL.x=Z.x}if(AG.y==null||Z.yAL.y){AL.y=Z.y}}}AK.push(' ">');if(!AM){R(this,AK)}else{a(this,AK,AG,AL)}AK.push("");this.element_.insertAdjacentHTML("beforeEnd",AK.join(""))}};function R(j,AE){var i=Y(j.strokeStyle);var m=i.color;var p=i.alpha*j.globalAlpha;var Z=j.lineScale_*j.lineWidth;if(Z<1){p*=Z}AE.push("')}function a(AO,AG,Ah,AP){var AH=AO.fillStyle;var AY=AO.arcScaleX_;var AX=AO.arcScaleY_;var Z=AP.x-Ah.x;var m=AP.y-Ah.y;if(AH instanceof v){var AL=0;var Ac={x:0,y:0};var AU=0;var AK=1;if(AH.type_=="gradient"){var AJ=AH.x0_/AY;var j=AH.y0_/AX;var AI=AH.x1_/AY;var Aj=AH.y1_/AX;var Ag=AO.getCoords_(AJ,j);var Af=AO.getCoords_(AI,Aj);var AE=Af.x-Ag.x;var p=Af.y-Ag.y;AL=Math.atan2(AE,p)*180/Math.PI;if(AL<0){AL+=360}if(AL<0.000001){AL=0}}else{var Ag=AO.getCoords_(AH.x0_,AH.y0_);Ac={x:(Ag.x-Ah.x)/Z,y:(Ag.y-Ah.y)/m};Z/=AY*D;m/=AX*D;var Aa=z.max(Z,m);AU=2*AH.r0_/Aa;AK=2*AH.r1_/Aa-AU}var AS=AH.colors_;AS.sort(function(Ak,i){return Ak.offset-i.offset});var AN=AS.length;var AR=AS[0].color;var AQ=AS[AN-1].color;var AW=AS[0].alpha*AO.globalAlpha;var AV=AS[AN-1].alpha*AO.globalAlpha;var Ab=[];for(var Ae=0;Ae')}else{if(AH instanceof u){if(Z&&m){var AF=-Ah.x;var AZ=-Ah.y;AG.push("')}}else{var Ai=Y(AO.fillStyle);var AT=Ai.color;var Ad=Ai.alpha*AO.globalAlpha;AG.push('')}}}M.fill=function(){this.stroke(true)};M.closePath=function(){this.currentPath_.push({type:"close"})};M.getCoords_=function(j,i){var Z=this.m_;return{x:D*(j*Z[0][0]+i*Z[1][0]+Z[2][0])-F,y:D*(j*Z[0][1]+i*Z[1][1]+Z[2][1])-F}};M.save=function(){var Z={};Q(this,Z);this.aStack_.push(Z);this.mStack_.push(this.m_);this.m_=d(V(),this.m_)};M.restore=function(){if(this.aStack_.length){Q(this.aStack_.pop(),this);this.m_=this.mStack_.pop()}};function H(Z){return isFinite(Z[0][0])&&isFinite(Z[0][1])&&isFinite(Z[1][0])&&isFinite(Z[1][1])&&isFinite(Z[2][0])&&isFinite(Z[2][1])}function y(i,Z,j){if(!H(Z)){return }i.m_=Z;if(j){var p=Z[0][0]*Z[1][1]-Z[0][1]*Z[1][0];i.lineScale_=k(b(p))}}M.translate=function(j,i){var Z=[[1,0,0],[0,1,0],[j,i,1]];y(this,d(Z,this.m_),false)};M.rotate=function(i){var m=U(i);var j=J(i);var Z=[[m,j,0],[-j,m,0],[0,0,1]];y(this,d(Z,this.m_),false)};M.scale=function(j,i){this.arcScaleX_*=j;this.arcScaleY_*=i;var Z=[[j,0,0],[0,i,0],[0,0,1]];y(this,d(Z,this.m_),true)};M.transform=function(p,m,AF,AE,i,Z){var j=[[p,m,0],[AF,AE,0],[i,Z,1]];y(this,d(j,this.m_),true)};M.setTransform=function(AE,p,AG,AF,j,i){var Z=[[AE,p,0],[AG,AF,0],[j,i,1]];y(this,Z,true)};M.drawText_=function(AK,AI,AH,AN,AG){var AM=this.m_,AQ=1000,i=0,AP=AQ,AF={x:0,y:0},AE=[];var Z=P(X(this.font),this.element_);var j=AA(Z);var AR=this.element_.currentStyle;var p=this.textAlign.toLowerCase();switch(p){case"left":case"center":case"right":break;case"end":p=AR.direction=="ltr"?"right":"left";break;case"start":p=AR.direction=="rtl"?"right":"left";break;default:p="left"}switch(this.textBaseline){case"hanging":case"top":AF.y=Z.size/1.75;break;case"middle":break;default:case null:case"alphabetic":case"ideographic":case"bottom":AF.y=-Z.size/2.25;break}switch(p){case"right":i=AQ;AP=0.05;break;case"center":i=AP=AQ/2;break}var AO=this.getCoords_(AI+AF.x,AH+AF.y);AE.push('');if(AG){R(this,AE)}else{a(this,AE,{x:-i,y:0},{x:AP,y:Z.size})}var AL=AM[0][0].toFixed(3)+","+AM[1][0].toFixed(3)+","+AM[0][1].toFixed(3)+","+AM[1][1].toFixed(3)+",0,0";var AJ=K(AO.x/D)+","+K(AO.y/D);AE.push('','','');this.element_.insertAdjacentHTML("beforeEnd",AE.join(""))};M.fillText=function(j,Z,m,i){this.drawText_(j,Z,m,i,false)};M.strokeText=function(j,Z,m,i){this.drawText_(j,Z,m,i,true)};M.measureText=function(j){if(!this.textMeasureEl_){var Z='';this.element_.insertAdjacentHTML("beforeEnd",Z);this.textMeasureEl_=this.element_.lastChild}var i=this.element_.ownerDocument;this.textMeasureEl_.innerHTML="";this.textMeasureEl_.style.font=this.font;this.textMeasureEl_.appendChild(i.createTextNode(j));return{width:this.textMeasureEl_.offsetWidth}};M.clip=function(){};M.arcTo=function(){};M.createPattern=function(i,Z){return new u(i,Z)};function v(Z){this.type_=Z;this.x0_=0;this.y0_=0;this.r0_=0;this.x1_=0;this.y1_=0;this.r1_=0;this.colors_=[]}v.prototype.addColorStop=function(i,Z){Z=Y(Z);this.colors_.push({offset:i,color:Z.color,alpha:Z.alpha})};function u(i,Z){q(i);switch(Z){case"repeat":case null:case"":this.repetition_="repeat";break;case"repeat-x":case"repeat-y":case"no-repeat":this.repetition_=Z;break;default:n("SYNTAX_ERR")}this.src_=i.src;this.width_=i.width;this.height_=i.height}function n(Z){throw new o(Z)}function q(Z){if(!Z||Z.nodeType!=1||Z.tagName!="IMG"){n("TYPE_MISMATCH_ERR")}if(Z.readyState!="complete"){n("INVALID_STATE_ERR")}}function o(Z){this.code=this[Z];this.message=Z+": DOM Exception "+this.code}var x=o.prototype=new Error;x.INDEX_SIZE_ERR=1;x.DOMSTRING_SIZE_ERR=2;x.HIERARCHY_REQUEST_ERR=3;x.WRONG_DOCUMENT_ERR=4;x.INVALID_CHARACTER_ERR=5;x.NO_DATA_ALLOWED_ERR=6;x.NO_MODIFICATION_ALLOWED_ERR=7;x.NOT_FOUND_ERR=8;x.NOT_SUPPORTED_ERR=9;x.INUSE_ATTRIBUTE_ERR=10;x.INVALID_STATE_ERR=11;x.SYNTAX_ERR=12;x.INVALID_MODIFICATION_ERR=13;x.NAMESPACE_ERR=14;x.INVALID_ACCESS_ERR=15;x.VALIDATION_ERR=16;x.TYPE_MISMATCH_ERR=17;G_vmlCanvasManager=E;CanvasRenderingContext2D=W;CanvasGradient=v;CanvasPattern=u;DOMException=o})()}; -------------------------------------------------------------------------------- /public/stylesheets/smoothness/jquery-ui-1.8.7.custom.css: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery UI CSS Framework 1.8.7 3 | * 4 | * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) 5 | * Dual licensed under the MIT or GPL Version 2 licenses. 6 | * http://jquery.org/license 7 | * 8 | * http://docs.jquery.com/UI/Theming/API 9 | */ 10 | 11 | /* Layout helpers 12 | ----------------------------------*/ 13 | .ui-helper-hidden { display: none; } 14 | .ui-helper-hidden-accessible { position: absolute !important; clip: rect(1px 1px 1px 1px); clip: rect(1px,1px,1px,1px); } 15 | .ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; } 16 | .ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } 17 | .ui-helper-clearfix { display: inline-block; } 18 | /* required comment for clearfix to work in Opera \*/ 19 | * html .ui-helper-clearfix { height:1%; } 20 | .ui-helper-clearfix { display:block; } 21 | /* end clearfix */ 22 | .ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); } 23 | 24 | 25 | /* Interaction Cues 26 | ----------------------------------*/ 27 | .ui-state-disabled { cursor: default !important; } 28 | 29 | 30 | /* Icons 31 | ----------------------------------*/ 32 | 33 | /* states and images */ 34 | .ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; } 35 | 36 | 37 | /* Misc visuals 38 | ----------------------------------*/ 39 | 40 | /* Overlays */ 41 | .ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } 42 | 43 | 44 | /* 45 | * jQuery UI CSS Framework 1.8.7 46 | * 47 | * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) 48 | * Dual licensed under the MIT or GPL Version 2 licenses. 49 | * http://jquery.org/license 50 | * 51 | * http://docs.jquery.com/UI/Theming/API 52 | * 53 | * To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana,Arial,sans-serif&fwDefault=normal&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=cccccc&bgTextureHeader=03_highlight_soft.png&bgImgOpacityHeader=75&borderColorHeader=aaaaaa&fcHeader=222222&iconColorHeader=222222&bgColorContent=ffffff&bgTextureContent=01_flat.png&bgImgOpacityContent=75&borderColorContent=aaaaaa&fcContent=222222&iconColorContent=222222&bgColorDefault=e6e6e6&bgTextureDefault=02_glass.png&bgImgOpacityDefault=75&borderColorDefault=d3d3d3&fcDefault=555555&iconColorDefault=888888&bgColorHover=dadada&bgTextureHover=02_glass.png&bgImgOpacityHover=75&borderColorHover=999999&fcHover=212121&iconColorHover=454545&bgColorActive=ffffff&bgTextureActive=02_glass.png&bgImgOpacityActive=65&borderColorActive=aaaaaa&fcActive=212121&iconColorActive=454545&bgColorHighlight=fbf9ee&bgTextureHighlight=02_glass.png&bgImgOpacityHighlight=55&borderColorHighlight=fcefa1&fcHighlight=363636&iconColorHighlight=2e83ff&bgColorError=fef1ec&bgTextureError=02_glass.png&bgImgOpacityError=95&borderColorError=cd0a0a&fcError=cd0a0a&iconColorError=cd0a0a&bgColorOverlay=aaaaaa&bgTextureOverlay=01_flat.png&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=aaaaaa&bgTextureShadow=01_flat.png&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px 54 | */ 55 | 56 | 57 | /* Component containers 58 | ----------------------------------*/ 59 | .ui-widget { font-family: Verdana,Arial,sans-serif; font-size: 1.1em; } 60 | .ui-widget .ui-widget { font-size: 1em; } 61 | .ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Verdana,Arial,sans-serif; font-size: 1em; } 62 | .ui-widget-content { border: 1px solid #aaaaaa; background: #ffffff url(images/ui-bg_flat_75_ffffff_40x100.png) 50% 50% repeat-x; color: #222222; } 63 | .ui-widget-content a { color: #222222; } 64 | .ui-widget-header { border: 1px solid #aaaaaa; background: #cccccc url(images/ui-bg_highlight-soft_75_cccccc_1x100.png) 50% 50% repeat-x; color: #222222; font-weight: bold; } 65 | .ui-widget-header a { color: #222222; } 66 | 67 | /* Interaction states 68 | ----------------------------------*/ 69 | .ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #d3d3d3; background: #e6e6e6 url(images/ui-bg_glass_75_e6e6e6_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #555555; } 70 | .ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #555555; text-decoration: none; } 71 | .ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #999999; background: #dadada url(images/ui-bg_glass_75_dadada_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #212121; } 72 | .ui-state-hover a, .ui-state-hover a:hover { color: #212121; text-decoration: none; } 73 | .ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #aaaaaa; background: #ffffff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #212121; } 74 | .ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #212121; text-decoration: none; } 75 | .ui-widget :active { outline: none; } 76 | 77 | /* Interaction Cues 78 | ----------------------------------*/ 79 | .ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #fcefa1; background: #fbf9ee url(images/ui-bg_glass_55_fbf9ee_1x400.png) 50% 50% repeat-x; color: #363636; } 80 | .ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636; } 81 | .ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #cd0a0a; background: #fef1ec url(images/ui-bg_glass_95_fef1ec_1x400.png) 50% 50% repeat-x; color: #cd0a0a; } 82 | .ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #cd0a0a; } 83 | .ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #cd0a0a; } 84 | .ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; } 85 | .ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; } 86 | .ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; } 87 | 88 | /* Icons 89 | ----------------------------------*/ 90 | 91 | /* states and images */ 92 | .ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_222222_256x240.png); } 93 | .ui-widget-content .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); } 94 | .ui-widget-header .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); } 95 | .ui-state-default .ui-icon { background-image: url(images/ui-icons_888888_256x240.png); } 96 | .ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_454545_256x240.png); } 97 | .ui-state-active .ui-icon {background-image: url(images/ui-icons_454545_256x240.png); } 98 | .ui-state-highlight .ui-icon {background-image: url(images/ui-icons_2e83ff_256x240.png); } 99 | .ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_cd0a0a_256x240.png); } 100 | 101 | /* positioning */ 102 | .ui-icon-carat-1-n { background-position: 0 0; } 103 | .ui-icon-carat-1-ne { background-position: -16px 0; } 104 | .ui-icon-carat-1-e { background-position: -32px 0; } 105 | .ui-icon-carat-1-se { background-position: -48px 0; } 106 | .ui-icon-carat-1-s { background-position: -64px 0; } 107 | .ui-icon-carat-1-sw { background-position: -80px 0; } 108 | .ui-icon-carat-1-w { background-position: -96px 0; } 109 | .ui-icon-carat-1-nw { background-position: -112px 0; } 110 | .ui-icon-carat-2-n-s { background-position: -128px 0; } 111 | .ui-icon-carat-2-e-w { background-position: -144px 0; } 112 | .ui-icon-triangle-1-n { background-position: 0 -16px; } 113 | .ui-icon-triangle-1-ne { background-position: -16px -16px; } 114 | .ui-icon-triangle-1-e { background-position: -32px -16px; } 115 | .ui-icon-triangle-1-se { background-position: -48px -16px; } 116 | .ui-icon-triangle-1-s { background-position: -64px -16px; } 117 | .ui-icon-triangle-1-sw { background-position: -80px -16px; } 118 | .ui-icon-triangle-1-w { background-position: -96px -16px; } 119 | .ui-icon-triangle-1-nw { background-position: -112px -16px; } 120 | .ui-icon-triangle-2-n-s { background-position: -128px -16px; } 121 | .ui-icon-triangle-2-e-w { background-position: -144px -16px; } 122 | .ui-icon-arrow-1-n { background-position: 0 -32px; } 123 | .ui-icon-arrow-1-ne { background-position: -16px -32px; } 124 | .ui-icon-arrow-1-e { background-position: -32px -32px; } 125 | .ui-icon-arrow-1-se { background-position: -48px -32px; } 126 | .ui-icon-arrow-1-s { background-position: -64px -32px; } 127 | .ui-icon-arrow-1-sw { background-position: -80px -32px; } 128 | .ui-icon-arrow-1-w { background-position: -96px -32px; } 129 | .ui-icon-arrow-1-nw { background-position: -112px -32px; } 130 | .ui-icon-arrow-2-n-s { background-position: -128px -32px; } 131 | .ui-icon-arrow-2-ne-sw { background-position: -144px -32px; } 132 | .ui-icon-arrow-2-e-w { background-position: -160px -32px; } 133 | .ui-icon-arrow-2-se-nw { background-position: -176px -32px; } 134 | .ui-icon-arrowstop-1-n { background-position: -192px -32px; } 135 | .ui-icon-arrowstop-1-e { background-position: -208px -32px; } 136 | .ui-icon-arrowstop-1-s { background-position: -224px -32px; } 137 | .ui-icon-arrowstop-1-w { background-position: -240px -32px; } 138 | .ui-icon-arrowthick-1-n { background-position: 0 -48px; } 139 | .ui-icon-arrowthick-1-ne { background-position: -16px -48px; } 140 | .ui-icon-arrowthick-1-e { background-position: -32px -48px; } 141 | .ui-icon-arrowthick-1-se { background-position: -48px -48px; } 142 | .ui-icon-arrowthick-1-s { background-position: -64px -48px; } 143 | .ui-icon-arrowthick-1-sw { background-position: -80px -48px; } 144 | .ui-icon-arrowthick-1-w { background-position: -96px -48px; } 145 | .ui-icon-arrowthick-1-nw { background-position: -112px -48px; } 146 | .ui-icon-arrowthick-2-n-s { background-position: -128px -48px; } 147 | .ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; } 148 | .ui-icon-arrowthick-2-e-w { background-position: -160px -48px; } 149 | .ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; } 150 | .ui-icon-arrowthickstop-1-n { background-position: -192px -48px; } 151 | .ui-icon-arrowthickstop-1-e { background-position: -208px -48px; } 152 | .ui-icon-arrowthickstop-1-s { background-position: -224px -48px; } 153 | .ui-icon-arrowthickstop-1-w { background-position: -240px -48px; } 154 | .ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; } 155 | .ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; } 156 | .ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; } 157 | .ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; } 158 | .ui-icon-arrowreturn-1-w { background-position: -64px -64px; } 159 | .ui-icon-arrowreturn-1-n { background-position: -80px -64px; } 160 | .ui-icon-arrowreturn-1-e { background-position: -96px -64px; } 161 | .ui-icon-arrowreturn-1-s { background-position: -112px -64px; } 162 | .ui-icon-arrowrefresh-1-w { background-position: -128px -64px; } 163 | .ui-icon-arrowrefresh-1-n { background-position: -144px -64px; } 164 | .ui-icon-arrowrefresh-1-e { background-position: -160px -64px; } 165 | .ui-icon-arrowrefresh-1-s { background-position: -176px -64px; } 166 | .ui-icon-arrow-4 { background-position: 0 -80px; } 167 | .ui-icon-arrow-4-diag { background-position: -16px -80px; } 168 | .ui-icon-extlink { background-position: -32px -80px; } 169 | .ui-icon-newwin { background-position: -48px -80px; } 170 | .ui-icon-refresh { background-position: -64px -80px; } 171 | .ui-icon-shuffle { background-position: -80px -80px; } 172 | .ui-icon-transfer-e-w { background-position: -96px -80px; } 173 | .ui-icon-transferthick-e-w { background-position: -112px -80px; } 174 | .ui-icon-folder-collapsed { background-position: 0 -96px; } 175 | .ui-icon-folder-open { background-position: -16px -96px; } 176 | .ui-icon-document { background-position: -32px -96px; } 177 | .ui-icon-document-b { background-position: -48px -96px; } 178 | .ui-icon-note { background-position: -64px -96px; } 179 | .ui-icon-mail-closed { background-position: -80px -96px; } 180 | .ui-icon-mail-open { background-position: -96px -96px; } 181 | .ui-icon-suitcase { background-position: -112px -96px; } 182 | .ui-icon-comment { background-position: -128px -96px; } 183 | .ui-icon-person { background-position: -144px -96px; } 184 | .ui-icon-print { background-position: -160px -96px; } 185 | .ui-icon-trash { background-position: -176px -96px; } 186 | .ui-icon-locked { background-position: -192px -96px; } 187 | .ui-icon-unlocked { background-position: -208px -96px; } 188 | .ui-icon-bookmark { background-position: -224px -96px; } 189 | .ui-icon-tag { background-position: -240px -96px; } 190 | .ui-icon-home { background-position: 0 -112px; } 191 | .ui-icon-flag { background-position: -16px -112px; } 192 | .ui-icon-calendar { background-position: -32px -112px; } 193 | .ui-icon-cart { background-position: -48px -112px; } 194 | .ui-icon-pencil { background-position: -64px -112px; } 195 | .ui-icon-clock { background-position: -80px -112px; } 196 | .ui-icon-disk { background-position: -96px -112px; } 197 | .ui-icon-calculator { background-position: -112px -112px; } 198 | .ui-icon-zoomin { background-position: -128px -112px; } 199 | .ui-icon-zoomout { background-position: -144px -112px; } 200 | .ui-icon-search { background-position: -160px -112px; } 201 | .ui-icon-wrench { background-position: -176px -112px; } 202 | .ui-icon-gear { background-position: -192px -112px; } 203 | .ui-icon-heart { background-position: -208px -112px; } 204 | .ui-icon-star { background-position: -224px -112px; } 205 | .ui-icon-link { background-position: -240px -112px; } 206 | .ui-icon-cancel { background-position: 0 -128px; } 207 | .ui-icon-plus { background-position: -16px -128px; } 208 | .ui-icon-plusthick { background-position: -32px -128px; } 209 | .ui-icon-minus { background-position: -48px -128px; } 210 | .ui-icon-minusthick { background-position: -64px -128px; } 211 | .ui-icon-close { background-position: -80px -128px; } 212 | .ui-icon-closethick { background-position: -96px -128px; } 213 | .ui-icon-key { background-position: -112px -128px; } 214 | .ui-icon-lightbulb { background-position: -128px -128px; } 215 | .ui-icon-scissors { background-position: -144px -128px; } 216 | .ui-icon-clipboard { background-position: -160px -128px; } 217 | .ui-icon-copy { background-position: -176px -128px; } 218 | .ui-icon-contact { background-position: -192px -128px; } 219 | .ui-icon-image { background-position: -208px -128px; } 220 | .ui-icon-video { background-position: -224px -128px; } 221 | .ui-icon-script { background-position: -240px -128px; } 222 | .ui-icon-alert { background-position: 0 -144px; } 223 | .ui-icon-info { background-position: -16px -144px; } 224 | .ui-icon-notice { background-position: -32px -144px; } 225 | .ui-icon-help { background-position: -48px -144px; } 226 | .ui-icon-check { background-position: -64px -144px; } 227 | .ui-icon-bullet { background-position: -80px -144px; } 228 | .ui-icon-radio-off { background-position: -96px -144px; } 229 | .ui-icon-radio-on { background-position: -112px -144px; } 230 | .ui-icon-pin-w { background-position: -128px -144px; } 231 | .ui-icon-pin-s { background-position: -144px -144px; } 232 | .ui-icon-play { background-position: 0 -160px; } 233 | .ui-icon-pause { background-position: -16px -160px; } 234 | .ui-icon-seek-next { background-position: -32px -160px; } 235 | .ui-icon-seek-prev { background-position: -48px -160px; } 236 | .ui-icon-seek-end { background-position: -64px -160px; } 237 | .ui-icon-seek-start { background-position: -80px -160px; } 238 | /* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */ 239 | .ui-icon-seek-first { background-position: -80px -160px; } 240 | .ui-icon-stop { background-position: -96px -160px; } 241 | .ui-icon-eject { background-position: -112px -160px; } 242 | .ui-icon-volume-off { background-position: -128px -160px; } 243 | .ui-icon-volume-on { background-position: -144px -160px; } 244 | .ui-icon-power { background-position: 0 -176px; } 245 | .ui-icon-signal-diag { background-position: -16px -176px; } 246 | .ui-icon-signal { background-position: -32px -176px; } 247 | .ui-icon-battery-0 { background-position: -48px -176px; } 248 | .ui-icon-battery-1 { background-position: -64px -176px; } 249 | .ui-icon-battery-2 { background-position: -80px -176px; } 250 | .ui-icon-battery-3 { background-position: -96px -176px; } 251 | .ui-icon-circle-plus { background-position: 0 -192px; } 252 | .ui-icon-circle-minus { background-position: -16px -192px; } 253 | .ui-icon-circle-close { background-position: -32px -192px; } 254 | .ui-icon-circle-triangle-e { background-position: -48px -192px; } 255 | .ui-icon-circle-triangle-s { background-position: -64px -192px; } 256 | .ui-icon-circle-triangle-w { background-position: -80px -192px; } 257 | .ui-icon-circle-triangle-n { background-position: -96px -192px; } 258 | .ui-icon-circle-arrow-e { background-position: -112px -192px; } 259 | .ui-icon-circle-arrow-s { background-position: -128px -192px; } 260 | .ui-icon-circle-arrow-w { background-position: -144px -192px; } 261 | .ui-icon-circle-arrow-n { background-position: -160px -192px; } 262 | .ui-icon-circle-zoomin { background-position: -176px -192px; } 263 | .ui-icon-circle-zoomout { background-position: -192px -192px; } 264 | .ui-icon-circle-check { background-position: -208px -192px; } 265 | .ui-icon-circlesmall-plus { background-position: 0 -208px; } 266 | .ui-icon-circlesmall-minus { background-position: -16px -208px; } 267 | .ui-icon-circlesmall-close { background-position: -32px -208px; } 268 | .ui-icon-squaresmall-plus { background-position: -48px -208px; } 269 | .ui-icon-squaresmall-minus { background-position: -64px -208px; } 270 | .ui-icon-squaresmall-close { background-position: -80px -208px; } 271 | .ui-icon-grip-dotted-vertical { background-position: 0 -224px; } 272 | .ui-icon-grip-dotted-horizontal { background-position: -16px -224px; } 273 | .ui-icon-grip-solid-vertical { background-position: -32px -224px; } 274 | .ui-icon-grip-solid-horizontal { background-position: -48px -224px; } 275 | .ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; } 276 | .ui-icon-grip-diagonal-se { background-position: -80px -224px; } 277 | 278 | 279 | /* Misc visuals 280 | ----------------------------------*/ 281 | 282 | /* Corner radius */ 283 | .ui-corner-tl { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; border-top-left-radius: 4px; } 284 | .ui-corner-tr { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; border-top-right-radius: 4px; } 285 | .ui-corner-bl { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; } 286 | .ui-corner-br { -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; } 287 | .ui-corner-top { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; border-top-left-radius: 4px; -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; border-top-right-radius: 4px; } 288 | .ui-corner-bottom { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; } 289 | .ui-corner-right { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; border-top-right-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; } 290 | .ui-corner-left { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; border-top-left-radius: 4px; -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; } 291 | .ui-corner-all { -moz-border-radius: 4px; -webkit-border-radius: 4px; border-radius: 4px; } 292 | 293 | /* Overlays */ 294 | .ui-widget-overlay { background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); } 295 | .ui-widget-shadow { margin: -8px 0 0 -8px; padding: 8px; background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); -moz-border-radius: 8px; -webkit-border-radius: 8px; border-radius: 8px; }/* 296 | * jQuery UI Resizable 1.8.7 297 | * 298 | * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) 299 | * Dual licensed under the MIT or GPL Version 2 licenses. 300 | * http://jquery.org/license 301 | * 302 | * http://docs.jquery.com/UI/Resizable#theming 303 | */ 304 | .ui-resizable { position: relative;} 305 | .ui-resizable-handle { position: absolute;font-size: 0.1px;z-index: 99999; display: block;} 306 | .ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; } 307 | .ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0; } 308 | .ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0; } 309 | .ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0; height: 100%; } 310 | .ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0; height: 100%; } 311 | .ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; } 312 | .ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; } 313 | .ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; } 314 | .ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}/* 315 | * jQuery UI Selectable 1.8.7 316 | * 317 | * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) 318 | * Dual licensed under the MIT or GPL Version 2 licenses. 319 | * http://jquery.org/license 320 | * 321 | * http://docs.jquery.com/UI/Selectable#theming 322 | */ 323 | .ui-selectable-helper { position: absolute; z-index: 100; border:1px dotted black; } 324 | /* 325 | * jQuery UI Button 1.8.7 326 | * 327 | * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) 328 | * Dual licensed under the MIT or GPL Version 2 licenses. 329 | * http://jquery.org/license 330 | * 331 | * http://docs.jquery.com/UI/Button#theming 332 | */ 333 | .ui-button { display: inline-block; position: relative; padding: 0; margin-right: .1em; text-decoration: none !important; cursor: pointer; text-align: center; zoom: 1; overflow: visible; } /* the overflow property removes extra width in IE */ 334 | .ui-button-icon-only { width: 2.2em; } /* to make room for the icon, a width needs to be set here */ 335 | button.ui-button-icon-only { width: 2.4em; } /* button elements seem to need a little more width */ 336 | .ui-button-icons-only { width: 3.4em; } 337 | button.ui-button-icons-only { width: 3.7em; } 338 | 339 | /*button text element */ 340 | .ui-button .ui-button-text { display: block; line-height: 1.4; } 341 | .ui-button-text-only .ui-button-text { padding: .4em 1em; } 342 | .ui-button-icon-only .ui-button-text, .ui-button-icons-only .ui-button-text { padding: .4em; text-indent: -9999999px; } 343 | .ui-button-text-icon-primary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 1em .4em 2.1em; } 344 | .ui-button-text-icon-secondary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 2.1em .4em 1em; } 345 | .ui-button-text-icons .ui-button-text { padding-left: 2.1em; padding-right: 2.1em; } 346 | /* no icon support for input elements, provide padding by default */ 347 | input.ui-button { padding: .4em 1em; } 348 | 349 | /*button icon element(s) */ 350 | .ui-button-icon-only .ui-icon, .ui-button-text-icon-primary .ui-icon, .ui-button-text-icon-secondary .ui-icon, .ui-button-text-icons .ui-icon, .ui-button-icons-only .ui-icon { position: absolute; top: 50%; margin-top: -8px; } 351 | .ui-button-icon-only .ui-icon { left: 50%; margin-left: -8px; } 352 | .ui-button-text-icon-primary .ui-button-icon-primary, .ui-button-text-icons .ui-button-icon-primary, .ui-button-icons-only .ui-button-icon-primary { left: .5em; } 353 | .ui-button-text-icon-secondary .ui-button-icon-secondary, .ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; } 354 | .ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; } 355 | 356 | /*button sets*/ 357 | .ui-buttonset { margin-right: 7px; } 358 | .ui-buttonset .ui-button { margin-left: 0; margin-right: -.3em; } 359 | 360 | /* workarounds */ 361 | button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra padding in Firefox */ 362 | /* 363 | * jQuery UI Dialog 1.8.7 364 | * 365 | * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) 366 | * Dual licensed under the MIT or GPL Version 2 licenses. 367 | * http://jquery.org/license 368 | * 369 | * http://docs.jquery.com/UI/Dialog#theming 370 | */ 371 | .ui-dialog { position: absolute; padding: .2em; width: 300px; overflow: hidden; } 372 | .ui-dialog .ui-dialog-titlebar { padding: .5em 1em .3em; position: relative; } 373 | .ui-dialog .ui-dialog-title { float: left; margin: .1em 16px .2em 0; } 374 | .ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; } 375 | .ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; } 376 | .ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; } 377 | .ui-dialog .ui-dialog-content { position: relative; border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; } 378 | .ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; } 379 | .ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { float: right; } 380 | .ui-dialog .ui-dialog-buttonpane button { margin: .5em .4em .5em 0; cursor: pointer; } 381 | .ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; } 382 | .ui-draggable .ui-dialog-titlebar { cursor: move; } 383 | /* 384 | * jQuery UI Slider 1.8.7 385 | * 386 | * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) 387 | * Dual licensed under the MIT or GPL Version 2 licenses. 388 | * http://jquery.org/license 389 | * 390 | * http://docs.jquery.com/UI/Slider#theming 391 | */ 392 | .ui-slider { position: relative; text-align: left; } 393 | .ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; } 394 | .ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; background-position: 0 0; } 395 | 396 | .ui-slider-horizontal { height: .8em; } 397 | .ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; } 398 | .ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; } 399 | .ui-slider-horizontal .ui-slider-range-min { left: 0; } 400 | .ui-slider-horizontal .ui-slider-range-max { right: 0; } 401 | 402 | .ui-slider-vertical { width: .8em; height: 100px; } 403 | .ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; } 404 | .ui-slider-vertical .ui-slider-range { left: 0; width: 100%; } 405 | .ui-slider-vertical .ui-slider-range-min { bottom: 0; } 406 | .ui-slider-vertical .ui-slider-range-max { top: 0; }/* 407 | * jQuery UI Tabs 1.8.7 408 | * 409 | * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) 410 | * Dual licensed under the MIT or GPL Version 2 licenses. 411 | * http://jquery.org/license 412 | * 413 | * http://docs.jquery.com/UI/Tabs#theming 414 | */ 415 | .ui-tabs { position: relative; padding: .2em; zoom: 1; } /* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */ 416 | .ui-tabs .ui-tabs-nav { margin: 0; padding: .2em .2em 0; } 417 | .ui-tabs .ui-tabs-nav li { list-style: none; float: left; position: relative; top: 1px; margin: 0 .2em 1px 0; border-bottom: 0 !important; padding: 0; white-space: nowrap; } 418 | .ui-tabs .ui-tabs-nav li a { float: left; padding: .5em 1em; text-decoration: none; } 419 | .ui-tabs .ui-tabs-nav li.ui-tabs-selected { margin-bottom: 0; padding-bottom: 1px; } 420 | .ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; } 421 | .ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */ 422 | .ui-tabs .ui-tabs-panel { display: block; border-width: 0; padding: 1em 1.4em; background: none; } 423 | .ui-tabs .ui-tabs-hide { display: none !important; } 424 | /* 425 | * jQuery UI Datepicker 1.8.7 426 | * 427 | * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) 428 | * Dual licensed under the MIT or GPL Version 2 licenses. 429 | * http://jquery.org/license 430 | * 431 | * http://docs.jquery.com/UI/Datepicker#theming 432 | */ 433 | .ui-datepicker { width: 17em; padding: .2em .2em 0; display: none; } 434 | .ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; } 435 | .ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; } 436 | .ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; } 437 | .ui-datepicker .ui-datepicker-prev { left:2px; } 438 | .ui-datepicker .ui-datepicker-next { right:2px; } 439 | .ui-datepicker .ui-datepicker-prev-hover { left:1px; } 440 | .ui-datepicker .ui-datepicker-next-hover { right:1px; } 441 | .ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; } 442 | .ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; } 443 | .ui-datepicker .ui-datepicker-title select { font-size:1em; margin:1px 0; } 444 | .ui-datepicker select.ui-datepicker-month-year {width: 100%;} 445 | .ui-datepicker select.ui-datepicker-month, 446 | .ui-datepicker select.ui-datepicker-year { width: 49%;} 447 | .ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; } 448 | .ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; } 449 | .ui-datepicker td { border: 0; padding: 1px; } 450 | .ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; } 451 | .ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; } 452 | .ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; } 453 | .ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; } 454 | 455 | /* with multiple calendars */ 456 | .ui-datepicker.ui-datepicker-multi { width:auto; } 457 | .ui-datepicker-multi .ui-datepicker-group { float:left; } 458 | .ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; } 459 | .ui-datepicker-multi-2 .ui-datepicker-group { width:50%; } 460 | .ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; } 461 | .ui-datepicker-multi-4 .ui-datepicker-group { width:25%; } 462 | .ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; } 463 | .ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; } 464 | .ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; } 465 | .ui-datepicker-row-break { clear:both; width:100%; } 466 | 467 | /* RTL support */ 468 | .ui-datepicker-rtl { direction: rtl; } 469 | .ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; } 470 | .ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; } 471 | .ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; } 472 | .ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; } 473 | .ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; } 474 | .ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; } 475 | .ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; } 476 | .ui-datepicker-rtl .ui-datepicker-group { float:right; } 477 | .ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; } 478 | .ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; } 479 | 480 | /* IE6 IFRAME FIX (taken from datepicker 1.5.3 */ 481 | .ui-datepicker-cover { 482 | display: none; /*sorry for IE5*/ 483 | display/**/: block; /*sorry for IE5*/ 484 | position: absolute; /*must have*/ 485 | z-index: -1; /*must have*/ 486 | filter: mask(); /*must have*/ 487 | top: -4px; /*must have*/ 488 | left: -4px; /*must have*/ 489 | width: 200px; /*must have*/ 490 | height: 200px; /*must have*/ 491 | }/* 492 | * jQuery UI Progressbar 1.8.7 493 | * 494 | * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) 495 | * Dual licensed under the MIT or GPL Version 2 licenses. 496 | * http://jquery.org/license 497 | * 498 | * http://docs.jquery.com/UI/Progressbar#theming 499 | */ 500 | .ui-progressbar { height:2em; text-align: left; } 501 | .ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; } --------------------------------------------------------------------------------