epubforge 0.0.9 → 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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