diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..e717f5e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +# http://editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore index 04250ff..8d55926 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .DS_Store +*.sw[po] pkg/* *.gem .bundle diff --git a/README.md b/README.md index 0ff6522..4ca96b4 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,6 @@ ## Usage - $ confetti generate android_manifest - -or, from Ruby - require "confetti" c_whatever = Confetti::Config.new "/some/dir/config.xml" c_whatever.write_android_manifest "/some/dir/AndroidManifest.xml" @@ -30,13 +26,6 @@ Supported outputs right now: `android_manifest`, `android_strings`, `webos_appin Let's say you want write a `nintendo_ds_config` generator: -* create a feature file, like `features/nintendo.feature` - * specify all the files to generate for your platform - * specify the name each file will go under - * `nintendo_ds_config` - * and the default filename - * `NintendoDSConfig.xml` - * run `rake features` to verify that it fails * add a sample configuration file to `spec/fixtures` * the filename is based on the descriptive name * `nintendo_ds_config_expected.xml` @@ -66,7 +55,3 @@ Let's say you want write a `nintendo_ds_config` generator: * add your platform to the `generate_and_write` call * `generate_and_write ... :nintendo_ds_config` * run `rake spec` again -* build and install the gem, or whatever, so the cucumber tests work -* run `feature/nintendo.feature` - * it's all green! - * you're the man now dog diff --git a/lib/confetti.rb b/lib/confetti.rb index 92a00f8..bb7633a 100644 --- a/lib/confetti.rb +++ b/lib/confetti.rb @@ -30,6 +30,7 @@ require 'confetti/templates/ios_info' require 'confetti/templates/ios_remote_plist' require 'confetti/templates/windows_phone8_manifest' +require 'confetti/templates/firefoxos_manifest' require 'confetti/template_helper' diff --git a/lib/confetti/config.rb b/lib/confetti/config.rb index a671371..21a4329 100644 --- a/lib/confetti/config.rb +++ b/lib/confetti/config.rb @@ -12,7 +12,8 @@ class Config :url_scheme_set, :platform_set generate_and_write :android_manifest, :android_strings, :ios_info, - :ios_remote_plist, :windows_phone8_manifest + :ios_remote_plist, :windows_phone8_manifest, + :firefoxos_manifest # handle bad generate/write calls def method_missing(method_name, *args) diff --git a/lib/confetti/templates/firefoxos_manifest.rb b/lib/confetti/templates/firefoxos_manifest.rb new file mode 100644 index 0000000..3e0c6c8 --- /dev/null +++ b/lib/confetti/templates/firefoxos_manifest.rb @@ -0,0 +1,163 @@ +module Confetti + module Template + class FirefoxosManifest < Base + + # See: https://developer.mozilla.org/en-US/Apps/Build/Manifest + + REQUIRED_FIELDS = ['name', 'description', 'launch_path', 'icons', + 'developer', 'default_locale', 'type'] + + OPTIONAL_FIELDS = ['version', 'fullscreen', 'orientation', 'permissions'] + + ALL_FIELDS = REQUIRED_FIELDS + OPTIONAL_FIELDS + + # Unhandled / unmappable fields: + # + # activities + # appcache_path + # chrome + # csp + # installs_allowed_from + # locales + # messages + # moz-firefox-accounts + # origin + # precompile + # redirects + # role + + ORIENTATIONS_MAP = { + :default => nil, + :landscape => "landscape", + :portrait => "portrait" + } + + GAP_PERMISSIONS_MAP = { + "camera" => %w{camera}, + "notification" => %w{desktop-notification}, + "geolocation" => %w{geolocation}, + "media" => %w{audio-capture + camera + video-capture}, + "contacts" => %w{contacts}, + "file" => %w{storage + device-storage:videos + device-storage:sdcard + device-storage:pictures + device-storage:music}, + "network" => %w{mobilenetwork + tcp-socket + systemXHR}, + "battery" => %w{} + } + + # TODO: Need a way to more granularly control access? + PERMISSIONS_ACCESS_MAP = { + "contacts" => 'readwrite', + "storage" => 'readwrite', + "device-storage:videos" => 'readwrite', + "device-storage:sdcard" => 'readwrite', + "device-storage:pictures" => 'readwrite', + "device-storage:music" => 'readwrite' + } + + def output_filename + "manifest.webapp" + end + + def name + @config.name.name + end + + def description + @config.description + end + + def version + @config.version_string || '0.0.1' + end + + def launch_path + "index.html" + end + + def icons + out = {} + @config.icon_set.each do |icon| + out[icon.width] = icon.src + end + out + end + + def developer + out = { + "name" => @config.author.name + } + out['url'] = @config.author.href if @config.author.href + out + end + + def type + 'privileged' + end + + def default_locale + 'en' + end + + def fullscreen + true + end + + def orientation + ORIENTATIONS_MAP[@config.orientation] + end + + def permissions + names = translate_feature GAP_PERMISSIONS_MAP + if names.empty? + out = nil + else + out = {} + names.each do |name| + out[name] = { + # TODO: Need to somehow derive a better description? + "description" => "Required feature" + } + out[name]['access'] = PERMISSIONS_ACCESS_MAP[name] if PERMISSIONS_ACCESS_MAP[name] + end + end + out + end + + def translate_feature map + features = [] + phonegap_api = /http\:\/\/api.phonegap.com\/1[.]0\/(\w+)/ + feature_names = @config.feature_set.map { |f| f.name } + feature_names = feature_names - [nil] + feature_names.sort + + feature_names.each do |f| + feature_name = f.match(phonegap_api)[1] if f.match(phonegap_api) + associated_features = map[feature_name] + + features.concat(associated_features) if associated_features + end + + features.sort! + features + end + + # HACK: Override Mustache rendering because JSON is easier & more robust + def render + out = {} + ALL_FIELDS.each do |name| + val = send(name) + out[name] = val if val != nil + end + JSON.pretty_generate out + end + + end + end +end diff --git a/spec/fixtures/firefoxos/firefoxos_manifest_spec.json b/spec/fixtures/firefoxos/firefoxos_manifest_spec.json new file mode 100644 index 0000000..d45a98d --- /dev/null +++ b/spec/fixtures/firefoxos/firefoxos_manifest_spec.json @@ -0,0 +1,15 @@ +{ + "name": "Awesome App", + "description": "This is an awesome app", + "launch_path": "index.html", + "version": "0.0.1", + "icons": { + "128": "icon.png" + }, + "developer": { + "name": "Awesome Developer" + }, + "default_locale": "en", + "type": "privileged", + "fullscreen": true +} diff --git a/spec/templates/firefoxos_manifest_spec.rb b/spec/templates/firefoxos_manifest_spec.rb new file mode 100644 index 0000000..5e04ad2 --- /dev/null +++ b/spec/templates/firefoxos_manifest_spec.rb @@ -0,0 +1,118 @@ +require 'spec_helper' +require 'json' + +describe Confetti::Template::FirefoxosManifest do + include HelpfulPaths + + before :all do + @template_class = Confetti::Template::FirefoxosManifest + end + + it "should inherit from the base template" do + @template_class.superclass.should be Confetti::Template::Base + end + + it "should have the template_file \"firefoxos.mustache\" in the confetti/templates dir" do + @template_class.template_file.should == "#{ templates_dir }/firefoxos_manifest.mustache" + end + + describe "default values" do + it "should define output filename as \"manifest.webapp\"" do + @template_class.new.output_filename.should == "manifest.webapp" + end + end + + describe "when passed a config object" do + before do + @config = Confetti::Config.new + @config.name.name = "Awesome App" + @config.author.name = "Awesome Developer" + @config.description = "This is an awesome app" + + icon = Confetti::Config::Image.new('icon.png', '128', '128', {}, 0) + @config.icon_set << icon + end + + it "should accept the config object" do + lambda { + @template_class.new(@config) + }.should_not raise_error + end + + describe "templated attributes" do + before do + @template = @template_class.new(@config) + end + + it "should set name correctly" do + @template.name.should == "Awesome App" + end + + it "should set description correctly" do + @template.description.should == "This is an awesome app" + end + + it "should use the default version" do + @template.version.should == "0.0.1" + end + + it "should set developer info correctly" do + @template.developer['name'] == @config.author.name + @template.developer.should_not include 'url' + end + + it "should render the correct web app manifest" do + expected_json = File.read("#{ fixture_dir }/firefoxos/firefoxos_manifest_spec.json") + expected = JSON.parse(expected_json) + result = JSON.parse(@template.render) + result.should == expected + end + end + + end + + it "should populate the manifest with all the icons" do + @config = Confetti::Config.new("#{fixture_dir}/config-icons.xml") + @template = @template_class.new(@config) + result = JSON.parse(@template.render) + result['icons'].should == { + "100"=>"smallicon.png", + "200"=>"bigicon.png" + } + end + + it "should handle orientation preference" do + @config = Confetti::Config.new("#{fixture_dir}/config_with_orientation.xml") + @template = @template_class.new(@config) + + JSON.parse(@template.render)['orientation'].should == 'landscape' + + @config.preference_set.clear() + JSON.parse(@template.render).should_not include 'orientation' + + @config.preference_set << Confetti::Config::Preference.new( + 'orientation', 'portrait', false) + JSON.parse(@template.render)['orientation'].should == 'portrait' + end + + it "should handle permissions" do + @config = Confetti::Config.new("#{fixture_dir}/config-features.xml") + @config.feature_set << Confetti::Config::Feature.new( + 'http://api.phonegap.com/1.0/contacts', false) + @template = @template_class.new(@config) + + result = JSON.parse(@template.render) + result['permissions'].should == { + "desktop-notification" => { "description" => "Required feature" }, + "geolocation" => { "description" => "Required feature" }, + "mobilenetwork" => { "description" => "Required feature" }, + "systemXHR" => { "description" => "Required feature" }, + "tcp-socket" => { "description" => "Required feature" }, + "contacts" => { + "description" => "Required feature", + "access" => "readwrite" + } + } + end + +end