Class: BPM::Project

Inherits:
Package show all
Defined in:
lib/bpm/project.rb

Direct Known Subclasses

PackageProject

Constant Summary collapse

DEFAULT_CONFIG_PATH =
File.expand_path('../default.json', __FILE__)
DEFAULT_CONFIG =
JSON.load File.read(DEFAULT_CONFIG_PATH)
BPM_DIR =
'.bpm'

Constants inherited from Package

BPM::Package::EXT, BPM::Package::FIELDS, BPM::Package::METADATA_FIELDS, BPM::Package::PLUGIN_TYPES, BPM::Package::REQUIRED_FIELDS, BPM::Package::SPEC_FIELDS

Instance Attribute Summary collapse

Attributes inherited from Package

#email, #errors, #json_path, #root_path

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Package

#bin_files, #bin_path, #dependencies_build, #directory_files, #expanded_deps, #file_name, #fill_from_gemspec, #find_transport_plugins, from_spec, #full_name, #generator_for, #has_json?, is_package_root?, #lib_path, #merged_dependencies, #provided_formats, #provided_minifier, #provided_postprocessors, #provided_preprocessors, #provided_transport, #say, #shell, #standalone?, #template_path, #tests_path, #to_json, #to_spec, #used_dependencies, #used_formats, #used_postprocessors, #used_preprocessors, #used_transports, #valid?, #validate

Constructor Details

#initialize(root_path, name = nil) ⇒ Project

Returns a new instance of Project.



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/bpm/project.rb', line 41

def initialize(root_path, name=nil)
  super root_path

  if !name
    # If no name, try to find project json and get name from it
    project_file = self.class.project_file_path(root_path)
    name = File.basename(project_file, '.json') if project_file
  else
    project_file = File.join root_path, "#{name}.json"
  end

  @name = name || File.basename(root_path)
  @json_path = project_file || File.join(root_path, "#{File.basename(root_path)}.json")

  load_json && validate
end

Instance Attribute Details

#nameObject (readonly)

Returns the value of attribute name.



39
40
41
# File 'lib/bpm/project.rb', line 39

def name
  @name
end

Class Method Details

.is_project_json?(path) ⇒ Boolean

Returns:

  • (Boolean)


17
18
19
20
# File 'lib/bpm/project.rb', line 17

def self.is_project_json?(path)
  json = JSON.load(File.read(path)) rescue nil
  return !!(json && json["bpm"])
end

.is_project_root?(path) ⇒ Boolean

Returns:

  • (Boolean)


22
23
24
25
# File 'lib/bpm/project.rb', line 22

def self.is_project_root?(path)
  !!project_file_path(path) ||
  File.exists?(File.join(path, 'assets', 'bpm_libs.js'))
end

.nearest_project(path) ⇒ Object



27
28
29
30
31
32
33
34
35
36
37
# File 'lib/bpm/project.rb', line 27

def self.nearest_project(path)
  path = File.expand_path path

  last = nil
  while path != last
    return new(path) if is_project_root?(path)
    last = path
    path = File.dirname path
  end
  nil
end

.project_file_path(path) ⇒ Object



13
14
15
# File 'lib/bpm/project.rb', line 13

def self.project_file_path(path)
  Dir[File.join(path, '*.json')].find{|p| is_project_json?(p) }
end

Instance Method Details

#add_dependencies(new_deps, development = false, verbose = false) ⇒ Object

Add a new dependency

Adds to the project json and installs dependency



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/bpm/project.rb', line 206

def add_dependencies(new_deps, development=false, verbose=false)
  old_deps  = build_local_dependency_list(false) || []
  hard_deps = (development ? dependencies_development : dependencies).merge(new_deps)
  all_hard_deps = all_dependencies.merge(new_deps)
  exp_deps = find_non_local_dependencies(all_hard_deps, true)

  say "Fetching packages from remote..." if verbose
  core_fetch_dependencies(exp_deps, verbose)

  if development
    self.dependencies_development = hard_deps
  else
    self.dependencies = hard_deps
  end

  rebuild_dependency_list(all_hard_deps, verbose)

  local_deps.each do |dep|
    next if old_deps.find { |pkg| (pkg.name == dep.name) && (pkg.version == dep.version) }
    say "Added #{development ? "development " : ""}package '#{dep.name}' (#{dep.version})"
  end

  save!
