sourcery 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.ruby +44 -0
- data/bin/sourcery +4 -0
- data/lib/sourcery.rb +16 -0
- data/lib/sourcery/caster.rb +187 -0
- data/lib/sourcery/cli.rb +56 -0
- data/lib/sourcery/context.rb +45 -0
- data/lib/sourcery/metadata.rb +136 -0
- metadata +69 -0
data/.ruby
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
---
|
2
|
+
source:
|
3
|
+
- Profile
|
4
|
+
authors:
|
5
|
+
- name: Trans
|
6
|
+
email: transfire@gmail.com
|
7
|
+
copyrights:
|
8
|
+
- holder: Rubyworks
|
9
|
+
year: '2009'
|
10
|
+
license: BSD-2-Clause
|
11
|
+
replacements: []
|
12
|
+
alternatives: []
|
13
|
+
requirements:
|
14
|
+
- name: detroit
|
15
|
+
groups:
|
16
|
+
- build
|
17
|
+
development: true
|
18
|
+
dependencies: []
|
19
|
+
conflicts: []
|
20
|
+
repositories:
|
21
|
+
- uri: git://github.com/rubyworks/sourcery.git
|
22
|
+
scm: git
|
23
|
+
name: upstream
|
24
|
+
resources:
|
25
|
+
home: https://github.com/rubyworks/sourcery
|
26
|
+
code: https://github.com/rubyworks/sourcery
|
27
|
+
bugs: https://github.com/rubyworks/sourcery/issues
|
28
|
+
mail: https://groups.google.com/groups/rubyworks-mailinglist
|
29
|
+
extra: {}
|
30
|
+
load_path:
|
31
|
+
- lib
|
32
|
+
revision: 0
|
33
|
+
name: sourcery
|
34
|
+
title: Sourcery
|
35
|
+
version: 0.1.0
|
36
|
+
summary: New Age Ruby, Coding in the Astrological Plain.
|
37
|
+
created: '2009-07-15'
|
38
|
+
description: ! 'Sourcery puts a spell-casting layer between
|
39
|
+
|
40
|
+
the code you write and the code you ship. Using ERB templating
|
41
|
+
|
42
|
+
in this layer lets you write highly meta-magical code.'
|
43
|
+
organization: rubyworks
|
44
|
+
date: '2012-02-06'
|
data/bin/sourcery
ADDED
data/lib/sourcery.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
module Sourcery
|
2
|
+
|
3
|
+
def self.metadata
|
4
|
+
@metadata ||= (
|
5
|
+
require 'yaml'
|
6
|
+
YAML.load_file(File.dirname(__FILE__) + '/sourcery.yml')
|
7
|
+
)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.const_missing(name)
|
11
|
+
metadata[name.to_s.downcase] || super(name)
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
require 'sourcery/cli'
|
@@ -0,0 +1,187 @@
|
|
1
|
+
module Sourcery
|
2
|
+
|
3
|
+
require 'sourcery/context'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'erb'
|
6
|
+
|
7
|
+
# Spell Caster renders `src/` files to `lib/`.
|
8
|
+
#
|
9
|
+
class Caster
|
10
|
+
|
11
|
+
# Source directory, defaults to `src`.
|
12
|
+
attr :source
|
13
|
+
|
14
|
+
# Target directory, defaults to `lib`.
|
15
|
+
attr :target
|
16
|
+
|
17
|
+
#
|
18
|
+
attr :files
|
19
|
+
|
20
|
+
#
|
21
|
+
attr_accessor :ask
|
22
|
+
|
23
|
+
#
|
24
|
+
attr_accessor :skip
|
25
|
+
|
26
|
+
#
|
27
|
+
attr_accessor :stdout
|
28
|
+
|
29
|
+
#
|
30
|
+
#attr_accessor :delete
|
31
|
+
|
32
|
+
#
|
33
|
+
def initialize(options={})
|
34
|
+
@source = options[:source] || 'src'
|
35
|
+
@target = options[:target] || 'lib'
|
36
|
+
@force = options[:ask]
|
37
|
+
@skip = options[:skip]
|
38
|
+
@stdout = options[:stdout]
|
39
|
+
#@delete = options[:delete]
|
40
|
+
|
41
|
+
if options[:files]
|
42
|
+
@files = options[:files]
|
43
|
+
else
|
44
|
+
@files = collect_files(source)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Collect all files from source except those starting with an `_` or `.`.
|
49
|
+
def collect_files(dir)
|
50
|
+
Dir[File.join(dir,'**/*')].reject do |f|
|
51
|
+
basename = File.basename(f)
|
52
|
+
basename.start_with?('_') or basename.start_with?('.')
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
#
|
57
|
+
def ask?
|
58
|
+
@ask
|
59
|
+
end
|
60
|
+
|
61
|
+
#
|
62
|
+
def skip?
|
63
|
+
@skip
|
64
|
+
end
|
65
|
+
|
66
|
+
#
|
67
|
+
#def delete? ; @delete ; end
|
68
|
+
|
69
|
+
#
|
70
|
+
def debug?
|
71
|
+
$DEBUG
|
72
|
+
end
|
73
|
+
|
74
|
+
#
|
75
|
+
def trial?
|
76
|
+
$TRIAL
|
77
|
+
end
|
78
|
+
|
79
|
+
#
|
80
|
+
def call
|
81
|
+
ensure_target_directory
|
82
|
+
|
83
|
+
copy_map = {}
|
84
|
+
|
85
|
+
files.each do |file|
|
86
|
+
output = target_file(file)
|
87
|
+
if output == file
|
88
|
+
raise "output and source file are identical -- #{file}."
|
89
|
+
end
|
90
|
+
copy_map[file] = output
|
91
|
+
end
|
92
|
+
|
93
|
+
copy_map.each do |file, output|
|
94
|
+
render(file, output)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Render and save ERB template `file` to `output` file.
|
99
|
+
def render(file, output)
|
100
|
+
if File.file?(output) && skip?
|
101
|
+
print_line('SKIP', output)
|
102
|
+
else
|
103
|
+
template = ERB.new(File.read(file))
|
104
|
+
result = template.result(context.__binding__)
|
105
|
+
if stdout
|
106
|
+
puts result
|
107
|
+
else
|
108
|
+
save(result, output, file)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Determine output file name given source `file`.
|
114
|
+
def target_file(file)
|
115
|
+
name = file.sub(source+'/', '')
|
116
|
+
File.join(target, name)
|
117
|
+
end
|
118
|
+
|
119
|
+
#
|
120
|
+
def context
|
121
|
+
@context ||= Context.new
|
122
|
+
end
|
123
|
+
|
124
|
+
#
|
125
|
+
def save(text, output, source_file)
|
126
|
+
name = output # relative_path(output)
|
127
|
+
if trial?
|
128
|
+
puts " CAST #{name} (dryrun)"
|
129
|
+
else
|
130
|
+
save = false
|
131
|
+
if File.exist?(output)
|
132
|
+
if FileUtils.uptodate?(output, [source_file])
|
133
|
+
print_line('UNCHANGED', name)
|
134
|
+
elsif ask?
|
135
|
+
case ask("%11s %s?" % ['OVERWRITE', name])
|
136
|
+
when 'y', 'yes'
|
137
|
+
save = true
|
138
|
+
end
|
139
|
+
else
|
140
|
+
save = true
|
141
|
+
end
|
142
|
+
else
|
143
|
+
save = true
|
144
|
+
end
|
145
|
+
|
146
|
+
if save
|
147
|
+
save_file(output, text)
|
148
|
+
puts " CAST #{name}"
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
#
|
154
|
+
def print_line(label, string)
|
155
|
+
"%11s %s" % [label, string]
|
156
|
+
end
|
157
|
+
|
158
|
+
# Save file and make it read-only.
|
159
|
+
def save_file(file, text)
|
160
|
+
#File.open(file, 'w'){ |f| << text }
|
161
|
+
mode = nil
|
162
|
+
if File.exist?(file)
|
163
|
+
mode = File.stat(file).mode
|
164
|
+
mask = mode | 0000200
|
165
|
+
File.chmod(mask, file) # make writeable
|
166
|
+
end
|
167
|
+
File.open(file, 'w'){ |f| f << text }
|
168
|
+
mode = mode || File.stat(file).mode
|
169
|
+
mask = mode & (mode ^ 0000222)
|
170
|
+
File.chmod(mask, file) # make read-only
|
171
|
+
end
|
172
|
+
|
173
|
+
#
|
174
|
+
def ask(prompt=nil)
|
175
|
+
$stdout << "#{prompt}"
|
176
|
+
$stdout.flush
|
177
|
+
$stdin.gets.chomp!
|
178
|
+
end
|
179
|
+
|
180
|
+
#
|
181
|
+
def ensure_target_directory
|
182
|
+
FileUtils.mkdir(target) unless File.directory?(target)
|
183
|
+
end
|
184
|
+
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
data/lib/sourcery/cli.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
module Sourcery
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'sourcery/caster'
|
5
|
+
|
6
|
+
#
|
7
|
+
def self.cli(*argv)
|
8
|
+
options = {}
|
9
|
+
|
10
|
+
usage = OptionParser.new do |use|
|
11
|
+
use.banner = 'Usage: sourcery [OPTIONS] [FILE1 FILE2 ...]'
|
12
|
+
|
13
|
+
#use.on('--delete', 'delete templates when finished') do
|
14
|
+
# options[:delete] = true
|
15
|
+
#end
|
16
|
+
|
17
|
+
use.on('-a', '--ask', 'prompt user before overwrites') do
|
18
|
+
options[:ask] = true
|
19
|
+
end
|
20
|
+
|
21
|
+
use.on('-s', '--skip', 'automatically skip overwrites') do
|
22
|
+
options[:skip] = true
|
23
|
+
end
|
24
|
+
|
25
|
+
use.on('-o', '--stdout', 'dump output to stdout instead of saving') do
|
26
|
+
options[:stdout] = true
|
27
|
+
end
|
28
|
+
|
29
|
+
use.on('-t', '--trial', 'run in trial mode (no actual disk write)') do
|
30
|
+
$TRIAL = true
|
31
|
+
end
|
32
|
+
|
33
|
+
use.on('--debug', 'run in debug mode') do
|
34
|
+
$DEBUG = true
|
35
|
+
end
|
36
|
+
|
37
|
+
use.on_tail('-h', '--help', 'display this help information') do
|
38
|
+
puts use
|
39
|
+
exit
|
40
|
+
end
|
41
|
+
|
42
|
+
#use['<DIR>', 'directory to till; default is working directory']
|
43
|
+
end
|
44
|
+
|
45
|
+
usage.parse!(argv)
|
46
|
+
|
47
|
+
if !argv.empty?
|
48
|
+
options[:files] = argv
|
49
|
+
end
|
50
|
+
|
51
|
+
caster = Caster.new(options)
|
52
|
+
|
53
|
+
caster.call
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Sourcery
|
2
|
+
|
3
|
+
require 'sourcery/metadata'
|
4
|
+
|
5
|
+
#
|
6
|
+
class Context
|
7
|
+
|
8
|
+
#
|
9
|
+
attr :metadata
|
10
|
+
|
11
|
+
#
|
12
|
+
def initialize(dir=nil)
|
13
|
+
@metadata = Metadata.new(dir)
|
14
|
+
end
|
15
|
+
|
16
|
+
#
|
17
|
+
def method_missing(s)
|
18
|
+
@metadata.send(s)
|
19
|
+
end
|
20
|
+
|
21
|
+
#
|
22
|
+
def root
|
23
|
+
metadata.root
|
24
|
+
end
|
25
|
+
|
26
|
+
#
|
27
|
+
def to_h
|
28
|
+
@metadata.to_h
|
29
|
+
end
|
30
|
+
|
31
|
+
# Processes through erb.
|
32
|
+
#def erb(text)
|
33
|
+
# erb = ERB.new(text)
|
34
|
+
# erb.result(binding)
|
35
|
+
#end
|
36
|
+
|
37
|
+
# Get a clean binding.
|
38
|
+
def __binding__
|
39
|
+
binding
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
@@ -0,0 +1,136 @@
|
|
1
|
+
module Sourcery
|
2
|
+
|
3
|
+
#
|
4
|
+
class Metadata
|
5
|
+
|
6
|
+
#
|
7
|
+
# Project root pathname.
|
8
|
+
#
|
9
|
+
attr :root
|
10
|
+
|
11
|
+
#
|
12
|
+
# Initialize new Metadata object.
|
13
|
+
#
|
14
|
+
def initialize(root=nil)
|
15
|
+
#@root = self.class.root(root) || Dir.pwd
|
16
|
+
@root = root || Dir.pwd
|
17
|
+
@cache = {}
|
18
|
+
|
19
|
+
raise "not a directory -- #{root}" unless File.directory?(root)
|
20
|
+
|
21
|
+
load_metadata
|
22
|
+
end
|
23
|
+
|
24
|
+
#
|
25
|
+
# If method is missing see if there is a metadata entry for it.
|
26
|
+
#
|
27
|
+
def method_missing(s, *a)
|
28
|
+
m = s.to_s
|
29
|
+
case m
|
30
|
+
when /=$/
|
31
|
+
raise ArgumentError if a.size != 1
|
32
|
+
@cache[s.to_s] = a.first
|
33
|
+
else
|
34
|
+
super(s, *a) unless a.empty?
|
35
|
+
@cache[s.to_s]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
#
|
40
|
+
# Return copy of metadata store.
|
41
|
+
#
|
42
|
+
def to_h
|
43
|
+
@cache.dup
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
#
|
49
|
+
# Load metadata.
|
50
|
+
#
|
51
|
+
def load_metadata
|
52
|
+
load_metadata_from_directory
|
53
|
+
load_metadata_from_dotruby
|
54
|
+
end
|
55
|
+
|
56
|
+
#
|
57
|
+
# Load metadata from metadata directory.
|
58
|
+
#
|
59
|
+
def load_metadata_from_directory
|
60
|
+
entries = Dir.glob(File.join(meta_dir, '*'))
|
61
|
+
entries.each do |f|
|
62
|
+
val = File.read(f).strip
|
63
|
+
val = YAML.load(val) if val =~ /\A---/
|
64
|
+
@cache[File.basename(f)] = val
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
#
|
69
|
+
# Load metadata from .ruby file.
|
70
|
+
#
|
71
|
+
def load_metadata_from_dotruby
|
72
|
+
file = Dir[File.join(root, '.ruby')].first
|
73
|
+
if file
|
74
|
+
@cache.update(YAML.load_file(file))
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
#
|
79
|
+
# Locate the project's metadata directory. This is either
|
80
|
+
# `meta` or `.meta` or `var`.
|
81
|
+
#
|
82
|
+
def meta_dir
|
83
|
+
@meta_dir ||= Dir[File.join(root, '{meta,.meta,var}/')].first || '.meta/'
|
84
|
+
end
|
85
|
+
|
86
|
+
#
|
87
|
+
#def load_value(name)
|
88
|
+
# file = File.join(metadir, name)
|
89
|
+
# file = Dir[file].first
|
90
|
+
# if file && File.file?(file)
|
91
|
+
# #return erb(file).strip
|
92
|
+
# return File.read(file).strip
|
93
|
+
# end
|
94
|
+
#end
|
95
|
+
|
96
|
+
public
|
97
|
+
|
98
|
+
#
|
99
|
+
# Root directory is indicated by the presence of a +src/+ directory.
|
100
|
+
#
|
101
|
+
ROOT_INDICATORS = ['.ruby,var/,meta/,.meta/,.git,.hg,_darcs']
|
102
|
+
|
103
|
+
#
|
104
|
+
# Locate the project's root directory. This is determined
|
105
|
+
# by ascending up the directory tree from the current position
|
106
|
+
# until the ROOT_INDICATORS is matched. Returns +nil+ if not found.
|
107
|
+
#
|
108
|
+
def self.root(local=Dir.pwd)
|
109
|
+
local ||= Dir.pwd
|
110
|
+
Dir.chdir(local) do
|
111
|
+
dir = nil
|
112
|
+
ROOT_INDICATORS.find do |i|
|
113
|
+
dir = locate_root_at(i)
|
114
|
+
end
|
115
|
+
dir ? Pathname.new(File.dirname(dir)) : nil
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
#
|
120
|
+
# Helper method for `Metadata.root()`.
|
121
|
+
#
|
122
|
+
def self.locate_root_at(indicator)
|
123
|
+
root = nil
|
124
|
+
dir = Dir.pwd
|
125
|
+
while !root && dir != '/'
|
126
|
+
find = File.join(dir, indicator)
|
127
|
+
root = Dir.glob(find, File::FNM_CASEFOLD).first
|
128
|
+
#break if root
|
129
|
+
dir = File.dirname(dir)
|
130
|
+
end
|
131
|
+
root ? Pathname.new(root) : nil
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
metadata
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sourcery
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Trans
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-02-07 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: detroit
|
16
|
+
requirement: &14325640 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *14325640
|
25
|
+
description: ! 'Sourcery puts a spell-casting layer between
|
26
|
+
|
27
|
+
the code you write and the code you ship. Using ERB templating
|
28
|
+
|
29
|
+
in this layer lets you write highly meta-magical code.'
|
30
|
+
email:
|
31
|
+
- transfire@gmail.com
|
32
|
+
executables:
|
33
|
+
- sourcery
|
34
|
+
extensions: []
|
35
|
+
extra_rdoc_files: []
|
36
|
+
files:
|
37
|
+
- .ruby
|
38
|
+
- bin/sourcery
|
39
|
+
- lib/sourcery/caster.rb
|
40
|
+
- lib/sourcery/cli.rb
|
41
|
+
- lib/sourcery/context.rb
|
42
|
+
- lib/sourcery/metadata.rb
|
43
|
+
- lib/sourcery.rb
|
44
|
+
homepage: https://github.com/rubyworks/sourcery
|
45
|
+
licenses:
|
46
|
+
- BSD-2-Clause
|
47
|
+
post_install_message:
|
48
|
+
rdoc_options: []
|
49
|
+
require_paths:
|
50
|
+
- lib
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ! '>='
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ! '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
requirements: []
|
64
|
+
rubyforge_project:
|
65
|
+
rubygems_version: 1.8.11
|
66
|
+
signing_key:
|
67
|
+
specification_version: 3
|
68
|
+
summary: New Age Ruby, Coding in the Astrological Plain.
|
69
|
+
test_files: []
|