insup 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 865c49f6dd236ccef3027dc04084c735b87f525a
4
+ data.tar.gz: 97b3f43e5fa341d031b8f05e8fabfa26bd51f862
5
+ SHA512:
6
+ metadata.gz: e601f6a59cd8c163c34bc9e1448417c35db3abab1ae781f1753f40120161348a339d6299b8c736f63321c9b5e457a847ad75f6276ef1a5d76a86546d4fbf7382
7
+ data.tar.gz: 3e7e51c70057e53e8d62d6f5b0b6e9d286d1f94105ca07b7a69e4f568dc15f67958a51f1b38b97db2225cfae8dc646331a8524aa92670ca4db771f3e7fe6ba0d
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /coverage
2
+ vendor/bundle
3
+ .bundle
4
+ .irb-history
5
+ .insup
6
+ log
7
+
8
+ /.tags*
9
+ /*.sublime-*
10
+
11
+ insup-*.gem
data/.insup.template ADDED
@@ -0,0 +1,23 @@
1
+ track:
2
+ - media
3
+ - snippets
4
+ - templates
5
+
6
+ tracker:
7
+ class: git
8
+
9
+ insales:
10
+ subdomain:
11
+ api_key:
12
+ password:
13
+
14
+ uploader:
15
+ class: insales
16
+ theme_id:
17
+
18
+ ignore:
19
+ - '*.swp'
20
+ - '*.swx'
21
+ - '.*'
22
+ - 'thumbs.db'
23
+ - '.DS_Store'
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --default-path spec/lib
2
+ --color
3
+ -fd
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.1.1
data/.travis.yml ADDED
@@ -0,0 +1,10 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.1
4
+ - 1.9.3
5
+ install: "bundle install --path vendor/bundle"
6
+ cache:
7
+ directories:
8
+ - vendor/bundle
9
+ script: "bundle exec rspec"
10
+
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :test do
6
+ gem 'rspec'
7
+ gem 'coveralls'
8
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,91 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ insup (0.1.2)
5
+ activeresource (~> 4.0)
6
+ colorize (~> 0.7)
7
+ listen (~> 2.7)
8
+ match_files (~> 0.1)
9
+ trollop (~> 2.0)
10
+
11
+ GEM
12
+ remote: https://rubygems.org/
13
+ specs:
14
+ activemodel (4.1.0)
15
+ activesupport (= 4.1.0)
16
+ builder (~> 3.1)
17
+ activeresource (4.0.0)
18
+ activemodel (~> 4.0)
19
+ activesupport (~> 4.0)
20
+ rails-observers (~> 0.1.1)
21
+ activesupport (4.1.0)
22
+ i18n (~> 0.6, >= 0.6.9)
23
+ json (~> 1.7, >= 1.7.7)
24
+ minitest (~> 5.1)
25
+ thread_safe (~> 0.1)
26
+ tzinfo (~> 1.1)
27
+ builder (3.2.2)
28
+ celluloid (0.15.2)
29
+ timers (~> 1.1.0)
30
+ celluloid-io (0.15.0)
31
+ celluloid (>= 0.15.0)
32
+ nio4r (>= 0.5.0)
33
+ colorize (0.7.2)
34
+ coveralls (0.7.0)
35
+ multi_json (~> 1.3)
36
+ rest-client
37
+ simplecov (>= 0.7)
38
+ term-ansicolor
39
+ thor
40
+ diff-lcs (1.2.5)
41
+ docile (1.1.3)
42
+ ffi (1.9.3)
43
+ i18n (0.6.9)
44
+ json (1.8.1)
45
+ listen (2.7.2)
46
+ celluloid (>= 0.15.2)
47
+ celluloid-io (>= 0.15.0)
48
+ rb-fsevent (>= 0.9.3)
49
+ rb-inotify (>= 0.9)
50
+ match_files (0.1)
51
+ mime-types (2.2)
52
+ minitest (5.3.3)
53
+ multi_json (1.9.3)
54
+ nio4r (1.0.0)
55
+ rails-observers (0.1.2)
56
+ activemodel (~> 4.0)
57
+ rb-fsevent (0.9.4)
58
+ rb-inotify (0.9.4)
59
+ ffi (>= 0.5.0)
60
+ rest-client (1.6.7)
61
+ mime-types (>= 1.16)
62
+ rspec (2.14.1)
63
+ rspec-core (~> 2.14.0)
64
+ rspec-expectations (~> 2.14.0)
65
+ rspec-mocks (~> 2.14.0)
66
+ rspec-core (2.14.8)
67
+ rspec-expectations (2.14.5)
68
+ diff-lcs (>= 1.1.3, < 2.0)
69
+ rspec-mocks (2.14.6)
70
+ simplecov (0.8.2)
71
+ docile (~> 1.1.0)
72
+ multi_json
73
+ simplecov-html (~> 0.8.0)
74
+ simplecov-html (0.8.0)
75
+ term-ansicolor (1.3.0)
76
+ tins (~> 1.0)
77
+ thor (0.19.1)
78
+ thread_safe (0.3.3)
79
+ timers (1.1.0)
80
+ tins (1.1.0)
81
+ trollop (2.0)
82
+ tzinfo (1.1.0)
83
+ thread_safe (~> 0.1)
84
+
85
+ PLATFORMS
86
+ ruby
87
+
88
+ DEPENDENCIES
89
+ coveralls
90
+ insup!
91
+ rspec
data/README.md ADDED
@@ -0,0 +1,90 @@
1
+ # Insup
2
+
3
+ [![Build Status](https://travis-ci.org/httplab/insup.svg?branch=master)](https://travis-ci.org/httplab/insup)
4
+ [![Gem Version](https://badge.fury.io/rb/insup.svg)](http://badge.fury.io/rb/insup)
5
+ [![Coverage Status](https://coveralls.io/repos/httplab/insup/badge.png)](https://coveralls.io/r/httplab/insup)
6
+ [![Code Climate](https://codeclimate.com/github/httplab/insup.png)](https://codeclimate.com/github/httplab/insup)
7
+ [![Dependency Status](https://gemnasium.com/httplab/insup.svg)](https://gemnasiurm.com/httplab/insup)
8
+
9
+ ## Description
10
+
11
+ Insup Insales theme uploader.
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ gem install insup
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ### Preparing
22
+
23
+ Enter the directory you want to sync with the remote service. Then type `insup init` to initialize your working directory with **.insup** file that holds all neccessary Insup configuration.
24
+
25
+ Open **.insup** file with your favourite text editor and modify the configuration according to your needs.
26
+
27
+ ### Configuration
28
+ **.insup** file is a YAML file. Here's a list of the configuration options.
29
+
30
+ * **track** section is an array of directories which you want to track for changes. Specify locations relative to the working directory
31
+ * **ignore** section specifies an array of patterns that are used to ignore files. Files that match ignore patterns will be neither tracked nor uploaded. The patterns must be specified in Git ignore format relative to the working directory.
32
+ * **tracker** section specifies the tracker to use as well as its configuration. The tracker class is specified by the `class` option.
33
+ * **uploader** section specifies the uploader to use as well as its configuration. The uploader class is specified by the `class` option.
34
+ * **insales** secion holds information for connecting to Insales shop. To use insales features you should specify `subdomain`, `api_key` and `password` parameters.
35
+
36
+ #### Trackers
37
+
38
+ Currently there are two trackers available:
39
+
40
+ * **Git tracker** finds changes in the working directory using Git index. This tracker will operate only if the working dir is a Git repository. Select this tracker by specifying `Insup::Tracker::GitTracker` or `git` in the `class` section of the tracker configuration.
41
+ * **Simple tracker** will always consider all files in the working directory modified (except the ignored ones). Specify `Insup::Tracker::SimpleTracker` or `simple` ins the `class` section.
42
+
43
+ #### Uploaders
44
+
45
+ Currently there are two uploaders available:
46
+
47
+ * **Insales uploader** will upload files to the specified Insales theme. Specify `Insup::Uploader::Insales` or `insales` in the `class` section of the uploader configuration. Insales uploader also requires `theme_id` to be specified. Working directory should include 3 subfolders *media*, *snippets* and *templates*. The same 3 folders should be specified in the `track` sectio of .insup file.
48
+
49
+ * **Dummy uploader** will only print its operations to the console without actually uploading anything. It can be used for testing the configuration. Use by specifying `dummy` or `Insup::Uploader::Dummy` in the `class` section.
50
+
51
+ ### Operation modes
52
+
53
+ #### Listen mode
54
+
55
+ In this mode Insup will continuously listen to the changes in tracked locations and upload the changes immidiately. It will use the specified uploader, but the tracker is ignored. Listen mode will only watch files that do not match ignore patterns.
56
+
57
+ Activate listen mode by typing
58
+ ```bash
59
+ insup listen
60
+ ```
61
+ in your working directory.
62
+
63
+
64
+ #### Track mode
65
+
66
+ In this mode you can periodically check for changes and upload changed files to the remote storage. Tracker specified in the .insup file is used to detect changes.
67
+
68
+ Print working directory status accroding to the selected tracker
69
+ ```bash
70
+ insup status
71
+ ```
72
+
73
+ Upload changes
74
+ ```bash
75
+ insup commit [file1 [file2 [file3 [...]]]]
76
+ ```
77
+ Call `commit` command with the file list to upload only files specified.
78
+
79
+ ### Other commands
80
+
81
+ List all themes in the Insales shop if `insales` section is given in the .insup file:
82
+ ```bash
83
+ insup insales list-themes
84
+ ```
85
+
86
+ List files under tracked locations:
87
+ ```bash
88
+ insup list-files [--all|--ignored]
89
+ ```
90
+ Use `--all` option to list *all* files, and `--ignored` to list only ignored files. Calling this command without options will result in a list of tracked files only.
data/bin/insup ADDED
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'trollop'
4
+
5
+ require_relative '../lib/insup/version'
6
+ require_relative '../lib/insup.rb'
7
+
8
+ SUB_COMMANDS = %w(init status config commit list-locations list-files listen insales)
9
+
10
+ global_opts = Trollop::options do
11
+ version "insup #{Insup::VERSION} (c) 2014 nuinuhin@gmail.com"
12
+ banner <<-EOS
13
+ Usage:
14
+ insup status
15
+ insup commit [file1, [file2, [...]]]
16
+ insup config
17
+ insup list-files
18
+ insup listen
19
+ insup init
20
+ insup list-locations
21
+ insup insales list-themes
22
+ EOS
23
+ stop_on SUB_COMMANDS
24
+ end
25
+
26
+ cmd = ARGV.shift
27
+
28
+ if cmd.nil?
29
+ raise Trollop::HelpNeeded
30
+ end
31
+
32
+ if(!SUB_COMMANDS.include? cmd)
33
+ Trollop::die "unknown subcommand #{cmd.inspect}"
34
+ end
35
+
36
+ begin
37
+ case cmd
38
+ when 'status'
39
+ Insup.status
40
+ when 'config'
41
+ Insup.print_config
42
+ when 'list-locations'
43
+ Insup.list_locations
44
+ when 'list-files'
45
+ if ARGV.include?('--all')
46
+ Insup.list_files(mode: :all)
47
+ elsif ARGV.include?('--ignored')
48
+ Insup.list_files(mode: :ignored)
49
+ else
50
+ Insup.list_files
51
+ end
52
+ when 'commit'
53
+ Insup.commit ARGV.map{|f| Insup::TrackedFile.new(f,Insup::TrackedFile::MODIFIED)}
54
+ when 'listen'
55
+ Insup.listen
56
+ when 'init'
57
+ Insup.init(ARGV[0])
58
+ when 'insales'
59
+ cmd = ARGV.shift
60
+ case cmd
61
+ when 'list-themes'
62
+ Insup::Insales.list_themes
63
+ end
64
+ end
65
+ rescue => ex
66
+ puts ex.message
67
+ end
data/insup.gemspec ADDED
@@ -0,0 +1,29 @@
1
+ require File.expand_path("../lib/insup/version", __FILE__)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'insup'
5
+ s.version = Insup::VERSION
6
+ s.platform = Gem::Platform::RUBY
7
+ s.date = Date.today.to_s
8
+
9
+ s.summary = 'InSales theme uploader.'
10
+ s.description = 'Utility for automatic upload of InSales theme files.'
11
+ s.authors = ['HttpLab', 'Nu-hin']
12
+ s.email = 'nuinuhin@gmail.com'
13
+ s.homepage = 'https://github.com/httplab/insup'
14
+ s.license = 'MIT'
15
+
16
+ s.add_dependency 'colorize', '~> 0.7'
17
+ s.add_dependency 'trollop', '~> 2.0'
18
+ s.add_dependency 'listen', '~> 2.7'
19
+ s.add_dependency 'match_files', '~> 0.1'
20
+ s.add_dependency 'activeresource', '~> 4.0'
21
+
22
+ s.files = `git ls-files`.split("\n")
23
+ s.bindir = 'bin'
24
+ s.test_files = `git ls-files -- {spec}/*`.split("\n")
25
+ s.require_paths = ["lib"]
26
+ s.executables << 'insup'
27
+
28
+ s.post_install_message = "type 'insup help' for help"
29
+ end
@@ -0,0 +1,5 @@
1
+ module Insup::Exceptions
2
+ InsupError = Class.new(StandardError)
3
+ UploaderError = Class.new(InsupError)
4
+ NotAGitRepositoryError = Class.new(InsupError)
5
+ end
data/lib/insup/git.rb ADDED
@@ -0,0 +1,113 @@
1
+ class Insup::Git
2
+
3
+ def initialize(base)
4
+ Dir.chdir(base) do
5
+ `git status 2>&1`
6
+
7
+ if $?.exitstatus == 128
8
+ raise Insup::Exceptions::NotAGitRepositoryError, 'Not a GIT repository'
9
+ end
10
+ end
11
+
12
+ @base = base
13
+ end
14
+
15
+ def status
16
+ files = ls_files
17
+ ignore = ignored_files
18
+
19
+ Dir.chdir(@base) do
20
+ Dir.glob('**/*', File::FNM_DOTMATCH) do |file|
21
+ next if files[file] || File.directory?(file) || ignore.include?(file) || file =~ /^.git\/.+/
22
+ files[file] = {:path => file, :untracked => true}
23
+ end
24
+ end
25
+
26
+ diff_files.each do |path, data|
27
+ files[path] ? files[path].merge!(data) : files[path] = data
28
+ end
29
+
30
+ diff_index('HEAD').each do |path, data|
31
+ files[path] ? files[path].merge!(data) : files[path] = data
32
+ end
33
+
34
+ files.each do |k, file_hash|
35
+ file_hash
36
+ end
37
+
38
+ files
39
+ end
40
+
41
+ private
42
+
43
+ def ignored_files
44
+ command_lines('ls-files', ['--others', '-i', '--exclude-standard'])
45
+ end
46
+
47
+ def ls_files
48
+ hsh = {}
49
+ command_lines('ls-files', ['--stage']).each do |line|
50
+ (info, file) = line.split("\t")
51
+ (mode, sha, stage) = info.split
52
+ file = eval(file) if file =~ /^\".*\"$/
53
+ hsh[file] = {:path => file, :stage => stage}
54
+ end
55
+ hsh
56
+ end
57
+
58
+ def command_lines(cmd, opts = [])
59
+ command(cmd, opts).split("\n")
60
+ end
61
+
62
+ def command(cmd, opts = [])
63
+ opts = [opts].flatten.map {|s| escape(s) }.join(' ')
64
+ git_cmd = "git #{cmd} #{opts} 2>&1"
65
+ out = nil
66
+
67
+ if Dir.getwd != @base
68
+ Dir.chdir(@base) { out = `#{git_cmd}` }
69
+ else
70
+ out = `#{git_cmd}`
71
+ end
72
+
73
+ if $?.exitstatus > 0
74
+ if $?.exitstatus == 1 && out == ''
75
+ return ''
76
+ end
77
+
78
+ raise 'git error'
79
+ end
80
+ out
81
+ end
82
+
83
+ def diff_files
84
+ diff_as_hash('diff-files')
85
+ end
86
+
87
+ def diff_index(treeish)
88
+ diff_as_hash('diff-index', treeish)
89
+ end
90
+
91
+ def diff_as_hash(diff_command, opts=[])
92
+ command_lines(diff_command, opts).inject({}) do |memo, line|
93
+ info, file = line.split("\t")
94
+ mode_src, mode_dest, sha_src, sha_dest, type = info.split
95
+
96
+ memo[file] = {
97
+ # :mode_index => mode_dest,
98
+ # :mode_repo => mode_src.to_s[1, 7],
99
+ :path => file,
100
+ # :sha_repo => sha_src,
101
+ # :sha_index => sha_dest,
102
+ :type => type
103
+ }
104
+
105
+ memo
106
+ end
107
+ end
108
+
109
+ def escape(s)
110
+ "'#{s && s.to_s.gsub('\'','\'"\'"\'')}'"
111
+ end
112
+
113
+ end
@@ -0,0 +1,5 @@
1
+ class Insup::Insales::Asset < Insup::Insales::Base
2
+ belongs_to :theme, class_name: 'Insup::Insales::Theme'
3
+
4
+ self.prefix = '/admin/themes/:theme_id/'
5
+ end
@@ -0,0 +1,17 @@
1
+ class Insup::Insales::Base < ActiveResource::Base
2
+ self.headers['User-Agent'] = %W(
3
+ insup/#{Insup::VERSION}
4
+ ActiveResource/#{ActiveResource::VERSION::STRING}
5
+ Ruby/#{RUBY_VERSION}
6
+ ).join(' ')
7
+
8
+ self.format = :xml
9
+ self.prefix = '/admin/'
10
+
11
+ def self.configure(api_key, domain, password)
12
+ self.user = api_key
13
+ self.site = "http://#{domain}"
14
+ self.password = password
15
+ self
16
+ end
17
+ end
@@ -0,0 +1,5 @@
1
+ class Insup::Insales::Asset < Insup::Insales::Base; end
2
+
3
+ class Insup::Insales::Theme < Insup::Insales::Base
4
+ has_many :assets, class_name: 'Insup::Insales::Asset'
5
+ end
@@ -0,0 +1,33 @@
1
+ module Insup::Insales
2
+
3
+ def self.configure_api
4
+ @config = Insup::Settings.instance.insales;
5
+
6
+ return false if @config.nil?
7
+
8
+ if !@has_api
9
+ active_resource_logger = Logger.new('log/active_resource.log', 'daily')
10
+ active_resource_logger.level = Logger::DEBUG
11
+ ActiveResource::Base.logger = active_resource_logger
12
+ @has_api = ::Insup::Insales::Base.configure(@config['api_key'], @config['subdomain'], @config['password'])
13
+ end
14
+ end
15
+
16
+ def self.list_themes
17
+ configure_api
18
+ themes = Theme.all
19
+ themes.each do |theme|
20
+ puts "#{theme.id}\t#{theme.title}"
21
+ end
22
+ end
23
+
24
+ def print_config
25
+
26
+ end
27
+
28
+ end
29
+
30
+ require 'active_resource'
31
+ require_relative 'insales/base'
32
+ require_relative 'insales/theme'
33
+ require_relative 'insales/asset'
@@ -0,0 +1,84 @@
1
+ require 'listen'
2
+
3
+ class Listener
4
+
5
+ def initialize(base)
6
+ @base = base
7
+ end
8
+
9
+ def listen(&block)
10
+ return if @listener
11
+
12
+ @listener = Listen.to(tracked_locations) do |modified, added, removed|
13
+ flags = {}
14
+
15
+ added.each do |file|
16
+ flags[file] = flags[file] ? flags[file] | 4 : 4
17
+ end
18
+
19
+ modified.each do |file|
20
+ flags[file] = flags[file] ? flags[file] | 2 : 2
21
+ end
22
+
23
+ removed.each do |file|
24
+ flags[file] = flags[file] ? flags[file] | 1 : 1
25
+ end
26
+
27
+ res = []
28
+
29
+ flags.each do |f, flags|
30
+ pn = Pathname.new(f)
31
+ basepn = Pathname.new(@base)
32
+ file = pn.relative_path_from(basepn).to_s
33
+
34
+ next if ignore_matcher.matched?(file)
35
+
36
+ case flags
37
+ when 1
38
+ res << Insup::TrackedFile.new(file, Insup::TrackedFile::DELETED)
39
+ when 2
40
+ res << Insup::TrackedFile.new(file, Insup::TrackedFile::MODIFIED)
41
+ when 3
42
+ res << Insup::TrackedFile.new(file, Insup::TrackedFile::DELETED)
43
+ when 4
44
+ res << Insup::TrackedFile.new(file, Insup::TrackedFile::NEW)
45
+ when 5
46
+ if File.exist?(file)
47
+ res << Insup::TrackedFile.new(file, Insup::TrackedFile::UNSURE)
48
+ end
49
+ when 6
50
+ res << Insup::TrackedFile.new(file, Insup::TrackedFile::NEW)
51
+ when 7
52
+ if File.exist?(file)
53
+ res << Insup::TrackedFile.new(file, Insup::TrackedFile::UNSURE)
54
+ end
55
+ end
56
+ end
57
+
58
+ yield res
59
+ end
60
+
61
+ @listener.start
62
+ end
63
+
64
+ def stop
65
+ if @listener
66
+ @listener.stop
67
+ end
68
+ end
69
+
70
+ protected
71
+
72
+ def ignore_matcher
73
+ @ignore_matcher ||= ::MatchFiles.git(@base, ignore_patterns)
74
+ end
75
+
76
+ def tracked_locations
77
+ @track = ::Insup::Settings.instance.tracked_locations
78
+ end
79
+
80
+ def ignore_patterns
81
+ @ignore_patterns ||= ::Insup::Settings.instance.ignore_patterns
82
+ end
83
+
84
+ end
@@ -0,0 +1,39 @@
1
+ require 'yaml'
2
+
3
+ class Insup::Settings
4
+ attr_reader :settings
5
+
6
+ def self.instance
7
+ @instance ||= new()
8
+ end
9
+
10
+ def initialize(filename = '.insup')
11
+ insupfile = IO.read(filename)
12
+ @settings = YAML.load(insupfile)
13
+ end
14
+
15
+ def tracked_locations
16
+ return settings['track'] || []
17
+ end
18
+
19
+ def uploader
20
+ return settings['uploader']
21
+ end
22
+
23
+ def tracker
24
+ return settings['tracker']
25
+ end
26
+
27
+ def ignore_patterns
28
+ return settings['ignore'] || []
29
+ end
30
+
31
+ def insales
32
+ return settings['insales']
33
+ end
34
+
35
+ def save(filename)
36
+ File.write(filename, @settings.to_yaml)
37
+ end
38
+
39
+ end
@@ -0,0 +1,12 @@
1
+ require 'pathname'
2
+
3
+ class Insup::TrackedFile < Struct.new(:path, :state)
4
+ NEW = 0
5
+ MODIFIED = 1
6
+ DELETED = 2
7
+ UNSURE = 3
8
+
9
+ def file_name
10
+ Pathname.new(path).basename.to_s
11
+ end
12
+ end
@@ -0,0 +1,35 @@
1
+ require_relative('../git')
2
+
3
+ # Git tracker: tracks files by evaluating changes in Git repo
4
+ class Insup::Tracker::GitTracker < Insup::Tracker
5
+
6
+ tracker :git
7
+
8
+ def initialize config = nil
9
+ super
10
+ @git = ::Insup::Git.new(@path)
11
+ end
12
+
13
+ protected
14
+
15
+ STATUS_MAP = {
16
+ 'A' => Insup::TrackedFile::NEW,
17
+ 'M' => Insup::TrackedFile::MODIFIED,
18
+ 'D' => Insup::TrackedFile::DELETED
19
+ }
20
+
21
+ def raw_changes
22
+ changed = @git.status.select do |x,v|
23
+ v[:untracked] || (['A','M','D'].include? v[:type])
24
+ end
25
+
26
+ changed.map do |x, v|
27
+ if v[:untracked]
28
+ Insup::TrackedFile.new v[:path], Insup::TrackedFile::NEW
29
+ else
30
+ Insup::TrackedFile.new v[:path], STATUS_MAP[v[:type]]
31
+ end
32
+ end
33
+ end
34
+
35
+ end
@@ -0,0 +1,12 @@
1
+ # Simple tracker: will list all tracked files as changed
2
+ class Insup::Tracker::SimpleTracker < Insup::Tracker
3
+
4
+ tracker :simple
5
+
6
+ def raw_changes
7
+ tracked_files.map do |p|
8
+ Insup::TrackedFile.new(p, Insup::TrackedFile::MODIFIED)
9
+ end
10
+ end
11
+
12
+ end
@@ -0,0 +1,62 @@
1
+ require('match_files')
2
+ class Insup::Tracker
3
+
4
+ def self.tracker(tracker_alias)
5
+ @@trackers ||= {}
6
+ @@trackers[tracker_alias] = self
7
+ end
8
+
9
+ def self.find_tracker(tracker_alias)
10
+ @@trackers[tracker_alias.to_sym]
11
+ end
12
+
13
+ def initialize(config = nil)
14
+ @config = config
15
+ @path = Dir.getwd
16
+ end
17
+
18
+ # Lists ALL files in the tracked locations whether they are ignored or not
19
+ def all_files
20
+ locations = tracked_locations
21
+ locations.map do |loc|
22
+ loc_pat = File.join(loc, '**/*')
23
+ Dir.glob(loc_pat, File::FNM_DOTMATCH)
24
+ .select{|e| File.file?(e)}
25
+ end.flatten
26
+ end
27
+
28
+ # Lists ALL tracked files in the tracked locations i. e. all files but ignored
29
+ def tracked_files
30
+ all_files.reject{|f| ignore_matcher.matched?(f)}
31
+ end
32
+
33
+ # Lists ALL tracked files in the tracked locations i. e. all files but ignored
34
+ def ignored_files
35
+ all_files.select{|f| ignore_matcher.matched?(f)}
36
+ end
37
+
38
+ def changes
39
+ res = raw_changes.select do |x|
40
+ tracked_locations.any? do |loc|
41
+ !ignore_matcher.matched?(x.path) && File.fnmatch(File.join(loc,'/*'), x.path)
42
+ end
43
+ end
44
+ end
45
+
46
+ protected
47
+
48
+ def raw_changes; end;
49
+
50
+ def ignore_matcher
51
+ @ignore_matcher ||= ::MatchFiles.git(@path, ignore_patterns)
52
+ end
53
+
54
+ def tracked_locations
55
+ @track = ::Insup::Settings.instance.tracked_locations
56
+ end
57
+
58
+ def ignore_patterns
59
+ @ignore_patterns ||= ::Insup::Settings.instance.ignore_patterns
60
+ end
61
+
62
+ end
@@ -0,0 +1,24 @@
1
+ require 'colorize'
2
+
3
+ class Insup::Uploader::DummyUploader < Insup::Uploader
4
+
5
+ uploader :dummy
6
+
7
+ def upload_file(file)
8
+ case file.state
9
+ when Insup::TrackedFile::NEW
10
+ puts "Creating file #{file.path}".green
11
+ when Insup::TrackedFile::MODIFIED, Insup::TrackedFile::UNSURE
12
+ puts "Uploading file #{file.path}".yellow
13
+ end
14
+ end
15
+
16
+ def remove_file file
17
+ puts "Deleting file #{file.path}".red
18
+ end
19
+
20
+ def batch_upload(files)
21
+ puts "Batch uploading #{files.count} files"
22
+ end
23
+
24
+ end
@@ -0,0 +1,135 @@
1
+ require 'base64'
2
+ require_relative '../insales'
3
+
4
+ class Insup::Uploader::InsalesUploader < Insup::Uploader
5
+
6
+ uploader :insales
7
+
8
+ InsalesUploaderError = Class.new(Insup::Exceptions::UploaderError)
9
+
10
+ def initialize(config)
11
+ super
12
+ Insup::Insales.configure_api
13
+ assets_list(true)
14
+ end
15
+
16
+ def upload_file(file)
17
+ case file.state
18
+ when Insup::TrackedFile::NEW
19
+ upload_new_file(file)
20
+ when Insup::TrackedFile::MODIFIED
21
+ upload_modified_file(file)
22
+ when Insup::TrackedFile::UNSURE
23
+ upload_modified_file(file)
24
+ end
25
+ end
26
+
27
+ def upload_new_file(file)
28
+ asset = find_asset(file)
29
+
30
+ if !asset.nil?
31
+ upload_modified_file(file)
32
+ return
33
+ end
34
+
35
+ puts "Creating #{file.path}".green
36
+ asset_type = get_asset_type(file.path)
37
+
38
+ if(!asset_type)
39
+ raise InsalesUploaderError, "Cannot determine asset type for file #{file.path}"
40
+ end
41
+
42
+ file_contents = File.read(file.path)
43
+
44
+ asset = ::Insup::Insales::Asset.create({
45
+ name: file.file_name,
46
+ attachment: Base64.encode64(file_contents),
47
+ theme_id: @config['theme_id'],
48
+ type: asset_type
49
+ })
50
+
51
+ assets_list << asset
52
+ end
53
+
54
+ def upload_modified_file(file)
55
+ asset = find_asset(file)
56
+
57
+ if !asset
58
+ upload_new_file(file)
59
+ end
60
+
61
+ puts "Updating #{file.path}".yellow
62
+
63
+ file_contents = File.read(file.path)
64
+
65
+ if asset.content_type.start_with? 'text/'
66
+ asset.update_attribute(:content, file_contents)
67
+ else
68
+ remove_file(file)
69
+ upload_new_file(file)
70
+ end
71
+ end
72
+
73
+ def remove_file(file)
74
+ asset = find_asset(file)
75
+
76
+ if !asset
77
+ raise InsalesUploaderError, "Cannot find remote counterpart for file #{file.path}"
78
+ end
79
+
80
+ puts "Deleting #{file.path}".red
81
+ asset.destroy
82
+ assets_list.delete(asset)
83
+ end
84
+
85
+ private
86
+ ASSET_TYPE_MAP = {
87
+ 'media/' => 'Asset::Media',
88
+ 'snippets/' => 'Asset::Snippet',
89
+ 'templates/' => 'Asset::Template'
90
+ }.freeze
91
+
92
+ def get_asset_type path
93
+ res = nil
94
+
95
+ ASSET_TYPE_MAP.each do |k,v|
96
+ if path.start_with? k
97
+ res = v
98
+ break
99
+ end
100
+ end
101
+
102
+ res
103
+ end
104
+
105
+ def theme
106
+ @theme ||= ::Insup::Insales::Theme.find(@config['theme_id'])
107
+ end
108
+
109
+ def find_asset file
110
+ asset_type = get_asset_type(file.path)
111
+
112
+ if(!asset_type)
113
+ raise InsalesUploaderError, "Cannot determine asset type for file #{file.path}"
114
+ end
115
+
116
+ files = assets_list.select do |el|
117
+ el.type == asset_type && (el.human_readable_name == file.file_name || el.name == file.file_name)
118
+ end
119
+
120
+ if files && !files.empty?
121
+ return files.first
122
+ else
123
+ return nil
124
+ end
125
+ end
126
+
127
+ def assets_list(reload = false)
128
+ if !@assets_list || reload
129
+ @assets_list ||= theme.assets.to_a
130
+ else
131
+ @assets_list
132
+ end
133
+ end
134
+
135
+ end
@@ -0,0 +1,26 @@
1
+ class Insup::Uploader
2
+
3
+ def self.uploader(uploader_alias)
4
+ @@uploaders ||= {}
5
+ @@uploaders[uploader_alias] = self
6
+ end
7
+
8
+ def self.find_uploader(uploader_alias)
9
+ @@uploaders[uploader_alias.to_sym]
10
+ end
11
+
12
+ def initialize config = nil
13
+ @config = config
14
+ end
15
+
16
+ def upload_file(file); end
17
+
18
+ def delete_file(file); end
19
+
20
+ def batch_upload(files)
21
+ files.each do |file|
22
+ upload_file(file)
23
+ end
24
+ end
25
+
26
+ end
@@ -0,0 +1,3 @@
1
+ module Insup
2
+ VERSION = '0.1.2'
3
+ end
data/lib/insup.rb ADDED
@@ -0,0 +1,119 @@
1
+ require 'colorize'
2
+ require 'fileutils'
3
+
4
+ module Insup
5
+
6
+ # Initializes a directory with a default .insup file
7
+ def self.init(dir = nil)
8
+ dir ||= Dir.getwd
9
+
10
+ template_file = File.join(File.dirname(File.expand_path(__FILE__)), '../.insup.template')
11
+ FileUtils.cp(template_file, File.join(dir, '.insup'))
12
+ end
13
+
14
+ def self.list_locations
15
+ puts Insup::Settings.instance.tracked_locations
16
+ end
17
+
18
+ def self.list_files(options = {})
19
+ files = case options[:mode]
20
+ when nil
21
+ tracker.tracked_files
22
+ when :all
23
+ tracker.all_files
24
+ when :ignored
25
+ tracker.ignored_files
26
+ end
27
+
28
+ puts files
29
+ end
30
+
31
+ def self.uploader
32
+ @uploader ||= if uploader_conf = Settings.instance.uploader
33
+ klass = Insup::Uploader.find_uploader(uploader_conf['class']) || Object::const_get(uploader_conf['class'])
34
+ klass.new(uploader_conf)
35
+ else
36
+ Uploader::Dummy.new({})
37
+ end
38
+ end
39
+
40
+ def self.tracker
41
+ @tracker ||= if tracker_conf = Settings.instance.tracker
42
+ klass = Insup::Tracker.find_tracker(tracker_conf['class']) || Object::const_get(tracker_conf['class'])
43
+ klass.new(tracker_conf)
44
+ else
45
+ Tracker::SimpleTracker.new({})
46
+ end
47
+ end
48
+
49
+ def self.commit(changed_files = nil)
50
+ begin
51
+ changed_files ||= get_changes
52
+ uploader = get_uploader
53
+ uploader.process_all changed_files
54
+ rescue => ex
55
+ puts ex
56
+ end
57
+ end
58
+
59
+ def self.status
60
+ tracker.changes.each do |x|
61
+ case x.state
62
+ when Insup::TrackedFile::NEW
63
+ puts "New: #{x.path}".green
64
+ when Insup::TrackedFile::MODIFIED
65
+ puts "Modified: #{x.path}".yellow
66
+ when Insup::TrackedFile::DELETED
67
+ puts "Deleted: #{x.path}".red
68
+ end
69
+ end
70
+ end
71
+
72
+ def self.print_config
73
+ puts "Tracker: #{Insup::Settings.instance.tracker['class']}"
74
+ puts "Uploader: #{Insup::Settings.instance.uploader['class']}"
75
+ puts 'Tracked locations:'
76
+ puts Settings.instance.tracked_locations.map{|loc| "\t#{loc}"}
77
+ puts 'Ignore patterns:'
78
+ puts Settings.instance.ignore_patterns.map{|ip| "\t#{ip}"}
79
+ end
80
+
81
+ def self.listen
82
+ puts 'Listening...'
83
+ listener = Listener.new(Dir.getwd)
84
+
85
+ listener.listen do |changes|
86
+ changes.each do |change|
87
+ case change.state
88
+ when Insup::TrackedFile::DELETED
89
+ uploader.remove_file(change)
90
+ else
91
+ uploader.upload_file(change)
92
+ end
93
+ end
94
+ end
95
+
96
+ exit_requested = false
97
+ Kernel.trap( "INT" ) { exit_requested = true }
98
+
99
+ while !exit_requested do
100
+ sleep 0.1
101
+ end
102
+
103
+ puts 'Stopping listener...'
104
+ listener.stop
105
+ puts 'Terminated by user'
106
+ end
107
+
108
+ end
109
+
110
+ require_relative 'insup/exceptions.rb'
111
+ require_relative 'insup/settings.rb'
112
+ require_relative 'insup/tracked_file.rb'
113
+ require_relative 'insup/tracker.rb'
114
+ require_relative 'insup/tracker/git_tracker.rb'
115
+ require_relative 'insup/tracker/simple_tracker.rb'
116
+ require_relative 'insup/listener.rb'
117
+ require_relative 'insup/uploader.rb'
118
+ require_relative 'insup/uploader/dummy_uploader.rb'
119
+ require_relative 'insup/uploader/insales_uploader.rb'
@@ -0,0 +1,7 @@
1
+ require_relative '../../spec_helper'
2
+
3
+ describe 'Insup::Git' do
4
+ it 'should get status' do
5
+ expect{git = Insup::Git.new(Dir.getwd)}.not_to raise_error
6
+ end
7
+ end
@@ -0,0 +1,24 @@
1
+ require_relative '../../spec_helper'
2
+
3
+ describe 'Insup::Settings' do
4
+ before(:each) do
5
+ @settings = Insup::Settings.new('spec/support/.insup')
6
+ end
7
+
8
+ it 'should have correct tracked locations' do
9
+ expect(['media', 'templates', 'snippets'] - @settings.tracked_locations).to be_empty
10
+ end
11
+
12
+ it 'should have correct tracker' do
13
+ expect(@settings.tracker['class']).to eq ('Insup::Tracker::GitTracker')
14
+ end
15
+
16
+ it 'should have correct uploader' do
17
+ expect(@settings.uploader['class']).to eq ('Insup::Uploader::InsalesUploader')
18
+ end
19
+
20
+ it 'should have correct ignore paths' do
21
+ expect(@settings.ignore_patterns).to include('*.swp')
22
+ end
23
+
24
+ end
@@ -0,0 +1,9 @@
1
+ require_relative '../../spec_helper'
2
+
3
+ describe 'Insup::Tracker' do
4
+
5
+ before(:each) do
6
+ @tracker = Insup::Tracker.new()
7
+ end
8
+
9
+ end
@@ -0,0 +1,5 @@
1
+ require_relative '../../../spec_helper'
2
+
3
+ describe 'Insup::Uploader::InsalesUploader' do
4
+
5
+ end
@@ -0,0 +1,5 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe 'Insup' do
4
+
5
+ end
@@ -0,0 +1,5 @@
1
+ require 'coveralls'
2
+
3
+ Coveralls.wear!
4
+
5
+ require_relative('../lib/insup')
@@ -0,0 +1,18 @@
1
+ track:
2
+ - media
3
+ - snippets
4
+ - templates
5
+
6
+ tracker:
7
+ class: Insup::Tracker::GitTracker
8
+
9
+ uploader:
10
+ class: Insup::Uploader::InsalesUploader
11
+ subdomain: subdomain.myinsales.ru
12
+ password: password
13
+ api_key: apikey
14
+
15
+ ignore:
16
+ - '*.swp'
17
+ - '*.swx'
18
+ - '.*'
metadata ADDED
@@ -0,0 +1,149 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: insup
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - HttpLab
8
+ - Nu-hin
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-05-04 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: colorize
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '0.7'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '0.7'
28
+ - !ruby/object:Gem::Dependency
29
+ name: trollop
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '2.0'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '2.0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: listen
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '2.7'
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '2.7'
56
+ - !ruby/object:Gem::Dependency
57
+ name: match_files
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: '0.1'
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '0.1'
70
+ - !ruby/object:Gem::Dependency
71
+ name: activeresource
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: '4.0'
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '4.0'
84
+ description: Utility for automatic upload of InSales theme files.
85
+ email: nuinuhin@gmail.com
86
+ executables:
87
+ - insup
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - ".gitignore"
92
+ - ".insup.template"
93
+ - ".rspec"
94
+ - ".ruby-version"
95
+ - ".travis.yml"
96
+ - Gemfile
97
+ - Gemfile.lock
98
+ - README.md
99
+ - bin/insup
100
+ - insup.gemspec
101
+ - lib/insup.rb
102
+ - lib/insup/exceptions.rb
103
+ - lib/insup/git.rb
104
+ - lib/insup/insales.rb
105
+ - lib/insup/insales/asset.rb
106
+ - lib/insup/insales/base.rb
107
+ - lib/insup/insales/theme.rb
108
+ - lib/insup/listener.rb
109
+ - lib/insup/settings.rb
110
+ - lib/insup/tracked_file.rb
111
+ - lib/insup/tracker.rb
112
+ - lib/insup/tracker/git_tracker.rb
113
+ - lib/insup/tracker/simple_tracker.rb
114
+ - lib/insup/uploader.rb
115
+ - lib/insup/uploader/dummy_uploader.rb
116
+ - lib/insup/uploader/insales_uploader.rb
117
+ - lib/insup/version.rb
118
+ - spec/lib/insup/git_spec.rb
119
+ - spec/lib/insup/settings_spec.rb
120
+ - spec/lib/insup/tracker_spec.rb
121
+ - spec/lib/insup/uploader/insales_uploader_spec.rb
122
+ - spec/lib/insup_spec.rb
123
+ - spec/spec_helper.rb
124
+ - spec/support/.insup
125
+ homepage: https://github.com/httplab/insup
126
+ licenses:
127
+ - MIT
128
+ metadata: {}
129
+ post_install_message: type 'insup help' for help
130
+ rdoc_options: []
131
+ require_paths:
132
+ - lib
133
+ required_ruby_version: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ required_rubygems_version: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ requirements: []
144
+ rubyforge_project:
145
+ rubygems_version: 2.2.2
146
+ signing_key:
147
+ specification_version: 4
148
+ summary: InSales theme uploader.
149
+ test_files: []