Skip to content

Commit

Permalink
Actually initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
condor committed Feb 5, 2019
1 parent 5445d01 commit 27a18af
Show file tree
Hide file tree
Showing 18 changed files with 572 additions and 1 deletion.
7 changes: 7 additions & 0 deletions .gitignore
Expand Up @@ -48,3 +48,10 @@ build-iPhoneSimulator/

# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
.rvmrc

/.idea/

# Avoid the merging of the build intermediates
/target/

/lib/altsv/native.bundle
7 changes: 7 additions & 0 deletions .travis.yml
@@ -0,0 +1,7 @@
---
sudo: false
language: ruby
cache: bundler
rvm:
- 2.6.0
before_install: gem install bundler -v 1.17.2
41 changes: 41 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions Cargo.toml
@@ -0,0 +1,11 @@
[package]
name = "altsv"
version = "0.0.1"
edition = "2018"

[lib]
crate-type = ["cdylib"]

[dependencies]
helix = "0.7.5"
libcruby-sys = "0.7.5"
6 changes: 6 additions & 0 deletions Gemfile
@@ -0,0 +1,6 @@
source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

# Specify your gem's dependencies in altsv.gemspec
gemspec
71 changes: 70 additions & 1 deletion README.md
@@ -1,2 +1,71 @@
# altsv
Alternative LTSV handling gem partially written in Rust.

An ALTernative LTSV Parser / Dumper gem partially written in Rust.

## Installation

Add this line to your application's Gemfile:

```ruby
gem 'altsv'
```

And then execute:

$ bundle

Or install it yourself as:

$ gem install altsv

## Usage

At first, you should require altsv:

require 'altsv'

In addition, if you manage gems with bundler, you should add the statement below into your Gemfile:

gem 'altsv'


### parsing LTSV

# parse string
string = "label1:value1\tlabel2:value2"
values = Altsv.parse(string) # => [{:label1 => "value1", :label2 => "value2"}]

# parse via stream
# content: as below
# label1_1:value1_1\tlabel1_2:value1_2
# label2_1:value2_1\tlabel2_2:value2_2
stream = File.open("some_file.ltsv", "r")
values = Altsv.parse(stream)
# => [{:label1_1 => "value1_2", :label1_2 => "value1_2"},
# {:label2_1 => "value2_2", :label2_2 => "value2_2"}]

### loading LTSV file

# load via path
values = Altsv.load("some_path.ltsv")

# load via stream
stream = File.open("some_file.ltsv", "r")
values = Altsv.load(stream) # => same as LTSV.parse(stream)

### dumping into LTSV

value = {label1: "value1", label2: "value2"}
dumped = Altsv.dump(value) # => "label1:value1\tlabel2:value2"

Dumped objects should respond to :to_hash.

## Development

After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).

## Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/condor/altsv.
5 changes: 5 additions & 0 deletions Rakefile
@@ -0,0 +1,5 @@
require 'helix_runtime/build_task'

HelixRuntime::BuildTask.new

require "bundler/gem_tasks"
44 changes: 44 additions & 0 deletions altsv.gemspec
@@ -0,0 +1,44 @@
# frozen_string_literal: true

lib = File.expand_path("../lib", __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require "altsv/version"

Gem::Specification.new do |spec|
spec.name = "altsv"
spec.version = Altsv::VERSION
spec.authors = ["condor"]
spec.email = ["condor1226@gmail.com"]

spec.summary = %q{an ALTernative ltSV parser / dumper library}
spec.description = %q{A more lightweight LTSV handler library partially written in Rust.}
spec.homepage = "https://github.com/condor/altsv"

# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
# to allow pushing to a single host or delete this section to allow pushing to any host.
if spec.respond_to?(:metadata)
spec.metadata["homepage_uri"] = spec.homepage
spec.metadata["source_code_uri"] = "https://github.com/condor/altsv"
spec.metadata["changelog_uri"] = "https://github.com/condor/altsv/blob/master/CHANGELOG.md"
else
raise "RubyGems 2.0 or newer is required to protect against " \
"public gem pushes."
end

# Specify which files should be added to the gem when it is released.
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
end
spec.bindir = "exe"
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]

spec.add_dependency 'helix_runtime', '~> 0.7.5'

spec.add_development_dependency 'pry'
spec.add_development_dependency "bundler", "~> 1.17"
spec.add_development_dependency "rake", "~> 10.0"
spec.add_development_dependency "rspec", "~> 3.8", '< 4.0'
spec.add_development_dependency 'ltsv'
end
14 changes: 14 additions & 0 deletions bin/console
@@ -0,0 +1,14 @@
#!/usr/bin/env ruby

require "bundler/setup"
require "altsv"

# You can add fixtures and/or initialization code here to make experimenting
# with your gem easier. You can also use a different console, if you like.

# (If you use this, don't forget to add pry to your Gemfile!)
# require "pry"
# Pry.start

require "irb"
IRB.start(__FILE__)
8 changes: 8 additions & 0 deletions bin/setup
@@ -0,0 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
set -vx

bundle install

# Do any other automated setup that you need to do here
23 changes: 23 additions & 0 deletions lib/altsv.rb
@@ -0,0 +1,23 @@
# frozen_string_literal: true
require 'helix_runtime'
require 'altsv/native'

require "altsv/version"

class Altsv
class Error < StandardError; end
# Your code goes here...

