Class: DSLCompiler

Inherits:
Object
  • Object
show all
Defined in:
lib/raka/compile.rb

Overview

compiles rule (lhs = rhs) to rake task

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(env, options) ⇒ DSLCompiler

keep env as running environment of rake since we want to inject rules



23
24
25
26
# File 'lib/raka/compile.rb', line 23

def initialize(env, options)
  @env = env
  @options = options
end

Instance Attribute Details

#envObject (readonly)

Returns the value of attribute env.



20
21
22
# File 'lib/raka/compile.rb', line 20

def env
  @env
end

Instance Method Details

#compile(lhs, rhs) ⇒ Object

compile token = rhs to rake rule rubocop:disable Style/MethodLength rubocop:disable Style/PerceivedComplexity



175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/raka/compile.rb', line 175

def compile(lhs, rhs)
  unless @env.instance_of?(Object)
    raise "DSL compile error: seems not a valid @env of rake with class #{@env.class}"
  end

  # Extract typed components first
  components = extract_typed_components(rhs)
  typed_deps = components[:deps]
  typed_posts = components[:posts]  
  filtered_rhs = components[:filtered_rhs]

  # the format is [dep, ...] | [action, ...] | [post, ...], where the posts
  # are those will be raked after the actions
  actions_start = filtered_rhs.find_index { |item| item.respond_to?(:call) }

  # case 1: has action
  if actions_start
    extra_deps = filtered_rhs[0, actions_start]
    actions_end = filtered_rhs[actions_start, filtered_rhs.length].find_index do |item|
      !item.respond_to?(:call)
    end

    # case 1.1: has post
    if actions_end
      actions_end += actions_start
      actions = filtered_rhs[actions_start, actions_end]
      extra_tasks = filtered_rhs[actions_end, filtered_rhs.length]
    # case 1.2: no post
    else
      actions = filtered_rhs[actions_start, filtered_rhs.length]
      extra_tasks = []
    end
  # case 2: no action
  else
    extra_deps = filtered_rhs
    actions = []
    extra_tasks = []
  end

  # Merge typed components with extracted components
  extra_deps = extra_deps + typed_deps
  extra_tasks = extra_tasks + typed_posts

  unless lhs._input_?
    create_rule lhs, proc { [] }, actions, extra_deps, extra_tasks
    return
  end

  # We generate a rule for each possible input type

  (lhs._options_[:input_exts] || @options.input_types).each do |ext|
    # We find auto source from both THE scope and the root
    create_rule lhs, ext, actions, extra_deps, extra_tasks
  end
end

#create_rule(lhs, input_ext, actions, extra_deps, extra_tasks) ⇒ Object

build one rule



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/raka/compile.rb', line 111

def create_rule(lhs, input_ext, actions, extra_deps, extra_tasks)
  # the "rule" method is private, maybe here are better choices
  @env.send(:rule, lhs._pattern_ => [proc do |target|
    inputs = lhs._inputs_(target, input_ext)
    output = lhs._parse_output_(target)
    plain_extra_deps = extra_deps.map do |templ|
      resolve_by_output(templ, output)
    end
    # main data source and extra dependencies
    inputs + plain_extra_deps
  end]) do |task|
    # rake continue task even if dependencies not met, we handle ourselves
    absence = task.prerequisites.find_index { |f| !File.exist? f }
    unless absence.nil?
      @env.logger.warn\
        "Dependent #{task.prerequisites[absence]} does not exist, skip task #{task.name}"
      next
    end
    rule_action(lhs, actions, extra_tasks, task)
  end
end

#dsl_task(token, task) ⇒ Object

Raka task structure, input task is rake’s task pushed into blocks



29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/raka/compile.rb', line 29

def dsl_task(token, task)
  name = task.name
  deps = task.prerequisites 
  output_info = token._parse_output_ name
  task_info = {
    name: name,
    deps: deps,
    deps_str: deps.join(','),
    dep_scopes: deps.map { |d| File.dirname(d) },
    input: deps.first || '',
    task: task
  }
  OpenStruct.new(output_info.to_h.merge(task_info))
end

