├── .gitignore ├── .repo.yml ├── LICENSE ├── README.md ├── Rakefile ├── bin └── ledger_web ├── ledger_web.gemspec ├── lib ├── ledger_web.rb └── ledger_web │ ├── app.rb │ ├── config.rb │ ├── db.rb │ ├── db │ └── migrate │ │ ├── 20111226180900_initial_schema.rb │ │ ├── 20111231132900_add_views.rb │ │ ├── 20120104192400_add_cost_column.rb │ │ └── 20120105185800_add_prices_table.rb │ ├── decorators.rb │ ├── helpers.rb │ ├── price_lookup.rb │ ├── public │ ├── codemirror │ │ ├── keymap │ │ │ ├── emacs.js │ │ │ └── vim.js │ │ ├── lib │ │ │ ├── codemirror.css │ │ │ ├── codemirror.js │ │ │ └── util │ │ │ │ ├── dialog.css │ │ │ │ ├── dialog.js │ │ │ │ ├── foldcode.js │ │ │ │ ├── formatting.js │ │ │ │ ├── javascript-hint.js │ │ │ │ ├── overlay.js │ │ │ │ ├── runmode.js │ │ │ │ ├── search.js │ │ │ │ ├── searchcursor.js │ │ │ │ ├── simple-hint.css │ │ │ │ └── simple-hint.js │ │ ├── mode │ │ │ ├── clike │ │ │ │ ├── clike.js │ │ │ │ └── index.html │ │ │ ├── clojure │ │ │ │ ├── clojure.js │ │ │ │ └── index.html │ │ │ ├── coffeescript │ │ │ │ ├── LICENSE │ │ │ │ ├── coffeescript.js │ │ │ │ └── index.html │ │ │ ├── css │ │ │ │ ├── css.js │ │ │ │ └── index.html │ │ │ ├── diff │ │ │ │ ├── diff.css │ │ │ │ ├── diff.js │ │ │ │ └── index.html │ │ │ ├── gfm │ │ │ │ ├── gfm.js │ │ │ │ └── index.html │ │ │ ├── groovy │ │ │ │ ├── groovy.js │ │ │ │ └── index.html │ │ │ ├── haskell │ │ │ │ ├── haskell.js │ │ │ │ └── index.html │ │ │ ├── htmlembedded │ │ │ │ ├── htmlembedded.js │ │ │ │ └── index.html │ │ │ ├── htmlmixed │ │ │ │ ├── htmlmixed.js │ │ │ │ └── index.html │ │ │ ├── javascript │ │ │ │ ├── index.html │ │ │ │ └── javascript.js │ │ │ ├── jinja2 │ │ │ │ ├── index.html │ │ │ │ └── jinja2.js │ │ │ ├── lua │ │ │ │ ├── index.html │ │ │ │ └── lua.js │ │ │ ├── markdown │ │ │ │ ├── index.html │ │ │ │ └── markdown.js │ │ │ ├── ntriples │ │ │ │ ├── index.html │ │ │ │ └── ntriples.js │ │ │ ├── pascal │ │ │ │ ├── LICENSE │ │ │ │ ├── index.html │ │ │ │ └── pascal.js │ │ │ ├── perl │ │ │ │ ├── LICENSE │ │ │ │ ├── index.html │ │ │ │ └── perl.js │ │ │ ├── php │ │ │ │ ├── index.html │ │ │ │ └── php.js │ │ │ ├── plsql │ │ │ │ ├── index.html │ │ │ │ └── plsql.js │ │ │ ├── python │ │ │ │ ├── LICENSE.txt │ │ │ │ ├── index.html │ │ │ │ └── python.js │ │ │ ├── r │ │ │ │ ├── LICENSE │ │ │ │ ├── index.html │ │ │ │ └── r.js │ │ │ ├── rpm │ │ │ │ ├── changes │ │ │ │ │ ├── changes.js │ │ │ │ │ └── index.html │ │ │ │ └── spec │ │ │ │ │ ├── index.html │ │ │ │ │ ├── spec.css │ │ │ │ │ └── spec.js │ │ │ ├── rst │ │ │ │ ├── index.html │ │ │ │ └── rst.js │ │ │ ├── ruby │ │ │ │ ├── LICENSE │ │ │ │ ├── index.html │ │ │ │ └── ruby.js │ │ │ ├── rust │ │ │ │ ├── index.html │ │ │ │ └── rust.js │ │ │ ├── scheme │ │ │ │ ├── index.html │ │ │ │ └── scheme.js │ │ │ ├── smalltalk │ │ │ │ ├── index.html │ │ │ │ └── smalltalk.js │ │ │ ├── sparql │ │ │ │ ├── index.html │ │ │ │ └── sparql.js │ │ │ ├── stex │ │ │ │ ├── index.html │ │ │ │ └── stex.js │ │ │ ├── tiddlywiki │ │ │ │ ├── index.html │ │ │ │ ├── tiddlywiki.css │ │ │ │ └── tiddlywiki.js │ │ │ ├── velocity │ │ │ │ ├── index.html │ │ │ │ └── velocity.js │ │ │ ├── xml │ │ │ │ ├── index.html │ │ │ │ └── xml.js │ │ │ ├── xmlpure │ │ │ │ ├── index.html │ │ │ │ └── xmlpure.js │ │ │ └── yaml │ │ │ │ ├── index.html │ │ │ │ └── yaml.js │ │ └── theme │ │ │ ├── cobalt.css │ │ │ ├── eclipse.css │ │ │ ├── elegant.css │ │ │ ├── monokai.css │ │ │ ├── neat.css │ │ │ ├── night.css │ │ │ └── rubyblue.css │ ├── css │ │ ├── bootstrap.min.css │ │ └── ledger.css │ ├── favicon.ico │ ├── img │ │ ├── glyphicons-halflings-white.png │ │ └── glyphicons-halflings.png │ └── js │ │ ├── bootstrap-dropdown.js │ │ ├── bootstrap.min.js │ │ ├── d3.min.js │ │ ├── jquery-1.7.1.min.js │ │ └── jquery.tablesorter.min.js │ ├── report.rb │ ├── reports │ └── savings_rate.erb │ ├── table.rb │ ├── version.rb │ ├── views │ ├── error.erb │ ├── help.erb │ ├── layout.erb │ ├── pdf.erb │ ├── table.erb │ └── visualization.erb │ └── watcher.rb └── test ├── config_spec.rb ├── database_load_spec.rb ├── fixtures ├── complex_costs.dat ├── quoted.dat └── small.dat ├── report_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | .bundle 2 | db/*.sqlite3 3 | log/*.log 4 | tmp/ 5 | .sass-cache/ 6 | env.sh 7 | .DS_Store 8 | \#* 9 | .\#* 10 | *.gem -------------------------------------------------------------------------------- /.repo.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Ledger Web 3 | description: A web-based reporting system for the Ledger command line accounting program -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Peter Keen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require 'rake' 3 | 4 | task :test do 5 | system 'rspec --color --format=documentation test' 6 | end 7 | -------------------------------------------------------------------------------- /bin/ledger_web: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'rubygems' 4 | require 'optparse' 5 | 6 | STDOUT.sync = true 7 | 8 | begin 9 | require 'ledger_web' 10 | rescue LoadError => e 11 | path = File.expand_path '../../lib', __FILE__ 12 | $:.unshift(path) if File.directory?(path) && !$:.include?(path) 13 | require 'ledger_web' 14 | end 15 | 16 | user_dir = "#{ENV['HOME']}/.ledger_web" 17 | 18 | OptionParser.new do |opts| 19 | opts.banner = "Usage: ledger_web [options]" 20 | 21 | opts.on("-p", "--port PORT", Integer, "Port to expose the web interface") do |p| 22 | LedgerWeb::Config.instance.set :port, p.to_i 23 | end 24 | 25 | opts.on("-f", "--ledger-file FILE", String, "Ledger file to watch and load") do |f| 26 | LedgerWeb::Config.instance.set :ledger_file, f 27 | end 28 | 29 | opts.on("-d", "--database-url URL", String, "Database URL to load into") do |d| 30 | LedgerWeb::Config.instance.set :database_url, d 31 | end 32 | 33 | opts.on("-U", "--user-dir DIR", String, "Directory containing reports and config") do |d| 34 | user_dir = d 35 | end 36 | end.parse! 37 | 38 | LedgerWeb::Config.instance.load_user_config(user_dir) 39 | 40 | LedgerWeb::Database.connect 41 | 42 | LedgerWeb::Watcher.run! 43 | LedgerWeb::Application.run!(:port => LedgerWeb::Config.instance.get(:port)) 44 | -------------------------------------------------------------------------------- /ledger_web.gemspec: -------------------------------------------------------------------------------- 1 | $:.push File.expand_path("../lib", __FILE__) 2 | 3 | require 'ledger_web/version' 4 | 5 | Gem::Specification.new do |s| 6 | s.name = "ledger_web" 7 | s.version = LedgerWeb::VERSION 8 | s.date = `date +%Y-%m-%d` 9 | s.platform = Gem::Platform::RUBY 10 | s.authors = ["Pete Keen"] 11 | s.email = ["pete@bugsplat.info"] 12 | s.homepage = "https://github.com/peterkeen/ledger-web" 13 | s.summary = %q{A web-based, sql-backed front-end for the Ledger command-line accounting system} 14 | s.description = %q{Allows arbitrary reporting on your ledger using easy-to-write SQL queries} 15 | 16 | s.add_dependency("pg") 17 | s.add_dependency("sequel") 18 | s.add_dependency("directory_watcher", "~> 1.5.1") 19 | s.add_dependency("rack", ">= 1.3.6") 20 | s.add_dependency("sinatra") 21 | s.add_dependency("sinatra-session") 22 | s.add_dependency("sinatra-contrib") 23 | s.add_dependency("rspec") 24 | s.add_dependency("database_cleaner") 25 | s.add_dependency("docverter") 26 | 27 | s.bindir = 'bin' 28 | s.files = `git ls-files`.split("\n") 29 | s.test_files = `git ls-files -- test/*`.split("\n") 30 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 31 | s.require_paths = ["lib"] 32 | end 33 | 34 | -------------------------------------------------------------------------------- /lib/ledger_web.rb: -------------------------------------------------------------------------------- 1 | libdir = File.dirname(__FILE__) 2 | $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir) 3 | 4 | require 'ledger_web/price_lookup' 5 | require 'ledger_web/config' 6 | require 'ledger_web/db' 7 | require 'ledger_web/report' 8 | require 'ledger_web/table' 9 | require 'ledger_web/decorators' 10 | require 'ledger_web/watcher' 11 | require 'ledger_web/helpers' 12 | require 'ledger_web/app' 13 | -------------------------------------------------------------------------------- /lib/ledger_web/app.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'sinatra/base' 3 | require 'sinatra/contrib' 4 | require 'sinatra/session' 5 | require 'docverter' 6 | 7 | module LedgerWeb 8 | class Application < Sinatra::Base 9 | register Sinatra::Session 10 | 11 | set :session_secret, LedgerWeb::Config.instance.get(:session_secret) 12 | set :session_expire, LedgerWeb::Config.instance.get(:session_expire) 13 | set :reload_templates, true 14 | 15 | helpers Sinatra::Capture 16 | helpers LedgerWeb::Helpers 17 | 18 | def find_template(views, name, engine, &block) 19 | _views = LedgerWeb::Config.instance.get(:report_directories) + LedgerWeb::Config.instance.get(:additional_view_directories) + [File.join(File.dirname(__FILE__), 'views')] 20 | Array(_views).each { |v| super(v, name, engine, &block) } 21 | end 22 | 23 | before do 24 | if not session? 25 | session_start! 26 | today = Date.today 27 | session[:from] = Date.new(today.year - 1, today.month, today.day).strftime("%Y/%m/%d") 28 | session[:to] = today.strftime("%Y/%m/%d") 29 | end 30 | Report.session = session 31 | Report.params = params 32 | 33 | @reports = find_all_reports 34 | end 35 | 36 | post '/update-date-range' do 37 | 38 | if params[:reset] 39 | today = Date.today 40 | session[:from] = Date.new(today.year - 1, today.month, today.day).strftime('%Y/%m/%d') 41 | session[:to] = today.strftime('%Y/%m/%d') 42 | else 43 | session[:from] = Date.parse(params[:from]).strftime('%Y/%m/%d') 44 | session[:to] = Date.parse(params[:to]).strftime('%Y/%m/%d') 45 | end 46 | 47 | redirect back 48 | end 49 | 50 | get '/reports/:name' do 51 | begin 52 | erb params[:name].to_sym 53 | rescue Exception => e 54 | @error = e 55 | erb :error 56 | end 57 | end 58 | 59 | get '/pdf/:name' do 60 | begin 61 | res = Docverter::Conversion.run do |c| 62 | c.from = 'html' 63 | c.to = 'pdf' 64 | c.content = erb(params[:name].to_sym, layout: :pdf) 65 | end 66 | content_type 'application/pdf' 67 | return res 68 | rescue Exception => e 69 | @error = e 70 | erb :error 71 | end 72 | end 73 | 74 | get '/' do 75 | index_report = LedgerWeb::Config.instance.get :index_report 76 | if index_report 77 | redirect "/reports/#{index_report.to_s}" 78 | else 79 | redirect '/help' 80 | end 81 | end 82 | 83 | get '/help' do 84 | erb :help 85 | end 86 | end 87 | 88 | end 89 | -------------------------------------------------------------------------------- /lib/ledger_web/config.rb: -------------------------------------------------------------------------------- 1 | module LedgerWeb 2 | 3 | class Config 4 | attr_reader :vars, :hooks 5 | 6 | @@should_load_user_config = true 7 | @@instance = nil 8 | 9 | def self.should_load_user_config 10 | @@should_load_user_config 11 | end 12 | 13 | def self.should_load_user_config=(val) 14 | @@should_load_user_config = val 15 | end 16 | 17 | def initialize 18 | @vars = {} 19 | @hooks = {} 20 | 21 | if block_given? 22 | yield self 23 | end 24 | end 25 | 26 | def set(key, value) 27 | @vars[key] = value 28 | end 29 | 30 | def get(key) 31 | @vars[key] 32 | end 33 | 34 | def add_hook(phase, &block) 35 | _add_hook(phase, block) 36 | end 37 | 38 | def _add_hook(phase, hook) 39 | @hooks[phase] ||= [] 40 | @hooks[phase] << hook 41 | end 42 | 43 | def run_hooks(phase, data) 44 | if @hooks.has_key? phase 45 | @hooks[phase].each do |hook| 46 | hook.call(data) 47 | end 48 | end 49 | return data 50 | end 51 | 52 | def override_with(config) 53 | config.vars.each do |key, value| 54 | set key, value 55 | end 56 | 57 | config.hooks.each do |phase, hooks| 58 | hooks.each do |hook| 59 | _add_hook phase, hook 60 | end 61 | end 62 | end 63 | 64 | def load_user_config(user_dir) 65 | if LedgerWeb::Config.should_load_user_config && File.directory?(user_dir) 66 | if File.directory? "#{user_dir}/reports" 67 | dirs = self.get(:report_directories) 68 | dirs.unshift "#{user_dir}/reports" 69 | self.set :report_directories, dirs 70 | end 71 | 72 | if File.directory? "#{user_dir}/migrate" 73 | self.set :user_migrate_dir, "#{user_dir}/migrate" 74 | end 75 | 76 | if File.exists? "#{user_dir}/config.rb" 77 | self.override_with(LedgerWeb::Config.from_file("#{user_dir}/config.rb")) 78 | end 79 | end 80 | end 81 | 82 | def self.from_file(filename) 83 | File.open(filename) do |file| 84 | return eval(file.read, nil, filename) 85 | end 86 | end 87 | 88 | def self.instance 89 | @@instance ||= LedgerWeb::Config.new do |config| 90 | config.set :database_url, "postgres://localhost/ledger" 91 | config.set :port, "9090" 92 | config.set :ledger_file, ENV['LEDGER_FILE'] 93 | config.set :report_directories, ["#{File.dirname(__FILE__)}/reports"] 94 | config.set :additional_view_directories, [] 95 | config.set :session_secret, 'SomethingSecretThisWayPassed' 96 | config.set :session_expire, 60*60 97 | config.set :watch_interval, 5 98 | config.set :watch_stable_count, 3 99 | config.set :ledger_bin_path, "ledger" 100 | 101 | config.set :ledger_format, "%(quoted(xact.beg_line)),%(quoted(date)),%(quoted(payee)),%(quoted(account)),%(quoted(commodity)),%(quoted(quantity(scrub(display_amount)))),%(quoted(cleared)),%(quoted(virtual)),%(quoted(join(note | xact.note))),%(quoted(cost))\n" 102 | config.set :ledger_columns, [ :xtn_id, :xtn_date, :note, :account, :commodity, :amount, :cleared, :virtual, :tags, :cost ] 103 | 104 | config.set :price_lookup_skip_symbols, ['$'] 105 | 106 | func = Proc.new do |symbol, min_date, max_date| 107 | LedgerWeb::YahooPriceLookup.new(symbol, min_date - 1, max_date).lookup 108 | end 109 | config.set :price_function, func 110 | end 111 | end 112 | end 113 | end 114 | -------------------------------------------------------------------------------- /lib/ledger_web/db.rb: -------------------------------------------------------------------------------- 1 | require 'sequel' 2 | require 'sequel/extensions/migration' 3 | require 'csv' 4 | require 'tempfile' 5 | 6 | module LedgerWeb 7 | class Database 8 | 9 | def self.connect 10 | @@db = Sequel.connect(LedgerWeb::Config.instance.get(:database_url)) 11 | self.run_migrations() 12 | end 13 | 14 | def self.close 15 | @@db.disconnect 16 | end 17 | 18 | def self.handle 19 | @@db 20 | end 21 | 22 | def self.run_migrations 23 | Sequel::Migrator.apply(@@db, File.join(File.dirname(__FILE__), "db/migrate")) 24 | 25 | user_migrations = LedgerWeb::Config.instance.get :user_migrate_dir 26 | if not user_migrations.nil? 27 | Sequel::Migrator.run(@@db, user_migrations, :table => "user_schema_changes") 28 | end 29 | end 30 | 31 | def self.dump_ledger_to_csv 32 | ledger_bin_path = LedgerWeb::Config.instance.get :ledger_bin_path 33 | ledger_file = LedgerWeb::Config.instance.get :ledger_file 34 | ledger_format = LedgerWeb::Config.instance.get :ledger_format 35 | 36 | puts "Dumping ledger to file..." 37 | file = Tempfile.new('ledger') 38 | system "#{ledger_bin_path} -f #{ledger_file} --format='#{ledger_format}' reg > #{file.path}" 39 | replaced_file = Tempfile.new('ledger') 40 | replaced_file.write(file.read.gsub('\"', '""')) 41 | replaced_file.flush 42 | 43 | puts "Dump finished" 44 | return replaced_file 45 | end 46 | 47 | def self.load_database(file) 48 | counter = 0 49 | @@db.transaction do 50 | 51 | LedgerWeb::Config.instance.run_hooks(:before_load, @@db) 52 | 53 | puts "Clearing ledger table...." 54 | @@db["DELETE FROM ledger"].delete 55 | puts "Done clearing ledger table" 56 | 57 | puts "Loading into database...." 58 | 59 | ledger_columns = LedgerWeb::Config.instance.get :ledger_columns 60 | 61 | CSV.foreach(file.path) do |row| 62 | counter += 1 63 | row = Hash[*ledger_columns.zip(row).flatten] 64 | 65 | xtn_date = Date.strptime(row[:xtn_date], '%Y/%m/%d') 66 | 67 | row[:xtn_month] = xtn_date.strftime('%Y/%m/01') 68 | row[:xtn_year] = xtn_date.strftime('%Y/01/01') 69 | row[:cost] = parse_cost(row[:cost]) 70 | 71 | row = LedgerWeb::Config.instance.run_hooks(:before_insert_row, row) 72 | @@db[:ledger].insert(row) 73 | LedgerWeb::Config.instance.run_hooks(:after_insert_row, row) 74 | end 75 | 76 | puts "Running after_load hooks" 77 | LedgerWeb::Config.instance.run_hooks(:after_load, @@db) 78 | end 79 | puts "Analyzing ledger table" 80 | @@db.fetch('VACUUM ANALYZE ledger').all 81 | puts "Done!" 82 | counter 83 | end 84 | 85 | def self.parse_cost(cost) 86 | match = cost.match(/([\d\.-]+) (.+) {(.+)} \[(.+)\]/) 87 | if match 88 | amount = match[1].to_f 89 | price = match[3].gsub(/[^\d\.-]/, '').to_f 90 | return price * amount 91 | end 92 | cost.gsub(/[^\d\.-]/, '') 93 | end 94 | 95 | def self.load_prices 96 | query = < row[:commodity], :price_date => price[0], :price => price[1]) 133 | end 134 | end 135 | @@db.fetch("analyze prices").all 136 | puts "Done loading prices" 137 | end 138 | end 139 | end 140 | 141 | -------------------------------------------------------------------------------- /lib/ledger_web/db/migrate/20111226180900_initial_schema.rb: -------------------------------------------------------------------------------- 1 | Sequel.migration do 2 | up do 3 | create_table(:ledger, :ignore_index_errors=>true) do 4 | Date :xtn_date 5 | String :checknum, :text=>true 6 | String :note, :text=>true 7 | String :account, :text=>true 8 | String :commodity, :text=>true 9 | BigDecimal :amount 10 | String :tags, :text=>true 11 | Date :xtn_month 12 | Date :xtn_year 13 | TrueClass :virtual 14 | Integer :xtn_id 15 | TrueClass :cleared 16 | 17 | index [:account] 18 | index [:commodity] 19 | index [:note] 20 | index [:tags] 21 | index [:virtual] 22 | index [:xtn_date] 23 | index [:xtn_month] 24 | index [:xtn_year] 25 | end 26 | 27 | create_table(:schema_info) do 28 | String :filename, :text=>true, :null=>false 29 | 30 | primary_key [:filename] 31 | end 32 | end 33 | 34 | down do 35 | drop_table(:ledger, :schema_info) 36 | end 37 | end 38 | 39 | -------------------------------------------------------------------------------- /lib/ledger_web/db/migrate/20111231132900_add_views.rb: -------------------------------------------------------------------------------- 1 | Sequel.migration do 2 | change do 3 | create_or_replace_view(:accounts_days, < true 6 | BigDecimal :price 7 | 8 | index [:price_date] 9 | index [:commodity] 10 | index [:price_date, :commodity] 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/ledger_web/decorators.rb: -------------------------------------------------------------------------------- 1 | require 'cgi' 2 | 3 | module LedgerWeb::Decorators 4 | class NumberDecorator 5 | def initialize(precision=2) 6 | @precision = precision 7 | end 8 | 9 | def decorate(cell, row) 10 | if cell.value.is_a?(Numeric) 11 | cell.align = 'right' 12 | cell.text = sprintf("%0.#{@precision}f", cell.value) 13 | end 14 | cell 15 | end 16 | end 17 | 18 | class LinkDecorator 19 | def initialize(href_pattern) 20 | @href_pattern = href_pattern 21 | end 22 | 23 | def decorate(cell, row) 24 | url = String.new(@href_pattern) 25 | row.each_with_index do |c,i| 26 | url.gsub!(":#{i}", CGI.escape(c.value.to_s)) 27 | end 28 | url.gsub!(':title', CGI.escape(cell.title.to_s)) 29 | url.gsub!(':now', CGI.escape(DateTime.now.strftime('%Y-%m-%d'))) 30 | url.gsub!(':this', CGI.escape(cell.value.to_s)) 31 | prev_text = cell.text 32 | cell.text = "#{cell.text}" 33 | cell 34 | end 35 | end 36 | 37 | class IconDecorator 38 | def initialize(icon) 39 | @icon = icon 40 | end 41 | 42 | def decorate(cell, row) 43 | cell.text = "" 44 | cell 45 | end 46 | end 47 | 48 | class HighlightDecorator 49 | def initialize(color) 50 | @color = color 51 | end 52 | 53 | def decorate(cell, row) 54 | cell.style['background-color'] = @color 55 | cell 56 | end 57 | end 58 | 59 | end 60 | 61 | 62 | -------------------------------------------------------------------------------- /lib/ledger_web/helpers.rb: -------------------------------------------------------------------------------- 1 | require 'rack/utils' 2 | require 'cgi' 3 | 4 | module LedgerWeb 5 | module Helpers 6 | 7 | include Rack::Utils 8 | 9 | def partial (template, locals = {}) 10 | erb(template, :layout => false, :locals => locals) 11 | end 12 | 13 | def table(report, options = {}) 14 | Table.new(report) do |t| 15 | t.decorate :all => LedgerWeb::Decorators::NumberDecorator.new 16 | t.attributes[:class] = 'table table-striped table-hover table-bordered table-condensed' 17 | yield t if block_given? 18 | end.render 19 | end 20 | 21 | def query(options={}, &block) 22 | q = capture(&block) 23 | report = Report.from_query(q) 24 | if options[:pivot] 25 | report = report.pivot(options[:pivot], options[:pivot_sort_order]) 26 | end 27 | return report 28 | end 29 | 30 | def expect(expected) 31 | not_present = [] 32 | expected.each do |key| 33 | if not params.has_key? key 34 | not_present << key 35 | end 36 | end 37 | 38 | if not_present.length > 0 39 | raise "Missing params: #{not_present.join(', ')}" 40 | end 41 | end 42 | 43 | def default(key, value) 44 | if not Report.params.has_key? key 45 | puts "Setting #{key} to #{value}" 46 | Report.params[key] = value 47 | end 48 | end 49 | 50 | def visualization(report, options={}, &block) 51 | vis = capture(&block) 52 | @vis_count ||= 0 53 | @vis_count += 1 54 | @_out_buf.concat( 55 | partial( 56 | :visualization, 57 | :report => report, 58 | :visualization_code => vis, 59 | :div_id => "vis_#{@vis_count}" 60 | ) 61 | ) 62 | end 63 | end 64 | end 65 | 66 | -------------------------------------------------------------------------------- /lib/ledger_web/price_lookup.rb: -------------------------------------------------------------------------------- 1 | require 'csv' 2 | require 'uri' 3 | require 'net/http' 4 | 5 | module LedgerWeb 6 | class YahooPriceLookup 7 | def initialize(symbol, min_date, max_date) 8 | @symbol = symbol.gsub(/"/,'') 9 | @min_date = min_date 10 | @max_date = max_date 11 | end 12 | 13 | def lookup 14 | params = { 15 | 'a' => @min_date.month - 1, 16 | 'b' => @min_date.day, 17 | 'c' => @min_date.year, 18 | 'd' => @max_date.month - 1, 19 | 'e' => @max_date.day, 20 | 'f' => @max_date.year, 21 | 's' => @symbol, 22 | 'ignore' => '.csv', 23 | } 24 | 25 | query = params.map { |k,v| "#{k}=#{v}" }.join("&") 26 | uri = URI.parse("http://ichart.finance.yahoo.com/table.csv?#{query}") 27 | response = Net::HTTP.get_response(uri) 28 | 29 | if response.code != '200' 30 | return [] 31 | end 32 | 33 | rows = [] 34 | CSV.parse(response.body, :headers => true) do |row| 35 | rows << [Date.parse(row["Date"]), row["Close"].to_f] 36 | end 37 | rows 38 | end 39 | end 40 | end 41 | 42 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/keymap/emacs.js: -------------------------------------------------------------------------------- 1 | // TODO number prefixes 2 | (function() { 3 | // Really primitive kill-ring implementation. 4 | var killRing = []; 5 | function addToRing(str) { 6 | killRing.push(str); 7 | if (killRing.length > 50) killRing.shift(); 8 | } 9 | function getFromRing() { return killRing[killRing.length - 1] || ""; } 10 | function popFromRing() { if (killRing.length > 1) killRing.pop(); return getFromRing(); } 11 | 12 | CodeMirror.keyMap.emacs = { 13 | "Ctrl-X": function(cm) {cm.setOption("keyMap", "emacs-Ctrl-X");}, 14 | "Ctrl-W": function(cm) {addToRing(cm.getSelection()); cm.replaceSelection("");}, 15 | "Ctrl-Alt-W": function(cm) {addToRing(cm.getSelection()); cm.replaceSelection("");}, 16 | "Alt-W": function(cm) {addToRing(cm.getSelection());}, 17 | "Ctrl-Y": function(cm) {cm.replaceSelection(getFromRing());}, 18 | "Alt-Y": function(cm) {cm.replaceSelection(popFromRing());}, 19 | "Ctrl-/": "undo", "Shift-Ctrl--": "undo", "Shift-Alt-,": "goDocStart", "Shift-Alt-.": "goDocEnd", 20 | "Ctrl-S": "findNext", "Ctrl-R": "findPrev", "Ctrl-G": "clearSearch", "Shift-Alt-5": "replace", 21 | "Ctrl-Z": "undo", "Cmd-Z": "undo", 22 | fallthrough: ["basic", "emacsy"] 23 | }; 24 | 25 | CodeMirror.keyMap["emacs-Ctrl-X"] = { 26 | "Ctrl-S": "save", "Ctrl-W": "save", "S": "saveAll", "F": "open", "U": "undo", "K": "close", 27 | auto: "emacs", catchall: function(cm) {/*ignore*/} 28 | }; 29 | })(); 30 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/keymap/vim.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var count = ""; 3 | function pushCountDigit(digit) { return function(cm) {count += digit;} } 4 | function popCount() { var i = parseInt(count); count = ""; return i || 1; } 5 | function countTimes(func) { 6 | if (typeof func == "string") func = CodeMirror.commands[func]; 7 | return function(cm) { for (var i = 0, c = popCount(); i < c; ++i) func(cm); } 8 | } 9 | 10 | function iterObj(o, f) { 11 | for (var prop in o) if (o.hasOwnProperty(prop)) f(prop, o[prop]); 12 | } 13 | 14 | var word = [/\w/, /[^\w\s]/], bigWord = [/\S/]; 15 | function findWord(line, pos, dir, regexps) { 16 | var stop = 0, next = -1; 17 | if (dir > 0) { stop = line.length; next = 0; } 18 | var start = stop, end = stop; 19 | // Find bounds of next one. 20 | outer: for (; pos != stop; pos += dir) { 21 | for (var i = 0; i < regexps.length; ++i) { 22 | if (regexps[i].test(line.charAt(pos + next))) { 23 | start = pos; 24 | for (; pos != stop; pos += dir) { 25 | if (!regexps[i].test(line.charAt(pos + next))) break; 26 | } 27 | end = pos; 28 | break outer; 29 | } 30 | } 31 | } 32 | return {from: Math.min(start, end), to: Math.max(start, end)}; 33 | } 34 | function moveToWord(cm, regexps, dir, where) { 35 | var cur = cm.getCursor(), ch = cur.ch, line = cm.getLine(cur.line), word; 36 | while (true) { 37 | word = findWord(line, ch, dir, regexps); 38 | ch = word[where == "end" ? "to" : "from"]; 39 | if (ch == cur.ch && word.from != word.to) ch = word[dir < 0 ? "from" : "to"]; 40 | else break; 41 | } 42 | cm.setCursor(cur.line, word[where == "end" ? "to" : "from"], true); 43 | } 44 | 45 | var map = CodeMirror.keyMap.vim = { 46 | "0": function(cm) {count.length > 0 ? pushCountDigit("0")(cm) : CodeMirror.commands.goLineStart(cm);}, 47 | "I": function(cm) {popCount(); cm.setOption("keyMap", "vim-insert");}, 48 | "G": function(cm) {cm.setOption("keyMap", "vim-prefix-g");}, 49 | catchall: function(cm) {/*ignore*/} 50 | }; 51 | // Add bindings for number keys 52 | for (var i = 1; i < 10; ++i) map[i] = pushCountDigit(i); 53 | // Add bindings that are influenced by number keys 54 | iterObj({"H": "goColumnLeft", "L": "goColumnRight", "J": "goLineDown", "K": "goLineUp", 55 | "Left": "goColumnLeft", "Right": "goColumnRight", "Down": "goLineDown", "Up": "goLineUp", 56 | "Backspace": "goCharLeft", "Space": "goCharRight", 57 | "B": function(cm) {moveToWord(cm, word, -1, "end");}, 58 | "E": function(cm) {moveToWord(cm, word, 1, "end");}, 59 | "W": function(cm) {moveToWord(cm, word, 1, "start");}, 60 | "Shift-B": function(cm) {moveToWord(cm, bigWord, -1, "end");}, 61 | "Shift-E": function(cm) {moveToWord(cm, bigWord, 1, "end");}, 62 | "Shift-W": function(cm) {moveToWord(cm, bigWord, 1, "start");}, 63 | "U": "undo", "Ctrl-R": "redo", "Shift-4": "goLineEnd"}, 64 | function(key, cmd) { map[key] = countTimes(cmd); }); 65 | 66 | CodeMirror.keyMap["vim-prefix-g"] = { 67 | "E": countTimes(function(cm) { moveToWord(cm, word, -1, "start");}), 68 | "Shift-E": countTimes(function(cm) { moveToWord(cm, bigWord, -1, "start");}), 69 | auto: "vim", catchall: function(cm) {/*ignore*/} 70 | }; 71 | 72 | CodeMirror.keyMap["vim-insert"] = { 73 | "Esc": function(cm) {cm.setOption("keyMap", "vim");}, 74 | fallthrough: ["default"] 75 | }; 76 | })(); 77 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/lib/codemirror.css: -------------------------------------------------------------------------------- 1 | .CodeMirror { 2 | line-height: 1em; 3 | font-family: monospace; 4 | } 5 | 6 | .CodeMirror-scroll { 7 | overflow: auto; 8 | height: 300px; 9 | /* This is needed to prevent an IE[67] bug where the scrolled content 10 | is visible outside of the scrolling box. */ 11 | position: relative; 12 | } 13 | 14 | .CodeMirror-gutter { 15 | position: absolute; left: 0; top: 0; 16 | z-index: 10; 17 | background-color: #f7f7f7; 18 | border-right: 1px solid #eee; 19 | min-width: 2em; 20 | height: 100%; 21 | } 22 | .CodeMirror-gutter-text { 23 | color: #aaa; 24 | text-align: right; 25 | padding: .4em .2em .4em .4em; 26 | white-space: pre !important; 27 | } 28 | .CodeMirror-lines { 29 | padding: .4em; 30 | } 31 | 32 | .CodeMirror pre { 33 | -moz-border-radius: 0; 34 | -webkit-border-radius: 0; 35 | -o-border-radius: 0; 36 | border-radius: 0; 37 | border-width: 0; margin: 0; padding: 0; background: transparent; 38 | font-family: inherit; 39 | font-size: inherit; 40 | padding: 0; margin: 0; 41 | white-space: pre; 42 | word-wrap: normal; 43 | } 44 | 45 | .CodeMirror-wrap pre { 46 | word-wrap: break-word; 47 | white-space: pre-wrap; 48 | } 49 | .CodeMirror-wrap .CodeMirror-scroll { 50 | overflow-x: hidden; 51 | } 52 | 53 | .CodeMirror textarea { 54 | outline: none !important; 55 | } 56 | 57 | .CodeMirror pre.CodeMirror-cursor { 58 | z-index: 10; 59 | position: absolute; 60 | visibility: hidden; 61 | border-left: 1px solid black; 62 | } 63 | .CodeMirror-focused pre.CodeMirror-cursor { 64 | visibility: visible; 65 | } 66 | 67 | span.CodeMirror-selected { background: #d9d9d9; } 68 | .CodeMirror-focused span.CodeMirror-selected { background: #d2dcf8; } 69 | 70 | .CodeMirror-searching {background: #ffa;} 71 | 72 | /* Default theme */ 73 | 74 | .cm-s-default span.cm-keyword {color: #708;} 75 | .cm-s-default span.cm-atom {color: #219;} 76 | .cm-s-default span.cm-number {color: #164;} 77 | .cm-s-default span.cm-def {color: #00f;} 78 | .cm-s-default span.cm-variable {color: black;} 79 | .cm-s-default span.cm-variable-2 {color: #05a;} 80 | .cm-s-default span.cm-variable-3 {color: #085;} 81 | .cm-s-default span.cm-property {color: black;} 82 | .cm-s-default span.cm-operator {color: black;} 83 | .cm-s-default span.cm-comment {color: #a50;} 84 | .cm-s-default span.cm-string {color: #a11;} 85 | .cm-s-default span.cm-string-2 {color: #f50;} 86 | .cm-s-default span.cm-meta {color: #555;} 87 | .cm-s-default span.cm-error {color: #f00;} 88 | .cm-s-default span.cm-qualifier {color: #555;} 89 | .cm-s-default span.cm-builtin {color: #30a;} 90 | .cm-s-default span.cm-bracket {color: #cc7;} 91 | .cm-s-default span.cm-tag {color: #170;} 92 | .cm-s-default span.cm-attribute {color: #00c;} 93 | .cm-s-default span.cm-header {color: #a0a;} 94 | .cm-s-default span.cm-quote {color: #090;} 95 | .cm-s-default span.cm-hr {color: #999;} 96 | .cm-s-default span.cm-link {color: #00c;} 97 | 98 | span.cm-header, span.cm-strong {font-weight: bold;} 99 | span.cm-em {font-style: italic;} 100 | span.cm-emstrong {font-style: italic; font-weight: bold;} 101 | span.cm-link {text-decoration: underline;} 102 | 103 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} 104 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} 105 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/lib/util/dialog.css: -------------------------------------------------------------------------------- 1 | .CodeMirror-dialog { 2 | position: relative; 3 | } 4 | 5 | .CodeMirror-dialog > div { 6 | position: absolute; 7 | top: 0; left: 0; right: 0; 8 | background: white; 9 | border-bottom: 1px solid #eee; 10 | z-index: 15; 11 | padding: .1em .8em; 12 | overflow: hidden; 13 | color: #333; 14 | } 15 | 16 | .CodeMirror-dialog input { 17 | border: none; 18 | outline: none; 19 | background: transparent; 20 | width: 20em; 21 | color: inherit; 22 | font-family: monospace; 23 | } 24 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/lib/util/dialog.js: -------------------------------------------------------------------------------- 1 | // Open simple dialogs on top of an editor. Relies on dialog.css. 2 | 3 | (function() { 4 | function dialogDiv(cm, template) { 5 | var wrap = cm.getWrapperElement(); 6 | var dialog = wrap.insertBefore(document.createElement("div"), wrap.firstChild); 7 | dialog.className = "CodeMirror-dialog"; 8 | dialog.innerHTML = '
' + template + '
'; 9 | return dialog; 10 | } 11 | 12 | CodeMirror.defineExtension("openDialog", function(template, callback) { 13 | var dialog = dialogDiv(this, template); 14 | var closed = false, me = this; 15 | function close() { 16 | if (closed) return; 17 | closed = true; 18 | dialog.parentNode.removeChild(dialog); 19 | } 20 | var inp = dialog.getElementsByTagName("input")[0]; 21 | if (inp) { 22 | CodeMirror.connect(inp, "keydown", function(e) { 23 | if (e.keyCode == 13 || e.keyCode == 27) { 24 | CodeMirror.e_stop(e); 25 | close(); 26 | me.focus(); 27 | if (e.keyCode == 13) callback(inp.value); 28 | } 29 | }); 30 | inp.focus(); 31 | CodeMirror.connect(inp, "blur", close); 32 | } 33 | return close; 34 | }); 35 | 36 | CodeMirror.defineExtension("openConfirm", function(template, callbacks) { 37 | var dialog = dialogDiv(this, template); 38 | var buttons = dialog.getElementsByTagName("button"); 39 | var closed = false, me = this, blurring = 1; 40 | function close() { 41 | if (closed) return; 42 | closed = true; 43 | dialog.parentNode.removeChild(dialog); 44 | me.focus(); 45 | } 46 | buttons[0].focus(); 47 | for (var i = 0; i < buttons.length; ++i) { 48 | var b = buttons[i]; 49 | (function(callback) { 50 | CodeMirror.connect(b, "click", function(e) { 51 | CodeMirror.e_preventDefault(e); 52 | close(); 53 | if (callback) callback(me); 54 | }); 55 | })(callbacks[i]); 56 | CodeMirror.connect(b, "blur", function() { 57 | --blurring; 58 | setTimeout(function() { if (blurring <= 0) close(); }, 200); 59 | }); 60 | CodeMirror.connect(b, "focus", function() { ++blurring; }); 61 | } 62 | }); 63 | })(); -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/lib/util/foldcode.js: -------------------------------------------------------------------------------- 1 | CodeMirror.braceRangeFinder = function(cm, line) { 2 | var lineText = cm.getLine(line); 3 | var startChar = lineText.lastIndexOf("{"); 4 | if (startChar < 0 || lineText.lastIndexOf("}") > startChar) return; 5 | var tokenType = cm.getTokenAt({line: line, ch: startChar}).className; 6 | var count = 1, lastLine = cm.lineCount(), end; 7 | outer: for (var i = line + 1; i < lastLine; ++i) { 8 | var text = cm.getLine(i), pos = 0; 9 | for (;;) { 10 | var nextOpen = text.indexOf("{", pos), nextClose = text.indexOf("}", pos); 11 | if (nextOpen < 0) nextOpen = text.length; 12 | if (nextClose < 0) nextClose = text.length; 13 | pos = Math.min(nextOpen, nextClose); 14 | if (pos == text.length) break; 15 | if (cm.getTokenAt({line: i, ch: pos + 1}).className == tokenType) { 16 | if (pos == nextOpen) ++count; 17 | else if (!--count) { end = i; break outer; } 18 | } 19 | ++pos; 20 | } 21 | } 22 | if (end == null || end == line + 1) return; 23 | return end; 24 | }; 25 | 26 | 27 | CodeMirror.newFoldFunction = function(rangeFinder, markText) { 28 | var folded = []; 29 | if (markText == null) markText = '
%N%'; 30 | 31 | function isFolded(cm, n) { 32 | for (var i = 0; i < folded.length; ++i) { 33 | var start = cm.lineInfo(folded[i].start); 34 | if (!start) folded.splice(i--, 1); 35 | else if (start.line == n) return {pos: i, region: folded[i]}; 36 | } 37 | } 38 | 39 | function expand(cm, region) { 40 | cm.clearMarker(region.start); 41 | for (var i = 0; i < region.hidden.length; ++i) 42 | cm.showLine(region.hidden[i]); 43 | } 44 | 45 | return function(cm, line) { 46 | cm.operation(function() { 47 | var known = isFolded(cm, line); 48 | if (known) { 49 | folded.splice(known.pos, 1); 50 | expand(cm, known.region); 51 | } else { 52 | var end = rangeFinder(cm, line); 53 | if (end == null) return; 54 | var hidden = []; 55 | for (var i = line + 1; i < end; ++i) { 56 | var handle = cm.hideLine(i); 57 | if (handle) hidden.push(handle); 58 | } 59 | var first = cm.setMarker(line, markText); 60 | var region = {start: first, hidden: hidden}; 61 | cm.onDeleteLine(first, function() { expand(cm, region); }); 62 | folded.push(region); 63 | } 64 | }); 65 | }; 66 | }; 67 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/lib/util/javascript-hint.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | function forEach(arr, f) { 3 | for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]); 4 | } 5 | 6 | function arrayContains(arr, item) { 7 | if (!Array.prototype.indexOf) { 8 | var i = arr.length; 9 | while (i--) { 10 | if (arr[i] === item) { 11 | return true; 12 | } 13 | } 14 | return false; 15 | } 16 | return arr.indexOf(item) != -1; 17 | } 18 | 19 | CodeMirror.javascriptHint = function(editor) { 20 | // Find the token at the cursor 21 | var cur = editor.getCursor(), token = editor.getTokenAt(cur), tprop = token; 22 | // If it's not a 'word-style' token, ignore the token. 23 | if (!/^[\w$_]*$/.test(token.string)) { 24 | token = tprop = {start: cur.ch, end: cur.ch, string: "", state: token.state, 25 | className: token.string == "." ? "property" : null}; 26 | } 27 | // If it is a property, find out what it is a property of. 28 | while (tprop.className == "property") { 29 | tprop = editor.getTokenAt({line: cur.line, ch: tprop.start}); 30 | if (tprop.string != ".") return; 31 | tprop = editor.getTokenAt({line: cur.line, ch: tprop.start}); 32 | if (!context) var context = []; 33 | context.push(tprop); 34 | } 35 | return {list: getCompletions(token, context), 36 | from: {line: cur.line, ch: token.start}, 37 | to: {line: cur.line, ch: token.end}}; 38 | } 39 | 40 | var stringProps = ("charAt charCodeAt indexOf lastIndexOf substring substr slice trim trimLeft trimRight " + 41 | "toUpperCase toLowerCase split concat match replace search").split(" "); 42 | var arrayProps = ("length concat join splice push pop shift unshift slice reverse sort indexOf " + 43 | "lastIndexOf every some filter forEach map reduce reduceRight ").split(" "); 44 | var funcProps = "prototype apply call bind".split(" "); 45 | var keywords = ("break case catch continue debugger default delete do else false finally for function " + 46 | "if in instanceof new null return switch throw true try typeof var void while with").split(" "); 47 | 48 | function getCompletions(token, context) { 49 | var found = [], start = token.string; 50 | function maybeAdd(str) { 51 | if (str.indexOf(start) == 0 && !arrayContains(found, str)) found.push(str); 52 | } 53 | function gatherCompletions(obj) { 54 | if (typeof obj == "string") forEach(stringProps, maybeAdd); 55 | else if (obj instanceof Array) forEach(arrayProps, maybeAdd); 56 | else if (obj instanceof Function) forEach(funcProps, maybeAdd); 57 | for (var name in obj) maybeAdd(name); 58 | } 59 | 60 | if (context) { 61 | // If this is a property, see if it belongs to some object we can 62 | // find in the current environment. 63 | var obj = context.pop(), base; 64 | if (obj.className == "variable") 65 | base = window[obj.string]; 66 | else if (obj.className == "string") 67 | base = ""; 68 | else if (obj.className == "atom") 69 | base = 1; 70 | while (base != null && context.length) 71 | base = base[context.pop().string]; 72 | if (base != null) gatherCompletions(base); 73 | } 74 | else { 75 | // If not, just look in the window object and any local scope 76 | // (reading into JS mode internals to get at the local variables) 77 | for (var v = token.state.localVars; v; v = v.next) maybeAdd(v.name); 78 | gatherCompletions(window); 79 | forEach(keywords, maybeAdd); 80 | } 81 | return found; 82 | } 83 | })(); 84 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/lib/util/overlay.js: -------------------------------------------------------------------------------- 1 | // Utility function that allows modes to be combined. The mode given 2 | // as the base argument takes care of most of the normal mode 3 | // functionality, but a second (typically simple) mode is used, which 4 | // can override the style of text. Both modes get to parse all of the 5 | // text, but when both assign a non-null style to a piece of code, the 6 | // overlay wins, unless the combine argument was true, in which case 7 | // the styles are combined. 8 | 9 | CodeMirror.overlayParser = function(base, overlay, combine) { 10 | return { 11 | startState: function() { 12 | return { 13 | base: CodeMirror.startState(base), 14 | overlay: CodeMirror.startState(overlay), 15 | basePos: 0, baseCur: null, 16 | overlayPos: 0, overlayCur: null 17 | }; 18 | }, 19 | copyState: function(state) { 20 | return { 21 | base: CodeMirror.copyState(base, state.base), 22 | overlay: CodeMirror.copyState(overlay, state.overlay), 23 | basePos: state.basePos, baseCur: null, 24 | overlayPos: state.overlayPos, overlayCur: null 25 | }; 26 | }, 27 | 28 | token: function(stream, state) { 29 | if (stream.start == state.basePos) { 30 | state.baseCur = base.token(stream, state.base); 31 | state.basePos = stream.pos; 32 | } 33 | if (stream.start == state.overlayPos) { 34 | stream.pos = stream.start; 35 | state.overlayCur = overlay.token(stream, state.overlay); 36 | state.overlayPos = stream.pos; 37 | } 38 | stream.pos = Math.min(state.basePos, state.overlayPos); 39 | if (stream.eol()) state.basePos = state.overlayPos = 0; 40 | 41 | if (state.overlayCur == null) return state.baseCur; 42 | if (state.baseCur != null && combine) return state.baseCur + " " + state.overlayCur; 43 | else return state.overlayCur; 44 | }, 45 | 46 | indent: function(state, textAfter) { 47 | return base.indent(state.base, textAfter); 48 | }, 49 | electricChars: base.electricChars 50 | }; 51 | }; 52 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/lib/util/runmode.js: -------------------------------------------------------------------------------- 1 | CodeMirror.runMode = function(string, modespec, callback) { 2 | var mode = CodeMirror.getMode({indentUnit: 2}, modespec); 3 | var isNode = callback.nodeType == 1; 4 | if (isNode) { 5 | var node = callback, accum = []; 6 | callback = function(string, style) { 7 | if (string == "\n") 8 | accum.push("
"); 9 | else if (style) 10 | accum.push("" + CodeMirror.htmlEscape(string) + ""); 11 | else 12 | accum.push(CodeMirror.htmlEscape(string)); 13 | } 14 | } 15 | var lines = CodeMirror.splitLines(string), state = CodeMirror.startState(mode); 16 | for (var i = 0, e = lines.length; i < e; ++i) { 17 | if (i) callback("\n"); 18 | var stream = new CodeMirror.StringStream(lines[i]); 19 | while (!stream.eol()) { 20 | var style = mode.token(stream, state); 21 | callback(stream.current(), style, i, stream.start); 22 | stream.start = stream.pos; 23 | } 24 | } 25 | if (isNode) 26 | node.innerHTML = accum.join(""); 27 | }; 28 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/lib/util/search.js: -------------------------------------------------------------------------------- 1 | // Define search commands. Depends on dialog.js or another 2 | // implementation of the openDialog method. 3 | 4 | // Replace works a little oddly -- it will do the replace on the next 5 | // Ctrl-G (or whatever is bound to findNext) press. You prevent a 6 | // replace by making sure the match is no longer selected when hitting 7 | // Ctrl-G. 8 | 9 | (function() { 10 | function SearchState() { 11 | this.posFrom = this.posTo = this.query = null; 12 | this.marked = []; 13 | } 14 | function getSearchState(cm) { 15 | return cm._searchState || (cm._searchState = new SearchState()); 16 | } 17 | function dialog(cm, text, shortText, f) { 18 | if (cm.openDialog) cm.openDialog(text, f); 19 | else f(prompt(shortText, "")); 20 | } 21 | function confirmDialog(cm, text, shortText, fs) { 22 | if (cm.openConfirm) cm.openConfirm(text, fs); 23 | else if (confirm(shortText)) fs[0](); 24 | } 25 | function parseQuery(query) { 26 | var isRE = query.match(/^\/(.*)\/$/); 27 | return isRE ? new RegExp(isRE[1]) : query; 28 | } 29 | var queryDialog = 30 | 'Search: (Use /re/ syntax for regexp search)'; 31 | function doSearch(cm, rev) { 32 | var state = getSearchState(cm); 33 | if (state.query) return findNext(cm, rev); 34 | dialog(cm, queryDialog, "Search for:", function(query) { 35 | cm.operation(function() { 36 | if (!query || state.query) return; 37 | state.query = parseQuery(query); 38 | if (cm.lineCount() < 2000) { // This is too expensive on big documents. 39 | for (var cursor = cm.getSearchCursor(query); cursor.findNext();) 40 | state.marked.push(cm.markText(cursor.from(), cursor.to(), "CodeMirror-searching")); 41 | } 42 | state.posFrom = state.posTo = cm.getCursor(); 43 | findNext(cm, rev); 44 | }); 45 | }); 46 | } 47 | function findNext(cm, rev) {cm.operation(function() { 48 | var state = getSearchState(cm); 49 | var cursor = cm.getSearchCursor(state.query, rev ? state.posFrom : state.posTo); 50 | if (!cursor.find(rev)) { 51 | cursor = cm.getSearchCursor(state.query, rev ? {line: cm.lineCount() - 1} : {line: 0, ch: 0}); 52 | if (!cursor.find(rev)) return; 53 | } 54 | cm.setSelection(cursor.from(), cursor.to()); 55 | state.posFrom = cursor.from(); state.posTo = cursor.to(); 56 | })} 57 | function clearSearch(cm) {cm.operation(function() { 58 | var state = getSearchState(cm); 59 | if (!state.query) return; 60 | state.query = null; 61 | for (var i = 0; i < state.marked.length; ++i) state.marked[i].clear(); 62 | state.marked.length = 0; 63 | })} 64 | 65 | var replaceQueryDialog = 66 | 'Replace: (Use /re/ syntax for regexp search)'; 67 | var replacementQueryDialog = 'With: '; 68 | var doReplaceConfirm = "Replace? "; 69 | function replace(cm, all) { 70 | dialog(cm, replaceQueryDialog, "Replace:", function(query) { 71 | if (!query) return; 72 | query = parseQuery(query); 73 | dialog(cm, replacementQueryDialog, "Replace with:", function(text) { 74 | if (all) { 75 | cm.operation(function() { 76 | for (var cursor = cm.getSearchCursor(query); cursor.findNext();) { 77 | if (typeof query != "string") { 78 | var match = cm.getRange(cursor.from(), cursor.to()).match(query); 79 | cursor.replace(text.replace(/\$(\d)/, function(w, i) {return match[i];})); 80 | } else cursor.replace(text); 81 | } 82 | }); 83 | } else { 84 | clearSearch(cm); 85 | var cursor = cm.getSearchCursor(query, cm.getCursor()); 86 | function advance() { 87 | var start = cursor.from(), match; 88 | if (!(match = cursor.findNext())) { 89 | cursor = cm.getSearchCursor(query); 90 | if (!(match = cursor.findNext()) || 91 | (cursor.from().line == start.line && cursor.from().ch == start.ch)) return; 92 | } 93 | cm.setSelection(cursor.from(), cursor.to()); 94 | confirmDialog(cm, doReplaceConfirm, "Replace?", 95 | [function() {doReplace(match);}, advance]); 96 | } 97 | function doReplace(match) { 98 | cursor.replace(typeof query == "string" ? text : 99 | text.replace(/\$(\d)/, function(w, i) {return match[i];})); 100 | advance(); 101 | } 102 | advance(); 103 | } 104 | }); 105 | }); 106 | } 107 | 108 | CodeMirror.commands.find = function(cm) {clearSearch(cm); doSearch(cm);}; 109 | CodeMirror.commands.findNext = doSearch; 110 | CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);}; 111 | CodeMirror.commands.clearSearch = clearSearch; 112 | CodeMirror.commands.replace = replace; 113 | CodeMirror.commands.replaceAll = function(cm) {replace(cm, true);}; 114 | })(); 115 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/lib/util/searchcursor.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | function SearchCursor(cm, query, pos, caseFold) { 3 | this.atOccurrence = false; this.cm = cm; 4 | if (caseFold == null) caseFold = typeof query == "string" && query == query.toLowerCase(); 5 | 6 | pos = pos ? cm.clipPos(pos) : {line: 0, ch: 0}; 7 | this.pos = {from: pos, to: pos}; 8 | 9 | // The matches method is filled in based on the type of query. 10 | // It takes a position and a direction, and returns an object 11 | // describing the next occurrence of the query, or null if no 12 | // more matches were found. 13 | if (typeof query != "string") // Regexp match 14 | this.matches = function(reverse, pos) { 15 | if (reverse) { 16 | var line = cm.getLine(pos.line).slice(0, pos.ch), match = line.match(query), start = 0; 17 | while (match) { 18 | var ind = line.indexOf(match[0]); 19 | start += ind; 20 | line = line.slice(ind + 1); 21 | var newmatch = line.match(query); 22 | if (newmatch) match = newmatch; 23 | else break; 24 | start++; 25 | } 26 | } 27 | else { 28 | var line = cm.getLine(pos.line).slice(pos.ch), match = line.match(query), 29 | start = match && pos.ch + line.indexOf(match[0]); 30 | } 31 | if (match) 32 | return {from: {line: pos.line, ch: start}, 33 | to: {line: pos.line, ch: start + match[0].length}, 34 | match: match}; 35 | }; 36 | else { // String query 37 | if (caseFold) query = query.toLowerCase(); 38 | var fold = caseFold ? function(str){return str.toLowerCase();} : function(str){return str;}; 39 | var target = query.split("\n"); 40 | // Different methods for single-line and multi-line queries 41 | if (target.length == 1) 42 | this.matches = function(reverse, pos) { 43 | var line = fold(cm.getLine(pos.line)), len = query.length, match; 44 | if (reverse ? (pos.ch >= len && (match = line.lastIndexOf(query, pos.ch - len)) != -1) 45 | : (match = line.indexOf(query, pos.ch)) != -1) 46 | return {from: {line: pos.line, ch: match}, 47 | to: {line: pos.line, ch: match + len}}; 48 | }; 49 | else 50 | this.matches = function(reverse, pos) { 51 | var ln = pos.line, idx = (reverse ? target.length - 1 : 0), match = target[idx], line = fold(cm.getLine(ln)); 52 | var offsetA = (reverse ? line.indexOf(match) + match.length : line.lastIndexOf(match)); 53 | if (reverse ? offsetA >= pos.ch || offsetA != match.length 54 | : offsetA <= pos.ch || offsetA != line.length - match.length) 55 | return; 56 | for (;;) { 57 | if (reverse ? !ln : ln == cm.lineCount() - 1) return; 58 | line = fold(cm.getLine(ln += reverse ? -1 : 1)); 59 | match = target[reverse ? --idx : ++idx]; 60 | if (idx > 0 && idx < target.length - 1) { 61 | if (line != match) return; 62 | else continue; 63 | } 64 | var offsetB = (reverse ? line.lastIndexOf(match) : line.indexOf(match) + match.length); 65 | if (reverse ? offsetB != line.length - match.length : offsetB != match.length) 66 | return; 67 | var start = {line: pos.line, ch: offsetA}, end = {line: ln, ch: offsetB}; 68 | return {from: reverse ? end : start, to: reverse ? start : end}; 69 | } 70 | }; 71 | } 72 | } 73 | 74 | SearchCursor.prototype = { 75 | findNext: function() {return this.find(false);}, 76 | findPrevious: function() {return this.find(true);}, 77 | 78 | find: function(reverse) { 79 | var self = this, pos = this.cm.clipPos(reverse ? this.pos.from : this.pos.to); 80 | function savePosAndFail(line) { 81 | var pos = {line: line, ch: 0}; 82 | self.pos = {from: pos, to: pos}; 83 | self.atOccurrence = false; 84 | return false; 85 | } 86 | 87 | for (;;) { 88 | if (this.pos = this.matches(reverse, pos)) { 89 | this.atOccurrence = true; 90 | return this.pos.match || true; 91 | } 92 | if (reverse) { 93 | if (!pos.line) return savePosAndFail(0); 94 | pos = {line: pos.line-1, ch: this.cm.getLine(pos.line-1).length}; 95 | } 96 | else { 97 | var maxLine = this.cm.lineCount(); 98 | if (pos.line == maxLine - 1) return savePosAndFail(maxLine); 99 | pos = {line: pos.line+1, ch: 0}; 100 | } 101 | } 102 | }, 103 | 104 | from: function() {if (this.atOccurrence) return this.pos.from;}, 105 | to: function() {if (this.atOccurrence) return this.pos.to;}, 106 | 107 | replace: function(newText) { 108 | var self = this; 109 | if (this.atOccurrence) 110 | self.pos.to = this.cm.replaceRange(newText, self.pos.from, self.pos.to); 111 | } 112 | }; 113 | 114 | CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) { 115 | return new SearchCursor(this, query, pos, caseFold); 116 | }); 117 | })(); 118 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/lib/util/simple-hint.css: -------------------------------------------------------------------------------- 1 | .CodeMirror-completions { 2 | position: absolute; 3 | z-index: 10; 4 | overflow: hidden; 5 | -webkit-box-shadow: 2px 3px 5px rgba(0,0,0,.2); 6 | -moz-box-shadow: 2px 3px 5px rgba(0,0,0,.2); 7 | box-shadow: 2px 3px 5px rgba(0,0,0,.2); 8 | } 9 | .CodeMirror-completions select { 10 | background: #fafafa; 11 | outline: none; 12 | border: none; 13 | padding: 0; 14 | margin: 0; 15 | font-family: monospace; 16 | } 17 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/lib/util/simple-hint.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | CodeMirror.simpleHint = function(editor, getHints) { 3 | // We want a single cursor position. 4 | if (editor.somethingSelected()) return; 5 | var result = getHints(editor); 6 | if (!result || !result.list.length) return; 7 | var completions = result.list; 8 | function insert(str) { 9 | editor.replaceRange(str, result.from, result.to); 10 | } 11 | // When there is only one completion, use it directly. 12 | if (completions.length == 1) {insert(completions[0]); return true;} 13 | 14 | // Build the select widget 15 | var complete = document.createElement("div"); 16 | complete.className = "CodeMirror-completions"; 17 | var sel = complete.appendChild(document.createElement("select")); 18 | // Opera doesn't move the selection when pressing up/down in a 19 | // multi-select, but it does properly support the size property on 20 | // single-selects, so no multi-select is necessary. 21 | if (!window.opera) sel.multiple = true; 22 | for (var i = 0; i < completions.length; ++i) { 23 | var opt = sel.appendChild(document.createElement("option")); 24 | opt.appendChild(document.createTextNode(completions[i])); 25 | } 26 | sel.firstChild.selected = true; 27 | sel.size = Math.min(10, completions.length); 28 | var pos = editor.cursorCoords(); 29 | complete.style.left = pos.x + "px"; 30 | complete.style.top = pos.yBot + "px"; 31 | document.body.appendChild(complete); 32 | // Hack to hide the scrollbar. 33 | if (completions.length <= 10) 34 | complete.style.width = (sel.clientWidth - 1) + "px"; 35 | 36 | var done = false; 37 | function close() { 38 | if (done) return; 39 | done = true; 40 | complete.parentNode.removeChild(complete); 41 | } 42 | function pick() { 43 | insert(completions[sel.selectedIndex]); 44 | close(); 45 | setTimeout(function(){editor.focus();}, 50); 46 | } 47 | CodeMirror.connect(sel, "blur", close); 48 | CodeMirror.connect(sel, "keydown", function(event) { 49 | var code = event.keyCode; 50 | // Enter 51 | if (code == 13) {CodeMirror.e_stop(event); pick();} 52 | // Escape 53 | else if (code == 27) {CodeMirror.e_stop(event); close(); editor.focus();} 54 | else if (code != 38 && code != 40) { 55 | close(); editor.focus(); 56 | setTimeout(function(){CodeMirror.simpleHint(editor, getHints);}, 50); 57 | } 58 | }); 59 | CodeMirror.connect(sel, "dblclick", pick); 60 | 61 | sel.focus(); 62 | // Opera sometimes ignores focusing a freshly created node 63 | if (window.opera) setTimeout(function(){if (!done) sel.focus();}, 100); 64 | return true; 65 | }; 66 | })(); 67 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/clike/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CodeMirror: C-like mode 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

CodeMirror: C-like mode

13 | 14 |
81 | 82 | 89 | 90 |

Simple mode that tries to handle C-like languages as well as it 91 | can. Takes two configuration parameters: keywords, an 92 | object whose property names are the keywords in the language, 93 | and useCPP, which determines whether C preprocessor 94 | directives are recognized.

95 | 96 |

MIME types defined: text/x-csrc 97 | (C code), text/x-c++src (C++ 98 | code), text/x-java (Java 99 | code), text/x-groovy (Groovy code).

100 | 101 | 102 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/clojure/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CodeMirror: Clojure mode 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

CodeMirror: Clojure mode

13 |
59 | 62 | 63 |

MIME types defined: text/x-clojure.

64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/coffeescript/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2011 Jeff Pickhardt 4 | Modified from the Python CodeMirror mode, Copyright (c) 2010 Timothy Farrell 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/css/css.js: -------------------------------------------------------------------------------- 1 | CodeMirror.defineMode("css", function(config) { 2 | var indentUnit = config.indentUnit, type; 3 | function ret(style, tp) {type = tp; return style;} 4 | 5 | function tokenBase(stream, state) { 6 | var ch = stream.next(); 7 | if (ch == "@") {stream.eatWhile(/[\w\\\-]/); return ret("meta", stream.current());} 8 | else if (ch == "/" && stream.eat("*")) { 9 | state.tokenize = tokenCComment; 10 | return tokenCComment(stream, state); 11 | } 12 | else if (ch == "<" && stream.eat("!")) { 13 | state.tokenize = tokenSGMLComment; 14 | return tokenSGMLComment(stream, state); 15 | } 16 | else if (ch == "=") ret(null, "compare"); 17 | else if ((ch == "~" || ch == "|") && stream.eat("=")) return ret(null, "compare"); 18 | else if (ch == "\"" || ch == "'") { 19 | state.tokenize = tokenString(ch); 20 | return state.tokenize(stream, state); 21 | } 22 | else if (ch == "#") { 23 | stream.eatWhile(/[\w\\\-]/); 24 | return ret("atom", "hash"); 25 | } 26 | else if (ch == "!") { 27 | stream.match(/^\s*\w*/); 28 | return ret("keyword", "important"); 29 | } 30 | else if (/\d/.test(ch)) { 31 | stream.eatWhile(/[\w.%]/); 32 | return ret("number", "unit"); 33 | } 34 | else if (/[,.+>*\/]/.test(ch)) { 35 | return ret(null, "select-op"); 36 | } 37 | else if (/[;{}:\[\]]/.test(ch)) { 38 | return ret(null, ch); 39 | } 40 | else { 41 | stream.eatWhile(/[\w\\\-]/); 42 | return ret("variable", "variable"); 43 | } 44 | } 45 | 46 | function tokenCComment(stream, state) { 47 | var maybeEnd = false, ch; 48 | while ((ch = stream.next()) != null) { 49 | if (maybeEnd && ch == "/") { 50 | state.tokenize = tokenBase; 51 | break; 52 | } 53 | maybeEnd = (ch == "*"); 54 | } 55 | return ret("comment", "comment"); 56 | } 57 | 58 | function tokenSGMLComment(stream, state) { 59 | var dashes = 0, ch; 60 | while ((ch = stream.next()) != null) { 61 | if (dashes >= 2 && ch == ">") { 62 | state.tokenize = tokenBase; 63 | break; 64 | } 65 | dashes = (ch == "-") ? dashes + 1 : 0; 66 | } 67 | return ret("comment", "comment"); 68 | } 69 | 70 | function tokenString(quote) { 71 | return function(stream, state) { 72 | var escaped = false, ch; 73 | while ((ch = stream.next()) != null) { 74 | if (ch == quote && !escaped) 75 | break; 76 | escaped = !escaped && ch == "\\"; 77 | } 78 | if (!escaped) state.tokenize = tokenBase; 79 | return ret("string", "string"); 80 | }; 81 | } 82 | 83 | return { 84 | startState: function(base) { 85 | return {tokenize: tokenBase, 86 | baseIndent: base || 0, 87 | stack: []}; 88 | }, 89 | 90 | token: function(stream, state) { 91 | if (stream.eatSpace()) return null; 92 | var style = state.tokenize(stream, state); 93 | 94 | var context = state.stack[state.stack.length-1]; 95 | if (type == "hash" && context == "rule") style = "atom"; 96 | else if (style == "variable") { 97 | if (context == "rule") style = "number"; 98 | else if (!context || context == "@media{") style = "tag"; 99 | } 100 | 101 | if (context == "rule" && /^[\{\};]$/.test(type)) 102 | state.stack.pop(); 103 | if (type == "{") { 104 | if (context == "@media") state.stack[state.stack.length-1] = "@media{"; 105 | else state.stack.push("{"); 106 | } 107 | else if (type == "}") state.stack.pop(); 108 | else if (type == "@media") state.stack.push("@media"); 109 | else if (context == "{" && type != "comment") state.stack.push("rule"); 110 | return style; 111 | }, 112 | 113 | indent: function(state, textAfter) { 114 | var n = state.stack.length; 115 | if (/^\}/.test(textAfter)) 116 | n -= state.stack[state.stack.length-1] == "rule" ? 2 : 1; 117 | return state.baseIndent + n * indentUnit; 118 | }, 119 | 120 | electricChars: "}" 121 | }; 122 | }); 123 | 124 | CodeMirror.defineMIME("text/css", "css"); 125 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/css/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CodeMirror: CSS mode 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

