Skip to content

Commit d5fa2cc

Browse files
committed
Merge branch 'dev' of github.com:cloud66/alterant
# Conflicts: # README.md
2 parents 64d3d2c + 0f903de commit d5fa2cc

33 files changed

+705
-0
lines changed

.ruby-gemset

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
alterant

.ruby-version

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ruby-2.5.1

.vscode/settings.json

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"cSpell.words": [
3+
"fqin"
4+
]
5+
}

Gemfile

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
source 'https://rubygems.org'
2+
gemspec

Gemfile.lock

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
PATH
2+
remote: .
3+
specs:
4+
alterant (0.0.1)
5+
colorize (~> 0.8)
6+
diffy (~> 3.2)
7+
json (~> 1.4)
8+
jsonpath (~> 0.9)
9+
mini_racer (~> 0.2)
10+
thor (~> 0.20)
11+
12+
GEM
13+
remote: https://rubygems.org/
14+
specs:
15+
colorize (0.8.1)
16+
diffy (3.2.1)
17+
ffi (1.9.25)
18+
json (1.8.6)
19+
jsonpath (0.9.4)
20+
multi_json
21+
to_regexp (~> 0.2.1)
22+
libv8 (6.7.288.46.1)
23+
listen (3.1.5)
24+
rb-fsevent (~> 0.9, >= 0.9.4)
25+
rb-inotify (~> 0.9, >= 0.9.7)
26+
ruby_dep (~> 1.2)
27+
mini_racer (0.2.3)
28+
libv8 (>= 6.3)
29+
multi_json (1.13.1)
30+
rake (10.5.0)
31+
rb-fsevent (0.10.3)
32+
rb-inotify (0.9.10)
33+
ffi (>= 0.5.0, < 2)
34+
rerun (0.13.0)
35+
listen (~> 3.0)
36+
ruby_dep (1.5.0)
37+
thor (0.20.0)
38+
to_regexp (0.2.1)
39+
40+
PLATFORMS
41+
ruby
42+
43+
DEPENDENCIES
44+
alterant!
45+
bundler (~> 1.14)
46+
rake (~> 10.0)
47+
rerun (~> 0.13)
48+
49+
BUNDLED WITH
50+
1.16.6

Rakefile

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
require "bundler/gem_tasks"
2+
task :default => :spec

alterant.gemspec

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
2+
# coding: utf-8
3+
lib = File.expand_path('../lib', __FILE__)
4+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5+
6+
require 'alterant/version'
7+
8+
Gem::Specification.new do |spec|
9+
spec.name = "alterant"
10+
spec.version = Alterant::VERSION
11+
spec.authors = ["Khash Sajadi"]
12+
spec.email = ["[email protected]"]
13+
14+
spec.summary = %q{Alterant gem and command line}
15+
spec.description = %q{Alterant is a tool to alter configuration files}
16+
spec.homepage = "https://github.com/cloud66/alterant"
17+
spec.license = 'Nonstandard'
18+
19+
spec.files = Dir.glob("{bin,lib}/**/*") + %w(README.md)
20+
spec.bindir = "bin"
21+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
22+
spec.require_paths = ["lib"]
23+
24+
spec.add_development_dependency "bundler", "~> 1.14"
25+
spec.add_development_dependency "rake", "~> 10.0"
26+
spec.add_development_dependency "rerun", "~> 0.13"
27+
28+
spec.add_dependency 'jsonpath', '~>0.9'
29+
spec.add_dependency 'json', '~> 1.4'
30+
spec.add_dependency 'thor', '~> 0.20'
31+
spec.add_dependency 'mini_racer', '~> 0.2'
32+
spec.add_dependency 'colorize', '~> 0.8'
33+
spec.add_dependency 'diffy', '~> 3.2'
34+
end

bin/alterant