#extract_typed_components(rhs) ⇒ Object

extract typed components from rhs array and return filtered array + components



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/raka/compile.rb', line 134

def extract_typed_components(rhs)
  deps = []
  posts = []
  filtered_rhs = []
  
  rhs.each do |item|
    if item.is_a?(Hash)
      # Handle hash with multiple key-value pairs like [dep: dep3, post: post1]
      item.each do |key, value|
        case key
        when :dep, 'dep'
          # Handle both single values and arrays, but don't convert Token objects
          if value.is_a?(Array)
            deps.concat(value)
          else
            deps << value
          end
        when :post, 'post'  
          # Handle both single values and arrays, but don't convert Token objects
          if value.is_a?(Array)
            posts.concat(value)
          else
            posts << value
          end
        else
          # Unknown typed component - raise error
          raise "Unknown typed component: #{key}. Supported components are 'dep' and 'post'"
        end
      end
    else
      # Regular item (dependency, action, or post-task)
      filtered_rhs << item
    end
  end
  
  { deps: deps, posts: posts, filtered_rhs: filtered_rhs }
end

#resolve(target, task) ⇒ Object

resolve auto variables with dsl task



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/raka/compile.rb', line 71

def resolve(target, task)
  # convert target to text whether it is expression or already text
  text = resolve_by_output target, task

  # convert $0, $1 to the universal shape of %{dep} as captures
  text = text
    .gsub('$^', task.deps_str)
    .gsub('$<', task.input || '')
    .gsub('$(deps)', task.deps_str)
    .gsub('$(input)', task.input || '')

  protect_percent_symbol text do |safe_text|
    # add numbered auto variables like $0, $2 referring to the first and third deps
    safe_text = safe_text.gsub(/\$\(dep(\d+)\)/, '%{\1}') % array_to_hash(task.deps)
    safe_text.gsub(/\$\(dep(\d+)_stem\)/, '%{\1}') % array_to_hash(task.deps.map {|d| stem(d)})
  end
end

#resolve_by_output(target, output_info) ⇒ Object

resolve auto variables with only output info, useful when resolve extra deps (task is not available yet)



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/raka/compile.rb', line 50

def resolve_by_output(target, output_info)
  info = output_info
  text = target.respond_to?(:_template_) ? target._template_(info.scope).to_s : target.to_s
  text = text
    .gsub('$(scope)', info.scope.nil? ? '' : info.scope)
    .gsub('$(target_scope)', info.target_scope.nil? ? '' : info.target_scope)
    .gsub('$(output)', info.output)
    .gsub('$(output_stem)', stem(info.stem))
    .gsub('$(input_stem)', info.input_stem.nil? ? '' : info.input_stem)
    .gsub('$(func)', info.func.nil? ? '' : info.func)
    .gsub('$(ext)', info.ext)
    .gsub('$@', info.name)

  protect_percent_symbol text do |safe_text|
    safe_text = safe_text % (info.to_h.merge info.captures.to_h)
    safe_text = safe_text.gsub(/\$\(rule_scope(\d+)\)/, '%{\1}') % array_to_hash(info.rule_scopes)
    safe_text.gsub(/\$\(target_scope(\d+)\)/, '%{\1}') % array_to_hash(info.target_scope_captures)
  end
end

#rule_action(lhs, actions, extra_tasks, task) ⇒ Object



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/raka/compile.rb', line 89

def rule_action(lhs, actions, extra_tasks, task)
  return if actions.empty?

  task = dsl_task(lhs, task)
  @env.logger.info "raking #{task.name}"
  unless task.scope.nil?
    folder = task.scope
    folder = File.join(task.scope, task.target_scope) unless task.target_scope.nil?
    FileUtils.makedirs(folder)
  end
  actions.each do |action|
    action.call @env, task do |code|
      resolve(code, task)
    end
  end

  extra_tasks.each do |templ|
    Rake::Task[resolve(templ, task)].invoke
  end
end

#stem(path) ⇒ Object



44
45
46
# File 'lib/raka/compile.rb', line 44

def stem(path)
  File.basename(path, File.extname(path))
end