fled 0.0.1
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/README.md +230 -0
- data/bin/fled +152 -0
- data/lib/dtc/utils/dsldsl.rb +259 -0
- data/lib/dtc/utils/exec.rb +134 -0
- data/lib/dtc/utils/file_visitor.rb +78 -0
- data/lib/dtc/utils/interactive_edit.rb +81 -0
- data/lib/dtc/utils/mini_select.rb +177 -0
- data/lib/dtc/utils.rb +9 -0
- data/lib/fled/file_listing.rb +260 -0
- data/lib/fled.rb +38 -0
- data/tests/helper.rb +90 -0
- data/tests/readme.rb +256 -0
- data/tests/test_operations.rb +229 -0
- metadata +59 -0
data/README.md
ADDED
@@ -0,0 +1,230 @@
|
|
1
|
+
|
2
|
+
# FlEd
|
3
|
+
|
4
|
+
`fled` lets you organise your files and folders in your favourite editor
|
5
|
+
|
6
|
+
## Disclaimer
|
7
|
+
|
8
|
+
Warning: This is a very dangerous tool. The author recommends you do not
|
9
|
+
use it. The author cannot be held responsible in any case.
|
10
|
+
|
11
|
+
## Introduction
|
12
|
+
|
13
|
+
`fled` enumerates a folder and its files, and generates a text listing.
|
14
|
+
You can then edit that listing in your favourite editor, and save changes.
|
15
|
+
`fled` then reloads those changes, and prints a shell script that would move
|
16
|
+
your files and folders around as-per your edits.
|
17
|
+
|
18
|
+
**You should review that shell script very carefully before running it.**
|
19
|
+
|
20
|
+
### Philosophy
|
21
|
+
|
22
|
+
`fled` only generates text, it does not perform any operation directly.
|
23
|
+
|
24
|
+
The design optimises for making the edits very simple. The consequence of
|
25
|
+
this is that very small edits can have large consequences, which makes
|
26
|
+
this a **very dangerous** tool. But so is `rm` and the rest of the shell anyway...
|
27
|
+
|
28
|
+
### Caveats
|
29
|
+
|
30
|
+
`fled` is only aware of files it scanned. It will not warn for overwrites,
|
31
|
+
nor use temporary files in those cases, etc.
|
32
|
+
|
33
|
+
`fled`'s editing model is rather complex and fuzzy. While there are some test
|
34
|
+
cases defined, any help is much appreciated.
|
35
|
+
|
36
|
+
You should be scared when using `fled`.
|
37
|
+
|
38
|
+
### Examples
|
39
|
+
|
40
|
+
Print help text and option list
|
41
|
+
|
42
|
+
fled --help
|
43
|
+
|
44
|
+
Edit current folder
|
45
|
+
|
46
|
+
fled
|
47
|
+
|
48
|
+
Edit all files directly in `path` folder
|
49
|
+
|
50
|
+
fled -a path -d 0
|
51
|
+
|
52
|
+
Save default options
|
53
|
+
|
54
|
+
fled --options > fled.config.yaml
|
55
|
+
|
56
|
+
Edit current folder using options
|
57
|
+
|
58
|
+
fled --load fled.config.yaml
|
59
|
+
|
60
|
+
Add options to a command (`mkdir`, `mv`, `rm` or `rmdir`)
|
61
|
+
|
62
|
+
fled | sed 's/^mv/mv -i/'
|
63
|
+
|
64
|
+
## Listing Format
|
65
|
+
|
66
|
+
folder/ :0
|
67
|
+
file_one :1
|
68
|
+
folder_two/ :2
|
69
|
+
file_three :3
|
70
|
+
|
71
|
+
Each line of the listing is in the format *`[indentation]`* *`[name]`* `:`*`[uid]`*
|
72
|
+
|
73
|
+
- The *indentation* must consist of only spaces, and is used to indicate the parent folder
|
74
|
+
- The *name* must not use colons (`:`). If it is cleared, it is assumed the file/folder is to be deleted
|
75
|
+
- The *uid* is used by FlEd to recognise the original of the edited line. Do not assume a *uid* does not
|
76
|
+
change between runs. It is valid only once.
|
77
|
+
|
78
|
+
## Operations
|
79
|
+
### Creating a new folder
|
80
|
+
|
81
|
+
Add a new line (therefore with no uid):
|
82
|
+
|
83
|
+
folder/ :0
|
84
|
+
new_folder
|
85
|
+
folder_two/ :2
|
86
|
+
|
87
|
+
Generates:
|
88
|
+
|
89
|
+
mkdir folder/new_folder
|
90
|
+
|
91
|
+
### Moving
|
92
|
+
|
93
|
+
Change the indentation and/or line order to change the parent of a file or folder:
|
94
|
+
|
95
|
+
folder/ :0
|
96
|
+
folder_two/ :2
|
97
|
+
file_one :1
|
98
|
+
file_three :3
|
99
|
+
|
100
|
+
Generates:
|
101
|
+
|
102
|
+
mv folder/file_one folder/folder_two/file_one
|
103
|
+
|
104
|
+
*Moving an item below itself or its children is not recommended, as the listing may not be exhaustive*
|
105
|
+
|
106
|
+
### Renaming
|
107
|
+
|
108
|
+
Edit the name while preserving the uid to rename the item
|
109
|
+
|
110
|
+
folder_renamed/ :0
|
111
|
+
file_one :1
|
112
|
+
folder_two/ :2
|
113
|
+
file_changed :3
|
114
|
+
|
115
|
+
Generates:
|
116
|
+
|
117
|
+
mv folder folder_renamed
|
118
|
+
mv folder_renamed/folder_two/file_three folder_renamed/folder_two/file_changed
|
119
|
+
|
120
|
+
*Swapping file names may not work in cases where the generated intermediary file exists but was not included in the listing*
|
121
|
+
|
122
|
+
### Deleting
|
123
|
+
|
124
|
+
Clear a name but leave the uid to delete that item
|
125
|
+
|
126
|
+
folder_renamed/ :0
|
127
|
+
:1
|
128
|
+
:2
|
129
|
+
:3
|
130
|
+
|
131
|
+
Generates:
|
132
|
+
|
133
|
+
mv folder folder_renamed
|
134
|
+
rm folder_renamed/folder_two/file_three
|
135
|
+
rm folder_renamed/file_one
|
136
|
+
rmdir folder_renamed/folder_two
|
137
|
+
|
138
|
+
### No-op
|
139
|
+
|
140
|
+
If a line (and all child-lines) is removed from the listing, it will have no operation.
|
141
|
+
|
142
|
+
folder/ :0
|
143
|
+
|
144
|
+
Generates:
|
145
|
+
*No operation*
|
146
|
+
|
147
|
+
*Note that removing a folder without removing its children will move its children:*
|
148
|
+
|
149
|
+
folder/ :0
|
150
|
+
file_one :1
|
151
|
+
file_three :3
|
152
|
+
|
153
|
+
Generates:
|
154
|
+
|
155
|
+
mv folder/folder_two/file_three folder/file_three
|
156
|
+
|
157
|
+
|
158
|
+
If an indent is forgotten:
|
159
|
+
|
160
|
+
folder/ :0
|
161
|
+
file_one :1
|
162
|
+
file_three :3
|
163
|
+
|
164
|
+
Generates:
|
165
|
+
|
166
|
+
mv folder/folder_two/file_three folder/file_one/file_three
|
167
|
+
|
168
|
+
### All together
|
169
|
+
|
170
|
+
folder_new/ :0
|
171
|
+
new_folder/
|
172
|
+
first :1
|
173
|
+
second :3
|
174
|
+
:2
|
175
|
+
|
176
|
+
Generates:
|
177
|
+
|
178
|
+
mv folder folder_new
|
179
|
+
mkdir folder_new/new_folder
|
180
|
+
mv folder_new/file_one folder_new/new_folder/first
|
181
|
+
mv folder_new/folder_two/file_three folder_new/new_folder/second
|
182
|
+
rmdir folder_new/folder_two
|
183
|
+
|
184
|
+
## Edge cases
|
185
|
+
|
186
|
+
These sort-of work, but are still rather experimental
|
187
|
+
|
188
|
+
### Swapping files
|
189
|
+
|
190
|
+
folder/ :0
|
191
|
+
file_one :1
|
192
|
+
file_two :2
|
193
|
+
|
194
|
+
When applying
|
195
|
+
|
196
|
+
folder/ :0
|
197
|
+
file_two :1
|
198
|
+
file_one :2
|
199
|
+
|
200
|
+
Generates:
|
201
|
+
|
202
|
+
mv folder/file_two folder/file_one.tmp
|
203
|
+
mv folder/file_one folder/file_two
|
204
|
+
mv folder/file_one.tmp folder/file_one
|
205
|
+
|
206
|
+
### Tree swapping
|
207
|
+
|
208
|
+
folder/ :0
|
209
|
+
sub_folder/ :1
|
210
|
+
sub_sub_folder/ :2
|
211
|
+
file.txt :3
|
212
|
+
|
213
|
+
When applying
|
214
|
+
|
215
|
+
sub_sub_folder/ :2
|
216
|
+
sub_folder/ :1
|
217
|
+
folder/ :0
|
218
|
+
file.txt :3
|
219
|
+
|
220
|
+
Generates:
|
221
|
+
|
222
|
+
mv folder/sub_folder/sub_sub_folder sub_sub_folder
|
223
|
+
mv folder/sub_folder sub_sub_folder/sub_folder
|
224
|
+
mv folder sub_sub_folder/sub_folder/folder
|
225
|
+
mv sub_sub_folder/file.txt sub_sub_folder/sub_folder/folder/file.txt
|
226
|
+
|
227
|
+
## Contributors
|
228
|
+
|
229
|
+
- [Eric Doughty-Papassideris](http://github.com/ddlsmurf)
|
230
|
+
|
data/bin/fled
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'yaml'
|
3
|
+
require 'optparse'
|
4
|
+
require 'shellwords'
|
5
|
+
begin
|
6
|
+
require 'fled'
|
7
|
+
rescue LoadError => e
|
8
|
+
$:.unshift File.join(File.dirname(__FILE__),'../lib')
|
9
|
+
require 'fled'
|
10
|
+
end
|
11
|
+
|
12
|
+
class App
|
13
|
+
DEFAULT_SKIP_FOLDERS_RX = ['^\..*', '.*\.app$']
|
14
|
+
DEFAULT_SKIP_FOLDERS = ['.svn', '_svn', '.git', 'CVS', '.hg']
|
15
|
+
DEFAULT_SKIP_FILES_RX = ['^\..*', '.*~$']
|
16
|
+
DEFAULT_SKIP_FILES = [".DS_Store", "Thumbs.db", "Temporary Items"]
|
17
|
+
|
18
|
+
attr_reader :options
|
19
|
+
|
20
|
+
def initialize(arguments = ARGV)
|
21
|
+
@arguments = arguments.dup
|
22
|
+
@options = {}
|
23
|
+
@options[:verbose] = false
|
24
|
+
end
|
25
|
+
|
26
|
+
def run
|
27
|
+
if parsed_options?
|
28
|
+
if options[:output_options]
|
29
|
+
output_options
|
30
|
+
exit 0
|
31
|
+
end
|
32
|
+
output_version if @options[:verbose]
|
33
|
+
$stderr.puts "Starting on path #{@base_path} at #{DateTime.now}" if @options[:verbose]
|
34
|
+
builder = FlEd::ListingBuilder.new()
|
35
|
+
filter = DTC::Utils::FilteringFileVisitor.new builder, @options
|
36
|
+
DTC::Utils::FileVisitor.browse @base_path, filter
|
37
|
+
content = builder.listing.to_s
|
38
|
+
if content == ""
|
39
|
+
$stderr.puts "No files/folders found"
|
40
|
+
exit 1
|
41
|
+
end
|
42
|
+
result = DTC::Utils::InteractiveEditor::edit(content, ".yaml")
|
43
|
+
if result.nil? || result.strip == "" || result == content
|
44
|
+
$stderr.puts "No changes - aborting" ; exit 2
|
45
|
+
end
|
46
|
+
target_listing = FlEd::FileListing.parse(result)
|
47
|
+
ops = target_listing.operations_from!(builder.listing)
|
48
|
+
if ops.empty?
|
49
|
+
$stderr.puts "No changes - aborting" ; exit 2
|
50
|
+
end
|
51
|
+
ops = [[:pushd, @base_path]] + ops + [[:popd]] unless @options[:no_pushd]
|
52
|
+
puts FlEd::operation_list_to_bash(ops).join("\n")
|
53
|
+
$stderr.puts "\nFinished at #{DateTime.now}" if @options[:verbose]
|
54
|
+
else
|
55
|
+
output_usage
|
56
|
+
end
|
57
|
+
rescue SystemExit => e
|
58
|
+
raise
|
59
|
+
rescue Exception => e
|
60
|
+
puts e
|
61
|
+
output_usage
|
62
|
+
exit 127
|
63
|
+
end
|
64
|
+
protected
|
65
|
+
|
66
|
+
def self.default_options
|
67
|
+
({
|
68
|
+
:max_depth => -1,
|
69
|
+
:excluded_files =>
|
70
|
+
DEFAULT_SKIP_FILES_RX +
|
71
|
+
DEFAULT_SKIP_FILES.map { |e| '\A' + Regexp.escape(e) + '\z' },
|
72
|
+
:excluded_directories =>
|
73
|
+
DEFAULT_SKIP_FOLDERS_RX +
|
74
|
+
DEFAULT_SKIP_FOLDERS.map { |e| '\A' + Regexp.escape(e) + '\z' },
|
75
|
+
})
|
76
|
+
end
|
77
|
+
|
78
|
+
def parsed_options?
|
79
|
+
options = {}
|
80
|
+
@optionparser = OptionParser.new do |opts|
|
81
|
+
opts.banner = "Usage: #{$0} [options] [base_path]"
|
82
|
+
opts.separator ""
|
83
|
+
opts.separator "Selection"
|
84
|
+
|
85
|
+
opts.on('-i', '--include RX', String, "Only include files and folders that match any such regexp") { |rx| (options[:included] ||= []) << rx }
|
86
|
+
opts.on('--include-dirs RX', String, "Only include folders that match any such regexp") { |rx| (options[:included_directories] ||= []) << rx }
|
87
|
+
opts.on('--include-files RX', String, "Only include files that match any such regexp") { |rx| (options[:included_files] ||= []) << rx }
|
88
|
+
opts.on('-x', '--exclude RX', String, "Exclude files and folders that match any such regexp") { |rx| (options[:excluded] ||= []) << rx }
|
89
|
+
opts.on('--exclude-dirs RX', String, "Exclude folders that match any such regexp") { |rx| (options[:excluded_directories] ||= []) << rx }
|
90
|
+
opts.on('--exclude-files RX', String, "Exclude files that match any such regexp") { |rx| (options[:excluded_files] ||= []) << rx }
|
91
|
+
opts.on('-a', '--no-exclude', "Empties all the lists of exclusions") do
|
92
|
+
options[:excluded] = []
|
93
|
+
options[:excluded_files] = []
|
94
|
+
options[:excluded_directories] = []
|
95
|
+
end
|
96
|
+
opts.on('-r', '--recursive', "Scan directories recursively") { options[:max_depth] ||= -1 }
|
97
|
+
opts.on("-d", "--depth N", Integer, "Set maximum recursion to N subfolders. (0=no recursion)") { |n| options[:max_depth] = n.to_i }
|
98
|
+
|
99
|
+
opts.separator ""
|
100
|
+
opts.separator "Script"
|
101
|
+
opts.on('--no-pushd', "Do not include pushd/popd pair in script") { options[:no_pushd] = true }
|
102
|
+
|
103
|
+
opts.separator ""
|
104
|
+
opts.separator "General"
|
105
|
+
opts.on_tail('-l', '--load PATH.YAML', "Merge in the specified yaml files options") { |file|
|
106
|
+
File.open(file) { |file| options = options.merge(YAML.load(file)) }
|
107
|
+
}
|
108
|
+
opts.on_tail('--options', "Show options as interpreted and exit without doing anything") { options[:output_options] = true }
|
109
|
+
opts.on('-v', '--verbose', "Display more information about what the tool is doing...") { options[:verbose] = true }
|
110
|
+
opts.on_tail('--version', "Show version of this tool") { output_version ; exit 0 }
|
111
|
+
opts.on_tail('-h', '--help', "Show this help message") { output_help ; exit 0 }
|
112
|
+
end
|
113
|
+
@optionparser.parse!(@arguments)
|
114
|
+
@base_path = File.expand_path(@arguments.first || options[:base_path] || ".")
|
115
|
+
raise RuntimeError, "No more than one argument should be present" if @arguments.count > 1
|
116
|
+
@options = self.class.default_options.merge(options)
|
117
|
+
true
|
118
|
+
end
|
119
|
+
|
120
|
+
def output_options out = $stdout
|
121
|
+
opts = @options.dup
|
122
|
+
opts.delete(:output_options)
|
123
|
+
out.puts opts.to_yaml
|
124
|
+
end
|
125
|
+
|
126
|
+
def output_help
|
127
|
+
output_version
|
128
|
+
$stderr.puts ""
|
129
|
+
$stderr.puts " Disclaimer: The author is not responsible for anything related"
|
130
|
+
$stderr.puts " to this tool. This is quite powerful and slight mistakes can"
|
131
|
+
$stderr.puts " lead to loss of data or worse. The author recommends you not"
|
132
|
+
$stderr.puts " use this."
|
133
|
+
$stderr.puts ""
|
134
|
+
$stderr.puts " Operation:"
|
135
|
+
$stderr.puts " - Generate list of files and folder"
|
136
|
+
$stderr.puts " - Open in your favorite ($EDITOR) text editor"
|
137
|
+
$stderr.puts " - You edit file names in the editor, then save and close"
|
138
|
+
$stderr.puts " - The file list is reloaded and compared to the original"
|
139
|
+
$stderr.puts " - A shell script to re-organise the files/folders is printed"
|
140
|
+
$stderr.puts ""
|
141
|
+
output_usage
|
142
|
+
end
|
143
|
+
|
144
|
+
def output_usage
|
145
|
+
$stderr.puts @optionparser
|
146
|
+
end
|
147
|
+
|
148
|
+
def output_version
|
149
|
+
$stderr.puts "#{File.basename(__FILE__)} version #{VERSION}"
|
150
|
+
end
|
151
|
+
end
|
152
|
+
App.new.run
|
@@ -0,0 +1,259 @@
|
|
1
|
+
module DTC
|
2
|
+
module Utils
|
3
|
+
module DSLDSL
|
4
|
+
class DSLVisitor
|
5
|
+
# very approximate visitor =)
|
6
|
+
def enter key, *args ; end
|
7
|
+
def leave ; end
|
8
|
+
def add key, *args ; end
|
9
|
+
def visit_dsl &blk
|
10
|
+
builder = self.class.delegate_klass.new(self)
|
11
|
+
self.class.context_klass.new(builder).__instance_exec(&blk)
|
12
|
+
builder.flush
|
13
|
+
end
|
14
|
+
protected
|
15
|
+
def self.delegate_klass ; StaticTreeDSLDelegate ; end
|
16
|
+
def self.context_klass ; StaticTreeDSLContextBlank ; end
|
17
|
+
end
|
18
|
+
|
19
|
+
class DSLArrayWriter < DSLVisitor
|
20
|
+
attr_reader :stack
|
21
|
+
def initialize
|
22
|
+
@stack = [[]]
|
23
|
+
end
|
24
|
+
def enter sym, *args
|
25
|
+
@stack.push [[sym, *args]]
|
26
|
+
end
|
27
|
+
def leave
|
28
|
+
@stack.pop
|
29
|
+
end
|
30
|
+
def add sym, *args
|
31
|
+
@stack.last << [sym, *args]
|
32
|
+
end
|
33
|
+
def each &blk
|
34
|
+
return enum_for(:each) unless block_given?
|
35
|
+
each_call_of @stack.first, &blk
|
36
|
+
end
|
37
|
+
protected
|
38
|
+
def each_call_of parent, &block
|
39
|
+
parent.each do |item|
|
40
|
+
if item.first.is_a?(Symbol)
|
41
|
+
yield item
|
42
|
+
else
|
43
|
+
each_call_of item, &block
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class DSLHashWriter < DSLVisitor
|
50
|
+
def initialize target
|
51
|
+
@stack = [target]
|
52
|
+
end
|
53
|
+
def enter key, *args
|
54
|
+
container = add_container(key, *args)
|
55
|
+
@stack.push(container) if container
|
56
|
+
container
|
57
|
+
end
|
58
|
+
def leave
|
59
|
+
@stack.pop
|
60
|
+
end
|
61
|
+
def add key, *args
|
62
|
+
add_key key, args
|
63
|
+
end
|
64
|
+
def self.write_static_tree_dsl target = {}, &blk
|
65
|
+
visitor = self.new(result = target)
|
66
|
+
visitor.visit_dsl(&blk)
|
67
|
+
result
|
68
|
+
end
|
69
|
+
def add_container key, *args
|
70
|
+
container = {}
|
71
|
+
container[:options] = args unless args.empty?
|
72
|
+
add_key key, container
|
73
|
+
end
|
74
|
+
def add_key key, val
|
75
|
+
container = @stack.last
|
76
|
+
key = key.to_s
|
77
|
+
raise RuntimeError, "Key #{key.inspect} already defined" if container[key]
|
78
|
+
container[key] = val
|
79
|
+
val
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
class StaticTreeDSLDelegate
|
84
|
+
def initialize visitor, prefix = nil
|
85
|
+
@visitor = visitor
|
86
|
+
@prefix = prefix
|
87
|
+
@pending_prefix = nil
|
88
|
+
@called = false
|
89
|
+
end
|
90
|
+
def prefix sym
|
91
|
+
@called = true
|
92
|
+
flush
|
93
|
+
@pending_prefix = self.class.new(@visitor, with_prefix(sym))
|
94
|
+
end
|
95
|
+
def flush
|
96
|
+
if @pending_prefix
|
97
|
+
@pending_prefix.add_unless_called
|
98
|
+
@pending_prefix = nil
|
99
|
+
end
|
100
|
+
end
|
101
|
+
def add sym, *args
|
102
|
+
@called = true
|
103
|
+
@visitor.add(with_prefix(sym), *args)
|
104
|
+
end
|
105
|
+
def enter sym, *args
|
106
|
+
flush
|
107
|
+
@called = true
|
108
|
+
@visitor.enter(with_prefix(sym), *args)
|
109
|
+
end
|
110
|
+
def leave
|
111
|
+
flush
|
112
|
+
@visitor.leave
|
113
|
+
end
|
114
|
+
protected
|
115
|
+
def with_prefix sym
|
116
|
+
@prefix ? (sym ? "#{@prefix}.#{sym}".to_sym : @prefix) : sym
|
117
|
+
end
|
118
|
+
def add_unless_called
|
119
|
+
flush
|
120
|
+
@visitor.add(@prefix) unless @called
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
class StaticTreeDSLContextBlank
|
125
|
+
alias_method :__instance_exec, :instance_exec
|
126
|
+
alias_method :__class, :class
|
127
|
+
instance_methods.each { |meth| undef_method(meth) unless meth =~ /\A__/ || meth == :object_id }
|
128
|
+
def initialize delegate, unprefixed = delegate
|
129
|
+
@delegate = delegate
|
130
|
+
@unprefixed = unprefixed
|
131
|
+
end
|
132
|
+
def method_missing(meth, *args, &block)
|
133
|
+
if block
|
134
|
+
@delegate.enter meth, *args
|
135
|
+
__class.new(@unprefixed, @unprefixed).__instance_exec(&block)
|
136
|
+
@unprefixed.flush
|
137
|
+
@delegate.leave
|
138
|
+
else
|
139
|
+
if args.empty?
|
140
|
+
return __class.new(@delegate.prefix(meth), @unprefixed)
|
141
|
+
else
|
142
|
+
@delegate.add(meth, *args)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
self
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
if __FILE__ == $0
|
150
|
+
|
151
|
+
class DebugStaticTreeDSLDelegate < StaticTreeDSLDelegate
|
152
|
+
def prefix sym
|
153
|
+
p [:prefix, sym, @prefix]
|
154
|
+
super
|
155
|
+
end
|
156
|
+
def flush
|
157
|
+
p [:flush, @prefix] if @pending_prefix
|
158
|
+
super
|
159
|
+
end
|
160
|
+
def add sym, *args
|
161
|
+
p [:add, sym, @prefix]
|
162
|
+
super
|
163
|
+
end
|
164
|
+
def enter sym, *args
|
165
|
+
p [:enter, sym, @prefix]
|
166
|
+
super
|
167
|
+
end
|
168
|
+
def leave
|
169
|
+
p [:leave, @prefix]
|
170
|
+
super
|
171
|
+
end
|
172
|
+
def add_unless_called
|
173
|
+
p [:flush_self, @prefix] unless @called
|
174
|
+
super
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
class DebugDSLHashWriter < DSLHashWriter
|
179
|
+
def enter sym, *args
|
180
|
+
p [:wenter, sym]
|
181
|
+
super
|
182
|
+
end
|
183
|
+
def leave
|
184
|
+
p [:wleave]
|
185
|
+
super
|
186
|
+
end
|
187
|
+
def add sym, *args
|
188
|
+
p [:wadd, sym, args]
|
189
|
+
super
|
190
|
+
end
|
191
|
+
def self.delegate_klass ; DebugStaticTreeDSLDelegate ; end
|
192
|
+
end
|
193
|
+
|
194
|
+
module Examples
|
195
|
+
module Simple
|
196
|
+
result = DSLHashWriter.write_static_tree_dsl do
|
197
|
+
fichier "value"
|
198
|
+
fichier2.txt "one", "two"
|
199
|
+
end
|
200
|
+
result # => {"fichier"=>["value"], "fichier2.txt"=>["one", "two"]}
|
201
|
+
|
202
|
+
result = DSLHashWriter.write_static_tree_dsl do
|
203
|
+
dossier {
|
204
|
+
sous.dossier "valeur" do
|
205
|
+
file.txt
|
206
|
+
end
|
207
|
+
}
|
208
|
+
end
|
209
|
+
result # => {"dossier"=>{"sous.dossier"=>{:options=>["valeur"], "file.txt"=>[]}}}
|
210
|
+
|
211
|
+
result = DSLHashWriter.write_static_tree_dsl do
|
212
|
+
%w[hdpi mdpi ldpi].each do |resolution|
|
213
|
+
image.__send__(resolution.to_sym).png
|
214
|
+
end
|
215
|
+
end
|
216
|
+
result # => {"image.ldpi.png"=>[], "image.hdpi.png"=>[], "image.mdpi.png"=>[]}
|
217
|
+
|
218
|
+
result = DSLHashWriter.write_static_tree_dsl do
|
219
|
+
user {
|
220
|
+
name :string
|
221
|
+
email :string
|
222
|
+
address(:json) {
|
223
|
+
line :string
|
224
|
+
country
|
225
|
+
}
|
226
|
+
}
|
227
|
+
end
|
228
|
+
result # => {"user"=>{"address"=>{"country"=>[], "line"=>[:string], :options=>[:json]}, "name"=>[:string], "email"=>[:string]}}
|
229
|
+
|
230
|
+
result = DSLHashWriter.write_static_tree_dsl do
|
231
|
+
def base_file
|
232
|
+
yes.this.is.base
|
233
|
+
end
|
234
|
+
base_file.txt
|
235
|
+
base_file.png
|
236
|
+
end
|
237
|
+
result # => {"yes.this.is.base.png"=>[], "yes.this.is.base.txt"=>[]}
|
238
|
+
end
|
239
|
+
module CustomBuilder
|
240
|
+
class EscapableStaticTreeDSLContextBlank < StaticTreeDSLContextBlank
|
241
|
+
def method_missing(meth, *args, &block)
|
242
|
+
meth = args.shift if meth == :raw
|
243
|
+
super(meth, *args, &block)
|
244
|
+
end
|
245
|
+
end
|
246
|
+
class EscapableDSLHashWriter < DSLHashWriter
|
247
|
+
def self.context_klass ; EscapableStaticTreeDSLContextBlank ; end
|
248
|
+
end
|
249
|
+
result = EscapableDSLHashWriter.write_static_tree_dsl do
|
250
|
+
3.times { |i| raw(i).png }
|
251
|
+
image.raw("%!") { file }
|
252
|
+
end
|
253
|
+
result # => {"0.png"=>[], "1.png"=>[], "image.%!"=>{"file"=>[]}, "2.png"=>[]}
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|