+170
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
#!/usr/bin/env ruby
2+
require 'thor'
3+
require 'yaml'
4+
require 'colorize'
5+
require_relative '../lib/alterant'
6+
7+
module Alterant
8+
class AlterantCLI < Thor
9+
package_name "alterant"
10+
11+
desc "version", "Show Alterant version"
12+
def version
13+
say "#{::Alterant::APP_NAME} #{::Alterant::VERSION}\n#{::Alterant::COPYRIGHT_MESSAGE}"
14+
end
15+
16+
desc "update", "Update Alterant to the latest version"
17+
option :version, type: :string, desc: "Force a specific version"
18+
def update
19+
say "Updating Alterant..."
20+
unless options[:version]
21+
say `gem install alterant --no-ri --no-rdoc`
22+
else
23+
say `gem install alterant -v #{options[:version]} --no-ri --no-rdoc`
24+
end
25+
end
26+
27+
desc "modify", "Runs the given script against a file"
28+
option :modifier, type: :string, desc: "Alterant JS script"
29+
option :in, type: :string, desc: "Input configuration file"
30+
option :out, type: :string, desc: "Output configuration file"
31+
option :output_format, type: :string, enum: ['yaml', 'json'], default: 'yaml', desc: "Output format"
32+
option :diff, type: :boolean, default: false, desc: "Return the diff instead of the output itself"
33+
option :debug, type: :boolean, default: false
34+
option :input_format, type: :string, enum: ['yaml', 'json'], default: 'yaml', desc: "Input format if it's based on a stream and not a file"
35+
option :overwrite, type: :boolean, default: false
36+
def modify
37+
$debug = options[:debug] || false
38+
overwrite = options[:overwrite]
39+
diff = options[:diff]
40+
output_format = options[:output_format]
41+
input_format = options[:input_format]
42+
43+
in_file = options[:in]
44+
if !in_file
45+
STDERR.puts "No input file provided. Use --in option".red
46+
exit(1)
47+
end
48+
if in_file != '-'
49+
unless File.exists?(in_file)
50+
STDERR.puts "Input file #{in_file} not found".red
51+
exit(1)
52+
end
53+
# detect the type
54+
input_ext = File.extname(in_file)
55+
unless ['.yaml', '.yml', '.json'].include? input_ext
56+
STDERR.puts "Only yaml and json files are supported for input".red
57+
exit(1)
58+
end
59+
if ['.yaml', '.yml'].include?(input_ext)
60+
input_format = 'yaml'
61+
else
62+
input_format = 'json'
63+
end
64+
65+
output_format = input_format if !output_format
66+
end
67+
68+
modifier_file = options[:modifier]
69+
if !modifier_file
70+
STDERR.puts "No script file provided. Use --modifier option".red
71+
exit(1)
72+
end
73+
unless File.exists?(modifier_file)
74+
STDERR.puts "Modifier file #{modifier_file} not found".red
75+
exit(1)
76+
end
77+
78+
out_file = options[:out]
79+
if out_file
80+
if !overwrite && File.exists?(out_file)
81+
STDERR.puts "Output file #{out_file} already exists. Use --overwrite flag to overwrite it".red
82+
exit(1)
83+
end
84+
else
85+
out_file = '-' # output to stdout
86+
end
87+
88+
89+
data = []
90+
if in_file == '-'
91+
input_text = STDIN.read
92+
else
93+
input_text = File.read(in_file)
94+
end
95+
96+
if input_format == 'yaml'
97+
if input_text.empty?
98+
STDERR.puts "Empty input file".red
99+
exit(2)
100+
end
101+
102+
input_text.split('---').each_with_index do |part, idx|
103+
part_data = YAML.load(part)
104+
data << part_data
105+
end
106+
107+
else
108+
file_data = JSON.parse(input_text)
109+
if file_data.is_a? Array
110+
data = file_data
111+
else
112+
data = [file_data]
113+
end
114+
end
115+
116+
run_context = {}
117+
modifier = File.read(modifier_file)
118+
if in_file != '-'
119+
run_context[:basedir] = File.dirname(in_file)
120+
end
121+
122+
run_context[:js_preload] = ::Alterant::Classes.LoadClasses
123+
alter = ::Alterant::Alterant.new(input: data, modifier: modifier, filename: modifier_file, options: run_context)
124+
results = alter.execute(timeout: 500)
125+
126+
if results.nil?
127+
STDERR.puts "Aborting".red
128+
exit(2)
129+
end
130+
131+
if output_format == 'yaml' || output_format == 'yml'
132+
converted_as_text = results.map { |r| r.to_yaml }.join("\n")
133+
input = data.map { |r| r.to_yaml }.join("\n")
134+
elsif output_format == 'json'
135+
converted_as_text = JSON.pretty_generate(results)
136+
input = JSON.pretty_generate(data)
137+
end
138+
139+
if diff
140+
output_as_text = Diffy::Diff.new(input, converted_as_text)
141+
else
142+
output_as_text = converted_as_text
143+
end
144+
145+
if out_file == "-"
146+
puts output_as_text
147+
else
148+
File.open(out_file, 'w') do |file|
149+
file.write(output_as_text)
150+
end
151+
end
152+
return true
153+
rescue ::Alterant::ParseError => exc
154+
STDERR.puts "Syntax error: #{exc.message}".red
155+
return false
156+
rescue ::Alterant::RuntimeError => exc
157+
STDERR.puts "Runtime error: #{exc.message}".red
158+
return false
159+
rescue => exc
160+
if $debug
161+
raise
162+
else
163+
STDERR.puts exc
164+
end
165+
return false
166+
end
167+
end
168+
169+
AlterantCLI.start
170+
end

