sourcery 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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'
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'sourcery'
3
+ Sourcery.cli(*ARGV)
4
+
@@ -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
@@ -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: []