epubforge 0.0.9 → 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.9
1
+ 0.0.10
@@ -0,0 +1,22 @@
1
+ module EpubForge
2
+ module Action
3
+ class Forge < ThorAction
4
+ include_standard_options
5
+ description "Create ebooks in various formats from the .markdown files in the project's book/ and notes/ subdirectories."
6
+
7
+ desc( "forge", "Wraps the project up in a .epub (ebook) file.")
8
+ def forge( *args )
9
+ before_start
10
+ builder = EpubForge::Epub::Builder.new( @project, :page_order => @project.config["pages"]["book"] )
11
+
12
+ builder.build
13
+ builder.package( @project.filename_for_epub_book )
14
+ builder.clean
15
+ puts "Done building epub <#{@project.filename_for_epub_book}>"
16
+ end
17
+
18
+ desc( "forge:epub", "Wraps the project up in a .epub (ebook) file." )
19
+ alias :epub :forge # I _think_ this will allow me to also use forge:epub as an alias for forge
20
+ end
21
+ end
22
+ end
@@ -1,20 +1,18 @@
1
1
  module EpubForge
2
2
  module Action
3
3
  class Help < ThorAction
4
- description "The help menu."
5
- keywords :help, :"-h", :"--help"
6
- usage "#{$PROGRAM_NAME} -h"
7
4
  project_not_required
8
5
 
9
- desc( "do:help", "print out help for the various actions.")
10
- def do( project, *args )
6
+ desc( "help", "print out help for the various actions.")
7
+ def help( *args )
11
8
  say_instruction "epubforge [action] [folder]"
12
9
  say_instruction "\tActions:"
13
- for action in Action::Runner.new.actions_lookup.actions
14
- say_instruction "\t( #{action.keywords.join(" | ")} ) :"
15
- say_instruction "\t\tDescription: #{action.description}"
16
- say_instruction "\t\tUsage: #{action.usage}\n"
17
- end
10
+ say_instruction ThorAction.command_to_action_classes.inspect
11
+ # for action in ThorAction.c
12
+ # say_instruction "\t( #{action.keywords.join(" | ")} ) :"
13
+ # say_instruction "\t\tDescription: #{action.description}"
14
+ # say_instruction "\t\tUsage: #{action.usage}\n"
15
+ # end
18
16
  end
19
17
  end
20
18
  end
@@ -1,12 +1,12 @@
1
1
  module EpubForge
2
2
  module Action
3
3
  class Init < ThorAction
4
- keywords :init, :initialize, :new
4
+ include_standard_options
5
5
  project_not_required
6
6
 
7
- desc("do:init", "create a new epubforge project")
8
- def do( project, *args )
9
- unless project.nil?
7
+ desc("init", "create a new epubforge project")
8
+ def init( *args )
9
+ unless @project.nil?
10
10
  say_error "Project already exists. Quitting."
11
11
  return false
12
12
  end
@@ -111,6 +111,8 @@ module EpubForge
111
111
  (@template_options[:title] || "").epf_underscorize + ".epubforge.git"
112
112
  end
113
113
 
114
+ # Expects the following arguments: 1:<project directory (shouldn't exist)>, 2: options hash.
115
+ # Options hash includes:
114
116
  def parse_args( *args )
115
117
  @opts = args.last.is_a?(Hash) ? args.pop : {}
116
118
  root = args.shift
@@ -128,7 +130,7 @@ module EpubForge
128
130
  return false
129
131
  end
130
132
 
131
- @template_to_use = "default"
133
+ @template_to_use = "default" # TODO: should turn into an option
132
134
  true
133
135
  end
134
136
  end
@@ -7,15 +7,12 @@ module EpubForge
7
7
  # requires_executable "ebook-convert"
8
8
 
9
9
  # TODO: Hard-coded. Need a global settings file?
10
- KINDLE_DEVICE_DIR = "/".fwf_filepath.join( "Volumes", "Kindle" )
11
- KINDLE_PUSH_DIR = KINDLE_DEVICE_DIR.join("documents", "fic-mine")
12
10
 
13
- desc( "do:kindle", "Turn your .epub file into a .mobi file. Check to see if your Kindle is connected, then pushes it." )
11
+ desc( "do:kindle", "<<OBSOLETE>> Turn your .epub file into a .mobi file. Check to see if your Kindle is connected, then pushes it." )
14
12
  def do( project, *args )
15
13
  @project = project
16
14
  @src_epub = @project.filename_for_epub_book.fwf_filepath
17
15
  @dst_mobi = @project.filename_for_mobi_book.fwf_filepath
18
-
19
16
  end
20
17
 
21
18
  protected
@@ -37,16 +34,6 @@ module EpubForge
37
34
  end
38
35
  end
39
36
 
40
- def push_to_device mobi_file
41
- if KINDLE_DEVICE_DIR.directory? && KINDLE_PUSH_DIR.directory?
42
- FileUtils.copy( mobi_file, KINDLE_PUSH_DIR )
43
- say_all_is_well "File pushed to Kindle."
44
- true
45
- else
46
- say_error "NOT installed on Kindle. It may not be plugged in."
47
- false
48
- end
49
- end
50
37
 
51
38
  def fulfill_requirements
52
39
  unless ebook_convert_installed?
@@ -54,7 +41,7 @@ module EpubForge
54
41
  return false
55
42
  end
56
43
 
57
- BookToEpub.new.do( @project )
44
+ Forge.new.do( @project )
58
45
 
59
46
  unless @src_epub.exist?
60
47
  say_error( "Cannot find source .epub #{src_epub}" )