CodeMirror: CSS mode

13 |
48 | 51 | 52 |

MIME types defined: text/css.

53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/diff/diff.css: -------------------------------------------------------------------------------- 1 | span.cm-rangeinfo {color: #a0b;} 2 | span.cm-minus {color: red;} 3 | span.cm-plus {color: #2b2;} 4 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/diff/diff.js: -------------------------------------------------------------------------------- 1 | CodeMirror.defineMode("diff", function() { 2 | return { 3 | token: function(stream) { 4 | var ch = stream.next(); 5 | stream.skipToEnd(); 6 | if (ch == "+") return "plus"; 7 | if (ch == "-") return "minus"; 8 | if (ch == "@") return "rangeinfo"; 9 | } 10 | }; 11 | }); 12 | 13 | CodeMirror.defineMIME("text/x-diff", "diff"); 14 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/diff/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CodeMirror: Diff mode 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

CodeMirror: Diff mode

14 |
92 | 95 | 96 |

MIME types defined: text/x-diff.

97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/gfm/gfm.js: -------------------------------------------------------------------------------- 1 | CodeMirror.defineMode("gfm", function(config, parserConfig) { 2 | var mdMode = CodeMirror.getMode(config, "markdown"); 3 | var aliases = { 4 | html: "htmlmixed", 5 | js: "javascript", 6 | json: "application/json", 7 | c: "text/x-csrc", 8 | "c++": "text/x-c++src", 9 | java: "text/x-java", 10 | csharp: "text/x-csharp", 11 | "c#": "text/x-csharp", 12 | }; 13 | 14 | // make this lazy so that we don't need to load GFM last 15 | var getMode = (function () { 16 | var i, modes = {}, mimes = {}, mime; 17 | 18 | var list = CodeMirror.listModes(); 19 | for (i = 0; i < list.length; i++) { 20 | modes[list[i]] = list[i]; 21 | } 22 | var mimesList = CodeMirror.listMIMEs(); 23 | for (i = 0; i < mimesList.length; i++) { 24 | mime = mimesList[i].mime; 25 | mimes[mime] = mimesList[i].mime; 26 | } 27 | 28 | for (var a in aliases) { 29 | if (aliases[a] in modes || aliases[a] in mimes) 30 | modes[a] = aliases[a]; 31 | } 32 | 33 | return function (lang) { 34 | return modes[lang] ? CodeMirror.getMode(config, modes[lang]) : null; 35 | } 36 | }()); 37 | 38 | function markdown(stream, state) { 39 | // intercept fenced code blocks 40 | if (stream.sol() && stream.match(/^```([\w+#]*)/)) { 41 | // try switching mode 42 | state.localMode = getMode(RegExp.$1) 43 | if (state.localMode) 44 | state.localState = state.localMode.startState(); 45 | 46 | state.token = local; 47 | return 'code'; 48 | } 49 | 50 | return mdMode.token(stream, state.mdState); 51 | } 52 | 53 | function local(stream, state) { 54 | if (stream.sol() && stream.match(/^```/)) { 55 | state.localMode = state.localState = null; 56 | state.token = markdown; 57 | return 'code'; 58 | } 59 | else if (state.localMode) { 60 | return state.localMode.token(stream, state.localState); 61 | } else { 62 | stream.skipToEnd(); 63 | return 'code'; 64 | } 65 | } 66 | 67 | // custom handleText to prevent emphasis in the middle of a word 68 | // and add autolinking 69 | function handleText(stream, mdState) { 70 | var match; 71 | if (stream.match(/^\w+:\/\/\S+/)) { 72 | return 'linkhref'; 73 | } 74 | if (stream.match(/^[^\[*\\<>` _][^\[*\\<>` ]*[^\[*\\<>` _]/)) { 75 | return mdMode.getType(mdState); 76 | } 77 | if (match = stream.match(/^[^\[*\\<>` ]+/)) { 78 | var word = match[0]; 79 | if (word[0] === '_' && word[word.length-1] === '_') { 80 | stream.backUp(word.length); 81 | return undefined; 82 | } 83 | return mdMode.getType(mdState); 84 | } 85 | if (stream.eatSpace()) { 86 | return null; 87 | } 88 | } 89 | 90 | return { 91 | startState: function() { 92 | var mdState = mdMode.startState(); 93 | mdState.text = handleText; 94 | return {token: markdown, mode: "markdown", mdState: mdState, 95 | localMode: null, localState: null}; 96 | }, 97 | 98 | copyState: function(state) { 99 | return {token: state.token, mode: state.mode, mdState: CodeMirror.copyState(mdMode, state.mdState), 100 | localMode: state.localMode, 101 | localState: state.localMode ? CodeMirror.copyState(state.localMode, state.localState) : null}; 102 | }, 103 | 104 | token: function(stream, state) { 105 | return state.token(stream, state); 106 | } 107 | } 108 | }); 109 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/gfm/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CodeMirror: GFM mode 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |

CodeMirror: GFM mode

17 | 18 | 19 |
36 | 37 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/groovy/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CodeMirror: Groovy mode 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

CodeMirror: Groovy mode

13 | 14 |
60 | 61 | 68 | 69 |

MIME types defined: text/x-groovy

70 | 71 | 72 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/haskell/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CodeMirror: Haskell mode 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

CodeMirror: Haskell mode

14 | 15 |
49 | 50 | 57 | 58 |

MIME types defined: text/x-haskell.

59 | 60 | 61 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/htmlembedded/htmlembedded.js: -------------------------------------------------------------------------------- 1 | CodeMirror.defineMode("htmlembedded", function(config, parserConfig) { 2 | 3 | //config settings 4 | var scriptStartRegex = parserConfig.scriptStartRegex || /^<%/i, 5 | scriptEndRegex = parserConfig.scriptEndRegex || /^%>/i; 6 | 7 | //inner modes 8 | var scriptingMode, htmlMixedMode; 9 | 10 | //tokenizer when in html mode 11 | function htmlDispatch(stream, state) { 12 | if (stream.match(scriptStartRegex, false)) { 13 | state.token=scriptingDispatch; 14 | return scriptingMode.token(stream, state.scriptState); 15 | } 16 | else 17 | return htmlMixedMode.token(stream, state.htmlState); 18 | } 19 | 20 | //tokenizer when in scripting mode 21 | function scriptingDispatch(stream, state) { 22 | if (stream.match(scriptEndRegex, false)) { 23 | state.token=htmlDispatch; 24 | return htmlMixedMode.token(stream, state.htmlState); 25 | } 26 | else 27 | return scriptingMode.token(stream, state.scriptState); 28 | } 29 | 30 | 31 | return { 32 | startState: function() { 33 | scriptingMode = scriptingMode || CodeMirror.getMode(config, parserConfig.scriptingModeSpec); 34 | htmlMixedMode = htmlMixedMode || CodeMirror.getMode(config, "htmlmixed"); 35 | return { 36 | token : parserConfig.startOpen ? scriptingDispatch : htmlDispatch, 37 | htmlState : htmlMixedMode.startState(), 38 | scriptState : scriptingMode.startState() 39 | } 40 | }, 41 | 42 | token: function(stream, state) { 43 | return state.token(stream, state); 44 | }, 45 | 46 | indent: function(state, textAfter) { 47 | if (state.token == htmlDispatch) 48 | return htmlMixedMode.indent(state.htmlState, textAfter); 49 | else 50 | return scriptingMode.indent(state.scriptState, textAfter); 51 | }, 52 | 53 | copyState: function(state) { 54 | return { 55 | token : state.token, 56 | htmlState : CodeMirror.copyState(htmlMixedMode, state.htmlState), 57 | scriptState : CodeMirror.copyState(scriptingMode, state.scriptState) 58 | } 59 | }, 60 | 61 | 62 | electricChars: "/{}:" 63 | } 64 | }); 65 | 66 | CodeMirror.defineMIME("application/x-ejs", { name: "htmlembedded", scriptingModeSpec:"javascript"}); 67 | CodeMirror.defineMIME("application/x-aspx", { name: "htmlembedded", scriptingModeSpec:"text/x-csharp"}); 68 | CodeMirror.defineMIME("application/x-jsp", { name: "htmlembedded", scriptingModeSpec:"text/x-java"}); 69 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/htmlembedded/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CodeMirror: Html Embedded Scripts mode 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |

CodeMirror: Html Embedded Scripts mode

17 | 18 |
30 | 31 | 42 | 43 |

Mode for html embedded scripts like JSP and ASP.NET. Depends on HtmlMixed which in turn depends on 44 | JavaScript, CSS and XML.
Other dependancies include those of the scriping language chosen.

45 | 46 |

MIME types defined: application/x-aspx (ASP.NET), 47 | application/x-ejs (Embedded Javascript), application/x-jsp (JavaServer Pages)

48 | 49 | 50 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/htmlmixed/htmlmixed.js: -------------------------------------------------------------------------------- 1 | CodeMirror.defineMode("htmlmixed", function(config, parserConfig) { 2 | var htmlMode = CodeMirror.getMode(config, {name: "xml", htmlMode: true}); 3 | var jsMode = CodeMirror.getMode(config, "javascript"); 4 | var cssMode = CodeMirror.getMode(config, "css"); 5 | 6 | function html(stream, state) { 7 | var style = htmlMode.token(stream, state.htmlState); 8 | if (style == "tag" && stream.current() == ">" && state.htmlState.context) { 9 | if (/^script$/i.test(state.htmlState.context.tagName)) { 10 | state.token = javascript; 11 | state.localState = jsMode.startState(htmlMode.indent(state.htmlState, "")); 12 | state.mode = "javascript"; 13 | } 14 | else if (/^style$/i.test(state.htmlState.context.tagName)) { 15 | state.token = css; 16 | state.localState = cssMode.startState(htmlMode.indent(state.htmlState, "")); 17 | state.mode = "css"; 18 | } 19 | } 20 | return style; 21 | } 22 | function maybeBackup(stream, pat, style) { 23 | var cur = stream.current(); 24 | var close = cur.search(pat); 25 | if (close > -1) stream.backUp(cur.length - close); 26 | return style; 27 | } 28 | function javascript(stream, state) { 29 | if (stream.match(/^<\/\s*script\s*>/i, false)) { 30 | state.token = html; 31 | state.curState = null; 32 | state.mode = "html"; 33 | return html(stream, state); 34 | } 35 | return maybeBackup(stream, /<\/\s*script\s*>/, 36 | jsMode.token(stream, state.localState)); 37 | } 38 | function css(stream, state) { 39 | if (stream.match(/^<\/\s*style\s*>/i, false)) { 40 | state.token = html; 41 | state.localState = null; 42 | state.mode = "html"; 43 | return html(stream, state); 44 | } 45 | return maybeBackup(stream, /<\/\s*style\s*>/, 46 | cssMode.token(stream, state.localState)); 47 | } 48 | 49 | return { 50 | startState: function() { 51 | var state = htmlMode.startState(); 52 | return {token: html, localState: null, mode: "html", htmlState: state}; 53 | }, 54 | 55 | copyState: function(state) { 56 | if (state.localState) 57 | var local = CodeMirror.copyState(state.token == css ? cssMode : jsMode, state.localState); 58 | return {token: state.token, localState: local, mode: state.mode, 59 | htmlState: CodeMirror.copyState(htmlMode, state.htmlState)}; 60 | }, 61 | 62 | token: function(stream, state) { 63 | return state.token(stream, state); 64 | }, 65 | 66 | indent: function(state, textAfter) { 67 | if (state.token == html || /^\s*<\//.test(textAfter)) 68 | return htmlMode.indent(state.htmlState, textAfter); 69 | else if (state.token == javascript) 70 | return jsMode.indent(state.localState, textAfter); 71 | else 72 | return cssMode.indent(state.localState, textAfter); 73 | }, 74 | 75 | compareStates: function(a, b) { 76 | return htmlMode.compareStates(a.htmlState, b.htmlState); 77 | }, 78 | 79 | electricChars: "/{}:" 80 | } 81 | }); 82 | 83 | CodeMirror.defineMIME("text/html", "htmlmixed"); 84 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/htmlmixed/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CodeMirror: HTML mixed mode 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

CodeMirror: HTML mixed mode

16 |
40 | 43 | 44 |

The HTML mixed mode depends on the XML, JavaScript, and CSS modes.

45 | 46 |

MIME types defined: text/html 47 | (redefined, only takes effect if you load this parser after the 48 | XML parser).

49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/javascript/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CodeMirror: JavaScript mode 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

CodeMirror: JavaScript mode

13 | 14 |
63 | 64 | 70 | 71 |

JavaScript mode supports a single configuration 72 | option, json, which will set the mode to expect JSON 73 | data rather than a JavaScript program.

74 | 75 |

MIME types defined: text/javascript, application/json.

76 | 77 | 78 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/jinja2/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CodeMirror: Jinja2 mode 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

CodeMirror: Jinja2 mode

13 |
31 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/jinja2/jinja2.js: -------------------------------------------------------------------------------- 1 | CodeMirror.defineMode("jinja2", function(config, parserConf) { 2 | var keywords = ["block", "endblock", "for", "endfor", "in", "true", "false", 3 | "loop", "none", "self", "super", "if", "as", "not", "and", 4 | "else", "import", "with", "without", "context"]; 5 | keywords = new RegExp("^((" + keywords.join(")|(") + "))\\b"); 6 | 7 | function tokenBase (stream, state) { 8 | var ch = stream.next(); 9 | if (ch == "{") { 10 | if (ch = stream.eat(/\{|%|#/)) { 11 | stream.eat("-"); 12 | state.tokenize = inTag(ch); 13 | return "tag"; 14 | } 15 | } 16 | } 17 | function inTag (close) { 18 | if (close == "{") { 19 | close = "}"; 20 | } 21 | return function (stream, state) { 22 | var ch = stream.next(); 23 | if ((ch == close || (ch == "-" && stream.eat(close))) 24 | && stream.eat("}")) { 25 | state.tokenize = tokenBase; 26 | return "tag"; 27 | } 28 | if (stream.match(keywords)) { 29 | return "keyword"; 30 | } 31 | return close == "#" ? "comment" : "string"; 32 | }; 33 | } 34 | return { 35 | startState: function () { 36 | return {tokenize: tokenBase}; 37 | }, 38 | token: function (stream, state) { 39 | return state.tokenize(stream, state); 40 | } 41 | }; 42 | }); 43 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/lua/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CodeMirror: Lua mode 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

CodeMirror: Lua mode

14 |
55 | 62 | 63 |

Loosely based on Franciszek 64 | Wawrzak's CodeMirror 65 | 1 mode. One configuration parameter is 66 | supported, specials, to which you can provide an 67 | array of strings to have those identifiers highlighted with 68 | the lua-special style.

69 |

MIME types defined: text/x-lua.

70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/ntriples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CodeMirror: NTriples mode 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |

CodeMirror: NTriples mode

17 |
18 | 25 |
26 | 27 | 30 |

MIME types defined: text/n-triples.

31 | 32 | 33 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/pascal/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 souceLair 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/pascal/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CodeMirror: Pascal mode 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

CodeMirror: Pascal mode

13 | 14 |
37 | 38 | 45 | 46 |

MIME types defined: text/x-pascal.

47 | 48 | 49 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/pascal/pascal.js: -------------------------------------------------------------------------------- 1 | CodeMirror.defineMode("pascal", function(config) { 2 | function words(str) { 3 | var obj = {}, words = str.split(" "); 4 | for (var i = 0; i < words.length; ++i) obj[words[i]] = true; 5 | return obj; 6 | } 7 | var keywords = words("and array begin case const div do downto else end file for forward integer " + 8 | "boolean char function goto if in label mod nil not of or packed procedure " + 9 | "program record repeat set string then to type until var while with"); 10 | var blockKeywords = words("case do else for if switch while struct then of"); 11 | var atoms = {"null": true}; 12 | 13 | var isOperatorChar = /[+\-*&%=<>!?|\/]/; 14 | var curPunc; 15 | 16 | function tokenBase(stream, state) { 17 | var ch = stream.next(); 18 | if (ch == "#" && state.startOfLine) { 19 | stream.skipToEnd(); 20 | return "meta"; 21 | } 22 | if (ch == '"' || ch == "'") { 23 | state.tokenize = tokenString(ch); 24 | return state.tokenize(stream, state); 25 | } 26 | if (ch == "(" && stream.eat("*")) { 27 | state.tokenize = tokenComment; 28 | return tokenComment(stream, state); 29 | } 30 | if (/[\[\]{}\(\),;\:\.]/.test(ch)) { 31 | curPunc = ch; 32 | return null 33 | } 34 | if (/\d/.test(ch)) { 35 | stream.eatWhile(/[\w\.]/); 36 | return "number"; 37 | } 38 | if (ch == "/") { 39 | if (stream.eat("/")) { 40 | stream.skipToEnd(); 41 | return "comment"; 42 | } 43 | } 44 | if (isOperatorChar.test(ch)) { 45 | stream.eatWhile(isOperatorChar); 46 | return "operator"; 47 | } 48 | stream.eatWhile(/[\w\$_]/); 49 | var cur = stream.current(); 50 | if (keywords.propertyIsEnumerable(cur)) { 51 | if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; 52 | return "keyword"; 53 | } 54 | if (atoms.propertyIsEnumerable(cur)) return "atom"; 55 | return "word"; 56 | } 57 | 58 | function tokenString(quote) { 59 | return function(stream, state) { 60 | var escaped = false, next, end = false; 61 | while ((next = stream.next()) != null) { 62 | if (next == quote && !escaped) {end = true; break;} 63 | escaped = !escaped && next == "\\"; 64 | } 65 | if (end || !escaped) state.tokenize = null; 66 | return "string"; 67 | }; 68 | } 69 | 70 | function tokenComment(stream, state) { 71 | var maybeEnd = false, ch; 72 | while (ch = stream.next()) { 73 | if (ch == ")" && maybeEnd) { 74 | state.tokenize = null; 75 | break; 76 | } 77 | maybeEnd = (ch == "*"); 78 | } 79 | return "comment"; 80 | } 81 | 82 | function Context(indented, column, type, align, prev) { 83 | this.indented = indented; 84 | this.column = column; 85 | this.type = type; 86 | this.align = align; 87 | this.prev = prev; 88 | } 89 | function pushContext(state, col, type) { 90 | return state.context = new Context(state.indented, col, type, null, state.context); 91 | } 92 | function popContext(state) { 93 | var t = state.context.type; 94 | if (t == ")" || t == "]" ) 95 | state.indented = state.context.indented; 96 | return state.context = state.context.prev; 97 | } 98 | 99 | // Interface 100 | 101 | return { 102 | startState: function(basecolumn) { 103 | return { 104 | tokenize: null, 105 | context: new Context((basecolumn || 0) - config.indentUnit, 0, "top", false), 106 | indented: 0, 107 | startOfLine: true 108 | }; 109 | }, 110 | 111 | token: function(stream, state) { 112 | var ctx = state.context; 113 | if (stream.sol()) { 114 | if (ctx.align == null) ctx.align = false; 115 | state.indented = stream.indentation(); 116 | state.startOfLine = true; 117 | } 118 | if (stream.eatSpace()) return null; 119 | curPunc = null; 120 | var style = (state.tokenize || tokenBase)(stream, state); 121 | if (style == "comment" || style == "meta") return style; 122 | if (ctx.align == null) ctx.align = true; 123 | 124 | if ((curPunc == ";" || curPunc == ":") && ctx.type == "statement") popContext(state); 125 | else if (curPunc == "[") pushContext(state, stream.column(), "]"); 126 | else if (curPunc == "(") pushContext(state, stream.column(), ")"); 127 | else if (curPunc == ctx.type) popContext(state); 128 | else if ( ctx.type == "top" || (ctx.type == "statement" && curPunc == "newstatement")) 129 | pushContext(state, stream.column(), "statement"); 130 | state.startOfLine = false; 131 | return style; 132 | }, 133 | 134 | electricChars: "{}" 135 | }; 136 | }); 137 | 138 | CodeMirror.defineMIME("text/x-pascal", "pascal"); 139 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/perl/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2011 by Sabaca under the MIT license. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/perl/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CodeMirror: Perl mode 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

CodeMirror: Perl mode

13 | 14 |
52 | 53 | 59 | 60 |

MIME types defined: text/x-perl.

61 | 62 | 63 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/php/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CodeMirror: PHP mode 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |

CodeMirror: PHP mode

17 | 18 |
29 | 30 | 41 | 42 |

Simple HTML/PHP mode based on 43 | the C-like mode. Depends on XML, 44 | JavaScript, CSS, and C-like modes.

45 | 46 |

MIME types defined: application/x-httpd-php (HTML with PHP code), text/x-php (plain, non-wrapped PHP code).

47 | 48 | 49 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/php/php.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | function keywords(str) { 3 | var obj = {}, words = str.split(" "); 4 | for (var i = 0; i < words.length; ++i) obj[words[i]] = true; 5 | return obj; 6 | } 7 | function heredoc(delim) { 8 | return function(stream, state) { 9 | if (stream.match(delim)) state.tokenize = null; 10 | else stream.skipToEnd(); 11 | return "string"; 12 | } 13 | } 14 | var phpConfig = { 15 | name: "clike", 16 | keywords: keywords("abstract and array as break case catch cfunction class clone const continue declare " + 17 | "default do else elseif enddeclare endfor endforeach endif endswitch endwhile extends " + 18 | "final for foreach function global goto if implements interface instanceof namespace " + 19 | "new or private protected public static switch throw try use var while xor return" + 20 | "die echo empty exit eval include include_once isset list require require_once print unset"), 21 | blockKeywords: keywords("catch do else elseif for foreach if switch try while"), 22 | atoms: keywords("true false null TRUE FALSE NULL"), 23 | multiLineStrings: true, 24 | hooks: { 25 | "$": function(stream, state) { 26 | stream.eatWhile(/[\w\$_]/); 27 | return "variable-2"; 28 | }, 29 | "<": function(stream, state) { 30 | if (stream.match(/</; 57 | state.mode = 'php'; 58 | } 59 | else if (style == "tag" && stream.current() == ">" && state.curState.context) { 60 | if (/^script$/i.test(state.curState.context.tagName)) { 61 | state.curMode = jsMode; 62 | state.curState = jsMode.startState(htmlMode.indent(state.curState, "")); 63 | state.curClose = /^<\/\s*script\s*>/i; 64 | state.mode = 'javascript'; 65 | } 66 | else if (/^style$/i.test(state.curState.context.tagName)) { 67 | state.curMode = cssMode; 68 | state.curState = cssMode.startState(htmlMode.indent(state.curState, "")); 69 | state.curClose = /^<\/\s*style\s*>/i; 70 | state.mode = 'css'; 71 | } 72 | } 73 | return style; 74 | } 75 | else if (stream.match(state.curClose, false)) { 76 | state.curMode = htmlMode; 77 | state.curState = state.html; 78 | state.curClose = null; 79 | state.mode = 'html'; 80 | return dispatch(stream, state); 81 | } 82 | else return state.curMode.token(stream, state.curState); 83 | } 84 | 85 | return { 86 | startState: function() { 87 | var html = htmlMode.startState(); 88 | return {html: html, 89 | php: phpMode.startState(), 90 | curMode: parserConfig.startOpen ? phpMode : htmlMode, 91 | curState: parserConfig.startOpen ? phpMode.startState() : html, 92 | curClose: parserConfig.startOpen ? /^\?>/ : null, 93 | mode: parserConfig.startOpen ? 'php' : 'html'} 94 | }, 95 | 96 | copyState: function(state) { 97 | var html = state.html, htmlNew = CodeMirror.copyState(htmlMode, html), 98 | php = state.php, phpNew = CodeMirror.copyState(phpMode, php), cur; 99 | if (state.curState == html) cur = htmlNew; 100 | else if (state.curState == php) cur = phpNew; 101 | else cur = CodeMirror.copyState(state.curMode, state.curState); 102 | return {html: htmlNew, php: phpNew, curMode: state.curMode, curState: cur, curClose: state.curClose}; 103 | }, 104 | 105 | token: dispatch, 106 | 107 | indent: function(state, textAfter) { 108 | if ((state.curMode != phpMode && /^\s*<\//.test(textAfter)) || 109 | (state.curMode == phpMode && /^\?>/.test(textAfter))) 110 | return htmlMode.indent(state.html, textAfter); 111 | return state.curMode.indent(state.curState, textAfter); 112 | }, 113 | 114 | electricChars: "/{}:" 115 | } 116 | }); 117 | CodeMirror.defineMIME("application/x-httpd-php", "php"); 118 | CodeMirror.defineMIME("application/x-httpd-php-open", {name: "php", startOpen: true}); 119 | CodeMirror.defineMIME("text/x-php", phpConfig); 120 | })(); 121 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/plsql/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CodeMirror: Oracle PL/SQL mode 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

CodeMirror: Oracle PL/SQL mode

13 | 14 |
46 | 47 | 55 | 56 |

57 | Simple mode that handles Oracle PL/SQL language (and Oracle SQL, of course). 58 |

59 | 60 |

MIME type defined: text/x-plsql 61 | (PLSQL code) 62 | 63 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/python/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2010 Timothy Farrell 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/python/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CodeMirror: Python mode 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

CodeMirror: Python mode

13 | 14 |
103 | 114 |

Configuration Options:

115 |
    116 |
  • version - 2/3 - The version of Python to recognize. Default is 2.
  • 117 |
  • singleLineStringErrors - true/false - If you have a single-line string that is not terminated at the end of the line, this will show subsequent lines as errors if true, otherwise it will consider the newline as the end of the string. Default is false.
  • 118 |
119 | 120 |

MIME types defined: text/x-python.

121 | 122 | 123 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/r/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011, Ubalo, Inc. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the Ubalo, Inc nor the names of its 12 | contributors may be used to endorse or promote products derived 13 | from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL UBALO, INC BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/r/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CodeMirror: R mode 5 | 6 | 7 | 8 | 15 | 16 | 17 | 18 |

CodeMirror: R mode

19 |
62 | 65 | 66 |

MIME types defined: text/x-rsrc.

67 | 68 |

Development of the CodeMirror R mode was kindly sponsored 69 | by Ubalo, who hold 70 | the license.

71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/r/r.js: -------------------------------------------------------------------------------- 1 | CodeMirror.defineMode("r", function(config) { 2 | function wordObj(str) { 3 | var words = str.split(" "), res = {}; 4 | for (var i = 0; i < words.length; ++i) res[words[i]] = true; 5 | return res; 6 | } 7 | var atoms = wordObj("NULL NA Inf NaN NA_integer_ NA_real_ NA_complex_ NA_character_"); 8 | var builtins = wordObj("list quote bquote eval return call parse deparse"); 9 | var keywords = wordObj("if else repeat while function for in next break"); 10 | var blockkeywords = wordObj("if else repeat while function for"); 11 | var opChars = /[+\-*\/^<>=!&|~$:]/; 12 | var curPunc; 13 | 14 | function tokenBase(stream, state) { 15 | curPunc = null; 16 | var ch = stream.next(); 17 | if (ch == "#") { 18 | stream.skipToEnd(); 19 | return "comment"; 20 | } else if (ch == "0" && stream.eat("x")) { 21 | stream.eatWhile(/[\da-f]/i); 22 | return "number"; 23 | } else if (ch == "." && stream.eat(/\d/)) { 24 | stream.match(/\d*(?:e[+\-]?\d+)?/); 25 | return "number"; 26 | } else if (/\d/.test(ch)) { 27 | stream.match(/\d*(?:\.\d+)?(?:e[+\-]\d+)?L?/); 28 | return "number"; 29 | } else if (ch == "'" || ch == '"') { 30 | state.tokenize = tokenString(ch); 31 | return "string"; 32 | } else if (ch == "." && stream.match(/.[.\d]+/)) { 33 | return "keyword"; 34 | } else if (/[\w\.]/.test(ch) && ch != "_") { 35 | stream.eatWhile(/[\w\.]/); 36 | var word = stream.current(); 37 | if (atoms.propertyIsEnumerable(word)) return "atom"; 38 | if (keywords.propertyIsEnumerable(word)) { 39 | if (blockkeywords.propertyIsEnumerable(word)) curPunc = "block"; 40 | return "keyword"; 41 | } 42 | if (builtins.propertyIsEnumerable(word)) return "builtin"; 43 | return "variable"; 44 | } else if (ch == "%") { 45 | if (stream.skipTo("%")) stream.next(); 46 | return "variable-2"; 47 | } else if (ch == "<" && stream.eat("-")) { 48 | return "arrow"; 49 | } else if (ch == "=" && state.ctx.argList) { 50 | return "arg-is"; 51 | } else if (opChars.test(ch)) { 52 | if (ch == "$") return "dollar"; 53 | stream.eatWhile(opChars); 54 | return "operator"; 55 | } else if (/[\(\){}\[\];]/.test(ch)) { 56 | curPunc = ch; 57 | if (ch == ";") return "semi"; 58 | return null; 59 | } else { 60 | return null; 61 | } 62 | } 63 | 64 | function tokenString(quote) { 65 | return function(stream, state) { 66 | if (stream.eat("\\")) { 67 | var ch = stream.next(); 68 | if (ch == "x") stream.match(/^[a-f0-9]{2}/i); 69 | else if ((ch == "u" || ch == "U") && stream.eat("{") && stream.skipTo("}")) stream.next(); 70 | else if (ch == "u") stream.match(/^[a-f0-9]{4}/i); 71 | else if (ch == "U") stream.match(/^[a-f0-9]{8}/i); 72 | else if (/[0-7]/.test(ch)) stream.match(/^[0-7]{1,2}/); 73 | return "string-2"; 74 | } else { 75 | var next; 76 | while ((next = stream.next()) != null) { 77 | if (next == quote) { state.tokenize = tokenBase; break; } 78 | if (next == "\\") { stream.backUp(1); break; } 79 | } 80 | return "string"; 81 | } 82 | }; 83 | } 84 | 85 | function push(state, type, stream) { 86 | state.ctx = {type: type, 87 | indent: state.indent, 88 | align: null, 89 | column: stream.column(), 90 | prev: state.ctx}; 91 | } 92 | function pop(state) { 93 | state.indent = state.ctx.indent; 94 | state.ctx = state.ctx.prev; 95 | } 96 | 97 | return { 98 | startState: function(base) { 99 | return {tokenize: tokenBase, 100 | ctx: {type: "top", 101 | indent: -config.indentUnit, 102 | align: false}, 103 | indent: 0, 104 | afterIdent: false}; 105 | }, 106 | 107 | token: function(stream, state) { 108 | if (stream.sol()) { 109 | if (state.ctx.align == null) state.ctx.align = false; 110 | state.indent = stream.indentation(); 111 | } 112 | if (stream.eatSpace()) return null; 113 | var style = state.tokenize(stream, state); 114 | if (style != "comment" && state.ctx.align == null) state.ctx.align = true; 115 | 116 | var ctype = state.ctx.type; 117 | if ((curPunc == ";" || curPunc == "{" || curPunc == "}") && ctype == "block") pop(state); 118 | if (curPunc == "{") push(state, "}", stream); 119 | else if (curPunc == "(") { 120 | push(state, ")", stream); 121 | if (state.afterIdent) state.ctx.argList = true; 122 | } 123 | else if (curPunc == "[") push(state, "]", stream); 124 | else if (curPunc == "block") push(state, "block", stream); 125 | else if (curPunc == ctype) pop(state); 126 | state.afterIdent = style == "variable" || style == "keyword"; 127 | return style; 128 | }, 129 | 130 | indent: function(state, textAfter) { 131 | if (state.tokenize != tokenBase) return 0; 132 | var firstChar = textAfter && textAfter.charAt(0), ctx = state.ctx, 133 | closing = firstChar == ctx.type; 134 | if (ctx.type == "block") return ctx.indent + (firstChar == "{" ? 0 : config.indentUnit); 135 | else if (ctx.align) return ctx.column + (closing ? 0 : 1); 136 | else return ctx.indent + (closing ? 0 : config.indentUnit); 137 | } 138 | }; 139 | }); 140 | 141 | CodeMirror.defineMIME("text/x-rsrc", "r"); 142 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/rpm/changes/changes.js: -------------------------------------------------------------------------------- 1 | CodeMirror.defineMode("changes", function(config, modeConfig) { 2 | var headerSeperator = /^-+$/; 3 | var headerLine = /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) ?\d{1,2} \d{2}:\d{2}(:\d{2})? [A-Z]{3,4} \d{4} - /; 4 | var simpleEmail = /^[\w+.-]+@[\w.-]+/; 5 | 6 | return { 7 | token: function(stream) { 8 | if (stream.sol()) { 9 | if (stream.match(headerSeperator)) { return 'tag'; } 10 | if (stream.match(headerLine)) { return 'tag'; } 11 | } 12 | if (stream.match(simpleEmail)) { return 'string'; } 13 | stream.next(); 14 | return null; 15 | } 16 | }; 17 | }); 18 | 19 | CodeMirror.defineMIME("text/x-rpm-changes", "changes"); 20 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/rpm/changes/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CodeMirror: RPM changes mode 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

CodeMirror: RPM changes mode

13 | 14 |
41 | 50 | 51 |

MIME types defined: text/x-rpm-changes.

52 | 53 | 54 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/rpm/spec/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CodeMirror: RPM spec mode 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

CodeMirror: RPM spec mode

14 | 15 |
88 | 96 | 97 |

MIME types defined: text/x-rpm-spec.

98 | 99 | 100 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/rpm/spec/spec.css: -------------------------------------------------------------------------------- 1 | .cm-s-default span.cm-preamble {color: #b26818; font-weight: bold;} 2 | .cm-s-default span.cm-macro {color: #b218b2;} 3 | .cm-s-default span.cm-section {color: green; font-weight: bold;} 4 | .cm-s-default span.cm-script {color: red;} 5 | .cm-s-default span.cm-issue {color: yellow;} 6 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/rpm/spec/spec.js: -------------------------------------------------------------------------------- 1 | // Quick and dirty spec file highlighting 2 | 3 | CodeMirror.defineMode("spec", function(config, modeConfig) { 4 | var arch = /^(i386|i586|i686|x86_64|ppc64|ppc|ia64|s390x|s390|sparc64|sparcv9|sparc|noarch|alphaev6|alpha|hppa|mipsel)/; 5 | 6 | var preamble = /^(Name|Version|Release|License|Summary|Url|Group|Source|BuildArch|BuildRequires|BuildRoot|AutoReqProv|Provides|Requires(\(\w+\))?|Obsoletes|Conflicts|Recommends|Source\d*|Patch\d*|ExclusiveArch|NoSource|Supplements):/; 7 | var section = /^%(debug_package|package|description|prep|build|install|files|clean|changelog|preun|postun|pre|post|triggerin|triggerun|pretrans|posttrans|verifyscript|check|triggerpostun|triggerprein|trigger)/; 8 | var control_flow_complex = /^%(ifnarch|ifarch|if)/; // rpm control flow macros 9 | var control_flow_simple = /^%(else|endif)/; // rpm control flow macros 10 | var operators = /^(\!|\?|\<\=|\<|\>\=|\>|\=\=|\&\&|\|\|)/; // operators in control flow macros 11 | 12 | return { 13 | startState: function () { 14 | return { 15 | controlFlow: false, 16 | macroParameters: false, 17 | section: false, 18 | }; 19 | }, 20 | token: function (stream, state) { 21 | var ch = stream.peek(); 22 | if (ch == "#") { stream.skipToEnd(); return "comment"; } 23 | 24 | if (stream.sol()) { 25 | if (stream.match(preamble)) { return "preamble"; } 26 | if (stream.match(section)) { return "section"; } 27 | } 28 | 29 | if (stream.match(/^\$\w+/)) { return "def"; } // Variables like '$RPM_BUILD_ROOT' 30 | if (stream.match(/^\$\{\w+\}/)) { return "def"; } // Variables like '${RPM_BUILD_ROOT}' 31 | 32 | if (stream.match(control_flow_simple)) { return "keyword"; } 33 | if (stream.match(control_flow_complex)) { 34 | state.controlFlow = true; 35 | return "keyword"; 36 | } 37 | if (state.controlFlow) { 38 | if (stream.match(operators)) { return "operator"; } 39 | if (stream.match(/^(\d+)/)) { return "number"; } 40 | if (stream.eol()) { state.controlFlow = false; } 41 | } 42 | 43 | if (stream.match(arch)) { return "number"; } 44 | 45 | // Macros like '%make_install' or '%attr(0775,root,root)' 46 | if (stream.match(/^%[\w]+/)) { 47 | if (stream.match(/^\(/)) { state.macroParameters = true; } 48 | return "macro"; 49 | } 50 | if (state.macroParameters) { 51 | if (stream.match(/^\d+/)) { return "number";} 52 | if (stream.match(/^\)/)) { 53 | state.macroParameters = false; 54 | return "macro"; 55 | } 56 | } 57 | if (stream.match(/^%\{\??[\w \-]+\}/)) { return "macro"; } // Macros like '%{defined fedora}' 58 | 59 | //TODO: Include bash script sub-parser (CodeMirror supports that) 60 | stream.next(); 61 | return null; 62 | } 63 | }; 64 | }); 65 | 66 | CodeMirror.defineMIME("text/x-rpm-spec", "spec"); 67 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/ruby/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011, Ubalo, Inc. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the Ubalo, Inc. nor the names of its 12 | contributors may be used to endorse or promote products derived 13 | from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL UBALO, INC BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/rust/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CodeMirror: Rust mode 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

CodeMirror: Rust mode

13 | 14 |
37 | 38 | 45 | 46 |

MIME types defined: text/x-rustsrc.

47 | 48 | 49 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/scheme/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CodeMirror: Scheme mode 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

CodeMirror: Scheme mode

13 |
57 | 60 | 61 |

MIME types defined: text/x-scheme.

62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/smalltalk/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CodeMirror: Smalltalk mode 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |

CodeMirror: Smalltalk mode

17 | 18 |
41 | 42 | 50 | 51 |

Simple Smalltalk mode.

52 | 53 |

MIME types defined: text/x-stsrc.

54 | 55 | 56 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/smalltalk/smalltalk.js: -------------------------------------------------------------------------------- 1 | CodeMirror.defineMode('smalltalk', function(config, modeConfig) { 2 | 3 | var specialChars = /[+\-/\\*~<>=@%|&?!.:;^]/; 4 | var keywords = /true|false|nil|self|super|thisContext/; 5 | 6 | var Context = function(tokenizer, parent) { 7 | this.next = tokenizer; 8 | this.parent = parent; 9 | }; 10 | 11 | var Token = function(name, context, eos) { 12 | this.name = name; 13 | this.context = context; 14 | this.eos = eos; 15 | }; 16 | 17 | var State = function() { 18 | this.context = new Context(next, null); 19 | this.expectVariable = true; 20 | this.indentation = 0; 21 | this.userIndentationDelta = 0; 22 | }; 23 | 24 | State.prototype.userIndent = function(indentation) { 25 | this.userIndentationDelta = indentation > 0 ? (indentation / config.indentUnit - this.indentation) : 0; 26 | }; 27 | 28 | var next = function(stream, context, state) { 29 | var token = new Token(null, context, false); 30 | var char = stream.next(); 31 | 32 | if (char === '"') { 33 | token = nextComment(stream, new Context(nextComment, context)); 34 | 35 | } else if (char === '\'') { 36 | token = nextString(stream, new Context(nextString, context)); 37 | 38 | } else if (char === '#') { 39 | stream.eatWhile(/[^ .]/); 40 | token.name = 'string-2'; 41 | 42 | } else if (char === '$') { 43 | stream.eatWhile(/[^ ]/); 44 | token.name = 'string-2'; 45 | 46 | } else if (char === '|' && state.expectVariable) { 47 | token.context = new Context(nextTemporaries, context); 48 | 49 | } else if (/[\[\]{}()]/.test(char)) { 50 | token.name = 'bracket'; 51 | token.eos = /[\[{(]/.test(char); 52 | 53 | if (char === '[') { 54 | state.indentation++; 55 | } else if (char === ']') { 56 | state.indentation = Math.max(0, state.indentation - 1); 57 | } 58 | 59 | } else if (specialChars.test(char)) { 60 | stream.eatWhile(specialChars); 61 | token.name = 'operator'; 62 | token.eos = char !== ';'; // ; cascaded message expression 63 | 64 | } else if (/\d/.test(char)) { 65 | stream.eatWhile(/[\w\d]/); 66 | token.name = 'number' 67 | 68 | } else if (/[\w_]/.test(char)) { 69 | stream.eatWhile(/[\w\d_]/); 70 | token.name = state.expectVariable ? (keywords.test(stream.current()) ? 'keyword' : 'variable') : null; 71 | 72 | } else { 73 | token.eos = state.expectVariable; 74 | } 75 | 76 | return token; 77 | }; 78 | 79 | var nextComment = function(stream, context) { 80 | stream.eatWhile(/[^"]/); 81 | return new Token('comment', stream.eat('"') ? context.parent : context, true); 82 | }; 83 | 84 | var nextString = function(stream, context) { 85 | stream.eatWhile(/[^']/); 86 | return new Token('string', stream.eat('\'') ? context.parent : context, false); 87 | }; 88 | 89 | var nextTemporaries = function(stream, context, state) { 90 | var token = new Token(null, context, false); 91 | var char = stream.next(); 92 | 93 | if (char === '|') { 94 | token.context = context.parent; 95 | token.eos = true; 96 | 97 | } else { 98 | stream.eatWhile(/[^|]/); 99 | token.name = 'variable'; 100 | } 101 | 102 | return token; 103 | } 104 | 105 | return { 106 | startState: function() { 107 | return new State; 108 | }, 109 | 110 | token: function(stream, state) { 111 | state.userIndent(stream.indentation()); 112 | 113 | if (stream.eatSpace()) { 114 | return null; 115 | } 116 | 117 | var token = state.context.next(stream, state.context, state); 118 | state.context = token.context; 119 | state.expectVariable = token.eos; 120 | 121 | state.lastToken = token; 122 | return token.name; 123 | }, 124 | 125 | blankLine: function(state) { 126 | state.userIndent(0); 127 | }, 128 | 129 | indent: function(state, textAfter) { 130 | var i = state.context.next === next && textAfter && textAfter.charAt(0) === ']' ? -1 : state.userIndentationDelta; 131 | return (state.indentation + i) * config.indentUnit; 132 | }, 133 | 134 | electricChars: ']' 135 | }; 136 | 137 | }); 138 | 139 | CodeMirror.defineMIME('text/x-stsrc', {name: 'smalltalk'}); -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/sparql/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CodeMirror: SPARQL mode 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

CodeMirror: SPARQL mode

13 |
29 | 36 | 37 |

MIME types defined: application/x-sparql-query.

38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/sparql/sparql.js: -------------------------------------------------------------------------------- 1 | CodeMirror.defineMode("sparql", function(config) { 2 | var indentUnit = config.indentUnit; 3 | var curPunc; 4 | 5 | function wordRegexp(words) { 6 | return new RegExp("^(?:" + words.join("|") + ")$", "i"); 7 | } 8 | var ops = wordRegexp(["str", "lang", "langmatches", "datatype", "bound", "sameterm", "isiri", "isuri", 9 | "isblank", "isliteral", "union", "a"]); 10 | var keywords = wordRegexp(["base", "prefix", "select", "distinct", "reduced", "construct", "describe", 11 | "ask", "from", "named", "where", "order", "limit", "offset", "filter", "optional", 12 | "graph", "by", "asc", "desc"]); 13 | var operatorChars = /[*+\-<>=&|]/; 14 | 15 | function tokenBase(stream, state) { 16 | var ch = stream.next(); 17 | curPunc = null; 18 | if (ch == "$" || ch == "?") { 19 | stream.match(/^[\w\d]*/); 20 | return "variable-2"; 21 | } 22 | else if (ch == "<" && !stream.match(/^[\s\u00a0=]/, false)) { 23 | stream.match(/^[^\s\u00a0>]*>?/); 24 | return "atom"; 25 | } 26 | else if (ch == "\"" || ch == "'") { 27 | state.tokenize = tokenLiteral(ch); 28 | return state.tokenize(stream, state); 29 | } 30 | else if (/[{}\(\),\.;\[\]]/.test(ch)) { 31 | curPunc = ch; 32 | return null; 33 | } 34 | else if (ch == "#") { 35 | stream.skipToEnd(); 36 | return "comment"; 37 | } 38 | else if (operatorChars.test(ch)) { 39 | stream.eatWhile(operatorChars); 40 | return null; 41 | } 42 | else if (ch == ":") { 43 | stream.eatWhile(/[\w\d\._\-]/); 44 | return "atom"; 45 | } 46 | else { 47 | stream.eatWhile(/[_\w\d]/); 48 | if (stream.eat(":")) { 49 | stream.eatWhile(/[\w\d_\-]/); 50 | return "atom"; 51 | } 52 | var word = stream.current(), type; 53 | if (ops.test(word)) 54 | return null; 55 | else if (keywords.test(word)) 56 | return "keyword"; 57 | else 58 | return "variable"; 59 | } 60 | } 61 | 62 | function tokenLiteral(quote) { 63 | return function(stream, state) { 64 | var escaped = false, ch; 65 | while ((ch = stream.next()) != null) { 66 | if (ch == quote && !escaped) { 67 | state.tokenize = tokenBase; 68 | break; 69 | } 70 | escaped = !escaped && ch == "\\"; 71 | } 72 | return "string"; 73 | }; 74 | } 75 | 76 | function pushContext(state, type, col) { 77 | state.context = {prev: state.context, indent: state.indent, col: col, type: type}; 78 | } 79 | function popContext(state) { 80 | state.indent = state.context.indent; 81 | state.context = state.context.prev; 82 | } 83 | 84 | return { 85 | startState: function(base) { 86 | return {tokenize: tokenBase, 87 | context: null, 88 | indent: 0, 89 | col: 0}; 90 | }, 91 | 92 | token: function(stream, state) { 93 | if (stream.sol()) { 94 | if (state.context && state.context.align == null) state.context.align = false; 95 | state.indent = stream.indentation(); 96 | } 97 | if (stream.eatSpace()) return null; 98 | var style = state.tokenize(stream, state); 99 | 100 | if (style != "comment" && state.context && state.context.align == null && state.context.type != "pattern") { 101 | state.context.align = true; 102 | } 103 | 104 | if (curPunc == "(") pushContext(state, ")", stream.column()); 105 | else if (curPunc == "[") pushContext(state, "]", stream.column()); 106 | else if (curPunc == "{") pushContext(state, "}", stream.column()); 107 | else if (/[\]\}\)]/.test(curPunc)) { 108 | while (state.context && state.context.type == "pattern") popContext(state); 109 | if (state.context && curPunc == state.context.type) popContext(state); 110 | } 111 | else if (curPunc == "." && state.context && state.context.type == "pattern") popContext(state); 112 | else if (/atom|string|variable/.test(style) && state.context) { 113 | if (/[\}\]]/.test(state.context.type)) 114 | pushContext(state, "pattern", stream.column()); 115 | else if (state.context.type == "pattern" && !state.context.align) { 116 | state.context.align = true; 117 | state.context.col = stream.column(); 118 | } 119 | } 120 | 121 | return style; 122 | }, 123 | 124 | indent: function(state, textAfter) { 125 | var firstChar = textAfter && textAfter.charAt(0); 126 | var context = state.context; 127 | if (/[\]\}]/.test(firstChar)) 128 | while (context && context.type == "pattern") context = context.prev; 129 | 130 | var closing = context && firstChar == context.type; 131 | if (!context) 132 | return 0; 133 | else if (context.type == "pattern") 134 | return context.col; 135 | else if (context.align) 136 | return context.col + (closing ? 0 : 1); 137 | else 138 | return context.indent + (closing ? 0 : indentUnit); 139 | } 140 | }; 141 | }); 142 | 143 | CodeMirror.defineMIME("application/x-sparql-query", "sparql"); 144 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/stex/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CodeMirror: sTeX mode 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

CodeMirror: sTeX mode

13 |
88 | 91 | 92 |

MIME types defined: text/stex.

93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/stex/stex.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: Constantin Jucovschi (c.jucovschi@jacobs-university.de) 3 | * Licence: MIT 4 | */ 5 | 6 | CodeMirror.defineMode("stex", function(cmCfg, modeCfg) 7 | { 8 | function pushCommand(state, command) { 9 | state.cmdState.push(command); 10 | } 11 | 12 | function peekCommand(state) { 13 | if (state.cmdState.length>0) 14 | return state.cmdState[state.cmdState.length-1]; 15 | else 16 | return null; 17 | } 18 | 19 | function popCommand(state) { 20 | if (state.cmdState.length>0) { 21 | var plug = state.cmdState.pop(); 22 | plug.closeBracket(); 23 | } 24 | } 25 | 26 | function applyMostPowerful(state) { 27 | var context = state.cmdState; 28 | for (var i = context.length - 1; i >= 0; i--) { 29 | var plug = context[i]; 30 | if (plug.name=="DEFAULT") 31 | continue; 32 | return plug.styleIdentifier(); 33 | } 34 | return null; 35 | } 36 | 37 | function addPluginPattern(pluginName, cmdStyle, brackets, styles) { 38 | return function () { 39 | this.name=pluginName; 40 | this.bracketNo = 0; 41 | this.style=cmdStyle; 42 | this.styles = styles; 43 | this.brackets = brackets; 44 | 45 | this.styleIdentifier = function(content) { 46 | if (this.bracketNo<=this.styles.length) 47 | return this.styles[this.bracketNo-1]; 48 | else 49 | return null; 50 | }; 51 | this.openBracket = function(content) { 52 | this.bracketNo++; 53 | return "bracket"; 54 | }; 55 | this.closeBracket = function(content) { 56 | }; 57 | } 58 | } 59 | 60 | var plugins = new Array(); 61 | 62 | plugins["importmodule"] = addPluginPattern("importmodule", "tag", "{[", ["string", "builtin"]); 63 | plugins["documentclass"] = addPluginPattern("documentclass", "tag", "{[", ["", "atom"]); 64 | plugins["usepackage"] = addPluginPattern("documentclass", "tag", "[", ["atom"]); 65 | plugins["begin"] = addPluginPattern("documentclass", "tag", "[", ["atom"]); 66 | plugins["end"] = addPluginPattern("documentclass", "tag", "[", ["atom"]); 67 | 68 | plugins["DEFAULT"] = function () { 69 | this.name="DEFAULT"; 70 | this.style="tag"; 71 | 72 | this.styleIdentifier = function(content) { 73 | }; 74 | this.openBracket = function(content) { 75 | }; 76 | this.closeBracket = function(content) { 77 | }; 78 | }; 79 | 80 | function setState(state, f) { 81 | state.f = f; 82 | } 83 | 84 | function normal(source, state) { 85 | if (source.match(/^\\[a-z]+/)) { 86 | var cmdName = source.current(); 87 | cmdName = cmdName.substr(1, cmdName.length-1); 88 | var plug = plugins[cmdName]; 89 | if (typeof(plug) == 'undefined') { 90 | plug = plugins["DEFAULT"]; 91 | } 92 | plug = new plug(); 93 | pushCommand(state, plug); 94 | setState(state, beginParams); 95 | return plug.style; 96 | } 97 | 98 | var ch = source.next(); 99 | if (ch == "%") { 100 | setState(state, inCComment); 101 | return "comment"; 102 | } 103 | else if (ch=='}' || ch==']') { 104 | plug = peekCommand(state); 105 | if (plug) { 106 | plug.closeBracket(ch); 107 | setState(state, beginParams); 108 | } else 109 | return "error"; 110 | return "bracket"; 111 | } else if (ch=='{' || ch=='[') { 112 | plug = plugins["DEFAULT"]; 113 | plug = new plug(); 114 | pushCommand(state, plug); 115 | return "bracket"; 116 | } 117 | else if (/\d/.test(ch)) { 118 | source.eatWhile(/[\w.%]/); 119 | return "atom"; 120 | } 121 | else { 122 | source.eatWhile(/[\w-_]/); 123 | return applyMostPowerful(state); 124 | } 125 | } 126 | 127 | function inCComment(source, state) { 128 | source.skipToEnd(); 129 | setState(state, normal); 130 | return "comment"; 131 | } 132 | 133 | function beginParams(source, state) { 134 | var ch = source.peek(); 135 | if (ch == '{' || ch == '[') { 136 | var lastPlug = peekCommand(state); 137 | var style = lastPlug.openBracket(ch); 138 | source.eat(ch); 139 | setState(state, normal); 140 | return "bracket"; 141 | } 142 | if (/[ \t\r]/.test(ch)) { 143 | source.eat(ch); 144 | return null; 145 | } 146 | setState(state, normal); 147 | lastPlug = peekCommand(state); 148 | if (lastPlug) { 149 | popCommand(state); 150 | } 151 | return normal(source, state); 152 | } 153 | 154 | return { 155 | startState: function() { return { f:normal, cmdState:[] }; }, 156 | copyState: function(s) { return { f: s.f, cmdState: s.cmdState.slice(0, s.cmdState.length) }; }, 157 | 158 | token: function(stream, state) { 159 | var t = state.f(stream, state); 160 | var w = stream.current(); 161 | return t; 162 | } 163 | }; 164 | }); 165 | 166 | 167 | CodeMirror.defineMIME("text/x-stex", "stex"); 168 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/tiddlywiki/tiddlywiki.css: -------------------------------------------------------------------------------- 1 | .cm-s-default span.cm-header {color: blue; font-weight:bold;} 2 | .cm-s-default span.cm-code {color: #a50;} 3 | .cm-s-default span.cm-code-inline {color: #660;} 4 | 5 | .cm-s-default span.cm-quote {color: #555;} 6 | .cm-s-default span.cm-list {color: #c60;} 7 | .cm-s-default span.cm-hr {color: #999;} 8 | .cm-s-default span.cm-em {font-style: italic;} 9 | .cm-s-default span.cm-strong {font-weight: bold;} 10 | 11 | .cm-s-default span.cm-link-external {color: blue;} 12 | .cm-s-default span.cm-brace {color: #170; font-weight: bold;} 13 | .cm-s-default span.cm-macro {color: #9E3825;} 14 | .cm-s-default span.cm-table {color: blue;} 15 | .cm-s-default span.cm-warning {color: red; font-weight: bold;} 16 | 17 | .cm-s-default span.cm-underlined {text-decoration: underline;} 18 | .cm-s-default span.cm-line-through {text-decoration: line-through;} 19 | 20 | .cm-s-default span.cm-comment {color: #666;} 21 | 22 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/velocity/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CodeMirror: Velocity mode 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

CodeMirror: Velocity mode

14 |
89 | 99 | 100 |

MIME types defined: text/velocity.

101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/xml/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CodeMirror: XML mode 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

CodeMirror: XML mode

13 |
25 | 31 |

The XML mode supports two configuration parameters:

32 |
33 |
htmlMode (boolean)
34 |
This switches the mode to parse HTML instead of XML. This 35 | means attributes do not have to be quoted, and some elements 36 | (such as br) do not require a closing tag.
37 |
alignCDATA (boolean)
38 |
Setting this to true will force the opening tag of CDATA 39 | blocks to not be indented.
40 |
41 | 42 |

MIME types defined: application/xml, text/html.

43 | 44 | 45 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/xmlpure/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CodeMirror: Pure XML mode 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

CodeMirror: XML mode

13 |
30 | 33 | 34 |

This is my XML parser, based on the original:

35 |
    36 |
  • No html mode - this is pure xml
  • 37 |
  • Illegal attributes and element names are errors
  • 38 |
  • Attributes must have a value
  • 39 |
  • XML declaration supported (e.g.: <?xml version="1.0" encoding="utf-8" standalone="no" ?>)
  • 40 |
  • CDATA and comment blocks are not indented (except for their start-tag)
  • 41 |
  • Better handling of errors per line with the state object - provides good infrastructure for extending it
  • 42 |
43 | 44 |

What's missing:

45 |
    46 |
  • Make sure only a single root element exists at the document level
  • 47 |
  • Multi-line attributes should NOT indent
  • 48 |
  • Start tags are not painted red when they have no matching end tags (is this really wrong?)
  • 49 |
50 | 51 |

MIME types defined: application/xml, text/xml.

52 | 53 |

@author: Dror BG (deebug.dev[at]gmail.com)
54 |

@date: August, 2011
55 |

@github: https://github.com/deebugger/CodeMirror2

56 | 57 |

MIME types defined: application/xml, text/xml.

58 | 59 | 60 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/yaml/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CodeMirror: YAML mode 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

CodeMirror: YAML mode

13 |
60 | 63 | 64 |

MIME types defined: text/x-yaml.

65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/mode/yaml/yaml.js: -------------------------------------------------------------------------------- 1 | CodeMirror.defineMode("yaml", function() { 2 | 3 | var cons = ['true', 'false', 'on', 'off', 'yes', 'no']; 4 | var keywordRegex = new RegExp("\\b(("+cons.join(")|(")+"))$", 'i'); 5 | 6 | return { 7 | token: function(stream, state) { 8 | var ch = stream.peek(); 9 | var esc = state.escaped; 10 | state.escaped = false; 11 | /* comments */ 12 | if (ch == "#") { stream.skipToEnd(); return "comment"; } 13 | if (state.literal && stream.indentation() > state.keyCol) { 14 | stream.skipToEnd(); return "string"; 15 | } else if (state.literal) { state.literal = false; } 16 | if (stream.sol()) { 17 | state.keyCol = 0; 18 | state.pair = false; 19 | state.pairStart = false; 20 | /* document start */ 21 | if(stream.match(/---/)) { return "def"; } 22 | /* document end */ 23 | if (stream.match(/\.\.\./)) { return "def"; } 24 | /* array list item */ 25 | if (stream.match(/\s*-\s+/)) { return 'meta'; } 26 | } 27 | /* pairs (associative arrays) -> key */ 28 | if (!state.pair && stream.match(/^\s*([a-z0-9\._-])+(?=\s*:)/i)) { 29 | state.pair = true; 30 | state.keyCol = stream.indentation(); 31 | return "atom"; 32 | } 33 | if (state.pair && stream.match(/^:\s*/)) { state.pairStart = true; return 'meta'; } 34 | 35 | /* inline pairs/lists */ 36 | if (stream.match(/^(\{|\}|\[|\])/)) { 37 | if (ch == '{') 38 | state.inlinePairs++; 39 | else if (ch == '}') 40 | state.inlinePairs--; 41 | else if (ch == '[') 42 | state.inlineList++; 43 | else 44 | state.inlineList--; 45 | return 'meta'; 46 | } 47 | 48 | /* list seperator */ 49 | if (state.inlineList > 0 && !esc && ch == ',') { 50 | stream.next(); 51 | return 'meta'; 52 | } 53 | /* pairs seperator */ 54 | if (state.inlinePairs > 0 && !esc && ch == ',') { 55 | state.keyCol = 0; 56 | state.pair = false; 57 | state.pairStart = false; 58 | stream.next(); 59 | return 'meta'; 60 | } 61 | 62 | /* start of value of a pair */ 63 | if (state.pairStart) { 64 | /* block literals */ 65 | if (stream.match(/^\s*(\||\>)\s*/)) { state.literal = true; return 'meta'; }; 66 | /* references */ 67 | if (stream.match(/^\s*(\&|\*)[a-z0-9\._-]+\b/i)) { return 'variable-2'; } 68 | /* numbers */ 69 | if (state.inlinePairs == 0 && stream.match(/^\s*-?[0-9\.\,]+\s?$/)) { return 'number'; } 70 | if (state.inlinePairs > 0 && stream.match(/^\s*-?[0-9\.\,]+\s?(?=(,|}))/)) { return 'number'; } 71 | /* keywords */ 72 | if (stream.match(keywordRegex)) { return 'keyword'; } 73 | } 74 | 75 | /* nothing found, continue */ 76 | state.pairStart = false; 77 | state.escaped = (ch == '\\'); 78 | stream.next(); 79 | return null; 80 | }, 81 | startState: function() { 82 | return { 83 | pair: false, 84 | pairStart: false, 85 | keyCol: 0, 86 | inlinePairs: 0, 87 | inlineList: 0, 88 | literal: false, 89 | escaped: false 90 | }; 91 | } 92 | }; 93 | }); 94 | 95 | CodeMirror.defineMIME("text/x-yaml", "yaml"); 96 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/theme/cobalt.css: -------------------------------------------------------------------------------- 1 | .cm-s-cobalt { background: #002240; color: white; } 2 | .cm-s-cobalt span.CodeMirror-selected { background: #b36539 !important; } 3 | .cm-s-cobalt .CodeMirror-gutter { background: #002240; border-right: 1px solid #aaa; } 4 | .cm-s-cobalt .CodeMirror-gutter-text { color: #d0d0d0; } 5 | .cm-s-cobalt .CodeMirror-cursor { border-left: 1px solid white !important; } 6 | 7 | .cm-s-cobalt span.cm-comment { color: #08f; } 8 | .cm-s-cobalt span.cm-atom { color: #845dc4; } 9 | .cm-s-cobalt span.cm-number, .cm-s-cobalt span.cm-attribute { color: #ff80e1; } 10 | .cm-s-cobalt span.cm-keyword { color: #ffee80; } 11 | .cm-s-cobalt span.cm-string { color: #3ad900; } 12 | .cm-s-cobalt span.cm-meta { color: #ff9d00; } 13 | .cm-s-cobalt span.cm-variable-2, .cm-s-cobalt span.cm-tag { color: #9effff; } 14 | .cm-s-cobalt span.cm-variable-3, .cm-s-cobalt span.cm-def { color: white; } 15 | .cm-s-cobalt span.cm-error { color: #9d1e15; } 16 | .cm-s-cobalt span.cm-bracket { color: #d8d8d8; } 17 | .cm-s-cobalt span.cm-builtin, .cm-s-cobalt span.cm-special { color: #ff9e59; } 18 | .cm-s-cobalt span.cm-link { color: #845dc4; } 19 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/theme/eclipse.css: -------------------------------------------------------------------------------- 1 | .cm-s-eclipse span.cm-meta {color: #FF1717;} 2 | .cm-s-eclipse span.cm-keyword { font-weight: bold; color: #7F0055; } 3 | .cm-s-eclipse span.cm-atom {color: #219;} 4 | .cm-s-eclipse span.cm-number {color: #164;} 5 | .cm-s-eclipse span.cm-def {color: #00f;} 6 | .cm-s-eclipse span.cm-variable {color: black;} 7 | .cm-s-eclipse span.cm-variable-2 {color: #0000C0;} 8 | .cm-s-eclipse span.cm-variable-3 {color: #0000C0;} 9 | .cm-s-eclipse span.cm-property {color: black;} 10 | .cm-s-eclipse span.cm-operator {color: black;} 11 | .cm-s-eclipse span.cm-comment {color: #3F7F5F;} 12 | .cm-s-eclipse span.cm-string {color: #2A00FF;} 13 | .cm-s-eclipse span.cm-string-2 {color: #f50;} 14 | .cm-s-eclipse span.cm-error {color: #f00;} 15 | .cm-s-eclipse span.cm-qualifier {color: #555;} 16 | .cm-s-eclipse span.cm-builtin {color: #30a;} 17 | .cm-s-eclipse span.cm-bracket {color: #cc7;} 18 | .cm-s-eclipse span.cm-tag {color: #170;} 19 | .cm-s-eclipse span.cm-attribute {color: #00c;} 20 | .cm-s-eclipse span.cm-link {color: #219;} 21 | 22 | .cm-s-eclipse .CodeMirror-matchingbracket { 23 | border:1px solid grey; 24 | color:black !important;; 25 | } 26 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/theme/elegant.css: -------------------------------------------------------------------------------- 1 | .cm-s-elegant span.cm-number, .cm-s-elegant span.cm-string, .cm-s-elegant span.cm-atom {color: #762;} 2 | .cm-s-elegant span.cm-comment {color: #262;font-style: italic;} 3 | .cm-s-elegant span.cm-meta {color: #555;font-style: italic;} 4 | .cm-s-elegant span.cm-variable {color: black;} 5 | .cm-s-elegant span.cm-variable-2 {color: #b11;} 6 | .cm-s-elegant span.cm-qualifier {color: #555;} 7 | .cm-s-elegant span.cm-keyword {color: #730;} 8 | .cm-s-elegant span.cm-builtin {color: #30a;} 9 | .cm-s-elegant span.cm-error {background-color: #fdd;} 10 | .cm-s-elegant span.cm-link {color: #762;} 11 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/theme/monokai.css: -------------------------------------------------------------------------------- 1 | /* Based on Sublime Text's Monokai theme */ 2 | 3 | .cm-s-monokai {background: #272822; color: #f8f8f2;} 4 | .cm-s-monokai span.CodeMirror-selected {background: #ffe792 !important;} 5 | .cm-s-monokai .CodeMirror-gutter {background: #272822; border-right: 0px;} 6 | .cm-s-monokai .CodeMirror-gutter-text {color: #d0d0d0;} 7 | .cm-s-monokai .CodeMirror-cursor {border-left: 1px solid #f8f8f0 !important;} 8 | 9 | .cm-s-monokai span.cm-comment {color: #75715e;} 10 | .cm-s-monokai span.cm-atom {color: #ae81ff;} 11 | .cm-s-monokai span.cm-number {color: #ae81ff;} 12 | 13 | .cm-s-monokai span.cm-property, .cm-s-monokai span.cm-attribute {color: #a6e22e;} 14 | .cm-s-monokai span.cm-keyword {color: #f92672;} 15 | .cm-s-monokai span.cm-string {color: #e6db74;} 16 | 17 | .cm-s-monokai span.cm-variable {color: #a6e22e;} 18 | .cm-s-monokai span.cm-variable-2 {color: #9effff;} 19 | .cm-s-monokai span.cm-def {color: #fd971f;} 20 | .cm-s-monokai span.cm-error {background: #f92672; color: #f8f8f0;} 21 | .cm-s-monokai span.cm-bracket {color: #f8f8f2;} 22 | .cm-s-monokai span.cm-tag {color: #f92672;} 23 | .cm-s-monokai span.cm-link {color: #ae81ff;} 24 | 25 | .cm-s-monokai .CodeMirror-matchingbracket { 26 | text-decoration: underline; 27 | color: white !important; 28 | } 29 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/theme/neat.css: -------------------------------------------------------------------------------- 1 | .cm-s-neat span.cm-comment { color: #a86; } 2 | .cm-s-neat span.cm-keyword { font-weight: bold; color: blue; } 3 | .cm-s-neat span.cm-string { color: #a22; } 4 | .cm-s-neat span.cm-builtin { font-weight: bold; color: #077; } 5 | .cm-s-neat span.cm-special { font-weight: bold; color: #0aa; } 6 | .cm-s-neat span.cm-variable { color: black; } 7 | .cm-s-neat span.cm-number, .cm-s-neat span.cm-atom { color: #3a3; } 8 | .cm-s-neat span.cm-meta {color: #555;} 9 | .cm-s-neat span.cm-link { color: #3a3; } 10 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/theme/night.css: -------------------------------------------------------------------------------- 1 | /* Loosely based on the Midnight Textmate theme */ 2 | 3 | .cm-s-night { background: #0a001f; color: #f8f8f8; } 4 | .cm-s-night span.CodeMirror-selected { background: #a8f !important; } 5 | .cm-s-night .CodeMirror-gutter { background: #0a001f; border-right: 1px solid #aaa; } 6 | .cm-s-night .CodeMirror-gutter-text { color: #f8f8f8; } 7 | .cm-s-night .CodeMirror-cursor { border-left: 1px solid white !important; } 8 | 9 | .cm-s-night span.cm-comment { color: #6900a1; } 10 | .cm-s-night span.cm-atom { color: #845dc4; } 11 | .cm-s-night span.cm-number, .cm-s-night span.cm-attribute { color: #ffd500; } 12 | .cm-s-night span.cm-keyword { color: #599eff; } 13 | .cm-s-night span.cm-string { color: #37f14a; } 14 | .cm-s-night span.cm-meta { color: #7678e2; } 15 | .cm-s-night span.cm-variable-2, .cm-s-night span.cm-tag { color: #99b2ff; } 16 | .cm-s-night span.cm-variable-3, .cm-s-night span.cm-def { color: white; } 17 | .cm-s-night span.cm-error { color: #9d1e15; } 18 | .cm-s-night span.cm-bracket { color: #8da6ce; } 19 | .cm-s-night span.cm-comment { color: #6900a1; } 20 | .cm-s-night span.cm-builtin, .cm-s-night span.cm-special { color: #ff9e59; } 21 | .cm-s-night span.cm-link { color: #845dc4; } 22 | -------------------------------------------------------------------------------- /lib/ledger_web/public/codemirror/theme/rubyblue.css: -------------------------------------------------------------------------------- 1 | .cm-s-rubyblue { font:13px/1.4em Trebuchet, Verdana, sans-serif; } /* - customized editor font - */ 2 | 3 | .cm-s-rubyblue { background: #112435; color: white; } 4 | .cm-s-rubyblue span.CodeMirror-selected { background: #0000FF !important; } 5 | .cm-s-rubyblue .CodeMirror-gutter { background: #1F4661; border-right: 7px solid #3E7087; min-width:2.5em; } 6 | .cm-s-rubyblue .CodeMirror-gutter-text { color: white; } 7 | .cm-s-rubyblue .CodeMirror-cursor { border-left: 1px solid white !important; } 8 | 9 | .cm-s-rubyblue span.cm-comment { color: #999; font-style:italic; } 10 | .cm-s-rubyblue span.cm-atom { color: #F4C20B; } 11 | .cm-s-rubyblue span.cm-number, .cm-s-rubyblue span.cm-attribute { color: #82C6E0; } 12 | .cm-s-rubyblue span.cm-keyword { color: #F0F; } 13 | .cm-s-rubyblue span.cm-string { color: #F08047; } 14 | .cm-s-rubyblue span.cm-meta { color: #F0F; } 15 | .cm-s-rubyblue span.cm-variable-2, .cm-s-rubyblue span.cm-tag { color: #7BD827; } 16 | .cm-s-rubyblue span.cm-variable-3, .cm-s-rubyblue span.cm-def { color: white; } 17 | .cm-s-rubyblue span.cm-error { color: #AF2018; } 18 | .cm-s-rubyblue span.cm-bracket { color: #F0F; } 19 | .cm-s-rubyblue span.cm-link { color: #F4C20B; } 20 | .cm-s-rubyblue span.CodeMirror-matchingbracket { color:#F0F !important; } 21 | .cm-s-rubyblue span.cm-builtin, .cm-s-rubyblue span.cm-special { color: #FF9D00; } 22 | -------------------------------------------------------------------------------- /lib/ledger_web/public/css/ledger.css: -------------------------------------------------------------------------------- 1 | .prettyprint { 2 | background-color: #FEFBF3; 3 | padding: 9px; 4 | border: 1px solid rgba(0, 0, 0, .2); 5 | webkit-box-shadow: 0 1px 2px rgba(0,0,0,.1); 6 | moz-box-shadow: 0 1px 2px rgba(0,0,0,.1); 7 | box-shadow: 0 1px 2px rgba(0,0,0,.1); 8 | border-image: initial; 9 | } 10 | 11 | body { 12 | margin-top: 60px; 13 | } 14 | 15 | -------------------------------------------------------------------------------- /lib/ledger_web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ledger/ledger-web/dc7802ac54598371f418b2b4b47e1fabaa909728/lib/ledger_web/public/favicon.ico -------------------------------------------------------------------------------- /lib/ledger_web/public/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ledger/ledger-web/dc7802ac54598371f418b2b4b47e1fabaa909728/lib/ledger_web/public/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /lib/ledger_web/public/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ledger/ledger-web/dc7802ac54598371f418b2b4b47e1fabaa909728/lib/ledger_web/public/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /lib/ledger_web/public/js/bootstrap-dropdown.js: -------------------------------------------------------------------------------- 1 | /* ============================================================ 2 | * bootstrap-dropdown.js v1.4.0 3 | * http://twitter.github.com/bootstrap/javascript.html#dropdown 4 | * ============================================================ 5 | * Copyright 2011 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ============================================================ */ 19 | 20 | 21 | !function( $ ){ 22 | 23 | "use strict" 24 | 25 | /* DROPDOWN PLUGIN DEFINITION 26 | * ========================== */ 27 | 28 | $.fn.dropdown = function ( selector ) { 29 | return this.each(function () { 30 | $(this).delegate(selector || d, 'click', function (e) { 31 | var li = $(this).parent('li') 32 | , isActive = li.hasClass('open') 33 | 34 | clearMenus() 35 | !isActive && li.toggleClass('open') 36 | return false 37 | }) 38 | }) 39 | } 40 | 41 | /* APPLY TO STANDARD DROPDOWN ELEMENTS 42 | * =================================== */ 43 | 44 | var d = 'a.menu, .dropdown-toggle' 45 | 46 | function clearMenus() { 47 | $(d).parent('li').removeClass('open') 48 | } 49 | 50 | $(function () { 51 | $('html').bind("click", clearMenus) 52 | $('body').dropdown( '[data-dropdown] a.menu, [data-dropdown] .dropdown-toggle' ) 53 | }) 54 | 55 | }( window.jQuery || window.ender ); 56 | -------------------------------------------------------------------------------- /lib/ledger_web/report.rb: -------------------------------------------------------------------------------- 1 | module LedgerWeb 2 | class Cell 3 | attr_reader :title, :value, :style 4 | attr_accessor :text, :align 5 | 6 | def initialize(title, value) 7 | @title = title 8 | @value = value 9 | @style = {} 10 | @text = value 11 | @align = 'left' 12 | end 13 | 14 | end 15 | 16 | class Report 17 | 18 | attr_accessor :error, :fields, :rows 19 | 20 | @@session = {} 21 | @@params = {} 22 | 23 | def self.session=(session) 24 | @@session = session 25 | end 26 | 27 | def self.session 28 | @@session 29 | end 30 | 31 | def self.params=(params) 32 | @@params = params 33 | end 34 | 35 | def self.params 36 | @@params 37 | end 38 | 39 | def self.from_query(query) 40 | params = { 41 | :from => Report.session[:from], 42 | :to => Report.session[:to] 43 | } 44 | 45 | @@params.each do |key, val| 46 | params[key.to_sym] = val 47 | end 48 | 49 | ds = LedgerWeb::Database.handle.fetch(query, params) 50 | report = self.new 51 | begin 52 | row = ds.first 53 | if row.nil? 54 | raise "No data" 55 | end 56 | ds.columns.each do |col| 57 | report.add_field col.to_s 58 | end 59 | 60 | ds.each do |row| 61 | vals = [] 62 | ds.columns.each do |col| 63 | vals << Cell.new(col.to_s, row[col]) 64 | end 65 | report.add_row(vals) 66 | end 67 | rescue Exception => e 68 | report.error = e 69 | end 70 | 71 | return report 72 | end 73 | 74 | def initialize 75 | @fields = [] 76 | @rows = [] 77 | end 78 | 79 | def add_field(field) 80 | @fields << field 81 | end 82 | 83 | def add_row(row) 84 | if row.length != @fields.length 85 | raise "row length not equal to fields length" 86 | end 87 | @rows << row 88 | end 89 | 90 | def each 91 | @rows.each do |row| 92 | yield row 93 | end 94 | end 95 | 96 | def pivot(column, sort_order) 97 | new_report = self.class.new 98 | 99 | bucket_column_index = 0 100 | self.fields.each_with_index do |f, i| 101 | if f == column 102 | bucket_column_index = i 103 | break 104 | else 105 | new_report.add_field(f) 106 | end 107 | end 108 | 109 | buckets = {} 110 | new_rows = {} 111 | 112 | self.each do |row| 113 | key = row[0, bucket_column_index].map { |r| r.value } 114 | bucket_name = row[bucket_column_index].value 115 | bucket_value = row[bucket_column_index + 1].value 116 | 117 | if not buckets.has_key? bucket_name 118 | buckets[bucket_name] = bucket_name 119 | end 120 | 121 | new_rows[key] ||= {} 122 | new_rows[key][bucket_name] = bucket_value 123 | end 124 | 125 | bucket_keys = buckets.keys.sort 126 | if sort_order && sort_order == 'desc' 127 | bucket_keys = bucket_keys.reverse 128 | end 129 | 130 | bucket_keys.each do |bucket| 131 | new_report.add_field(buckets[bucket]) 132 | end 133 | 134 | new_rows.each do |key, value| 135 | row = key.each_with_index.map { |k,i| Cell.new(new_report.fields[i], k) } 136 | bucket_keys.each do |b| 137 | row << Cell.new(b.to_s, value[b]) 138 | end 139 | 140 | new_report.add_row(row) 141 | end 142 | 143 | return new_report 144 | end 145 | end 146 | 147 | end 148 | 149 | def find_all_reports 150 | directories = LedgerWeb::Config.instance.get :report_directories 151 | 152 | reports = {} 153 | 154 | directories.each do |dir| 155 | if File.directory? dir 156 | Dir.glob(File.join(dir, "*.erb")) do |report| 157 | basename = File.basename(report).gsub('.erb', '') 158 | reports[basename] = 1 159 | end 160 | end 161 | end 162 | 163 | reports.keys.sort.map do |report| 164 | name = report.split(/_/).map { |w| w.capitalize }.join(" ") 165 | [report, name] 166 | end 167 | 168 | end 169 | -------------------------------------------------------------------------------- /lib/ledger_web/reports/savings_rate.erb: -------------------------------------------------------------------------------- 1 | <% @query = query do %> 2 | select 3 | month as "Month", 4 | income as "Income", 5 | expenses as "Expenses", 6 | rate as "Savings Rate", 7 | rate_12 as "Prev Year", 8 | rate_24 as "Two Years" 9 | from ( 10 | select 11 | month, 12 | income, 13 | expenses, 14 | rate, 15 | lead(rate, 12) over() as rate_12, 16 | lead(rate, 24) over() as rate_24 17 | from ( 18 | select 19 | month, 20 | income, 21 | expenses, 22 | income - expenses as savings, 23 | round(case when income = 0 then null else (income - expenses) / income * 100 end, 2) as rate 24 | from ( 25 | select 26 | date_trunc('month', xtn_date)::date as month, 27 | 0 - sum(case when account ~ 'Income' then amount else 0 end) as income, 28 | sum(case when account ~ 'Expenses' then amount else 0 end) as expenses 29 | from 30 | ledger 31 | group by 32 | date_trunc('month', xtn_date)::date 33 | ) x 34 | order by 35 | month desc 36 | ) x 37 | ) x 38 | where 39 | month <= :to 40 | and month >= :from 41 | <% end %> 42 | 45 |
46 |
47 | <%= table @query %> 48 |
49 |
-------------------------------------------------------------------------------- /lib/ledger_web/table.rb: -------------------------------------------------------------------------------- 1 | module LedgerWeb 2 | class Table 3 | 4 | attr_reader :attributes 5 | 6 | def initialize(report) 7 | @report = report 8 | @decorators = [] 9 | @attributes = {} 10 | yield self if block_given? 11 | end 12 | 13 | def decorate decorator 14 | @decorators << decorator 15 | end 16 | 17 | def clear_decorators 18 | @decorators.clear 19 | end 20 | 21 | def link href 22 | if_clause = href.delete(:if) 23 | href[href.keys.first] = LedgerWeb::Decorators::LinkDecorator.new(href.values.first) 24 | href[:if] = if_clause 25 | @decorators << href 26 | end 27 | 28 | def render 29 | body_rows = [] 30 | header_aligns = {} 31 | 32 | @report.each do |row| 33 | body_rows << row.each_with_index.map do |cell, cell_index| 34 | @decorators.each do |decorator| 35 | dec = decorator.dup 36 | if_clause = dec.delete(:if) 37 | matcher = dec.keys.first 38 | 39 | next unless matcher == :all || cell.title =~ matcher 40 | if if_clause 41 | next unless if_clause.call(cell, row) 42 | end 43 | cell = dec[matcher].decorate(cell, row) 44 | header_aligns[cell_index] = cell.align 45 | end 46 | 47 | style = cell.style.map { |key, val| "#{key}:#{val}"}.join(";") 48 | %Q{#{cell.text}} 49 | end.join("") 50 | end 51 | 52 | body = "" + body_rows.map { |r| "#{r}" }.join("") + "" 53 | header = "" + @report.fields.each_with_index.map { |f,i| "#{f}" }.join("") + "" 54 | 55 | attrs = attributes.map { |key,val| "#{key}=\"#{val}\"" }.join(" ") 56 | "#{header}#{body}
" 57 | end 58 | end 59 | 60 | end 61 | -------------------------------------------------------------------------------- /lib/ledger_web/version.rb: -------------------------------------------------------------------------------- 1 | module LedgerWeb 2 | VERSION = '1.5.2' 3 | end 4 | -------------------------------------------------------------------------------- /lib/ledger_web/views/error.erb: -------------------------------------------------------------------------------- 1 | 4 |

<%= @error.to_s %>

5 |

<%= @error.backtrace.join("
") %>

-------------------------------------------------------------------------------- /lib/ledger_web/views/help.erb: -------------------------------------------------------------------------------- 1 | 4 |

5 | There are some example reports in the Reports menu up there. Why don't you check them out to see what's possible? 6 |

7 | -------------------------------------------------------------------------------- /lib/ledger_web/views/layout.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | Ledger Web 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 16 | 17 | 18 | 36 |
37 | <%= yield %> 38 |
39 | 40 | 41 | -------------------------------------------------------------------------------- /lib/ledger_web/views/pdf.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | Ledger Web 4 | 39 | 40 | 41 |
42 | <%= yield %> 43 |
44 | 45 | 46 | -------------------------------------------------------------------------------- /lib/ledger_web/views/table.erb: -------------------------------------------------------------------------------- 1 | <% if report.error %> 2 |
3 |

<%= report.error %>

4 | <% if report.error.to_s != "No data" %> 5 |

<%= report.error.backtrace %>

6 | <% end %> 7 |
8 | <% else %> 9 | 10 | 11 | 12 | <% report.fields.each do |f| %> 13 | 14 | <% end %> 15 | 16 | 17 | 18 | <% report.each_row do |row| %> 19 | 20 | <% row.each do |val| %> 21 | 26 | <% end %> 27 | 28 | <% end %> 29 | 30 |
<%= f.title %>
22 | 23 | <%= linkify(links, row, val, (val[1].value_type == 'number' && ! val[0].nil?) ? sprintf("%0.2f", val[0]) : val[0]) %> 24 | 25 |
31 | <% end %> 32 | -------------------------------------------------------------------------------- /lib/ledger_web/views/visualization.erb: -------------------------------------------------------------------------------- 1 | 18 |
19 | -------------------------------------------------------------------------------- /lib/ledger_web/watcher.rb: -------------------------------------------------------------------------------- 1 | require 'directory_watcher' 2 | 3 | module LedgerWeb 4 | class Watcher 5 | def self.run! 6 | directory = LedgerWeb::Config.instance.get :watch_directory 7 | glob = "*" 8 | 9 | if directory.nil? 10 | directory = File.dirname(LedgerWeb::Config.instance.get :ledger_file) 11 | glob = File.basename(LedgerWeb::Config.instance.get :ledger_file) 12 | end 13 | 14 | @@dw = DirectoryWatcher.new directory, :glob => glob 15 | @@dw.interval = LedgerWeb::Config.instance.get :watch_interval 16 | @@dw.stable = LedgerWeb::Config.instance.get :watch_stable_count 17 | 18 | LedgerWeb::Database.connect 19 | 20 | @@dw.add_observer do |*args| 21 | args.each do |event| 22 | if event.type == :stable 23 | puts "Loading database" 24 | LedgerWeb::Database.run_migrations 25 | file = LedgerWeb::Database.dump_ledger_to_csv 26 | count = LedgerWeb::Database.load_database(file) 27 | puts "Loaded #{count} records" 28 | end 29 | end 30 | end 31 | 32 | @@dw.start 33 | 34 | end 35 | 36 | def self.stop! 37 | @@dw.stop 38 | end 39 | end 40 | 41 | end 42 | -------------------------------------------------------------------------------- /test/config_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + "/spec_helper") 2 | require 'ledger_web/config' 3 | 4 | describe LedgerWeb::Config do 5 | describe "#initialize" do 6 | 7 | it "should get and set simple values" do 8 | conf = LedgerWeb::Config.new do |config| 9 | config.set :key_one, "value one" 10 | config.set :key_two, "value two" 11 | end 12 | 13 | conf.get(:key_one).should eq("value one") 14 | conf.get(:key_two).should eq("value two") 15 | end 16 | 17 | it "should get and run simple hooks" do 18 | conf = LedgerWeb::Config.new do |config| 19 | config.add_hook :sample do |val| 20 | val[:foo] = val[:foo] * 2 21 | end 22 | end 23 | 24 | test_val = { :foo => 2 } 25 | conf.run_hooks(:sample, test_val) 26 | test_val[:foo].should eq(4) 27 | end 28 | end 29 | 30 | describe "#override_with" do 31 | it "should override keys" do 32 | conf_one = LedgerWeb::Config.new do |config| 33 | config.set :key_one, "value one" 34 | end 35 | 36 | conf_two = LedgerWeb::Config.new do |config| 37 | config.set :key_one, "value two" 38 | end 39 | 40 | conf_one.override_with(conf_two) 41 | 42 | conf_one.get(:key_one).should eq("value two") 43 | end 44 | 45 | it "should append hooks" do 46 | conf_one = LedgerWeb::Config.new do |config| 47 | config.add_hook(:sample) do |val| 48 | val[:list] << 'one' 49 | end 50 | end 51 | 52 | conf_two = LedgerWeb::Config.new do |config| 53 | config.add_hook(:sample) do |val| 54 | val[:list] << 'two' 55 | end 56 | end 57 | 58 | conf_one.override_with(conf_two) 59 | 60 | test_val = {:list => []} 61 | conf_one.run_hooks(:sample, test_val) 62 | test_val[:list].should eq(['one', 'two']) 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /test/fixtures/complex_costs.dat: -------------------------------------------------------------------------------- 1 | "7288","2011/08/30","Tttttttttt Pagamento","Liabilities:Funds:Retirement Plan","""B PGBL F10""","103.59","false","true","","103.59 ""B PGBL F10"" {R$4.2701032918} [2011/08/30]" 2 | -------------------------------------------------------------------------------- /test/fixtures/quoted.dat: -------------------------------------------------------------------------------- 1 | 2012/01/01 * Transaction One 2 | Assets:Savings 100.00 "Foo 123" 3 | Assets:Checking 200.00 "Foo 123" 4 | Equity:Opening Balances 5 | 6 | 2012/01/02 * Lunch 7 | Expenses:Lunch 10.00 "Foo 123" 8 | Assets:Checking -------------------------------------------------------------------------------- /test/fixtures/small.dat: -------------------------------------------------------------------------------- 1 | 2012/01/01 * Transaction One 2 | Assets:Savings $100.00 3 | Assets:Checking $200.00 4 | Equity:Opening Balances 5 | 6 | 2012/01/02 * Lunch 7 | Expenses:Lunch $10.00 8 | Assets:Checking -------------------------------------------------------------------------------- /test/report_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + "/spec_helper") 2 | require 'ledger_web/report' 3 | require 'ledger_web/helpers' 4 | 5 | describe LedgerWeb::Report do 6 | describe "#from_query" do 7 | let(:helpers) { TestHelper.new } 8 | it "should run the query" do 9 | 10 | LedgerWeb::Report.session = {:from => '2012/01/01', :to => '2012/01/01'} 11 | 12 | load_fixture('small') 13 | 14 | report = LedgerWeb::Report.from_query("select count(1) as foo from ledger") 15 | rows = [] 16 | report.each do |row| 17 | rows << row 18 | end 19 | 20 | rows[0][0].value.should eq(5) 21 | rows[0][0].align.should eq('left') 22 | end 23 | 24 | it "should respect defaults" do 25 | LedgerWeb::Report.params = {} 26 | helpers.default('foo', 'bar') 27 | 28 | report = LedgerWeb::Report.from_query("select :foo as foo") 29 | rows = [] 30 | report.each do |row| 31 | rows << row 32 | end 33 | 34 | rows[0][0].value.should eq("bar") 35 | end 36 | 37 | end 38 | 39 | describe "#pivot" do 40 | it "should create the correct fields" do 41 | LedgerWeb::Report.session = {:from => '2012/01/01', :to => '2012/01/01'} 42 | load_fixture('small') 43 | 44 | report = LedgerWeb::Report.from_query("select xtn_month, account, sum(amount) from ledger group by xtn_month, account") 45 | report = report.pivot("account", "asc") 46 | 47 | report.fields.should eq([ 48 | 'xtn_month', 49 | 'Assets:Checking', 50 | 'Assets:Savings', 51 | 'Equity:Opening Balances', 52 | 'Expenses:Lunch' 53 | ]) 54 | 55 | end 56 | 57 | it "should put the values in the right place" do 58 | LedgerWeb::Report.session = {:from => '2012/01/01', :to => '2012/01/01'} 59 | load_fixture('small') 60 | 61 | report = LedgerWeb::Report.from_query("select xtn_month, account, sum(amount)::integer from ledger group by xtn_month, account") 62 | report = report.pivot("account", "asc") 63 | 64 | report.rows[0].map(&:value).should eq( 65 | [Date.new(2012,1,1), 190, 100, -300, 10] 66 | ) 67 | end 68 | 69 | it "should respect other date formats" do 70 | LedgerWeb::Report.session = {:from => '2012-01-01', :to => '2012-01-01'} 71 | load_fixture('small') 72 | 73 | report = LedgerWeb::Report.from_query("select xtn_month, account, sum(amount)::integer from ledger group by xtn_month, account") 74 | report = report.pivot("account", "asc") 75 | 76 | report.rows[0].map(&:value).should eq( 77 | [Date.new(2012,1,1), 190, 100, -300, 10] 78 | ) 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /test/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $:.unshift(File.join(File.dirname(__FILE__), "..", "lib")) 2 | 3 | require 'rspec' 4 | require 'ledger_web/config' 5 | require 'ledger_web/db' 6 | require 'ledger_web/report' 7 | require 'ledger_web/helpers' 8 | require 'database_cleaner' 9 | 10 | RSpec.configure do |config| 11 | 12 | config.before(:suite) do 13 | 14 | system 'createdb ledger-test' 15 | LedgerWeb::Config.should_load_user_config = false 16 | LedgerWeb::Config.instance.set :database_url, 'postgres://localhost/ledger-test' 17 | LedgerWeb::Database.connect 18 | LedgerWeb::Database.run_migrations 19 | 20 | DatabaseCleaner.strategy = :truncation 21 | DatabaseCleaner.clean_with(:truncation) 22 | end 23 | 24 | config.before(:each) do 25 | DatabaseCleaner.start 26 | end 27 | 28 | config.after(:each) do 29 | DatabaseCleaner.clean 30 | end 31 | 32 | config.after(:suite) do 33 | LedgerWeb::Database.close 34 | system 'dropdb ledger-test' 35 | end 36 | 37 | end 38 | 39 | def set_config(key, val) 40 | LedgerWeb::Config.instance.set key, val 41 | end 42 | 43 | def fixture(name) 44 | File.join(File.dirname(__FILE__), "fixtures", name + ".dat") 45 | end 46 | 47 | def convert_bd_to_string(objs) 48 | objs.map do |obj| 49 | obj.each do |k,v| 50 | if v.is_a? BigDecimal 51 | obj[k] = v.truncate(2).to_s('F') 52 | end 53 | end 54 | obj 55 | end 56 | end 57 | 58 | def load_fixture(name) 59 | set_config :ledger_file, fixture(name) 60 | file = LedgerWeb::Database.dump_ledger_to_csv 61 | LedgerWeb::Database.load_database(file) 62 | end 63 | 64 | def field(name, type, css_class) 65 | LedgerWeb::Field.new(name, type, css_class) 66 | end 67 | 68 | def string_field(name) 69 | field(name, 'string', 'pull-left') 70 | end 71 | 72 | def number_field(name) 73 | field(name, 'number', 'pull-right') 74 | end 75 | 76 | class TestHelper 77 | include LedgerWeb::Helpers 78 | end 79 | --------------------------------------------------------------------------------