├── .dockerignore ├── .gitignore ├── .rspec ├── .travis.yml ├── Dockerfile ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── app.rb ├── config.rb ├── docker-compose.yml ├── heroku.yml ├── lib ├── cv_maker.rb ├── txt2yaml.rb └── util.rb ├── make_cv.rb ├── makefile ├── photo.png ├── public ├── .gitkeep ├── script.js └── style.css ├── sample ├── README_origin.md └── screen_pc.png ├── share └── .gitkeep ├── spec ├── app_spec.rb ├── cv_maker_spec.rb ├── spec_helper.rb └── txt2yaml_spec.rb ├── templates ├── academic.txt ├── academic.yaml ├── data.yaml └── style.txt ├── views ├── error.erb ├── index.erb ├── layout.erb └── partial │ ├── cdn.erb │ ├── footer.erb │ ├── header.erb │ └── seo.erb └── yaml2txt.rb /.dockerignore: -------------------------------------------------------------------------------- 1 | # git 2 | .git 3 | .gitignore 4 | LICENSE 5 | VERSION 6 | README.md 7 | Changelog.md 8 | # Makefile 9 | Dockerfile 10 | docker-compose.yml 11 | docs 12 | 13 | # production 14 | fonts 15 | *.pdf 16 | sample 17 | share 18 | 19 | # Created by https://www.gitignore.io/api/ruby 20 | # Edit at https://www.gitignore.io/?templates=ruby 21 | 22 | ### Ruby ### 23 | *.gem 24 | *.rbc 25 | /.config 26 | /coverage/ 27 | /InstalledFiles 28 | /pkg/ 29 | /spec/reports/ 30 | /spec/examples.txt 31 | /test/tmp/ 32 | /test/version_tmp/ 33 | /tmp/ 34 | 35 | # Used by dotenv library to load environment variables. 36 | # .env 37 | 38 | # Ignore Byebug command history file. 39 | .byebug_history 40 | 41 | ## Specific to RubyMotion: 42 | .dat* 43 | .repl_history 44 | build/ 45 | *.bridgesupport 46 | build-iPhoneOS/ 47 | build-iPhoneSimulator/ 48 | 49 | ## Specific to RubyMotion (use of CocoaPods): 50 | # 51 | # We recommend against adding the Pods directory to your .gitignore. However 52 | # you should judge for yourself, the pros and cons are mentioned at: 53 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 54 | # vendor/Pods/ 55 | 56 | ## Documentation cache and generated files: 57 | /.yardoc/ 58 | /_yardoc/ 59 | /doc/ 60 | /rdoc/ 61 | 62 | ## Environment normalization: 63 | /.bundle/ 64 | /vendor/bundle 65 | /lib/bundler/man/ 66 | 67 | # for a library or gem, you might want to ignore these files since the code is 68 | # intended to run in multiple environments; otherwise, check them in: 69 | # Gemfile.lock 70 | .ruby-version 71 | # .ruby-gemset 72 | 73 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 74 | .rvmrc 75 | 76 | # End of https://www.gitignore.io/api/ruby 77 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | fonts 2 | *.pdf 3 | share/* 4 | !share/.gitkeep 5 | 6 | # Created by https://www.gitignore.io/api/ruby 7 | # Edit at https://www.gitignore.io/?templates=ruby 8 | 9 | ### Ruby ### 10 | *.gem 11 | *.rbc 12 | /.config 13 | /coverage/ 14 | /InstalledFiles 15 | /pkg/ 16 | /spec/reports/ 17 | /spec/examples.txt 18 | /test/tmp/ 19 | /test/version_tmp/ 20 | /tmp/ 21 | 22 | # Used by dotenv library to load environment variables. 23 | # .env 24 | 25 | # Ignore Byebug command history file. 26 | .byebug_history 27 | 28 | ## Specific to RubyMotion: 29 | .dat* 30 | .repl_history 31 | build/ 32 | *.bridgesupport 33 | build-iPhoneOS/ 34 | build-iPhoneSimulator/ 35 | 36 | ## Specific to RubyMotion (use of CocoaPods): 37 | # 38 | # We recommend against adding the Pods directory to your .gitignore. However 39 | # you should judge for yourself, the pros and cons are mentioned at: 40 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 41 | # vendor/Pods/ 42 | 43 | ## Documentation cache and generated files: 44 | /.yardoc/ 45 | /_yardoc/ 46 | /doc/ 47 | /rdoc/ 48 | 49 | ## Environment normalization: 50 | /.bundle/ 51 | /vendor/bundle 52 | /lib/bundler/man/ 53 | 54 | # for a library or gem, you might want to ignore these files since the code is 55 | # intended to run in multiple environments; otherwise, check them in: 56 | # Gemfile.lock 57 | .ruby-version 58 | # .ruby-gemset 59 | 60 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 61 | .rvmrc 62 | 63 | # End of https://www.gitignore.io/api/ruby 64 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | --format documentation 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.7.2 4 | before_install: 5 | - gem install bundler -v '> 2' 6 | before_script: 7 | - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter 8 | - chmod +x ./cc-test-reporter 9 | - ./cc-test-reporter before-build 10 | - curl https://moji.or.jp/wp-content/ipafont/IPAexfont/IPAexfont00401.zip > fonts.zip 11 | - unzip -oj fonts.zip -d fonts/ && rm -rf fonts.zip 12 | script: 13 | - bundle exec rspec 14 | after_script: 15 | - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT 16 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:2-alpine 2 | 3 | # Setup 4 | RUN set -x && apk update && apk upgrade && apk add --no-cache \ 5 | gnupg gcc g++ make unzip curl openssh-client git bash imagemagick 6 | RUN gem install bundler 7 | 8 | WORKDIR /usr/src/ 9 | 10 | # RUN git clone https://github.com/jerrywdlee/yaml_2_resume.git app 11 | ADD ./ /usr/src/app/ 12 | 13 | WORKDIR /usr/src/app 14 | 15 | RUN bundle install 16 | RUN curl https://moji.or.jp/wp-content/ipafont/IPAexfont/IPAexfont00401.zip > fonts.zip && \ 17 | unzip -oj fonts.zip -d fonts/ && rm -rf fonts.zip 18 | 19 | EXPOSE 4567 20 | CMD ["bundle", "exec", "ruby", "app.rb", "-o", "0.0.0.0", "-p", "4567"] 21 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } 6 | 7 | gem 'prawn' 8 | gem 'rake' 9 | gem 'wareki' 10 | gem 'sinatra' 11 | gem 'sinatra-contrib', group: :development 12 | gem 'mini_magick' 13 | 14 | group :development, :test do 15 | gem 'rspec' 16 | gem 'simplecov' 17 | gem 'rack-test' 18 | end 19 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | backports (3.15.0) 5 | diff-lcs (1.3) 6 | docile (1.3.1) 7 | json (2.2.0) 8 | mini_magick (4.9.3) 9 | multi_json (1.13.1) 10 | mustermann (1.0.3) 11 | pdf-core (0.7.0) 12 | prawn (2.2.2) 13 | pdf-core (~> 0.7.0) 14 | ttfunk (~> 1.5) 15 | rack (2.0.7) 16 | rack-protection (2.0.5) 17 | rack 18 | rack-test (1.1.0) 19 | rack (>= 1.0, < 3) 20 | rake (12.3.2) 21 | rspec (3.8.0) 22 | rspec-core (~> 3.8.0) 23 | rspec-expectations (~> 3.8.0) 24 | rspec-mocks (~> 3.8.0) 25 | rspec-core (3.8.0) 26 | rspec-support (~> 3.8.0) 27 | rspec-expectations (3.8.3) 28 | diff-lcs (>= 1.2.0, < 2.0) 29 | rspec-support (~> 3.8.0) 30 | rspec-mocks (3.8.0) 31 | diff-lcs (>= 1.2.0, < 2.0) 32 | rspec-support (~> 3.8.0) 33 | rspec-support (3.8.0) 34 | simplecov (0.16.1) 35 | docile (~> 1.1) 36 | json (>= 1.8, < 3) 37 | simplecov-html (~> 0.10.0) 38 | simplecov-html (0.10.2) 39 | sinatra (2.0.5) 40 | mustermann (~> 1.0) 41 | rack (~> 2.0) 42 | rack-protection (= 2.0.5) 43 | tilt (~> 2.0) 44 | sinatra-contrib (2.0.5) 45 | backports (>= 2.8.2) 46 | multi_json 47 | mustermann (~> 1.0) 48 | rack-protection (= 2.0.5) 49 | sinatra (= 2.0.5) 50 | tilt (>= 1.3, < 3) 51 | tilt (2.0.9) 52 | ttfunk (1.5.1) 53 | wareki (1.0.0) 54 | ya_kansuji (> 0.0.9, < 2.0.0) 55 | ya_kansuji (0.2.0) 56 | 57 | PLATFORMS 58 | ruby 59 | 60 | DEPENDENCIES 61 | mini_magick 62 | prawn 63 | rack-test 64 | rake 65 | rspec 66 | simplecov 67 | sinatra 68 | sinatra-contrib 69 | wareki 70 | 71 | BUNDLED WITH 72 | 2.0.1 73 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 J. C. Augusta 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 all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | YAMLによる履歴書作成 2 | === 3 | 4 | [![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE) 5 | [![Ruby](https://img.shields.io/badge/ruby-%3E%3D2.3-red.svg)](Ruby) 6 | [![Build Status](https://travis-ci.com/jerrywdlee/yaml_2_resume.svg?branch=master)](https://travis-ci.com/jerrywdlee/yaml_2_resume) 7 | [![Test Coverage](https://api.codeclimate.com/v1/badges/82051a8ba32117145b21/test_coverage)](https://codeclimate.com/github/jerrywdlee/yaml_2_resume/test_coverage) 8 | [![Maintainability](https://api.codeclimate.com/v1/badges/82051a8ba32117145b21/maintainability)](https://codeclimate.com/github/jerrywdlee/yaml_2_resume/maintainability) 9 | 10 | [kaityo256氏が開発した `yaml_cv`](https://github.com/kaityo256/yaml_cv)のマイクロサービス版です。 11 | YAML形式で書かれたデータファイルと、 12 | YAMLもしくはテキストファイル形式で書かれた[スタイル](https://qiita.com/kaityo256/items/e3884d0109223c324baf) 13 | から履歴書PDFファイルを作成します。 14 | [Qiita記事はこちら](https://qiita.com/jerrywdlee/items/d0c36549136211937473) 15 | 16 | [![dockeri.co](https://dockeri.co/image/jerrywdlee/yaml_2_resume)](https://hub.docker.com/r/jerrywdlee/yaml_2_resume) 17 | 18 | # Application 19 | **DEMO: https://yaml-2-resume.herokuapp.com/** 20 | 21 | ![sample/photo.png](sample/screen_pc.png) 22 | 23 | # インストール&使用 24 | ## ローカルインストール 25 | ### 必要なライブラリ等 26 | * Ruby >= v2.3 27 | * bundler >= 2.0 28 | * [ImageMagick](https://imagemagick.org/index.php) 29 | * [IAPexフォント](https://moji.or.jp/ipafont/) 30 | 31 | ### MacOS 32 | #### 依存パケージのインストール 33 | ```sh 34 | $ brew install imagemagick 35 | $ gem install bundler 36 | $ bundle install 37 | ``` 38 | 39 | #### フォントのダウンロード、バージョンは適宜に替えていいです 40 | ```sh 41 | $ curl https://moji.or.jp/wp-content/ipafont/IPAexfont/IPAexfont00401.zip > fonts.zip 42 | $ unzip -oj fonts.zip -d fonts/ && rm -rf fonts.zip 43 | ``` 44 | 45 | 上記コマンドを使わなくても、[ここ](https://moji.or.jp/ipafont/)よりフォントを 46 | ダウンロードして、下記の配置になるよう解凍すればいい。 47 | 48 | ``` 49 | ├── fonts 50 | │   ├── ipaexg.ttf 51 | │   └── ipaexm.ttf 52 | └── make_cv.rb 53 | ``` 54 | 55 | #### アプリの起動 56 | ##### webアプリとして 57 | 58 | ```sh 59 | $ bundle exec ruby app.rb 60 | $ open http://localhost:4567 61 | ``` 62 | 63 | ##### また、ローカルでは[kaityo256/yaml_cv](https://github.com/kaityo256/yaml_cv)が提供したコマンドも実行できる。 64 | 65 | ```sh 66 | $ ruby make_cv.rb -h 67 | Usage: make_cv [options] 68 | -i, --input [datafile] 69 | -s, --style [stylefile] 70 | -o, --output [output] 71 | -v, --version 72 | ``` 73 | 74 | ```sh 75 | ruby make_cv.rb -i templates/data.yaml -s templates/style.txt -o output.pdf 76 | ``` 77 | 78 | ##### テストの実行 79 | ```sh 80 | $ bundle exec rspec 81 | ``` 82 | 83 | ## Dockerを使う 84 | ### 純Docker(Webアプリとして) 85 | #### ローカルでビルドする 86 | 87 | ```sh 88 | $ docker build -t my_yaml_2_resume . 89 | $ docker run --rm -p 14567:4567 my_yaml_2_resume 90 | $ open http://localhost:14567 91 | ``` 92 | 93 | #### [Docker Hub](https://cloud.docker.com/repository/docker/jerrywdlee/yaml_2_resume)を利用する 94 | 95 | ```sh 96 | $ docker pull jerrywdlee/yaml_2_resume:latest 97 | $ docker run --rm -p 14567:4567 jerrywdlee/yaml_2_resume 98 | $ open http://localhost:14567 99 | ``` 100 | 101 | ## Docker Composeを使う 102 | ### Webアプリ 103 | ```sh 104 | $ docker-compose build 105 | $ docker-compose up 106 | $ open http://localhost:14567 107 | ``` 108 | 109 | ### CMDモード 110 | `share/`の配下にご自分のデータを置いて、`docker-compose.yml`の`command`フィールドを修正して使う。もちろん、`templates/`配下のサンプルデータも使える。 111 | 112 | ```diff 113 | version: '3.5' 114 | services: 115 | yaml_2_resume: 116 | container_name: yaml_2_resume 117 | build: . 118 | ports: 119 | - 14567:4567 120 | working_dir: /usr/src/app 121 | volumes: 122 | - ./share:/usr/src/app/share 123 | # web app mode 124 | - command: ruby app.rb -o 0.0.0.0 125 | # cmd mode 126 | + command: ruby make_cv.rb -i share/YOUR_DATA.yaml -s share/YOUR_STYLE.txt -o share/output.pdf 127 | ``` 128 | 129 | ```sh 130 | $ docker-compose build 131 | $ docker-compose up 132 | $ open ./share/output.pdf 133 | ``` 134 | 135 | ## HerokuでDeploy 136 | 137 | ```sh 138 | $ heroku create YOUR-APP-NAME 139 | $ heroku stack:set container 140 | $ git push heroku master 141 | ``` 142 | 143 | # [kaityo256/yaml_cv](https://github.com/kaityo256/yaml_cv)との変更点 144 | 145 | - `data.yaml`の`photo`フィールドは、URLも使えることになった。 146 | - 提供された画像は向きを補正し、サイズも自動調節することになった。 147 | - コマンドモードを使う際、`data.yaml`と`style.txt`の中に、`erb`文法が書けるようになった。 148 | - `data.yaml`に`@date`で現在の年月日を出していて、[和暦](https://github.com/sugi/wareki)も使えることになった。 149 | - セキュリティの観点で、WEB版では`erb`文法の利用ができません。 150 | - `data.yaml`の `date: 令和2年1月14日現在` は空白の場合、和暦で本日の日付が入れられます。 151 | - `data.yaml`の `birth_day` Field「日付」はのみ記入した場合、年齢が自動で計算されます。 152 | - 「日付」の書式については、[和暦](https://github.com/sugi/wareki#%E3%83%91%E3%83%BC%E3%82%B9)または[西暦](https://docs.ruby-lang.org/ja/latest/class/Date.html#S_PARSE)が使えます。 153 | - サンプルデータとスタイルは`templates/`配下に置いた。 154 | - サンプルデータを当たり障りのない文章に再構成した。 155 | - サンプル写真を[StyleGAN](https://github.com/NVlabs/stylegan)で生成された偽の人物像を使用した。 156 | -------------------------------------------------------------------------------- /app.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require 'sinatra' 3 | require 'sinatra/reloader' if development? 4 | 5 | require './config' 6 | require './lib/cv_maker' 7 | require './lib/txt2yaml' 8 | require './lib/util' 9 | 10 | include Util 11 | 12 | get '/' do 13 | @title = "YAML to 履歴書" 14 | @date = Date.today 15 | @data_yml = load_as_erb("templates/data.yaml") 16 | @style_txt = load_as_erb("templates/style.txt") 17 | erb :index 18 | end 19 | 20 | post '/create' do 21 | begin 22 | @data = YAML.load(params[:data_yml]) 23 | @style = TXT2YAMLConverter.new.convert(params[:style_txt]) 24 | 25 | if !params[:photo].nil? 26 | @photo = params[:photo][:tempfile] 27 | @data["photo"] = @photo.path 28 | end 29 | 30 | @doc = CVMaker.new.generate(@data, @style) 31 | content_type 'application/pdf' 32 | @doc.render 33 | rescue => exception 34 | p exception 35 | @error_msg = exception.message 36 | status 403 37 | erb :error 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /config.rb: -------------------------------------------------------------------------------- 1 | class Config 2 | class << self 3 | @@settings = { 4 | VERSION: '0.4.0' 5 | } 6 | 7 | @@settings.each do |key, value| 8 | define_method key.to_sym do 9 | value.freeze 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.5' 2 | services: 3 | yaml_2_resume: 4 | container_name: yaml_2_resume 5 | build: . 6 | ports: 7 | - 14567:4567 8 | working_dir: /usr/src/app 9 | volumes: 10 | - ./share:/usr/src/app/share 11 | # web app mode 12 | command: ruby app.rb -o 0.0.0.0 13 | # cmd mode 14 | # command: ruby make_cv.rb -i templates/academic.yaml -s templates/academic.txt -o share/output.pdf 15 | -------------------------------------------------------------------------------- /heroku.yml: -------------------------------------------------------------------------------- 1 | build: 2 | docker: 3 | web: Dockerfile 4 | run: 5 | web: ruby app.rb -o 0.0.0.0 6 | -------------------------------------------------------------------------------- /lib/cv_maker.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require "mini_magick" 3 | require "open-uri" 4 | require "prawn" 5 | require "wareki" 6 | require "yaml" 7 | 8 | require "./lib/txt2yaml" 9 | require "./lib/util" 10 | 11 | $font_faces = Hash.new 12 | $font_faces["mincho"] = "fonts/ipaexm.ttf" 13 | $font_faces["gothic"] = "fonts/ipaexg.ttf" 14 | 15 | DEFAULT_FONT_FACE = "mincho" 16 | DEFAULT_FONT_SIZE = 12 17 | DEFAULT_LINE_WIDTH = 0.5 18 | 19 | class CVMaker 20 | include TXT2YAML 21 | include Util 22 | 23 | def line_style(h) 24 | if h.has_key?("line_style") 25 | case h["line_style"] 26 | when "solid" 27 | undash 28 | when "dashed" 29 | d = DEFAULT_LINE_WIDTH 30 | @doc.dash([d, d]) 31 | end 32 | else 33 | @doc.undash 34 | end 35 | if h.has_key?("line_width") 36 | @doc.line_width(h["line_width"]) 37 | else 38 | @doc.line_width(DEFAULT_LINE_WIDTH) 39 | end 40 | end 41 | 42 | def get_value(h) 43 | value = h["value"] 44 | if value =~ /\$(.*)$/ 45 | value = @data.fetch($1, "") 46 | end 47 | value 48 | end 49 | 50 | def get_font(h) 51 | font_size = h.fetch("font_size", DEFAULT_FONT_SIZE).to_f 52 | face = h.fetch("font_face", DEFAULT_FONT_FACE) 53 | font_face = "" 54 | if $font_faces.has_key?(face) 55 | font_face = $font_faces[face] 56 | else 57 | font_face = face 58 | end 59 | return font_size, font_face 60 | end 61 | 62 | def put_string(x, y, str, font_size, font_face) 63 | return if str == "" 64 | w = font_size * str.size 65 | h = font_size 66 | @doc.bounding_box([x, y], :width => w, :height => h) do 67 | @doc.font_size font_size 68 | @doc.font font_face 69 | @doc.text str 70 | end 71 | end 72 | 73 | def photo(h) 74 | return if !@data.has_key?("photo") 75 | x = size(h["x"]) 76 | y = size(h["y"]) 77 | width = size(h["width"]) 78 | height = size(h["height"]) 79 | file = @data["photo"] 80 | unless file.strip !~ /^https?|^ftp\:\/\// 81 | file = open(file).path 82 | end 83 | image = MiniMagick::Image.new(file) 84 | # 画像の向きを調整、切り抜き 85 | image.combine_options do |img| 86 | img.auto_orient 87 | img.strip # EXIF情報の除去 88 | img.gravity(:center) 89 | img.crop(resize_image_opt(image, width, height)) 90 | end 91 | @doc.image(image.path, :at => [x, y], :width => width, :height => height) 92 | end 93 | 94 | def string(h) 95 | x = size(h["x"]) 96 | y = size(h["y"]) 97 | str = get_value(h) 98 | font_size, font_face = get_font(h) 99 | w = font_size * str.size 100 | h = font_size 101 | @doc.bounding_box([x, y], :width => w, :height => h) do 102 | @doc.font_size font_size 103 | @doc.font font_face 104 | @doc.text str 105 | end 106 | end 107 | 108 | def box(h) 109 | line_style(h) 110 | x = size(h["x"]) 111 | y = size(h["y"]) 112 | w = size(h["width"]) 113 | h = size(h["height"]) 114 | @doc.move_to(x, y) 115 | @doc.line_to(x + w, y) 116 | @doc.line_to(x + w, y + h) 117 | @doc.line_to(x, y + h) 118 | @doc.close_and_stroke 119 | end 120 | 121 | def line(h) 122 | line_style(h) 123 | x = size(h["x"]) 124 | y = size(h["y"]) 125 | dx = size(h["dx"]) 126 | dy = size(h["dy"]) 127 | @doc.move_to(x, y) 128 | @doc.line_to(x + dx, y + dy) 129 | @doc.stroke 130 | end 131 | 132 | def lines(h) 133 | line_style(h) 134 | points = h["points"] 135 | x = size(points[0]["x"]) 136 | y = size(points[0]["y"]) 137 | close = h.has_key?("close") 138 | @doc.move_to(x, y) 139 | points[1..(points.size - 1)].each do |i| 140 | if i.has_key?("dx") 141 | x = x + size(i["dx"]) 142 | else 143 | x = size(i["x"]) 144 | end 145 | if i.has_key?("dy") 146 | y = y + size(i["dy"]) 147 | else 148 | y = size(i["y"]) 149 | end 150 | @doc.line_to(x, y) 151 | end 152 | if close 153 | @doc.close_and_stroke 154 | else 155 | @doc.stroke 156 | end 157 | end 158 | 159 | def multi_lines(h) 160 | line_style(h) 161 | x = size(h["x"]) 162 | y = size(h["y"]) 163 | dx = size(h["dx"]) 164 | dy = size(h["dy"]) 165 | sx = size(h["sx"]) 166 | sy = size(h["sy"]) 167 | n = h["num"].to_i 168 | n.times do |i| 169 | @doc.move_to(x, y) 170 | @doc.line_to(x + dx, y + dy) 171 | @doc.stroke 172 | x = x + sx 173 | y = y + sy 174 | end 175 | end 176 | 177 | def puts_history(year_x, month_x, value_x, h) 178 | year = h.fetch("year", "").to_s 179 | month = h.fetch("month", "").to_s 180 | put_string(year_x, y, year, font_size, font_face) 181 | x = month_x - (month.size - 1) * font_size * 0.3 182 | put_string(x, y, month, font_size, font_face) 183 | put_string(value_x, y, h["value"].to_s, font_size, font_face) 184 | end 185 | 186 | # 学歴・職歴 187 | def education_experience(h) 188 | y = size(h["y"]) 189 | year_x = size(h["year_x"]) 190 | month_x = size(h["month_x"]) 191 | value_x = size(h["value_x"]) 192 | ijo_x = size(h["ijo_x"]) 193 | dy = size(h["dy"]) 194 | caption_x = size(h["caption_x"]) 195 | font_size, font_face = get_font(h) 196 | put_string(caption_x, y, "学歴", font_size, font_face) 197 | y = y - dy 198 | education = @data["education"] 199 | education.each do |i| 200 | year = i.fetch("year", "").to_s 201 | month = i.fetch("month", "").to_s 202 | put_string(year_x, y, year, font_size, font_face) 203 | x = month_x - (month.size - 1) * font_size * 0.3 204 | put_string(x, y, month, font_size, font_face) 205 | put_string(value_x, y, i["value"].to_s, font_size, font_face) 206 | y = y - dy 207 | end 208 | put_string(caption_x, y, "職歴", font_size, font_face) 209 | y = y - dy 210 | experience = @data["experience"] 211 | experience.each do |i| 212 | year = i.fetch("year", "").to_s 213 | month = i.fetch("month", "").to_s 214 | put_string(year_x, y, year, font_size, font_face) 215 | x = month_x - (month.size - 1) * font_size * 0.3 216 | put_string(x, y, month, font_size, font_face) 217 | put_string(value_x, y, i["value"].to_s, font_size, font_face) 218 | y = y - dy 219 | end 220 | put_string(ijo_x, y, "以上", font_size, font_face) 221 | end 222 | 223 | def new_page(h) 224 | @doc.start_new_page 225 | end 226 | 227 | # 年・月・内容形式 228 | def history(h) 229 | y = size(h["y"]) 230 | year_x = size(h["year_x"]) 231 | month_x = size(h["month_x"]) 232 | value_x = size(h["value_x"]) 233 | font_size, font_face = get_font(h) 234 | data = get_value(h) 235 | dy = size(h["dy"]) 236 | return if data == "" 237 | data.each do |i| 238 | year = i.fetch("year", "").to_s 239 | month = i.fetch("month", "").to_s 240 | put_string(year_x, y, year, font_size, font_face) 241 | x = month_x - (month.size - 1) * font_size * 0.3 242 | put_string(x, y, month, font_size, font_face) 243 | put_string(value_x, y, i["value"].to_s, font_size, font_face) 244 | y = y + dy 245 | end 246 | end 247 | 248 | def textbox(h) 249 | x = size(h["x"]) 250 | y = size(h["y"]) 251 | width = size(h["width"]) 252 | height = size(h["height"]) 253 | value = get_value(h) 254 | font_size, font_face = get_font(h) 255 | @doc.bounding_box([x, y], :width => width, :height => height) do 256 | @doc.font_size font_size 257 | @doc.font font_face 258 | @doc.text value 259 | end 260 | end 261 | 262 | def generate(data, style) 263 | @data = data 264 | # 今日の日付を入力 265 | @data['date'] ||= Date.today.strftime("%Jf") + "現在" 266 | # 年齢を計算、「平成7年1月13日 (満 25 歳)」のような入力であれば、 267 | # 故意にエラーを発生させて計算はしない 268 | begin 269 | current_date = Date.parse(@data['date']) 270 | birthday = Date.parse(@data['birth_day']) 271 | age = current_date.strftime('%Y%m%d').to_i 272 | age -= birthday.strftime('%Y%m%d').to_i 273 | age /= 10000 274 | @data['birth_day'] += " (満 #{age} 歳)" 275 | rescue => e 276 | puts e.message 277 | end 278 | @doc = Prawn::Document.new(:page_size => "A4") 279 | style.each do |i| 280 | send(i["type"], i) 281 | end 282 | @doc 283 | end 284 | end 285 | -------------------------------------------------------------------------------- /lib/txt2yaml.rb: -------------------------------------------------------------------------------- 1 | require "yaml" 2 | 3 | module TXT2YAML 4 | def size(s, dpi = 75) 5 | if s =~ /\s*(\-?[0-9\.]+)\s*mm/ 6 | $1.to_f / 25.4 * dpi 7 | elsif s =~ /\s*(-?[0-9\.]+)\s*cm/ 8 | $1.to_f / 25.4 * dpi * 10 9 | elsif s =~ /\s*(-?[0-9\.]+)\s*px/ 10 | $1.to_f 11 | else 12 | s.to_f 13 | end 14 | end 15 | 16 | def rest(a, h) 17 | a.each do |v| 18 | b = v.split(/=/) 19 | h[b[0]] = b[1] 20 | end 21 | end 22 | 23 | def string(a) 24 | h = Hash.new 25 | h["type"] = a.shift 26 | h["x"] = a.shift 27 | h["y"] = a.shift 28 | h["value"] = a.shift 29 | rest(a, h) 30 | h 31 | end 32 | 33 | def box(a) 34 | h = Hash.new 35 | h["type"] = a.shift 36 | h["x"] = a.shift 37 | h["y"] = a.shift 38 | h["width"] = a.shift 39 | h["height"] = a.shift 40 | rest(a, h) 41 | h 42 | end 43 | 44 | def photo(a) 45 | h = Hash.new 46 | h["type"] = a.shift 47 | h["x"] = a.shift 48 | h["y"] = a.shift 49 | h["width"] = a.shift 50 | h["height"] = a.shift 51 | h 52 | end 53 | 54 | def line(a) 55 | h = Hash.new 56 | h["type"] = a.shift 57 | h["x"] = a.shift 58 | h["y"] = a.shift 59 | h["dx"] = a.shift 60 | h["dy"] = a.shift 61 | if a.size != 0 62 | rest(a, h) 63 | end 64 | h 65 | end 66 | 67 | def lines(a) 68 | h = Hash.new 69 | h["type"] = a.shift 70 | n = a.shift.to_i 71 | points = [] 72 | h["points"] = points 73 | points << {"x" => a.shift, "y" => a.shift} 74 | (n - 1).times do 75 | points << {"dx" => a.shift, "dy" => a.shift} 76 | end 77 | rest(a, h) 78 | h 79 | end 80 | 81 | def multi_lines(a) 82 | h = Hash.new 83 | h["type"] = a.shift 84 | h["x"] = a.shift 85 | h["y"] = a.shift 86 | h["dx"] = a.shift 87 | h["dy"] = a.shift 88 | h["num"] = a.shift 89 | h["sx"] = a.shift 90 | h["sy"] = a.shift 91 | h 92 | end 93 | 94 | def education_experience(a) 95 | h = Hash.new 96 | h["type"] = a.shift 97 | h["y"] = a.shift 98 | h["year_x"] = a.shift 99 | h["month_x"] = a.shift 100 | h["value_x"] = a.shift 101 | h["dy"] = a.shift 102 | h["caption_x"] = a.shift 103 | h["ijo_x"] = a.shift 104 | rest(a, h) 105 | h 106 | end 107 | 108 | def new_page(a) 109 | h = {"type" => "new_page"} 110 | h 111 | end 112 | 113 | def history(a) 114 | h = Hash.new 115 | h["type"] = a.shift 116 | h["y"] = a.shift 117 | h["year_x"] = a.shift 118 | h["month_x"] = a.shift 119 | h["value_x"] = a.shift 120 | h["dy"] = a.shift 121 | h["value"] = a.shift 122 | rest(a, h) 123 | h 124 | end 125 | 126 | def ymbox(a) 127 | a.shift 128 | name = a.shift 129 | y = size(a.shift) 130 | num = a.shift.to_i 131 | value = a.shift 132 | h = Hash[*a.map { |v| v.split(/=/) }.flatten] 133 | history_fontsize = h.fetch("font_size", 12) 134 | sy = size("7mm") 135 | dy = (num + 1) * sy 136 | namepos = 104 - name.length * 1.7 137 | r = Array.new 138 | r.push box("box,0,#{y},177mm,#{dy},line_width=2".split(",")) 139 | r.push line("line,19mm,#{y + dy},0,#{-dy},line_style=dashed".split(",")) 140 | r.push line("line,31mm,#{y + dy},0,#{-dy}".split(",")) 141 | r.push multi_lines("multi_lines,0,#{y + dy - sy},177mm,0,#{num},0,-7mm".split(",")) 142 | r.push history("history,#{y + dy - sy - size("2mm")},3mm,24mm,35mm,-7mm,#{value},font_size=#{history_fontsize}".split(",")) 143 | r.push string("string,8mm,#{y + dy - size("2mm")},年,font_size=9".split(",")) 144 | r.push string("string,24mm,#{y + dy - size("2mm")},月,font_size=9".split(",")) 145 | r.push string("string,#{namepos}mm,#{y + dy - size("2mm")},#{name},font_size=9".split(",")) 146 | return r 147 | end 148 | 149 | def miscbox(a) 150 | a.shift 151 | name = a.shift 152 | y = size(a.shift) 153 | height = size(a.shift) 154 | value = a.shift 155 | h = Hash[*a.map { |v| v.split(/=/) }.flatten] 156 | textbox_fontsize = h.fetch("font_size", 12) 157 | namepos = size((88.5 - name.length * 1.7).to_s + "mm") 158 | r = Array.new 159 | r.push string("string,#{namepos},#{y + height - size("2mm")},#{name},font_size=9".split(",")) 160 | r.push line("line,0,#{y + height - size("7mm")},177mm,0".split(",")) 161 | r.push textbox("textbox,2mm,#{y + height - size("9mm")},175mm,#{height - size("9mm")},#{value},font_size=#{textbox_fontsize}".split(",")) 162 | r.push box("box,0,#{y},177mm,#{height},line_width=2".split(",")) 163 | r 164 | end 165 | 166 | def textbox(a) 167 | h = Hash.new 168 | h["type"] = a.shift 169 | h["x"] = a.shift 170 | h["y"] = a.shift 171 | h["width"] = a.shift 172 | h["height"] = a.shift 173 | h["value"] = a.shift 174 | rest(a, h) 175 | h 176 | end 177 | 178 | def convert(style_txt) 179 | data = [] 180 | style_txt.lines.each do |line| 181 | next if line =~ /^#/ 182 | next if line.chomp == "" 183 | a = line.chomp.split(/,/) 184 | d = send(a[0], a) 185 | next if d == nil 186 | if d.kind_of?(Array) 187 | data = data + d 188 | else 189 | data.push d 190 | end 191 | end 192 | data 193 | end 194 | 195 | def load_file(file_path) 196 | style_txt = File.read(file_path) 197 | convert(style_txt) 198 | end 199 | end 200 | 201 | class TXT2YAMLConverter 202 | include TXT2YAML 203 | end 204 | 205 | if caller.length == 0 206 | if ARGV.size == 0 207 | puts "usage: input.txt" 208 | exit 209 | end 210 | 211 | filename = ARGV[0] 212 | data = TXT2YAMLConverter.new.convert(filename) 213 | YAML.dump(data, $stdout) 214 | end 215 | -------------------------------------------------------------------------------- /lib/util.rb: -------------------------------------------------------------------------------- 1 | require 'erb' 2 | require 'date' 3 | require 'wareki' 4 | 5 | module Util 6 | def load_as_erb(file_path) 7 | ERB.new(File.read(file_path)).result(binding) 8 | end 9 | 10 | def resize_image_opt(img, w, h) 11 | w_ori = img[:width].to_f 12 | h_ori = img[:height].to_f 13 | w_f = w.to_f 14 | h_f = h.to_f 15 | w_tar = w_ori 16 | h_tar = h_ori 17 | 18 | len_max = [w_tar, h_tar].min 19 | if w_tar > h_tar 20 | h_tar = len_max 21 | w_tar = len_max * w_f / h_f 22 | else 23 | h_tar = len_max * h_f / w_f 24 | w_tar = len_max 25 | end 26 | "#{w_tar.to_i}x#{h_tar.to_i}+0+0!" 27 | end 28 | 29 | end 30 | -------------------------------------------------------------------------------- /make_cv.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require "optparse" 3 | require "prawn" 4 | require "yaml" 5 | 6 | require './config' 7 | require './lib/cv_maker' 8 | require './lib/txt2yaml' 9 | require './lib/util' 10 | 11 | include Util 12 | 13 | def parse_option 14 | args = {} 15 | OptionParser.new do |op| 16 | op.on("-i [datafile]", "--input [datafile]") { |v| args[:input] = v } 17 | op.on("-s [stylefile]", "--style [stylefile]") { |v| args[:style] = v } 18 | op.on("-o [output]", "--output [output]") { |v| args[:output] = v } 19 | op.on("-v", "--version") { |v| args[:version] = v } 20 | op.parse!(ARGV) 21 | end 22 | args 23 | end 24 | 25 | def check_fonts 26 | $font_faces.each do |k, v| 27 | if !File.exists?(v) 28 | puts <<-EOS 29 | Font files are not found. 30 | Please download IPAex fonts via 31 | 32 | https://ipafont.ipa.go.jp/node26 33 | 34 | and place them as 35 | 36 | ├── fonts 37 | │   ├── ipaexg.ttf 38 | │   └── ipaexm.ttf 39 | └── make_cv.rb 40 | EOS 41 | 42 | exit 43 | end 44 | end 45 | end 46 | 47 | def cerate_pdf(input_file, style_file, output_file) 48 | @date = Date.today 49 | @data = YAML.load(load_as_erb(input_file)) 50 | if style_file =~ /\.txt$/ 51 | @style = TXT2YAMLConverter.new.convert(load_as_erb(style_file)) 52 | else 53 | @style = YAML.load(load_as_erb(style_file)) 54 | end 55 | 56 | puts "input file: #{input_file}" 57 | puts "style file: #{style_file}" 58 | puts "output file: #{output_file}" 59 | 60 | @doc = CVMaker.new.generate(@data, @style) 61 | @doc.render_file output_file 62 | puts "Done." 63 | end 64 | 65 | check_fonts 66 | 67 | args = parse_option 68 | if args[:version] 69 | puts Config.VERSION 70 | exit(0) 71 | end 72 | input_file = args.fetch(:input, "templates/data.yaml") 73 | style_file = args.fetch(:style, "templates/style.txt") 74 | output_file = args.fetch(:output, "output.pdf") 75 | 76 | cerate_pdf(input_file, style_file, output_file) 77 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | all: output.pdf academic.pdf 2 | 3 | output.pdf: style.txt data.yaml 4 | ruby make_cv.rb -i data.yaml -s style.txt -o $@ 5 | 6 | academic.pdf: academic.txt data.yaml 7 | ruby make_cv.rb -i data.yaml -s academic.txt -o $@ 8 | 9 | clean: 10 | rm -f output.pdf academic.pdf 11 | -------------------------------------------------------------------------------- /photo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jerrywdlee/yaml_2_resume/70a14c94d6bdde66e8a645d3ff531ea9c3155ce9/photo.png -------------------------------------------------------------------------------- /public/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jerrywdlee/yaml_2_resume/70a14c94d6bdde66e8a645d3ff531ea9c3155ce9/public/.gitkeep -------------------------------------------------------------------------------- /public/script.js: -------------------------------------------------------------------------------- 1 | ($ => { 2 | const FILE_SIZE_LIMIT = 2 // 2mega 3 | $(() => { 4 | initEditor() 5 | initPhotoBtn() 6 | 7 | $(document) 8 | .on('click', '#photoBtn', () => { 9 | $('#photoInput').click() 10 | }) 11 | .on('change', '#photoInput', ev => { 12 | const el = ev.currentTarget 13 | const file = el.files[0] 14 | if (file) { 15 | const fileSize = file.size 16 | if (fileSize > 1024 * 1024 * FILE_SIZE_LIMIT) { 17 | alert(`写真データを ${FILE_SIZE_LIMIT}m 以下にしてください!`) 18 | el.value = '' 19 | } else { 20 | setPhotoBtn(file) 21 | } 22 | } else { 23 | $('#photoBtn').text('写真をアップ') 24 | } 25 | }) 26 | }) 27 | })(jQuery) 28 | 29 | function initEditor() { 30 | const [dataYamlArea, styleTxtArea] = ['data_yml', 'style_txt'].map(id => { 31 | return document.getElementById(id) 32 | }) 33 | if (!dataYamlArea || !styleTxtArea) { 34 | return 35 | } 36 | const editorOpt = { 37 | mode: "yaml", 38 | theme: 'eclipse', 39 | lineNumbers: true, 40 | indentUnit: 4 41 | } 42 | window.dataYamlEditor = CodeMirror.fromTextArea(dataYamlArea, editorOpt) 43 | window.styleTxtEditor = CodeMirror.fromTextArea(styleTxtArea, editorOpt) 44 | window.editorSave = () => { 45 | dataYamlEditor.save() 46 | styleTxtEditor.save() 47 | return true 48 | } 49 | } 50 | 51 | function initPhotoBtn() { 52 | const fileInput = document.querySelector('#photoInput') 53 | if (fileInput) { 54 | const file = fileInput.files[0] 55 | if (file) { 56 | setPhotoBtn(file) 57 | } 58 | } 59 | } 60 | 61 | function setPhotoBtn(file) { 62 | let fileName = file.name 63 | if (fileName.length > 10) { 64 | const l = fileName.length 65 | fileName = '...' + fileName.substring(l - 10, l) 66 | } 67 | $('#photoBtn').text(fileName) 68 | } 69 | -------------------------------------------------------------------------------- /public/style.css: -------------------------------------------------------------------------------- 1 | .main-container { 2 | padding: 3em; 3 | } 4 | 5 | .photo-area { 6 | display: none; 7 | } 8 | 9 | .code-area { 10 | border: solid 1px #ccc; 11 | border-radius: 5px; 12 | } 13 | .CodeMirror { 14 | font-family: Monaco, 'Andale Mono', 'Lucida Console', 'Bitstream Vera Sans Mono', 'Courier New', Courier, monospace; 15 | font-size: 10pt; 16 | height: 450px; 17 | border-radius: 5px; 18 | } 19 | .CodeMirror pre.CodeMirror-line { 20 | padding-left: 10px 21 | } 22 | .CodeMirror .CodeMirror-gutters { 23 | border-top-left-radius: 5px; 24 | border-bottom-left-radius: 5px; 25 | } 26 | 27 | @media screen and (min-width: 769px) { 28 | .error-container { 29 | min-height: calc(100vh - 250px) 30 | } 31 | } 32 | 33 | @media screen and (max-width: 1023px) { 34 | .main-container { 35 | padding: 1em; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /sample/README_origin.md: -------------------------------------------------------------------------------- 1 | YAMLによる履歴書作成スクリプト 2 | === 3 | 4 | [![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE) 5 | 6 | YAML形式で書かれたデータファイルと、YAMLもしくはテキストファイル形式で書かれたスタイルから履歴書PDFファイルを作成するRubyスクリプトです。 7 | 8 | ## 必要なライブラリ等 9 | 10 | * Prawn 11 | * IAPexフォント 12 | 13 | Prawnから日本語を出力するためにIPAexフォントを使っています。スクリプトと同じ場所にfontsディレクトリを用意して、[ここ](https://ipafont.ipa.go.jp/node26)からフォントをダウンロードして以下のように配置してください。 14 | 15 | ``` 16 | ├── fonts 17 | │   ├── ipaexg.ttf 18 | │   └── ipaexm.ttf 19 | └── make_cv.rb 20 | ``` 21 | 22 | ## 使い方 23 | 24 | 以下のように、`-i`に続けてデータファイル、`-s`に続けてスタイルファイル、`-o`に続けて出力ファイルを指定します。省略した場合のデフォルトはそれぞれ`data.yaml`、`style.txt`、`output.pdf`です。 25 | 26 | ``` 27 | $ ruby make_cv -h 28 | Usage: make_cv [options] 29 | -i, --input [datafile] 30 | -s, --style [stylefile] 31 | -o, --output [output] 32 | ``` 33 | 34 | YAML形式のデータファイル(例:`data.yaml`)とスタイルファイル(例:`style.txt`)を用意し、スクリプトを以下のように実行します。 35 | 36 | ``` 37 | $ ruby make_cv -i data.yaml -s style.txt -o output.pdf 38 | ``` 39 | 40 | 添付のサンプルでは以下のような出力が得られます。 41 | 42 | [PDFファイル](sample/output.pdf) 43 | 44 | ![output1.png](sample/output1.png) 45 | ![output2.png](sample/output2.png) 46 | 47 | また、スタイルファイルを変えることで、同じデータファイルから異なる出力を得られます。以下はアカデミックポスト向けのスタイルファイル`academic.txt`を使った場合です。 48 | 49 | ``` 50 | $ ruby make_cv -i data.yaml -s academic.txt -o academic.pdf 51 | ``` 52 | 53 | [PDFファイル](sample/academic.pdf) 54 | 55 | ![academic1.png](sample/academic1.png) 56 | ![academic2.jpg](sample/academic2.png) 57 | 58 | ## データの用意の仕方 59 | 60 | データはYAML形式で用意します。 61 | 62 | 以下はサンプルの`data.yaml`の一部です。 63 | 64 | ```YAML 65 | # 名前等 66 | date: 2018年 6月 5日現在 67 | name_kana: りれきしょ かくたろう 68 | name: 履歴書 書太郎 69 | birth_day: 1543年1月31日 (満 73 歳) 70 | gender: 男 71 | cell_phone: 090-1234-5678 72 | email: hoge@hogehoge.org 73 | photo: photo.jpg 74 | 75 | # 住所 76 | address_kana: とうきょうとちよだくちよだ 77 | address: 東京都千代田区千代田1-1-1 78 | address_zip: 100-0001 79 | tel: 0120-000-XXX 80 | fax: 0120-111-XXX 81 | # 連絡先 82 | address_kana2: ほっかいどう わっかないし そうやみさき 83 | address2: 北海道稚内市宗谷岬 84 | address_zip2: 098-6758 85 | tel2: 0120-222-XXX 86 | fax2: 0120-333-XXX 87 | ``` 88 | 89 | 例えば、写真が`photo.jpg`として用意してある場合、 90 | 91 | ```YAML 92 | photo: photo.jpg 93 | ``` 94 | 95 | とすれば写真が貼られます。もし写真ファイルを指定しなかった場合、例えば 96 | 97 | ```YAML 98 | # photo: photo.jpg 99 | ``` 100 | 101 | としてコメントアウトすると、写真部分は 102 | 103 | ![sample/photo.png](sample/photo.png) 104 | 105 | のような出力になります。 106 | 107 | ## スタイルファイル 108 | 109 | 履歴書のスタイルファイルは、一行に一要素ずつ記述していきます。例えば`academic.txt`は以下のような内容になっています。 110 | 111 | ``` 112 | # ヘッダー 113 | string,5mm,247mm,履 歴 書,font_size=14,font_face=gothic 114 | string,110mm,245mm,$date,font_size=9 115 | 116 | # 住所・連絡先等 117 | box,145mm,204mm,30mm,40mm,line_style=dashed 118 | string,148mm,240mm,写真を貼る位置,font_size=9 119 | string,147mm,233mm,1. 縦36〜40 mm,font_size=8 120 | string,150mm,230mm,横24〜30 mm,font_size=8 121 | string,147mm,227mm,2. 本人単身胸から上,font_size=8 122 | string,147mm,224mm,3. 裏面にのりづけ,font_size=8 123 | string,147mm,221mm,4. 裏面に氏名記入,font_size=8 124 | ``` 125 | 126 | 一行目の 127 | 128 | ``` 129 | string,5mm,247mm,履 歴 書,font_size=14,font_face=gothic 130 | ``` 131 | 132 | は、(5mm,247mm)の位置に「履 歴 書」という文字を、フォントサイズ14で、フォントはゴシックで記述する命令です。このうち必須なのは座標と文字列ですが、その後にオプションとしてフォントサイズやフォントの指定ができます。 133 | 134 | 次の行の 135 | 136 | ``` 137 | string,110mm,245mm,$date,font_size=9 138 | ``` 139 | 140 | も同様に(110mm, 245mm)の位置に文字列を出力する命令ですが、出力する文字列が`$date`となっています。これは、データファイルの`data`要素に置換されます。今、データファイルは 141 | 142 | ```yaml 143 | date: 2018年 6月 5日現在 144 | ``` 145 | 146 | とあるので、 147 | 148 | ``` 149 | string,110mm,245mm,$date,font_size=9 150 | ``` 151 | 152 | は 153 | 154 | ``` 155 | string,110mm,245mm,2018年 6月 5日現在,font_size=9 156 | ``` 157 | 158 | に展開されます。 159 | 160 | テキスト形式の命令リストは以下の通りです。 161 | 162 | 命令| フォーマット| 説明 163 | -- | ---- | ---- 164 | string | string, x, y, value[,font options] | (x,y)に文字列valueを出力 165 | line | line, x, y, dx, dy[,line options] | (x,y)から(x+dx, y+dy)に線を描画 166 | box | box,x,y,width,height[,line options]| (x,y)に指定された幅と高さのボックスを描画 167 | photo |photo, x, y, width, height | 指定位置、サイズに写真ファイル`$photo`を描画 168 | new_page |new_page | 改ページ 169 | textbox |textbox,x,y,width,height,value[,font options] |指定位置、サイズにテキストボックスを描画 170 | multi_lines| multi_lines, x,y,dx,dy,num,sx,sy| (x,y)から、(dx,dy)方向に、毎回(sx,sy)だけ座標をずらしながらnum回線を引きます 171 | ymbox| ymbox,title,height,num,$value | $valueをhistoryデータとして、高さyに、年月表を作るマクロです。 172 | miscbox| miscbox,title,y,height,$value | タイトル付きテキストボックスです。$valueの内容をテキストボックス内に展開します。 173 | 174 | ### 特別な命令 175 | 176 | #### history 177 | 178 | 年、月、内容をともなったデータを描画する命令です。ymboxやmiscboxの内部で呼ばれます。 179 | 180 | ``` 181 | history, y, year_x, month_x, value_x, dy, value[,font options] 182 | ``` 183 | 184 | * y: 描画を始めるy座標 185 | * year_x, month_x, value_x: それぞれ年、月、内容を書くx座標 186 | * dy: 改行時にどれだけ下に下げるか 187 | * value: 内容 188 | 189 | history環境に使われるデータは以下のように指定します。 190 | 191 | ```yaml 192 | licences: 193 | - 194 | year: 19XX 195 | month: 11 196 | value: 普通自動車免許 197 | - 198 | year: 20XX 199 | month: 11 200 | value: 履歴書検定1級 201 | ``` 202 | 203 | `year`や`month`は省略可能です(空白になります)。 204 | 205 | #### education_experience 206 | 207 | 学歴、職歴を書く命令です。命令フォーマットは以下の通りです。 208 | 209 | ``` 210 | education_experience, y, year_x, month_x, value_x, dy, caption_x, ijo_x, [,font options] 211 | ``` 212 | 213 | * y: 描画を始めるy座標 214 | * year_x, month_x, value_x: それぞれ年、月、内容を書くx座標 215 | * dy: 改行時にどれだけ下に下げるか 216 | * caption_x: 「学歴」や「職歴」の文字のx座標 217 | * ijo_x: 「以上」の文字のx座標 218 | 219 | なお、学歴データは`$education`が、職歴データは`$experience$`が仮定されます。 220 | 221 | #### lines 222 | 223 | 複数の線を書く命令です。履歴書によくある右上が凹んだボックスを書くために用意してあります。命令フォーマットは以下の通りです。 224 | 225 | ``` 226 | lines, num, x,y, dx, dy, ..., [,line options] 227 | ``` 228 | 229 | 最初に「何個の点データがあるか」を`num`で指定します。その後、始点、次の点の相対座標・・・と続け、最後に線のオプションを指定します。なお、この命令だけのオプションとして「close」というものがあります。これを指定すると最後に線を閉じます。 230 | 231 | #### ymbox 232 | 233 | 「年、月、事柄」を書くマクロです。例えば 234 | 235 | ``` 236 | ymbox,免許・資格,204mm,4,$licences 237 | ``` 238 | 239 | は、「免許・資格」というタイトルの4行分の年月ボックスを、高さ204mmのところに配置する、という意味です。最後がデータのYAMLファイルの対応するレコードです。例えば、 240 | 241 | ```yaml 242 | # 免許・資格 243 | licences: 244 | - 245 | year: 19XX 246 | month: 11 247 | value: 普通自動車免許 248 | - 249 | year: 20XX 250 | month: 11 251 | value: 履歴書検定1級 252 | ``` 253 | 254 | というデータだったとすると、 255 | 256 | ![sample/licenses.png](sample/licenses.png) 257 | 258 | に展開されます。 259 | 260 | ``` 261 | ymbox,免許・資格,204mm,4,$licences,font_size=9 262 | ``` 263 | 264 | などのように、最後にフォントサイズを指定できます。 265 | 266 | #### miscbox 267 | 268 | タイトル付きのテキストボックスに展開されるマクロです。例えば、 269 | 270 | ``` 271 | miscbox,教育歴,120mm,50mm,$teaching,font_size=12 272 | ``` 273 | 274 | は、「教育歴」というタイトルの高さ(height)50mmのテキストボックスを、高さ(y座標)120mmのところに配置する、という意味です。最後のフォントサイズ指定は任意です。対応するYAMLレコードは\$teachingです。 275 | 276 | 例えば 277 | 278 | ```yaml 279 | # 教育歴 280 | teaching: | 281 | 「履歴書学特論」平成15年から17年まで 282 | 「PDF出力特論」平成18年から23年まで 283 | 「スタイルファイル特別演習」平成20年から29年まで 284 | ``` 285 | 286 | というデータだったとすると、 287 | 288 | ![sample/teaching.png](sample/teaching.png) 289 | 290 | に展開されます。 291 | 292 | ## スタイルファイル(YAML形式) 293 | 294 | 上記のテキスト形式で書かれたスタイルファイルは内部的にYAMLに変換されて処理されているため、スタイルファイルはYAML形式でも書くことができます。・・・が、実際やってみたら死ぬほど面倒だったので、通常はテキスト形式で書くことになろうかと思います。テキスト形式のスタイルファイルは`txt2yaml.rb`でYAML形式に変換できます。 295 | 296 | 例えば 297 | 298 | ``` 299 | string,5mm,247mm,履 歴 書,font_size=14,font_face=gothic 300 | string,110mm,245mm,$date,font_size=9 301 | ``` 302 | 303 | を`txt2yaml.rb`に食わせると、 304 | 305 | 306 | ```YAML 307 | --- 308 | - type: string 309 | x: 5mm 310 | y: 247mm 311 | value: 履 歴 書 312 | font_size: '14' 313 | font_face: gothic 314 | - type: string 315 | x: 110mm 316 | y: 245mm 317 | value: "$date" 318 | font_size: '9' 319 | ``` 320 | 321 | が出力されます。同様にYAMLで書かれたスタイルファイルは`yaml2txt.rb`でテキスト形式に変換できます。 322 | 323 | ## 参考 324 | 325 | このスクリプトは、[PruneMazui](https://github.com/PruneMazui)さんの[resume-maker](https://github.com/PruneMazui/resume-maker)に影響されて開発したものです。 326 | 327 | 328 | ## 履歴 329 | 330 | - 2018年7月30日 ymboxマクロ及びmiscboxマクロを追加 331 | - 2018年6月6日 リリース 332 | 333 | ## Herokuメモ 334 | 335 | ```sh 336 | $ heroku create yaml-2-resume 337 | $ heroku stack:set container 338 | $ git push heroku master 339 | ``` -------------------------------------------------------------------------------- /sample/screen_pc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jerrywdlee/yaml_2_resume/70a14c94d6bdde66e8a645d3ff531ea9c3155ce9/sample/screen_pc.png -------------------------------------------------------------------------------- /share/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jerrywdlee/yaml_2_resume/70a14c94d6bdde66e8a645d3ff531ea9c3155ce9/share/.gitkeep -------------------------------------------------------------------------------- /spec/app_spec.rb: -------------------------------------------------------------------------------- 1 | # require File.expand_path '../spec_helper.rb', __FILE__ 2 | require './app.rb' 3 | require './lib/util' 4 | include Util 5 | 6 | describe "Sinatra Application" do 7 | it "should allow accessing the home page" do 8 | get '/' 9 | expect(last_response).to be_ok 10 | end 11 | 12 | it "should receive form-data" do 13 | @date = Date.today 14 | data = { 15 | data_yml: load_as_erb("templates/data.yaml"), 16 | style_txt: load_as_erb("templates/style.txt"), 17 | } 18 | post '/create', data 19 | expect(last_response).to be_ok 20 | end 21 | 22 | it "should raise error" do 23 | data = {} 24 | post '/create', data 25 | expect(last_response).not_to be_ok 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/cv_maker_spec.rb: -------------------------------------------------------------------------------- 1 | require 'securerandom' 2 | 3 | require './lib/cv_maker' 4 | require './lib/txt2yaml' 5 | require './lib/util' 6 | 7 | include Util 8 | 9 | describe "CVMaker" do 10 | before do 11 | @converter = TXT2YAMLConverter.new 12 | @date = Date.today 13 | @file_name = "_spec_#{SecureRandom.hex(8)}.pdf" 14 | end 15 | 16 | after do 17 | begin 18 | File.delete(@file_name) 19 | rescue 20 | p $! 21 | end 22 | end 23 | 24 | it "should create normal resume pdf" do 25 | data = YAML.load(load_as_erb('templates/data.yaml')) 26 | style = @converter.load_file('templates/style.txt') 27 | doc = CVMaker.new.generate(data, style) 28 | expect(doc).to be_truthy 29 | doc.render_file @file_name 30 | expect(File.exists?(@file_name)).to be_truthy 31 | end 32 | 33 | it "should create academic resume pdf" do 34 | data = YAML.load(load_as_erb('templates/academic.yaml')) 35 | style = @converter.load_file('templates/academic.txt') 36 | doc = CVMaker.new.generate(data, style) 37 | expect(doc).to be_truthy 38 | doc.render_file @file_name 39 | expect(File.exists?(@file_name)).to be_truthy 40 | end 41 | 42 | end 43 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # spec/spec_helper.rb 2 | require 'rack/test' 3 | require 'rspec' 4 | require 'simplecov' 5 | 6 | SimpleCov.start 7 | 8 | ENV['RACK_ENV'] = 'test' 9 | 10 | module RSpecMixin 11 | include Rack::Test::Methods 12 | def app() Sinatra::Application end 13 | end 14 | 15 | RSpec.configure { |c| c.include RSpecMixin } 16 | -------------------------------------------------------------------------------- /spec/txt2yaml_spec.rb: -------------------------------------------------------------------------------- 1 | require './lib/txt2yaml.rb' 2 | 3 | describe "TXT2YAMLConverter" do 4 | before do 5 | @converter = TXT2YAMLConverter.new 6 | end 7 | 8 | it "should convert style.txt to style array" do 9 | style = @converter.load_file('templates/style.txt') 10 | expect(style.size).to be > 0 11 | end 12 | 13 | it "should convert academic.txt to style array" do 14 | style = @converter.load_file('templates/academic.txt') 15 | expect(style.size).to be > 0 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /templates/academic.txt: -------------------------------------------------------------------------------- 1 | # ヘッダー 2 | string,5mm,247mm,履 歴 書,font_size=14,font_face=gothic 3 | string,110mm,245mm,$date,font_size=9 4 | 5 | # 住所・連絡先等 6 | box,145mm,204mm,30mm,40mm,line_style=dashed 7 | string,148mm,240mm,写真を貼る位置,font_size=9 8 | string,147mm,233mm,1. 縦36〜40 mm,font_size=8 9 | string,150mm,230mm,横24〜30 mm,font_size=8 10 | string,147mm,227mm,2. 本人単身胸から上,font_size=8 11 | string,147mm,224mm,3. 裏面にのりづけ,font_size=8 12 | string,147mm,221mm,4. 裏面に氏名記入,font_size=8 13 | photo,145mm,244mm,30mm,40mm 14 | lines,6,0mm,240mm,139mm,0mm,0mm,-41mm,38mm,0mm,0mm,-36mm,-177mm,0mm,line_width=2.0,close=true 15 | line,0,233mm,139mm,0,line_style=dashed 16 | string,2mm,238mm,ふりがな,font_size=9 17 | string,30mm,238mm,$name_kana,font_size=9 18 | string,2mm,231mm,氏  名,font_size=9 19 | string,30mm,231mm,$name,font_size=14 20 | line,0,218mm,139mm,0mm 21 | line,15mm,218mm,0,-13mm 22 | string,1.5mm,217mm,生年月日,font_size=9 23 | string,30mm,213mm,$birth_day,font_size=12 24 | line,110mm,218mm,0mm,-13mm 25 | string,121mm,214mm,$gender,font_size=12 26 | line,21mm,205mm,0,-6mm,line_style=dashed 27 | string,1.5mm,203.5mm,携帯電話番号,font_size=9 28 | line,0,205mm,139mm,0mm 29 | string,25mm,203.5mm,$cell_phone,font_size=9 30 | line,56mm,205mm,0mm,-6mm 31 | line,69mm,205mm,0mm,-6mm,line_style=dashed 32 | string,57mm,204mm,E-MAIL,font_size=9 33 | string,75mm,204mm,$email,font_size=9 34 | line,0,205mm,139mm,0mm 35 | line,0,199mm,139mm,0mm 36 | line,0,194mm,139mm,0,line_style=dashed 37 | line,0,181mm,177mm,0 38 | line,0,176mm,139mm,0,line_style=dashed 39 | line,139mm,199mm,0mm,-36mm 40 | string,141mm,198mm,電話,font_size=9 41 | string,143mm,190mm,$tel,font_size=9 42 | string,141mm,180mm,電話,font_size=9 43 | string,143mm,172mm,$tel2,font_size=9 44 | string,2mm,198mm,ふりがな,font_size=8 45 | string,20mm,198mm,$address_kana,font_size=8 46 | string,2mm,193mm,現住所 〒,font_size=8 47 | string,15mm,193mm,$address_zip,font_size=8 48 | string,15mm,188mm,$address,font_size=12 49 | string,2mm,180mm,ふりがな,font_size=8 50 | string,20mm,180mm,$address_kana2,font_size=8 51 | string,2mm,175mm,連絡先 〒,font_size=9 52 | string,15mm,175mm,$address_zip2,font_size=9 53 | string,15mm,169mm,$address2,font_size=12 54 | 55 | # 所属学会・学位論文など 56 | box,0mm,137mm,177mm,24mm,line_width=2 57 | line,0mm,145mm,177mm,0mm 58 | line,0mm,153mm,177mm,0mm 59 | line,25mm,137mm,0mm,24mm 60 | line,88mm,153mm,0mm,8mm 61 | line,113mm,153mm,0mm,8mm 62 | 63 | string,4mm,159mm,博士学位 64 | string,92mm,159mm,取得年度 65 | string,4mm,151mm,授与機関 66 | string,4mm,143mm,学位論文 67 | string,26mm,159mm,$degree 68 | string,117mm,159mm,$degree_year 69 | string,26mm,151mm,$degree_affiliation 70 | string,26mm,143mm,$thesis_title,font_face=Times-Roman 71 | 72 | # 学歴・職歴 73 | box,0mm,9mm,177mm,126mm,line_width=2 74 | multi_lines,0mm,16mm,177mm,0,17,0,7mm 75 | line,19mm,135mm,0,-126mm,line_style=dashed 76 | line,31mm,135mm,0,-126mm 77 | string,8mm,133mm,年,font_size=9 78 | string,24mm,133mm,月,font_size=9 79 | string,77mm,133mm,学歴・職歴(各項目ごとにまとめて書く),font_size=9 80 | education_experience,127mm,3mm,24mm,35mm,7mm,95mm,155mm,font_size=12 81 | 82 | 83 | # 2ページ目 84 | new_page 85 | ymbox,免許・資格,204mm,4,$licences 86 | ymbox,主な受賞・表彰等,173mm,3,$awards 87 | miscbox,教育歴,120mm,50mm,$teaching,font_size=12 88 | miscbox,所属学会等,84mm,33mm,$affiliated_society 89 | miscbox,その他特記事項,55mm,26mm,$notices,font_size=12 90 | 91 | # 署名欄 92 | string,113mm,36mm,上記のとおり相違ありません。 93 | string,140mm,24mm,年  月  日 94 | string,165mm,12mm,印 95 | string,85mm,12mm,氏名 96 | line,80mm,5mm,90mm,0 97 | -------------------------------------------------------------------------------- /templates/academic.yaml: -------------------------------------------------------------------------------- 1 | # 名前等 2 | date: <%= @date.strftime("%Y年%m月%d日") %>現在 3 | name_kana: りれき たろう 4 | name: 履歴 太郎 5 | birth_day: <%= (@date << 30 * 12).strftime("%Y年%m月%d日") %> (満 30 歳) 6 | gender: 男 7 | cell_phone: 090-1234-5678 8 | email: rireki-taro@example.org 9 | # 写真をアップロードした場合、このフィールドが無視されます 10 | # photo: https://cdn.jsdelivr.net/gh/jerrywdlee/yaml_cv@master/photo.png 11 | 12 | # 住所 13 | address_kana: とうきょうとちよだくちよだ 14 | address: 東京都千代田区千代田1-1-1 15 | address_zip: 100-0001 16 | tel: 0120-000-XXX 17 | fax: 0120-111-XXX 18 | # 連絡先 19 | address_kana2: ほっかいどう わっかないし そうやみさき 20 | address2: 北海道稚内市宗谷岬 21 | address_zip2: 098-6758 22 | tel2: 0120-222-XXX 23 | fax2: 0120-333-XXX 24 | 25 | # 学位 26 | degree: 博士(工学) 27 | # 学位取得年度 28 | degree_year: <%= (@date << 12).year %>年度 29 | # 学位授与機関 30 | degree_affiliation: 履歴書大学 31 | # 学位論文 32 | thesis_title: How to write a resume without a spreadsheet 33 | 34 | 35 | # 学歴 36 | education: 37 | - 38 | year: <%= (@date << 9 * 12).year %> 39 | month: 4 40 | value: 履歴書大学履歴書学部 入学 41 | - 42 | year: <%= (@date << 5 * 12).year %> 43 | month: 3 44 | value: 同 卒業 45 | - 46 | year: <%= (@date << 5 * 12).year %> 47 | month: 4 48 | value: 履歴書大学履歴書研究科履歴書専攻博士前期課程 入学 49 | - 50 | year: <%= (@date << 3 * 12).year %> 51 | month: 3 52 | value: 同 修了 53 | - 54 | year: <%= (@date << 3 * 12).year %> 55 | month: 4 56 | value: 履歴書大学履歴書研究科履歴書専攻博士後期課程 入学 57 | - 58 | year: <%= (@date << 1 * 12).year %> 59 | month: 3 60 | value: 同 修了 61 | 62 | 63 | 64 | #職歴 65 | experience: 66 | - 67 | year: <%= (@date << 1 * 12).year %> 68 | month: 4 69 | value: 株式会社履歴書入社 70 | - 71 | year: <%= @date.year %> 72 | month: 10 73 | value: 株式会社履歴書退職 74 | - 75 | year: <%= @date.year %> 76 | month: 10 77 | value: 株式会社XXX 入社 78 | - 79 | value: 現在にいたる 80 | 81 | # 免許・資格 82 | licences: 83 | - 84 | year: <%= (@date << 9 * 12).year %> 85 | month: 11 86 | value: 普通自動車免許 87 | - 88 | year: <%= (@date << 5 * 12).year %> 89 | month: 11 90 | value: 英検1級 91 | 92 | 93 | # 主な受賞・表彰等 94 | awards: 95 | - 96 | year: <%= (@date << 7 * 12).year %> 97 | month: 98 | value: 第二回履歴書学会ポスター賞 99 | - 100 | year: <%= (@date << 4 * 12).year %> 101 | month: 102 | value: 第五回履歴書学会若手奨励賞 103 | 104 | # 教育歴 105 | teaching: | 106 | 「履歴書学特論」平成15年から17年まで 107 | 「PDF出力特論」平成18年から23年まで 108 | 「スタイルファイル特別演習」平成20年から29年まで 109 | 110 | # 所属学会 111 | affiliated_society: | 112 | 日本履歴書学会 113 | 神エクセル研究会 114 | 115 | # その他特記事項 116 | notices: | 117 | 着任可能時期: <%= (@date >> 12).year %>年4月1日 118 | 希望職位: 助教 119 | 120 | #通勤時間 121 | commuting_time: 1時間20分 122 | 123 | #扶養家族数(配偶者を除く) 124 | dependents: 2人 125 | 126 | #配偶者の有無 127 | spouse: 有 128 | 129 | #配偶者の扶養義務 130 | supporting_spouse: 有 131 | 132 | #趣味 133 | hobby: | 134 | 読書、音楽鑑賞 135 | 136 | #志望動機 137 | motivation: | 138 | 貴学校の研究室の設備が充実しており、過去実績も豊富しております。 139 | 是非とも貴学校で研究を行いたいと思います。 140 | 141 | # 本人希望記入欄 142 | request: | 143 | 所属研究室の規定に従います。 144 | -------------------------------------------------------------------------------- /templates/data.yaml: -------------------------------------------------------------------------------- 1 | # 名前等 2 | # date が空白の時、今日の日付(和暦)が自動で入ります 3 | # date: <%= @date.strftime("%Jf") %>現在 4 | name_kana: りれき たろう 5 | name: 履歴 太郎 6 | birth_day: <%= (@date << 25 * 12).strftime("%Jf") %> 7 | # 年齢は誕生日を基づいて計算し、下記の書式になります 8 | # <%= (@date << 25 * 12).strftime("%Jf") %> (満 25 歳) 9 | gender: 男 10 | cell_phone: 090-1234-5678 11 | email: rireki-taro@example.org 12 | # 写真をアップロードした場合、このフィールドが無視されます 13 | photo: https://cdn.jsdelivr.net/gh/jerrywdlee/yaml_cv@master/photo.png 14 | 15 | # 住所 16 | address_kana: とうきょうとちよだくちよだ 17 | address: 東京都千代田区千代田1-1-1 18 | address_zip: 100-0001 19 | tel: 03-000-000 20 | # fax: 0120-111-XXX 21 | # 連絡先 22 | # address_kana2: ほっかいどう わっかないし そうやみさき 23 | # address2: 北海道稚内市宗谷岬 24 | # address_zip2: 098-6758 25 | # tel2: 0120-222-XXX 26 | # fax2: 0120-333-XXX 27 | address2: 同上 28 | 29 | # 学歴 30 | education: 31 | - 32 | year: <%= (@date << 10 * 12).strftime("%Jy") %> 33 | month: 4 34 | value: 履歴高校普通科 入学 35 | - 36 | year: <%= (@date << 7 * 12).strftime("%Jy") %> 37 | month: 3 38 | value: 同 卒業 39 | - 40 | year: <%= (@date << 7 * 12).strftime("%Jy") %> 41 | month: 4 42 | value: 履歴大学履歴書学部 入学 43 | - 44 | year: <%= (@date << 3 * 12).strftime("%Jy") %> 45 | month: 3 46 | value: 同 卒業 47 | 48 | #職歴 49 | experience: 50 | - 51 | year: <%= (@date << 3 * 12).strftime("%Jy") %> 52 | month: 4 53 | value: 株式会社履歴書 入社 54 | - 55 | year: <%= (@date << 1 * 12).strftime("%Jy") %> 56 | month: 10 57 | value: 株式会社履歴書 退職 58 | - 59 | year: <%= (@date << 1 * 12).strftime("%Jy") %> 60 | month: 11 61 | value: 株式会社XXX 入社 62 | - 63 | value: 現在にいたる 64 | 65 | # 免許・資格 66 | licences: 67 | - 68 | year: <%= (@date << 7 * 12).strftime("%Jy") %> 69 | month: 4 70 | value: 普通自動車免許 71 | - 72 | year: <%= (@date << 7 * 12).strftime("%Jy") %> 73 | month: 11 74 | value: 英検準1級 75 | 76 | #通勤時間 77 | commuting_time: 1時間20分 78 | 79 | #扶養家族数(配偶者を除く) 80 | dependents: 0人 81 | 82 | #配偶者の有無 83 | spouse: なし 84 | 85 | #配偶者の扶養義務 86 | supporting_spouse: なし 87 | 88 | #趣味 89 | hobby: | 90 | 読書、音楽鑑賞 91 | 92 | #志望動機 93 | motivation: | 94 | 貴社の事業に共感を持ち、是非ご一緒に働きたいと思います。 95 | 96 | # 本人希望記入欄 97 | request: | 98 | 貴社の規定に従います。 99 | -------------------------------------------------------------------------------- /templates/style.txt: -------------------------------------------------------------------------------- 1 | # ヘッダー 2 | string,110mm,245mm,$date,font_size=9 3 | string,5mm,247mm,履 歴 書,font_size=14,font_face=gothic 4 | 5 | # 写真 6 | box,145mm,204mm,30mm,40mm,line_style=dashed 7 | string,148mm,240mm,写真を貼る位置,font_size=9 8 | string,147mm,233mm,1. 縦36〜40 mm,font_size=8 9 | string,150mm,230mm,横24〜30 mm,font_size=8 10 | string,147mm,227mm,2. 本人単身胸から上,font_size=8 11 | string,147mm,224mm,3. 裏面にのりづけ,font_size=8 12 | string,147mm,221mm,4. 裏面に氏名記入,font_size=8 13 | photo,145mm,244mm,30mm,40mm 14 | lines,6,0mm,240mm,139mm,0mm,0mm,-41mm,38mm,0mm,0mm,-53mm,-177mm,0mm,line_width=2.0,close=true 15 | line,0,233mm,139mm,0,line_style=dashed 16 | string,2mm,238mm,ふりがな,font_size=9 17 | string,30mm,238mm,$name_kana,font_size=9 18 | string,2mm,231mm,氏  名,font_size=9 19 | string,30mm,231mm,$name,font_size=14 20 | line,0,218mm,139mm,0mm 21 | line,15mm,218mm,0,-13mm 22 | string,1.5mm,217mm,生年月日,font_size=9 23 | string,30mm,213mm,$birth_day,font_size=12 24 | line,110mm,218mm,0mm,-13mm 25 | string,121mm,214mm,$gender,font_size=12 26 | line,21mm,205mm,0,-6mm,line_style=dashed 27 | string,1.5mm,203.5mm,携帯電話番号,font_size=9 28 | line,0,205mm,139mm,0mm 29 | string,25mm,203.5mm,$cell_phone,font_size=9 30 | line,56mm,205mm,0mm,-6mm 31 | line,69mm,205mm,0mm,-6mm,line_style=dashed 32 | string,57mm,204mm,E-MAIL,font_size=9 33 | string,75mm,204mm,$email,font_size=9 34 | line,0,205mm,139mm,0mm 35 | line,0,199mm,139mm,0mm 36 | line,0,192mm,139mm,0,line_style=dashed 37 | line,0,173mm,177mm,0 38 | line,0,166mm,139mm,0,line_style=dashed 39 | line,139mm,199mm,0mm,-53mm 40 | line,139mm,186mm,38mm,0mm,line_style=dashed 41 | string,141mm,198mm,電話,font_size=9 42 | string,143mm,192mm,$tel,font_size=9 43 | string,141mm,185mm,FAX,font_size=9 44 | string,143mm,179mm,$fax,font_size=9 45 | string,141mm,172mm,電話,font_size=9 46 | string,143mm,166mm,$tel2,font_size=9 47 | string,141mm,159mm,FAX,font_size=9 48 | string,143mm,153mm,$fax2,font_size=9 49 | line,139mm,160mm,38mm,0mm,line_style=dashed 50 | string,2mm,197mm,ふりがな,font_size=9 51 | string,20mm,197mm,$address_kana,font_size=9 52 | string,2mm,191mm,現住所 〒,font_size=9 53 | string,15mm,191mm,$address_zip,font_size=9 54 | string,15mm,185mm,$address,font_size=12 55 | string,2mm,171mm,ふりがな,font_size=9 56 | string,20mm,171mm,$address_kana2,font_size=9 57 | string,2mm,165mm,連絡先 〒,font_size=9 58 | string,15mm,165mm,$address_zip2,font_size=9 59 | string,15mm,159mm,$address2,font_size=12 60 | box,0mm,17mm,177mm,119mm,line_width=2 61 | multi_lines,0mm,24mm,177mm,0,16,0,7mm 62 | line,19mm,136mm,0,-119mm 63 | line,31mm,136mm,0,-119mm 64 | string,8mm,134mm,年,font_size=9 65 | string,24mm,134mm,月,font_size=9 66 | string,77mm,134mm,学歴・職歴(各項目ごとにまとめて書く),font_size=9 67 | education_experience,128mm,3mm,24mm,35mm,7mm,95mm,155mm,font_size=12 68 | string,1mm,14mm,記入上の注意,font_size=9 69 | string,22mm,14mm,数字はアラビア数字で、文字はくずさず正確に書く。,font_size=9 70 | new_page 71 | box,0,190mm,177mm,49mm,line_width=2 72 | line,19mm,239mm,0,-49mm 73 | line,31mm,239mm,0,-49mm 74 | multi_lines,0mm,232mm,177mm,0,6,0,-7mm 75 | string,8mm,237mm,年,font_size=9 76 | string,24mm,237mm,月,font_size=9 77 | string,90mm,237mm,免許・資格,font_size=9 78 | history,230mm,3mm,24mm,35mm,-7mm,$licences,font_size=12 79 | box,0,167mm,177mm,15mm,line_width=2 80 | string,2mm,180mm,通勤時間,font_size=9 81 | string,5mm,173mm,$commuting_time,font_size=12 82 | string,59mm,180mm,扶養家族,font_size=9 83 | string,59mm,173mm,(配偶者を除く),font_size=9 84 | string,85mm,173mm,$dependents,font_size=12 85 | string,99mm,180mm,配偶者,font_size=9 86 | string,116mm,173mm,$spouse,font_size=12 87 | string,139mm,180mm,配偶者の扶養義務,font_size=9 88 | string,155mm,173mm,$supporting_spouse,font_size=12 89 | line,57mm,167mm,0,15mm 90 | line,97mm,167mm,0,15mm 91 | line,137mm,167mm,0,15mm 92 | box,0,120mm,177mm,40mm,line_width=2 93 | string,2mm,158mm,趣味・特技,font_size=9 94 | textbox,2mm,152mm,173mm,30mm,$hobby,font_size=13 95 | box,0,73mm,177mm,40mm,line_width=2 96 | string,2mm,111mm,志望動機,font_size=9 97 | textbox,2mm,105mm,173mm,30mm,$motivation,font_size=13 98 | box,0,26mm,177mm,40mm,line_width=2 99 | string,2mm,64mm,本人希望記入欄,font_size=9 100 | textbox,2mm,58mm,173mm,30mm,$request,font_size=13 101 | -------------------------------------------------------------------------------- /views/error.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |

システムエラーが発生しました

6 |
7 |

下記のエラーが発生しました

8 |
<%= @error_msg %>
9 |
10 |
11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /views/index.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 |
7 | 17 |
18 | 20 |
21 |
22 |
23 | 30 |
31 | 33 |
34 |
35 |
36 | 39 |
40 |
41 |
42 | -------------------------------------------------------------------------------- /views/layout.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%= @title %> 8 | 9 | 10 | <%= erb :'partial/seo' %> 11 | <%= erb :'partial/cdn' %> 12 | 13 | 14 | <%= erb :'partial/header' %> 15 | <%= yield %> 16 | <%= erb :'partial/footer' %> 17 | 18 | 19 | -------------------------------------------------------------------------------- /views/partial/cdn.erb: -------------------------------------------------------------------------------- 1 | 3 | 4 | 6 | 7 | 9 | 10 | 12 | 14 | 16 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /views/partial/footer.erb: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /views/partial/header.erb: -------------------------------------------------------------------------------- 1 | 55 | 56 | -------------------------------------------------------------------------------- /views/partial/seo.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /yaml2txt.rb: -------------------------------------------------------------------------------- 1 | require "yaml" 2 | 3 | def string(h) 4 | a = [] 5 | a.push h.delete("type") 6 | a.push h.delete("x") 7 | a.push h.delete("y") 8 | a.push h.delete("value") 9 | a = a + h.collect { |k, v| "#{k}=#{v}" } 10 | puts a.join(",") 11 | end 12 | 13 | def box(h) 14 | a = [] 15 | a.push h.delete("type") 16 | a.push h.delete("x") 17 | a.push h.delete("y") 18 | a.push h.delete("width") 19 | a.push h.delete("height") 20 | a = a + h.collect { |k, v| "#{k}=#{v}" } 21 | puts a.join(",") 22 | end 23 | 24 | def photo(h) 25 | a = [] 26 | a.push h.delete("type") 27 | a.push h.delete("x") 28 | a.push h.delete("y") 29 | a.push h.delete("width") 30 | a.push h.delete("height") 31 | puts a.join(",") 32 | end 33 | 34 | def line(h) 35 | a = [] 36 | a.push h.delete("type") 37 | a.push h.delete("x") 38 | a.push h.delete("y") 39 | a.push h.delete("dx") 40 | a.push h.delete("dy") 41 | a = a + h.collect { |k, v| "#{k}=#{v}" } 42 | puts a.join(",") 43 | end 44 | 45 | def lines(h) 46 | a = [] 47 | a.push h.delete("type") 48 | points = h.delete("points") 49 | a.push points.size 50 | s = points.shift 51 | a.push s["x"] 52 | a.push s["y"] 53 | points.each do |i| 54 | a.push i["dx"] 55 | a.push i["dy"] 56 | end 57 | a = a + h.collect { |k, v| "#{k}=#{v}" } 58 | puts a.join(",") 59 | end 60 | 61 | def multi_lines(h) 62 | a = [] 63 | a.push h.delete("type") 64 | a.push h.delete("x") 65 | a.push h.delete("y") 66 | a.push h.delete("dx") 67 | a.push h.delete("dy") 68 | a.push h.delete("num") 69 | a.push h.delete("sx") 70 | a.push h.delete("sy") 71 | puts a.join(",") 72 | end 73 | 74 | def education_experience(h) 75 | a = [] 76 | a.push h.delete("type") 77 | a.push h.delete("y") 78 | a.push h.delete("year_x") 79 | a.push h.delete("month_x") 80 | a.push h.delete("value_x") 81 | a.push h.delete("dy") 82 | a.push h.delete("caption_x") 83 | a.push h.delete("ijo_x") 84 | a = a + h.collect { |k, v| "#{k}=#{v}" } 85 | puts a.join(",") 86 | end 87 | 88 | def new_page(h) 89 | puts h.delete("type") 90 | end 91 | 92 | def history(h) 93 | a = [] 94 | a.push h.delete("type") 95 | a.push h.delete("y") 96 | a.push h.delete("year_x") 97 | a.push h.delete("month_x") 98 | a.push h.delete("value_x") 99 | a.push h.delete("dy") 100 | a.push h.delete("value") 101 | a = a + h.collect { |k, v| "#{k}=#{v}" } 102 | puts a.join(",") 103 | end 104 | 105 | def textbox(h) 106 | a = [] 107 | a.push h.delete("type") 108 | a.push h.delete("x") 109 | a.push h.delete("y") 110 | a.push h.delete("width") 111 | a.push h.delete("height") 112 | a.push h.delete("value") 113 | a = a + h.collect { |k, v| "#{k}=#{v}" } 114 | puts a.join(",") 115 | end 116 | 117 | if ARGV.size == 0 118 | puts "usage: input.yaml" 119 | exit 120 | end 121 | 122 | filename = ARGV[0] 123 | 124 | y = YAML.load_file(filename) 125 | 126 | y.each do |i| 127 | send(i["type"], i) 128 | end 129 | --------------------------------------------------------------------------------