@@ -0,0 +1,45 @@
1
+ module EpubForge
2
+ module Action
3
+ class LocalAction < ThorAction
4
+ TEMPLATE = EpubForge.root( "templates", "default", "settings", "actions", "local_action.rb.example" )
5
+
6
+ method_option :desc, :type => :string, :default => "Describe this module"
7
+ method_option :actions, :type => :string, :default => "do"
8
+ method_option :template, :type => :string, :default => TEMPLATE
9
+ method_option :outfile, :type => :string
10
+
11
+ desc( "action:local:add", "description" )
12
+ def add( *args )
13
+ before_start
14
+
15
+ command_name = @args.shift # Will either be a CamelCase (making a class) or a downcased
16
+ class_name ||= "ExampleAction"
17
+ actions = @actions.split(",").map(&:strip)
18
+ slug = class_name.epf_decamelize
19
+ template = @template
20
+ outfile = (@outfile || @project.settings_folder.join( "actions", "#{slug}.rb" )).fwf_filepath
21
+
22
+ if outfile.file?
23
+ puts "File already exists: #{outfile}"
24
+ exit(-1)
25
+ end
26
+
27
+ with_locals( { :desc => @desc, :class_name => class_name, :slug => slug, :actions => actions } ) do
28
+ erb = ERB.new( template.read )
29
+ result = erb.result(binding)
30
+
31
+ outfile.write( result )
32
+ end
33
+ end
34
+
35
+ protected
36
+ def before_start
37
+ super()
38
+ @desc = @options[:desc]
39
+ @actions = @options[:actions]
40
+ @template = @options[:template]
41
+ @outfile = @options[:outfile]
42
+ end
43
+ end
44
+ end
45
+ end
@@ -1,21 +1,26 @@
1
1
  module EpubForge
2
2
  module Action
3
- class Mobify < ThorAction
4
- description "Create a .mobi book and try to push it to your Kindle (conversion requires Calibre)"
5
- keywords :mobify
6
- usage "#{$PROGRAM_NAME} mobify <project_directory(optional)>"
7
- # requires_executable "ebook-convert", "ebook-convert is included as part of the Calibre ebook management software."
3
+ class Forge < ThorAction
4
+ # TODO: These should be user-specific settings
5
+ KINDLE_DEVICE_DIR = "/".fwf_filepath.join( "Volumes", "Kindle" )
6
+ KINDLE_PUSH_DIR = KINDLE_DEVICE_DIR.join( "documents", "fic-mine" )
8
7
 
9
- desc( "do:mobify", "Turn your .epub file into a .mobi file." )
10
- def do( project, *args )
11
- @project = project
8
+ method_option :push, :type => :string, :default => KINDLE_PUSH_DIR
9
+
10
+ desc( "forge:mobi", "Create a .mobi book. Optionally, try to push it to your Kindle. Conversion requires Calibre (esp. the ebook-convert command-line utility)." )
11
+ def mobi( *args )
12
+ before_start
13
+ @push_dir = (@options[:push] ? @options[:push].fwf_filepath.expand : nil)
14
+
15
+ @args = args
16
+ @push, @push_to = self.push?
12
17
  @src_epub = @project.filename_for_epub_book.fwf_filepath
13
18
  @dst_mobi = @project.filename_for_mobi_book.fwf_filepath
14
19
 
15
- @args = args
16
20
  @regenerate_epub = !!( @args.include?( "--no-cache" ) )
17
21
 
18
22
  mobify
23
+ push if @push_dir
19
24
  end
20
25
 
21
26
  protected
@@ -35,7 +40,18 @@ module EpubForge
35
40
  false
36
41
  end
37
42
  end
38
-
43
+
44
+ def push
45
+ if @push_dir.directory?
46
+ FileUtils.copy( mobi_file, @push_dir )
47
+ say_all_is_well "File pushed to Kindle."
48
+ true
49
+ else
50
+ say_error "#{@push_dir} does not exist. eBook NOT installed. Your device may not be plugged in."
51
+ false
52
+ end
53
+ end
54
+
39
55
 
40
56
  def fulfill_requirements
41
57
  unless ebook_convert_installed?
@@ -44,7 +60,7 @@ module EpubForge
44
60
  end
45
61
 
46
62
  if !@src_epub.exist? || @regenerate_epub
47
- BookToEpub.new.do( @project )
63
+ Forge.new.do( @project )
48
64
  end
49
65
 
50
66
  unless @src_epub.exist?
@@ -1,13 +1,11 @@
1
1
  module EpubForge
2
2
  module Action
3
- class NotesToEpub < ThorAction
4
- description "Create an epub book from the .markdown files in the project's notes/ subdirectory."
5
- keywords :notes, :forge_notes
6
- usage "#{$PROGRAM_NAME} notes <project_directory> (optional if current directory)"
3
+ class Forge < ThorAction
4
+ include_standard_options
7
5
 
8
- desc( "do:notes", "Wraps your story notes up in a .epub (ebook) file." )
9
- def do( project, *args )
10
- @project = project
6
+ desc( "forge:notes", "Wraps your story notes up in a .epub (ebook) file." )
7
+ def notes( *args )
8
+ before_start
11
9
  builder = EpubForge::Epub::Builder.new( @project, book_dir: @project.target_dir.join("notes"),
12
10
  page_order: @project.config[:pages][:notes] )
13
11
  builder.build
