mg2en 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 811186babe943626c085e819708eea8c8cc8fdf2
4
+ data.tar.gz: 8c31fb2042764c2bed691631191bde88fb8051bb
5
+ SHA512:
6
+ metadata.gz: 5715130d51e157b789ac687308049c91a33b364af563d652b10c7c73cea30dddcce8de6066b9f3c37bd18f3e90e44d006334aaacac2de21014fffb66306e34af
7
+ data.tar.gz: 8ec629fc09e5bd91dfcb7e9ed1bfff69c5adc48a6381bdc6c349c2bd6f5924ec5a2fbef14da5ea000f18fc4fff064159c82c5544a3f84798c725539427105657
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in mg2en.gemspec
4
+ gemspec
@@ -0,0 +1,9 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard :rspec, cmd: 'bundle exec rspec' do
5
+ watch(%r{^spec/.+_spec\.rb$})
6
+ watch(%r{^lib/mg2en/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
7
+ watch('spec/spec_helper.rb') { "spec" }
8
+ end
9
+
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013 Jeff Hutchison
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,38 @@
1
+ # MacGourmet to Evernote
2
+
3
+ A library to parse a MacGourmet 3 export file and convert into an Evernote
4
+ export file. The resulting Evernote export file (*.enex) can be imported into
5
+ Evernote.
6
+
7
+ This converts most, but not all, MacGourmet recipe attributes.
8
+
9
+ ## Installation
10
+
11
+ Download and install [FreeImage](http://sourceforge.net/projects/freeimage/).
12
+ [Homebrew](http://brew.sh) users can:
13
+
14
+ brew install freeimage
15
+
16
+ Add this line to your application's Gemfile:
17
+
18
+ gem 'mg2en'
19
+
20
+ And then execute:
21
+
22
+ $ bundle
23
+
24
+ Or install it yourself as:
25
+
26
+ $ gem install mg2en
27
+
28
+ ## Usage
29
+
30
+ mg2en -h
31
+
32
+ ## Contributing
33
+
34
+ 1. Fork it
35
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
36
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
37
+ 4. Push to the branch (`git push origin my-new-feature`)
38
+ 5. Create new Pull Request
@@ -0,0 +1,60 @@
1
+ require "bundler/gem_tasks"
2
+ require 'pathname'
3
+ require 'mg2en'
4
+
5
+ task :default => [:preview]
6
+
7
+ # defaults for environment variables:
8
+ # MG3=spec/fixtures/1.mgourmet3
9
+ # ENEX=tmp/<basename of MG3>.enex
10
+ # NOTEBOOK=Test
11
+ # TEMPLATE=default
12
+
13
+ notebook = ENV['NOTEBOOK'] || "Test"
14
+ Mg2en::Options.defaults[:template] = ENV['TEMPLATE'] || "default"
15
+
16
+ # paths to source (mg3) and dest (enex)
17
+ mg3 = ENV['MG3'] ? File.absolute_path(ENV['MG3']) :
18
+ File.expand_path( '../spec/fixtures/1.mgourmet3', __FILE__)
19
+ enex = ENV['ENEX'] ? File.absolute_path(ENV['ENEX']) :
20
+ File.join(File.expand_path( '../tmp', __FILE__), File.basename(mg3, '.mgourmet3') + '.enex')
21
+
22
+ # display task description paths as relative paths to fit rake -T line
23
+ pwd = Pathname.new(Dir.getwd)
24
+ enex_relpath = Pathname.new(enex).relative_path_from(pwd)
25
+ mg3_relpath = Pathname.new(mg3).relative_path_from(pwd)
26
+
27
+ # Convert to/from files specified by environment vairiables:
28
+ # - MG3: conversion source file, defaults as above
29
+ # - ENEX: conversion destination file, defaults as above
30
+ # e.g. MG3=/tmp/notes.macgourmet3 ENEX=/tmp/notes.enex rake convert
31
+ desc "Convert MG3=#{mg3_relpath} to ENEX=#{enex_relpath}"
32
+ task :convert do
33
+ parser = Mg2en::Parser.new(mg3)
34
+ generator = Mg2en::Generator.new(parser.recipes)
35
+ generator.write(enex)
36
+ end
37
+
38
+ # Tell Evernote to import file to notebook specified by environment variables:
39
+ # - ENEX: the file to import, defaults as above
40
+ # - NOTEBOOK: the target notebook, defaults as above, must exist
41
+ # e.g. ENEX=/tmp/notes.enex NOTEBOOK=Notes rake import
42
+
43
+ desc "Imports ENEX=#{enex_relpath} into NOTEBOOK=#{notebook} (OS X only)"
44
+ task :import do
45
+ raise "task requires AppleScript" unless RUBY_PLATFORM =~ /darwin/
46
+
47
+ system "osascript -e 'tell application \"Evernote\" to import POSIX \
48
+ file \"#{enex}\" to notebook \"#{notebook}\" tags true'"
49
+ end
50
+
51
+ desc "Convert and preview MG3=#{mg3_relpath} in NOTEBOOK=#{notebook} (OS X only)"
52
+ task :preview => [:convert, :import]
53
+
54
+ desc "Render ENML for MG3=#{mg3_relpath}"
55
+ task :enml do
56
+ parser = Mg2en::Parser.new(mg3)
57
+ parser.recipes.each do |r|
58
+ puts r.enml
59
+ end
60
+ end
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
4
+ require 'optparse'
5
+ require 'ostruct'
6
+ require 'mg2en'
7
+
8
+ OptionParser.new do |opts|
9
+ opts.banner = 'Usage: mg2en [options] input_file [output_file]'
10
+
11
+ opts.on('-v', '--[no-]verbose', 'Run verbosely') do |v|
12
+ Mg2en::Options.defaults[:verbose] = v
13
+ end
14
+
15
+ opts.on('--version', 'Show version') do
16
+ puts Mg2en::VERSION
17
+ exit
18
+ end
19
+
20
+ opts.on('-t', '--template TEMPLATE',
21
+ 'Name of template',
22
+ ' (e.g. --template default)') do |t|
23
+ unless File.exists?(File.expand_path("../../templates/#{t}.haml",
24
+ __FILE__))
25
+ puts "ERROR: templates/#{t}.haml not found"
26
+ exit
27
+ end
28
+ Mg2en::Options.defaults[:template] = t
29
+ end
30
+
31
+ opts.on('-l', '--list-templates', 'List included templates') do
32
+ templates_dir = File.expand_path('../../templates', __FILE__)
33
+ Dir.foreach(templates_dir) do |t|
34
+ puts Regexp.last_match(1) if t =~ /^(.+)\.haml$/
35
+ end
36
+ exit
37
+ end
38
+
39
+ end.parse!
40
+
41
+ input_file = ARGV[0]
42
+ output_file = ARGV[1]
43
+
44
+ unless input_file && File.exists?(input_file)
45
+ puts 'ERROR: input file not found'
46
+ exit
47
+ end
48
+
49
+ parser = Mg2en::Parser.new(input_file)
50
+ generator = Mg2en::Generator.new(parser.recipes)
51
+ generator.write(output_file)
52
+
53
+ if Mg2en::Options.defaults[:verbose]
54
+ puts "Converted #{input_file} to #{output_file}" \
55
+ " using template #{Mg2en::Options.defaults[:template]}"
56
+ end
@@ -0,0 +1,10 @@
1
+ require 'mg2en/parser'
2
+ require 'mg2en/generator'
3
+ require 'mg2en/recipe'
4
+ require 'mg2en/options'
5
+ require 'mg2en/version'
6
+
7
+ # Configuration for library
8
+ module Mg2en
9
+ Haml::Options.defaults[:format] = :xhtml
10
+ end
@@ -0,0 +1,17 @@
1
+ module Mg2en
2
+ # This class holds a recipe direction step.
3
+ class Direction
4
+ attr_reader :description, :label, :highlighted
5
+ alias_method :highlighted?, :highlighted
6
+
7
+ def initialize(d)
8
+ @description = d['DIRECTION_TEXT']
9
+ @label = d['LABEL_TEXT']
10
+ @highlighted = d['IS_HIGHLIGHTED']
11
+ end
12
+
13
+ def to_s
14
+ "#{label} #{description} #{highlighted}"
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,56 @@
1
+ require 'builder'
2
+ require 'base64'
3
+
4
+ module Mg2en
5
+ # Writes out an array of recipe objects into ENEX file.
6
+ class Generator
7
+ def initialize(recipes)
8
+ @recipes = recipes
9
+ end
10
+
11
+ def write(destination = nil)
12
+ open_destination(destination)
13
+ @xm.instruct! :xml, version: '1.0', encoding: 'UTF-8'
14
+ @xm.declare! :DOCTYPE, :"en-export", :SYSTEM,
15
+ 'http://xml.evernote.com/pub/evernote-export3.dtd'
16
+ @xm.tag!(:'en-export',
17
+ :'export-date' => Time.new.strftime('%Y%m%dT%H%M%S%z'),
18
+ application: 'mg2en',
19
+ version: Mg2en::VERSION) do
20
+ @recipes.each do |recipe|
21
+ @xm.note { write_recipe(recipe) }
22
+ end
23
+ end
24
+ @xm.target!.close if destination
25
+ end
26
+
27
+ def open_destination(destination)
28
+ if destination
29
+ dfile = open(destination, 'w')
30
+ @xm = Builder::XmlMarkup.new(target: dfile)
31
+ else
32
+ @xm = Builder::XmlMarkup.new(target: STDOUT)
33
+ end
34
+ end
35
+
36
+ def write_recipe(recipe)
37
+ @xm.title(recipe.name)
38
+ @xm.content { @xm.cdata!(recipe.enml) }
39
+ recipe.tags.each do |t|
40
+ @xm.tag(t)
41
+ end
42
+ @xm.tag!(:'note-attributes') do
43
+ @xm.source(recipe.source) if recipe.source
44
+ @xm.tag!(:'source-url') { @xm.text! recipe.url } if recipe.url
45
+ end
46
+ if recipe.image
47
+ @xm.resource do
48
+ @xm.data(encoding: 'base64') do |d|
49
+ d << Base64.encode64(recipe.image)
50
+ end
51
+ @xm.mime('image/jpeg')
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,46 @@
1
+ module Mg2en
2
+ # This class holds an ingredient.
3
+ class Ingredient
4
+ attr_reader :measurement, :quantity, :description, :direction, :group,
5
+ :ingredients, :link
6
+ alias_method :group?, :group
7
+ alias_method :link?, :link
8
+
9
+ def initialize(i)
10
+ if i.key?('DESCRIPTION')
11
+ @group = false
12
+ @quantity = i['QUANTITY']
13
+ @measurement = i['MEASUREMENT']
14
+ @description = i['DESCRIPTION']
15
+ @direction = i['DIRECTION']
16
+ @link = i['INCLUDED_RECIPE_ID'] > 0
17
+ elsif i.key?('DIVIDER_INGREDIENT')
18
+ @group = true
19
+ @description = i['DIVIDER_INGREDIENT']['DESCRIPTION']
20
+ @ingredients = []
21
+ ingts = i['INGREDIENTS']
22
+ ingts.each do |ing|
23
+ ingredient = Mg2en::Ingredient.new(ing)
24
+ @ingredients.push ingredient
25
+ end
26
+ else
27
+ fail 'Did not recognize input'
28
+ end
29
+ end
30
+
31
+ def to_s
32
+ return @description if self.group?
33
+ output = ''
34
+ output << @quantity unless @quantity.empty?
35
+ output << without_quantity
36
+ end
37
+
38
+ def without_quantity
39
+ output = ''
40
+ output << ' ' << @measurement unless @measurement.empty?
41
+ output << ' ' << @description
42
+ output << ', ' << @direction unless @direction.empty?
43
+ output
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,16 @@
1
+ module Mg2en
2
+ # This class holds library options.
3
+ class Options
4
+ @defaults = {
5
+ verbose: false,
6
+ template: 'default',
7
+ image_size: 192,
8
+ }
9
+
10
+ # The default option values.
11
+ # @return Hash
12
+ def self.defaults # rubocop:disable TrivialAccessors
13
+ @defaults
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,19 @@
1
+ require 'mg2en/recipe'
2
+ require 'plist'
3
+
4
+ module Mg2en
5
+ # Parse a MacGourmet 3 Plist export file into array of recipe objects.
6
+ class Parser
7
+ attr_reader :recipes
8
+ def initialize(filename_or_xml)
9
+ recipe_input = Plist.parse_xml(filename_or_xml)
10
+ fail ArgumentError, 'Unable to parse input' unless recipe_input
11
+
12
+ @recipes = []
13
+ recipe_input.each do |r|
14
+ recipe = Mg2en::Recipe.new(r)
15
+ @recipes.push recipe
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,82 @@
1
+ require 'mg2en/ingredient'
2
+ require 'mg2en/direction'
3
+ require 'haml'
4
+ require 'digest'
5
+ require 'image_science'
6
+ require 'tempfile'
7
+ require 'uri'
8
+ require 'active_support/core_ext/object/blank'
9
+
10
+ module Mg2en
11
+ # This class holds a recipe.
12
+ class Recipe
13
+ attr_reader :name, :summary, :note, :source, :url, :image
14
+ attr_reader :ingredients, :directions, :notes, :tags, :cuisine
15
+
16
+ def initialize(r)
17
+ @name = r['NAME']
18
+ @summary = r['SUMMARY']
19
+ @note = r['NOTE']
20
+ @source = r['SOURCE']
21
+ @url = check_url(r['PUBLICATION_PAGE'])
22
+ @yield = r['YIELD']
23
+ @servings = r['SERVINGS']
24
+ scale_image(r['IMAGE'].read) if r['IMAGE']
25
+
26
+ @ingredients = []
27
+ r['INGREDIENTS_TREE'].each do |i|
28
+ ingredient = Mg2en::Ingredient.new(i)
29
+ @ingredients.push ingredient
30
+ end
31
+
32
+ @directions = []
33
+ r['DIRECTIONS_LIST'].each do |d|
34
+ direction = Mg2en::Direction.new(d)
35
+ @directions.push direction
36
+ end
37
+
38
+ @notes = []
39
+ r['NOTES_LIST'] && r['NOTES_LIST'].each { |n| @notes.push n['NOTE_TEXT'] }
40
+
41
+ @tags = []
42
+ r['CATEGORIES'] && r['CATEGORIES'].each { |c| @tags.push c['NAME'] }
43
+
44
+ @cuisine = []
45
+ r['CUISINE'] && r['CUISINE'].each { |c| @cuisine.push c['NAME']}
46
+ end
47
+
48
+ def enml
49
+ template_file = Mg2en::Options.defaults[:template] + '.haml'
50
+ template = File.expand_path("../../../templates/#{template_file}",
51
+ __FILE__)
52
+ engine = Haml::Engine.new(File.open(template).read)
53
+ engine.render(self)
54
+ end
55
+
56
+ def scale_image(image)
57
+ file = Tempfile.new(['thumb', '.jpg'])
58
+ begin
59
+ ImageScience.with_image_from_memory(image) do |i|
60
+ i.cropped_thumbnail(Mg2en::Options.defaults[:image_size]) do |thumb|
61
+ thumb.save(file.path)
62
+ end
63
+ end
64
+ @image = file.read
65
+ ensure
66
+ file.close
67
+ file.unlink
68
+ end
69
+ end
70
+
71
+ def check_url(url)
72
+ uri = URI.parse(url)
73
+ if uri.scheme.eql?('http') || uri.scheme.eql?('https')
74
+ return uri.to_s
75
+ else
76
+ return nil
77
+ end
78
+ rescue
79
+ return nil
80
+ end
81
+ end
82
+ end