end

#all_dependenciesObject



75
76
77
78
# File 'lib/bpm/project.rb', line 75

def all_dependencies
  deps = dependencies.merge(dependencies_development || {})
  deps.merge(dependencies_build)
end

#as_jsonObject

Hash for conversion to json



506
507
508
509
510
# File 'lib/bpm/project.rb', line 506

def as_json
  json = super
  json["bpm"] = self.bpm
  json
end

#assets_pathObject



121
122
123
# File 'lib/bpm/project.rb', line 121

def assets_path
  'assets'
end

#assets_root(*paths) ⇒ Object



125
126
127
# File 'lib/bpm/project.rb', line 125

def assets_root(*paths)
  File.join @root_path, assets_path, *paths
end

#bpmObject



58
59
60
# File 'lib/bpm/project.rb', line 58

def bpm
  @bpm || BPM::COMPAT_VERSION
end

#build(mode = :debug, verbose = false) ⇒ Object

Builds assets directory for dependent packages



280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
# File 'lib/bpm/project.rb', line 280

def build(mode=:debug, verbose=false)

  verify_and_repair mode, verbose

  say "Building static assets..." if verbose

  report_package_locations if verbose

  # Seed the project with any required files to ensure they are built
  buildable_asset_filenames(mode).each do |filename|
    dst_path = assets_root filename
    next if File.exists? dst_path
    FileUtils.mkdir_p File.dirname(dst_path)
    FileUtils.touch dst_path
  end

  pipeline = BPM::Pipeline.new self, mode
  pipeline.buildable_assets.each do |asset|
    dst_path = assets_root asset.logical_path
    FileUtils.mkdir_p File.dirname(dst_path)

    if asset.kind_of? Sprockets::StaticAsset
      say "~ Copying #{asset.logical_path}" if verbose
      FileUtils.rm dst_path if File.exists? dst_path
      FileUtils.cp asset.pathname, dst_path
    else
      $stdout << "~ Building #{asset.logical_path}..." if verbose
      File.open(dst_path, 'w+') { |fd| fd << asset.to_s }
      if verbose
        gzip_size = LibGems.gzip(asset.to_s).bytesize
        gzip_size = gzip_size < 1024 ? "#{gzip_size} bytes" : "#{gzip_size / 1024} Kb"
        $stdout << " (gzipped size: #{gzip_size})\n"
      end
    end
  end

  say "\n" if verbose
end

#build_app=(value) ⇒ Object



141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/bpm/project.rb', line 141

def build_app=(value)
  bpm_libs = "bpm_libs.js"
  bpm_styles = "bpm_styles.css"

  if value
    bpm_build[bpm_libs] ||= {}
    hash = bpm_build[bpm_libs]
    hash['files'] ||= hash['directories'] || []
    hash['files'] << 'lib' if hash['files'].size==0
    hash['minifier']    ||= 'uglify-js'

    bpm_build[bpm_styles] ||= {}
    hash = bpm_build[bpm_styles]
    hash['files'] ||= hash['directories'] || []
    hash['files'] << 'css' if hash['files'].size==0

    directories ||= {}
    directories['lib'] ||= ['lib']
  else
    bpm_build[bpm_libs]['files'] = []
    bpm_build[bpm_styles]['files'] = []
  end
  value
end

#build_app?Boolean

Returns:

  • (Boolean)


133
134
135
136
137
138
139
# File 'lib/bpm/project.rb', line 133

def build_app?
  # Make sure we have some lib files
  !!(bpm_build &&
      bpm_build["bpm_libs.js"] &&
      ((bpm_build["bpm_libs.js"]["files"] && bpm_build["bpm_libs.js"]["files"].size>0) ||
        (bpm_build["bpm_libs.js"]["directories"] && bpm_build["bpm_libs.js"]["directories"].size>0)))
end

#build_settings(mode = :debug) ⇒ Object

Returns a fully normalized hash of build settings



67
68
69
70
71
72
73
# File 'lib/bpm/project.rb', line 67