@@ -0,0 +1,79 @@
1
+ module EpubForge
2
+ module Action
3
+ class SpellDefinition
4
+ attr_accessor :incorrect, :correct, :regex, :exact, :hits
5
+ def initialize( incorrect, correct, opts = nil )
6
+ @incorrect = incorrect
7
+ @correct = correct
8
+ @opts = opts
9
+ @hits = 0
10
+
11
+ @exact = !!(@opts =~ /x/)
12
+ if @exact
13
+ @regex = /(\W|^)#{@incorrect}(\W|$)/
14
+ else
15
+ @regex = /#{@incorrect}/i
16
+ end
17
+ end
18
+
19
+ def hit
20
+ @hits += 1
21
+ end
22
+ end
23
+
24
+ class Spell < ThorAction
25
+ SPELL_CORRECTION_FILE = "spellings"
26
+
27
+ description "Highlight possible misspellings, as defined by the settings file /#{SPELL_CORRECTION_FILE}."
28
+ keywords :spell
29
+ usage "#{$PROGRAM_NAME} spell <project_directory>"
30
+
31
+ desc( "do:spell", "replace common misspellings." )
32
+
33
+ def do( project, *args )
34
+ @project = project
35
+ @spellings_file = @project.settings_folder( SPELL_CORRECTION_FILE )
36
+
37
+ load_spellings
38
+
39
+ @auto = args.include?("--auto")
40
+
41
+ for file in @project.pages
42
+ puts "\n#{file}"
43
+ puts "=" * file.to_s.length
44
+
45
+ file.readlines.each_with_index do |line, i|
46
+ for spelling in @spellings
47
+ if m = line.match( spelling.regex )
48
+ spelling.hit
49
+ puts "#{i}: #{spelling.incorrect} ==> #{spelling.correct} - #{line.gsub(spelling.regex, "<<<<#{spelling.incorrect}>>>>>")}"
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+
56
+ hit_report
57
+ end
58
+
59
+ protected
60
+ def load_spellings
61
+ quit_with_error( "File does not exist: #{@spellings_file}") unless @spellings_file.file?
62
+ @spellings = []
63
+ for line in @spellings_file.readlines
64
+ next if line =~ /^\s*(#|$)/ # remove comments and blank lines
65
+ chunks = line.split("|").map(&:strip)
66
+ @spellings << SpellDefinition.new( *chunks )
67
+ end
68
+
69
+ puts @spellings.inspect
70
+ end
71
+
72
+ def hit_report
73
+ for spelling in @spellings.select{|spell| spell.hit > 0}.sort_by(&:hits)
74
+ puts "#{spelling.incorrect} ===> #{spelling.correct} (#{spelling.hits})"
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -3,15 +3,17 @@ require 'time' # for Time.parse
3
3
  module EpubForge
4
4
  module Action
5
5
  class WordCount < ThorAction
6
+ include_standard_options
7
+
6
8
  WORD_COUNT_FILE = "wordcount"
7
9
 
8
- description "Gives approximate word counts for book chapters and notes."
10
+ description "Manage your word counts for book chapters and notes. wc (standalone) is main command"
9
11
  keywords :wc, :count
10
12
  usage "#{$PROGRAM_NAME} count <project_directory>"
11
13
 
12
- desc( "do:wc", "Countify words.")
13
- def do( project, *args )
14
- @project = project
14
+ desc( "wc", "Countify words.")
15
+ def wc( *args )
16
+ before_start
15
17
  @report = { "Notes" => wc_one_folder( @project.notes_dir ),
16
18
  "Book" => wc_one_folder( @project.book_dir ) }
17
19
 
@@ -4,13 +4,17 @@ module EpubForge
4
4
  attr_accessor :actions, :actions_directories, :keywords
5
5
 
6
6
  def initialize
7
+ clear
8
+ end
9
+
10
+ def clear
7
11
  @keywords = {}
8
12
  @actions = []
9
- @actions_directories = []
13
+ @actions_directories = []
10
14
  end
11
-
15
+
12
16
  def add_actions( *args )
13
- Utils::ActionLoader.require_me( *args )
17
+ Utils::ActionLoader.load_me( *args )
14
18
 
15
19
  new_actions = Utils::ActionLoader.loaded_classes - @actions
16
20
  @actions += new_actions
@@ -0,0 +1,23 @@
1
+ # NOT EVEN REMOTELY DONE
2
+ module EpubForge
3
+ module Actions
4
+ class HooksInterface
5
+ def add_hook( hookset, block ) # Symbol: either :before or :after
6
+
7
+ end
8
+
9
+ def run_hooks( hookset )
10
+ super( hookset ) unless self == ThorAction
11
+
12
+ end
13
+
14
+ def self.included( base )
15
+ if base == ThorAction
16
+ base.add_hook(:before) do
17
+ @project = @options[:project]
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -3,7 +3,8 @@ module EpubForge
3
3
  class RunDescription
4
4
  attr_accessor :args,
5
5
  :project,
6
- :keyword,
6
+ :namespace,
7
+ :subcommand,
7
8
  :klass,
8
9
  :errors,
9
10
  :state,
@@ -12,7 +13,8 @@ module EpubForge
12
13
  def initialize
13
14
  @args = nil
14
15
  @project = nil
15
- @keyword = nil
16
+ @namepace = nil
17
+ @subcommand = nil
16
18
  @klass = nil
17
19
  @errors = []
18
20
  @state = :initialized
@@ -21,7 +23,8 @@ module EpubForge
21
23
  def run
22
24
  if self.runnable?
23
25
  handle_errors do
24
- @execution_returned = self.klass.new.do( self.project, *(self.args) )
26
+ @args[0] = (@args[0]).split(":").last
27
+ @execution_returned = self.klass.start( @args )
25
28
  end
26
29
  end
27
30
 
@@ -43,6 +46,14 @@ module EpubForge
43
46
  puts "Error(s) trying to complete the requested action:"
44
47
  end
45
48
 
49
+ def quit_on_errors
50
+ if self.errors?
51
+ self.finish
52
+ self.report_errors
53
+ exit( -1 )
54
+ end
55
+ end
56
+
46
57
  def runnable?
47
58
  ! errors?
48
59
  end
@@ -65,8 +76,8 @@ module EpubForge
65
76
 
66
77
  def to_s
67
78
  str = "RunDescription:\n"
68
- [ :args, :project, :keyword, :klass, :errors, :state ].each do |data|
69
- str << "#{data} : #{self.send(data)}\n"
79
+ [ :args, :project, :namespace, :subcommand, :klass, :errors, :state ].each do |data|
80
+ str << "#{data} : #{self.send(data).inspect}\n"
70
81
  end
71
82
 
72
83
  str
data/lib/action/runner.rb CHANGED
@@ -4,6 +4,7 @@ module EpubForge
4
4
  module Action
5
5
  class Runner
6
6
  attr_accessor :actions_lookup
7
+
7
8
  def initialize
8
9
  reset
9
10
  end
@@ -11,9 +12,18 @@ module EpubForge
11
12
  def reset
12
13
  @args = []
13
14
  @run_description = RunDescription.new
14
- @actions_lookup = ActionsLookup.new
15
- @actions_lookup.add_actions( EpubForge::ACTIONS_DIR )
16
- @actions_lookup.add_actions( EpubForge::USER_ACTIONS_DIR ) if EpubForge::USER_ACTIONS_DIR.directory?
15
+ end
16
+
17
+ def load_actions_dirs
18
+ ThorAction.actions_lookup.add_actions( EpubForge::ACTIONS_DIR )
19
+ ThorAction.actions_lookup.add_actions( EpubForge::USER_ACTIONS_DIR ) if EpubForge::USER_ACTIONS_DIR.directory?
20
+ end
21
+
22
+ def load_project_machinery
23
+ if @run_description.project
24
+ ThorAction.actions_lookup.add_actions( @run_description.project.settings_folder( "actions" ) )
25
+ Utils::Htmlizer.instance.add_htmlizers( @run_description.project.settings_folder( "htmlizers.rb" ) )
26
+ end
17
27
  end
18
28
 
19
29
  def run
@@ -36,82 +46,88 @@ module EpubForge
36
46
  end
37
47
 
38
48
 
39
- # The priority for the project directory
40
- # 1) explicitly stated directory --project=/home/andersbr/writ/fic/new_project
41
- # 2) the current working directory (if it's an existing project)
42
- # 3) the final arg
43
- #
44
- # At this point,
49
+
45
50
  protected
46
51
  def parse_args
52
+ @args << "help" if @args.epf_blank?
47
53
  @run_description = RunDescription.new
48
- @run_description.keyword = @args.shift || "help"
49
-
50
- existing_project = false
51
- project_dir = get_explicit_project_option( @args )
52
-
53
- # see if the last argument is a project directory
54
- unless project_dir || @args.length == 0
55
- last_arg = @args.pop
56
- unless project_dir = ( Project.is_project_dir?( last_arg ) )
57
- @args.push( last_arg )
58
- end
59
- end
60
54
 
61
- # see if current working directory is a project directory
62
- unless project_dir
63
- cwd = FunWith::Files::FilePath.cwd
64
- if Project.is_project_dir?( cwd )
65
- project_dir = cwd
66
- end
67
- end
55
+ fetch_project
56
+ @run_description.quit_on_errors
68
57
 
69
- # At this point, if we're going to find an existing project directory, we'll have found it by now.
70
- # Time to load the actions and determine whether the keyword matches an existing action
71
- if project_dir && Project.is_project_dir?( project_dir )
72
- existing_project = true
73
- @run_description.project = Project.new( project_dir )
74
- @actions_lookup.add_actions( @run_description.project.settings_folder( "actions" ) )
75
- Utils::Htmlizer.instance.add_htmlizers( @run_description.project.settings_folder( "htmlizers.rb" ) )
76
- end
58
+ load_actions_dirs
59
+ load_project_machinery
77
60
 
78
- map_keyword_to_action
61
+ map_command_to_klass
79
62
 
80
- if !existing_project && @run_description.klass.project_required?
63
+ return false unless @run_description.klass
64
+
65
+ if @run_description.project.nil? && @run_description.klass.project_required?
81
66
  @run_description.errors << "Could not find a project directory, but the action #{@run_description.klass} requires one. Current directory is not an epubforge project."
82
67
  else
83
68
  @run_description.args = @args
84
69
  end
85
70
  end
86
71
 
87
- def map_keyword_to_action
88
- actions = actions_lookup.keyword_to_action( @run_description.keyword )
72
+ def map_command_to_klass
73
+ @run_description.klass = ThorAction.command_to_action_classes[@args.first]
74
+ if @run_description.klass.nil?
75
+ @run_description.errors << "Unrecognized keyword <#{@args.first}>. Quitting."
76
+ end
77
+ end
78
+
79
+
80
+ # The priority for the project directory
81
+ # 1) explicitly stated directory --project=/home/andersbr/writ/fic/new_project
82
+ # 2) the arg immediately after the command:subcommand:subsubcommand arg
83
+ # 3) the current working directory (if it's an existing project)
84
+ #
85
+ # As a side-effect, replaces implicit directories with an explicit --project flag as the final argument
86
+ # because Thor seems to like explicit flags.
87
+ def fetch_project
88
+ project_dir = fetch_project_by_project_flag
89
+ project_dir ||= fetch_project_by_second_arg
90
+ project_dir ||= fetch_project_by_current_dir
89
91
 
90
- if actions.length == 1
91
- @run_description.klass = actions.first
92
- elsif actions.length == 0
93
- @run_description.errors << "Unrecognized keyword <#{keyword}>. Quitting."
94
- false
95
- else
96
- @run_description.errors << "Ambiguous keyword <#{keyword}>. Did you mean...?\n#{actions.map(&:usage).join('\n')}"
97
- false
92
+ if project_dir
93
+ @run_description.project = Project.new( project_dir )
94
+ @args.push( "--project=#{project_dir}" )
98
95
  end
99
96
  end
100
97
 
101
- def get_explicit_project_option( args )
102
- proj_opt_regex = /^--project=/
103
-
104
- proj_opt = args.find do |arg|
105
- arg.is_a?(String) && arg.match( proj_opt_regex )
98
+ def fetch_project_by_project_flag
99
+ project_dir = nil
100
+ project_flag_regex = /^--proj(ect)?=/
101
+ @args.each_with_index do |arg, i|
102
+ if arg.is_a?(String) && arg =~ project_flag_regex
103
+ project_dir = arg.gsub( project_flag_regex, "" ).epf_remove_surrounding_quotes.fwf_filepath.expand
104
+ if Project.is_project_dir?( project_dir )
105
+ @args.delete_at(i)
106
+ else
107
+ @run_description.errors << "Project given by flag --project= is not a valid project directory."
108
+ end
109
+ end
106
110
  end
111
+
112
+ project_dir
113
+ end
107
114
 
108
- if proj_opt
109
- args.delete( proj_opt )
110
- proj_opt.gsub( proj_opt, '' ).fwf_filepath
115
+ def fetch_project_by_second_arg
116
+ if Project.is_project_dir?( @args[1] )
117
+ return @args.delete_at(1)
111
118
  else
112
- false
119
+ return nil
113
120
  end
114
121
  end
122
+
123
+ def fetch_project_by_current_dir
124
+ cwd = FunWith::Files::FilePath.cwd
125
+ project_dir = (Project.is_project_dir?( cwd ) ? cwd : nil)
126
+ end
127
+
128
+ def print_help
129
+
130
+ end
115
131
  end
116
132
  end
117
133
  end
@@ -1,11 +1,51 @@
1
1
  module EpubForge
2
2
  module Action
3
3
  module SharedActionInterface
4
+ def actions_lookup
5
+ if self == ThorAction
6
+ @actions_lookup ||= ActionsLookup.new
7
+ else
8
+ ThorAction.actions_lookup
9
+ end
10
+ end
11
+
12
+ def register_action_subclass( klass )
13
+ if self == ThorAction
14
+ @subclasses ||= []
15
+ @subclasses = (@subclasses + [klass]).uniq
16
+ else
17
+ ThorAction.register_action_subclass( klass )
18
+ end
19
+ end
20
+
21
+ def subclasses
22
+ if self == ThorAction
23
+ @subclasses
24
+ else
25
+ ThorAction.subclasses
26
+ end
27
+ end
28
+
29
+ def command_to_action_classes
30
+ if self == ThorAction
31
+ @command_klass_lookup ||= {}
32
+ else
33
+ ThorAction.command_to_action_classes
34
+ end
35
+ end
36
+
4
37
  def description( str = nil )
5
38
  @description = str if str
6
39
  @description
7
40
  end
8
41
 
42
+ # eventually replace description
43
+ def desc( usage, description, options = {} )
44
+ self.command_to_action_classes[usage] = self
45
+ super( usage, description, options )
46
+ end
47
+
48
+ # TODO: Get rid of this
9
49
  def keywords( *args )
10
50
  if args.epf_blank?
11
51
  @keywords ||= []
@@ -34,16 +74,24 @@ module EpubForge
34
74
  def project_not_required
35
75
  @project_required = false
36
76
  end
77
+
78
+ def include_standard_options
79
+ method_option :verbose, :type => :boolean, :default => false, :aliases => "-v"
80
+ method_option :debug, :type => :boolean, :default => false, :aliases => "--dbg"
81
+ method_option :help, :type => :boolean, :default => false, :aliases => "-h"
82
+ method_option :project, :type => :string, :default => nil, :aliases => "--proj"
83
+ end
37
84
  end
38
85
 
39
86
  class ThorAction < Thor
87
+ def self.inherited( subclass )
88
+ self.register_action_subclass( subclass )
89
+ subclass.include_standard_options
90
+ end
91
+
40
92
  include Thor::Actions
41
93
  extend SharedActionInterface
42
-
43
- method_option :verbose, :type => :boolean, :default => false, :aliases => "-v"
44
- method_option :debug, :type => :boolean, :default => false, :aliases => "--dbg"
45
-
46
-
94
+
47
95
  CLEAR = Thor::Shell::Color::CLEAR
48
96
  RED = Thor::Shell::Color::RED
49
97
  BLUE = Thor::Shell::Color::BLUE
@@ -54,7 +102,7 @@ module EpubForge
54
102
  ON_BLUE = Thor::Shell::Color::ON_BLUE
55
103
 
56
104
 
57
- protected
105
+ protected
58
106
  def say_when_verbose( *args )
59
107
  say( *args ) if @verbose
60
108
  end
@@ -172,6 +220,19 @@ module EpubForge
172
220
  def project_already_gitted?
173
221
  @project.target_dir.join( ".git" ).directory?
174
222
  end
223
+
224
+ def quit_with_error( msg, errno = -1 )
225
+ STDERR.write( "\n#{msg}\n")
226
+ exit( errno )
227
+ end
228
+
229
+ def before_start
230
+ @project = @options[:project]
231
+ @debug = @options[:debug]
232
+ @help = @options[:help]
233
+ @verbose = @options[:verbose]
234
+ @project = Project.new( @project ) unless @project.nil?
235
+ end
175
236
  end
176
237
  end
177
238
  end
@@ -18,5 +18,13 @@ module Kernel
18
18
  instance_variable_set( var, v )
19
19
  end
20
20
  end
21
-
21
+
22
+ # Runs a block of code without warnings.
23
+ def silence_warnings(&block)
24
+ warn_level = $VERBOSE
25
+ $VERBOSE = nil
26
+ result = block.call
27
+ $VERBOSE = warn_level
28
+ result
29
+ end
22
30
  end
@@ -6,6 +6,18 @@ class String
6
6
  def epf_camelize
7
7
  gsub(/(?:^|_)(.)/) { $1.upcase }
8
8
  end
9
+
10
+ def epf_decamelize
11
+ self.gsub( /([a-z])([A-Z])/ ) { $1.downcase + "_" + $2.downcase }.downcase
12
+ end
13
+
14
+ def epf_remove_surrounding_quotes
15
+ self.gsub(/(^['"]|['"]$)/)
16
+ end
17
+
18
+ def epf_remove_surrounding_quotes!
19
+ self.replace( self.epf_remove_surrounding_quotes )
20
+ end
9
21
 
10
22
  # TODO: Need comprehensive list of characters to be protected.
11
23
  def epf_backhashed_filename
@@ -26,29 +26,34 @@ module EpubForge
26
26
  end
27
27
  end
28
28
 
29
+ # To see the STDOUT, simply call EpubForge.collect_stdout( STDOUT )
29
30
  def collect_stdout( dest = StringIO.new, &block )
30
- raise ArgumentError.new("No block given.") unless block_given?
31
+ if dest == $stdout
32
+ yield
33
+ else
34
+ raise ArgumentError.new("No block given.") unless block_given?
31
35
 
32
- prior_stdout = $stdout
33
- # @epf_prior_stdout_stack ||= []
34
- # @epf_prior_stdout_stack << $stdout
36
+ prior_stdout = $stdout
37
+ # @epf_prior_stdout_stack ||= []
38
+ # @epf_prior_stdout_stack << $stdout
35
39
 
36
- $stdout = begin
37
- if dest.is_a?( String ) || dest.is_a?( Pathname )
38
- File.open( dest, "a" )
39
- elsif dest.is_a?( IO ) || dest.is_a?( StringIO )
40
- dest
41
- else
42
- raise ArgumentError.new("collect_stdout cannot take a <#{dest.class.name}> as an argument.")
40
+ $stdout = begin
41
+ if dest.is_a?( String ) || dest.is_a?( Pathname )
42
+ File.open( dest, "a" )
43
+ elsif dest.is_a?( IO ) || dest.is_a?( StringIO )
44
+ dest
45
+ else
46
+ raise ArgumentError.new("collect_stdout cannot take a <#{dest.class.name}> as an argument.")
47
+ end
43
48
  end
44
- end
45
49
 
46
- $stdout.sync = true
47
- yield
50
+ $stdout.sync = true
51
+ yield
48
52
 
49
- $stdout = prior_stdout
53
+ $stdout = prior_stdout
50
54
 
51
- dest.is_a?( StringIO ) ? dest.string : nil
55
+ dest.is_a?( StringIO ) ? dest.string : nil
56
+ end
52
57
  end
53
58
 
54
59
  # def collect_stdout( *args, &block )
data/lib/epub/builder.rb CHANGED
@@ -19,13 +19,14 @@ module EpubForge
19
19
 
20
20
  class Builder
21
21
  attr_reader :stylesheets
22
+ attr_reader :project
22
23
 
23
24
  def initialize project, opts = {}
24
25
  puts "--------------- forgin' #{project.filename_for_epub_book} ------------------"
25
26
  @project = project
26
27
  @config = project.config
27
28
  @book_dir_short = opts[:book_dir] ? opts[:book_dir].split.last.to_s : "book"
28
- @book_dir = @project.target_dir.join( @book_dir_short ).fwf_filepath
29
+ @book_dir = @project.target_dir.join( @book_dir_short ).fwf_filepath.expand
29
30
  @config = @project.config
30
31
 
31
32
  @config.page_orderer = Utils::FileOrderer.new( opts[:page_order] || @config.pages[@book_dir_short] )
data/lib/epubforge.rb CHANGED
@@ -66,7 +66,6 @@ end
66
66
  EpubForge.install_fwc_config_from_file( EpubForge::USER_GLOBALS_FILE )
67
67
 
68
68
  EpubForge.config.activation_key = rand(20**32).to_s(16).gsub(/(.{5})/, '\1-')[0..-2]
69
- puts "Thank you for registering your copy of the epubforge gem. Please write down your activation key (#{EpubForge.config.activation_key}) in case you need to call customer service."
70
69
 
71
70
  require_relative 'utils/html_translator'
72
71
  require_relative 'utils/html_translator_queue'
@@ -58,6 +58,22 @@ module EpubForge
58
58
  @book_dir.glob("chapter-????.*")
59
59
  end
60
60
 
61
+
62
+ def pages( orderer = nil )
63
+ case orderer
64
+ when NilClass
65
+ orderer = Utils::FileOrderer.new( self.config.pages.book || [] )
66
+ when Utils::FileOrderer
67
+ # pass
68
+ when Array
69
+ orderer = Utils::FileOrderer.new( orderer )
70
+ else
71
+ raise "Project#pages cannot take #{order.class} as an ordering object."
72
+ end
73
+
74
+ orderer.reorder( @book_dir.glob( ext: EpubForge::Epub::PAGE_FILE_EXTENSIONS ) )
75
+ end
76
+
61
77
  def load_configuration
62
78
  self.install_fwc_config_from_file( config_file )
63
79
  end
@@ -77,6 +77,26 @@ module EpubForge
77
77
  end
78
78
  end
79
79
  end
80
+
81
+ # just loading all the files is simpler, and probably little harm from reloading. Plus, I want
82
+ # to be able to split up existing ThorClasses across multiple files.
83
+ def self.load_me( *loadables )
84
+ silence_warnings do
85
+ for loadable in loadables
86
+ loadable = loadable.fwf_filepath
87
+
88
+ if loadable.file?
89
+ load( loadable )
90
+ elsif loadable.directory?
91
+ for entry in loadable.glob( :ext => "rb", :recursive => true )
92
+ load( entry )
93
+ end
94
+ else
95
+ puts "Warning: No idea what I'm trying to load (#{loadable}:#{loadable.class})"
96
+ end
97
+ end
98
+ end
99
+ end
80
100
  end
81
101
  end
82
102
  end
@@ -1,14 +1,14 @@
1
1
  module EpubForge
2
2
  module Action
3
- class ActionFromProjectActionsDirectory < ThorAction
4
- description "Use this file in the actions/ folder as a template for your custom actions."
5
- keywords :project_action
6
- usage "#{$PROGRAM_NAME} project_action"
3
+ class <%= @class_name %> < ThorAction
7
4
 
8
-
9
- def do( project, *args )
10
- puts "Do something to your project."
5
+ <% for action in @actions %>
6
+ desc( "<%= @slug %>:<%= action %>", "do action <%= action %>" )
7
+ def <%= action %>( project, *args )
8
+ puts "Do action <%= action %> to your project."
11
9
  end
10
+
11
+ <% end %>
12
12
  end
13
13
  end
14
- end
14
+ end
data/test/helper.rb CHANGED
@@ -18,5 +18,50 @@ $LOAD_PATH.unshift(File.dirname(__FILE__))
18
18
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
19
19
  require 'epubforge'
20
20
 
21
- class Test::Unit::TestCase
21
+ module EpubForge
22
+ class TestCase < Test::Unit::TestCase
23
+ protected
24
+ def create_project( verbose = false, &block )
25
+ EpubForge::Utils::DirectoryBuilder.tmpdir do |d|
26
+ pipe_output_to = (verbose ? $stdout : StringIO.new)
27
+ @project_dir = d.current_path.join("project")
28
+
29
+ @printout = EpubForge.collect_stdout( pipe_output_to ) do # collect_stdout(STDOUT) to see what's being outputted.
30
+ EpubForge::Action::Runner.new.exec( "init", @project_dir, fill_in_project_options )
31
+ end
32
+
33
+ assert @project_dir.directory?, "Project directory doesn't exist. Cannot proceed."
34
+
35
+ @book_title = fill_in_project_options[:answers][:title]
36
+ @chapter_count = fill_in_project_options[:answers][:chapter_count].to_i
37
+ @ebook_file = @project_dir.join( @book_title.epf_underscorize + ".epub" )
38
+ @notes_file = @project_dir.join( @book_title.epf_underscorize + ".notes.epub" )
39
+
40
+ yield
41
+ end
42
+ end
43
+
44
+ def fill_in_project_options( opts = {} )
45
+ template_options = {
46
+ :answers => {
47
+ :chapter_count => 3,
48
+ :title => "The Courtesan of Fate",
49
+ :author => "Wilberforce Poncer",
50
+ :license => "You Owe Me All the Money Limited License, v. 2.1",
51
+ :use_git => true,
52
+ :git => {
53
+ :repo_id => "abcdef0123456789",
54
+ :backup_type => "Back up to a remote host.",
55
+ :host => "myhost.somewhere.com",
56
+ :user => "andersbr",
57
+ :repo => "/home/andersbr/git"
58
+ }
59
+ }
60
+ }
61
+
62
+ template_options[:answers].merge(opts)
63
+
64
+ template_options
65
+ end
66
+ end
22
67
  end
@@ -1,6 +1,6 @@
1
1
  require 'helper'
2
2
 
3
- class TestCustomHelpers < Test::Unit::TestCase #
3
+ class TestCustomHelpers < EpubForge::TestCase #
4
4
  context "Testing collect_stdout" do
5
5
  should "not print out" do
6
6
  outer = ""
@@ -2,7 +2,7 @@ require 'helper'
2
2
 
3
3
  DirBuilder = EpubForge::Utils::DirectoryBuilder
4
4
 
5
- class TestDirectoryBuilder < Test::Unit::TestCase
5
+ class TestDirectoryBuilder < EpubForge::TestCase
6
6
  context "tearing my hair out because shoulda seems borked" do
7
7
  should "stop blaming shoulda for my problems" do
8
8
  assert true
@@ -1,6 +1,6 @@
1
1
  require 'helper'
2
2
 
3
- class TestDirectoryBuilder < Test::Unit::TestCase
3
+ class TestDirectoryBuilder < EpubForge::TestCase
4
4
  context "testing absolute basics" do
5
5
  should "provide an accurate root" do
6
6
  assert_equal File.expand_path( File.join( File.dirname(__FILE__), ".." ) ), EpubForge.root.to_s
@@ -1,16 +1,17 @@
1
1
  require 'helper'
2
2
  require 'thor'
3
3
 
4
- class TestEpubforge < Test::Unit::TestCase #
4
+ class TestEpubforge < EpubForge::TestCase #
5
5
  context "Testing a few basic commands" do
6
- should "print successfully" do
7
- printout = EpubForge.collect_stdout do
8
- EpubForge::Action::Runner.new.exec # empty args, should append --help
9
- end
10
-
11
- assert_match /\( wc \| count \)/, printout
12
- assert_match /epubforge \[action\] \[folder\]/, printout
13
- end
6
+ # TODO: Figure out wtf to do for a help system
7
+ # should "print successfully" do
8
+ # printout = EpubForge.collect_stdout do
9
+ # EpubForge::Action::Runner.new.exec # empty args, should append --help
10
+ # end
11
+ #
12
+ # assert_match /\( wc \| count \)/, printout
13
+ # assert_match /epubforge \[action\] \[folder\]/, printout
14
+ # end
14
15
 
15
16
  should "initialize a new project" do
16
17
  create_project do
@@ -51,7 +52,7 @@ class TestEpubforge < Test::Unit::TestCase #
51
52
 
52
53
  should "create an .epub file" do
53
54
  create_project do
54
- printout = EpubForge.collect_stdout do
55
+ printout = EpubForge.collect_stdout() do # .collect_stdout(STDOUT) to see what's going on
55
56
  EpubForge::Action::Runner.new.exec( "forge", @project_dir )
56
57
  end
57
58
 
@@ -112,54 +113,16 @@ class TestEpubforge < Test::Unit::TestCase #
112
113
 
113
114
  should "create an .epub of the notes directory" do
114
115
  create_project do
115
- EpubForge::Action::Runner.new.exec( "forge_notes", @project_dir )
116
-
116
+ EpubForge::Action::Runner.new.exec( "forge:notes", @project_dir )
117
117
  assert @notes_file.file?
118
118
  assert ! @notes_file.empty?
119
119
  end
120
120
  end
121
- end
122
-
123
- protected
124
- def create_project( &block )
125
- EpubForge::Utils::DirectoryBuilder.tmpdir do |d|
126
- @project_dir = d.current_path.join("project")
127
- @printout = EpubForge.collect_stdout do
128
- EpubForge::Action::Runner.new.exec( "init", @project_dir, fill_in_project_options )
129
- end
130
-
131
- assert @project_dir.directory?, "Project directory doesn't exist. Cannot proceed."
132
-
133
- @book_title = fill_in_project_options[:answers][:title]
134
- @chapter_count = fill_in_project_options[:answers][:chapter_count].to_i
135
- @ebook_file = @project_dir.join( @book_title.epf_underscorize + ".epub" )
136
- @notes_file = @project_dir.join( @book_title.epf_underscorize + ".notes.epub" )
137
-
138
- yield
139
- end
140
- end
141
-
142
- def fill_in_project_options( opts = {} )
143
- template_options = {
144
- :answers => {
145
- :chapter_count => 3,
146
- :title => "The Courtesan of Fate",
147
- :author => "Wilberforce Poncer",
148
- :license => "You Owe Me All the Money Limited License, v. 2.1",
149
- :use_git => true,
150
- :git => {
151
- :repo_id => "abcdef0123456789",
152
- :backup_type => "Back up to a remote host.",
153
- :host => "myhost.somewhere.com",
154
- :user => "andersbr",
155
- :repo => "/home/andersbr/git"
156
- }
157
- }
158
- }
159
121
 
160
- template_options[:answers].merge(opts)
122
+ should "print friggin' something when no args" do
123
+ EpubForge::Action::Runner.new.exec()
124
+ end
161
125
 
162
- template_options
163
126
  end
164
127
  end
165
128
  end
@@ -1,7 +1,7 @@
1
1
  require 'helper'
2
2
  Htmlizer = EpubForge::Utils::Htmlizer
3
3
 
4
- class TestHtmlizers < Test::Unit::TestCase
4
+ class TestHtmlizers < EpubForge::TestCase
5
5
  context "testing htmlizers" do
6
6
  setup do
7
7
  @samples = EpubForge.root("test", "sample_text")
data/test/test_runner.rb CHANGED
@@ -2,7 +2,7 @@ require 'helper'
2
2
 
3
3
  Runner = EpubForge::Action::Runner
4
4
 
5
- class TestRunner < Test::Unit::TestCase
5
+ class TestRunner < EpubForge::TestCase
6
6
  context "Testing argument parsing" do
7
7
  setup do
8
8
  @runner = Runner.new
data/test/test_utils.rb CHANGED
@@ -2,7 +2,7 @@ require 'helper'
2
2
 
3
3
  include EpubForge::Utils
4
4
 
5
- class TestUtils < Test::Unit::TestCase
5
+ class TestUtils < EpubForge::TestCase
6
6
  context "testing file orderer" do
7
7
  should "accurately sort files in the templates/book dir" do
8
8
  files = %W(afterword.markdown
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: epubforge
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.9
4
+ version: 0.0.10
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-05-23 00:00:00.000000000 Z
12
+ date: 2013-12-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: xdg
@@ -214,7 +214,7 @@ extra_rdoc_files:
214
214
  - README.rdoc
215
215
  files:
216
216
  - ./bin/epubforge
217
- - ./config/actions/book_to_epub.rb
217
+ - ./config/actions/forge.rb
218
218
  - ./config/actions/generate.rb
219
219
  - ./config/actions/generate_chapter.rb
220
220
  - ./config/actions/git_backup.rb
@@ -223,9 +223,11 @@ files:
223
223
  - ./config/actions/help.rb
224
224
  - ./config/actions/init.rb
225
225
  - ./config/actions/kindle.rb
226
+ - ./config/actions/local_action.rb
226
227
  - ./config/actions/mobify.rb
227
228
  - ./config/actions/notes_to_epub.rb
228
229
  - ./config/actions/notes_to_kindle.rb
230
+ - ./config/actions/spell.rb
229
231
  - ./config/actions/version.rb
230
232
  - ./config/actions/word_count.rb
231
233
  - ./config/actions/wrap_scene_notes_in_hidden_div.rb
@@ -234,6 +236,7 @@ files:
234
236
  - ./lib/action/cli_command.rb
235
237
  - ./lib/action/cli_sequence.rb
236
238
  - ./lib/action/file_transformer.rb
239
+ - ./lib/action/hooks_interface.rb
237
240
  - ./lib/action/run_description.rb
238
241
  - ./lib/action/runner.rb
239
242
  - ./lib/action/thor_action.rb
@@ -317,7 +320,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
317
320
  version: '0'
318
321
  segments:
319
322
  - 0
320
- hash: -2032858985098992460
323
+ hash: -427571737354431550
321
324
  required_rubygems_version: !ruby/object:Gem::Requirement
322
325
  none: false
323
326
  requirements:
@@ -1,20 +0,0 @@
1
- module EpubForge
2
- module Action
3
- class BookToEpub < ThorAction
4
- description "Create an epub book from the .markdown files in the project's book/ subdirectory."
5
- keywords :forge, :book
6
- usage "#{$PROGRAM_NAME} forge <project_directory (optional if current directory)>"
7
-
8
- desc( "do:forge", "Wraps the project up in a .epub (ebook) file.")
9
- def do( project, *args )
10
- @project = project
11
- builder = EpubForge::Epub::Builder.new( @project, :page_order => @project.config["pages"]["book"] )
12
-
13
- builder.build
14
- builder.package( @project.filename_for_epub_book )
15
- builder.clean
16
- puts "Done building epub <#{@project.filename_for_epub_book}>"
17
- end
18
- end
19
- end
20
- end