bin/console

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/usr/bin/env ruby
2+
3+
require "bundler/setup"
4+
require "alterant"
5+
6+
# You can add fixtures and/or initialization code here to make experimenting
7+
# with your gem easier. You can also use a different console, if you like.
8+
9+
# (If you use this, don't forget to add pry to your Gemfile!)
10+
# require "pry"
11+
# Pry.start
12+
13+
require "irb"
14+
IRB.start(__FILE__)

bin/setup

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
IFS=$'\n\t'
4+
set -vx
5+
6+
bundle install
7+
8+
# Do any other automated setup that you need to do here

lib/alterant.rb

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
require_relative 'alterant/version'
2+
require_relative 'alterant/utils'
3+
require_relative 'alterant/loader'
4+
5+
module Alterant
6+
end

lib/alterant/alterant.rb

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
module Alterant
2+
class Alterant
3+
attr_reader :basedir
4+
5+
# input is a hash
6+
# filename is the modifier filename use for the backtrace
7+
# modifier is the script in string
8+
def initialize(input:, modifier:, filename:, options: {})
9+
@modifier = modifier
10+
@filename = filename
11+
@input = input
12+
@basedir = options[:basedir]
13+
@js_preload = options[:js_preload] || []
14+
end
15+
16+
# timeout is in ms
17+
# returns a hash
18+
def execute(timeout: 500, max_memory: 5000000)
19+
jpath = ::Alterant::Helpers::Jpath.new
20+
21+
result = []
22+
snapshot = MiniRacer::Snapshot.new("$$ = #{@input.to_json};\n" + @js_preload.join("\n")) # this is more efficient but we lose debug info (filename) of helper classes
23+
24+
isolate = MiniRacer::Isolate.new(snapshot)
25+
@input.each_with_index do |input, idx|
26+
ctx = ::MiniRacer::Context.new(isolate: isolate, timeout: timeout, max_memory: max_memory)
27+
ctx.eval("$ = #{input.to_json}")
28+
ctx.eval("$['fetch'] = function(key) { return jpath.fetch(JSON.stringify($), key); }")
29+
ctx.attach('jpath.fetch', proc{|x, y| jpath.fetch(x, y)})
30+
ctx.attach('console.log', proc{|x| STDERR.puts("DEBUG: #{x.inspect}") if $debug })
31+
ctx.attach('console.exception', proc{|x| raise ::Alterant::RuntimeError, x })
32+
ctx.attach('$$.push', proc{|x| result << x })
33+
ctx.attach('$.index', proc{ idx })
34+
ctx.attach('YamlReader', ::Alterant::Classes::YamlReader.new(self, ctx))
35+
ctx.attach('JsonReader', ::Alterant::Classes::JsonReader.new(self, ctx))
36+
37+
ctx.eval(@modifier, filename: @filename)
38+
pre_convert = ctx.eval("JSON.stringify($)")
39+
converted = JSON.parse(pre_convert)
40+
result << converted
41+
42+
ctx.dispose
43+
isolate.idle_notification(100)
44+
rescue ::MiniRacer::RuntimeError => exc
45+
if $debug
46+
raise
47+
else
48+
raise ::Alterant::ParseError, "part: #{idx} - #{exc.message}, #{exc.backtrace.first}"
49+
end
50+
rescue ::Alterant::AlterantError => exc
51+
STDERR.puts exc.message.red
52+
return nil
53+
end
54+
55+
return result
56+
end
57+
58+
end
59+
end

lib/alterant/classes/classes.rb

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
module Alterant
2+
module Classes
3+
CLASSES = {}
4+
5+
def self.LoadClasses
6+
# ::Alterant::Classes.constants.map(&::Alterant::Classes.method(:const_get)).grep(Class) do |c|
7+
# name = c.name.split('::').last
8+
# ::Alterant::Classes::CLASSES[name] = c
9+
# end
10+
11+
# load all JS files in the classes dir and construct a long text
12+
js_preload = []
13+
Dir["#{__dir__}/*.js"].each do |f|
14+
js_preload << File.read(f)
15+
end
16+
17+
return js_preload
18+
end
19+
end
20+
end

0 commit comments

Comments
 (0)