def build_settings(mode=:debug)
  ret = {}
  deps = mode == :debug ? sorted_deps : sorted_runtime_deps
  deps.each { |dep| merge_build_settings(ret, dep, mode) }
  merge_build_settings ret, self, mode, false
  ret
end

#buildable_asset_filenames(mode) ⇒ Object

returns array of all assets that should be generated for this project



167
168
169
# File 'lib/bpm/project.rb', line 167

def buildable_asset_filenames(mode)
  build_settings(mode).keys.reject { |x| !(x =~ /\..+$/) }
end

#fetch_dependencies(verbose = false) ⇒ Object

Get dependencies from server if not installed



271
272
273
274
275
# File 'lib/bpm/project.rb', line 271

def fetch_dependencies(verbose=false)
  say "Fetching packages from remote..." if verbose
  exp_deps = find_non_local_dependencies(all_dependencies, true)
  core_fetch_dependencies(exp_deps, verbose)
end

#find_vendored_package(name) ⇒ Object



113
114
115
# File 'lib/bpm/project.rb', line 113

def find_vendored_package(name)
  vendored_packages.find{|p| p.name == name }
end

#find_vendored_project(name) ⇒ Object



109
110
111
# File 'lib/bpm/project.rb', line 109

def find_vendored_project(name)
  vendored_projects.find{|p| p.name == name }
end

#has_local_package?(package_name) ⇒ Boolean

Tell if package is vendored

Returns:

  • (Boolean)


534
535
536
# File 'lib/bpm/project.rb', line 534

def has_local_package?(package_name)
  !!find_vendored_package(package_name)
end

#load_jsonObject



521
522
523
524
525
526
527
528
529
530
# File 'lib/bpm/project.rb', line 521

def load_json
  return super if has_json?
  (FIELDS.keys + %w(description summary homepage)).each do |f|
    send("#{c2u(f)}=", DEFAULT_CONFIG[f])
  end

  self.name = File.basename(@json_path, '.json')
  self.version = "0.0.1"
  true
end

#local_depsObject

List local dependency names, rebuilds list first time



434
435
436
# File 'lib/bpm/project.rb', line 434

def local_deps
  @local_deps ||= build_local_dependency_list
end

#map_to_packages(deps) ⇒ Object



438
439
440
441
442
# File 'lib/bpm/project.rb', line 438

def map_to_packages(deps)
  Array(deps).map do |dep_name, vers|
    local_deps.find { |x| x.name==dep_name }
  end
end

#minifier_name(asset_name) ⇒ Object

Name of minifier



515
516
517
518
519
# File 'lib/bpm/project.rb', line 515

def minifier_name(asset_name)
  build_settings[asset_name] &&
    build_settings[asset_name]['bpm:provides'] &&
    build_settings[asset_name]['bpm:provides']['minifier']
end

#package_and_module_from_path(path) ⇒ Object

Returns the package object and module id for the path. Path must match a package known to the project.



395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
# File 'lib/bpm/project.rb', line 395

