texico 0.2.0

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.
@@ -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: []