├── .gitignore ├── .rubocop.yml ├── .travis.yml ├── CHANGELOG.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── bin └── csvq ├── csv_query.gemspec ├── lib ├── csv_query.rb └── csv_query │ ├── command_line.rb │ ├── database.rb │ ├── outputter.rb │ ├── query.rb │ ├── query_builder.rb │ └── version.rb └── test ├── csv_query ├── database_test.rb ├── outputter_test.rb ├── query_builder_test.rb └── query_test.rb ├── fixtures └── simple.csv └── test_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | pkg/* 2 | *.gem 3 | .bundle 4 | Gemfile.lock 5 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | DisplayCopNames: true 3 | Exclude: 4 | - 'bin/*' 5 | - 'db/schema.rb' 6 | - 'lib/templates/**/*.rb' 7 | - 'node_modules/**/*' 8 | TargetRubyVersion: 2.3 9 | 10 | Layout/AlignParameters: 11 | EnforcedStyle: with_fixed_indentation 12 | 13 | Layout/DotPosition: 14 | EnforcedStyle: trailing 15 | 16 | Layout/EndAlignment: 17 | EnforcedStyleAlignWith: variable 18 | 19 | Layout/IndentHash: 20 | EnforcedStyle: consistent 21 | 22 | Layout/MultilineMethodCallIndentation: 23 | EnforcedStyle: indented 24 | 25 | Layout/MultilineOperationIndentation: 26 | EnforcedStyle: indented 27 | 28 | Layout/SpaceInsideHashLiteralBraces: 29 | EnforcedStyle: no_space 30 | 31 | Lint/AmbiguousBlockAssociation: 32 | Exclude: 33 | - "spec/**/*" 34 | 35 | Metrics/AbcSize: 36 | Exclude: 37 | - 'db/migrate/*' 38 | 39 | Metrics/BlockLength: 40 | Exclude: 41 | - 'config/**/*.rb' 42 | - 'lib/**/*.rake' 43 | - 'Guardfile' 44 | - 'Rakefile' 45 | - 'spec/**/*' 46 | - 'test/**/*' 47 | 48 | Metrics/ClassLength: 49 | Exclude: 50 | - 'app/dashboards/*' 51 | 52 | Metrics/LineLength: 53 | Exclude: 54 | - 'app/dashboards/*' 55 | 56 | Metrics/MethodLength: 57 | Exclude: 58 | - 'db/migrate/*' 59 | 60 | Naming/FileName: 61 | Enabled: false 62 | 63 | Rails/DynamicFindBy: 64 | Enabled: false 65 | 66 | Style/BlockDelimiters: 67 | Enabled: false 68 | 69 | Style/Documentation: 70 | Enabled: false 71 | 72 | Style/HashSyntax: 73 | EnforcedStyle: hash_rockets 74 | 75 | Style/IfUnlessModifier: 76 | Enabled: false 77 | 78 | Style/SafeNavigation: 79 | Enabled: false 80 | 81 | Style/StringLiterals: 82 | EnforcedStyle: double_quotes 83 | 84 | Style/SymbolArray: 85 | EnforcedStyle: brackets 86 | 87 | Style/WordArray: 88 | EnforcedStyle: brackets 89 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.3.7 4 | - 2.4.4 5 | - 2.5.1 6 | - 2.6.0-preview2 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | ## [2.0.0] - 2018-08-13 4 | 5 | ### Removed 6 | 7 | - Remove support for older Ruby releases that are no longer officially supported. The gem might still work, but we'll only target Rubies listed on https://www.ruby-lang.org/en/downloads/ going forward as official supported target versions. 8 | 9 | [2.0.0]: https://github.com/koppen/csv_query/compare/v1.0.4...v2.0.0 10 | 11 | ## [1.0.4] - 2015-01-10 12 | 13 | ### Fixed 14 | 15 | - Actually take user supplied options like `delimiter` and `select` into account instead of always overriding them with the defaults. 16 | 17 | [1.0.4]: https://github.com/koppen/csv_query/compare/v1.0.3...v1.0.4 18 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | # Specify your gem's dependencies in csv_query.gemspec 6 | gemspec 7 | 8 | gem "coveralls", :require => false 9 | gem "minitest" 10 | gem "rake" 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Jakob Skjerning 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 all 11 | 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 THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CSV Query - Use SQL to query CSV data 2 | ===================================== 3 | 4 | CSV Query is a command line tool that allows you to run SQL queries on data 5 | stored in CSV files. 6 | 7 | For example: 8 | 9 | $ csvq --select "count(*)" --where "name='Jakob'" sample.csv 10 | count(*) 11 | -------- 12 | 1 13 | 14 | [![Build Status](https://secure.travis-ci.org/koppen/csv_query.png?branch=master)](https://travis-ci.org/koppen/csv_query) [![Code Climate](https://codeclimate.com/github/koppen/csv_query/badges/gpa.svg)](https://codeclimate.com/github/koppen/csv_query) 15 | 16 | Assumptions 17 | ----------- 18 | 19 | The first row of data is assumed to be headers. 20 | 21 | All fields are created as `VARCHAR(255)`, which hopefully works for most cases. 22 | 23 | Behind the scenes 24 | ----------------- 25 | 26 | CSV Query loads the CSV data into an in-memory SQLite database. Thus, the SQL 27 | queries need to be SQLite-flavored where applicable. 28 | 29 | Alternative/related projects 30 | ---------------------------- 31 | 32 | * [csvq.py - Python variant that does almost the same thing](http://www.gl1tch.com/~lukewarm/software/csvq/) 33 | * [CSVql - A query language for CSV files](https://github.com/ondrasej/CSVql) 34 | 35 | License 36 | ------- 37 | 38 | Licensed under the MIT License. See LICENSE for details. 39 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "bundler" 4 | Bundler::GemHelper.install_tasks 5 | 6 | require "rake/testtask" 7 | Rake::TestTask.new do |t| 8 | t.pattern = "test/**/*_test.rb" 9 | end 10 | 11 | task :default => :test 12 | -------------------------------------------------------------------------------- /bin/csvq: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "csv_query" 4 | require "csv_query/command_line" 5 | 6 | CsvQuery::CommandLine.run 7 | -------------------------------------------------------------------------------- /csv_query.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | $LOAD_PATH.push File.expand_path("lib", __dir__) 4 | require "csv_query/version" 5 | 6 | Gem::Specification.new do |s| 7 | s.name = "csv_query" 8 | s.version = CsvQuery::VERSION 9 | s.platform = Gem::Platform::RUBY 10 | s.authors = ["Jakob Skjerning"] 11 | s.email = ["jakob@mentalized.net"] 12 | s.homepage = "http://mentalized.net" 13 | s.summary = "Use SQL to query CSV data" 14 | s.description = "CSV Query allows you to run SQL queries against data " \ 15 | "stored in CSV files." 16 | 17 | s.required_ruby_version = ">= 2.3" 18 | s.add_dependency("sqlite3") 19 | 20 | s.files = `git ls-files`.split("\n") 21 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 22 | s.executables = `git ls-files -- bin/*`.split("\n").map do |f| 23 | File.basename(f) 24 | end 25 | s.require_paths = ["lib"] 26 | end 27 | -------------------------------------------------------------------------------- /lib/csv_query.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module CsvQuery 4 | end 5 | -------------------------------------------------------------------------------- /lib/csv_query/command_line.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "csv_query/outputter" 4 | require "csv_query/query" 5 | 6 | module CsvQuery 7 | class CommandLine 8 | # rubocop:disable Metrics/AbcSize, Metrics/MethodLength 9 | def self.parse_options_from_commandline 10 | options = {} 11 | 12 | OptionParser.new do |opts| 13 | opts.banner = "Usage: csvq [options] [CSV file]" 14 | opts.separator "" 15 | opts.separator "Specific options:" 16 | opts.on( 17 | "-d", 18 | "--delimiter DELIMITER", 19 | "Sets the DELIMITER used between fields in the CSV data. " \ 20 | "Default: #{Query::DEFAULT_OPTIONS[:delimiter].inspect}" 21 | ) do |d| 22 | options[:delimiter] = d 23 | end 24 | opts.on( 25 | "-H", 26 | "--headers HEADERS", 27 | "Comma separated list of headers. Default: First row of CSV data." 28 | ) do |h| 29 | options[:headers] = h 30 | end 31 | opts.on( 32 | "-q", 33 | "--query SQL", 34 | "The SQL query to run on the dataset. " \ 35 | "The table name to select data from is named \"csv\", " \ 36 | "ie \"--query 'SELECT * FROM csv'\" recreates default behavior. " \ 37 | "If specified --select and --where will be ignored." 38 | ) do |q| 39 | options[:sql_query] = q 40 | end 41 | opts.on( 42 | "-s", 43 | "--select SQL", 44 | "The SQL statement to select what fields to return. " \ 45 | "Unused if --query is given. " \ 46 | "Default: #{Query::DEFAULT_OPTIONS[:select].inspect}." 47 | ) do |s| 48 | options[:select] = s 49 | end 50 | opts.on( 51 | "-w", 52 | "--where SQL", 53 | "The SQL conditions to use for quering the dataset. " \ 54 | "Unused if --query is given." 55 | ) do |w| 56 | options[:where] = w 57 | end 58 | 59 | opts.on_tail("-h", "--help", "Show this message.") do 60 | puts opts 61 | exit 62 | end 63 | end.parse! 64 | 65 | options 66 | end 67 | # rubocop:enable Metrics/AbcSize, Metrics/MethodLength 68 | 69 | def self.run 70 | options = parse_options_from_commandline 71 | csv_data = ARGF.read 72 | outputter = CsvQuery::Outputter 73 | CsvQuery::Query.new(csv_data, outputter, options).run 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /lib/csv_query/database.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "sqlite3" 4 | 5 | module CsvQuery 6 | # Wraps a SQLite in-memory database with a single table named csv. 7 | class Database 8 | attr_reader :database 9 | 10 | def initialize(csv) 11 | @database = SQLite3::Database.new(":memory:") 12 | @columns = csv.headers 13 | create_table(@columns) 14 | end 15 | 16 | def import_data_from_csv(csv) 17 | columns = csv.headers 18 | 19 | sql = "INSERT INTO csv VALUES (#{(['?'] * columns.size).join(',')})" 20 | statement = database.prepare(sql) 21 | 22 | csv.each do |row| 23 | statement.execute(row.fields) 24 | end 25 | end 26 | 27 | # Returns the results of sql. First row of the resultset contains the column 28 | # names 29 | def query(sql) 30 | database.execute2(sql) 31 | end 32 | 33 | private 34 | 35 | def create_table(column_names) 36 | column_definitions = column_names.collect do |name| 37 | "\"#{name}\" VARCHAR(255)" 38 | end 39 | database.execute "CREATE TABLE csv (#{column_definitions.join(', ')})" 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/csv_query/outputter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module CsvQuery 4 | class Outputter 5 | attr_reader :results 6 | 7 | def self.output(results) 8 | new(results).output 9 | end 10 | 11 | def initialize(results) 12 | @results = results 13 | end 14 | 15 | def output 16 | results.each_with_index do |result, index| 17 | puts format_string % result 18 | if index.zero? 19 | puts separator_line 20 | end 21 | end 22 | end 23 | 24 | private 25 | 26 | def column_widths 27 | results.each_with_object([0] * number_of_columns) { |row, column_widths| 28 | row.each_with_index do |value, index| 29 | width = value.to_s.size 30 | column_widths[index] = width if width > column_widths[index] 31 | end 32 | } 33 | end 34 | 35 | def format_string 36 | return @format_string if @format_string 37 | 38 | format_strings = column_widths.collect { |width| 39 | "%#{width}s" 40 | } 41 | @format_string = format_strings.join(" | ") 42 | end 43 | 44 | def number_of_columns 45 | if results.first 46 | results.first.size 47 | else 48 | 0 49 | end 50 | end 51 | 52 | def separator_line 53 | column_widths.collect { |width| "-" * width }.join("-+-") 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/csv_query/query.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "csv" 4 | require "optparse" 5 | require "sqlite3" 6 | 7 | require "csv_query/database" 8 | require "csv_query/query_builder" 9 | 10 | module CsvQuery 11 | class Query 12 | attr_reader :csv_data, :options 13 | 14 | DEFAULT_OPTIONS = { 15 | :delimiter => "," 16 | }.freeze 17 | 18 | def initialize(csv_data, outputter, options = {}) 19 | @csv_data = csv_data 20 | @outputter = outputter 21 | @options = DEFAULT_OPTIONS.merge(options) 22 | end 23 | 24 | def run 25 | results = run_query 26 | output_results_table(results) 27 | end 28 | 29 | private 30 | 31 | def build_sql_query 32 | sql_query = options[:sql_query] 33 | return sql_query unless sql_query.nil? 34 | 35 | QueryBuilder.new(options).call 36 | end 37 | 38 | def create_database_with_data_from_csv 39 | database = CsvQuery::Database.new(csv) 40 | database.import_data_from_csv(csv) 41 | database 42 | end 43 | 44 | def csv 45 | @csv ||= parse_csv_data 46 | end 47 | 48 | def database 49 | @database ||= create_database_with_data_from_csv 50 | end 51 | 52 | def headers 53 | options[:headers] || :first_row 54 | end 55 | 56 | def output_results_table(results) 57 | @outputter.output(results) 58 | end 59 | 60 | def parse_csv_data 61 | csv_options = { 62 | :headers => headers, 63 | :col_sep => options[:delimiter] 64 | } 65 | 66 | CSV.parse(csv_data, csv_options) 67 | end 68 | 69 | def run_query 70 | database.query(sql_query) 71 | end 72 | 73 | def sql_query 74 | @sql_query ||= build_sql_query 75 | end 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /lib/csv_query/query_builder.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module CsvQuery 4 | class QueryBuilder 5 | DEFAULT_OPTIONS = { 6 | :select => "*" 7 | }.freeze 8 | 9 | attr_reader :options 10 | 11 | def initialize(options) 12 | @options = DEFAULT_OPTIONS.merge(options) 13 | end 14 | 15 | def call 16 | build_sql_query 17 | end 18 | 19 | private 20 | 21 | def build_sql_query 22 | [ 23 | select_statement, 24 | from_statement, 25 | where_statement 26 | ].join(" ").strip 27 | end 28 | 29 | def from_statement 30 | "FROM csv" 31 | end 32 | 33 | def select_statement 34 | "SELECT #{options[:select]}" 35 | end 36 | 37 | def where_statement 38 | return nil unless options[:where] 39 | "WHERE #{options[:where]}" 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/csv_query/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module CsvQuery 4 | VERSION = "2.0.0" 5 | end 6 | -------------------------------------------------------------------------------- /test/csv_query/database_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../test_helper" 4 | 5 | require "csv_query/database" 6 | 7 | describe CsvQuery::Database do 8 | def csv_data 9 | @csv_data ||= CSV.parse("Bar", :headers => ["Foo"]) 10 | end 11 | 12 | def build_database 13 | CsvQuery::Database.new(csv_data) 14 | end 15 | 16 | describe ".new" do 17 | it "creates SQLite database" do 18 | database = build_database 19 | database.database.class.must_equal(SQLite3::Database) 20 | end 21 | 22 | it "creates a database table" do 23 | database = build_database 24 | 25 | columns = database.database.table_info("csv") 26 | columns.collect { |column| column["name"] }.must_equal(["Foo"]) 27 | end 28 | end 29 | 30 | describe "#import_csv" do 31 | it "imports data from csv" do 32 | database = build_database 33 | database.import_data_from_csv(csv_data) 34 | 35 | results = database.database.query("SELECT * FROM csv") 36 | results.to_a.must_equal([["Bar"]]) 37 | end 38 | end 39 | 40 | describe "#query" do 41 | it "returns headers and results as arrays" do 42 | database = build_database 43 | database.import_data_from_csv(csv_data) 44 | 45 | results = database.query("SELECT * FROM csv") 46 | results.to_a.must_equal([["Foo"], ["Bar"]]) 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /test/csv_query/outputter_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../test_helper" 4 | 5 | require "csv_query/outputter" 6 | 7 | describe CsvQuery::Outputter do 8 | describe "creating a new instance" do 9 | it "stores results for later access" do 10 | results = [["Foo"]] 11 | CsvQuery::Outputter.new(results).results.must_equal([["Foo"]]) 12 | end 13 | end 14 | 15 | describe ".output" do 16 | it "outputs results to STDOUT" do 17 | output = capture_stdout do 18 | CsvQuery::Outputter.output([["Foo"]]) 19 | end 20 | output.must_equal("Foo\n---\n") 21 | end 22 | end 23 | 24 | describe "#output" do 25 | it "outputs results to STDOUT" do 26 | results = [ 27 | ["Foo", "Bar"], 28 | ["Baz", "Qux"] 29 | ] 30 | 31 | output = capture_stdout do 32 | CsvQuery::Outputter.output(results) 33 | end 34 | 35 | output.must_equal <<~EXPECTED 36 | Foo | Bar 37 | ----+---- 38 | Baz | Qux 39 | EXPECTED 40 | end 41 | 42 | it "adapts column widths to result widths" do 43 | results = [ 44 | ["A", "B", "Somewhat long header"], 45 | ["1", "Somewhat long result", "3"] 46 | ] 47 | 48 | output = capture_stdout do 49 | CsvQuery::Outputter.output(results) 50 | end 51 | 52 | output.must_equal <<~EXPECTED 53 | A | B | Somewhat long header 54 | --+----------------------+--------------------- 55 | 1 | Somewhat long result | 3 56 | EXPECTED 57 | end 58 | 59 | it "works with numeric results" do 60 | results = [ 61 | ["Animal", "COUNT(*)", "Average"], 62 | ["Monkeys", 12, 123.456] 63 | ] 64 | 65 | output = capture_stdout do 66 | CsvQuery::Outputter.output(results) 67 | end 68 | 69 | output.must_equal <<~EXPECTED 70 | Animal | COUNT(*) | Average 71 | --------+----------+-------- 72 | Monkeys | 12 | 123.456 73 | EXPECTED 74 | end 75 | 76 | it "returns nothing if result is empty" do 77 | results = [] 78 | 79 | output = capture_stdout do 80 | CsvQuery::Outputter.output(results) 81 | end 82 | 83 | output.must_equal("") 84 | end 85 | end 86 | 87 | it "outputs nothing if results are empty" do 88 | output = capture_stdout do 89 | CsvQuery::Outputter.output([]) 90 | end 91 | output.must_equal "" 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /test/csv_query/query_builder_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../test_helper" 4 | 5 | require "csv_query/query_builder" 6 | 7 | describe CsvQuery::QueryBuilder do 8 | def described_class 9 | CsvQuery::QueryBuilder 10 | end 11 | 12 | describe "creating a new instance" do 13 | it "stores options later use" do 14 | query_builder = described_class.new(:foo => "bar") 15 | query_builder.options.must_equal(:select => "*", :foo => "bar") 16 | end 17 | 18 | it "uses default options" do 19 | query_builder = described_class.new(:foo => "bar") 20 | query_builder.options[:select].must_equal("*") 21 | end 22 | 23 | it "merges options with default options" do 24 | query = described_class.new(:where => "stuff") 25 | query.options[:select].must_equal("*") 26 | query.options[:where].must_equal("stuff") 27 | end 28 | end 29 | 30 | describe "#call" do 31 | it "returns a string with an SQL query" do 32 | query_builder = CsvQuery::QueryBuilder.new({}) 33 | result = query_builder.call 34 | result.must_equal("SELECT * FROM csv") 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /test/csv_query/query_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../test_helper" 4 | 5 | require "csv_query/query" 6 | require "csv_query/outputter" 7 | 8 | describe CsvQuery::Query do 9 | class CapturingOutputter 10 | attr_reader :lines 11 | 12 | def output(lines) 13 | @lines = lines 14 | end 15 | end 16 | 17 | describe "creating a new instance" do 18 | it "stores CSV data for later use" do 19 | query = CsvQuery::Query.new("foo", CsvQuery::Outputter, :bar => "baz") 20 | query.csv_data.must_equal("foo") 21 | end 22 | 23 | it "uses default options" do 24 | query = CsvQuery::Query.new("foo", CsvQuery::Outputter, :bar => "baz") 25 | query.options[:delimiter].must_equal(",") 26 | end 27 | 28 | it "merges options with default options" do 29 | query = CsvQuery::Query.new( 30 | "foo", 31 | CsvQuery::Outputter, 32 | :delimiter => ";", 33 | :select => "stuff" 34 | ) 35 | query.options[:delimiter].must_equal(";") 36 | query.options[:select].must_equal("stuff") 37 | end 38 | 39 | it "preserves extra options" do 40 | query = CsvQuery::Query.new("foo", CsvQuery::Outputter, :bar => "baz") 41 | query.options[:bar].must_equal("baz") 42 | end 43 | end 44 | 45 | describe "#run" do 46 | it "outputs results" do 47 | csv_data = "Foo\nBar" 48 | results = [["Foo"], ["Bar"]] 49 | 50 | outputter = CapturingOutputter.new 51 | 52 | query = CsvQuery::Query.new(csv_data, outputter) 53 | query.run 54 | 55 | outputter.lines.must_equal(results) 56 | end 57 | 58 | it "filters results using :where option" do 59 | csv_data = "Foo\nBar\nBaz" 60 | results = [["Foo"], ["Baz"]] 61 | 62 | outputter = CapturingOutputter.new 63 | 64 | query = CsvQuery::Query.new(csv_data, outputter, :where => "Foo='Baz'") 65 | query.run 66 | 67 | outputter.lines.must_equal(results) 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /test/fixtures/simple.csv: -------------------------------------------------------------------------------- 1 | Name,Age,Gender 2 | Jakob,36,Male 3 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "coveralls" 4 | Coveralls.wear! 5 | 6 | require "minitest/autorun" 7 | require "stringio" 8 | 9 | # Require files from the project lib-directory 10 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib")) 11 | 12 | def capture_stdout 13 | existing_stream = $stdout 14 | $stdout = StringIO.new 15 | yield 16 | output = $stdout.string 17 | $stdout = existing_stream 18 | output 19 | end 20 | --------------------------------------------------------------------------------