def package_and_module_from_path(path)
  path = File.expand_path path.to_s
  pkg = local_deps.find {|cur| path =~ /^#{Regexp.escape cur.root_path.to_s}\//}
  pkg = self if pkg.nil? && path =~ /^#{Regexp.escape root_path.to_s}/
  raise "#{path} is not within a known package" if pkg.nil?

  dir_name = nil
  pkg.directories.each do |dname, dpath|
    dpaths = Array(dpath).map{|d| File.expand_path(d, pkg.root_path) }
    dpaths.each do |d|
      # Find a match and see if we can replace
      if path.gsub!(/^#{Regexp.escape(d)}\//, "#{dname}/")
        dir_name = dname
        break
      end
    end
    break if dir_name
  end

  if dir_name
    parts = path.split("/")
  else
    parts = Pathname.new(path).relative_path_from(Pathname.new(pkg.root_path)).to_s.split("/")
    dir_name = parts.first
  end

  if dir_name == 'lib'
    parts.shift
  else
    parts[0] = "~#{dir_name}"
  end

  parts[parts.size-1] = File.basename(parts.last, '.*')
  [pkg, parts.join('/')]
end

#package_from_name(package_name) ⇒ Object

Find package with name



351
352
353
354
# File 'lib/bpm/project.rb', line 351

def package_from_name(package_name)
  return self if package_name == self.name
  local_deps.find { |pkg| pkg.name == package_name }
end

#package_manifest_pathObject



117
118
119
# File 'lib/bpm/project.rb', line 117

def package_manifest_path
  File.join(@root_path, BPM_DIR, 'package_manifest.json')
end

#path_from_module(module_path) ⇒ Object

Returns the path on disk for a given module id (relative to the project)



367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
# File 'lib/bpm/project.rb', line 367

def path_from_module(module_path)
  path_parts   = module_path.to_s.split '/'
  package_name = path_parts.shift
  module_path = path_from_package(package_name)

  if module_path
    # expand package_name => package_name/main
    path_parts = ['main'] if path_parts.size == 0

    # expand package_name/~dirname => package_name/mapped_dirname
    dirname = (path_parts.first && path_parts.first =~ /^~/) ?
                path_parts.shift[1..-1] : 'lib'

    pkg = BPM::Package.new(module_path)
    pkg.load_json
    dirname = (pkg && pkg.directories[dirname]) || dirname

    # join the rest of the path
    module_path = [package_name, dirname, *path_parts] * '/'
  end

  module_path
end

#path_from_package(package_name) ⇒ Object

Returns the path on disk to reach a given package name



359
360
361
362
# File 'lib/bpm/project.rb', line 359

def path_from_package(package_name)
  ret = package_from_name package_name
  ret && ret.root_path
end

#preview_root(*paths) ⇒ Object



129
130
131
# File 'lib/bpm/project.rb', line 129

def preview_root(*paths)
  File.join @root_path, '.bpm', 'preview', *paths
end

#rebuild_dependency_list(deps = nil, verbose = false) ⇒ Object

Verifies that packages are available to meet all the dependencies



487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
# File 'lib/bpm/project.rb', line 487

def rebuild_dependency_list(deps=nil, verbose=false)
  found = find_dependencies(deps, verbose)

  json = {}
  found.each do |f|
    json[f.name] = { :version => f.version.to_s, :path => f.root_path }
  end

  FileUtils.mkdir_p(File.dirname(package_manifest_path))
  File.open(package_manifest_path, 'w') do |f|
    f.puts JSON.pretty_generate(json)
  end

  @local_deps = nil
end

#rebuild_preview(verbose = false) ⇒ Object



180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/bpm/project.rb', line 180

def rebuild_preview(verbose=false)

  needs_rebuild = true

  if File.directory?(preview_root)
    cur_previews  = Dir[preview_root('**', '*')].sort.reject { |x| File.directory?(x) }
    exp_filenames = buildable_asset_filenames(:debug)
    exp_previews  = exp_filenames.map { |x| preview_root(x) }.sort
    needs_rebuild = cur_previews != exp_previews
  end

  if needs_rebuild
    FileUtils.rm_r preview_root if File.exists? preview_root
    buildable_asset_filenames(:debug).each do |filename|
      next if File.exists? preview_root(filename)
      FileUtils.mkdir_p File.dirname(preview_root(filename))
      FileUtils.touch preview_root(filename)
    end
  end

end

#remove_dependencies(package_names, verbose = false) ⇒ Object

Remove a dependency

Remove dependency from json. Does not remove from system.



236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/bpm/project.rb', line 236

def remove_dependencies(package_names, verbose=false)

  hard_deps = dependencies.dup
  old_deps = build_local_dependency_list(false)

  package_names.each do |pkg_name|
    raise "'#{pkg_name}' is not a dependency" if hard_deps[pkg_name].nil?
    hard_deps.delete pkg_name
  end

  @dependencies = hard_deps
  rebuild_dependency_list hard_deps, verbose

  old_deps.each do |dep|
    next if local_deps.find { |pkg| (pkg.name == dep.name) && (pkg.version == dep.version) }
    say "Removed package '#{dep.name}' (#{dep.version})"
  end

  save!

end

#save!Object

Save to json



261
262
263
264
265
266
# File 'lib/bpm/project.rb', line 261

def save!
  @had_changes = false
  File.open @json_path, 'w+' do |fd|
    fd.write JSON.pretty_generate as_json
  end
end

#sorted_depsObject

List of local dependency names in order of dependency



446
447
448
449
450
451
452
453
454
455
# File 'lib/bpm/project.rb', line 446

def sorted_deps
  dep_names = (dependencies.keys + dependencies_development.keys).uniq
  ret       = []

  dep_names.each do |dep_name|
    dep = local_deps.find { |x| x.name == dep_name }
    add_sorted_dep(dep, local_deps, :both, ret)
  end
  ret
end

#sorted_development_depsObject



469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
# File 'lib/bpm/project.rb', line 469

def sorted_development_deps
  dep_names = dependencies_development.map { |name, vers| name }
  deps = local_deps.reject { |dep| !dep_names.include?(dep.name) }

  deps = deps.inject([]) do |ret, dep|
    add_sorted_dep(dep, local_deps, :both, ret)
    ret
  end

  # development deps should include all dependencies of dev deps excluding
  # those that are bonafide runtime deps
  runtime_deps = sorted_runtime_deps
  deps.reject! { |pkg| runtime_deps.include?(pkg) }
  deps
end

#sorted_runtime_depsObject



457
458
459
460
461
462
463
464
465
466
467
# File 'lib/bpm/project.rb', line 457

def sorted_runtime_deps

  dep_names = dependencies.map { |name, vers| name }
  deps = local_deps.reject { |dep| !dep_names.include?(dep.name) }

  deps.inject([]) do |ret, dep|
    add_sorted_dep(dep, local_deps, :runtime, ret)
    ret
  end

end

#unbuild(verbose = false) ⇒ Object

Removes any built assets from the project. Usually called before a package is removed from the project to cleanup any assets



323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
# File 'lib/bpm/project.rb', line 323

def unbuild(verbose=false)
  say "Removing stale assets..." if verbose

  pipeline = BPM::Pipeline.new self
  asset_root = File.join root_path, 'assets'
  pipeline.buildable_assets.each do |asset|
    next if asset.logical_path =~ /^bpm_/
    asset_path = File.join asset_root, asset.logical_path
    next unless File.exists? asset_path
    say "~ Removing #{asset.logical_path}" if verbose
    FileUtils.rm asset_path

    # cleanup empty directories
    while !File.exists?(asset_path)
      asset_path = File.dirname asset_path
      FileUtils.rmdir(asset_path) if File.directory?(asset_path)
      if verbose && !File.exists?(asset_path)
        say "~ Removed empty directory #{File.basename asset_path}"
      end
    end
  end

  say "\n" if verbose
end

#validate_name_and_pathObject



62
63
64
# File 'lib/bpm/project.rb', line 62

def validate_name_and_path
  # do nothing. never an error
end

#vendor_rootObject



80
81
82
# File 'lib/bpm/project.rb', line 80

def vendor_root
  File.join @root_path, 'vendor'
end

#vendored_packagesObject



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/bpm/project.rb', line 92

def vendored_packages
  @vendored_packages ||= begin
    search_paths = [vendor_root, File.join(@root_path, 'packages')]
    paths = search_paths.map{|p| Dir.glob(File.join(p, '*')) }.flatten
    pkgs = paths.select{|p| Package.is_package_root?(p) }.map{|p| Package.new(p) }
    pkgs += vendored_projects.map{|p| p.vendored_packages }.flatten
    pkgs.select do |p|
      begin
        p.load_json
      rescue BPM::InvalidPackageError => e
        raise e if ENV['DEBUG']
        false
      end
    end
  end
end

#vendored_projectsObject



84
85
86
87
88
89
90
# File 'lib/bpm/project.rb', line 84

def vendored_projects
  @vendored_projects ||= begin
    Dir.glob(File.join(vendor_root, '*')).
      select{|p| Project.is_project_root?(p) }.
      map{|p| Project.new(p) }
  end
end

#verify_and_repair(mode = :debug, verbose = false) ⇒ Object

Validates that all required files are present in the project needed for compile to run. This will not fetch new dependencies from remote.



173
174
175
176
177
178
# File 'lib/bpm/project.rb', line 173

def verify_and_repair(mode=:debug, verbose=false)
  valid_deps = find_dependencies rescue nil
  fetch_dependencies(verbose) if valid_deps.nil?
  rebuild_dependency_list nil, verbose
  rebuild_preview verbose
end