class << self
def parse(input)
case input
when String
parse_native input
when StringIO
parse_native input.string
when IO
input.each_line.map{|line| parse_line_native line}
end
end
end
end
3 changes: 3 additions & 0 deletions lib/altsv/version.rb
@@ -0,0 +1,3 @@
class Altsv
VERSION = "0.1.0"
end
5 changes: 5 additions & 0 deletions spec/files/test.ltsv
@@ -0,0 +1,5 @@
label1:value1 label2:value\\nvalue
label3:value3 label4:value\\rvalue
label5:value5 label6:value\\tvalue
label7:value7 label8:value\\\\value
label9:value9 label10: label11:value11
134 changes: 134 additions & 0 deletions spec/lib/altsv_spec.rb
@@ -0,0 +1,134 @@
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Altsv do

describe :parse do
context 'String argument' do
it 'can parse labeled tab separated values into a hash in an array' do
line = "label1:value1\tlabel2:value2"
expect(Altsv.parse(line)).to eq [{:label1 => 'value1', :label2 => 'value2'}]
end

it 'can parse multiline labeled tab separated values into hashes in an array' do
line = "label1:value1\tlabel2:value2\nlabel1:value3\tlabel2:value4"
expect(Altsv.parse(line)).to eq [
{:label1 => 'value1', :label2 => 'value2'},
{:label1 => 'value3', :label2 => 'value4'}
]
end

it 'can parse the value that contains escape sequences' do

expect(Altsv.parse("label1:value1\tlabel2:value\\nvalue")).to \
eq [{:label1 => 'value1', :label2 => "value\nvalue"}]

expect(Altsv.parse("label1:value1\tlabel2:value\\rvalue")).to \
eq [{:label1 => 'value1', :label2 => "value\rvalue"}]

expect(Altsv.parse("label1:value1\tlabel2:value\\tvalue")).to \
eq [{:label1 => 'value1', :label2 => "value\tvalue"}]

expect(Altsv.parse("label1:value1\tlabel2:value\\\\value")).to \
eq [{:label1 => 'value1', :label2 => "value\\value"}]
end

it 'parses the value as-is when the backslash with a following ordinal character' do

expect(Altsv.parse("label1:value1\tlabel2:value\\avalue")).to \
eq [{:label1 => 'value1', :label2 => "value\\avalue"}]
end

it 'parses the empty value field as nil' do
expect(Altsv.parse("label1:\tlabel2:value2")).to \
eq [{:label1 => nil, :label2 => 'value2'}]
end
end

context 'IO argument' do
it 'can parse labeled tab separated values into file' do
file_dir = File.join(File.dirname(File.dirname(__FILE__)), 'files')
expect(Altsv.parse(File.open("#{file_dir}/test.ltsv"))).to \
eq [
{:label1 => 'value1', :label2 => 'value\\nvalue'},
{:label3 => 'value3', :label4 => 'value\\rvalue'},
{:label5 => 'value5', :label6 => 'value\\tvalue'},
{:label7 => 'value7', :label8 => 'value\\\\value'},
{:label9 => 'value9', :label10 => nil, :label11 => 'value11'}
]
end
end
end

describe :load do
specify 'can load labeled tab separated values from file' do
stream = File.open("#{File.dirname(__FILE__)}/test.ltsv")
expect(Altsv.load(stream)).to \
eq [
{:label1 => 'value1', :label2 => 'value\\nvalue'},
{:label3 => 'value3', :label4 => 'value\\rvalue'},
{:label5 => 'value5', :label6 => 'value\\tvalue'},
{:label7 => 'value7', :label8 => 'value\\\\value'},
{:label9 => 'value9', :label10 => nil, :label11 => 'value11'}
]
end
end

describe :dump do

specify 'dump into the format "label1:value1\tlabel2:value2"' do
expect(Altsv.dump({:label1 => "value1", :label2 => "value2"})).to \
eq "label1:value1\tlabel2:value2"
end

specify 'CRs, LFs, TABs, and backslashes in the value should be escaped' do
expect(Altsv.dump({:label1 => "value\rvalue"})).to eq "label1:value\\rvalue"
expect(Altsv.dump({:label1 => "value\nvalue"})).to eq "label1:value\\nvalue"
expect(Altsv.dump({:label1 => "value\tvalue"})).to eq "label1:value\\tvalue"
expect(Altsv.dump({:label1 => "value\\value"})).to eq "label1:value\\value"
end

specify ':s in the value should not be escaped' do
expect(Altsv.dump({:label1 => "value:value"})).to eq "label1:value:value"
end

specify 'should not fail when object to dump responds to :to_hash' do
target = Object.new
target.instance_eval do
def to_hash
{:label => 'value'}
end
end
expect(Altsv.dump(target)).to eq "label:value"
end

specify 'should not fail when object to dump responds to :to_h' do
target = Object.new
target.instance_eval do
def to_h
{:label => 'value'}
end
end
expect(Altsv.dump(target)).to eq "label:value"
end

specify 'fails when object to dump does not respond to :to_hash' do
expect(lambda{Altsv.dump(Object.new)}).to raise_exception(ArgumentError)
end

specify 'breaking change: Altsv.dump(nil) should return the empty string' do
expect(Altsv.dump(nil)).to eq ''
end

context 'when given Hash includes a value that returns a frozen String' do
let(:hash) do
{ label: double(to_s: "value".freeze) }
end

it 'does not fail' do
expect(Altsv.dump(hash)).to eq 'label:value'
end
end
end
end

0 comments on commit 27a18af

Please sign in to comment.