texico 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 88fb2853d7789e04ac86329f0cdbd5e9f140a66e69ee24a707af14541e19e0b1
4
+ data.tar.gz: f8738047d2a26f108a1353e7803d9eed2c6e9a796a7d76b7d61ce2e6ea29906e
5
+ SHA512:
6
+ metadata.gz: bcbdbcacd5bb859a342e4e96839851b9ae7a785d26967ab263026f4a9da7654308e828d5db9746b06c553adb440763f2fb937c82e21b5c4e79efaf2b1e43f0e2
7
+ data.tar.gz: 60f1c157554d8ffaf59b74c95fa3bc106b4e22dd323b395433d8c1e6b64f204473f80b3b464cd4ef5e2c228baea9ddbaf32da2d8dd52a3fa7f90ded5778b6615
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /references/
11
+ /test/tmp/
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.0
5
+ before_install: gem install bundler -v 1.12.5
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in texico.gemspec
4
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Sebastian Lindberg
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all 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,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,46 @@
1
+ # Texico
2
+
3
+ Texico is created to suit my Latex workflow. It can be used to
4
+
5
+ - setup new projects,
6
+ - build existing projects and
7
+ - tag versions for release.
8
+
9
+ More functionality is planned.
10
+
11
+ ## Installation
12
+
13
+ Building projects requires `latexmk`. You can install it by running
14
+
15
+ $ tlmgr install latexmk
16
+
17
+ Then install Texico with
18
+
19
+ $ gem install texico
20
+
21
+ ## Usage
22
+
23
+ To setup a new project in the current directory, run
24
+
25
+ $ texico init
26
+
27
+ You will be guided through the setup process. When it completes you will be able to build the project using
28
+
29
+ $ texico
30
+
31
+ ### Global Config
32
+
33
+ I find it useful to store my name and email address in the global config. You can either create a `.texico` file in your home directory and add the relevant fields, or run
34
+
35
+ $ texico config --global author='Your Name' email=name@example.com
36
+
37
+ ## Development
38
+
39
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
40
+
41
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
42
+
43
+ ## License
44
+
45
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
46
+
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "texico"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ #require 'texico'
4
+ # require 'fileutils'
5
+ # require 'slop'
6
+ # require 'pastel'
7
+ # require 'tty-prompt'
8
+
9
+ require 'texico'
10
+
11
+
12
+
13
+ # Notes:
14
+ # - Use 🌮 somewhere/everywhere
15
+ #
16
+
17
+ parser = Texico::CLI::ArgParser.new
18
+ command = parser.parse
19
+
20
+ command.run
21
+ #puts ""
@@ -0,0 +1,19 @@
1
+ require 'texico/version'
2
+ require 'texico/cli'
3
+ require 'texico/template/file'
4
+ require 'texico/template/file_status'
5
+ require 'texico/template'
6
+ require 'texico/compiler'
7
+ require 'texico/config_file'
8
+ require 'texico/git'
9
+ require 'texico/cli/command/base'
10
+ require 'texico/cli/command/init'
11
+ require 'texico/cli/command/build'
12
+ require 'texico/cli/command/release'
13
+ require 'texico/cli/command/config'
14
+ require 'texico/cli/command/clean'
15
+ require 'texico/cli/command'
16
+ require 'texico/cli/arg_parser'
17
+
18
+ module Texico
19
+ end
@@ -0,0 +1,5 @@
1
+ module Texico
2
+ module CLI
3
+ ICON = '🌮'
4
+ end
5
+ end
@@ -0,0 +1,63 @@
1
+ require 'slop'
2
+ require 'tty-prompt'
3
+
4
+ module Texico
5
+ module CLI
6
+ class ArgParser
7
+ # Returns a hash with the options
8
+ #
9
+ # The method may call Kernel.exit
10
+ def parse(items = ARGV, prompt: TTY::Prompt.new)
11
+ title = prompt.decorate('texico', :yellow)
12
+ opts =
13
+ Slop.parse(items) do |o|
14
+ o.banner = "#{title} [options] ..."
15
+
16
+ o.bool '-v', '--verbose', 'enable verbose mode'
17
+ o.bool '-q', '--quiet', 'suppress output (quiet mode)'
18
+ o.bool '-h', '--help', 'Display this help'
19
+ o.bool '-f', '--force', "Force #{title} to act"
20
+ o.bool '-d', '--dry-run', 'Only show what files would be copied'
21
+
22
+ o.string '-c', '--config', 'Config file to use',
23
+ default: ConfigFile::DEFAULT_NAME
24
+
25
+ o.on '--version', 'print the version' do
26
+ puts VERSION
27
+ exit
28
+ end
29
+
30
+ o.separator "\n#{title} [options] init [directory]"
31
+ o.separator " Initializes a new #{ICON} project in the " \
32
+ "current directory."
33
+
34
+ o.bool '--no-git', 'Do not initialize a new git repository'
35
+
36
+ o.separator "\n#{title} [options] config [--global] KEY=VALUE"
37
+ o.separator " Change configuration options."
38
+ o.bool '-g', '--global', 'edit the global configuration'
39
+
40
+ o.separator "\n#{title} [options] clean"
41
+ o.separator " Remove all build files"
42
+
43
+ o.separator "\n#{title} [options] release TAG_LABEL"
44
+ o.separator " Build and tag the project"
45
+ end
46
+
47
+ if opts[:help]
48
+ puts opts
49
+ exit
50
+ end
51
+
52
+ command = opts.arguments[0]
53
+
54
+ Command.match command,
55
+ prompt,
56
+ { cmd: command,
57
+ args: opts.arguments[1..-1],
58
+ title: title,
59
+ **opts.to_hash }
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,11 @@
1
+ module Texico
2
+ module CLI
3
+ module Command
4
+ extend self
5
+
6
+ def match(*args)
7
+ Base.match(*args)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,58 @@
1
+ module Texico
2
+ module CLI
3
+ module Command
4
+ class Base
5
+ attr_reader :prompt, :opts
6
+
7
+ def initialize(prompt, opts)
8
+ @prompt = prompt
9
+ @opts = opts
10
+ end
11
+
12
+ def run
13
+ prompt.error "I don't know what you mean with '#{opts[:cmd]}'"
14
+ end
15
+
16
+ def load_config(full = true)
17
+ ConfigFile.load(opts, full).tap do |config|
18
+ unless config
19
+ prompt.say 'I Couldn\'t find a valid config file. Run ' + \
20
+ prompt.decorate('texico init', :bold, :yellow) + \
21
+ ' to setup a new project'
22
+ exit
23
+ end
24
+ end
25
+ end
26
+
27
+ protected
28
+
29
+ class << self
30
+ def match?(command)
31
+ true
32
+ end
33
+
34
+ def priority
35
+ 0
36
+ end
37
+
38
+ def inherited(klass)
39
+ (@commands ||= []) << { klass: klass, prio: klass.priority }
40
+ end
41
+
42
+ def select(command)
43
+ @commands&.sort_by { |e| -e[:prio] }
44
+ &.map! { |e| e[:klass] }
45
+ &.each { |k| sk = k.select command; return sk if sk }
46
+
47
+ match?(command) && self
48
+ end
49
+
50
+ def match(command, *args)
51
+ klass = select(command)
52
+ klass.new(*args) if klass
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,41 @@
1
+ module Texico
2
+ module CLI
3
+ module Command
4
+ class Build < Base
5
+ SHADOW_BUILD_DIR = '.build'.freeze
6
+
7
+ def run
8
+ config = load_config
9
+
10
+ prompt.say "#{ICON} Building project", color: :bold
11
+
12
+ build config
13
+ end
14
+
15
+ def build(config)
16
+ compiler = Compiler.new output_directory: SHADOW_BUILD_DIR
17
+ build_result = compiler.compile config[:main_file]
18
+
19
+ return false unless build_result
20
+ copy_build build_result[:file], config
21
+ true
22
+ end
23
+
24
+ private
25
+
26
+ def copy_build(build_file, config)
27
+ dest = File.expand_path(config[:name] + '.pdf', config[:build])
28
+
29
+ FileUtils.mkdir config[:build] unless File.exist? config[:build]
30
+ FileUtils.mv build_file, dest
31
+ end
32
+
33
+ class << self
34
+ def match?(command)
35
+ command == 'build' || command.nil?
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,34 @@
1
+ require 'fileutils'
2
+
3
+ module Texico
4
+ module CLI
5
+ module Command
6
+ class Clean < Base
7
+ def run
8
+ config = load_config
9
+ build_dir = config[:build]
10
+
11
+ if remove(build_dir) || remove(Build::SHADOW_BUILD_DIR)
12
+ prompt.say "#{ICON} Removing old build files", color: :bold
13
+ else
14
+ prompt.say "#{ICON} Everything is already clean", color: :bold
15
+ return
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def remove(dir)
22
+ return false unless File.exist? dir
23
+ FileUtils.rm_r dir unless opts[:dry_run]
24
+ end
25
+
26
+ class << self
27
+ def match?(command)
28
+ command == 'clean'
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,52 @@
1
+ require 'tty-table'
2
+
3
+ module Texico
4
+ module CLI
5
+ module Command
6
+ class Config < Base
7
+ def run
8
+ config =
9
+ if opts[:global]
10
+ ConfigFile.global
11
+ else
12
+ load_config false
13
+ end.to_hash
14
+
15
+ did_change = false
16
+ opts[:args].each do |key_value|
17
+ key, value = key_value.split '='
18
+ key = key.to_sym
19
+ did_change = did_change || config[key] != value
20
+ config[key] = value
21
+ end
22
+
23
+ if did_change
24
+ prompt.say "#{ICON} Writing new configuration\n", color: :bold
25
+ else
26
+ prompt.say "#{ICON} Current configuration\n", color: :bold
27
+ end
28
+
29
+ table = TTY::Table.new \
30
+ header: %w(Option Value).map { |v| prompt.decorate v, :bold },
31
+ rows: config.to_a
32
+
33
+ prompt.say table.render(:basic)
34
+
35
+ return unless did_change
36
+
37
+ if opts[:global]
38
+ ConfigFile.store(config, opts, ConfigFile::GLOBAL_CONFIG_PATH)
39
+ else
40
+ ConfigFile.store(config, opts)
41
+ end
42
+ end
43
+
44
+ class << self
45
+ def match?(command)
46
+ command == 'config'
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,110 @@
1
+ require 'tty-tree'
2
+
3
+ module Texico
4
+ module CLI
5
+ module Command
6
+ class Init < Base
7
+ def run
8
+ # Show welcome text
9
+ welcome
10
+ # As for configuration options for this session
11
+ config = ask_config
12
+ # Indicate that the config was accepted
13
+ prompt.say "#{ICON} Creating new project\n", color: :bold
14
+ # Copy the template project
15
+ copy_template config
16
+ # Save the other config options to file
17
+ ConfigFile.store config, target, opts
18
+
19
+ Git.init(target, true) unless opts[:no_git]
20
+
21
+ # We are done
22
+ prompt.say "#{ICON} Done!", color: :bold
23
+ rescue TTY::Reader::InputInterrupt
24
+ prompt.error 'Aborting'
25
+ exit
26
+ end
27
+
28
+ private
29
+
30
+ def target
31
+ File.expand_path('', opts[:args][0] || '.')
32
+ end
33
+
34
+ def welcome
35
+ if ConfigFile.exist?(opts)
36
+ if opts[:force]
37
+ prompt.warn "#{ICON} Reinitializeing existing project."
38
+ else
39
+ prompt.say "#{ICON} Hey! This project has already been setup " \
40
+ "with #{opts[:title]}!", color: :bold
41
+ prompt.say ' Use -f to force me to reinitialize it.'
42
+ exit
43
+ end
44
+ else
45
+ prompt.say "#{ICON} I just need a few details", color: :bold
46
+ end
47
+ prompt.say "\n"
48
+ end
49
+
50
+ def ask_config
51
+ folder_name = File.basename target
52
+ template_choices =
53
+ Hash[Template.list.map { |p| [File.basename(p).capitalize, p] }]
54
+
55
+ answers =
56
+ prompt.collect do
57
+ key(:name).ask( 'What should be the name of the output PDF?',
58
+ default: folder_name.downcase.gsub(' ', '-'))
59
+ key(:title).ask( 'What is the title of your document?',
60
+ default: folder_name.gsub('_', ' ').capitalize)
61
+ key(:author).ask('What is your name?',
62
+ default: ConfigFile.default[:author])
63
+ key(:email).ask( 'What is your email address?',
64
+ default: ConfigFile.default[:email])
65
+ key(:template).select("Select a template", template_choices)
66
+ end
67
+
68
+ ConfigFile.new answers, ConfigFile::DEFAULT_CONFIG
69
+ end
70
+
71
+ def copy_template(config)
72
+ params = config.to_hash
73
+ template_path = params.delete :template
74
+ template = Template.load template_path
75
+
76
+ template_structure =
77
+ template.copy(target, params, opts) do |status|
78
+ file = status.file.basename
79
+ case status.status
80
+ when :successful then prompt.decorate(file, :green)
81
+ when :target_exist then prompt.decorate(file, :red)
82
+ when :replaced_target then prompt.decorate(file, :yellow)
83
+ when :template_error then prompt.decorate(file, :blue)
84
+ end
85
+ end
86
+
87
+ tree = TTY::Tree.new template_structure
88
+ prompt.say tree.render + "\n"
89
+ file_copy_legend
90
+ end
91
+
92
+ def file_copy_legend
93
+ prompt.say \
94
+ format("%s Did copy %s Replaced existing %s File existed %s Template Error\n\n",
95
+ prompt.decorate("∎", :green),
96
+ prompt.decorate("∎", :yellow),
97
+ prompt.decorate("∎", :red),
98
+ prompt.decorate("∎", :blue)
99
+ )
100
+ end
101
+
102
+ class << self
103
+ def match?(command)
104
+ command == 'init'
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,56 @@
1
+ module Texico
2
+ module CLI
3
+ module Command
4
+ class Release < Build
5
+ GIT_DIR = File.expand_path('.git').freeze
6
+
7
+ def run
8
+ unless File.exist? GIT_DIR
9
+ prompt.error "#{ICON} You don't seem to be using git."
10
+ exit
11
+ end
12
+
13
+ unless label
14
+ tags = Git.list_tags('.')
15
+ num_tags = tags.length
16
+ count = case num_tags
17
+ when 0 then 'no releases'
18
+ when 1 then 'one release'
19
+ else "#{num_tags} releases"
20
+ end
21
+ prompt.say "#{ICON} This project currently has #{count}\n",
22
+ color: :bold
23
+
24
+ if num_tags > 0
25
+ prompt.say tags.map { |t| "* #{t}" }.join("\n")
26
+ end
27
+
28
+ exit
29
+ end
30
+
31
+ success = super # Build the project
32
+
33
+ unless success
34
+ prompt.error "#{ICON} I will only tag the release when it builds " \
35
+ "without errors."
36
+ exit
37
+ end
38
+
39
+ Git.tag '.', label, "Releasing #{label}"
40
+ end
41
+
42
+ private
43
+
44
+ def label
45
+ opts[:args][0]
46
+ end
47
+
48
+ class << self
49
+ def match?(command)
50
+ command == 'release'
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,60 @@
1
+ require 'open3'
2
+
3
+ module Texico
4
+ class Compiler
5
+ COMMAND = 'latexmk'
6
+
7
+ LATEXMK_OPTIONS = {
8
+ pdf: true,
9
+ output_directory: '.build',
10
+ latexoption: {
11
+ interaction: 'nonstopmode',
12
+ file_line_error: true
13
+ }.freeze
14
+ }.freeze
15
+
16
+ OUTPUT_PATTERN =
17
+ %r{\AOutput written on ([^\s]+) \((\d+) page, (\d+) bytes\)}
18
+
19
+ def initialize(**options)
20
+ @args = LATEXMK_OPTIONS
21
+ .merge(options)
22
+ .map { |k, v| transform_option(k, v) }.join ' '
23
+ end
24
+
25
+ def compile(file)
26
+ # TODO: This looks very hacky...
27
+ build_result = false
28
+ Open3.popen2("#{COMMAND} #@args #{file}") do |_, stdout, _|
29
+ stdout.each_line do |line|
30
+ if m = line.match(OUTPUT_PATTERN)
31
+ build_result = { file: m[1], pages: m[2], bytes: m[3] }
32
+ break
33
+ end
34
+ end
35
+ end
36
+ build_result
37
+ end
38
+
39
+ # Takes a symbol (or string) one the form :some_option and transforms it
40
+ # into the string "-some-option". If the value is
41
+ # a) false, the output string will be empty.
42
+ # b) true, only the key will be returned.
43
+ # c) some other value, an array with the transformed key and the value is
44
+ # returned.
45
+ private def transform_option(key, value)
46
+ # Skip the option if the value is nil or false
47
+ return '' unless value
48
+ # Transform the key into a format that is accepted by pdflatex
49
+ option = '-' + key.to_s.gsub('_', '-')
50
+
51
+ case value
52
+ when TrueClass then return option
53
+ when Hash
54
+ value.map { |k, v| "#{option}=#{transform_option k, v }" }.join ' '
55
+ else
56
+ "#{option}=#{value}"
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,73 @@
1
+ require 'yaml'
2
+ require 'forwardable'
3
+
4
+ module Texico
5
+ class ConfigFile
6
+ extend Forwardable
7
+
8
+ DEFAULT_NAME = '.texico'.freeze
9
+ GLOBAL_CONFIG_PATH = File.expand_path(DEFAULT_NAME, ENV['HOME']).freeze
10
+ DEFAULT_CONFIG = {
11
+ name: 'main',
12
+ title: 'Title',
13
+ author: 'Author',
14
+ email: 'author@example.com',
15
+ build: 'build',
16
+ main_file: 'main.tex'
17
+ }.freeze
18
+
19
+ def_delegator :@config, :[]
20
+ def_delegator :@config, :to_a
21
+
22
+ def to_hash
23
+ @config.dup
24
+ end
25
+
26
+ private
27
+
28
+ def initialize(config, defaults = {})
29
+ @config = defaults.merge(config).freeze
30
+ end
31
+
32
+ class << self
33
+ def exist?(opts)
34
+ File.exist? opts[:config]
35
+ end
36
+
37
+ def global
38
+ new read_global
39
+ end
40
+
41
+ def default
42
+ return @default if @default
43
+ @default = DEFAULT_CONFIG.merge read_global
44
+ end
45
+
46
+ def load(opts, full = true)
47
+ return false unless File.exist? opts[:config]
48
+ new read_local(opts[:config]), (full ? default : {})
49
+ end
50
+
51
+ def store(config, dest = '', opts = {})
52
+ return if opts[:dry_run]
53
+ dest_path = File.expand_path opts[:config], dest
54
+ File.open dest_path, 'wb' do |file|
55
+ file.write YAML.dump(config.to_hash)
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def read_local(filename)
62
+ yaml = File.open(filename, 'rb') { |f| f.read }
63
+ YAML.load(yaml) || {}
64
+ rescue Errno::ENOENT
65
+ {}
66
+ end
67
+
68
+ def read_global
69
+ @global_defaults ||= read_local GLOBAL_CONFIG_PATH
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,27 @@
1
+ require 'open3'
2
+
3
+ module Texico
4
+ module Git
5
+ module_function
6
+ def init(target, initial_commit = false)
7
+ if initial_commit
8
+ system "git init '#{target}' && git -C '#{target}' add . " \
9
+ "&& git -C '#{target}' commit -m 'Initial commit'"
10
+ else
11
+ system "git init '#{target}'"
12
+ end
13
+ end
14
+
15
+ module_function
16
+ def tag(target, label, message)
17
+ system "git -C '#{target}' tag -a #{label} -m '#{message}'"
18
+ end
19
+
20
+ module_function
21
+ def list_tags(target)
22
+ Open3.popen2 "git -C '#{target}' tag -l" do |_, stdout, _|
23
+ stdout.each_line.map { |line| line.chomp }
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,91 @@
1
+ require 'erb'
2
+ require 'fileutils'
3
+
4
+ module Texico
5
+ # Template
6
+ #
7
+ # Class for handling Texico templates. A template is really just a folder with
8
+ # some files in it. The template object handles moving those files, as well as
9
+ # rendering any .erb files.
10
+ class Template
11
+ BASE_PATH = ::File.expand_path('../../../templates', __FILE__)
12
+
13
+ attr_reader :name
14
+
15
+ # Tree
16
+ #
17
+ # Returns the template structure in a format compatible with tty-tree.
18
+ def tree
19
+ { name => self.class.map_tree(@file_tree, &:to_s) }
20
+ end
21
+
22
+ # Returns a report of what files where copied.
23
+ def copy(dest, params, opts, &block)
24
+ map_status = block_given? ? block : ->(status) { status.to_s }
25
+ status_tree =
26
+ self.class.map_tree(@file_tree) do |file|
27
+ map_status.call(file.copy(params, dest, opts))
28
+ end
29
+ { name => status_tree }
30
+ end
31
+
32
+ private
33
+
34
+ def initialize(name, file_tree)
35
+ @name = name.freeze
36
+ @file_tree = file_tree
37
+
38
+ freeze
39
+ end
40
+
41
+ class << self
42
+ # List
43
+ #
44
+ # List available templates
45
+ def list
46
+ Dir.glob "#{BASE_PATH}/*"
47
+ end
48
+
49
+ def exist?(_)
50
+ raise RuntimeError
51
+ #::File.exist? template
52
+ end
53
+
54
+ def load_file_tree(root, current_dir = '')
55
+ base_path = ::File.expand_path current_dir, root
56
+ Dir.entries(base_path)
57
+ .reject { |entry| ['.', '..'].include? entry }
58
+ .map do |entry|
59
+ local_path = (current_dir + entry).freeze
60
+ full_path = ::File.expand_path local_path, root
61
+
62
+ if ::File.file?(full_path)
63
+ File.new local_path, root
64
+ else
65
+ { entry.freeze => load_file_tree(root, local_path + '/') }.freeze
66
+ end
67
+ end.freeze
68
+ end
69
+
70
+ def map_tree(tree, root = '', &block)
71
+ tree.map do |node|
72
+ if node.is_a? Hash
73
+ dir = node.keys[0]
74
+ { dir => map_tree(node[dir], "#{root}#{dir}/", &block) }
75
+ else
76
+ yield node
77
+ end
78
+ end
79
+ end
80
+
81
+ def load(template)
82
+ file_tree = load_file_tree template
83
+ template_name = ::File.basename(template).capitalize
84
+
85
+ new template_name, file_tree
86
+ rescue Errno::ENOENT
87
+ false
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,106 @@
1
+ require 'fileutils'
2
+
3
+ module Texico
4
+ class Template
5
+ # File
6
+ #
7
+ # The Template File object is an internal object used by the Template class
8
+ # to copy and render template files transparently. It should never be used
9
+ # directly.
10
+ class File
11
+ TEMPLATE_EXTNAME = '.erb'.freeze
12
+
13
+ def initialize(relative_path, base_path)
14
+ @relative_path = relative_path.freeze
15
+ @base_path = base_path.freeze
16
+
17
+ freeze
18
+ end
19
+
20
+ # Basename
21
+ #
22
+ # Returns the name of the file. In case of template files the .erb
23
+ # extension is removed.
24
+ def basename
25
+ ::File.basename @relative_path, TEMPLATE_EXTNAME
26
+ end
27
+
28
+ # Basename
29
+ #
30
+ # Returns the extension of the file. In case of template files the
31
+ # extension of the target file is returned.
32
+ def extname
33
+ ::File.extname basename
34
+ end
35
+
36
+ # Dirname
37
+ #
38
+ # Returns the local directory path of the file.
39
+ def dirname
40
+ ::File.dirname @relative_path
41
+ end
42
+
43
+ # Returns the filename
44
+ def to_s
45
+ basename
46
+ end
47
+
48
+ # Copy
49
+ #
50
+ # Copy the file with its relative path intact to the dest_base_path root.
51
+ #
52
+ # Returns a FileStatus object.
53
+ def copy(params, dest_base_path = '.', opts = {})
54
+ dest_dir = ::File.expand_path dirname, dest_base_path
55
+ dest_path = ::File.expand_path basename, dest_dir
56
+ force = false
57
+
58
+ if ::File.exist? dest_path
59
+ return FileStatus.new(self, :target_exist) unless opts[:force]
60
+ force = true
61
+ else
62
+ FileUtils.mkdir_p dest_dir unless opts[:dry_run]
63
+ end
64
+
65
+ if template?
66
+ err = copy_template src_path, dest_path, params,
67
+ noop: opts[:dry_run], verbose: opts[:verbose]
68
+ return err if err
69
+ else
70
+ FileUtils.cp src_path, dest_path,
71
+ noop: opts[:dry_run], verbose: opts[:verbose]
72
+ end
73
+
74
+ FileStatus.new(self, force ? :replaced_target : :successful)
75
+ end
76
+
77
+ private
78
+
79
+ def template?
80
+ ::File.extname(@relative_path) == TEMPLATE_EXTNAME
81
+ end
82
+
83
+ def src_path
84
+ ::File.expand_path @relative_path, @base_path
85
+ end
86
+
87
+ # Copy Template
88
+ #
89
+ # Returns a status object unless the operation was successful, in which
90
+ # case nil is returned.
91
+ def copy_template(src, dest, params, noop: nil, verbose: nil)
92
+ file_body =
93
+ ::File.open src, 'rb' do |file|
94
+ ERB.new(file.read, 0).result_with_hash params
95
+ end
96
+
97
+ return if noop
98
+
99
+ ::File.open(dest, 'wb') { |file| file.write file_body }
100
+ nil
101
+ rescue NameError => e
102
+ FileStatus.new self, e
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,32 @@
1
+ module Texico
2
+ class Template
3
+ # FileStatus
4
+ #
5
+ # The FileStatus object represents the result of a copy command on a
6
+ # template file. This class should never be used directly.
7
+ class FileStatus
8
+ STATUS = %i[successful target_exist replaced_target].freeze
9
+
10
+ attr_reader :file
11
+
12
+ def initialize(file, status = STATUS[0])
13
+ unless STATUS.include?(status) || status.is_a?(Exception)
14
+ raise ArgumentError, 'Unknown status'
15
+ end
16
+
17
+ @status = status
18
+ @file = file
19
+
20
+ freeze
21
+ end
22
+
23
+ def to_s
24
+ "#{file.basename} [#{status}]"
25
+ end
26
+
27
+ def status
28
+ @status.is_a?(Exception) ? :template_error : @status
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,3 @@
1
+ module Texico
2
+ VERSION = '0.2.0'.freeze
3
+ end
@@ -0,0 +1,2 @@
1
+ .build/
2
+ <%= build %>/
File without changes
@@ -0,0 +1,93 @@
1
+ \documentclass[a4paper,11pt]{article}
2
+
3
+ \usepackage[utf8]{inputenc}
4
+ \usepackage[english]{babel}
5
+ \usepackage{amsmath,amssymb,amsthm,amsfonts}
6
+ \usepackage[demo]{graphicx} % REMOVE "demo" to include figures!
7
+ \usepackage{hyperref}
8
+ \usepackage{cleveref}
9
+ \usepackage{autonum}
10
+ \usepackage[inline]{enumitem}
11
+
12
+ %
13
+ % ---- Global Document Properties ----
14
+ %
15
+
16
+ \title{<%= title %>}
17
+ \date{\today}
18
+ \author{
19
+ <%= author %>\\
20
+ <%= email %>
21
+ }
22
+
23
+ \topmargin=-0.45in
24
+ \evensidemargin=0in
25
+ \oddsidemargin=0in
26
+ \textwidth=6.3in
27
+ \textheight=9.6in
28
+ \headsep=0.25in
29
+
30
+ % Turn off numbering for sections
31
+ \setcounter{secnumdepth}{0}
32
+
33
+ \graphicspath{{./assets/}}
34
+
35
+
36
+ %
37
+ % ---- Enumeration Styles ----
38
+ %
39
+
40
+ % Set default style to a), b) ...
41
+ %\setlist[enumerate]{label=\alph*)}
42
+ % Use \begin{enumerate*} ... for inline lists
43
+
44
+
45
+ %
46
+ % ---- Custom Commands ----
47
+ %
48
+
49
+ % Usage: \image{file_name}{caption}
50
+ % \image[width=8cm]{file_name}{caption}
51
+ % Reference: \cref{fig:file_name}
52
+ \newcommand{\image}[3][width=1.0\columnwidth]{
53
+ \begin{figure}[h!]
54
+ \centering
55
+ \includegraphics[#1]{#2}
56
+ \caption{#3}
57
+ \label{fig:#2}
58
+ \end{figure}
59
+ }
60
+
61
+
62
+ %
63
+ % ---- Document ----
64
+ % Exported to build/<%= name %>.pdf
65
+
66
+ \begin{document}
67
+ \maketitle
68
+
69
+ \section{Introduction}
70
+
71
+
72
+
73
+
74
+ %
75
+ % ---- Bibliography ----
76
+ %
77
+
78
+ % Usage: \begin{thebibliography}{[max_number_citations]}
79
+ %
80
+ % Usage: \bibitem{author:year}
81
+ % Author, F., Authod, S.:
82
+ % Nonlinear oscillations and
83
+ % boundary-value problems for Hamiltonian systems.
84
+ % Arch. Rat. Mech. Anal. 78, 315--333 (1982)
85
+ % Cite: \cite{author:year}
86
+
87
+ % \begin{thebibliography}{5}
88
+ % \bibitem{gold:2003}
89
+ % Gold, E.
90
+ % TechRepublic Tutorial: How to diagnose a faulty power supply
91
+ % \url{https://tex.stackexchange.com/questions/3587/how-can-i-use-bibtex-to-cite-a-web-page#3608} (2003)
92
+ % \end{thebibliography}
93
+ \end{document}
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'texico/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "texico"
8
+ spec.version = Texico::VERSION
9
+ spec.authors = ["Sebastian Lindberg"]
10
+ spec.email = ["seb.lindberg@gmail.com"]
11
+
12
+ spec.summary = "Command line utility for managing Latex projects."
13
+ spec.description = "Utility for handling project templates, build " \
14
+ "settings and project management"
15
+ spec.homepage = "https://github.com/seblindberg/texico"
16
+ spec.license = "MIT"
17
+
18
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
19
+ spec.bindir = "exe"
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.add_dependency "slop", "~> 4.6"
24
+ spec.add_dependency "tty-prompt", "~> 0.15"
25
+ spec.add_dependency "tty-tree", "~> 0.1"
26
+ spec.add_dependency "tty-table", "~> 0.10"
27
+
28
+ spec.add_development_dependency "bundler", "~> 1.12"
29
+ spec.add_development_dependency "rake", "~> 10.0"
30
+ spec.add_development_dependency "minitest", "~> 5.0"
31
+ end
metadata ADDED
@@ -0,0 +1,173 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: texico
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Sebastian Lindberg
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-03-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: slop
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.6'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: tty-prompt
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.15'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.15'
41
+ - !ruby/object:Gem::Dependency
42
+ name: tty-tree
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.1'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: tty-table
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.10'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.10'
69
+ - !ruby/object:Gem::Dependency
70
+ name: bundler
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.12'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.12'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '10.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '10.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: minitest
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '5.0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '5.0'
111
+ description: Utility for handling project templates, build settings and project management
112
+ email:
113
+ - seb.lindberg@gmail.com
114
+ executables:
115
+ - texico
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - ".gitignore"
120
+ - ".travis.yml"
121
+ - Gemfile
122
+ - LICENSE.txt
123
+ - README.md
124
+ - Rakefile
125
+ - bin/console
126
+ - bin/setup
127
+ - exe/texico
128
+ - lib/texico.rb
129
+ - lib/texico/cli.rb
130
+ - lib/texico/cli/arg_parser.rb
131
+ - lib/texico/cli/command.rb
132
+ - lib/texico/cli/command/base.rb
133
+ - lib/texico/cli/command/build.rb
134
+ - lib/texico/cli/command/clean.rb
135
+ - lib/texico/cli/command/config.rb
136
+ - lib/texico/cli/command/init.rb
137
+ - lib/texico/cli/command/release.rb
138
+ - lib/texico/compiler.rb
139
+ - lib/texico/config_file.rb
140
+ - lib/texico/git.rb
141
+ - lib/texico/template.rb
142
+ - lib/texico/template/file.rb
143
+ - lib/texico/template/file_status.rb
144
+ - lib/texico/version.rb
145
+ - templates/basic/.gitignore.erb
146
+ - templates/basic/assets/test.txt
147
+ - templates/basic/main.tex.erb
148
+ - texico.gemspec
149
+ homepage: https://github.com/seblindberg/texico
150
+ licenses:
151
+ - MIT
152
+ metadata: {}
153
+ post_install_message:
154
+ rdoc_options: []
155
+ require_paths:
156
+ - lib
157
+ required_ruby_version: !ruby/object:Gem::Requirement
158
+ requirements:
159
+ - - ">="
160
+ - !ruby/object:Gem::Version
161
+ version: '0'
162
+ required_rubygems_version: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ requirements: []
168
+ rubyforge_project:
169
+ rubygems_version: 2.7.3
170
+ signing_key:
171
+ specification_version: 4
172
+ summary: Command line utility for managing Latex projects.
173
+ test_files: []